/* * Copyright (C) 2007 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 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. * * * Author: Steve Ratcliffe * Create date: Dec 16, 2007 */ package test.display; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import uk.me.parabola.imgfmt.Utils; import uk.me.parabola.imgfmt.app.ImgReader; import uk.me.parabola.imgfmt.app.map.MapReader; import uk.me.parabola.imgfmt.app.net.RoadDef; import uk.me.parabola.mkgmap.general.LevelInfo; /** * Standalone program to display the NOD file as it is worked out. Will start * out with what is in imgdecode by John Mechalas. * * Credits: * Alexander Atanasov and his libgarmin (http://libgarmin.sourceforge.net/) project. * Robert Vollmert */ public class NodConvert extends CommonDisplay { // whether this is a new direction compared to the previous link // not set for first link // thus, we have a short link if NEWDIR is not set and it's not the first link private static final int NEWDIR = 0x80; // this appears to signify the orientation of the link within the road private static final int SIGN = 0x40; private static final int CURVE = 0x20; private static final int LEN = 0x18; private static final int EXTRA = CURVE|LEN; // restrictions, first byte private static final int TOLL = 0x80; private static final int CLASS = 0x70; private static final int ONEWAY = 0x08; private static final int SPEED = 0x07; // second byte + top 2 bits of net pointer private static final int NOEMERGENCY = 0x8000; private static final int NODELIVERY = 0x4000; private static final int NOCAR = 0x0001; private static final int NOBUS = 0x0002; private static final int NOTAXI = 0x0004; private static final int NOCARPOOL = 0x0008; private static final int NOFOOT = 0x0010; private static final int NOBIKE = 0x0020; private static final int NOTRUCK = 0x0040; private static final int NO_THROUGH_ROUTING = 0x0080; private int citySize = 1; private int zipSize = 1; private int nodesStart; private int nodesLen; private int roadDataStart; private int roadDataLen; private int boundriesStart; private int boundriesLen; private byte boundriesRecsize; private final TreeMap nod1recs = new TreeMap(); private NetDisplay netDisplay; private byte align; private int aMask; private Table curTable; private final String booltag = "\n"; private final String inttag = "\n"; private final String bytetag = "\n"; private final String strtag = "\n"; boolean convert = true; protected void print() { readCommonHeader(); printHeader(); /* openLbl(); if (lbl.getCities().size() > 255) citySize = 2; if (lbl.getZips().size() > 255) zipSize = 2; openTre(); openNet(); openRgn(); */ // rgn.setNetFile(net); System.out.printf("\n"); System.out.printf("\n"); printRoadData(); readBoundryNodes(); for (Integer i:nod1recs.keySet()) System.err.println("nod1off = 0x" + Integer.toHexString(i)); printNodes(); System.out.printf("\n"); } private long calcTablePosition(long start, int low) { long pos = start - nodesStart; pos += aMask + 1; pos += low * ((long) 1 << align); pos &= ~aMask; return nodesStart + pos; } private void printNodes() { Displayer d = new Displayer(reader); d.setTitle("NOD 1 (nodes)"); d.setSectStart(nodesStart); reader.position(nodesStart); while (reader.position() < nodesStart + nodesLen) { long start = reader.position(); int low = reader.get() & 0xff; long end = calcTablePosition(start, low); int testSectoff = (int)(start - nodesStart); Offset off = nod1recs.get(testSectoff); if (off == null){ testSectoff = getNextKnownOffset(testSectoff, start+1000); if (testSectoff >= 0 && testSectoff < end - nodesStart){ long pos2 = reader.position(); reader.position(testSectoff + nodesStart); int low2 = reader.get() & 0xff; long end2 = calcTablePosition(start, low); if (end != end2){ System.err.println("sync?"); } reader.position(pos2); } long dd = 4; } d.item().addText("tables at %x", end); // determine size of Table C offsets int tabCOffsetSize = 0; reader.position(end); // read Table C format int fmt = reader.get(); if((fmt & 2) != 0) { // Table C size is 16 bits, so offset must be too tabCOffsetSize = 2; } else if((fmt & 1) != 0) { // this is tricky because the size could be more than // 128 in which case the offset needs to be 16 bits so // we need to actually read the size to find out reader.position(end + 7); int tabASize = reader.get() & 0xff; int tabBSize = reader.get() & 0xff; reader.position(end + 9 + tabASize * 5 + tabBSize * 3); if((reader.get() & 0xff) >= 0x80) tabCOffsetSize = 2; else tabCOffsetSize = 1; } reader.position(start); // step back to low byte if(tabCOffsetSize == 1) d.item().addText("Table C offsets are 8 bits"); else if(tabCOffsetSize == 2) d.item().addText("Table C offsets are 16 bits"); curTable = readTable(end, tabCOffsetSize); printNode(end, tabCOffsetSize); // reader.position(end); // d.print(outStream); //printTables(0); reader.position(curTable.next); } d.print(outStream); } private void printNode(long end, int tabCOffsetSize) { long groupStart = reader.position(); TableHeader tableHeader = readTableHeader((int) end); int lastLow = Integer.MAX_VALUE; while (reader.position() < end) { // d.gap(); // d.item().addText("New node"); long nodeOff = reader.position(); if (nodeOff == 5043){ long dd = 4; } int sectoff = (int)nodeOff - nodesStart; Offset off = nod1recs.get(sectoff); long rest = end - nodeOff; if (rest < 10){ long dd = 4; } int low = reader.get() & 0xff; /* if (low > lastLow){ System.err.println("lost sync: low byte too high:" + low + " offset:" + sectoff ); for (int i = sectoff - 1; i > 0; i--){ Offset testOff = nod1recs.get(i); if (testOff != null){ System.err.println("prev known offset is at " + i + " (" + (nodesStart + i + 1) + ")"); break; } } boolean foundMore = false; for (int i = sectoff + 1; i < end-nodesStart; i++){ Offset testOff = nod1recs.get(i); if (testOff != null){ System.err.println("next known offset is at " + i + " (" + (nodesStart + i + 1) + ")"); reader.position(testOff.getOffset() + nodesStart); foundMore = true; break; } } if (foundMore) continue; break; } lastLow = low; */ if (calcTablePosition(nodeOff, low) != end) { /* if (low == 0) { d.item().addText("spurious 0?"); continue; } d.item().addText("lost sync calc-end=%x, should be=%x", calcTablePosition(nodeOff, low), end); d.rawValue((int) (end - reader.position()), "remaining bytes"); */ System.err.println("lost sync calc-end " + low); break; } // byte2 looks like flags, only a small number of values int flags = reader.get() & 0xff; System.err.printf("flags 0x%x\n",flags); boolean restr = (flags & 0x10) == 0x10; // restrictions present at this node boolean bigoff = (flags & 0x20) == 0x20; // 2 bytes each for lat/lon offsets boolean boundry = (flags & 0x08) == 0x08; // this is a boundry node boolean arcs = (flags & 0x40) == 0x40; // has arcs int minRequired = ((bigoff) ? 4:3 ) + ((arcs) ? 4:0 ) ; if (rest < minRequired) break; StringBuilder sb = new StringBuilder(": "); if (restr) sb.append("restrictions "); if (bigoff) sb.append("large-offsets "); if (boundry) sb.append("boundry-node "); if (arcs) sb.append("arcs "); if (restr && tabCOffsetSize == 0 ){ System.err.println("lost sync: restr is true, but table is empty"); } // System.out.printf(booltag, "verified offset", (off != null)); if (off == null){ long dd = 4; if (boundry) return; } // d.item().addText(sb.toString()); System.out.printf("\n", off!=null); System.out.printf("\n", restr); System.out.printf("\n", boundry); System.out.printf("\n"); if(arcs){ if (off == null){ long dd = 4; } rest = end - reader.position(); if (rest < 10){ System.err.println("reading arc near table"); } pointerPart(nodeOff, groupStart, end); } /* if(restr) { boolean done = false; while(!done) { if(tabCOffsetSize == 1) { int off = d.byteValue("Restriction offset 0x%x"); done = (off & 0x80) == 0x80; } else if(tabCOffsetSize == 2) { int off = d.charValue("Restriction offset 0x%x"); done = (off & 0x8000) == 0x8000; } else { done = true; } } } */ } } private int tableId = 1; class Table { final int id; final int nodeid; int lat, lon; long next; // start of next RouteCenter // just the class for now final ArrayList tableA = new ArrayList(); // destination offsets for now final ArrayList tableB = new ArrayList(); Table() { id = tableId++; nodeid = -id; } } private Table readTable(long offset, int tabCOffsetSize) { if (offset == 0x62bf){ long dd = 4; } System.err.println("reading table at 0x" + Long.toHexString(offset)); long orig = reader.position(); Table table = new Table(); reader.position(offset); // Get the header int restrbytes = reader.get() & 0xff; int l = latLongField(); table.lon = l; l = latLongField(); table.lat = l; System.out.printf(Locale.ROOT, "\n", table.nodeid, Utils.toDegrees(table.lon), Utils.toDegrees(table.lat)); System.out.printf(strtag, "routecenter", table.id); System.out.printf("\n"); int n = reader.get() & 0xff; int m = reader.get() & 0xff; System.err.println("table sizes: " + n + " " + m); // Now do 'Table A' (segments) for (int i = 0; i < n; i++) { int off = reader.get3(); off &= 0x3fffff; System.err.printf("net off: %06x\n", off); int paramA = reader.get(); table.tableA.add((paramA & CLASS) >> 4); reader.get(); } // 'Table B' (inter-section pointers) for (int i = 0; i < m; i++) { System.err.println("adding table b entry " + i); int nod1off = reader.getu3(); if (nod1off > nodesLen){ System.err.println("lost sync: invalid entry in tableB" ); } table.tableB.add(nod1off); } if (tabCOffsetSize != 0){ // 'Table C' (restrictions) int size=0; // I've seen restrbytes up to 2 if (restrbytes == 0) { // if next byte is 0, discard it long pos = reader.position(); int val = reader.get(); if(val == 0) { //d.item().addText("table c size is zero"); System.err.println("zero byte discarded"); } else { // rewind so that byte gets used reader.position(pos); } } else if ((restrbytes & 1) != 0) { size = reader.get() & 0xff; } else if ((restrbytes & 2) != 0) { size = reader.getChar() & 0xffff; } if (size % 11 == 0) { // assume these are fixed length records of size 11 for (; size > 0; size -= 11) { // turn restriction at second node from first node (via first segment) // to third node (via second segment) reader.get3(); for (int i = 0; i < 3; i++) { reader.getChar(); } reader.get(); reader.get(); } } else { reader.get(size); } } if ((restrbytes & 4) != 0) { //d.byteValue("??? restrformat4"); reader.get(); } if ((restrbytes & 8) != 0) { //d.byteValue("%d unpaved roads"); reader.get(); } if((restrbytes & 0x10) != 0) { //d.byteValue("%d ferry routes"); reader.get(); } if(/*restrbytes != 0 && (restrbytes & 0x03) == 0*/ tabCOffsetSize == 0) { // d.byteValue("???"); long pos = reader.position(); int unk = reader.get() & 0xff; if (unk != 0) reader.position(pos); else System.err.printf("unk byte eaten: %x\n" , unk); } table.next = reader.position(); // Restore position reader.position(orig); return table; } private TableHeader readTableHeader(int offset) { long orig = reader.position(); TableHeader tableHeader = new TableHeader(); reader.position(offset); reader.get(); tableHeader.setLong(reader.get3()); tableHeader.setLat(reader.get3()); // Restore position reader.position(orig); return tableHeader; } private void positionOffsets(TableHeader currentTableHeader, boolean bigoff) { short latoff, longoff; if (bigoff ) { longoff = (short) reader.getChar(); latoff = (short) reader.getChar(); } else { int latlon = reader.get3(); latoff = (short) (latlon >> 12); if ((latoff & 0x800) != 0) latoff |= 0xf000; longoff = (short) (latlon & 0xfff); if ((longoff & 0x800) != 0) longoff |= 0xf000; } System.out.printf(Locale.ROOT, "lon='%f' lat='%f'>\n", Utils.toDegrees(longoff + currentTableHeader.getLon()), Utils.toDegrees(latoff + currentTableHeader.getLat())); } private void pointerPart(long offset, long min, long max) { // looks like there are 2b before the low1 pointer and 4 after. boolean end = false; boolean first = true; do { if (reader.position() == 5184){ long dd = 4; } if (reader.position() + 10 > max ){ long dd = 4; // close to end } System.out.printf("\n", reader.position()); System.out.printf("\n", offset+1); // Start with alt6 byte // bit 0x20 seems to determine whether there's an extra byte at the end int alt6 = reader.get() & 0xff; System.out.printf(bytetag, "alt6", alt6); // this is not the class of the segment, but the max of classes of the dest node int classmask = 0x07; boolean newdir = (alt6 & NEWDIR) != 0; System.out.printf(booltag, "newdir", newdir); int destclass = alt6 & classmask; System.out.printf(inttag, "destclass", destclass); System.out.printf(booltag, "sign", (alt6 & SIGN) != 0); if (first) { newdir = true; first = false; } // Continue with two byte values. The first one has the top // bit set if this is the last pointer in the node record. int flagB = reader.get() & 0xff; // Note that this is the last if it is. if ((flagB & 0x80) == 0x80) { end = true; System.out.printf(booltag, "last", true); } // The second highest bit, means inter-section pointer boolean external = (flagB & 0x40) == 0x40; System.out.printf(booltag, "external", external); long pos = reader.position(); if (external) { int idx = flagB & 0x3f; if (idx == 0x3f) { idx = reader.get() & 0xff; } System.out.printf(inttag, "indexB", idx); int off = curTable.tableB.get(idx); if (nod1recs.containsKey(off) == false){ long dd = 4; } else { long dd = 4; } System.out.printf("\n", off +1); } else { // in-section relative node pointer int intro2 = reader.get() & 0xff; short nodeoff = (short) ((flagB << 8 | intro2) & 0x3fff); // Construct a pointer to another node, signed 16 bit quantity if ((nodeoff & 0x2000) != 0) nodeoff |= 0xc000; long otherNode = offset + nodeoff; if (min > otherNode || otherNode >= max) { System.err.println("lost sync in pointerPart"); } System.out.printf("\n", otherNode+1); } if (newdir && curTable.tableA.isEmpty()){ System.err.println("newdir with empty tableA"); } if (newdir){ int indexA = reader.get() & 0xff; System.out.printf(inttag, "indexA", indexA); System.out.printf(inttag, "roadclass", curTable.tableA.get(indexA)); } else { //d.item().addText("short link, no segment pointer"); } System.out.printf(strtag, "type", newdir ? "arc" : "link"); int len; boolean curve; int curveInfo = (alt6 & EXTRA) >> 3; System.err.println("curveinfo: " + curveInfo); if (curveInfo != 7){ long dd = 4; } if ((alt6 & EXTRA) == EXTRA) { int len1 = reader.get() & 0xff; if ((len1 & 0x80) == 0x80) { if ((len1 & 0x40) == 0x40) { int len2 = reader.getChar(); len = (len1 & 0x3f) | (len2 << 6); // 6+16 bits System.out.printf(inttag, "lengthbits", 22); if (len <(1 << 14)) { System.err.println("invalid length 22 bits?"); } curve = true; } else { int len2 = reader.get() & 0xff; len = (len1 & 0x3f) | (len2 << 6); // 6+8 bits System.out.printf(inttag, "lengthbits", 14); curve = false; } } else { int len2 = reader.get() & 0xff; len = (len1 & 0x7f) | (len2 << 7); // 7+8 bits System.out.printf(inttag, "lengthbits", 15); curve = true; } } else { curve = (alt6 & CURVE) == CURVE; len = (alt6 & 0x08) << 5; len |= reader.get() & 0xff; // 1+8 bits if (len > 0x200){ System.err.println("invalid length 9 bits?"); } System.out.printf(inttag, "lengthbits", 9); } // length should be in units of 16 feet System.out.printf(inttag, "length-16f", len); System.out.printf(inttag, "length-m", (int)(len * 16 / 3.2808)); if (newdir) { int dir = reader.get(); System.out.printf(inttag, "direction", (dir * 360) >> 8); } if (curve) { int curvea = reader.get() & 0xff; pos = reader.position(); int curveb; if ((curvea & 0xe0) == 0) { curveb = reader.get() & 0xff; //item.addText("curve[0] %02x curve[1] %02x (two bytes)", curvea, curveb); System.out.printf(inttag, "curvebytes", 2); if (curveb == 0){ long dd = 4; // reader.position(pos); } } else { System.out.printf(inttag, "curvebytes", 1); System.out.printf(inttag, "curvebyte-raw", curvea); int angle = 360 * (byte)(((curvea & 0x1f) << 3) | ((curvea & 0xe0) >> 5)) / 256; //item.addText("curve[0] %02x (%d deg?)", curvea, angle); curveb = (curvea & 0x1f) << 3; curvea = curvea & 0x70; } System.out.printf(inttag, "curvea", curvea); System.out.printf(inttag, "curveb", curveb); } System.out.printf("\n"); } while (!end); } private void printTables(int len) { int remain = len; // Get the header TableHeader tableHeader = new TableHeader(); tableHeader.setPosition(reader.position()); int restrformat = reader.get() & 0xff; remain -= 1; int l = latLongField(); tableHeader.setLong(l); remain -= 3; l = latLongField(); tableHeader.setLat(l); remain -= 3; int n = reader.get() & 0xff; remain -= 1; int m = reader.get() & 0xff; remain -= 1; // Now do 'Table A' (segments) for (int i = 0; i < n; i++) { int off = reader.get3(); int paramA = reader.get(); int paramB = reader.get(); /* // top 2 bits of net pointer are access bits int access = (off & 0xc00000) >> 8; off &= 0x3fffff; String par = "class %d, speed %d"; if ((paramA & TOLL) == TOLL) par += ", toll"; if ((paramA & ONEWAY) == ONEWAY) par += ", oneway"; if ((paramB & NO_THROUGH_ROUTING) == NO_THROUGH_ROUTING) par += ", no through routing"; item.addText(par, (paramA & CLASS) >> 4, paramA & SPEED); access |= paramB & 0xff; par = ""; par += (access & NOEMERGENCY) == 0 ? "emergency, " : "no emergency, "; par += (access & NODELIVERY) == 0 ? "delivery, " : "no delivery, "; par += (access & NOCAR) == 0 ? "car, " : "no car, "; par += (access & NOBUS) == 0 ? "bus, " : "no bus, "; par += (access & NOTAXI) == 0 ? "taxi, " : "no taxi, "; par += (access & NOCARPOOL) == 0 ? "carpool, " : "no carpool, "; par += (access & NOFOOT) == 0 ? "foot, " : "no foot, "; par += (access & NOBIKE) == 0 ? "bike, " : "no bike, "; par += (access & NOTRUCK) == 0 ? "truck, " : "no truck, "; item.addText("access: %s", par); if((paramB & 0x08) != 0) item.addText("Unknown access bits: %02x", paramB & 0x08); */ remain -= 5; } // d.print(outStream); // 'Table B' (inter-section pointers) // d.setTitle("Table B (inter-section pointers)"); for (int i = 0; i < m; i++) { int nodeOff = reader.get3(); remain -= 3; } // d.print(outStream); // 'Table C' (restrictions) // d.setTitle("Table C (restrictions)"); int size=0; if (restrformat == 0) { // if next byte is 0, discard it long pos = reader.position(); int val = reader.get(); if(val == 0) { // d.item().addText("table c size is zero"); } else { // rewind so that byte gets used reader.position(pos); } } else if ((restrformat & 1) != 0) { size = reader.get() & 0xff; remain--; } else if ((restrformat & 2) != 0) { size = reader.getChar() & 0xffff; remain -= 2; } if (size % 11 == 0) { // assume these are fixed length records of size 11 remain -= size; for (; size > 0; size -= 11) { // turn restriction at second node from first node (via first segment) // to third node (via second segment) // d.rawValue(3, "restriction header"); reader.get3(); for (int i = 0; i < 3; i++) { int off = reader.getChar() & 0xffff; } reader.get(); reader.get(); } } else { reader.get(size); remain -= size; } if ((restrformat & 4) != 0) { // d.byteValue("??? restrformat4"); reader.get(); remain--; } if ((restrformat & 8) != 0) { // d.byteValue("%d unpaved roads"); reader.get(); remain--; } if((restrformat & 0x10) != 0) { // d.byteValue("%d ferry routes"); reader.get(); remain--; } if(restrformat != 0 && (restrformat & 0x03) == 0) { // d.byteValue("???"); reader.get(); remain--; } // d.print(outStream); // if there's something left, we probably missed the first // node of the next section /* if (remain > 0) { d.setTitle("left over"); d.rawValue(remain, "extra data remaining"); } d.print(outStream); d.setTitle(""); */ } private int latLongField() { int l = reader.get3(); if ((l & 0x800000) != 0) l |= 0xff000000; return l; } private String getNameFromNetOff(int off) { if (netDisplay != null) return netDisplay.getNameFromNetOff(off); else return ""; } private void printRoadData() { Displayer d = new Displayer(reader); d.setTitle("NOD 2 (road data)"); d.setSectStart(roadDataStart); d.print(outStream); reader.position(roadDataStart); int end = roadDataStart + roadDataLen; int nrecords = 0; while (reader.position() < end) { nrecords++; d = new Displayer(reader); d.setSectStart(roadDataStart); int recstart = (int) reader.position(); // XXX looks like a set of flags but ? // agree with speed & class in RouteParam (plus one bit) DisplayItem item = d.item(); int flags = item.setBytes(reader.get()); item.addText("Road classification speed=%d, type=%d", (flags & 0xf) >> 1, (flags & 0x70) >> 4); if((flags & 1) == 0) item.addText("bit 0 is zero"); int sectoff = d.int3Value("offset into NOD 1 %06x"); String origin = (new Formatter()).format("nod 2 %06x", recstart).toString(); Offset offset = nod1recs.get(sectoff); if (offset != null) { offset.appendOrigin(origin); } else { Offset offval = new Offset(sectoff, origin); nod1recs.put(sectoff, offval); } // Ok this is the number of bits in the following. int nbits = d.charValue("Bit stream len"); // The number of set bits appears to be the number of nodes in the road. // Usually th lowest nbits appear to be set, and I've seen the lowest // missing when an end of the road is not a node. --Rob // A number of bits follows. Work out how many bytes are needed to // hold that number of bits. int nstream = (nbits+7)/8; byte[] bs = d.rawValue(nstream, "Bit stream"); String bsStr = bitStreamAsString(bs, nbits); d.item().addText("BIT STREAM %s", bsStr); if((flags & 0x80) != 0) { int extraFormat = d.byteValue("extra data format"); if(extraFormat >= 0x01 && extraFormat <= 0x0b) { if((extraFormat & 0x01) != 0) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data 1"); } if((extraFormat & 0x02) != 0) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data 2"); } if((extraFormat & 0x04) != 0) { int extra = d.byteValue("extra data 4"); } if((extraFormat & 0x08) != 0) { int extra = d.charValue("extra data 8"); } } else if(extraFormat == 0x0c) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data c"); } else if(extraFormat == 0x0d) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data da"); len = d.byteValue("len"); d.rawValue(len >> 1, "extra data db"); } else if(extraFormat == 0x0e) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data ea"); len = d.byteValue("len"); d.rawValue(len >> 1, "extra data eb"); } else if(extraFormat == 0x0f) { int len = d.byteValue("len"); d.rawValue(len >> 1, "extra data fa"); len = d.byteValue("len"); d.rawValue(len >> 1, "extra data fb"); len = d.byteValue("len"); d.rawValue(len >> 1, "extra data fc"); } else d.item().addText("Unknown format"); } d.gap(); // d.print(outStream); } d.item().addText("Number of records %d", nrecords); d.print(outStream); } private String bitStreamAsString(byte[] bs, int nbits) { long l = 0; for (int i = 0; i < (nbits+7)/8; i++) { byte b = bs[i]; l |= (b & 0xff) << (i * 8); } String s = Long.toBinaryString(l); if (s.length() < nbits) { StringBuilder sb = new StringBuilder(s); for (int i = s.length(); i < nbits; i++) sb.insert(0, '0'); return sb.toString(); } return s; } int getNextKnownOffset(int offset, long end){ for (int i = offset + 1; i < end-nodesStart; i++){ Offset testOff = nod1recs.get(i); if (testOff != null){ System.err.println("next known offset is at " + i + " (" + (nodesStart + i + 1) + ")"); reader.position(testOff.getOffset() + nodesStart); return testOff.getOffset(); } } return -1; } /** * This is a set of fixed length records, so is a good one to start with. */ private void readBoundryNodes() { reader.position(boundriesStart); for (int pos = boundriesStart; pos < boundriesStart + boundriesLen; pos += boundriesRecsize) { //latLongField(d, "longitude"); //latLongField(d, "latitude"); reader.get(6); // limited range int sectoff = reader.getu3(); Offset offset = nod1recs.get(sectoff); if (offset != null) { offset.appendOrigin("NOD 3"); } else { Offset offval = new Offset(sectoff, "NOD 3"); nod1recs.put(sectoff, offval); } //d.int3Value("offset into NOD 1 %06x"); // We believe that this is an offset into unk1 too // nodeOffsets.add(new Offset(sectoff, "NOD 3 chunk3")); } } private void printHeader() { Displayer d = new Displayer(reader); d.setTitle("NOD header"); nodesStart = d.intValue("NOD 1 (nodes) at offset %#08x"); nodesLen = d.intValue("NOD 1 length %d"); d.item().addText("End of section %08x, len %#08x", nodesStart + nodesLen, nodesLen); int flags = d.charValue("flags"); // usually 0x0025 or 0x0027 if ((flags & 0x02) != 0) d.item().addText("have restrictions"); d.charValue("???"); //d.charValue("???"); align = d.byteValue("node align"); aMask = (1<"); System.exit(1); } String name = args[0]; NodConvert nd = new NodConvert(); nd.setOutStream(System.err); nd.display(name, "NOD"); } }