package test.display;
import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import test.elements.Line;
import test.files.NetFile;
import test.files.Nod2Record;
import test.files.NodFile;
import test.files.RgnFile;
import test.files.RoadData;
import test.files.RouteNode;
import test.files.Segment;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.app.trergn.TREFileReader;
import uk.me.parabola.imgfmt.app.trergn.Zoom;
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.sys.FileImgChannel;
import uk.me.parabola.imgfmt.sys.ImgFS;
/**
* Common code for files that have the 'common header' in a .img file.
*/
public abstract class CommonDisplay
{
public static final int COMMON_HEADER_LEN =
21;
// You will always have a reader.
protected ImgFileReader reader
;
// You may not have a filesystem.
private FileSystem fs
;
protected PrintStream outStream =
new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.
out)));
private int headerLen
;
protected long filelen
;
// The sections in the file, if any.
protected SectList sections =
new SectList
();
protected LBLFileReader lbl
;
protected TREFileReader tre
;
protected RgnFile rgn
;
protected NetFile net
;
protected NodFile nod
;
protected int citySize =
1;
protected int zipSize =
1;
protected int gmpOffset
;
protected abstract void print
();
protected CommonDisplay
() {
super();
try {
outStream =
new PrintStream(new BufferedOutputStream(new FileOutputStream(FileDescriptor.
out)),
false,
"utf-8");
} catch (UnsupportedEncodingException e
) {
e.
printStackTrace();
}
}
protected void readCommonHeader
() {
reader.
position(reader.
getGMPOffset());
Displayer d =
new Displayer
(reader
);
d.
setTitle("Common Header");
headerLen = d.
charValue("Header length %d");
d.
stringValue(10,
"File type %s");
d.
byteValue("???");
d.
byteValue("Set if locked");
DisplayItem item = d.
rawItem(7);
Date date = Utils.
makeCreationTime(item.
getBytes());
DateFormat df =
new SimpleDateFormat("HH:mm:ss d MMM yyyy");
item.
addText(df.
format(date
));
d.
print(outStream
);
}
protected void readHeaderLen
() {
reader.
position(reader.
getGMPOffset());
headerLen = reader.
get2u();
reader.
position(COMMON_HEADER_LEN
);
}
/**
* Read the common combination offset + size (+ record size) in file headers.
* Utility function for subclasses.
*
* @param d The {@link Displayer} to use.
* @param name The name of the sub-file.
* @param number The section number.
* @param hasRecSize There is a record size to be read.
* @param hasMagic Has an 4 byte integer value following the header information. Otherwise
* false and any extra values must be read explicitly.
* @return The section information. It has also been saved for retrieval with getSection.
*/
protected Section readSection
(Displayer d,
String name,
int number,
boolean hasRecSize,
boolean hasMagic
) {
assert number
!=
0;
d.
gap();
int start = d.
intValue(name +
" at offset %#08x");
int len = d.
intValue(name +
" length %d");
Section section =
new Section
(name, start, len
);
d.
item().
addText("End of section %08x, len %#08x", start + len, len
);
if (hasRecSize
) {
int recordSize = d.
charValue(name +
" record size %02x");
if (recordSize
!=
0) {
//assert len % recordSize == 0 : "sect" + number + ", len=" + len + ", recsize=" + recordSize;
d.
item().
addText("Number of records %d", len / recordSize
);
if (len
% recordSize
!=
0)
d.
item().
addText("FRACTIONAL RECORD");
}
section.
setRecordSize(recordSize
);
}
if (hasMagic
) {
int magic = d.
intValue(name +
" header flags %04x");
section.
setMagic(magic
);
}
while (sections.
size() < number-
1)
sections.
add(null);
sections.
add(number-
1, section
);
return section
;
}
protected Section getSection
(int n
) {
assert n
!=
0;
return sections.
get(n-
1);
}
protected int numberOfSections
() {
return sections.
size();
}
/**
* This is used when you want to open a plain file.
* @param name The name of the file to open.
*/
protected void display
(String name
) {
try (RandomAccessFile raf =
new RandomAccessFile(name,
"r");
ImgChannel chan =
new FileImgChannel
(raf.
getChannel())) {
filelen = raf.
length();
this.
reader =
new BufferedImgFileReader
(chan, gmpOffset
);
print
();
outStream.
flush();
} catch (FileNotFoundException e
) {
System.
err.
println("Could not open file: " + name
);
} catch (IOException e
) {
System.
err.
println("Could not get file size or read file " + name
);
}
}
/**
* This is used to open a file within an img file.
*
* @param name The name of the .img file.
* @param fileExt The file to get. It is found by extension.
*/
protected void display
(String name,
String fileExt
) {
if (name.
toLowerCase().
endsWith(fileExt.
toLowerCase())) {
display
(name
);
return;
}
try {
fs = ImgFS.
openFs(name
);
ImgChannel chan = findFile
(fileExt
);
this.
reader =
new BufferedImgFileReader
(chan, gmpOffset
);
print
();
outStream.
flush();
} catch (FileNotFoundException e
) {
System.
err.
println("Could not open " + fileExt +
" in file: " + name
);
} finally {
if (fs
!=
null) {
fs.
close();
}
}
}
protected ImgChannel findFile
(String fileExt
) throws FileNotFoundException {
if (fs ==
null)
throw new FileNotFoundException("Not an img file");
List<DirectoryEntry
> entries = fs.
list();
ImgChannel chan =
null;
for (DirectoryEntry ent : entries
) {
if (ent.
getExt().
equals(fileExt
)) {
filelen = ent.
getSize();
chan = fs.
open(ent.
getFullName(),
"r");
break;
}
if ("GMP".
equals(ent.
getExt())) {
// quick hack to allow reading from GMP
filelen = ent.
getSize();
try (ImgChannel gmpChan = fs.
open(ent.
getFullName(),
"r");
BufferedImgFileReader gmpReader =
new BufferedImgFileReader
(gmpChan
)) {
int offsetPos = -
1;
switch (fileExt
) {
case "TRE":
offsetPos = 0x19
;
break;
case "RGN":
offsetPos = 0x1d
;
break;
case "LBL":
offsetPos = 0x21
;
break;
case "NET":
offsetPos = 0x25
;
break;
case "NOD":
offsetPos = 0x29
;
break;
case "DEM":
offsetPos = 0x2d
;
break;
default:
break;
}
if (offsetPos
>=
0) {
gmpReader.
position(offsetPos
);
gmpOffset = gmpReader.
get4();
if (gmpOffset ==
0) {
// GMP doesn't contain requested file
break;
}
}
} catch (IOException e
) {
// TODO Auto-generated catch block
e.
printStackTrace();
}
chan = fs.
open(ent.
getFullName(),
"r");
break;
}
}
if (chan ==
null)
throw new FileNotFoundException("No file with " + fileExt +
" extension");
return chan
;
}
protected void openLbl
() {
if (lbl
!=
null)
return;
try {
ImgChannel chan = findFile
("LBL");
lbl =
new LBLFileReader
(chan,
true, gmpOffset
);
int numCities = lbl.
getCities().
size();
if (numCities
> 255)
citySize =
2;
int numZips = lbl.
getZips().
size();
if (numZips
> 255)
zipSize =
2;
} catch (FileNotFoundException e
) {
outStream.
println(e.
getMessage());
System.
err.
println("Could not open LBL file");
}
}
protected String fetchLabel
(int laboff
) {
if (lbl ==
null)
return "";
return lbl.
fetchLabel(laboff
).
getText();
}
protected void openTre
() {
if (tre
!=
null)
return;
try {
ImgChannel chan = findFile
("TRE");
tre =
new TREFileReader
(chan, gmpOffset
);
} catch (FileNotFoundException e
) {
outStream.
println(e.
getMessage());
System.
err.
println("Could not open TRE file");
}
}
protected void openRgn
() {
if (rgn
!=
null)
return;
if (lbl ==
null)
openLbl
();
if (net ==
null)
openNet
();
try {
ImgChannel chan = findFile
("RGN");
rgn =
new RgnFile
(chan
);
rgn.
setLblFile(lbl
);
rgn.
setNetFile(net
);
} catch (FileNotFoundException e
) {
outStream.
println(e.
getMessage());
System.
err.
println("Could not open RGN file");
}
}
protected void openNet
() {
if (net
!=
null)
return;
if (lbl ==
null)
openLbl
();
try {
ImgChannel chan = findFile
("NET");
net =
new NetFile
(new BufferedImgFileReader
(chan, gmpOffset
));
net.
setLableFile(lbl
);
} catch (FileNotFoundException e
) {
outStream.
println(e.
getMessage());
System.
err.
println("Could not open NET file");
}
}
protected void openNod
() {
if (nod
!=
null)
return;
try {
ImgChannel chan = findFile
("NOD");
nod =
new NodFile
(new BufferedImgFileReader
(chan, gmpOffset
));
} catch (FileNotFoundException e
) {
outStream.
println(e.
getMessage());
System.
err.
println("Could not open NOD file");
}
}
protected void initRoads
() {
if (nod ==
null)
openNod
();
Zoom
[] levels = tre.
getMapLevels();
Subdivision
[] subdivisions = tre.
subdivForLevel(levels
[levels.
length -
1].
getLevel());
Map<Integer,
Line> lines =
new HashMap<>();
Set<Integer> netOffsets =
new HashSet<>();
// Fetch all lines and save all net offsets found.
for (Subdivision sub : subdivisions
) {
for (Line line : rgn.
linesForSubdiv(sub
)) {
int divNum = line.
getDivAndNum();
lines.
put(divNum, line
);
if (line.
hasNet()) {
netOffsets.
add(line.
getNetOffset());
}
}
}
// Go through all net offsets and set the line information into the road data.
for (int noff : netOffsets
) {
RoadData road = net.
getRoad(noff
);
for (Segment seg : road.
getSegments()) {
int divNum = seg.
getDivAndNum();
Line line = lines.
get(divNum
);
assert line
!=
null;
seg.
setLine(line
);
}
int offsetNod2 = road.
getOffsetNod2();
if (offsetNod2
>=
0){
Nod2Record nod2 = nod.
getNod2(offsetNod2
);
nod2.
setRoadData(road
);
road.
setNod2(nod2
);
RouteNode node = nod2.
getNode();
if (node ==
null) {
System.
out.
printf("Could not find node for road %x nod2=%x\n",
noff,
offsetNod2
);
continue;
}
node.
setLinkedRoad(road
);
}
}
}
protected void setOutStream
(PrintStream outStream
) {
this.
outStream = outStream
;
}
protected int getHeaderLen
() {
return headerLen
;
}
protected void analyze
(PrintStream outStream
) {
sections.
analyze(outStream
);
}
protected void addSection
(Section section
) {
sections.
add(section
);
}
/**
* This contains a codebook which is used to decode the strings
* in MDR 15 or GMP RGN file if they are compressed. The encoding is done with
* Huffman encoding and the decoder uses tables to decode.
*/
protected HuffmanDecoder readHuffmanTable
(Displayer d,
long start
) {
HuffmanDecoder decoder =
new HuffmanDecoder
();
d.
setSectStart(start
);
reader.
position(start
);
// first byte(s) give remaining size of section
// see also usage of peek in mdr17, same encoding used there
AtomicInteger varLength =
new AtomicInteger();
int remSize = d.
varLength("size of remaining bytes: %d", varLength
);
int expectedLen = remSize + varLength.
get();
DisplayItem item = d.
byteItem();
decoder.
setLookupBits(item.
getValue() & 0xf
); // spotted values: 0x15 and 0x16
item.
addText("initial bits to read (in lower 4 bits):%d", item.
getValue() & 0xf
);
/** typically a 32x2 byte lookup table */
int maxDepth = d.
byteValue("max code length: %d");
int rowsTab1 = d.
byteValue("rows in search table: %d");
int symWidth = d.
byteValue("symbol width: %d");
decoder.
setSymWidth(symWidth
);
if (symWidth
% 8 !=
0)
return null; // don't know yet how to handle this
final int oneSymBytes = symWidth /
8;
final int lookupRowLen =
1 + oneSymBytes
;
int huffmanTab2Size =
(1 << decoder.
getLookupBits()) * lookupRowLen
;
int numSymbolsLast = d.
varLength("number of symbols in last block: %d", varLength
);
int symBytes = numSymbolsLast
* oneSymBytes
;
int offsetSize = varLength.
get();
int minCodeBytes =
(int) Math.
ceil(maxDepth /
8.0);
for (int i =
0; i
< rowsTab1
; i++
) {
// the minCode value is shifted to allow a binary search
int minCodeShifted = d.
intValue(minCodeBytes,
"minCode (shifted) %d");
int depth = d.
byteValue("depth: %d");
int offset = d.
intValue(offsetSize,
"offset into symbols %d");
decoder.
addSearchTab(minCodeShifted, depth, offset
);
}
item = d.
item();
item.
addText("\n---- lookup table 2 ----" );
long tab2Pos = reader.
position();
decoder.
setLookupTable(reader.
get(huffmanTab2Size
));
reader.
position(tab2Pos
);
for (int i =
0; i
< 1 << decoder.
getLookupBits(); i++
) {
long pos = reader.
position();
item = d.
rawItem(1 + oneSymBytes
);
reader.
position(pos
);
int stat = reader.
get1u();
int val = reader.
getNu(oneSymBytes
);
if (decoder.
getLookupBits() ==
0)
break;
String prefix =
Integer.
toBinaryString(i
);
// add leading 0 to make it 5 bytes long
prefix =
"0000000000".
substring(0, decoder.
getLookupBits() - prefix.
length()) + prefix
;
if (stat
% 2 ==
1) {
if (symWidth ==
8)
item.
addText("prefix %s (stat=%2d): depth=%d v=%s", prefix, stat, stat
>> 1, displayChar
((byte) val
));
else if (symWidth ==
32) {
item.
addText("prefix %s (stat=%2d): depth=%d v=%08x", prefix, stat, stat
>> 1, val
);
}
} else
item.
addText("prefix %s (stat=%2d): minIdx=%d maxIdx=%d", prefix, stat, stat
>> 1, val
);
}
long posSymbols = reader.
position();
decoder.
setSymbols(reader.
get(symBytes
));
if (reader.
position() - start
!= expectedLen
) {
decoder.
setHuffmanDecodingFailed(true);
}
reader.
position(posSymbols
);
if (symWidth ==
8)
d.
rawValueAsChars(symBytes,
"Remaining symbols: %d bytes");
else {
item = d.
item();
item.
addText("remaining symbols: " + symBytes +
" bytes");
int offset =
0;
while (reader.
position() < posSymbols + symBytes
) {
d.
intValue(oneSymBytes,
"symbol at off " + offset
);
offset++
;
}
}
return decoder
;
}
/** return a byte interpreted as char and with hex code.
*
* @param val the byte
* @return
*/
private static String displayChar
(byte val
) {
byte[] bb =
{ val
};
CharBuffer cbuf = StandardCharsets.
UTF_8.
decode(ByteBuffer.
wrap(bb
));
char v = cbuf.
get();
return String.
format("'%c' 0x%02x", v
<= 0x20
? '.' : v, val
& 0xff
);
}
protected int readVarUInt32
() {
int bytes
;
int shift
;
int b = reader.
get1u();
if ((b
& 1) ==
0) {
if ((b
& 2) ==
0) {
bytes =
((b
>> 2) & 1) ^
3;
shift =
5;
} else {
shift =
6;
bytes =
1;
}
} else {
shift =
7;
bytes =
0;
}
int val = b
>> (8 - shift
);
for (int i =
1; i
<= bytes
; i++
) {
b = reader.
get1u();
val |=
((b
) << (i
* 8)) >> (8 - shift
);
}
return val
;
}
}