/*
* Copyright (C) 2009.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package test.display;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import test.display.check.MdrStrings;
import test.util.BitReaderLR;
import uk.me.parabola.imgfmt.app.srt.Sort;
/**
* Standalone program to display an MDR file. There is no freely available
* knowledge of the file format.
*
* Can produce a massive file, so may take some time to write.
*
* @author Steve Ratcliffe
*/
public class MdrDisplay
extends CommonDisplay
{
private static final int NSECT =
41;
// Sections to be displayed
private final boolean[] wanted =
new boolean[NSECT
];
private int numberOfMaps
; // The number of maps
private boolean compressedStrings
;
private int citySizeFlagged
;
private Charset charset
;
MdrStrings strings
;
private int recordStart
;
private int recordCount
;
private HuffmanDecoder decoder
;
private boolean huffmanDecodingFailed
;
@
Override
protected void print
() {
readCommonHeader
();
printHeader
();
Section end =
new Section
("END OF FILE", filelen,
0);
addSection
(end
);
analyze
(outStream
);
if (compressedStrings
) {
try {
Displayer d =
new Displayer
(reader
);
decoder = readHuffmanTable
(d, getSection
(16).
getStart());
} catch (Exception e
) {
huffmanDecodingFailed =
true;
}
}
/*
* Uses reflection to find a method to print a section. If
* none is found then a generic method is used to print the
* section
*/
for (int i =
1; i
< numberOfSections
(); i++
) {
if (!want
(i
))
continue;
try {
Class<MdrDisplay
> c = MdrDisplay.
class;
Method method = c.
getDeclaredMethod("printSect" + i
);
if (i ==
16 && !huffmanDecodingFailed
) {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 16 (decompression codebook Huffman tree)");
readHuffmanTable
(d, getSection
(16).
getStart());
d.
print(outStream
);
} else {
method.
invoke(this);
}
} catch (NoSuchMethodException e
) {
printSectUnknown
(i
);
} catch (InvocationTargetException e
) {
Throwable targetException = e.
getTargetException();
System.
out.
printf("Failed in section %d: %s\n", i, targetException
);
} catch (IllegalAccessException e
) {
e.
printStackTrace();
}
}
}
/**
* Prints an unknown section. If there is a record size then each
* record is printed separately.
*/
private void printSectUnknown
(int n
) {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR " + n +
" (unknown)");
Section s = getSection
(n
);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
if (s.
getRecordSize() ==
0)
d.
rawValue(s.
getLen());
else {
int recno =
0;
while (reader.
position() < s.
getEnd()) {
d.
rawValue(s.
getRecordSize(),
"record " + recno++
);
recordPrint
(d
);
}
}
d.
print(outStream
);
}
public void setStart
(int start
) {
this.
recordStart = start
;
}
class MapInfo
{
int mapIndex
;
int mapid
;
int offset
;
int sublen
;
}
@
SuppressWarnings("unused")
private void printSect1
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 1 (maps)");
Section s = getSection
(1);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
List<MapInfo
> maps =
new ArrayList<>();
int index =
1;
long start
;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Map %d", index
);
MapInfo mi =
new MapInfo
();
mi.
mapIndex = index++
;
mi.
mapid = d.
intValue("mapname %d");
if (recsize
> 4)
mi.
offset = d.
intValue("offset to subsection %x");
if (recsize
> 8) {
int end = d.
intValue("end of subsection %x");
mi.
sublen = end - mi.
offset;
} else {
mi.
sublen = -
1; // will be calculated later
}
printRemainder
(d, recsize, start
);
maps.
add(mi
);
}
// mdr1 record follows the reverse index, so add fake entry which points to the start
// to be able to calculate the last length
MapInfo mi =
new MapInfo
();
mi.
offset =
(int) s.
getStart();
maps.
add(mi
);
d.
print(outStream
);
printSect1Mapsect
(maps
);
}
/**
* Contains a set of pointers and sizes elsewhere in the file.
* The lengths are I guess a number of items as they
* need to be multiplied by a number to get the true length as
* calculated by looking at the start offsets.
*/
private void printSect1Mapsect
(List<MapInfo
> maps
) {
for (int n =
0; n
< maps.
size() -
1; n++
) {
MapInfo mi = maps.
get(n
);
int suboff = mi.
offset;
if (suboff ==
0)
continue;
if (mi.
sublen < 0)
mi.
sublen = maps.
get(n +
1).
offset - suboff
;
Mdr1SubFileDisplay subFileDisplay =
new Mdr1SubFileDisplay
(reader, outStream, mi
);
subFileDisplay.
print();
}
}
/**
* A list of POI types without subtypes
*/
@
SuppressWarnings("unused")
private void printSect3
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 3 (polygon types)");
Section s = getSection
(3);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
int record = reader.
get2u();
DisplayItem item = d.
item();
item.
setBytes2(record
);
item.
addText("Type 0x%x. Unknown 0x%x",
record
& 0xff,
record
>> 8 & 0xff
);
}
d.
print(outStream
);
}
/**
* A list of POI types.
*/
@
SuppressWarnings("unused")
private void printSect4
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 4 (POI types)");
Section s = getSection
(4);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
int record = reader.
get3u();
DisplayItem item = d.
item();
item.
setBytes3(record
);
item.
addText("Type 0x%04x. Unknown 0x%x",
((record
>> 16) & 0xff
) +
((record
& 0xff
) << 8),
(record
>> 8) & 0xff
);
}
d.
print(outStream
);
}
/**
* A list of cities. It is alphabetically arranged.
*/
@
SuppressWarnings("unused")
private void printSect5
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 5 (cities)");
Section s = getSection
(5);
int recsize = s.
getRecordSize();
d.
setSectStart(s.
getStart());
int cityPtrSize =
(s.
getMagic() & 0x3
) +
1;
boolean hasRegion =
(s.
getMagic() & 0x4
) !=
0;
boolean hasStr =
(s.
getMagic() & 0x8
) !=
0;
boolean has20 =
(s.
getMagic() & 0x100
) !=
0;
boolean has20offset =
(s.
getMagic() & 0x800
) !=
0;
int mdr20PointerSize =
0;
if (getSection
(20).
getLen() > 0) {
if (has20
)
mdr20PointerSize = getSection
(20).
getBytesForRecords();
if (has20offset
)
mdr20PointerSize = getSection
(20).
getBytesForSize();
}
boolean has28_29_offset =
(s.
getMagic() & 0x400
) !=
0;
int mdr28_29PointerSize =
0;
if (has28_29_offset
)
mdr28_29PointerSize = getSection
(28).
getBytesForRecords();
if (has28_29_offset
&& mdr20PointerSize ==
0)
mdr28_29PointerSize = getSection
(29).
getBytesForRecords();
//boolean has44 = (s.getMagic() & 0x2000) != 0;
//int mdr44PointerSize = getSection(44).getBytesForRecords();
reader.
position(s.
getStart());
int citynum =
0;
while (reader.
position() < s.
getEnd()) {
int start =
(int) reader.
position();
d.
gap();
d.
item().
addText("City index %d", ++citynum
);
printMapIndex
(d
);
putValue
(d,
"the %d city in the map", cityPtrSize
);
DisplayItem item = d.
int3Item();
item.
addText("offset in LBL 0x%06x", item.
getValue() & ~0x800000
);
if (hasRegion
) {
item = d.
charItem();
item.
addText("region %d", item.
getValue() & 0x3fff
);
}
if (hasStr
)
printTextLabel
(d
);
if (has20 || has20offset
)
d.
intValue(mdr20PointerSize,
"mdr20 %d");
if (has28_29_offset
)
d.
intValue(mdr28_29PointerSize,
"mdr28/29 %d");
int remain =
(int) (start + s.
getRecordSize() - reader.
position());
assert remain
>=
0;
if (remain
> 0)
d.
rawValue(remain
);
recordPrint
(d
);
}
d.
print(outStream
);
}
private void recordPrint
(Displayer d
) {
d.
print((++recordCount
< recordStart
)? null: outStream
);
d.
setTitle(null);
}
protected void putValue
(Displayer d,
String text,
int n
) {
switch (n
) {
case 1: d.
byteValue(text
); break;
case 2: d.
charValue(text
); break;
case 3: d.
int3Value(text
); break;
case 4: d.
intValue(text
); break;
}
}
/**
* A list of zips. It is alphabetically arranged.
*/
@
SuppressWarnings("unused")
private void printSect6
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 6 (ZIPs)");
Section s = getSection
(6);
int recsize = s.
getRecordSize();
d.
setSectStart(s.
getStart());
int zipPtrSize =
(s.
getMagic() & 0x3
) +
1;
d.
item().
addText("Zip size: "+zipPtrSize
);
boolean hasString =
(s.
getMagic() & 0x4
) !=
0;
reader.
position(s.
getStart());
int zipnum =
0;
while (reader.
position() < s.
getEnd()) {
d.
item().
addText("zip index %d", ++zipnum
);
printMapIndex
(d
);
putValue
(d,
"the %d zip in the map", zipPtrSize
);
if (hasString
)
printTextLabel
(d
);
}
d.
print(outStream
);
}
/**
* Contains an ordered list of pointers to street names.
*/
@
SuppressWarnings("unused")
private void printSect7
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 7 (street names)");
Section s = getSection
(7);
int magic = s.
getMagic();
boolean hasStr =
(magic
& 0x01
) !=
0;
boolean hasUnk1 =
(magic
& 0x20
) !=
0;
//boolean hasUnk2 = (magic & 0x40) != 0;
//int unk2size = (magic >> 7) & 0x3;
int unk2size =
(magic
>> 6) & 0x7
;
int unk2split =
((magic
>> 9) & 0xf
);
int splitMask =
(1 << unk2split
) -
1;
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
while (reader.
position() < s.
getEnd()) {
DisplayItem item = d.
item();
item.
addText("Record %d", count++
);
long itemStart = reader.
position();
printMapIndex
(d
);
item = d.
int3Item();
int off = item.
getValue();
item.
addText("Pointer back into LBL: %06x", off
& ~0x800000
);
if ((off
& 0x800000
) ==
0)
item.
addText("Repeated name");
if (hasStr
)
printTextLabel
(d
);
if (hasUnk1
)
d.
byteValue("name offset %d");
item = d.
intItem(unk2size
);
int val = item.
getValue();
item.
addText("%s sort seq p=%-3d s=%-3d",
(val
& 1) ==
1 ? "N" :
"R",
(val
>> 1) & splitMask,
(val
>>> (unk2split +
1)));
d.
rawValue((int) (itemStart + recsize - reader.
position()));
d.
gap();
recordPrint
(d
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect8
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 8 (index into streets)");
print8_12
(d,
8);
}
/**
* POI / Polygon groups.
*/
@
SuppressWarnings("unused")
private void printSect9
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 9 (Index into mdr10, grouped based on type)");
Section s = getSection
(9);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
d.
byteValue("group %d");
DisplayItem item = d.
item();
item = d.
intItem(recsize -
1);
int val = item.
getValue();
item.
addText("record in MDR10 %d", val
);
}
d.
print(outStream
);
}
/**
* POI types. Also has a pointer into MDR 11. Seems like a waste.
*/
@
SuppressWarnings("unused")
private void printSect10
() {
// Looks variable length
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 10 (POI types)");
Section s = getSection
(10);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
int count =
1;
// Calculate record size by determining how many points there are in MDR11
int nPoints = getSection
(11).
getNumberOfRecords();
while (reader.
position() < s.
getEnd()) {
d.
byteValue("[sub]type 0x%x");
DisplayItem item = d.
item();
int c
;
boolean isRepeat =
true;
if (nPoints
< 0x80
) {
c = item.
setBytes1(reader.
get1u());
if ((c
& 0x80
) !=
0) {
isRepeat =
false;
c
&= ~0x80
;
}
} else if (nPoints
< 0x8000
) {
c = item.
setBytes2(reader.
get2u());
if ((c
& 0x8000
) !=
0) {
isRepeat =
false;
c
&= ~0x8000
;
}
} else {
c = item.
setBytes3(reader.
get3u());
if ((c
& 0x800000
) !=
0) {
isRepeat =
false;
c
&= ~0x800000
;
}
}
item.
addText("[%d] rec in MDR11 %d %s", count++, c, isRepeat
? "(repeated name)":
"");
}
d.
print(outStream
);
}
/**
* Show a city ref
*/
private void printSect11_City
(Displayer d
) {
// if this is a city there is a reference to it.
DisplayItem item = d.
item();
int mask =
0;
int c =
0;
switch (citySizeFlagged
) {
case 1:
// There is supposed to be a min size of 2, but handle anyway
c = item.
setBytes1(reader.
get1u());
mask = 0x80
;
break;
case 2:
c = item.
setBytes2(reader.
get2u());
mask = 0x8000
;
break;
case 3:
c = item.
setBytes3(reader.
get3u());
mask = 0x800000
;
break;
case 4:
c = item.
setBytes4(reader.
get4());
mask = 0x80000000
;
break;
}
if (c
> 0) {
if ((c
& mask
) ==
0)
item.
addText("Region %d (in map)", c
);
else
item.
addText("City %d (MDR5)", c
& ~mask
);
}
}
/**
* These records contain the map the subdivision and the point (or line?) number
* and the offset in LBL. If it has an associated city (or is a city itself), there is
* a reference back into MDR 5.
*/
@
SuppressWarnings("unused")
private void printSect11
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 11 (POIs)");
Section s = getSection
(11);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize() - mapIndexSize
();
d.
item().
addText("section flags %x", s.
getMagic());
reader.
position(s.
getStart());
int count =
1;
while (reader.
position() < s.
getEnd()) {
int startPos =
(int) reader.
position();
// records in sect 1 appear to point here
d.
gap();
d.
item().
addText("record %d", count++
);
// The first values are always present.
printMapIndex
(d
);
d.
byteValue("Point number %d");
d.
charValue("In subdiv %d");
d.
int3Value("LBL offset %06x");
if ((s.
getMagic() & 0xb00
) == 0xb00
)
d.
charValue("???");
else if ((s.
getMagic() & 0x900
) == 0x900
)
d.
byteValue("???");
if (recsize
>=
8)
printSect11_City
(d
);
if (recsize
> 10 && (s.
getMagic() & 0x2
) !=
0)
printTextLabel
(d
);
int remain =
(int) (s.
getRecordSize() -
(reader.
position() - startPos
));
if (remain
> 0)
d.
rawValue(remain
);
recordPrint
(d
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect12
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 12 (index into POI)");
print8_12
(d,
12);
}
private void print8_12
(Displayer d,
int sectNumber
) {
// Get the number of records in the previous section
Section s = getSection
(sectNumber-
1);
int sizePrev = s.
getBytesForRecords();
s = getSection
(sectNumber
);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
int flags = s.
getMagic();
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
long start = reader.
position();
d.
stringValue((flags
>>8),
"Prefix:%s");
d.
intValue(sizePrev,
"record %d");
d.
rawValue((int) (recsize -
(reader.
position() - start
)));
}
d.
print(outStream
);
}
/**
* This is an ordered list containing pointers into the text strings,
* probably for regions, arranged per map.
*/
@
SuppressWarnings("unused")
private void printSect13
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 13 (regions)");
Section s = getSection
(13);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
int record =
0;
while (reader.
position() < s.
getEnd()) {
int start =
(int) reader.
position();
d.
item().
addText("Region %d", ++record
);
printMapIndex
(d
);
d.
charValue("number in map %d");
d.
charValue("country %x");
printTextLabel
(d
);
int remain =
(int) (start+s.
getRecordSize() - reader.
position());
if (remain
> 0)
d.
rawValue(remain
);
d.
gap();
}
d.
print(outStream
);
}
/**
* Countries for each map.
*/
@
SuppressWarnings("unused")
private void printSect14
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 14 (country)");
Section s = getSection
(14);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
int start =
(int) reader.
position();
printMapIndex
(d
);
d.
charValue("Number in map %d");
// probably country...
printTextLabel
(d
);
int remain =
(int) ((start + s.
getRecordSize()) - reader.
position());
if (remain
> 0)
d.
rawValue(remain
);
}
d.
print(outStream
);
}
/**
* This is the actual text strings that are being indexed.
* There are two formats possible, either a simple straightforward
* one or with Huffman encoded strings.
*/
@
SuppressWarnings("unused")
private void printSect15
() {
Displayer d =
new Displayer
(reader
);
if (compressedStrings
)
d.
setTitle("MDR 15 (compressed strings)");
else
d.
setTitle("MDR 15 (strings)");
Section s = getSection
(15);
long start = s.
getStart();
int sLen = s.
getLen();
long end = s.
getEnd();
d.
setSectStart(start
);
reader.
position(start
);
if (compressedStrings
&& huffmanDecodingFailed
) {
// don't know how to decode
d.
rawValue(sLen,
"compressed");
return;
}
// If we have already read the strings then don't bother to do it all again.
// If you want to see the string table then just view it separately.
if (strings
!=
null) {
d.
item().
addText("Strings omitted, call with just --print=15 on the command line to see");
} else {
if (compressedStrings
)
decoder.
initFreq();
int count =
0;
int offset =
0;
byte[] buffer =
new byte[1024];
int len =
0;
while (reader.
position() < end
) {
String text =
null;
long oldPos = reader.
position();
if (compressedStrings
) {
text = decoder.
decode(new BitReaderLR
(reader,
(int) (end - oldPos
)),
false, charset
);
} else {
len =
0;
while (text ==
null) {
byte b = reader.
get();
if (b ==
0) {
text =
new String(buffer,
0, len, charset
);
} else {
buffer
[len++
] = b
;
}
}
}
int inputLen =
(int) (reader.
position() - oldPos
);
reader.
position(oldPos
);
DisplayItem item = d.
item();
item.
setBytes(reader.
get(inputLen
));
item.
addText("text: %s", text
);
count++
;
}
d.
item().
addText("%d records", count
);
}
d.
print(outStream
);
if (compressedStrings
) {
outStream.
println("frequencies:" +
Arrays.
toString(decoder.
getFreq()));
}
}
@
SuppressWarnings("unused")
private void printSect16
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 16 (decompression codebook Huffman tree)");
Section s = getSection
(16);
long start = s.
getStart();
d.
setSectStart(start
);
reader.
position(start
);
d.
rawValue((s.
getLen()));
d.
print(outStream
);
}
/**
* MDR 17: String index table
*
* My personal guess is, that this is a search table for
* sub strings starting with the given short string
*/
@
SuppressWarnings("unused")
private void printSect17
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 17 (indexes based on initial sub strings)");
Section s = getSection
(17);
d.
setSectStart(s.
getStart());
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
int peek = 0xff
& d.
byteValue("Peek");
DisplayItem item = d.
item();
int partlength =
0;
if ((peek
& 1) ==
1) {
partlength = peek
;
partlength
>>=
1;
}
if ((peek
& 3) ==
2) {
partlength = reader.
get1u();
item.
setBytes1(partlength
);
partlength
<<=
8;
partlength += peek
;
partlength
>>=
2;
}
if ((peek
& 7) ==
4) {
partlength = reader.
get2u();
item.
setBytes2(partlength
);
partlength
<<=
8;
partlength += peek
;
partlength
>>=
3;
}
item.
addText("Length of following part: %d", partlength
);
int sectHeader = -
1;
if (partlength
> 2) {
item = d.
charItem();
sectHeader = item.
getValue();
item.
addText("Some header: 0x%04x", sectHeader
);
partlength -=
2;
}
if (sectHeader == 0x8000
) {
//TODO
d.
rawValue(s.
getLen(),
"please implement");
break;
}
// This can't be the whole story. Header flags from other sections help determine?
int strLen =
(sectHeader
>> 8) +
1;
int indLen = sectHeader
& 0xff
;
if (strLen ==
4)
indLen -= 0x10
;
indLen =
(indLen-
9)/
10 +
1;
item.
addText("str len %d, index len %d", strLen, indLen
);
partlength /= strLen + indLen
;
int record =
0;
for (int i =
0; i
< partlength
; i++
) {
record++
;
d.
stringValue(strLen,
String.
format("[%d] str: %%s", record
));
d.
intValue(indLen,
"Index? %d");
}
}
d.
print(outStream
);
}
/**
* MDR 18: Looks like a mapping to MDR 11 or MDR 19
*/
@
SuppressWarnings("unused")
private void printSect18
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 18 (poi type index into 19)");
Section s = getSection
(18);
d.
setSectStart(s.
getStart());
int size19 = getSection
(19).
getBytesForRecords();
reader.
position(s.
getStart());
while (reader.
position() < s.
getEnd()) {
DisplayItem item = d.
charItem();
int type = item.
getValue();
if (type == 0xffff
)
break;
item.
addText("Type: %02x/%02x",
(type
>>5) & 0xff, type
& 0x1f
);
d.
intValue(size19,
"Idx into MDR 19: %d");
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect19
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 19 (poi sorted by type)");
Section s = getSection
(19);
d.
setSectStart(s.
getStart());
int poiSize = getSection
(11).
getBytesForRecords();
int mask =
0;
if (poiSize ==
1)
mask = 0x80 -
1;
else if (poiSize ==
2)
mask = 0x8000 -
1;
else if (poiSize ==
3)
mask = 0x800000 -
1;
reader.
position(s.
getStart());
int count =
1;
while (reader.
position() < s.
getEnd()) {
DisplayItem item = d.
intItem(poiSize
);
int val = item.
getValue();
item.
addText("record %d: poi index %d", count++, val
& mask
);
}
d.
print(outStream
);
}
private void printSect2x
(String title,
int sectNum
) {
Displayer d =
new Displayer
(reader
);
d.
setTitle(title
);
Section s = getSection
(sectNum
);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
int magic = s.
getMagic();
reader.
position(s.
getStart());
if (recsize ==
0) {
printRemainder
(d, s.
getLen(), s.
getStart());
d.
print(outStream
);
return;
}
int count =
1;
int record =
1;
int lastid = -
1;
long start
;
int flgSize =
(magic
>> 3) & 0x7
;
int unk2split =
((magic
>> 6) & 0xf
);
int splitMask =
(1 << unk2split
) -
1;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Mdr-%d Record %d", sectNum, record++
);
if ((magic
& 0x2
) !=
0) {
d.
byteValue("map %d");
DisplayItem item = d.
int3Item();
int val = item.
getValue();
item.
addText("lbl 0x%06x%s", val
& 0x7fffff,
(val
& 0x800000
) !=
0 ? " F" :
"");
if ((magic
& 0x4
) !=
0) {
d.
byteValue("name offset %d");
}
if (flgSize
> 0) {
item = d.
intItem(flgSize
);
val = item.
getValue();
// item.addText("%ssplitInfo=%d", (val & 1) == 0 ? "[r] " : "", val >> 1);
item.
addText("%s sort seq p=%-3d s=%-3d",
(val
& 1) ==
1 ? "N" :
"R",
(val
>> 1) & splitMask,
(val
>>> (unk2split +
1)));
}
if ((magic
& 0x800
) !=
0) {
item = d.
intItem(recsize
);
int v = item.
getValue();
// Print a message whenever the id is less than the previous
// one. Not completely
// foolproof, but mostly shows where the boundaries are. By
// counting the groups
// you can guess what is pointing inward and work from there.
int id = v
>> 1;
if (id
< lastid
)
item.
addText("# New group %d", count++
);
lastid = id
;
item.
addText("street %d", id
);
item.
addText("flag %b",
(v
& 1) ==
1);
}
} else {
DisplayItem item = d.
intItem(recsize
);
int val = item.
getValue();
int id = val
>> 1;
if (id
< lastid
)
item.
addText("# New group %d", count++
);
lastid = id
;
item.
addText("street %d %s", id,
(val
& 1) ==
1 ? "" :
"R");
}
printRemainder
(d, recsize, start
);
d.
gap();
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect20
() {
printSect2x
("MDR 20 (streets by city)",
20);
}
@
SuppressWarnings("unused")
private void printSect21
() {
printSect2x
("MDR 21 (streets by region)",
21);
}
@
SuppressWarnings("unused")
private void printSect22
() {
printSect2x
("MDR 22 (streets by country)",
22);
}
@
SuppressWarnings("unused")
private void printSect23
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 23 (sorted regions)");
Section s = getSection
(23);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
int record =
1;
long start
;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
printMapIndex
(d
);
d.
charValue("region in tile %d");
d.
charValue("country in tile %d");
DisplayItem item = d.
int3Item();
int value = item.
getValue();
item.
addText("lbl offset %x", value
& 0x7fffff
);
printRemainder
(d, recsize, start
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect24
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 24 (sorted countries)");
Section s = getSection
(24);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
int record =
1;
long start
;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
printMapIndex
(d
);
d.
charValue("country in tile %d");
DisplayItem item = d.
int3Item();
int value = item.
getValue();
item.
addText("lbl offset %x", value
& 0x7fffff
);
printRemainder
(d, recsize, start
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect25
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 25 (cities by country)");
Section s = getSection
(25);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
int record =
1;
int lastid = -
1;
long start
;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
DisplayItem item = d.
intItem(recsize
);
int v = item.
getValue();
if (v
< lastid
)
item.
addText("# New group %d", count++
);
lastid = v
;
item.
addText("city %d", v
);
printRemainder
(d, recsize, start
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect26
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 26 (mdr28 ordered by country)");
Section s = getSection
(26);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
int record =
1;
int lastid =
Integer.
MAX_VALUE;
long start
;
int[] max =
new int[1];
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
DisplayItem item = d.
intItem(recsize
);
int m = item.
getValue();
if (m
< lastid
)
d.
item().
addText("New group %d", count++
);
lastid = m
;
item.
addText("m28 record %d", m
);
if (m
> max
[0]) max
[0] = m
;
printRemainder
(d, recsize, start
);
}
for (int i : max
)
d.
item().
addText("max %d", i
);
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect27
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 27 (cities by region?)");
Section s = getSection
(27);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
reader.
position(s.
getStart());
int count =
1;
int record =
1;
int lastid = -
1;
long start
;
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
DisplayItem item = d.
intItem(recsize
);
int v = item.
getValue();
if (v
< lastid
)
item.
addText("# New group %d", count++
);
lastid = v
;
item.
addText("city %d", v
);
printRemainder
(d, recsize, start
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect28
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 28 (region names with pointers)");
Section s = getSection
(28);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
int magic = s.
getMagic();
int size21 = getSection
(21).
getBytesForRecords();
int size23 = getSection
(23).
getBytesForRecords();
int size27 = getSection
(27).
getBytesForRecords();
int strSize =
0;
if ((magic
& 0x01
) !=
0)
strSize =
(getSection
(15).
getLen() > 0xffffff
) ? 4 :
3;
reader.
position(s.
getStart());
int count =
1;
int record =
1;
int lastid = -
1;
long start
;
int[] max =
new int[4];
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
// These will not work on all maps, until the sizes are correct
int m = d.
intValue(size23,
"mdr23 record %d");
if (m
> max
[0]) max
[0] = m
;
if (strSize
> 0)
printTextLabel
(d
);
if ((magic
& 2) !=
0) {
m = d.
intValue(size21,
"mdr21 record %d");
if (m
> max
[2])
max
[2] = m
;
}
if ((magic
& 4) !=
0) {
m = d.
intValue(size27,
"mdr27 record %d");
if (m
> max
[3])
max
[3] = m
;
}
printRemainder
(d, recsize, start
);
}
for (int m : max
)
d.
item().
addText("max %d", m
);
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect29
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR 29 (country names with pointers)");
Section s = getSection
(29);
int magic = s.
getMagic();
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
boolean hasStr =
(magic
& 0x1
) !=
0; // guessed
boolean has17 =
(magic
& 0x30
) !=
0;
boolean has26 =
(magic
& 0x8
) !=
0;
int size24 = getSection
(24).
getBytesForRecords();
int size22 = getSection
(22).
getBytesForRecords();
int size25 = getSection
(5).
getBytesForRecords(); // NB appears to be size of 5, not 25 (they are related of course).
int size26 =
(has26
)? getSection
(26).
getBytesForRecords() :
0;
int size17 =
(has17
)? (magic
& 0x30
) >> 4 :
0;
reader.
position(s.
getStart());
int count =
1;
int record =
1;
int lastid = -
1;
long start
;
int[] max =
new int[5];
while ((start = reader.
position()) < s.
getEnd()) {
d.
item().
addText("Record %d", record++
);
int m = d.
intValue(size24,
"mdr24 record %d");
if (m
> max
[0]) max
[0] = m
;
if (hasStr
)
printTextLabel
(d
);
m = d.
intValue(size22,
"mdr22 record %d");
if (m
> max
[2]) max
[2] = m
;
m = d.
intValue(size25,
"mdr25 record %d");
if (m
> max
[3]) max
[3] = m
;
if (has26
) {
m = d.
intValue(size26,
"mdr26 record %d");
if (m
> max
[4]) max
[4] = m
;
}
if (has17
) {
m = d.
intValue(size17,
"mdr17 record %d");
if (m
> max
[4]) max
[4] = m
;
}
printRemainder
(d, recsize, start
);
}
for (int m : max
)
d.
item().
addText("max %d", m
);
d.
print(outStream
);
}
private void print30Or32
(int sectNum,
final String title
) {
Displayer d =
new Displayer
(reader
);
d.
setTitle(title
);
Section s = getSection
(sectNum
);
d.
setSectStart(s.
getStart());
int recsize = s.
getRecordSize();
int numNext = sectNum +
1;
Section sNext = getSection
(numNext
);
int offsize = sNext.
getBytesForSize();
int unksize = recsize - offsize
;
int maxOff = sNext.
getLen();
reader.
position(s.
getStart());
final String msg =
"Offset in mdr" + numNext +
": %d";
while (reader.
position() < s.
getEnd()) {
int off = d.
intValue(offsize, msg
);
d.
intValue(unksize,
"unk %d");
int nextOff
;
if (reader.
position()< s.
getEnd()) {
nextOff = reader.
getNu(offsize
);
reader.
position(reader.
position() - offsize
);
} else {
nextOff = maxOff
;
}
long saveOff = reader.
position();
reader.
position(sNext.
getStart()+ off
);
byte[] bytes = reader.
get(nextOff - off
);
DisplayItem item = d.
item();
item.
addText("mdr%d string: %s", numNext,
new String(bytes, charset
));
reader.
position(saveOff
);
}
d.
print(outStream
);
}
@
SuppressWarnings("unused")
private void printSect30
() {
print30Or32
(30,
"MDR 30 (pointers to mdr31)");
}
@
SuppressWarnings("unused")
private void printSect32
() {
print30Or32
(32,
"MDR 32 (pointers to mdr33)");
}
private void printRemainder
(Displayer d,
int recsize,
long start
) {
int rem =
(int) ((start + recsize
) - reader.
position());
if (rem ==
0)
return;
if (rem
> 0 && rem
<=
4)
d.
intValue(rem,
"unk %d");
else
d.
rawValue(rem
);
recordPrint
(d
);
}
/**
* Get a string from the string section.
* @param d The displayer to use.
*/
private void printTextLabel
(Displayer d
) {
DisplayItem item = d.
item();
int off
;
if (getSection
(15).
getLen() > 0xffffff
)
off = item.
setBytes4(reader.
get4());
else
off = item.
setBytes3(reader.
get3u());
String str = getTextString
(off
);
item.
addText("Pointer to string: %s", str
);
}
/**
* Get a string from the string section.
* @param off The offset in the text section.
* @return The resultant string.
*/
private String getTextString
(int off
) {
if (strings ==
null)
strings =
new MdrStrings
(reader, getSection
(15), charset, decoder,
false);
return strings.
getTextString(off
);
}
private void printHeader
() {
reader.
position();
Displayer d =
new Displayer
(reader
);
d.
setTitle("MDR header");
int codePage = d.
charValue("codepage %d");
charset = Sort.
charsetFromCodepage(codePage
);
d.
charValue("id1 %d");
d.
charValue("id2 %d");
d.
charValue("??? %d");
Section sect = readSection
(d,
"MDR 1",
1,
true,
true);
numberOfMaps = sect.
getNumberOfRecords();
boolean onDevice = sect.
getRecordSize() ==
4;
d.
item().
addText("Number of maps %d", numberOfMaps
);
d.
item().
addText("Device MDR %s", onDevice
);
readSection
(d,
"MDR 2",
2,
true,
true);
readSection
(d,
"MDR 3",
3,
true,
true);
readSection
(d,
"MDR 4",
4,
true,
true);
readSection
(d,
"MDR 5",
5,
true,
true);
readSection
(d,
"MDR 6",
6,
true,
true);
readSection
(d,
"MDR 7",
7,
true,
true);
readSection
(d,
"MDR 8",
8,
true,
true);
readSection
(d,
"MDR 9",
9,
true,
true);
readSection
(d,
"MDR 10",
10,
false,
true);
readSection
(d,
"MDR 11",
11,
true,
true);
readSection
(d,
"MDR 12",
12,
true,
true);
readSection
(d,
"MDR 13",
13,
true,
true);
readSection
(d,
"MDR 14",
14,
true,
true);
readSection
(d,
"MDR 15",
15,
false,
false);
int flag = d.
byteValue("String flag %x");
getSection
(15).
setMagic(flag
);
if (flag ==
1) {
compressedStrings =
true;
}
readSection
(d,
"MDR 16",
16,
true,
true);
readSection
(d,
"MDR 17",
17,
false,
true);
readSection
(d,
"MDR 18",
18,
true,
true);
readSection
(d,
"MDR 19",
19,
true,
true);
if (getHeaderLen
() > 286) {
readSection
(d,
"MDR 20",
20,
true,
true);
readSection
(d,
"MDR 21",
21,
true,
true);
readSection
(d,
"MDR 22",
22,
true,
true);
readSection
(d,
"MDR 23",
23,
true,
true);
readSection
(d,
"MDR 24",
24,
true,
true);
readSection
(d,
"MDR 25",
25,
true,
true);
readSection
(d,
"MDR 26",
26,
true,
true);
readSection
(d,
"MDR 27",
27,
true,
true);
readSection
(d,
"MDR 28",
28,
true,
true);
readSection
(d,
"MDR 29",
29,
true,
true);
}
if (getHeaderLen
() > 426) {
readSection
(d,
"MDR 30",
30,
true,
true);
readSection
(d,
"MDR 31",
31,
false,
false);
readSection
(d,
"MDR 32",
32,
true,
true);
readSection
(d,
"MDR 33",
33,
false,
false);
readSection
(d,
"MDR 34",
34,
true,
true);
readSection
(d,
"MDR 35",
35,
true,
true);
readSection
(d,
"MDR 36",
36,
true,
true);
readSection
(d,
"MDR 37",
37,
true,
true);
readSection
(d,
"MDR 38",
38,
true,
true);
readSection
(d,
"MDR 39",
39,
true,
true);
readSection
(d,
"MDR 40",
40,
true,
true);
}
if (getHeaderLen
() > 568) {
d.
intValue("??? Offset: %08x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? Offset: %08x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? Offset: %08x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
d.
intValue("??? 0x%x");
}
// Print any remaining part
d.
rawValue((int) (getHeaderLen
() - reader.
position()));
// The city field in mdr11 appears to have a minimum of 2
int ncities = getSection
(5).
getNumberOfRecords();
if (ncities
> 0x7fffff
)
citySizeFlagged =
4;
else if (ncities
> 0x7fff
)
citySizeFlagged =
3;
else
citySizeFlagged =
2;
d.
print(outStream
);
outStream.
flush();
}
/**
* If there are more maps than will fit into a single byte, then
* two bytes are used. This routine sorts all that out.
*/
private void printMapIndex
(Displayer d
) {
int mi
;
if (numberOfMaps
> 255)
mi = d.
charValue("%d map number");
else
mi = d.
byteValue("%d map number");
if (mi
> numberOfMaps
) {
d.
print(outStream
);
outStream.
flush();
assert false:
"not a map number " + mi
;
}
}
/** The size of a map index - one or two bytes */
private int mapIndexSize
() {
return numberOfMaps
> 255? 2:
1;
}
/**
* Do we want to print this section.
*/
private boolean want
(int n
) {
Section section = getSection
(n
);
return section
!=
null && section.
getLen() > 0 && wanted
[n
];
}
/**
* Print an mdr file.
*
* The sections printed can be limited by adding a list of numbers
* after the filename.
*
* If the list of numbers is preceded by a '!' character then all
* sections except for the listed ones are printed.
*/
public static void main
(String[] args
) {
if (args.
length < 1) {
System.
err.
println("Usage: mdrdisplay <filename>");
System.
exit(1);
}
MdrDisplay md =
new MdrDisplay
();
List<String> argv =
Arrays.
asList(args
);
Iterator<String> it = argv.
iterator();
String name =
"osmmap_mdr.img";
boolean negate =
false;
boolean hasNumber =
false;
while (it.
hasNext()) {
String s = it.
next();
if (s.
startsWith("--print")) {
String[] split = s.
split("=");
if (split.
length > 1) {
String arg = split
[1];
if (arg.
charAt(0) ==
'!') {
Arrays.
fill(md.
wanted,
true);
negate =
true;
arg = arg.
substring(1);
}
for (String ns : arg.
split("[, ]"))
md.
wanted[Integer.
parseInt(ns
)] =
!negate
;
hasNumber =
true;
}
} else if (s.
startsWith("--")) {
System.
out.
println("Unknown flag " + s
);
System.
exit(1);
} else {
name = s
;
}
}
if (!hasNumber
)
Arrays.
fill(md.
wanted,
true);
md.
display(name,
"MDR");
}
}