// JAVA SOFTWARE TO DE-RLE the "Roller Coaster Tycoon" // Saved Track file (.TD4 file). // // Copyright (C) 1999 David M. Torok // // LICENSE: This software is released under the // GNU GENERAL PUBLIC LICENSE // Version 2, June 1991 // // VERSION 0.02 // // USAGE: java Rctrle -d "Track file name.TD4" [outfile] // Output: raw track file which is the un-RLE'd // version of the track file. // java Rctrle -c "RAWFILE" [outfile.TD4] // Output: RCT-compatible .TD4 output // // COMING SOON: Track info? Track editor? // // MANY THANKS to posters on alt.games.rctycoon for the // checksum algorithm (in C) and the general import java.io.*; public class Rctrle { public static final int TRACKBUFSIZE = 8058; public static final int MAXCHUNKSIZE = 125; byte[] readAndUncompressTrackFile(String filen) { InputStream in = null; BufferedInputStream bis = null; DataInputStream dataIn = null; ByteArrayOutputStream bout = null; try { File f = new File(filen); long filelen = f.length(); in = new java.io.FileInputStream(filen); bis = new BufferedInputStream(in); dataIn = new DataInputStream(bis); bout = new ByteArrayOutputStream(8058); int mycount = (int)filelen - 4; while (mycount > 0) { byte b = dataIn.readByte(); mycount--; if (b > 0) { for (int loop = 0; loop <=b; loop++) { bout.write(dataIn.readByte()); mycount--; } } else { byte abyte = dataIn.readByte(); mycount--; for (int loop = 0; loop >= b; loop--) { bout.write(abyte); } } } in.close(); return bout.toByteArray(); } // end try catch(java.io.IOException e) { System.out.println("Error in opening file or connection: " + filen); System.out.println(e.toString()); return null; } }//end readAndUncompressTrackFile void writeByteArrayToFile(String filen, byte[] buf) { try { FileOutputStream fout = new FileOutputStream(filen); fout.write(buf); fout.close(); } // end try catch(java.io.IOException e) { System.out.println("Error in opening file or connection: " + filen); System.out.println(e.toString()); return; } } int[] createChecksum(byte[] inbuf) { /** running checksum total **/ int cval = 0; /** byte-converted reversed checksum for writing to a file **/ int[] bout = new int[4]; try { ByteArrayInputStream bin = new ByteArrayInputStream(inbuf); DataInputStream dataIn = new DataInputStream(bin); int mycount = inbuf.length; while (mycount > 0) { byte b = dataIn. readByte(); //System.out.println("Byte = " + Integer.toHexString(b)); mycount--; //Low order add: add byte to our cval with NO carry byte lval = (byte)(cval & 0x000000FF); lval += b; int lval1 = ((int) lval) & 0x000000FF; cval = (lval1 | (cval & 0xFFFFFF00)); //rotate the whole thing left by 3 bits int leftbits = (cval & 0xE0000000) >>> 29; //System.out.println("Leftbits = " + Integer.toHexString(leftbits)); //int rightbits = ((cval & 0x1FFFFFFF) << 3); //System.out.println("Cval1 = " + Integer.toHexString(cval1)); cval = ((cval & 0x1FFFFFFF) << 3) | leftbits; //System.out.println("Checksum = " + Integer.toHexString(cval)); } //add the constant cval = cval - 108156; //arbitary TD4 checksum subtractor bin.close(); //need to write the Integer in reverse order... //in Java, bytes are signed, so we store our checksum in int[4] bout[0] = (cval & 0x000000FF); bout[1] = ((cval & 0x0000FF00) >>> 8); bout[2] = ((cval & 0x00FF0000) >>> 16); bout[3] = ((cval & 0xFF000000) >>> 24); } // end try catch(java.io.IOException e) { System.out.println("Error in stream"); System.out.println(e.toString()); return null; } return bout; }//end checksum compute byte[] readuncompressed(String filen) { InputStream in = null; BufferedInputStream bis = null; DataInputStream dataIn = null; ByteArrayOutputStream bout = null; try { File f = new File(filen); long filelen = f.length(); if (filelen != this.TRACKBUFSIZE) { System.out.println ("File wrong size"); return null; } byte[] filecontents = new byte[this.TRACKBUFSIZE]; in = new java.io.FileInputStream(filen); bis = new BufferedInputStream(in); dataIn = new DataInputStream(bis); dataIn.readFully(filecontents); in.close(); return filecontents; } catch(Throwable e) { System.out.println("Error in opening file or connection: " + filen); System.out.println(e.toString()); return null; } } //output a bytearray byte[] performRLE(byte[] inbuf) { InputStream in = null; BufferedInputStream bis = null; DataInputStream dataIn = null; if (inbuf.length != this.TRACKBUFSIZE) { System.out.println("Buffer not correct size"); return null; } ByteArrayOutputStream bout = new ByteArrayOutputStream(); int index = 0; int scanstart = -1; int repbyte = inbuf[index]; int repstart = -1; while (index < TRACKBUFSIZE) { scanstart = index + 1; repbyte = inbuf[index]; repstart = -1; //scan from here, up to 125 bytes ahead //we're looking for one of two cases: // a) a series of NON-REPEATING BYTES, followed by a series of repeating // b) a series of REPEATING BYTES, followed by the first non-repeating byte scanstart = index + 1; while (scanstart < TRACKBUFSIZE && (scanstart - index < this.MAXCHUNKSIZE)) { //haven't found a repeater yet if (repstart == -1) { if (inbuf[scanstart] != repbyte) { repbyte = inbuf[scanstart]; } else { //found a repeat byte repstart = scanstart - 1; } } else { //we've already started a repeating series; continue //until we find a no-match if (inbuf[scanstart] != repbyte) break; //break out of while loop else if (repstart != index && (scanstart - repstart > 3)) break; //found a lot of matches, but we're looking at non-repeating } scanstart++; } //OK, time to write some output //repeating bytes start at the index and continue to scanstart - 1; if (repstart >= 0 && repstart == index) { //System.out.println("Index = " + index + " found repeat bytes " + repbyte + " count= " + (scanstart - index)); //need to write a NEGATIVE byte; count -1 bout.write((index + 1) - scanstart); bout.write(inbuf[index]); index = scanstart; } //repeating bytes start somewhere later. else if (repstart >= 0 && repstart != index) { //System.out.println("Index = " + index + " found non-repeat bytes length = " + (repstart - index)); //need to write a POSITIVE byte; count - 1 bout.write( repstart - (index + 1)); while (index < repstart) { bout.write(inbuf[index]); index++; } } else //no repstart, so we just have non-matching gruel { //System.out.println("Index = " + index + " found only non-repeat bytes length = " + (scanstart - index)); //need to write a POSITIVE byte; count - 1 bout.write( scanstart - (index + 1)); while (index < scanstart) { bout.write(inbuf[index]); index++; } } } //end while index return bout.toByteArray(); } void writeTrackFile(String fname, byte[] buf, int[] checksum) { try { FileOutputStream fout = new FileOutputStream(fname); DataOutputStream dout = new DataOutputStream(fout); dout.write(buf); for (int i = 0; i < checksum.length; i++) dout.writeByte(checksum[i]); fout.close(); } // end try catch(Throwable e) { System.out.println("Error in opening file or connection: " + fname); System.out.println(e.toString()); return; } } public static void usage() { System.out.println("RCTRLE (C) 1999 David M. Torok"); System.out.println("USAGE:"); System.out.println("TO DECOMPRESS A .TD4 FILE FOR RAW TRACK EDITING:"); System.out.println(" java Rctrle -d [output raw track file]"); System.out.println("TO COMPRESS A RAW TRACK INTO RCT-COMPATIBLE .TD4 FORMAT:"); System.out.println(" java Rctrle -c [output RCT-format file]"); } public static void main(String argv[]) { String inname = null; String outname = null; if (argv.length >= 2) { if (argv[0].equals("-d")) { inname = argv[1]; if (argv.length >= 3) outname = argv[2]; else outname = inname + ".OUT"; Rctrle myobj = new Rctrle(); byte[] track = myobj.readAndUncompressTrackFile(inname); if (track != null) { myobj.writeByteArrayToFile(outname, track); } } else if (argv[0].equals("-c")) { inname = argv[1]; if (argv.length >= 3) outname = argv[2]; else outname = inname + ".RLE"; Rctrle myobj = new Rctrle(); byte[] mytrack = myobj.readuncompressed(inname); if (mytrack != null) { byte[] outrack = myobj.performRLE(mytrack); if (outrack != null) { int[] checksum = myobj.createChecksum(outrack); myobj.writeTrackFile(outname, outrack, checksum); } } } else usage(); } else usage(); } //end main() }