Subversion Repositories display

Rev

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

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

}