Subversion Repositories display

Rev

Rev 289 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2009.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package test.display;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Standalone program to display an MDR file.  There is no freely available
 * knowledge of the file format.
 *
 * Can produce a massive file, so may take some time to write.
 *
 * @author Steve Ratcliffe
 */

@SuppressWarnings({"UnusedDeclaration"})
public class MdrDisplay extends CommonDisplay {
        private static final int NSECT = 41;

        // Sections to be displayed
        private final boolean[] wanted = new boolean[NSECT];

        private int numberOfMaps;       // The number of maps

        private boolean compressedStrings;
        private int cityPtrSize;
        private Charset charSet;
        private Map<Integer, String> stringTable;
        private int start;
        private int record;

        protected void print() {
                readCommonHeader();

                printHeader();

                Section end = new Section("END OF FILE", filelen, 0);
                addSection(end);
                analyze(outStream);

                /*
                 * Uses reflection to find a method to print a section.  If
                 * none is found then a generic method is used to print the
                 * section
                 */

                for (int i = 1; i < numberOfSections(); i++) {
                        if (!want(i))
                                continue;
                        try {
                                Class<MdrDisplay> c = MdrDisplay.class;
                                Method method = c.getDeclaredMethod("printSect" + i);
                                method.invoke(this);
                        } catch (NoSuchMethodException e) {
                                printSectUnknown(i);
                        } catch (InvocationTargetException e) {
                                Throwable targetException = e.getTargetException();
                                System.out.printf("Failed in section %d: %s\n", i, targetException);
                        } catch (IllegalAccessException e) {
                                e.printStackTrace();
                        }
                }
        }

        /**
         * Prints an unknown section.  If there is a record size then each
         * record is printed separately.
         */

        private void printSectUnknown(int n) {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR " + n + " (unknown)");

                Section s = getSection(n);
                d.setSectStart(s.getStart());
                reader.position(s.getStart());
                if (s.getRecordSize() == 0)
                        d.rawValue(s.getLen());
                else {
                        int recno = 0;
                        while (reader.position() < s.getEnd()) {
                                d.rawValue(s.getRecordSize(), "record " + recno++);
                                recordPrint(d);
                        }
                }
                d.print(outStream);
        }

        public void setStart(int start) {
                this.start = start;
        }

        class MapInfo {
                int mapIndex;
                int mapid;
                int offset;
                int sublen;
        }

        private void printSect1() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 1 (maps)");

                Section s = getSection(1);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                List<MapInfo> maps = new ArrayList<MapInfo>();
                int index = 1;
                while (reader.position() < s.getEnd()) {
                        d.item().addText("Map %d", index);
                        MapInfo mi = new MapInfo();
                        mi.mapIndex = index++;
                        mi.mapid = d.intValue("mapname %d");
                        if (recsize > 4)
                                mi.offset = d.intValue("offset to subsection %x");
                        maps.add(mi);
                }

                MapInfo mi = new MapInfo();
                mi.offset = (int) s.getEnd();
                maps.add(mi);

                d.print(outStream);

                printSect1Mapsect(maps);
        }

        /**
         * Contains a set of pointers and sizes elsewhere in the file.
         * The lengths are I guess a number of items as they
         * need to be multiplied by a number to get the true length as
         * calculated by looking at the start offsets.
         */

        private void printSect1Mapsect(List<MapInfo> maps) {
                for (int n = 0; n < maps.size()-1; n++) {
                        MapInfo mi = maps.get(n);
                        int suboff = mi.offset;
                        if (suboff == 0)
                                continue;

                        mi.sublen = maps.get(n + 1).offset - suboff;
                        Mdr1SubFileDisplay subFileDisplay = new Mdr1SubFileDisplay(reader, outStream, mi);
                        subFileDisplay.print();
                }
        }

        /**
         * A list of POI types.
         */

        private void printSect4() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 4 (types)");

                Section s = getSection(4);

                int recsize = s.getRecordSize();
                d.setSectStart(s.getStart());

                reader.position(s.getStart());
                while (reader.position() < s.getEnd()) {
                        int record = reader.get3();
                        DisplayItem item = d.item();
                        item.setBytes3(record);
                        item.addText("Type 0x%04x.  Unknown 0x%x",
                                        ((record >> 16) & 0xff) + ((record & 0xff) << 8),
                                        (record >> 8) & 0xff);
                }
                d.print(outStream);
        }

        /**
         * A list of cities.  It is alphabetically arranged.
         */

        private void printSect5() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 5 (cities)");

                Section s = getSection(5);

                int recsize = s.getRecordSize();
                d.setSectStart(s.getStart());

                int cityPtrSize = (s.getMagic() & 0x3) + 1;
                boolean hasRegion = (s.getMagic() & 0x4) != 0;
                boolean hasStr = (s.getMagic() & 0x8) != 0;
                boolean has20 = (s.getMagic() & 0x100) != 0;
                boolean has20offset = (s.getMagic() & 0x800) != 0;
                int mdr20PointerSize = 0;
                if (has20)
                        mdr20PointerSize = getSection(20).getBytesForRecords();

                if (has20offset)
                        mdr20PointerSize = getSection(20).getBytesForSize();

                boolean has28_29_offset = (s.getMagic() & 0x400) != 0;
                int mdr28_29PointerSize = 0;
                if (has28_29_offset)
                        mdr28_29PointerSize = getSection(28).getBytesForRecords();
                if (has28_29_offset && mdr20PointerSize == 0)
                        mdr28_29PointerSize = getSection(29).getBytesForRecords();

                //boolean has44 = (s.getMagic() & 0x2000) != 0;
                //int mdr44PointerSize = getSection(44).getBytesForRecords();
                reader.position(s.getStart());
                int citynum = 0;
                while (reader.position() < s.getEnd()) {
                        int start = (int) reader.position();

                        d.gap();
                        d.item().addText("City index %d", ++citynum);

                        printMapIndex(d);

                        putValue(d, "the %d city in the map", cityPtrSize);

                        DisplayItem item = d.int3Item();
                        item.addText("offset in LBL 0x%06x", item.getValue() & ~0x800000);

                        if (hasRegion) {
                                item = d.charItem();
                                item.addText("region %d", item.getValue() & 0x3fff);
                        }

                        if (hasStr)
                                printTextLabel(d);

                        if (has20 || has20offset)
                                d.intValue(mdr20PointerSize, "mdr20 %d");

                        if (has28_29_offset)
                                d.intValue(mdr28_29PointerSize, "mdr28/29 %d");

                        int remain = (int) (start + s.getRecordSize() - reader.position());
                        if (remain > 0)
                                d.rawValue(remain);

                        recordPrint(d);
                }

                d.print(outStream);
        }

        private void recordPrint(Displayer d) {
                d.print((++record < start)? null: outStream);
                d.setTitle(null);
        }

        protected void putValue(Displayer d, String text, int n) {
                switch (n) {
                case 1: d.byteValue(text); break;
                case 2: d.charValue(text); break;
                case 3: d.int3Value(text); break;
                case 4: d.intValue(text); break;
                }
        }

       
        /**
         * A list of zips.  It is alphabetically arranged.
         */

        private void printSect6() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 6 (ZIPs)");

                Section s = getSection(6);

                int recsize = s.getRecordSize();
                d.setSectStart(s.getStart());

                int zipPtrSize = (s.getMagic() & 0x3) + 1;
                d.item().addText("Zip size: "+zipPtrSize);
        boolean hasString = (s.getMagic() & 0x4) != 0;

                reader.position(s.getStart());
                int zipnum = 0;
                while (reader.position() < s.getEnd()) {
                        d.item().addText("zip index %d", ++zipnum);
                        printMapIndex(d);
                        putValue(d, "the %d zip in the map", zipPtrSize);
                        if (hasString)
                printTextLabel(d);
                }

                d.print(outStream);
        }
       
        /**
         * Contains an ordered list of pointers to street names.
         */

        private void printSect7() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 7 (street names)");

                Section s = getSection(7);
                int magic = s.getMagic();

                boolean hasStr = (magic & 0x01) != 0;
                boolean hasUnk1 = (magic & 0x20) != 0;
                //boolean hasUnk2 = (magic & 0x40) != 0;
                //int unk2size = (magic >> 7) & 0x3;
                // cpreview has something equivalent to:
                int unk2size = (magic >> 6) & 0x3;

                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();
                reader.position(s.getStart());
        int count = 1;
                while (reader.position() < s.getEnd()) {
            DisplayItem item = d.item();
            item.addText("Record %d", count++);
            long itemStart = reader.position();

            printMapIndex(d);

            item = d.int3Item();
                        int off = item.getValue();
                        item.addText("Pointer back into LBL: %06x", off & ~0x800000);
                        if ((off & 0x800000) == 0)
                                item.addText("Repeated name");

                        if (hasStr)
                                printTextLabel(d);
                        if (hasUnk1)
                                d.byteValue("name offset %d");

                        d.intValue(unk2size, "unk2 %d");
                       
                        d.rawValue((int) (itemStart + recsize - reader.position()));
                        d.gap();
                        recordPrint(d);
                }

                d.print(outStream);
        }

        private void printSect8() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 8 (index into streets)");
        print8_12(d, 8);
        }

        private void printSect9() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 9 (Index into mdr10, grouped based on type)");

                Section s = getSection(9);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();
                reader.position(s.getStart());

                while (reader.position() < s.getEnd()) {
                        d.byteValue("group %d");
                        DisplayItem item = d.item();

                        int c;
                        if (recsize == 3)
                                c = item.setBytes(reader.getChar());
                        else
                                c = item.setBytes3(reader.get3());
                        item.addText("record in MDR10 %d", c);
                }

                d.print(outStream);
        }

        /**
         * POI types.  Also has a pointer into MDR 11.  Seems like a waste.
         */

        private void printSect10() {
                // Looks variable length
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 10 (POI types)");

                Section s = getSection(10);
                d.setSectStart(s.getStart());

                reader.position(s.getStart());
                int count = 1;
                while (reader.position() < s.getEnd()) {
                        d.byteValue("type 0x%x");
                        DisplayItem item = d.item();

                        // Calculate record size by determining how many points there are in MDR11
                        int nPoints = getSection(11).getNumberOfRecords();

                        int c;
                        boolean isRepeat = true;

                        if (nPoints < 0x80) {
                                c = item.setBytes(reader.get()) & 0xff;
                                if ((c & 0x80) != 0) {
                                        isRepeat = false;
                                        c &= ~0x80;
                                }
                        } else if (nPoints < 0x8000) {
                                c = item.setBytes(reader.getChar()) & 0xffff;
                                if ((c & 0x8000) != 0) {
                                        isRepeat = false;
                                        c &= ~0x8000;
                                }
                        } else {
                                c = item.setBytes3(reader.get3());
                                if ((c & 0x800000) != 0) {
                                        isRepeat = false;
                                        c &= ~0x800000;
                                }
                        }
                        item.addText("[%d] rec in MDR11 %d %s", count++, c, isRepeat? "(repeated name)": "");
                }

                d.print(outStream);
        }

        /**
         * Show a city ref
         */

        private void printSect11_City(Displayer d) {
                // if this is a city there is a reference to it.
                DisplayItem item = d.item();
                int mask;
                int c;
                switch (cityPtrSize) {
                case 3:
                        c = item.setBytes3(reader.get3());
                        mask = 0x800000;
                        break;
                default: // There is a min size of 2
                        c = item.setBytes(reader.getChar());
                        mask = 0x8000;
                        break;
                }
                if (c > 0) {
                        if ((c & mask) == 0)
                                item.addText("Region %d (in map)", c);
                        else
                                item.addText("City %d (MDR5)", c & ~mask);
                }
        }

        /**
         * Handle section 11, mainlength 8
         */

        private void printSect11_8(Displayer d) {
                d.rawValue(6);
                printSect11_City(d);
        }

        /**
         * These records contain the map the subdivision and the point (or line?) number
         * and the offset in LBL.  If it has an associated city (or is a city itself), there is
         * a reference back into MDR 5.
         */

        private void printSect11() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 11 (POIs)");

                Section s = getSection(11);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize() - mapIndexSize();

                d.item().addText("section flags %x", s.getMagic());

                reader.position(s.getStart());
                int count = 1;
                while (reader.position() < s.getEnd()) {
                        int startPos = (int) reader.position();
                        // records in sect 1 appear to point here
                        d.gap();
                        d.item().addText("record %d", count++);

                        // The first values are always present.
                        printMapIndex(d);
                        d.byteValue("Point number %d");
                        d.charValue("In subdiv %d");
                        d.int3Value("LBL offset %06x");

                        if (recsize >= 8)
                                printSect11_City(d);
                        if (recsize > 10)
                                printTextLabel(d);

                        int remain = (int) (s.getRecordSize() - (reader.position() - startPos));
                        if (remain > 0)
                                d.rawValue(remain);

                        recordPrint(d);
                }

                d.print(outStream);
        }

    private void printSect12() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 12 (index into POI)");

        print8_12(d, 12);
        }

    private void print8_12(Displayer d, int sectNumber) {
                // Get the number of records in the previous section
        Section s = getSection(sectNumber-1);
                int sizePrev = s.getBytesForRecords();

                s = getSection(sectNumber);
        d.setSectStart(s.getStart());
        int recsize = s.getRecordSize();
                int flags = s.getMagic();
        reader.position(s.getStart());
        while (reader.position() < s.getEnd()) {
                        long start = reader.position();
                       
            d.stringValue((flags>>8), "Prefix:%s");
                        d.intValue(sizePrev, "record %d");

                        d.rawValue((int) (recsize - (reader.position() - start)));
        }

        d.print(outStream);
    }

    /**
         * This is an ordered list containing pointers into the text strings,
         * probably for regions, arranged per map.
         */

        private void printSect13() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 13 (regions)");

                Section s = getSection(13);
                d.setSectStart(s.getStart());

                reader.position(s.getStart());

                int record = 0;
                while (reader.position() < s.getEnd()) {
                        int start = (int) reader.position();
                        d.item().addText("Region %d", ++record);
                        printMapIndex(d);
                        d.charValue("number in map %d");
                        d.charValue("country %x");
                        printTextLabel(d);
                        int remain = (int) (start+s.getRecordSize() - reader.position());
                        if (remain > 0)
                                d.rawValue(remain);
                        d.gap();
                }

                d.print(outStream);
        }

        /**
         * Countries for each map.
         */

        private void printSect14() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 14 (country)");

                Section s = getSection(14);
                d.setSectStart(s.getStart());

                reader.position(s.getStart());
                while (reader.position() < s.getEnd()) {
                        int start = (int) reader.position();
                        printMapIndex(d);
                        d.charValue("Number in map %d");

                        // probably country...
                        printTextLabel(d);
                        int remain = (int) ((start + s.getRecordSize()) - reader.position());
                        if (remain > 0)
                                d.rawValue(remain);
                }
                d.print(outStream);
        }

        /**
         * This is the actual text strings that are being indexed.
         * There are two formats possible, this is the simple straightforward
         * one.  There is an alternate one that is compressed in some way.
         */

        private void printSect15() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 15 (strings)");

                Section s = getSection(15);
                long start = s.getStart();
                long end = s.getEnd();
                d.setSectStart(start);
                reader.position(start);

                if (compressedStrings) {
                        // don't currently know this
                        d.rawValue((int) (end - start));
                } else {
                        // If we have already read the strings then don't bother to do it all again.
                        // If you want to see the string table then just view it separately.
                        if (stringTable != null) {
                                d.item().addText("Strings omitted, call with just 15 on the command line to see");
                        } else {
                                int count = 0;
                                while (reader.position() < end) {
                                        d.zstringValue("text: %s");
                                        count++;
                                }
                                d.item().addText("%d records", count);
                        }
                }
                d.print(outStream);
        }

        /**
         * MDR 17: String table
         *
         * My personal guess is, that this is a search table for
         * sub strings starting with the given short string
         */

        private void printSect17() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 17 (indexes based on initial sub strings)");

                Section s = getSection(17);
                d.setSectStart(s.getStart());

                reader.position(s.getStart());
                while (reader.position() < s.getEnd()) {

                        int peek = 0xff & (int) d.byteValue("Peek");
                        DisplayItem item = d.item();
                        int partlength = 0;
                        if ((peek & 1) == 1)
                        {
                                partlength = peek;
                                partlength >>= 1;
                        }
                        if ((peek & 3) == 2)
                        {
                                partlength = 0xff & (int) reader.get();
                                item.setBytes((byte) partlength);
                                partlength <<= 8;
                                partlength += peek;
                                partlength >>= 2;
                        }
                        if ((peek & 7) == 4)
                        {
                                partlength = 0xffff & (int)reader.getChar();
                                item.setBytes((char) partlength);
                                partlength <<= 8;
                                partlength += peek;
                                partlength >>= 3;
                        }
                        item.addText("Length of following part: %d", partlength);

                        peek = -1;
                        if (partlength > 2) {
                item = d.charItem();
                peek = item.getValue();
                                item.addText("Some header: 0x%04x", peek);
                                partlength -= 2;
                        }

            // This can't be the whole story. Header flags from other sections help determine?
            int strLen = (peek >> 8) + 1;
            int indLen = peek & 0xff;
            if (strLen == 4)
                indLen -= 0x10;
            indLen = (indLen-9)/10 + 1;

            item.addText("str len %d, index len %d", strLen, indLen);
            partlength /= strLen + indLen;
            int record = 0;
            for (int i = 0; i < partlength; i++) {
                record++;
                d.stringValue(strLen, String.format("[%d] str: %%s", record));
                d.intValue(indLen, "Index?  %d");
            }
                }

                d.print(outStream);
        }

        /**
         * MDR 18: Looks like a mapping to MDR 11 or MDR 19
         */

        private void printSect18() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 18 (poi type index into 19)");

                Section s = getSection(18);
                d.setSectStart(s.getStart());

        int size19 = getSection(19).getBytesForRecords();

                reader.position(s.getStart());
                while (reader.position() < s.getEnd()) {
            DisplayItem item = d.charItem();
            int type = item.getValue();
            item.addText("Type: %02x/%02x", (type>>5) & 0xff, type & 0x1f);
                        d.intValue(size19, "Idx into MDR 19: %d");
                }

                d.print(outStream);
        }

        private void printSect19() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 19 (poi sorted by type)");

                Section s = getSection(19);
                d.setSectStart(s.getStart());
        int poiSize = getSection(11).getBytesForRecords();

                reader.position(s.getStart());
                int count = 1;
                while (reader.position() < s.getEnd()) {
                        DisplayItem item = d.intItem(poiSize);
            int val = item.getValue();
                        item.addText("record %d: poi index %d", count++, val & 0x7fff);
                }

                d.print(outStream);
        }

        private void printSect20() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 20 (streets by city)");

                Section s = getSection(20);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        if (recsize == 8) {
                                d.byteValue("u1 %d");
                                DisplayItem item = d.int3Item();
                                int val = item.getValue();
                                item.addText("u2 %d%s", val&0x7fffff, (val&0x800000) !=0? " F": "");
                                d.intValue("u3 %d");
                        } else if (recsize == 5) {
                                d.byteValue("map %d");
                                DisplayItem item = d.int3Item();
                                int val = item.getValue();
                                item.addText("lbl 0x%06x%s", val&0x7fffff, (val&0x800000) !=0? " F": "");
                                d.byteValue("flag %d");
                        } else {
                                DisplayItem item = d.intItem(recsize);
                                int v = item.getValue();

                                // Print a message whenever the id is less than the previous one. Not completely
                                // foolproof, but mostly shows where the boundaries are.  By counting the groups
                                // you can guess what is pointing inward and work from there.
                                int id = v >> 1;
                                if (id < lastid)
                                        item.addText("# New group %d", count++);
                                lastid = id;
                                item.addText("street %d", id);
                                item.addText("flag %b", (v & 1) == 1);
                        }

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect21() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 21 (streets by region)");

                Section s = getSection(21);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        DisplayItem item = d.intItem(recsize);
                        int v = item.getValue();
                        int id = v >> 1;
                        if (id < lastid)
                                item.addText("# New group %d", count++);
                        lastid = id;
                        item.addText("street %d", id);
                        item.addText("flag %b", (v & 1) == 1);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect22() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 22 (streets by country)");

                Section s = getSection(22);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        DisplayItem item = d.intItem(recsize);
                        int v = item.getValue();
                        int id = v >> 1;
                        if (id < lastid)
                                item.addText("# New group %d", count++);
                        lastid = id;
                        item.addText("street %d", id);
                        item.addText("flag %b", (v & 1) == 1);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect23() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 23 (sorted regions)");

                Section s = getSection(23);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        printMapIndex(d);

                        d.charValue("region in tile %d");
                        d.charValue("country in tile %d");

                        DisplayItem item = d.int3Item();
                        int value = item.getValue();
                        item.addText("lbl offset %x", value & 0x7fffff);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect24() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 24 (sorted countries)");

                Section s = getSection(24);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        printMapIndex(d);

                        d.charValue("country in tile %d");

                        DisplayItem item = d.int3Item();
                        int value = item.getValue();
                        item.addText("lbl offset %x", value & 0x7fffff);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect25() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 25 (cities by country)");

                Section s = getSection(25);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        DisplayItem item = d.intItem(recsize);
                        int v = item.getValue();
                        if (v < lastid)
                                item.addText("# New group %d", count++);
                        lastid = v;
                        item.addText("city %d", v);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect26() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 26 (mdr28 ordered by country)");

                Section s = getSection(26);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = Integer.MAX_VALUE;
                long start;
                int[] max = new int[1];
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);

                        DisplayItem item = d.charItem();
                        int m = item.getValue();
                        if (m < lastid)
                                d.item().addText("New group %d", count++);
                        lastid = m;
                        item.addText("m28 record %d", m);
                        if (m > max[0]) max[0] = m;

                        printRemainder(d, recsize, start);
                }
                for (int i : max)
                        d.item().addText("max %d", i);
                d.print(outStream);
        }


        private void printSect27() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 27 (cities by region?)");

                Section s = getSection(27);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);
                        DisplayItem item = d.intItem(recsize);
                        int v = item.getValue();
                        if (v < lastid)
                                item.addText("# New group %d", count++);
                        lastid = v;
                        item.addText("city %d", v);

                        printRemainder(d, recsize, start);
                }

                d.print(outStream);
        }

        private void printSect28() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 28 (region names with pointers)");

                Section s = getSection(28);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                int size21 = getSection(21).getBytesForRecords();
                int size23 = getSection(23).getBytesForRecords();
                int size27 = getSection(27).getBytesForRecords();
                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                int[] max = new int[4];
                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);

                        // These will not work on all maps, until the sizes are correct
                        int m = d.intValue(size23, "mdr23 record %d");
                        if (m > max[0]) max[0] = m;

                        printTextLabel(d);

                        m = d.intValue(size21, "mdr21 record %d");
                        if (m > max[2]) max[2] = m;

                        m = d.intValue(size27, "mdr27 record %d");
                        if (m > max[3]) max[3] = m;

                        printRemainder(d, recsize, start);
                }

                for (int m : max)
                        d.item().addText("max %d", m);
                d.print(outStream);
        }

        private void printSect29() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 29 (country names with pointers)");

                Section s = getSection(29);
                int magic = s.getMagic();

                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();

                boolean hasStr = (magic & 0x1) != 0; // guessed
                boolean has17 = (magic & 0x30) != 0;
        boolean has26 = (magic & 0x8) != 0;

        int size24 = getSection(24).getBytesForRecords();
                int size22 = getSection(22).getBytesForRecords();
                int size25 = getSection(25).getBytesForRecords();
        int size26 = (has26)? getSection(26).getBytesForRecords() : 0;
        int size17 = (has17)? (magic & 0x30) >> 4 : 0;

                reader.position(s.getStart());
                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                int[] max = new int[5];

                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Record %d", record++);

                        int m = d.intValue(size24, "mdr24 record %d");
                        if (m > max[0]) max[0] = m;

                        if (hasStr)
                                printTextLabel(d);

                        m = d.intValue(size22, "mdr22 record %d");
                        if (m > max[2]) max[2] = m;

                        m = d.intValue(size25, "mdr25 record %d");
                        if (m > max[3]) max[3] = m;

            if (has26) {
                m = d.intValue(size26, "mdr26 record %d");
                if (m > max[4]) max[4] = m;
            }

            if (has17) {
                m = d.intValue(size17, "mdr17 record %d");
                if (m > max[4]) max[4] = m;
            }

                        printRemainder(d, recsize, start);
                }
                for (int m : max)
                        d.item().addText("max %d", m);
                d.print(outStream);
        }

        private void printRemainder(Displayer d, int recsize, long start) {
                int rem = (int) ((start + recsize) - reader.position());
                if (rem <= 4)
                        d.intValue(rem, "unk %d");
                else
                        d.rawValue(rem);

                recordPrint(d);
        }

        /**
         * Get a string from the string section.
         * @param d The displayer to use.
         */

        private void printTextLabel(Displayer d) {
                DisplayItem item = d.item();
                int off;

                if (getSection(15).getLen() > 0xffffff)
                        off = item.setBytes(reader.getInt());
                else
                        off = item.setBytes3(reader.get3());

                String str = getTextString(off);
                item.addText("Pointer to string: %s", str);
        }

        /**
         * Get a string from the string section.
         * @param off The offset in the text section.
         * @return The resultant string.
         */

        private String getTextString(int off) {
                if (compressedStrings)
                        return "";

                if (stringTable == null)
                        readAllStrings();

                return stringTable.get(off);
        }

        private void readAllStrings() {
                if (compressedStrings)
                        return;  // we don't know how to do this yet.

                stringTable = new LinkedHashMap<Integer, String>();

                // Remember where we are
                long rem = reader.position();

                Section textsect = getSection(15);
                long start = textsect.getStart();
                long end = textsect.getEnd();

                reader.position(start);

                byte[] buffer = new byte[1024];
                int len = 0;
                int offset = 0;
                while (reader.position() < end) {
                        byte b = reader.get();
                        if (b == 0) {
                                String str = new String(buffer, 0, len, charSet);
                                stringTable.put(offset, str);
                                offset = (int) (reader.position() - start);
                                len = 0;
                        } else {
                                buffer[len++] = b;
                        }
                }

                // Return to our callers position
                reader.position(rem);
        }

        private void printHeader() {
                reader.position();
                Displayer d = new Displayer(reader);
                d.setTitle("MDR header");

                int codePage = d.charValue("codepage %d");
                String s;
                if (codePage == 0)
                        s = "ascii";
                else if (codePage == 65001)
                        s = "utf-8";
                else
                        s = "cp" + codePage;

                charSet = Charset.forName(s);

                d.charValue("??? %d");
                d.charValue("??? %d");
                d.charValue("??? %d");

                Section sect = readSection(d, "MDR 1", 1, true, true);
                numberOfMaps = sect.getNumberOfRecords();
                boolean onDevice = sect.getRecordSize() == 4;
                d.item().addText("Number of maps %d", numberOfMaps);
                d.item().addText("Device MDR %s", onDevice);

                readSection(d, "MDR 2", 2, true, true);
                readSection(d, "MDR 3", 3, true, true);
                readSection(d, "MDR 4", 4, true, true);
                readSection(d, "MDR 5", 5, true, true);
                readSection(d, "MDR 6", 6, true, true);
                readSection(d, "MDR 7", 7, true, true);
                readSection(d, "MDR 8", 8, true, true);
                readSection(d, "MDR 9", 9, true, true);
                readSection(d, "MDR 10", 10, false, true);
                readSection(d, "MDR 11", 11, true, true);
                readSection(d, "MDR 12", 12, true, true);
                readSection(d, "MDR 13", 13, true, true);
                readSection(d, "MDR 14", 14, true, true);
                readSection(d, "MDR 15", 15, false, false);

                int flag = d.byteValue("String flag %x");
                if (flag == 1)
                        compressedStrings = true;

                readSection(d, "MDR 16", 16, true, true);
                readSection(d, "MDR 17", 17, false, true);
                readSection(d, "MDR 18", 18, true, true);
                readSection(d, "MDR 19", 19, true, true);

                if (getHeaderLen() > 286) {
                        readSection(d, "MDR 20", 20, true, true);
                        readSection(d, "MDR 21", 21, true, true);
                        readSection(d, "MDR 22", 22, true, true);
                        readSection(d, "MDR 23", 23, true, true);
                        readSection(d, "MDR 24", 24, true, true);
                        readSection(d, "MDR 25", 25, true, true);
                        readSection(d, "MDR 26", 26, true, true);
                        readSection(d, "MDR 27", 27, true, true);
                        readSection(d, "MDR 28", 28, true, true);
                        readSection(d, "MDR 29", 29, true, true);
                        readSection(d, "MDR 30", 30, true, true);
                        readSection(d, "MDR 31", 31, false, false);
                        readSection(d, "MDR 32", 32, true, true);
                        readSection(d, "MDR 33", 33, false, false);
                        readSection(d, "MDR 34", 34, true, true);
                        readSection(d, "MDR 35", 35, true, true);
                        readSection(d, "MDR 36", 36, true, true);
                        readSection(d, "MDR 37", 37, true, true);
                        readSection(d, "MDR 38", 38, true, true);
                        readSection(d, "MDR 39", 39, true, true);
                        readSection(d, "MDR 40", 40, true, true);
                }

                if (getHeaderLen() >= 620) {
                        d.intValue("??? Offset:  %08x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? Offset:  %08x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? Offset:  %08x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                        d.intValue("??? 0x%x");
                }

                // Print any remaining part
                d.rawValue((int) (getHeaderLen() - reader.position()));

                // The city field in mdr11 appears to have a minimum of 2
                int ncities = getSection(5).getNumberOfRecords();
                if (ncities > 0x7fff)
                        cityPtrSize = 3;
                else
                        cityPtrSize = 2;
               
                d.print(outStream);
                outStream.flush();
        }

        /**
         * If there are more maps than will fit into a single byte, then
         * two bytes are used.  This routine sorts all that out.
         */

        private void printMapIndex(Displayer d) {
                int mi;
                if (numberOfMaps > 255)
                        mi = d.charValue("%d map number");
                else
                        mi = d.byteValue("%d map number");
                if (mi > numberOfMaps) {
                        d.print(outStream);
                        outStream.flush();
                        assert false: "not a map number " + mi;
                }
        }

        /** The size of a map index - one or two bytes */
        private int mapIndexSize() {
                return numberOfMaps > 255? 2: 1;
        }

        /**
         * Do we want to print this section.
         */

        private boolean want(int n) {
                Section section = getSection(n);
                if (section != null && section.getLen() > 0 && wanted[n])
                        return true;
                else
                        return false;
        }

        /**
         * Print an mdr file.
         *
         * The sections printed can be limited by adding a list of numbers
         * after the filename.
         *
         * If the list of numbers is preceded by a '!' character then all
         * sections except for the listed ones are printed.
         */

        public static void main(String[] args) {
                if (args.length < 1) {
                        System.err.println("Usage: mdrdisplay <filename>");
                        System.exit(1);
                }

                MdrDisplay md = new MdrDisplay();

                List<String> argv = Arrays.asList(args);
                Iterator<String> it = argv.iterator();
                String name = it.next();
                int start = 0;

                boolean negate = false;
                boolean hasNumber = false;
                while (it.hasNext()) {
                        String s = it.next();
                        if (s.equals("--start")) {
                                start = Integer.parseInt(it.next());
                        } else if (s.equals("!")) {
                                Arrays.fill(md.wanted, true);
                                negate = true;
                        } else {
                                int n = Integer.parseInt(s);
                                md.wanted[n] = !negate;
                                hasNumber = true;
                        }
                }

                if (!hasNumber)
                        Arrays.fill(md.wanted, true);

                md.setStart(start);
                md.display(name, "MDR");
        }
}