Subversion Repositories display

Rev

Rev 531 | 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.util.Arrays;
import java.util.Formatter;

/**
 * Standalone program to learn how to calculate the values at 9a in
 * the TRE header.
 *
 * @author Steve Ratcliffe
 */

public class TreCalc extends CommonDisplay {

        protected void print() {
                readCommonHeader();
                readHeader();
        }


        public static void main(String[] args) {
                if (args.length < 1) {
                        System.err.println("Usage: tredisplay <filename>");
                        System.exit(1);
                }

                String name = args[0];

                CommonDisplay nd = new TreCalc();
                nd.display(name, "TRE");
        }

        void readHeader() {
                if (getHeaderLen() < 188)
                        return;
               
                Displayer d = new Displayer(reader);
                d.setTitle("TRE Header");

                // Print the map id out in various ways
                DisplayItem item = d.item();
                reader.position(0x74);
                int mapId = reader.get4();
                item.setStartPos(reader.position());
                item.setBytes4(mapId);
                item.addText("Map ID   %08d", mapId)
                                .addText("Map ID 0x%08x", mapId);

                // Print the actual values of the four words starting at 9A
                reader.position(0x9a);
                int u1 = d.intValue("9A actual: %08x");
                int u2 = d.intValue("9E actual: %08x");
                int u3 = d.intValue("A2 actual: %08x");
                int u4 = d.intValue("A6 actual: %08x");

                CalcValues actual = new CalcValues();
                actual.setVal(1, u1);
                actual.setVal(2, u2);
                actual.setVal(3, u3);
                actual.setVal(4, u4);
//              item.addText("u1 %s", actual.getAsString(1));

                // Now attempt to calculate the values.
                CalcValues vals = new CalcValues();
                vals.setMapId(mapId);

                // The third an fourth values are the same and built from the map id
                int offset = lookupOffset(mapId, u3);  // not known how to calculate offset
                int val = calcThird(mapId, offset);
                vals.setValRev(3, val);
                vals.setValRev(4, val);

                calcSecond(vals);
                calcFirst(vals);

                // Print out the calcualted values
                item = d.item();
                item.addText("Calculated values (with off %d)", offset);
                item.addText("U1 calc : %s", vals.getAsString(1));
                item.addText("U2 calc : %s", vals.getAsString(2));
                item.addText("U3 calc : %08x", vals.get(3));
                item.addText("U4 calc : %08x", vals.get(4));

                // Diff the calculated verses actual
                diffValues(d, vals, actual);

                d.gap();
                item = d.item();
                int l1 = actual.get(2, 2);
                int l2 = actual.get(2, 3);
                item.addText("Last val: %1x %1x", l1, l2);
                item.addText("diff is %d", l2 - l1);
                item.addText("   unoff: %x %x", (l1 - offset) & 0xf, (l2 - offset) & 0xf);
                // hmmm, looks like the un-offset value is not much dependant on the low
                // bytes of the map id.
               
                d.print(outStream);
        }

        private void diffValues(Displayer d, CalcValues calcValues, CalcValues actual) {
                for (int n = 1; n <= 4; n++) {
                        byte[] arr1 = calcValues.getArray(n);
                        byte[] arr2 = actual.getArray(n);

                        for (int i = 0; i < 8; i++) {
                                byte bcalc = arr1[i];
                                if (bcalc < 0)
                                        continue;
                                byte bactual = arr2[i];
                                if ((bcalc & 0xf) != (bactual & 0xf)) {
                                        DisplayItem item = d.item();
                                        item.addText("MISMATCH at U%d n%d", n, i);
                                }
                        }
                }
        }

        private void calcFirst(CalcValues vals) {
                byte[] arr = vals.getArray(1);

                // The first bytes are sometimes the low bytes of the mapId with
                // the offset added.
                arr[0] = (byte) (vals.getMapIdNibble(3) + vals.getOffset());
                arr[1] = (byte) (vals.getMapIdNibble(2) + vals.getOffset());
                arr[2] = (byte) (vals.getMapIdNibble(1) + vals.getOffset());
                arr[3] = (byte) (vals.getMapIdNibble(0) + vals.getOffset());

                // Adding the mapped value from the top bytes...
                // Nibbles are reversed here.
                arr[0] += mapIdCodeTable[vals.getMapIdNibble(6)];
                arr[1] += mapIdCodeTable[vals.getMapIdNibble(7)];
                arr[2] += mapIdCodeTable[vals.getMapIdNibble(4)];
                arr[3] += mapIdCodeTable[vals.getMapIdNibble(5)];

                // The following are copies of U3
                arr[4] = (byte) vals.get(3, 4);
                arr[5] = (byte) vals.get(3, 5);
                arr[6] = (byte) vals.get(3, 6);

                // seven is always(?) one more
                arr[7] = (byte) (vals.get(3, 7) + 1);
        }

        /**
         * Calculate the second value U2.
         * @param vals The existing values that have been calculated and where any
         * values we calculate here will be placed.
         */

        private void calcSecond(CalcValues vals) {
                byte[] arr = vals.getArray(2);
                // The first byte is copied from the first byte of U3
                arr[0] = (byte) vals.get(3, 0);
                arr[1] = (byte) vals.get(3, 1);

                // This byte appears to come from the corresponding byte
                // of the map id subtracting a constant (modulo 16).
                // The numbers to add come from the header length?
                int h1 = getHeaderLen() >> 4;
                int h2 = getHeaderLen();
                arr[2] = (byte) ((vals.get(3, 2) + h1) & 0xf);
                arr[3] = (byte) ((vals.get(3, 3) + h2) & 0xf);

                // The following are the sum of individual nibbles in U3 and the
                // corresponding nibble in the top half of mapId.
                arr[4] = (byte) (vals.get(3, 4) + vals.getMapIdNibble(7));
                arr[5] = (byte) (vals.get(3, 5) + vals.getMapIdNibble(6));
                arr[6] = (byte) (vals.get(3, 6) + vals.getMapIdNibble(5));
                arr[7] = (byte) (vals.get(3, 7) + vals.getMapIdNibble(4));

               
        }

        /**
         * We don't know how the calculate the offset value, so look it up
         * by getting the actual value of one of the bytes and subtracting
         * the value we would calculate without the offset.
         * @param mapId The map number.
         *
         * So this is cheating as we are looking at the 'answer' but it is only
         * one nibble which allows us to calculate over 16 nibbles.
         *
         * @param a2 The value at 0xa2 in the file.
         * @return The offset value that was used.
         */

        private int lookupOffset(int mapId, int a2) {
                int topMapId = mapIdCodeTable[(mapId >>> 28) & 0xf];

                return (((a2 >>> 24)&0xf) - topMapId);
        }

        /**
         * Calculate the third (and fourth as it is the same) value.
         *
         * We take the nibbles from the mapId as if we had printed the number out in hex
         * and started at the most significant nibble and work to the least.  Each nibble is
         * looked up in a table and an offset is added.  The values are the combined into
         * an integer from the least significant nibble upward.  This means that the
         * nibbles are reversed, but also swapped in pairs.
         *
         * @param mapId The map number.
         * @return The third word of the values.
         */

        private int calcThird(int mapId, int offset) {
                NibbleInt in = new NibbleInt(mapId);
                NibbleInt out = new NibbleInt(0);
                for (int i = 0; i < 8; i++) {
                        int nib = in.extractNibble(i);
                        int n = mapIdCodeTable[nib] + offset;

                        out.setNibble((7-(i^1)), n);
                }
                return out.intValue();
        }

        private final int[] mapIdCodeTable = {
                        0, 1, 0xf, 5,
                        0xd, 4, 7, 6,
                        0xb, 9, 0xe, 8,
                        2, 0xa, 0xc, 3
        };

        private static class NibbleInt {
                private int in;

                private NibbleInt(int in) {
                        this.in = in;
                }

                /**
                 * Return a nibble from the integer.  Counted little endian, 0 is the
                 * least significant nibble.
                 * @param n The nibble to extract.  0 is least significant.
                 */

                public int extractNibble(int n) {
                        return (in >>> (n * 4)) & 0xf;
                }

                public void setNibble(int n, int val) {
                        in |= (val & 0xf) << (n * 4);
                }

                public int intValue() {
                        return in;
                }

                public void set(byte[] arr) {
                        for (int i = 0; i < 8; i++)
                                setNibble(7-i, arr[i]);
                }
        }

        private class CalcValues {
                private int offset;
                private final byte[][] values = new byte[4][];
                private NibbleInt mapId;

                private final int[] offsetMap = {
                                6, 7, 5, 11,
                                3, 10, 13, 12,
                                1, 15,4, 14,
                                8, 0, 2, 9
                };

                private CalcValues() {
                        for (int i = 0; i < 4; i++) {
                                values[i] = new byte[8];
                                Arrays.fill(values[i], (byte) 0xff);
                        }
                }

                public void setValRev(int n, int val) {
                        NibbleInt nint = new NibbleInt(val);
                        byte[] arr = values[n - 1];

                        for (int i = 0; i < 8; i++) {
                                arr[i] = (byte) (nint.extractNibble(i) & 0xf);
                        }
                }

                public void setVal(int n, int val) {
                        NibbleInt nint = new NibbleInt(val);
                        byte[] arr = values[n - 1];

                        for (int i = 0; i < 8; i++) {
                                arr[i] = (byte) (nint.extractNibble(7-i) & 0xf);
                        }
                }

                public int get(int n) {
                        byte[] arr = values[n - 1];
                        NibbleInt nint = new NibbleInt(0);
                        nint.set(arr);
                        return nint.intValue();
                }

                public String getAsString(int n) {
                        byte[] arr = values[n - 1];

                        StringBuffer sb = new StringBuffer();
                        Formatter fmt = new Formatter(sb);
                        for (byte b : arr) {
                                if (b < 0)
                                        sb.append('x');
                                else
                                        fmt.format("%x", b & 0xf);
                        }
                        return sb.toString();
                }

                public int get(int n, int i) {
                        byte[] arr = values[n - 1];
                        return arr[i] & 0xf;
                }

                public byte[] getArray(int n) {
                        return values[n - 1];
                }

                public int getOffset() {
                        return offset;
                }

                public void setMapId(int mapId) {
                        NibbleInt nint = new NibbleInt(mapId);

                        // To get the offset value we add up all the even nibbles of the map
                        // number and transform via a table.
                        this.mapId = nint;
                        int rawoff = nint.extractNibble(0) + nint.extractNibble(2)
                                        + nint.extractNibble(4) + nint.extractNibble(6);

                        this.offset = offsetMap[rawoff & 0xf];
                }

                public int getMapIdNibble(int n) {
                        return mapId.extractNibble(n);
                }
        }
}