import java.io.File; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Jocaml { private static String TRAILER_MAGIC = "Caml1999X028"; private static long MARSHAL_MAGIC_SMALL = 0x8495A6BE; private static long MARSHAL_MAGIC_BIG = 0x8495A6BF; private static class CodeOffset { public final int offset; public CodeOffset(int offset) { this.offset = offset; } public String toString() { return "CodeOffset<" + offset + ">"; } public boolean equals(Object other) { return other instanceof CodeOffset && ((CodeOffset) other).offset == this.offset; } public int hashCode() { return offset * 13; } } private static class Block { public final int tag; public final List contents; public Block(int tag, List contents) { this.tag = tag; this.contents = contents; } public String toString() { return "Block<" + tag + ": " + contents.toString() + ">"; } } private static class Section { String name; ByteBuffer data; public Section(String name, ByteBuffer data) { this.name = name; this.data = data; } public String toString() { return "Section"; } public List unstring() { List strings = new ArrayList<>(); for (int i = 0; i < this.data.capacity();) { StringBuffer buf = new StringBuffer(); while (this.data.get(i) != 0) { buf.append((char) this.data.get(i)); i += 1; } strings.add(buf.toString()); i += 1; } return strings; } private Object parseNextObject(List shared) throws Exception { int nextTag = this.data.get() & (short) 0xff; switch (nextTag) { // integers case 0x00: { byte value = this.data.get(); return Long.valueOf(value); } case 0x01: { short value = this.data.getShort(); return Long.valueOf(value); } case 0x02: { int value = this.data.getInt(); return Long.valueOf(value); } case 0x03: { long value = this.data.getLong(); return Long.valueOf(value); } // shared elements case 0x04: { long offset = this.data.get() & (long) 0xff; return shared.get((int) offset); } case 0x05: { long offset = this.data.getShort() & (long) 0xff; return shared.get((int) offset); } case 0x06: { long offset = this.data.getInt() & (long) 0xff; return shared.get((int) offset); } // blocks case 0x08: { long header = this.data.getInt() & (long) 0xff; int tag = (int) header & 0xff; long size = header >> 10; List elems = new ArrayList<>(); Block block = new Block(tag, elems); if (size > 0) { shared.add(block); for (int i = 0; i < size; i++) { elems.add(parseNextObject(shared)); } } return block; } case 0x13: { throw new Exception("long block not supported"); } // string case 0x09: { int len = this.data.get() & (short) 0xff; byte[] buf = new byte[len]; this.data.get(buf); String s = new String(buf); shared.add(s); return s; } case 0x0a: { long len = this.data.getInt() & (long) 0xff; byte[] buf = new byte[(int)len]; this.data.get(buf); String s = new String(buf); shared.add(s); return s; } // float case 0x0b: case 0x0c: { Double obj = Double.valueOf(this.data.getDouble()); shared.add(obj); return obj; } case 0x0D: case 0x0E: { throw new Exception("double array not supported"); } case 0x07: case 0x0f: { throw new Exception("double array not supported"); } // misc case 0x10: { throw new Exception("code pointer with checksum"); } case 0x11: { throw new Exception("code pointer with closure"); } case 0x12: case 0x18: case 0x19: { StringBuffer buf = new StringBuffer(); while (true) { int next = this.data.get() & (short) 0xff; if (next == 0) { break; } buf.append((char) next); } String name = buf.toString(); switch (name) { // int64 case "_j": { long v = Long.valueOf(this.data.getLong()); shared.add(v); return v; } default: throw new Exception("unknown custom op: " + name); } } default: { if (nextTag < 0x20) { throw new Exception("invalid tag " + nextTag); } else if (nextTag < 0x40) { int length = nextTag & 0x1f; byte[] buf = new byte[(int)length]; this.data.get(buf); String s = new String(buf); shared.add(s); return s; } else if (nextTag < 0x80) { Long value = Long.valueOf(nextTag & 0x3f); return value; } else { long size = (nextTag >> 4) & 0x07; int tag = nextTag & 0x0f; List elems = new ArrayList<>(); Block block = new Block(tag, elems); if (size > 0) { shared.add(block); for (int i = 0; i < size; i++) { elems.add(parseNextObject(shared)); } } return block; } } } } public void unmarshal() throws Exception { int magic = this.data.getInt(0); if (magic != MARSHAL_MAGIC_SMALL) { throw new Exception("bad marshal magic"); } int byteLength = this.data.getInt(4); int numObjs = this.data.getInt(8); System.out.println(numObjs); List shared = new ArrayList<>(); List objs = new ArrayList<>(); this.data.position(20); while (this.data.position() < this.data.capacity()) { Object next = parseNextObject(shared); objs.add(next); } System.out.println(shared.size()); System.out.println(objs); } } public static Section findSection(Section[] sections, String name) { for (Section section : sections) { if (section.name.equals(name)) { return section; } } return null; } public static void main(String[] args) throws Exception { ByteBuffer buf; int len; { FileInputStream in = new FileInputStream(new File(args[0])); len = in.available(); buf = ByteBuffer.allocate(len); Channels.newChannel(in).read(buf); } for (int i = 0; i < 12; i++) { if (buf.get(len - 12 + i) != (byte) TRAILER_MAGIC.charAt(i)) { throw new Exception("invalid trailer"); } } int numSections = buf.getInt(len - 16); Section[] sections = new Section[numSections]; for (int i = numSections - 1, sectionBase = len - 16 - (numSections * 8); i >= 0; i--) { int base = len - 16 - (numSections * 8) + (i * 8); char[] name = new char[4]; name[0] = (char) buf.get(base + 0); name[1] = (char) buf.get(base + 1); name[2] = (char) buf.get(base + 2); name[3] = (char) buf.get(base + 3); int sectionLength = buf.getInt(base + 4); sectionBase -= sectionLength; byte[] data = new byte[sectionLength]; buf.position(sectionBase); buf.get(data); sections[i] = new Section(new String(name), ByteBuffer.wrap(data)); } System.out.println(Arrays.toString(sections)); findSection(sections, "SYMB").unmarshal(); findSection(sections, "CRCS").unmarshal(); findSection(sections, "DATA").unmarshal(); } }