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