Subversion Repositories display

Rev

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

/*
 * Copyright (C) 2011.
 *
 * 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.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.mdr.Mdr14Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr28Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr29Record;
import uk.me.parabola.imgfmt.app.trergn.Point;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.mdxfmt.MapInfo;
import uk.me.parabola.imgfmt.mdxfmt.MdxFileReader;
import uk.me.parabola.imgfmt.mps.MapBlock;
import uk.me.parabola.imgfmt.mps.MpsFileReader;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.imgfmt.sys.ImgFS;

import test.check.CommonCheck;
import test.display.check.BoundChecker;
import test.display.check.MapDetailList;
import test.display.check.MapDetails;
import test.display.check.MdrStrings;
import test.display.check.Street;
import test.display.check.SubsectionList;

/**
 * Program to check the contents of an mdr file against the files that it indexes.
 *
 * @author Steve Ratcliffe
 */

public class MdrCheck extends CommonCheck {
        private int numberOfMaps;
        private Charset charSet;
        private boolean[] print = new boolean[100];

        private final MapDetailList details = new MapDetailList();

        private MdrStrings mdrStrings;

        private final List<Mdr20Info> cityMdr20 = new ArrayList<>();
        private final List<List<Street>> streets = new ArrayList<>();

        private int[] streetMapNumbers;
        private int[] mdr20ToStreet;

        // These are the key to reading several other 2x sections
        List<Mdr28Record> regionNames = new ArrayList<>();
        List<Mdr29Record> countryNames = new ArrayList<>();
    private final List<TypeInfo> typeInfos = new ArrayList<>();
    private final Map<Integer, Integer> poiType = new HashMap<>();


    public MdrCheck() {
                Arrays.fill(print, true);
        }

        protected void print() {

                readHeader();
                readMaps();

                mdrStrings = new MdrStrings(reader, getSection(15), charSet);

                check5();
                check7();
                check20();

                check1();
                streetMapNumbers = null;
                mdr20ToStreet = null;

        check11();

                check13();

        check18();
        check19();

                // region and country keys to the other sections
                check28();
                check29();

                check23();
                check24();

                check25();
                check27();

                check21();
                check22();

                check26();
        }

        private void check1() {
                setShowLogs(print[1]);
                info("mdr1 check");

                if ((getSection(1).getMagic() & 1) == 0)
                        return;

                SubsectionList list = new SubsectionList(numberOfMaps);
                for (int i = 1; i <= numberOfMaps; i++) {
                        MapDetails map = details.getMap(i);
                        if (map == null)
                                return; // If we don't have all the maps, then give up currently.
                        list.read(reader, i, map.getSubHeaderOffset());
                }

                // Check that the totals summed across all the maps are correct.
                for (int s = 1; s <= 8; s++) {
                        int[] subNumbers = {0, 11, 10, 7, 5, 6, 20, 21, 22};
                        String[] subs = new String[]{
                                        "n/a",
                                        "mdr11", "mdr10", "mdr7", "mdr5",
                                        "mdr6", "mdr20", "mdr21", "mdr22"
                        };
                        int total = list.totalForSection(s);
                        int expected = getSection(subNumbers[(s == 2) ? s - 1 : s]).getNumberOfRecords();
                        checkEqual(expected, total, "sub%d (%s) total number", s, subs[s]);

                        list.setMax(s, expected);
                }

                // Now check that every pointer points to something that is in the correct map
                for (int ss = 1; ss <= 8; ss++) {
                        int max = 0;
                        for (int m = 1; m <= numberOfMaps; m++) {
                                int off = list.getOffset(m, ss);
                                int length = list.getLength(m, ss);

                                int size = list.getSize(ss);
                                int end = off + size*length;

                                reader.position(off);
                                info("mdr1 map%d sub%d; len %d, recsize %d", m, ss, length, size);

                                int count = 0;
                                while (reader.position() < end) {
                                        count++;
                                        int val = read(size);
                                        if (val > max) max = val;
                                        list.checkValue(m, ss, val);

                                        // In some cases we follow the pointer and check that it points to something
                                        // in the correct map.
                                        if (ss == 6) {
                                                int v2 = mdr20ToStreet[val];
                                                assert v2!=0;
                                                int foundMapNumber = streetMapNumbers[v2];
                                                checkEqual(m, foundMapNumber,
                                                                "mdr1 map%d sub5; map number wrong, pointer %d, value %d",
                                                                m, count, val);
                                        }
                                }
                        }

                        int expectedMax = list.getMax(ss);
                        checkEqual(expectedMax, max, "mdr1 sub%d; expected max value", ss);
                }
        }

        /**
         * Check details of the cities.
         */

        private void check5() {
                setShowLogs(print[5]);
                info("mdr5 check");
                Section section = getSection(5);

                long start = section.getStart();
                long end = section.getEnd();
                int magic = section.getMagic();

                cityMdr20.add(null);

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

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

                boolean has28_29_offset = (magic & 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();

                reader.position(start);
                int citynum = 0;
                int lastMdr20 = 0;
                Mdr20Info last20Info = null;
                while (reader.position() < end) {
                        citynum++;

                        int mapNumber = readMapNumber();
                        assert mapNumber > 0 && mapNumber <= numberOfMaps : "Bad map number " + mapNumber;

                        int localCityNum = read(cityPtrSize);
                        int lbl = read(3);

                        boolean repeated = (lbl & 0x800000) == 0;

                        int region = 0;
                        int country = 0;
                        if (hasRegion) {
                                int val = read(2);
                                if ((val & 0x4000) == 0) {
                                        region = val & 0x3fff;
                                } else {
                                        country = val & 0x3fff;
                                }
                        }

                        String strText = null;
                        if (hasStr)
                                strText = readString();

                        int off20 = 0;
                        if (has20 || has20offset) {
                                off20 = read(mdr20PointerSize);
                                if (off20 != 0 && off20 < lastMdr20)
                                        error("%d map%d; out of order mdr20=%d last=%d", citynum, mapNumber, off20, lastMdr20);

                                if (off20 != 0)
                                        lastMdr20 = off20;
                        }

                        if (has28_29_offset)
                                read(mdr28_29PointerSize);

                        Mdr20Info m20 = new Mdr20Info(off20);
                        m20.mapNumber = mapNumber;

                        MapDetails map = details.getMap(mapNumber);
                        if (map != null) {
                                if (hasRegion) {
                                        // There is always a country. If there is a region, then it will have a country.
                                        if (country == 0)
                                                country = map.getCountryFromRegion(region);

                                        m20.regionName = map.getRegionName(region);
                                        m20.countryName = map.getCountryName(country);

                                        checkNotZero(country, "%d no country", citynum);
                                } else {
                                        m20.regionName = "";
                                        m20.countryName = "";
                                }

                                // Print out the city information collected
                                String name = map.getLabelText(lbl & 0x7fffff);
                                info("%d map%d; %s mapCity=%d reg=%d (%s) country=%d (%s) mdr20=%d rep=%b\n",
                                                citynum, mapNumber, name, localCityNum,
                                                region, m20.regionName, country, m20.countryName,
                                                m20.mdr20index, repeated);

                                City city = map.getCity(localCityNum);
                                String nameFromCity = city.getName();
                                if (strText != null && !strText.isEmpty())
                                        checkEqual(name, strText, "map %d: city str table %d", mapNumber, citynum);
                                checkEqual(name, nameFromCity, "map %d: city %d city name", mapNumber, citynum);

                                m20.cityName = nameFromCity;

                                if (m20.sameCity(last20Info)) {
                                        if (!repeated)
                                                error("%d map%d; repeat flag not set on repeated name %s", citynum, mapNumber, nameFromCity);
                                } else {
                                        if (repeated)
                                                error("%d map%d; repeat flag set when not a repeat", citynum, mapNumber);
                                }
                                last20Info = m20;
                        } else {
                                if (strText != null && !strText.isEmpty())
                                        m20.cityName = strText;
                        }

                        cityMdr20.add(m20);
                }

                // Mark the last street
                Mdr20Info m = new Mdr20Info(getSection(20).getNumberOfRecords()+1);
                cityMdr20.add(m);
        }

        /**
         * Check details of the streets.
         */

        private void check7() {
                setShowLogs(print[7]);
                info("mdr7 check");

                Section section = getSection(7);

                long start = section.getStart();
                long end = section.getEnd();
                int magic = section.getMagic();

                boolean hasStr = (magic & 0x01) != 0;
                boolean hasUnk1 = (magic & 0x20) != 0;
                int unk2size = ((magic >> 6) & 0x7);

                streetMapNumbers = new int[section.getNumberOfRecords() + 1];

                // fill up the 0 element.
                streets.add(null);

                reader.position(start);
                int streetnum = 0;
                String lastName = null;
                int lastMapNumber = 0;
                while (reader.position() < end) {
                        streetnum++;

                        int mapNumber = readMapNumber();
                        streetMapNumbers[streetnum] = mapNumber;

                        int lbl = read(3);
                        boolean repeated = (lbl & 0x800000) == 0;

                        if (repeated && (mapNumber == lastMapNumber)) {
                                error("%d map%d; repeated street name in same map", streetnum, mapNumber);
                        }

                        String strText = null;
                        if (hasStr)
                                strText = readString();

                        int unk1 = 0;
                        if (hasUnk1) {
                                unk1 = read(1);
                        }

                        int unk2 = 0;
                        boolean partialRepeat = false;
                        if (unk2size > 0) {
                                unk2 = read(unk2size);
                                if ((unk2 & 0x1) == 0) {
                                        // I call it a partial repeat when you are using the separators between the name
                                        // and the Street, Road etc. and the first part is repeated.
                                        // So
                                        // MAIN^ROAD
                                        // MAIN^ROAD -- full repeat
                                        // MAIN^STREET -- partial repeat
                                        partialRepeat = true;
                                }
                        }

                        MapDetails map = details.getMap(mapNumber);
                        if (map != null) {
                                String name = map.getLabelText(lbl & 0x7fffff);
                                List<Street> streetList = details.getStreetsWithName(mapNumber, name);
                                //assert (!streetList.isEmpty()) : "no streets for <" + name + ">, in map " + mapNumber;
                                streets.add(streetList);

                                String repeatStr = String.format("(%s%s)", repeated ? "R" : "", partialRepeat ? "r" : "");
                                info("%d map%d street=%s %s %02x %06x %d %d",
                                                streetnum, mapNumber, name, repeatStr, unk1, unk2, (unk2 >>> 1) & 0xfff,
                                                unk2 >>> 13);

                                if (strText != null && !strText.isEmpty()) {
                                        String cleanName = Label.stripGarminCodes(name);
                                        checkEqual(cleanName, strText, "%d map%d string text", streetnum, mapNumber);
                                }

                                // Should we use the clean-name here?
                                // Should we remove even more characters?
                                if (lastName != null) {
                                        if (name.equals(lastName)) {
                                                if (!repeated)
                                                        error("%d map%d; repeated name not flagged", streetnum, mapNumber);
                                        } else {
                                                if (repeated)
                                                        error("%d map%d; new name flagged as repeated", streetnum, mapNumber);
                                        }
                                }
                                lastName = name;
                        } else {
                                lastName = null;
                                streets.add(null);
                        }

                        lastMapNumber = mapNumber;
                        assert streets.size() == streetnum + 1 : "streets size " + streets.size() + "/" + streetnum;
                }
        }

    private void check11() {
        setShowLogs(print[11]);
        info("mdr11 check");
        Section section = getSection(11);

        long start = section.getStart();
        long end = section.getEnd();
        int recsize = section.getRecordSize();

//        int magic = section.getMagic();

        reader.position(start);
        int record = 0;
        while (reader.position() < end) {
            record++;
            int next = (int) (reader.position() + recsize);

            int mapNumber = readMapNumber();
            int num = read(1);
            int subdiv = read(2);
            int lbloff = read(3);

            MapDetails map = details.getMap(mapNumber);
            if (map != null) {
                String text = map.getLabelText(lbloff);
                Point p = map.getPoint(subdiv, num);
                if (p == null)
                    error("%d: map%d; %s; not found subdiv=%d, ind=%d", record, mapNumber, text, subdiv, num);
                else {
                    info("%d: map%d; t=0x%04x num=%d/%d %s", record, mapNumber, p.getType(), subdiv,
                                    num, text);
                    int type = p.getType();
                    if (type <= 0xff)
                        type <<= 8;
                    poiType.put(record, type);
                }
            }

            reader.position(next);
        }
    }

        /**
         * Regions. We read this mainly to get a mapping of region names to country names
         * to check within other sections.
         */

        private void check13() {
                setShowLogs(print[13]);
                info("mdr13 check");
                Section section = getSection(13);

                long start = section.getStart();
                long end = section.getEnd();

                reader.position(start);
                int record = 0;
                while (reader.position() < end) {
                        record++;

                        int mapNumber = readMapNumber();
                        assert mapNumber > 0 && mapNumber <= numberOfMaps : "Bad map number " + mapNumber;

                        int regNum = read(2);
                        int countryNum = read(2);
                        String strText = readString();

                        MapDetails map = details.getMap(mapNumber);
                        if (map != null) {
                                String regionName = map.getRegionName(regNum);
                                String countryName = map.getCountryName(countryNum);

                                info("%d map%d; region %s country %s", record, mapNumber, regionName, countryName);

                                if (!regionName.equals(strText))
                                        error("%d map%d; region %s, text string %s", record, mapNumber, regionName, strText);

                        }
                }
        }

    private void check18() {
        setShowLogs(print[18]);
        info("mdr18 check");
        Section section = getSection(18);

        long start = section.getStart();
        long end = section.getEnd();

       
        int psize = getSection(19).getBytesForRecords();
        reader.position(start);
        while (reader.position() < end) {

            char type = reader.getChar();
            int rec = read(psize);
            TypeInfo ti = new TypeInfo();
            ti.type = type;
            ti.start = rec;
            typeInfos.add(ti);
        }
    }
   
    private void check19() {
        setShowLogs(print[19]);
        info("mdr19 check");

        if (typeInfos.isEmpty()) {
            error("no type info from mdr 18 available");
            return;
        }
       
        Section section = getSection(19);
        long start = section.getStart();

        int recsize = getSection(11).getBytesForRecords();
        int flag = 0;
        switch (recsize) {
            case 1: flag = 0x80; break;
            case 2: flag = 0x8000; break;
            case 3: flag = 0x800000; break;
        }

        reader.position(start);
        int record = 0;
        for (int i = 0, typeInfosSize = typeInfos.size(); i < typeInfosSize-1; i++) {
            TypeInfo ti = typeInfos.get(i);
            TypeInfo ti2 = typeInfos.get(i+1);
            int expected = ti.type & ~0xe000;
            expected = ((expected<< 3) & ~0xff) | (expected & 0x1f);

            if (ti2.start == ti.start)
                error("empty range for type %x", ti.type);
            for (int j = ti.start; j < ti2.start; j++) {
                record++;

                int rec = read(recsize);
                rec &= ~flag;
                Integer type = poiType.get(rec);
                if (type == null) {
                    error("could not get type for %d", rec);
                } else {
                    if (type == expected) info("%d: poi%d type %x", record, rec, type);
                    else error("%d: poi%d type was %x, expected %x", record, rec, type, expected);
                }
            }
        }
    }
   
    /**
     * Check that the mdr20 records point to streets in the correct city.
     */

    private void check20() {
                setShowLogs(print[20]);
                info("mdr20 check");

                Section section = getSection(20);

                long start = section.getStart();

                mdr20ToStreet = new int[section.getNumberOfRecords()+1];

                int size = Section.numberOfBytes(getSection(7).getNumberOfRecords() << 1);
                if (section.getRecordSize() != size) {
                        if (section.getRecordSize() == 5) {
                                check20_size5();
                        } else {
                                error("record size %d, byte required for mdr7 is %d", section.getRecordSize(), size);
                                error("  ... don't know how to deal with this version of the section");
                        }
                        return;
                }

                Mdr20Info first = cityMdr20.get(1);
                checkEqual(1, first.mdr20index, "first mdr20 not 1");

                reader.position(start);
                String lastName = "";
                int record = 1;
                for (int i = 1; i < cityMdr20.size()-1; i++) {
                        Mdr20Info from = cityMdr20.get(i);
                        if (from.mdr20index == 0)
                                continue;

                        Mdr20Info next = null;
                        for (int j = i + 1; next == null || next.mdr20index == 0 ; j++)
                                next = cityMdr20.get(j);

                        if (record != from.mdr20index) {
                                error("mdr20 record %d out of step with index %d", record, from.mdr20index);
                                record = from.mdr20index;
                        }
                        info("mdr20 %d city=%s r=%s c=%s, from=%d to=%d %s", record, from.cityName,
                                        from.regionName, from.countryName, from.mdr20index, next.mdr20index,
                                        (from.mdr20index == next.mdr20index) ? "empty" : "");
                        while (record < next.mdr20index) {
                                assert from.mdr20index != next.mdr20index : "from " + from.mdr20index + " to " + next.mdr20index;
                                int val = read(size);
                                boolean repeated = (val & 1) == 0;

                                int ind = val >> 1;
                                mdr20ToStreet[record] = ind;

                                List<Street> streetList = streets.get(ind);
                                if (streetList != null && !streetList.isEmpty()) {
                                        Street street = streetList.get(0);
                                        String name = street.getName();
                                        if (name.equals(lastName)) {
                                                if (!repeated)
                                                        error("%d map%d st=%d; not flagged when repeated", record, street.getMapIndex(), ind);
                                        } else {
                                                if (repeated)
                                                        error("%d map%d st=%d; flagged when not repeated", record, street.getMapIndex(), ind);
                                                lastName = name;
                                        }

                                        boolean found = false;
                                        for (Street s : streetList) {
                                                //System.out.println("from city " + from.cityName + "/r=" + from.regionName);
                                                //System.out.println("s         " + s.getCityName() + "/r=" + s.getRegionName());
                                                if (from.cityName.equals(s.getCityName()) && from.regionName.equals(s.getRegionName()) && from.countryName.equals(s.getCountryName())) {
                                                        found = true;
                                                        info("%d map%d st=%d, %s%s, city=%s, r=%s, c=%s",
                                                                        record, s.getMapIndex(), ind,
                                                                        s.getName(), repeated ? " (R)" : "", s.getCityName(),
                                                                        s.getRegionName(), s.getCountryName());
                                                }
                                        }
                                        if (!found) {
                                                error("%d no road %d name=%s in city %s", record, ind, streetList.get(0).getName(), from.cityName);
                                                for (Street s : streetList) {
                                                        error("  ... %s, in %s r=%s c=%s", s.getName(), s.getCityName(), s.getRegionName(), s.getCountryName());
                                                }
                                        }
                                } else {
                                        // This is an error if we have saved all the maps
                                        if (details.mapSaved(numberOfMaps)) {
                                                error("%d; could not find any streets", ind);
                                        }
                                }
                                record++;
                        }
                }
        }

        /**
         * Check mdr20 when the record size is 5.
         *
         * We don't know how the flags work for this section but they can be 0xa
         * for a 5 byte record size. If the flags are different then this routine
         * will probably not work either.
         */

        private void check20_size5() {
                setShowLogs(print[20]);
                info("mdr20 check 5");

                Section section = getSection(20);

                long start = section.getStart();

                mdr20ToStreet = new int[section.getNumberOfRecords()+1];

                Mdr20Info first = cityMdr20.get(1);
                checkEqual(1, first.mdr20index, "first mdr20 not 1");

                reader.position(start);
        int record = 1;
                for (int i = 1; i < cityMdr20.size()-1; i++) {
                        Mdr20Info from = cityMdr20.get(i);
                        if (from.mdr20index == 0)
                                continue;

                        Mdr20Info next = null;
                        for (int j = i + 1; next == null || next.mdr20index == 0 ; j++)
                                next = cityMdr20.get(j);

                        if (record != from.mdr20index) {
                                error("mdr20 record %d out of step with index %d", record, from.mdr20index);
                                record = from.mdr20index;
                        }
                        info("mdr20 %d city=%s r=%s c=%s, from=%d to=%d %s", record, from.cityName,
                                        from.regionName, from.countryName, from.mdr20index, next.mdr20index,
                                        (from.mdr20index == next.mdr20index) ? "empty" : "");
                        while (record < next.mdr20index) {
                                assert from.mdr20index != next.mdr20index : "from " + from.mdr20index + " to " + next.mdr20index;

                                // 1 map number, 2 lbl offset, 3 a flag
                                int mapNumber = read(1);
                                int val = read(3);
                                boolean repeated = (val & 0x800000) == 0;
                                int flg = read(1);

                                MapDetails map = details.getMap(mapNumber);
                                if (map != null) {
                                        int lblOffset = val & 0x7fffff;
                                        String name = map.getLabelText(lblOffset);

                                        info("%d map%d st=%s (%s%s)", record, mapNumber, name,
                                                        (repeated) ? "R" : "",
                                                        (flg == 0) ? "r" : "");
                                }

                                record++;
                        }
                }
        }

        private void check21() {
                setShowLogs(print[21]);
                info("mdr21 check");

                Section section = getSection(21);

                long start = section.getStart();
                long end = section.getEnd();

                int size = Section.numberOfBytes(getSection(7).getNumberOfRecords() << 1);
                if (section.getRecordSize() != size) {
                        error("mdr21: record size %d, byte required for mdr7 is %d", section.getRecordSize(), size);
                        error("  ... don't know how to deal with this version of the section");
                        return;
                }

                reader.position(start);
                String lastName = "";
                for (int i = 1; i < regionNames.size() - 1; i++) {
                        Mdr28Record from = regionNames.get(i);
                        if (from.getMdr21() == 0)
                                continue;

                        Mdr28Record next = null;
                        for (int j = i+1; next == null || next.getMdr21() == 0; j++)
                                next = regionNames.get(j);


                        String expectedName = from.getName();
                        info("mdr21 from %d to %d; expect region %s", from.getMdr21(), next.getMdr21(),
                                        expectedName);

                        for (int record = from.getMdr21(); record < next.getMdr21() && reader.position() < end; record++)
                        {
                                int val = read(size);

                                boolean repeated = (val & 1) == 0;
                                int idx = val >> 1;

                                List<Street> streetList = streets.get(idx);
                                if (streetList != null && !streetList.isEmpty()) {
                                        Street street = streetList.get(0);
                                        String name = street.getName();
                                        if (name.equals(lastName)) {
                                                if (!repeated)
                                                        error("%d map%d st=%d; not flagged when repeated", record, street.getMapIndex(), idx);
                                        } else {
                                                if (repeated)
                                                        error("%d map%d st=%d; flagged when not repeated", record, street.getMapIndex(), idx);
                                                lastName = name;
                                        }

                                        boolean found = false;
                                        for (Street s : streetList) {
                                                if (expectedName.equals(s.getRegionName())) {
                                                        found = true;
                                                        info("%d map%d st=%d, %s%s, r=%s, c=%s",
                                                                        record, s.getMapIndex(), idx,
                                                                        s.getName(), repeated ? " (R)" : "", s.getRegionName(),
                                                                        s.getCountryName());
                                                }
                                        }
                                        if (!found) {
                                                error("%d no road %d name=%s in region %s", record, idx, streetList.get(0).getName(), expectedName);
                                                for (Street s : streetList) {
                                                        error("  ... %s, in %s", s.getName(), s.getRegionName());
                                                }
                                        }
                                } else {
                                        // This is an error if we have saved all the maps
                                        if (details.mapSaved(numberOfMaps)) {
                                                error("%d; could not find any streets", idx);
                                        }
                                }
                        }
                }
        }

        private void check22() {
                setShowLogs(print[22]);
                info("mdr22 check");

                Section section = getSection(22);

                long start = section.getStart();
                long end = section.getEnd();

                int size = Section.numberOfBytes(getSection(7).getNumberOfRecords() << 1);
                if (section.getRecordSize() != size) {
                        error("mdr22: record size %d, byte required for mdr7 is %d", section.getRecordSize(), size);
                        error("  ... don't know how to deal with this version of the section");
                        return;
                }

                reader.position(start);
                String lastName = "";
                for (int i = 1; i < countryNames.size() - 1; i++) {
                        Mdr29Record from = countryNames.get(i);
                        if (from.getMdr22() == 0)
                                continue;

                        Mdr29Record next = null;
                        for (int j = i + 1; next == null || next.getMdr22() == 0; j++)
                                next = countryNames.get(j);

                        String expectedName = from.getName();
                        info("mdr22 from %d to %d; expect country %s", from.getMdr22(), next.getMdr22(),
                                        expectedName);

                        for (int record = from.getMdr22(); record < next.getMdr22() && reader.position() < end; record++)
                        {
                                int val = read(size);

                                boolean repeated = (val & 1) == 0;
                                int idx = val >> 1;

                                List<Street> streetList = streets.get(idx);
                                if (streetList != null && !streetList.isEmpty()) {
                                        Street street = streetList.get(0);
                                        String name = street.getName();
                                        if (name.equals(lastName)) {
                                                if (!repeated)
                                                        error("%d map%d st=%d; not flagged when repeated", record, street.getMapIndex(), idx);
                                        } else {
                                                if (repeated)
                                                        error("%d map%d st=%d; flagged when not repeated", record, street.getMapIndex(), idx);
                                                lastName = name;
                                        }

                                        boolean found = false;
                                        for (Street s : streetList) {
                                                if (expectedName.equals(s.getCountryName())) {
                                                        found = true;
                                                        info("%d map%d st=%d, %s%s, c=%s",
                                                                        record, s.getMapIndex(), idx,
                                                                        s.getName(), repeated ? " (R)" : "", s.getCountryName());
                                                }
                                        }
                                        if (!found) {
                                                error("%d no road %d name=%s in country %s", record, idx, streetList.get(0).getName(), expectedName);
                                                for (Street s : streetList) {
                                                        error("  ... %s, in %s", s.getName(), s.getCountryName());
                                                }
                                        }
                                } else {
                                        // This is an error if we have saved all the maps
                                        if (details.mapSaved(numberOfMaps)) {
                                                error("%d; could not find any streets", idx);
                                        }
                                }
                        }
                }
        }

        private void check23() {
                setShowLogs(print[23]);
                info("mdr23 check");

                Section section = getSection(23);

                long start = section.getStart();
                long end = section.getEnd();

                reader.position(start);
                int regionnum = 0;
                String lastName = "";
                int lastMapNumber = 0;
                while (reader.position() < end) {
                        regionnum++;

                        int mapNumber = readMapNumber();

                        int region = read(2);
                        int country = read(2);
                        int lbl = read(3);

                        int lblOffset = lbl & 0x7fffff;
                        boolean repeated = (lbl & 0x800000) == 0;

                        MapDetails map = details.getMap(mapNumber);
                        if (map != null) {
                                String name = map.getLabelText(lblOffset);
                                info("%d map%d; region %s, r %d, c %d", regionnum, mapNumber, name, region, country);

                                if (lastName.equals(name) && lastMapNumber == mapNumber) {
                                        error("%d map%d; region name repeated in same map %s",
                                                        regionnum, mapNumber, name);
                                }

                                if (repeated) {
                                        if (!lastName.equals(name)) {
                                                error("%d map%d; different name but has repeat flag, %s",
                                                                regionnum, mapNumber, name);
                                        }
                                } else {
                                        if (lastName.equals(name)) {
                                                error("%d map%d; same name but not flagged, %s",
                                                                regionnum, mapNumber, name);
                                        }
                                }
                                lastName = name;
                                lastMapNumber = mapNumber;
                        }
                }
        }

        private void check24() {
                setShowLogs(print[24]);
                info("mdr24 check");

                Section section = getSection(24);

                long start = section.getStart();
                long end = section.getEnd();

                reader.position(start);
                String lastName = "";
                int lastMapNumber = 0;
                for (int i = 1; i < countryNames.size() - 1; i++) {
                        Mdr29Record from = countryNames.get(i);
                        Mdr29Record next = countryNames.get(i + 1);
                        info("mdr24 from %d to %d", from.getMdr24(), next.getMdr24());

                        for (int countrynum = from.getMdr24(); countrynum < next.getMdr24() && reader.position() < end; countrynum++) {
                                String expectedCountry = from.getName();

                                int mapNumber = readMapNumber();

                                int country = read(2);
                                int lbl = read(3);

                                int lblOffset = lbl & 0x7fffff;
                                boolean repeated = (lbl & 0x800000) == 0;

                                MapDetails map = details.getMap(mapNumber);
                                if (map != null) {
                                        String name = map.getLabelText(lblOffset);
                                        info("%d map%d; country %s, %d", countrynum, mapNumber, name, country);
                                        checkEqual(expectedCountry, name, "%d map%d; country name", countrynum, mapNumber);

                                        if (lastName.equals(name) && lastMapNumber == mapNumber) {
                                                error("%d map%d; name repeated in same map %s",
                                                                countrynum, mapNumber, name);
                                        }

                                        if (repeated) {
                                                if (!lastName.equals(name)) {
                                                        error("%d map%d; different name but has repeat flag, %s",
                                                                        countrynum, mapNumber, name);
                                                }
                                        } else {
                                                if (lastName.equals(name)) {
                                                        error("%d map%d; same name but not flagged, %s",
                                                                        countrynum, mapNumber, name);
                                                }
                                        }
                                        lastName = name;
                                        lastMapNumber = mapNumber;
                                }
                        }
                }
        }

        /**
         * Cities by country.
         */

        private void check25() {
                setShowLogs(print[25]);
                info("mdr25 check");

                Section section = getSection(25);

                long start = section.getStart();
                long end = section.getEnd();

                int size = Section.numberOfBytes(getSection(5).getNumberOfRecords());
                if (section.getRecordSize() != size) {
                        error("mdr25 record size %d, bytes required for mdr5 is %d", section.getRecordSize(), size);
                        error("  ... don't know how to deal with this version of the section");
                        return;
                }

                reader.position(start);

                int lastMapNumber = 0;
                String lastCityName = "";
                int record = 0;
                while (reader.position() < end) {
                        record++;

                        int val = read(size);
                        Mdr20Info info = cityMdr20.get(val);

                        String countryName = info.countryName;

                        String cityName = info.cityName;
                        if (info.mapNumber == lastMapNumber && cityName.equals(lastCityName))
                                error("%d country %s; repeated city name %s in same map", record, countryName, cityName);

                        info("%d country %s; map%d city %d %s", record, countryName, info.mapNumber, val,
                                        cityName);

                        lastCityName = cityName;
                        lastMapNumber = info.mapNumber;
                }
        }

        /**
         * Mdr 28 ordered by country.
         */

        private void check26() {
                setShowLogs(print[26]);
                info("mdr26 check");

                Section section = getSection(26);

                long start = section.getStart();
                long end = section.getEnd();

                int size = Section.numberOfBytes(getSection(28).getNumberOfRecords());
                if (section.getRecordSize() != size) {
                        error("mdr26 record size %d, bytes required for mdr25 is %d", section.getRecordSize(), size);
                        error("  ... don't know how to deal with this version of the section");
                        return;
                }

                reader.position(start);
                for (int i = 1; i < countryNames.size() - 1; i++) {
                        Mdr29Record from = countryNames.get(i);
                        Mdr29Record next = countryNames.get(i + 1);
                        if (from.getMdr26() == 0)
                                continue;

                        info("mdr26 from %d to %d; country %s", from.getMdr26(), next.getMdr26(),
                                        from.getName());

                        for (int record = from.getMdr26(); record < next.getMdr26(); record++) {
                                if (reader.position() >= end) {
                                        error("mdr26 from %d; read beyond end of section", from.getMdr26());
                                        break;
                                }
                                int idx = read(size);
                                checkNotZero(idx, "%d: pointer zero", record);

                                Mdr28Record region = regionNames.get(idx);
                                if (region != null) {
                                        info("%d region %s; country %s", record, region.getName(),
                                                        region.getMdr14().getName());
                                }
                        }
                }
        }

        /**
         * Cities sorted by region.
         */

        private void check27() {
                setShowLogs(print[27]);
                info("mdr27 check");

                Section section = getSection(27);

                long start = section.getStart();
                long end = section.getEnd();

                int size = Section.numberOfBytes(getSection(5).getNumberOfRecords());
                if (section.getRecordSize() != size) {
                        error("mdr27 record size %d, bytes required for mdr5 is %d", section.getRecordSize(), size);
                        error("  ... don't know how to deal with this version of the section");
                        return;
                }

                reader.position(start);

                int lastMapNumber = 0;
                String lastCityName = "";
                for (int i = 1; i < regionNames.size() - 1; i++) {
                        Mdr28Record from = regionNames.get(i);
                        if (from.getMdr27() == 0)
                                continue;
                       
                        Mdr28Record next = regionNames.get(i + 1);

                        for (int j = i+2; next.getMdr27() == 0; j++)
                                next = regionNames.get(j);

                        assert next.getMdr27() != 0;
                        //checkNotZero(next.getMdr27(), "mdr27 %d; zero mdr27 pointer", from.getIndex());

                        String expectedName = from.getName();
                        info("mdr27 from %d to %d; expect region %s", from.getMdr27(), next.getMdr27(),
                                        expectedName);

                        for (int regionnum = from.getMdr27(); regionnum < next.getMdr27() && reader.position() < end; regionnum++) {

                                int val = read(size);
                                Mdr20Info info = cityMdr20.get(val);

                                String regionName = info.regionName;
                                checkEqual(expectedName, regionName, "%d mdr27 region name", regionnum);

                                String cityName = info.cityName;
                                if (info.mapNumber == lastMapNumber && cityName.equals(lastCityName))
                                        error("%d region %s; repeated city name %s in same map", regionnum, regionName, cityName);

                                info("%d region %s; map%d city %d %s", regionnum, regionName, info.mapNumber, val,
                                                cityName);

                                lastCityName = cityName;
                                lastMapNumber = info.mapNumber;
                        }
                }
        }

        private void check28() {
                setShowLogs(print[28]);
                info("mdr28 check");

                Section section = getSection(28);

                long start = section.getStart();
                long end = section.getEnd();

                int size21 = getSection(21).getBytesForRecords();
                int size23 = getSection(23).getBytesForRecords();
                int size27 = getSection(27).getBytesForRecords();

                regionNames.add(null);

                reader.position(start);
                int number = 0;

                BoundChecker bound23 = new BoundChecker("mdr23", getSection(23).getNumberOfRecords());
                BoundChecker bound21 = new BoundChecker("mdr21", getSection(21).getNumberOfRecords());
                BoundChecker bound27 = new BoundChecker("mdr27", getSection(27).getNumberOfRecords());
                while (reader.position() < end) {
                        number++;
                        int m23 = read(size23);
                        bound23.update(number, m23);

                        // XXX there must be a magic flag to control reading this
                        String name = readString();

                        int m21 = read(size21);
                        bound21.update(number, m21);

                        int m27 = read(size27);
                        bound27.update(number, m27);

                        info("%d m21 %d, m23 %d, m27 %d, %s", number, m21, m23, m27, name);

                        Mdr28Record reg = new Mdr28Record();
                        reg.setName(name);
                        reg.setMdr21(m21);
                        reg.setMdr23(m23);
                        reg.setMdr27(m27);
                        Mdr14Record country = new Mdr14Record();
                        reg.setMdr14(country);
                        regionNames.add(reg);
                }

                Mdr28Record last = new Mdr28Record();
                last.setMdr21(getSection(21).getNumberOfRecords() + 1);
                last.setMdr23(getSection(23).getNumberOfRecords() + 1);
                last.setMdr27(getSection(27).getNumberOfRecords() + 1);
                regionNames.add(last);
        }

        private void check29() {
                setShowLogs(print[29]);
                info("mdr29 check");

                countryNames.add(null);

                Section section = getSection(29);

                long start = section.getStart();
                long end = section.getEnd();
                int magic = section.getMagic();

                boolean hasStr = (magic & 0x1) != 0;
        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 size17 = (has17)? ((magic & 0x30) >> 4): 0;
        int size26 = (has26)? getSection(26).getBytesForRecords() : 0;
               
        reader.position(start);
                int number = 0;

                BoundChecker bound24 = new BoundChecker("mdr24", getSection(24).getNumberOfRecords());
                BoundChecker bound22 = new BoundChecker("mdr22", getSection(22).getNumberOfRecords());
                BoundChecker bound25 = new BoundChecker("mdr25", getSection(25).getNumberOfRecords());
                BoundChecker bound26 = has26? new BoundChecker("mdr26", getSection(26).getNumberOfRecords()): null;
                BoundChecker bound17 = has17? new BoundChecker("mdr17", getSection(17).getNumberOfRecords()): null;
                while (reader.position() < end) {
                        number++;
                        int m24 = read(size24);
                        bound24.update(number, m24);

                        // XXX there must be a magic flag to control reading this
                        String name = "";
                        if (hasStr)
                                name = readString();

                        int m22 = read(size22);
                        bound22.update(number, m22);

                        int m25 = read(size25);
                        bound25.update(number, m25);

            int m17_26 = 0; // Assume is either a 17 or 26 not both
            if (has26) {
                m17_26 = read(size26);
                bound26.update(number, m17_26);
            }
            if (has17) {
                m17_26 = read(size17);
                bound17.update(number, m17_26);
            }

                        info("%d m22 %d, m24 %d, m25 %d, m%s %d, %s", number, m22, m24, m25,
                                        has17 ? "17" : "26", m17_26, name);

                        Mdr29Record c = new Mdr29Record();
                        c.setName(name);
                        c.setMdr22(m22);
                        c.setMdr24(m24);
                        c.setMdr25(m25);
            c.setMdr26(m17_26);

                        countryNames.add(c);
                }

                Mdr29Record last = new Mdr29Record();
                last.setMdr22(getSection(22).getNumberOfRecords()+1);
                last.setMdr24(getSection(24).getNumberOfRecords() + 1);
                last.setMdr25(getSection(25).getNumberOfRecords() + 1);
                last.setMdr26(getSection(has17 ? 17 : 26).getNumberOfRecords() + 1);
                countryNames.add(last);
        }

        /**
         * Read in a map number, the number of bytes depends on the number of maps.
         * @return A map number. If the number read is out of range then an exception is
         * thrown.
         */

        private int readMapNumber() {
                int mn;
                if (numberOfMaps > 255)
                        mn = reader.getChar();
                else
                        mn = reader.get();
                if (mn > numberOfMaps) {
                        assert false: "not a map number " + mn;
                }
                return mn;
        }

        private String readString() {
                int off;
                if (getSection(15).getLen() > 0xffffff)
                        off = reader.getInt();
                else
                        off = reader.get3();

                return mdrStrings.getTextString(off);
        }

        private int read(int n) {
                switch (n) {
                case 1: return reader.get() & 0xff;
                case 2: return reader.getChar();
                case 3: return reader.getu3();
                case 4: return reader.getInt();
                }
                throw new IllegalArgumentException("integer size must be 1 to 4");
        }

        /**
         * Read mdr1 to obtain all the maps.
         */

        private void readMaps() {
                Section section = getSection(1);

                long start = section.getStart();
                long end = section.getEnd();
                int magic = section.getMagic();

                reader.position(start);
                int mapIndex = 0;
                while (reader.position() < end) {
                        mapIndex++;
                        int mapid = reader.getInt();
                        int offset = 0;
                        if ((magic & 0x1) != 0) {
                                offset = reader.getInt();
                        }

                        // Read in the actual map tile and save enough information to help us
                        // cross check the mdr file.
                        details.readMap(mapIndex, mapid, offset);
                }
        }

        /**
         * Read in the complete header.
         */

        private void readHeader() {
                readHeaderLen();

                int codePage = reader.getChar();
                String s = "cp" + codePage;
                charSet = Charset.forName(s);

                reader.getChar();
                reader.getChar();
                reader.getChar();

                Section sect = readSection(1, true, true);
                numberOfMaps = sect.getNumberOfRecords();

                readSection(2, true, true);
                readSection(3, true, true);
                readSection(4, true, true);
                readSection(5, true, true);
                readSection(6, true, true);
                readSection(7, true, true);
                readSection(8, true, true);
                readSection(9, true, true);
                readSection(10, false, true);
                readSection(11, true, true);
                readSection(12, true, true);
                readSection(13, true, true);
                readSection(14, true, true);
                readSection(15, false, false);
                getSection(15).setMagic(reader.get());
                readSection(16, true, true);
                readSection(17, false, true);
                readSection(18, true, true);
                readSection(19, true, true);

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

        /**
         * Read the header for a section.
         * @param number The section number.
         * @param hasRecSize True if the section to be read has a record size field of two bytes. It
         * will be read and saved.
         * @param hasMagic True if the section has a flags field of 4 bytes. It will be read and saved.
         * @return
         */

        protected Section readSection(int number, boolean hasRecSize, boolean hasMagic) {
                assert number != 0;

                int start = reader.getInt();
                int len = reader.getInt();
                Section section = new Section("", start, len);

                if (hasRecSize) {
                        int recordSize = reader.getChar();
                        section.setRecordSize(recordSize);
                }

                if (hasMagic) {
                        int magic = reader.getInt();
                        section.setMagic(magic);
                }

                while (sections.size() < number-1)
                        sections.add(null);
                sections.add(number-1, section);
                return section;
        }

        private static Map<Integer, Integer> makeNumberMapping(String name) {
                Map<Integer, Integer> numberMap = new HashMap<>();
                if (name.toLowerCase().endsWith("_mdr.img")) {
                        String base = name.substring(0, name.length() - 8);

                        String[] look = {
                                        base + ".MDX",
                                        base + ".mdx",
                                        base.toLowerCase() + ".MDX",
                                        base.toLowerCase() + ".mdx",
                        };
                        for (String fname : look) {
                                RandomAccessFile raf = null;
                                try {
                                        raf = new RandomAccessFile(fname, "r");
                                        ImgChannel chan = new FileImgChannel(raf.getChannel());
                                        MdxFileReader mdx = new MdxFileReader(chan);
                                        List<MapInfo> maps = mdx.getMaps();
                                        for (MapInfo bl : maps) {
                                                int mapNumber = bl.getMapname();
                                                int hexNumber = bl.getHexMapname();
                                                numberMap.put(hexNumber, mapNumber);
                                        }
                                        break;
                                } catch (IOException e) {
                                        // can't find a usable mdx file, try the next
                                } finally {
                                        Utils.closeFile(raf);
                                }
                        }
                } else if (name.toLowerCase().endsWith(".img")) {
                        FileSystem fs = null;
                        try {
                                fs = ImgFS.openFs(name);
                                List<DirectoryEntry> list = fs.list();
                                for (DirectoryEntry ent : list) {
                                        if (ent.getExt().equalsIgnoreCase("mps")) {
                                                String fullName = ent.getFullName();
                                                ImgChannel r = fs.open(fullName, "r");
                                                MpsFileReader mps = new MpsFileReader(r);
                                                List<MapBlock> maps = mps.getMaps();
                                                for (MapBlock bl : maps) {
                                                        int mapNumber = bl.getMapNumber();
                                                        int hexNumber = bl.getHexNumber();
                                                        numberMap.put(hexNumber, mapNumber);
                                                }
                                                System.out.println("# Using mps file");
                                                break;
                                        }
                                }
                        } catch (FileNotFoundException e) {
                                // There isn't a usable mps file.
                        } finally {
                                Utils.closeFile(fs);
                        }
                }
                return numberMap;
        }

        public void setNumberMap(Map<Integer,Integer> numberMap) {
                this.details.setNumberMap(numberMap);
        }

        public void extraArgs(Map<String, String> args) {
                System.out.println("EXTRA");
                String val = args.remove("errors");
                if (val != null) {
                        // show errors only
                        setShowLogs(false);
                        Arrays.fill(print, false);
                }

                val = args.remove("print");
                if (val != null) {
                        // only print logs for the given sections
                        String[] strings = val.split(",");
                        Arrays.fill(print, false);
                        for (String sect : strings)
                                print[Integer.parseInt(sect)] = true;
                }

                Map<Integer, Integer> numberMap = makeNumberMapping(getName());
                setNumberMap(numberMap);

                // Anything else is common or an error
                System.out.println("remaining " + args);
                super.extraArgs(args);
        }

        public static void main(String[] args) {
                runMain(MdrCheck.class, "MDR", args);
        }

        private static class Mdr20Info {
                private int mdr20index;
                private String cityName;
                private int mapNumber;
                private String regionName;
                private String countryName;

                private Mdr20Info(int mdr20index) {
                        this.mdr20index = mdr20index;
                }

                public boolean sameCity(Mdr20Info prev) {
                        return prev != null
                                        && cityName.equals(prev.cityName)
                                        && regionName.equals(prev.regionName)
                                        && countryName.equals(prev.countryName);
                }
        }
   
    private static class TypeInfo {
        int type;
        int start;
    }
}