Subversion Repositories display

Rev

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

/*
 * 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.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;

/**
 * Standalone program to display the NET file as it is worked out.  Will start
 * out with what is in imgdecode by John Mechalas.
 *
 * Can produce a massive file, so may take some time to write.
 *
 * @author Steve Ratcliffe
 */

public class NetDisplay extends CommonDisplay {
        private static final Logger log = Logger.getLogger(CommonDisplay.class);

        private static final int U_FLAG_1 = 0x04;
        private static final int U_FLAG_2 = 0x08;
        private static final int U_FLAG_3 = 0x10;

        private int roadDefStart;
        private int roadDefSize;

        // This is 'Unknown 2' in imgdecode.  It appears to be a list of offsets
        // to the records in road defs.  But it is in a different order, why? - Because
        // it puts them in alphabetical order, perhaps for ease of searching.
        //
        // .. and it is not complete, there are a subset. -- but all ones with names
        // are there.
        //
        // Also some of them have a 4 in the top nibble.  These always point to
        // a record with two labels.
        private int roadIndexStart;
        private int roadIndexSize;
        private int roadIndexRecsize;

        // Road data flags.
        private static final int RD_HAS_NOD_INFO = 0x40;
        private static final int RD_HAS_STREET_ADDRESS_INFO = 0x10;
        //private static final int RD_DIRECTION_INDICATOR = 0x02;

        private final Map<Long, String> netnames = new HashMap<Long, String>();

        // Index of net1 offset to text string, in order as in net3
        private final Map<Integer, String> sortedRoads = new LinkedHashMap<Integer, String>();

        private int citySize = 1;
        private int zipSize = 1;
        private int mult1;

        protected void print() {
                readCommonHeader();

                openLbl();

                printHeader();

                printRoadIndex();

                printRoadDefs();

                printSortedRoads();
        }

        private void printSortedRoads() {
                outStream.println("----------------- Roads as sorted in NET 3 ------------------------");
                for (Map.Entry<Integer, String> ent : sortedRoads.entrySet()) {
                        outStream.printf("  0x%06x: %s\n", ent.getKey(), ent.getValue());
                }
        }

        protected void openLbl() {
                try {
                        ImgChannel chan = findFile("LBL");
                        lbl = new LBLFileReader(chan);
                        int numCities = lbl.getCities().size();
                        System.out.println("NUM CITIES " + numCities);
                        if (numCities > 255)
                                citySize = 2;
                        int numZips = lbl.getZips().size();
                        if (numZips > 255)
                                zipSize = 2;
                } catch (FileNotFoundException e) {
                        System.err.println("Could not open LBL file");
                }
        }

        private static boolean USE_KNOWN = false;

        private void printRoadDefs() {
                assert !sortedRoads.isEmpty();
                Displayer d = new Displayer(reader);
                d.setTitle("NET 1 (Road definitions)");
                d.print(outStream);

        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int off : sortedRoads.keySet())
            list.add(off & ~0xc00000);
        Collections.sort(list);

                reader.position(roadDefStart);
                int end = roadDefStart + roadDefSize;
                int nextPos = list.remove(0);
        int roadNum = 0;
                while (reader.position() < end) {
                        d = new Displayer(reader);
                        d.setSectStart(roadDefStart);
                       
                        Formatter fmt = new Formatter();
                        long netoff = nextPos;
                        if (USE_KNOWN) {
                                reader.position(roadDefStart + netoff);

                                // We are only looking at roads that have a name here. This is because we do
                                // not know how to work out exactly where an entry starts in all cases yet.
                                while (nextPos <= netoff && !list.isEmpty()) {
                                        nextPos = list.remove(0);
                                }
                                if (nextPos <= netoff) {
                                        break;
                                }
                        } else {
                                netoff = reader.position() - roadDefStart;
                        }

                        while (mult1 != 0 && (reader.position() & (2*mult1 - 1)) != 0)
                                d.byteValue("pad");

                        d.gap();

            fmt.format("Road number %d, at offset %06x (next=x%x)", roadNum++, netoff, nextPos);
                        d.item().addText(fmt.toString());
            d.print(outStream);

            int labval;
                        DisplayItem item;
                        int labNum = 0;
                        do {
                                item = d.item();
                                labval = reader.get3();

                                item.setBytes3(labval);
                                int laboff = labval & 0x3fffff;
                                String labelstr = fetchLabel(laboff);
                                item.addText("Label offset: %x (%s)", laboff, labelstr);

                                netnames.put(netoff, labelstr);

                                // Set the name as found into our sorted roads map.
                                if (sortedRoads.containsKey((int) netoff + (labNum << 22)))
                                        sortedRoads.put((int) netoff + (labNum << 22), labelstr);
                                labNum++;
                        } while ((labval & 0x800000) == 0);

                        int flags = d.byteValue("Flags %02x");

                        item = d.item();
                        int rlen = reader.get3();
                        item.setBytes3(rlen);

                        // this is possibly different on different maps, working with this
                        // for now.
                        item.addText("Road len %d meters", 2*rlen);

                        printLevelDivs(d);

                        // Various sections that can be present depending on flags above.
                        if ((flags & RD_HAS_STREET_ADDRESS_INFO) == RD_HAS_STREET_ADDRESS_INFO) {
                                DisplayItem bitem = d.byteItem();

                                DisplayItem afitem = d.item();
                                int addrFlags = afitem.setBytes(reader.get());
                                int nblocks = (bitem.getValue() & 0xff) + ((addrFlags & 0x3) << 8);
                                bitem.addText("Number of blocks %d", nblocks);

                                afitem.addText("flags z%x c%x n%x (hi-part of blocks %x)", (addrFlags>>2)&0x3, (addrFlags>>4)&0x3, (addrFlags>>6)&0x3, addrFlags&0x3);
                                printAddrInfo(d, addrFlags >> 2, "zip", zipSize);
                                printAddrInfo(d, addrFlags >> 4, "city", citySize);
                                printAddrInfo(d, addrFlags >> 6, "numbers", (nblocks << 8) + flags);
                        }

                        if ((flags & RD_HAS_NOD_INFO) == RD_HAS_NOD_INFO) {
                                int nfollow = d.byteValue("flags for bytes following %x");
                                // OK now it is clear that this is a pointer into NOD-unknown2

                                // nfollow {
                                //   nod_pointer_size: 2
                                //   unk: 3
                                //   counter: 2

                                int nodSize = nfollow & 0x3;
                                d.intValue(nodSize + 1, "nod pointer %x");


                                int rest = nfollow & 0xfc;
                                if (rest > 0)
                                        d.item().addText("rest %x", rest);

                                int counter = (rest >> 5) & 3;
                                if (counter > 0) {
                                        d.item().addText("counter %d", counter);

                                        while (counter-- > 0) {
                                                int uflag = d.byteValue("uflag %x") & 0xff;

                                                d.item().addText("flag1=%b flag2=%b flag3=%b lo=%d hi=%d", (uflag&U_FLAG_1)!=0, (uflag&U_FLAG_2)!=0, (uflag&U_FLAG_3) !=0, uflag&0x3, (uflag>>>4));

                                                // Variable length (?) bit command.
                                                // 0 read number of bytes as given in lo bits, append to uflag and shift down
                                                // 10 shift down uflag, add one, read this many bytes
                                                // 11x shift down uflag, read 1, combine. read that many bytes.

                                                int lo = uflag & 0x3;
                                                if ((uflag & U_FLAG_1) == 0) {
                                                        int idx = d.intValue((uflag & 0x3)+1, "partial value");
                                                        idx += (uflag>>>3);

                                                } else {
                                                        int len;
                                                        if ((uflag & U_FLAG_2) == U_FLAG_2) {
                                                                len = d.byteValue("length (hi)") << 3;
                                                                len += (uflag >>> 5);
                                                        } else {
                                                                len = 1 + (uflag >>> 4);
                                                        }

                                                        d.item().addText("length %d", len);
                                                        if (len > 0) {
                                                                d.rawValue(len, "data");
                                                                //printAddrInfo(d, 0, "city", len);
                                                        } else {
                                                                d.item().addText("TODO: will not work");
                                                        }
                                                }

                                        }
                                } else if ((rest & 0x80) == 0x80) {
                                        d.rawValue(8, "80 data"); // XXX
                                }
                        }

                        netoff = reader.position() - roadDefStart;
                        while (nextPos < netoff && !list.isEmpty()) {
                                nextPos = list.remove(0);
                        }
                        if (mult1 == 0 && nextPos == netoff + 1) {
                                //reader.position(roadDefStart + nextPos);
                                d.rawValue((int) (nextPos - reader.position() + roadDefStart), "catch up to next start");
                        }
                        d.print(outStream);
                }
        }

        private void printRoadIndex() {

                Displayer d = new Displayer(reader);
                d.setTitle("NET 3 (Road index)");

                if (roadIndexRecsize > 3)
                        assert false : "not implemented, unknown record size " + roadIndexSize;

                reader.position(roadIndexStart);
                for (int pos = 0; pos < roadIndexSize; pos += roadIndexRecsize) {
                        int off = d.int3Value("Offset %06x");

                        // This establishes the order
                        sortedRoads.put(off, null);
                }

                d.print(outStream);
        }

        private void printLevelDivs(Displayer d) {
                DisplayItem item;
                // one has the 0x80 bit set.  They are counts of things to follow.
                int[] counts = new int[8];
                int ncounts = 0;

                boolean last = false;
                while (!last) {
                        item = d.item();
                        byte b = reader.get();
                        item.setBytes(b);
                        if ((b & 0x80) == 0x80) {
                                last = true;
                        }
                        counts[ncounts++] = b & 0x7f;
                        item.addText("count %d", counts[ncounts-1]);
                        if (ncounts > 6) {
                                log.error("Suspicious large ncounts " + ncounts);
                                d.print(outStream);
                                outStream.flush();
                        }
                }

                // Not in imgdecode, but is in JM's document.
                for (int i = 0; i < ncounts; i++) {
                        int n = counts[i];
                        while (n-- > 0) {
                                item = d.item();
                                int val = item.setBytes(reader.get());

                                Formatter fmt = new Formatter();// There are a variable number of bytes that follow.  The last
                                fmt.format("Level %d: line %d", i, val);
                                item.addText(fmt.toString());

                                d.charValue("in subdiv %d");
                        }
                }
        }


        private void printAddrInfo(Displayer d, int addrFlags, String type, int size) {
                switch (addrFlags & 0x3) {
                case 0: // number field;
                        int nfollow = d.byteValue("%d following");
                        d.print(outStream);
                        if (type.equals("numbers")) {
                                NumberDisplayer nd = new NumberDisplayer();
                                try {
                                        // size is flags here
                                        nd.printNumbers(d, nfollow<<mult1, size);
                                } catch (NumberException e) {
                                        d.item().addText("Except: %s", e.getMessage());
                                        nd.remainingBits();
                                        d.print(outStream);
                                        return;
                                } catch (ArrayIndexOutOfBoundsException e) {
                                        d.print(outStream);
                                        throw e;
                                }
                        } else {
                                d.rawValue(nfollow, type);
                        }
                        break;
                case 1: // number field;
                        nfollow = d.charValue("%d following");
                        if (type.equals("numbers")) {
                                try {
                                        // size is flags here
                                        (new NumberDisplayer()).printNumbers(d, nfollow, size);
                                } catch (NumberException e) {
                                        d.print(outStream);
                                        return;
                                }
                        } else {
                                d.rawValue(nfollow, type);
                        }
                        break;
                case 2: // zip index
                        // can be two bytes
                        if (size == 2)
                                d.charValue(type + " index %d");
                        else
                                d.byteValue(type + " index %d");
                        break;
                default:
                        // No field, do nothing
                        break;
                }
        }

    static class NumberException extends RuntimeException {
                public NumberException(String msg) {
                        super(msg);
                }
    }

    static class NumberDisplayer {
                private static final int STYLE_NONE = 0;
                private static final int STYLE_EVEN = 1;
                private static final int STYLE_ODD = 2;
                private static final int STYLE_BOTH = 3;

        private BitReader br;
        private int len;
        private DisplayItem item;

                // For reading the start differences and end difference numbers.
                private VarBitReader startReader;
                private VarBitReader endReader;
                private VarBitReader savedStartReader;
                private VarBitReader savedEndReader;
                private boolean doRestoreBitWidths;

                // base numbers
        private int leftBase;
        private int rightBase;

                // numbering styles
                private int leftStyle = STYLE_EVEN;
                private int rightStyle = STYLE_ODD;

                // start numbers
                private int leftStart;
                private int rightStart;

                // end numbers
                private int leftEnd;
                private int rightEnd;

                // saved end numbers
                private int leftLastEndDiff;
                private int rightLastEndDiff;

                // Numbers are a range between nodes. Keep count of them here
                private int nodeCounter;

                // was the last thing read a number range? helps in detecting
                // end of stream.
                private int lastCommand = -1;

                /**
                 * Main routine to print the house numbers.
                 */

                private void printNumbers(Displayer d, int len, int flags) throws NumberException {
            this.len = len * 8;
            item = d.rawItem(len & 0xff);
            byte[] bytes = item.getBytes();

                        br = new BitReader(bytes);
            item.addText("numbers");

                        if ((flags & 0x20) == 0) {
                                leftStyle = STYLE_ODD;
                                rightStyle = STYLE_EVEN;
                        } else {
                                leftStyle = STYLE_EVEN;
                                rightStyle = STYLE_ODD;
                        }
                        item.addText("def left=%s right=%s", toStyleString(leftStyle), toStyleString(rightStyle));

                        getBitWidths();

            getInitialBase();

                        // To do this properly we need to know the number of nodes I think, this is the
                        // best we can do: if there are more than 8 bits left, there must be another command
                        // left.  We could leave a short command at the end.
                        while (br.getBitPosition() < this.len - 8 || lastCommand != 1) {
                                runCommand();
                        }

            remainingBits();
        }

                /**
                 * Get the bit widths for the start and end differences.
                 * Based on code for reading the RGN streams, but the signed bit is the
                 * opposite value.
                 * x is for start value differences.  y is for end value differences.
                 */

                private void getBitWidths() {
                        startReader = new VarBitReader(br, 5);
                        endReader = new VarBitReader(br, 2);
                        item.addText("start read %s", startReader.toString());
                        item.addText("end   read %s", endReader.toString());
                }

                /**
                 * Decode the next command in the stream and run it.
                 */

                private void runCommand() throws NumberException {
                        int cmd = readCommand(); // fetch 1, 3 skip, 2 reload, 0 style

                        switch (cmd) {
                        case 0:
                                changeStyles();
                                break;
                        case 1:
                                fetchNumbers();
                                break;
                        case 2:
                                useBits();
                                break;
                        case 6:
                                skipNodes();
                                break;
                        default:
                                fail("unimplemented command: " + cmd);
                        }
                }

                /**
                 * Temporarily use a different bit width for the following number fetch.
                 */

                private void useBits() {
                        if (!doRestoreBitWidths) {
                                savedStartReader = startReader;
                                savedEndReader = endReader;
                        }
                        doRestoreBitWidths = true;

                        if (br.get1()) {
                                endReader = new VarBitReader(br, 2);
                                item.addText("cmd: bits end %s", endReader.toString());
                        } else {
                                startReader = new VarBitReader(br, 5);
                                item.addText("cmd: bits start %s", startReader.toString());
                        }
                }

                /**
                 * Skip nodes. For parts of a road that has no numbers.
                 */

                private void skipNodes() {
                        boolean f = br.get1();
                        int skip;
                        if (f)
                                skip = 1 + br.get(10);
                        else
                                skip = 1 + br.get(5);
                        nodeCounter += skip;
                        item.addText("cmd: skip %d", skip);
                }

                /**
                 * Read the next command from the stream. Commands are variable length in the bit
                 * stream.
                 * 0 - numbering style (none, odd, even, both)
                 * 1 - fetch numbers
                 * 2 - change bit widths
                 * 6 - skip nodes
                 * @return The command number
                 */

                private int readCommand() {
                        int cmd = 0;
                        if (br.get1()) {
                                cmd |= 0x1;
                        } else {
                                if (br.get1()) {
                                        cmd |= 0x2;
                                        if (br.get1()) {
                                                cmd |= 0x4;
                                        }
                                }
                        }
                        // probably end of stream
                        if (cmd == 0 && lastCommand == 0 && br.getBitPosition() > this.len - 8)
                                throw new NumberException("probably end of stream cmd="+cmd);
                        lastCommand = cmd;
                        return cmd;
                }

                /**
                 * Read the house numbers for a stretch of road.
                 *
                 * The start and end positions of the the left hand side of the road is first, followed
                 * by the right hand side of the road.
                 *
                 * The differences to the last point are stored. It is also possible to
                 */

                private void fetchNumbers() {
                        item.addText("cmd: fetch numbers");

                        // If one side has no numbers, then there is only one set of numbers to calculate, but
                        // changes to base are applied to both sides.
                        boolean doSingleSide = (leftStyle == 0 || rightStyle == 0);

                        if (leftStyle == 0)
                                leftBase = rightBase;

                        // Check for command to copy the base number
                        boolean doSameBase = false;
                        if (!doSingleSide) {
                                doSameBase = br.get1();
                                if (doSameBase)
                                        copyBase();
                        }

                        //int abc = br.get(3);
                        boolean doRightOverride = false;
                        if (!doSingleSide)
                                doRightOverride = !br.get1();
                        boolean doReadStart = !br.get1();
                        boolean doReadEnd = !br.get1();

                        int startDiff = 0, endDiff = leftLastEndDiff;

                        if (doReadStart) {
                                startDiff = startReader.read();
                                item.addText("      read start diff %d", startDiff);
                        }
                        if (doReadEnd) {
                                endDiff = endReader.read();
                                item.addText("      read end diff %d", endDiff);
                        }

                        item.addText("values: L base=%d, start diff=%d, end diff=%d", leftBase, startDiff, endDiff);

                        leftStart = leftBase + startDiff;
                        leftEnd = leftStart + endDiff;

                        leftBase = leftEnd;
                        leftLastEndDiff = endDiff;

                        if (doSingleSide) {
                                printSingleSide();
                                restoreReaders();
                                return;
                        }

                        // *** Now for the right hand side numbers ***

                        // Note that endDiff falls through to this part.
                        // Start diff also does, at least when doSameBase is true
                        if (!doSameBase)
                                startDiff = 0;

                        // If we didn't read an endDiff value for the left side or right is different then
                        // default to the saved value.
                        if (doRightOverride || !doReadEnd) {
                                endDiff = rightLastEndDiff;
                                item.addText("      default endDiff(R) %d", endDiff);
                        }

                        doReadStart = false;
                        doReadEnd = false;

                        if (!doSameBase)
                                doReadStart = !br.get1();

                        if (doRightOverride)
                                doReadEnd = !br.get1();

                        if (doReadStart) {
                                startDiff = startReader.read();
                                item.addText("      read start diff %d (R)", startDiff);
                        }
                        if (doReadEnd) {
                                endDiff = endReader.read();
                                item.addText("      read end diff %d (R)", endDiff);
                        }

                        item.addText("values: R base=%d, start diff=%d, end diff=%d", rightBase, startDiff, endDiff);
                        rightStart = rightBase + startDiff;
                        rightEnd = rightStart + endDiff;

                        rightBase = rightEnd;
                        rightLastEndDiff = endDiff;

                        adjustValues();
                        item.addText("Numbers %d,%s,%d,%d,%s,%d,%d", nodeCounter, toStyleString(leftStyle), leftStart, leftEnd,
                                        toStyleString(rightStyle), rightStart, rightEnd);
                        nodeCounter++;

                        restoreReaders();
                }

                /**
                 * After a temporary bit width change.
                 */

                private void restoreReaders() {
                        if (doRestoreBitWidths) {
                                startReader = savedStartReader;
                                endReader = savedEndReader;
                                doRestoreBitWidths = false;
                        }
                }

                /**
                 * If the road has numbers on just one side, then there is a shortened reading routine.
                 * The left variables are mostly used during reading regardless of which side of the
                 * road has numbers. Make everything work here.
                 */

                private void printSingleSide() {
                        rightBase = leftBase;
                        rightStart = leftStart;
                        rightEnd = leftEnd;
                        rightLastEndDiff = leftLastEndDiff;
                        adjustValues();
                        if (leftStyle == STYLE_NONE)
                                item.addText("Numbers %d,N,-1,-1,%s,%d,%d", nodeCounter, toStyleString(rightStyle), rightStart, rightEnd);
                        else
                                item.addText("Numbers %d,%s,%d,%d,N,-1,-1", nodeCounter, toStyleString(leftStyle), leftStart, leftEnd);
                        nodeCounter++;
                }

                /**
                 * When it is known if the numbers are odd or even, then a shorter bitstream is made
                 * by taking advantage of that fact. This leaves the start and end points needing
                 * adjustment to made them odd or even as appropriate.
                 */

                private void adjustValues() {
                        int ldirection = 1; // direction start is adjusted in; end in the opposite direction.
                        if (leftStart < leftEnd)
                                leftEnd--;
                        else if (leftStart > leftEnd) {
                                leftEnd++;
                                ldirection = -1;
                        }

                        int rdirection = 1; // direction start is adjusted in; end in the opposite direction.
                        if (rightStart < rightEnd)
                                rightEnd--;
                        else if (rightStart > rightEnd) {
                                rightEnd++;
                                rdirection = -1;
                        }

                        if (leftStyle == STYLE_EVEN) {
                                if ((leftStart & 1) == 1) leftStart += ldirection;
                                if ((leftEnd & 1) == 1) leftEnd -= ldirection;
                        } else if (leftStyle == STYLE_ODD) {
                                if ((leftStart & 1) == 0) leftStart+=ldirection;
                                if ((leftEnd & 1) == 0) leftEnd-=ldirection;
                        }
                        if (rightStyle == STYLE_EVEN) {
                                if ((rightStart & 1) == 1) rightStart+=rdirection;
                                if ((rightEnd & 1) == 1) rightEnd-=rdirection;
                        } else if (rightStyle == STYLE_ODD) {
                                if ((rightStart & 1) == 0) rightStart+=rdirection;
                                if ((rightEnd & 1) == 0) rightEnd-=rdirection;
                        }
                }

                /**
                 * Copy one of the bases to the other so they have the same value.
                 * The source is determined by reading a bit from the input.
                 */

                private void copyBase() {
                        boolean f2 = br.get1();
                        if (f2) {
                                item.addText("cmd: base rightBase = leftBase [%d (%d)]", leftBase, rightBase);
                                rightBase = leftBase;
                        } else {
                                item.addText("cmd: base leftBase = rightBase [%d (%d)]", rightBase, leftBase);
                                leftBase = rightBase;
                        }
                }

                private String toStyleString(int style) {
                        switch (style) {
                        case 0: return "N";
                        case 1: return "E";
                        case 2: return "O";
                        case 3: return "B";
                        default:
                                fail("bad numbering style");
                        }
                        return "X"; // not actually reachable
                }

                /**
                 * Change the numbering styles for this section of roads.
                 */

                private void changeStyles() {
                        leftStyle = br.get(2);
                        rightStyle = br.get(2);

                        item.addText("cmd: style left=%s, right=%s", toStyleString(leftStyle), toStyleString(rightStyle));
                }

                /**
                 * Get the initial base value. The first number for this section of road (although a diff
                 * can be applied to it).
                 *
                 * @throws NumberException
                 */

                private void getInitialBase() {
                        int extra = 0;
            boolean b1 = br.get1();
            if (!b1)
                extra = br.get(4);

            leftBase = br.get(5 + extra);
                        rightBase = leftBase;
            item.addText("base=%d", leftBase);
        }

        /**
         * For cases that are not implemented yet.
         */

        private void fail(String s) throws NumberException {
            item.addText("ABANDON: %s", s);
            remainingBits();
            throw new NumberException("ABANDON: " + s);
        }

                /**
                 * Just print out any remaining bits.
                 *
                 * Was mostly used during development, before the whole stream was decoded.
                 */

        private void remainingBits() {
            StringBuilder sb = new StringBuilder();
            while (br.getBitPosition() < len) {
                sb.insert(0, br.get1() ? "1" : "0");
            }
            item.addText(sb.toString());
        }

                /**
                 * Reads integers with specified numbers of bits and optionally with sign bits.
                 */

                static class VarBitReader {
                        private boolean signed;   // read as signed values
                        private boolean negative; // all values are read as positive and then negated
                        private int width;        // the number of bits
                        private final int off;    // a value to be added to width to get the true number to read.
                        private BitReader br;

                        public VarBitReader(BitReader br, int off) {
                                this.br = br;
                                this.off = off;
                                negative = br.get1();
                                signed = br.get1();
                                width = br.get(4);
                        }

                        public int read() {
                                int val;
                                if (signed) {
                                        val = br.sget(width + off + 1);
                                } else {
                                        val = br.get(width + off);
                                }

                                if (negative)
                                        val = -val;
                                return val;
                        }

                        public String toString() {
                                return String.format("sign=%b neg=%b width=%d+%d", signed, negative, width, off);
                        }
                }
    }


    private void printHeader() {
                Displayer d = new Displayer(reader);
                d.setTitle("NET header");

                roadDefStart = d.intValue("NET 1, Road defs, offset %x");
                roadDefSize = d.intValue("NET 1, Road defs length %d");
                DisplayItem item = d.item();
                item.addText("end of section at %#x", roadDefStart + roadDefSize);

                mult1 = d.byteValue("Unknown offsets are x%d ?");

                // Unknown sect 1, variable length fields?
                int u1Start = d.intValue("NET 2, Segmented roads, offset %x");
                int u1Size = d.intValue("NET 2, Segmented roads length %d");
                item = d.item();
                item.addText("end of section at %#x", u1Start + u1Size);

                // This may not be right, just my guess based on the prev
                d.byteValue("Unknown offsets are x%d ?");
               
                // Unknown section with fixed length fields
                roadIndexStart = d.intValue("NET 3, Road index, offset %x");
                roadIndexSize = d.intValue("NET 3, Road index length %d");
                item = d.item();
                item.addText("end of section at %#x", roadIndexStart + roadIndexSize);
                roadIndexRecsize = d.charValue("Sorted roads record size %d");
                d.item().addText("Number of records is %d", roadIndexSize/roadIndexRecsize);

                d.intValue("???");
                d.byteValue("???");
                d.byteValue("???");

                d.print(outStream);
        }

        String getNameFromNetOff(long off) {
                String s = netnames.get(off);
                if (s == null)
                        s = "";
                return s;
        }
       
        public static void main(String[] args) {
                if (args.length < 1) {
                        System.err.println("Usage: netdisplay <filename>");
                        System.exit(1);
                }

                String name = args[0];

                CommonDisplay nd = new NetDisplay();
                nd.display(name, "NET");
        }
}