Subversion Repositories display

Rev

Rev 610 | 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.List;

import test.display.check.MdrStrings;
import test.util.BitReaderLR;
import uk.me.parabola.imgfmt.app.srt.Sort;

/**
 * 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
 */

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 citySizeFlagged;
        private Charset charset;
        MdrStrings strings;
        private int recordStart;
        private int recordCount;
        private HuffmanDecoder decoder;

        private boolean huffmanDecodingFailed;

        @Override
        protected void print() {
                readCommonHeader();

                printHeader();

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

               
                if (compressedStrings) {
                        try {
                                Displayer d = new Displayer(reader);
                                decoder = readHuffmanTable(d, getSection(16).getStart());
                        } catch (Exception e) {
                                huffmanDecodingFailed = true;
                               
                        }
                }
               
                /*
                 * 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);
                                if (i == 16 && !huffmanDecodingFailed) {
                                        Displayer d = new Displayer(reader);
                                        d.setTitle("MDR 16 (decompression codebook Huffman tree)");
                                        readHuffmanTable(d, getSection(16).getStart());
                                        d.print(outStream);
                                } else {
                                        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.recordStart = start;
        }

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

        @SuppressWarnings("unused")
        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<>();
                int index = 1;
                long start;
                while ((start = 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");
                        if (recsize > 8) {
                                int end = d.intValue("end of subsection %x");
                                mi.sublen = end - mi.offset;
                        } else {
                                mi.sublen = -1; // will be calculated later
                        }
                        printRemainder(d, recsize, start);
                        maps.add(mi);
                }

                // mdr1 record follows the reverse index, so add fake entry which points to the start
                // to be able to calculate the last length
                MapInfo mi = new MapInfo();
                mi.offset = (int) s.getStart();
                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;
                        if (mi.sublen < 0)
                                mi.sublen = maps.get(n + 1).offset - suboff;
                        Mdr1SubFileDisplay subFileDisplay = new Mdr1SubFileDisplay(reader, outStream, mi);
                        subFileDisplay.print();
                }
        }

        /**
         * A list of POI types without subtypes
         */

        @SuppressWarnings("unused")
        private void printSect3() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 3 (polygon types)");

                Section s = getSection(3);

                d.setSectStart(s.getStart());

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

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

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

                Section s = getSection(4);

                d.setSectStart(s.getStart());

                reader.position(s.getStart());
                while (reader.position() < s.getEnd()) {
                        int record = reader.get3u();
                        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.
         */

        @SuppressWarnings("unused")
        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 (getSection(20).getLen() > 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());
                        assert remain >= 0;
                        if (remain > 0)
                                d.rawValue(remain);

                        recordPrint(d);
                }

                d.print(outStream);
        }

        private void recordPrint(Displayer d) {
                d.print((++recordCount < recordStart)? 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.
         */

        @SuppressWarnings("unused")
        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.
         */

        @SuppressWarnings("unused")
        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;
                int unk2size = (magic >> 6) & 0x7;
                int unk2split = ((magic >> 9) & 0xf);
                int splitMask = (1 << unk2split) - 1;

                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");

                        item = d.intItem(unk2size);
                        int val = item.getValue();
                        item.addText("%s sort seq p=%-3d s=%-3d", (val & 1) == 1 ? "N" : "R",
                                        (val >> 1) & splitMask,
                                        (val >>> (unk2split + 1)));

                        d.rawValue((int) (itemStart + recsize - reader.position()));
                        d.gap();
                        recordPrint(d);
                }

                d.print(outStream);
        }

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

        /**
         * POI / Polygon groups.
         */

        @SuppressWarnings("unused")
        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();

                        item = d.intItem(recsize - 1);
                        int val = item.getValue();
                        item.addText("record in MDR10 %d", val);
                }

                d.print(outStream);
        }

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

        @SuppressWarnings("unused")
        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;
                // Calculate record size by determining how many points there are in MDR11
                int nPoints = getSection(11).getNumberOfRecords();
                while (reader.position() < s.getEnd()) {
                        d.byteValue("[sub]type 0x%x");
                        DisplayItem item = d.item();

                        int c;
                        boolean isRepeat = true;

                        if (nPoints < 0x80) {
                                c = item.setBytes1(reader.get1u());
                                if ((c & 0x80) != 0) {
                                        isRepeat = false;
                                        c &= ~0x80;
                                }
                        } else if (nPoints < 0x8000) {
                                c = item.setBytes2(reader.get2u());
                                if ((c & 0x8000) != 0) {
                                        isRepeat = false;
                                        c &= ~0x8000;
                                }
                        } else {
                                c = item.setBytes3(reader.get3u());
                                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 = 0;
                int c = 0;
                switch (citySizeFlagged) {
                case 1: // There is supposed to be a min size of 2, but handle anyway
                        c = item.setBytes1(reader.get1u());
                        mask = 0x80;
                        break;
                case 2:
                        c = item.setBytes2(reader.get2u());
                        mask = 0x8000;
                        break;
                case 3:
                        c = item.setBytes3(reader.get3u());
                        mask = 0x800000;
                        break;
                case 4:
                        c = item.setBytes4(reader.get4());
                        mask = 0x80000000;
                        break;
                }
                if (c > 0) {
                        if ((c & mask) == 0)
                                item.addText("Region %d (in map)", c);
                        else
                                item.addText("City %d (MDR5)", c & ~mask);
                }
        }

        /**
         * 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.
         */

        @SuppressWarnings("unused")
        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 ((s.getMagic() & 0xb00) == 0xb00)
                                d.charValue("???");    
                        else if ((s.getMagic() & 0x900) == 0x900)
                                d.byteValue("???");    
                        if (recsize >= 8)
                                printSect11_City(d);
                        if (recsize > 10 && (s.getMagic() & 0x2) != 0)
                                printTextLabel(d);

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

                        recordPrint(d);
                }

                d.print(outStream);
        }

        @SuppressWarnings("unused")
        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.
         */

        @SuppressWarnings("unused")
        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.
         */

        @SuppressWarnings("unused")
        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, either a simple straightforward
         * one or with Huffman encoded strings.
         */

        @SuppressWarnings("unused")
        private void printSect15() {
                Displayer d = new Displayer(reader);
                if (compressedStrings)
                        d.setTitle("MDR 15 (compressed strings)");
                else
                        d.setTitle("MDR 15 (strings)");
                Section s = getSection(15);
                long start = s.getStart();
                int sLen = s.getLen();
                long end = s.getEnd();
                d.setSectStart(start);
                reader.position(start);
                if (compressedStrings && huffmanDecodingFailed) {
                        // don't know how to decode
                        d.rawValue(sLen, "compressed");
                        return;
                }
                // 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 (strings != null) {
                        d.item().addText("Strings omitted, call with just --print=15 on the command line to see");
                } else {
                        if (compressedStrings)
                                decoder.initFreq();
                        int count = 0;
                        int offset = 0;
                        byte[] buffer = new byte[1024];
                        int len = 0;

                        while (reader.position() < end) {
                                String text = null;
                                long oldPos = reader.position();
                                if (compressedStrings) {
                                        text = decoder.decode(new BitReaderLR(reader, (int) (end - oldPos)), false, charset);
                                } else {
                                        len = 0;
                                        while (text == null) {
                                                byte b = reader.get();
                                                if (b == 0) {
                                                        text = new String(buffer, 0, len, charset);
                                                } else {
                                                        buffer[len++] = b;
                                                }
                                        }
                                }
                                int inputLen = (int) (reader.position() - oldPos);
                                reader.position(oldPos);
                                DisplayItem item = d.item();
                                item.setBytes(reader.get(inputLen));
                                item.addText("text: %s", text);
                                count++;
                        }
                        d.item().addText("%d records", count);
                }
                d.print(outStream);
                if (compressedStrings) {
                        outStream.println("frequencies:" + Arrays.toString(decoder.getFreq()));
                }
        }

        @SuppressWarnings("unused")
        private void printSect16() {
                Displayer d = new Displayer(reader);
                d.setTitle("MDR 16 (decompression codebook Huffman tree)");

                Section s = getSection(16);
                long start = s.getStart();
                d.setSectStart(start);
                reader.position(start);
                d.rawValue((s.getLen()));
                d.print(outStream);
        }
       
        /**
         * MDR 17: String index table
         *
         * My personal guess is, that this is a search table for
         * sub strings starting with the given short string
         */

        @SuppressWarnings("unused")
        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 & d.byteValue("Peek");
                        DisplayItem item = d.item();
                        int partlength = 0;
                        if ((peek & 1) == 1) {
                                partlength = peek;
                                partlength >>= 1;
                        }
                        if ((peek & 3) == 2) {
                                partlength = reader.get1u();
                                item.setBytes1(partlength);
                                partlength <<= 8;
                                partlength += peek;
                                partlength >>= 2;
                        }
                        if ((peek & 7) == 4) {
                                partlength = reader.get2u();
                                item.setBytes2(partlength);
                                partlength <<= 8;
                                partlength += peek;
                                partlength >>= 3;
                        }
                        item.addText("Length of following part: %d", partlength);

                        int sectHeader = -1;
                        if (partlength > 2) {
                                item = d.charItem();
                                sectHeader = item.getValue();
                                item.addText("Some header: 0x%04x", sectHeader);
                                partlength -= 2;
                        }
                        if (sectHeader == 0x8000) {
                                //TODO
                                d.rawValue(s.getLen(),"please implement");
                                break;
                        }
                        // This can't be the whole story. Header flags from other sections help determine?
                        int strLen = (sectHeader >> 8) + 1;
                        int indLen = sectHeader & 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
         */

        @SuppressWarnings("unused")
        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();
                        if (type == 0xffff)
                                break;
                        item.addText("Type: %02x/%02x", (type>>5) & 0xff, type & 0x1f);
                        d.intValue(size19, "Idx into MDR 19: %d");
                }

                d.print(outStream);
        }

        @SuppressWarnings("unused")
        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();
                int mask = 0;
                if (poiSize == 1)
                        mask = 0x80 - 1;
                else if (poiSize == 2)
                        mask = 0x8000 - 1;
                else if (poiSize == 3)
                        mask = 0x800000 - 1;
                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 & mask);
                }

                d.print(outStream);
        }

        private void printSect2x(String title, int sectNum) {
                Displayer d = new Displayer(reader);
                d.setTitle(title);

                Section s = getSection(sectNum);
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();
                int magic = s.getMagic();
                reader.position(s.getStart());
                if (recsize == 0) {
                        printRemainder(d, s.getLen(), s.getStart());
                        d.print(outStream);
                        return;
                }

                int count = 1;
                int record = 1;
                int lastid = -1;
                long start;
                int flgSize = (magic >> 3) & 0x7;

                int unk2split = ((magic >> 6) & 0xf);
                int splitMask = (1 << unk2split) - 1;
               

                while ((start = reader.position()) < s.getEnd()) {
                        d.item().addText("Mdr-%d Record %d", sectNum, record++);
                        if ((magic & 0x2) != 0) {
                                d.byteValue("map %d");
                                DisplayItem item = d.int3Item();
                                int val = item.getValue();
                                item.addText("lbl 0x%06x%s", val & 0x7fffff, (val & 0x800000) != 0 ? " F" : "");
                       
                                if ((magic & 0x4) != 0) {
                                        d.byteValue("name offset %d");
                                }
                       
                                if (flgSize > 0) {

                                        item = d.intItem(flgSize);
                                        val = item.getValue();
                                        // item.addText("%ssplitInfo=%d", (val & 1) == 0 ? "[r] " : "", val >> 1);
                                        item.addText("%s sort seq p=%-3d s=%-3d", (val & 1) == 1 ? "N" : "R",
                                                        (val >> 1) & splitMask,
                                                        (val >>> (unk2split + 1)));
                                }

                                if ((magic & 0x800) != 0) {
                                        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);

                                }
                        } else {
                                DisplayItem item = d.intItem(recsize);
                                int val = item.getValue();
                                int id = val >> 1;
                                if (id < lastid)
                                        item.addText("# New group %d", count++);
                                lastid = id;
                                item.addText("street %d %s", id, (val & 1) == 1 ? "" : "R");
                               
                        }
                        printRemainder(d, recsize, start);
                        d.gap();
                }

                d.print(outStream);

        }
       
        @SuppressWarnings("unused")
        private void printSect20() {
                printSect2x("MDR 20 (streets by city)", 20);
        }

        @SuppressWarnings("unused")
        private void printSect21() {
                printSect2x("MDR 21 (streets by region)", 21);
        }

        @SuppressWarnings("unused")
        private void printSect22() {
                printSect2x("MDR 22 (streets by country)", 22);
        }

        @SuppressWarnings("unused")
        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);
        }

        @SuppressWarnings("unused")
        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);
        }

        @SuppressWarnings("unused")
        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);
        }

        @SuppressWarnings("unused")
        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.intItem(recsize);
                        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);
        }

        @SuppressWarnings("unused")
        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);
        }

        @SuppressWarnings("unused")
        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 magic = s.getMagic();

                int size21 = getSection(21).getBytesForRecords();
                int size23 = getSection(23).getBytesForRecords();
                int size27 = getSection(27).getBytesForRecords();
                int strSize = 0;
                if ((magic & 0x01) != 0)
                        strSize = (getSection(15).getLen() > 0xffffff) ? 4 : 3;
                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;

                        if (strSize > 0)
                                printTextLabel(d);
                       
                        if ((magic & 2) != 0) {
                                m = d.intValue(size21, "mdr21 record %d");
                                if (m > max[2])
                                        max[2] = m;
                        }
                        if ((magic & 4) != 0) {
                                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);
        }

        @SuppressWarnings("unused")
        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(5).getBytesForRecords();  // NB appears to be size of 5, not 25 (they are related of course).
                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 print30Or32(int sectNum, final String title) {
                Displayer d = new Displayer(reader);
                d.setTitle(title);
                Section s = getSection(sectNum);

               
                d.setSectStart(s.getStart());
                int recsize = s.getRecordSize();
                int numNext = sectNum + 1;
                Section sNext = getSection(numNext);
                int offsize = sNext.getBytesForSize();
                int unksize = recsize - offsize;
                int maxOff = sNext.getLen();
                reader.position(s.getStart());

                final String msg = "Offset in mdr" + numNext + ": %d";
                while (reader.position() < s.getEnd()) {
                        int off = d.intValue(offsize, msg);
                       
                        d.intValue(unksize, "unk %d");
                        int nextOff;
                        if (reader.position()< s.getEnd()) {
                                nextOff = reader.getNu(offsize);
                                reader.position(reader.position() - offsize);
                        } else {
                                nextOff = maxOff;
                        }
                        long saveOff = reader.position();

                        reader.position(sNext.getStart()+ off);
                        byte[] bytes = reader.get(nextOff - off);
                        DisplayItem item = d.item();
                        item.addText("mdr%d string: %s", numNext, new String(bytes, charset));
                        reader.position(saveOff);
                }
                d.print(outStream);
               
        }

        @SuppressWarnings("unused")
        private void printSect30() {
                print30Or32(30, "MDR 30 (pointers to mdr31)");

        }

        @SuppressWarnings("unused")
        private void printSect32() {
                print30Or32(32, "MDR 32 (pointers to mdr33)");
        }

        private void printRemainder(Displayer d, int recsize, long start) {
                int rem = (int) ((start + recsize) - reader.position());
                if (rem == 0)
                        return;
                if (rem > 0 && 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.setBytes4(reader.get4());
                else
                        off = item.setBytes3(reader.get3u());
                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 (strings == null)
                        strings = new MdrStrings(reader, getSection(15), charset, decoder, false);

                return strings.getTextString(off);
        }
       
        private void printHeader() {
                reader.position();
                Displayer d = new Displayer(reader);
                d.setTitle("MDR header");

                int codePage = d.charValue("codepage %d");

                charset = Sort.charsetFromCodepage(codePage);

                d.charValue("id1 %d");
                d.charValue("id2 %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");
                getSection(15).setMagic(flag);
                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);
                }

                if (getHeaderLen() > 426) {
                        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() > 568) {
                        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 > 0x7fffff)
                        citySizeFlagged = 4;
                else if (ncities > 0x7fff)
                        citySizeFlagged = 3;
                else
                        citySizeFlagged = 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);
                return section != null && section.getLen() > 0 && wanted[n];
        }

        /**
         * 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 = "osmmap_mdr.img";
                boolean negate = false;
                boolean hasNumber = false;
                while (it.hasNext()) {
                        String s = it.next();
                        if (s.startsWith("--print")) {
                                String[] split = s.split("=");
                                if (split.length > 1) {
                                        String arg = split[1];
                                        if (arg.charAt(0) == '!') {
                                                Arrays.fill(md.wanted, true);
                                                negate = true;
                                                arg = arg.substring(1);
                                        }

                                        for (String ns : arg.split("[, ]"))
                                                md.wanted[Integer.parseInt(ns)] = !negate;

                                        hasNumber = true;
                                }
                        } else if (s.startsWith("--")) {
                                System.out.println("Unknown flag " + s);
                                System.exit(1);
                        } else {
                                name = s;
                        }
                }

                if (!hasNumber)
                        Arrays.fill(md.wanted, true);
                md.display(name, "MDR");
        }
}