summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/org/catacombae/.cvsignore5
-rw-r--r--src/org/catacombae/io/.cvsignore5
-rw-r--r--src/org/catacombae/io/RandomAccessFileStream.java66
-rw-r--r--src/org/catacombae/io/RandomAccessStream.java54
-rw-r--r--src/org/catacombae/rar/.cvsignore5
-rw-r--r--src/org/catacombae/rar/RARFile.java32
-rw-r--r--src/org/catacombae/rarx/.cvsignore5
-rw-r--r--src/org/catacombae/rarx/CommentHeader.java54
-rw-r--r--src/org/catacombae/rarx/DecompressionCode.java1715
-rw-r--r--src/org/catacombae/rarx/FileAttributes.java29
-rw-r--r--src/org/catacombae/rarx/InvalidDataException.java29
-rw-r--r--src/org/catacombae/rarx/Leaf.java7
-rw-r--r--src/org/catacombae/rarx/ListArchiveContents.java438
-rw-r--r--src/org/catacombae/rarx/ListFilesInArchive.java173
-rw-r--r--src/org/catacombae/rarx/ListRecursive.java92
-rw-r--r--src/org/catacombae/rarx/MarkHeader.java48
-rw-r--r--src/org/catacombae/rarx/NewFileHeader.java428
-rw-r--r--src/org/catacombae/rarx/NewMainArchiveHeader.java94
-rw-r--r--src/org/catacombae/rarx/ProtectedHeader.java67
-rw-r--r--src/org/catacombae/rarx/RARFile.java248
-rw-r--r--src/org/catacombae/rarx/RARFile2.java29
-rw-r--r--src/org/catacombae/rarx/RARFileEntry.java76
-rw-r--r--src/org/catacombae/rarx/RARFileEntryStream.java550
-rw-r--r--src/org/catacombae/rarx/RARHeader.java121
-rw-r--r--src/org/catacombae/rarx/README.txt10
-rw-r--r--src/org/catacombae/rarx/TestRAFProperties.java32
-rw-r--r--src/org/catacombae/rarx/Tree.java55
-rw-r--r--src/org/catacombae/rarx/TreeNode.java17
-rw-r--r--src/org/catacombae/rarx/UNIXFileAttributes.java66
-rw-r--r--src/org/catacombae/rarx/UnknownHeader.java54
-rw-r--r--src/org/catacombae/rarx/Util.java305
-rw-r--r--src/org/catacombae/rarx/Win32FileAttributes.java68
-rw-r--r--src/org/fox/ttcomics/CbrComicArchive.java74
-rw-r--r--src/org/fox/ttcomics/ComicListFragment.java30
34 files changed, 5070 insertions, 11 deletions
diff --git a/src/org/catacombae/.cvsignore b/src/org/catacombae/.cvsignore
new file mode 100644
index 0000000..0de89ca
--- /dev/null
+++ b/src/org/catacombae/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/io/.cvsignore b/src/org/catacombae/io/.cvsignore
new file mode 100644
index 0000000..0de89ca
--- /dev/null
+++ b/src/org/catacombae/io/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/io/RandomAccessFileStream.java b/src/org/catacombae/io/RandomAccessFileStream.java
new file mode 100644
index 0000000..137bde2
--- /dev/null
+++ b/src/org/catacombae/io/RandomAccessFileStream.java
@@ -0,0 +1,66 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.io;
+
+import java.io.*;
+
+public class RandomAccessFileStream implements RandomAccessStream {
+ private RandomAccessFile raf;
+
+ public RandomAccessFileStream(RandomAccessFile raf) throws FileNotFoundException {
+ this.raf = raf;
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public void close() throws IOException {
+ raf.close();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public long getFilePointer() throws IOException {
+ return raf.getFilePointer();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public long length() throws IOException {
+ return raf.length();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public int read() throws IOException {
+ return raf.read();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b) throws IOException {
+ return raf.read(b);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b, int off, int len) throws IOException {
+ return raf.read(b, off, len);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public void seek(long pos) throws IOException {
+ raf.seek(pos);
+ }
+}
diff --git a/src/org/catacombae/io/RandomAccessStream.java b/src/org/catacombae/io/RandomAccessStream.java
new file mode 100644
index 0000000..3d6f708
--- /dev/null
+++ b/src/org/catacombae/io/RandomAccessStream.java
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.io;
+
+import java.io.*;
+
+/**
+ * Designed to mimic RandomAccessFile but removing the limitation that
+ * all data has to be stored in one physical file on the disk. The
+ * stream may be composed of multiple files, be read from memory etc.
+ * It's all implementation dependent. The stream must however be
+ * seekable, and have a known limited length.
+ */
+public interface RandomAccessStream {
+
+ /** @see java.io.RandomAccessFile */
+ public void close() throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public long getFilePointer() throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public long length() throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public int read() throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b) throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b, int off, int len) throws IOException;
+
+ /** @see java.io.RandomAccessFile */
+ public void seek(long pos) throws IOException;
+}
diff --git a/src/org/catacombae/rar/.cvsignore b/src/org/catacombae/rar/.cvsignore
new file mode 100644
index 0000000..0de89ca
--- /dev/null
+++ b/src/org/catacombae/rar/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/rar/RARFile.java b/src/org/catacombae/rar/RARFile.java
new file mode 100644
index 0000000..838e886
--- /dev/null
+++ b/src/org/catacombae/rar/RARFile.java
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rar;
+
+import org.catacombae.rarx.*;
+
+public class RARFile {
+ public RARHeader[] readHeaders() {
+ return null;
+ }
+ public RARFileEntryStream getData(NewFileHeader nfh) {
+ return null;
+ }
+}
diff --git a/src/org/catacombae/rarx/.cvsignore b/src/org/catacombae/rarx/.cvsignore
new file mode 100644
index 0000000..0de89ca
--- /dev/null
+++ b/src/org/catacombae/rarx/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/rarx/CommentHeader.java b/src/org/catacombae/rarx/CommentHeader.java
new file mode 100644
index 0000000..e3c080c
--- /dev/null
+++ b/src/org/catacombae/rarx/CommentHeader.java
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class CommentHeader extends NewFileHeader {
+ // This file header type is observed under UnpVer 0x1d (RAR 2.9)
+ // This file header type is actually an archive comment!
+ /* This type of header has a structure extactly like the NewFileHeader, but
+ with a different head type (0x7a). The filename is probably always "CMT"
+ (for "Comment"), and the data found when extracting it is the archive
+ comment. */
+
+ public CommentHeader(byte[] data, int offset) {
+ super(data, offset);
+ //super.super.validateData();
+ }
+
+ protected void validateData() {
+ //print(System.out, "");
+ //super.super.validateData(); //arvsproblem...
+ if(getHeadType() != COMMENT_HEAD)
+ throw new InvalidDataException("Incorrect head type! (headType=" + getHeadType() + ")");
+ if(getHeadSize() < getStaticSize())
+ throw new InvalidDataException("Invalid size! (size=" + getHeadSize() + ")");
+ if(getHostOSAsString() == null)
+ throw new InvalidDataException("Host OS value invalid.");
+
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "CommentHeader: ");
+ printFields(ps, prefix);
+ }
+}
diff --git a/src/org/catacombae/rarx/DecompressionCode.java b/src/org/catacombae/rarx/DecompressionCode.java
new file mode 100644
index 0000000..20f79f2
--- /dev/null
+++ b/src/org/catacombae/rarx/DecompressionCode.java
@@ -0,0 +1,1715 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+//import org.catacombae.rarx.*;
+import java.math.BigInteger;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.zip.CRC32;
+
+public class DecompressionCode {
+ private static final int MAXWINSIZE = 0x100000;
+ private static final int MAXWINMASK = (MAXWINSIZE-1);
+ private static final int UNP_MEMORY = MAXWINSIZE;
+
+ public static final short NC = 298; /* alphabet = {0,1,2, .,NC - 1} */
+ public static final short DC = 48;
+ public static final short RC = 28;
+ public static final short BC = 19;
+ public static final short MC = 257;
+
+ public static final int CODE_HUFFMAN = 0;
+ public static final int CODE_LZ = 1;
+ public static final int CODE_LZ2 = 2;
+ public static final int CODE_REPEATLZ = 3;
+ public static final int CODE_CACHELZ = 4;
+ public static final int CODE_STARTFILE = 5;
+ public static final int CODE_ENDFILE = 6;
+ public static final int CODE_ENDMM = 7;
+ public static final int CODE_STARTMM = 8;
+ public static final int CODE_MMDELTA = 9;
+
+ public static final int LHD_SPLIT_BEFORE = 1; //flag bit 0
+ public static final int LHD_SPLIT_AFTER = 2; //flag bit 1
+ public static final int LHD_PASSWORD = 4; //flag bit 2
+ public static final int LHD_COMMENT = 8; //flag bit 3
+ public static final int LHD_SOLID = 16; //flag bit 4
+
+ public static final int LHD_WINDOWMASK = 0x00e0;
+ public static final int LHD_WINDOW64 = 0;
+ public static final int LHD_WINDOW128 = 32;
+ public static final int LHD_WINDOW256 = 64;
+ public static final int LHD_WINDOW512 = 96;
+ public static final int LHD_WINDOW1024 = 128;
+ public static final int LHD_DIRECTORY = 0x00e0;
+
+ public static final int NROUNDS = 32;
+
+ /* Static variables belonging to the function Unpack. */
+ private static final short[] LDecode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20,
+ 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224}; //unsigned char[14]
+ private static final byte[] LBits = {0,0,0,0,0,0,0,0,1,1,1,1,2,2, //unsigned char[14]
+ 2,2,3,3,3,3,4,4,4,4,5,5,5,5};
+ private static final int[] DDecode = {0, 1, 2, 3, 4, 6, 8, 12, 16, 24,
+ 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
+ 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576,
+ 32768, 49152, 65536, 98304, 131072,196608,262144,327680,393216,458752,
+ 524288,589824,655360,720896,786432,851968,917504,983040}; //int[48]
+ private static final byte[] DBits = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10,10,11,11,12,12,13,13,14,14,
+ 15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16}; //unsigned char[48]
+ private static final short[] SDDecode = {0,4,8,16,32,64,128,192}; //unsigned char[8]
+ private static final byte[] SDBits = {2,2,3,4,5,6,6,6}; //unsigned char[8]
+
+ public static final /*UBYTE*/ short[] InitSubstTable = {
+ 215, 19,149, 35, 73,197,192,205,249, 28, 16,119, 48,221, 2, 42
+ ,232, 1,177,233, 14, 88,219, 25,223,195,244, 90, 87,239,153,137
+ ,255,199,147, 70, 92, 66,246, 13,216, 40, 62, 29,217,230, 86, 6
+ , 71, 24,171,196,101,113,218,123, 93, 91,163,178,202, 67, 44,235
+ ,107,250, 75,234, 49,167,125,211, 83,114,157,144, 32,193,143, 36
+ ,158,124,247,187, 89,214,141, 47,121,228, 61,130,213,194,174,251
+ , 97,110, 54,229,115, 57,152, 94,105,243,212, 55,209,245, 63, 11
+ ,164,200, 31,156, 81,176,227, 21, 76, 99,139,188,127, 17,248, 51
+ ,207,120,189,210, 8,226, 41, 72,183,203,135,165,166, 60, 98, 7
+ ,122, 38,155,170, 69,172,252,238, 39,134, 59,128,236, 27,240, 80
+ ,131, 3, 85,206,145, 79,154,142,159,220,201,133, 74, 64, 20,129
+ ,224,185,138,103,173,182, 43, 34,254, 82,198,151,231,180, 58, 10
+ ,118, 26,102, 12, 50,132, 22,191,136,111,162,179, 45, 4,148,108
+ ,161, 56, 78,126,242,222, 15,175,146, 23, 33,241,181,190, 77,225
+ , 0, 46,169,186, 68, 95,237, 65, 53,208,253,168, 9, 18,100, 52
+ ,116,184,160, 96,109, 37, 30,106,140,104,150, 5,204,117,112, 84
+ }; // UBYTE[256]
+ public static final /*UDWORD*/ int[] CRCTab = new int[256]; // Initialized in static constructor
+
+ static {
+ // Perform initialization of static structures and variables.
+ //CRCTab = new int[256];
+ InitCRC(CRCTab);
+ }
+
+ private CRC32 crc = new CRC32();
+
+ public final /*UBYTE*/ short[] SubstTable = new short[256];
+
+ private /*unsigned*/ byte PN1, PN2, PN3;
+
+ /* Note: temp_output_buffer is allocated when a file is about to be
+ extracted. It is allocated to NewLhd.UnpSize, which can be quite
+ large. The assumption that the unpacked file can be stored in
+ memory is awkward and must immediately be replaced with an output
+ stream. */
+ private /* unsigned char* */ byte[] temp_output_buffer; /* extract files to this pointer*/
+ private /* unsigned long* */ int temp_output_buffer_offset; /* size of temp. extract buffer */
+ //private /*unsigned int*/ int UnpPtr,WrPtr;
+ private final /*unsigned short*/ short[] OldKey = new short[4];
+
+ private static class Decode {
+ public int MaxNum; // unsigned int
+ public final int[] DecodeLen = new int[16]; // unsigned int[16]
+ public final int[] DecodePos = new int[16]; // unsigned int[16]
+ public final int[] DecodeNum; // unsigned int[]
+
+ public Decode() {
+ this(2);
+ }
+
+ protected Decode(int decodeNumSize) { DecodeNum = new int[decodeNumSize]; }
+ }
+ private static class LitDecode extends Decode {
+ public LitDecode() { super(NC); }
+ }
+ private static class DistDecode extends Decode {
+ public DistDecode() { super(DC); }
+ }
+ private static class RepDecode extends Decode {
+ public RepDecode() { super(RC); }
+ }
+ private static class MultDecode extends Decode {
+ public MultDecode() { super(MC); }
+ }
+ private static class BitDecode extends Decode {
+ public BitDecode() { super(BC); }
+ }
+
+ private final LitDecode LD = new LitDecode();
+ private final DistDecode DD = new DistDecode();
+ private final RepDecode RD = new RepDecode();
+ private final MultDecode[] MD = { new MultDecode(), new MultDecode(),
+ new MultDecode(), new MultDecode() }; //new MultDecode[4];
+ private final BitDecode BD = new BitDecode();
+
+ private final MultDecode[] MDPtr = { MD[0], MD[1], MD[2], MD[3] };
+
+
+ /* *****************************
+ * ** unpack stored RAR files **
+ * *****************************/
+
+// BOOL UnstoreFile(void)
+// {
+// if ((long)(*temp_output_buffer_offset=UnpRead(temp_output_buffer,
+// NewLhd.UnpSize))==-1)
+// {
+// debug_log("Read error of stored file!");
+// return FALSE;
+// }
+// return TRUE;
+// }
+
+
+
+
+ /* ****************************************
+ * ** RAR decompression code starts here **
+ * ****************************************/
+
+ /* #define statements */
+ public static class AudioVariables {
+ public int K1,K2,K3,K4,K5; // Should be short...?
+ public int D1,D2,D3,D4;
+ public int LastDelta;
+ public final int[] Dif = new int[11]; //unsigned
+ public int ByteCount; //unsigned
+ public int LastChar;
+
+ public void zero() {
+ K1 = 0;
+ K2 = 0;
+ K3 = 0;
+ K4 = 0;
+ K5 = 0;
+ D1 = 0;
+ D2 = 0;
+ D3 = 0;
+ D4 = 0;
+ LastDelta = 0;
+ Util.zero(Dif);
+ ByteCount = 0;
+ LastChar = 0;
+ }
+ }
+
+ public AudioVariables[] AudV = { new AudioVariables(), new AudioVariables(),
+ new AudioVariables(), new AudioVariables() };
+
+ /*
+#define GetBits() \
+ BitField = ( ( ( (UDWORD)InBuf[InAddr] << 16 ) | \
+ ( (UWORD) InBuf[InAddr+1] << 8 ) | \
+ ( InBuf[InAddr+2] ) ) \
+ >> (8-InBit) ) & 0xffff;
+ */
+ private static int GetBits(byte[] inBuf, int inAddr, int inBit) {
+ return ( ( ( (int) (inBuf[inAddr]&0xFF) << 16 ) |
+ ( (short) (inBuf[inAddr+1]&0xFF) << 8 ) |
+ ( (inBuf[inAddr+2]&0xFF) ) )
+ >>> (8-unsign(inBit)) ) & 0xffff;
+ }
+
+ /*
+#define AddBits(Bits) \
+ InAddr += ( InBit + (Bits) ) >> 3; \
+ InBit = ( InBit + (Bits) ) & 7;
+ */
+
+ /**
+ * Adds <code>Bits</code> to the address specifiers.
+ * InAddr is the high 32 bits of the 35-bit address (?) while InBit is the low 3 bits (?).
+ * Modifies: InAddr, InBit
+ * @param Bits the bits to add (interpreted as unsigned int)
+ */
+ private void AddBits(int Bits) {
+ InAddr += (int)(( unsign(InBit) + unsign(Bits) ) >>> 3); // (InBit + Bits) / 8
+ InBit = (int)(( unsign(InBit) + unsign(Bits) ) & 7);
+ }
+
+ public static int unsign(byte i) {
+ return i & 0xFF;
+ }
+ public static int unsign(short i) {
+ return i & 0xFFFF;
+ }
+ public static long unsign(int i) {
+ return i & 0xFFFFFFFFL;
+ }
+ public static BigInteger unsign(long i) {
+ return new BigInteger(1, Util.toByteArrayBE(i));
+ }
+
+ private static class Struct_NewFileHeader {
+ short HeadCRC;
+ byte HeadType;
+ short Flags;
+ short HeadSize;
+ int PackSize;
+ int UnpSize;
+ byte HostOS;
+ int FileCRC;
+ int FileTime;
+ byte UnpVer;
+ byte Method;
+ short NameSize;
+ int FileAttr;
+ };
+ private NewFileHeader NewLhd;// = new NewFileHeader();
+
+ private RARFileEntryStream ArcPtr = null;
+ private int Encryption;
+ private long UnpPackedSize;
+ private /*unsigned long*/ int CurUnpRead, CurUnpWrite;
+
+ //private byte[] UnpBuf;
+ private int BitField; //unsigned int
+ //private int Number; //unsigned int
+
+ public final byte[] InBuf = new byte[8192]; /* input read buffer */
+
+ public final byte[] UnpOldTable = new byte[MC*4];
+
+ public int InAddr,InBit,ReadTop; //unsigned int
+
+ //public int LastDist,LastLength; //unsigned int
+ //private int Length,Distance; //unsigned int
+
+ //public final int[] OldDist = new int[4]; //unsigned int
+ //public int OldDistPtr; //unsigned int
+
+
+ public int UnpAudioBlock;
+ public int UnpChannels;
+ public int CurChannel;
+ public int ChannelDelta;
+ private boolean FileFound;
+
+
+ /* *** 38.3% of all CPU time is spent within this function!!! */
+ /**
+ * Unpacks stuff.<p>
+ *
+ * <pre>
+ * Global read set (constants):
+ * DBits (final byte[48])
+ * DDecode (final int[48])
+ * LBits (final byte[14])
+ * LDecode (final short[14])
+ * LHD_SOLID (final int)
+ * MAXWINMASK (final int)
+ * MDPtr (final {@link MultDecode}[4])
+ * SDBits (final byte[8])
+ * SDDecode (final short[8])
+ *
+ * Global read set (variables):
+ * FileFound (boolean)
+ * NewLhd ({@link Struct_NewFileHeader})
+ *
+ * Global modify set:
+ * BitField (int)
+ * CurChannel (int)
+ * DD (final {@link DistDecode})
+ * InAddr (int)
+ * InBit (int)
+ * InBuf (final byte[8192])
+ * LD (final {@link LitDecode})
+ * temp_output_buffer (byte[])
+ * temp_output_buffer_offset (int)
+ * UnpAudioBlock (int)
+ * UnpChannels (int)
+ *
+ * Local use set:
+ * Bits (int)
+ * (in) DestUnpSize (long)
+ * Distance (int)
+ * LastDist (int)
+ * LastLength (int)
+ * Length (int)
+ * Number (int)
+ * OldDist (final int[4])
+ * OldDistPtr (int)
+ * (in) UnpAddr (byte[])
+ * UnpBuf (byte[])
+ * UnpPtr (int)
+ * WrPtr (int)
+ *
+ * Call set:
+ * {@link #UnpInitData}
+ * {@link #UnpReadBuf}
+ * {@link #ReadTables}
+ * {@link #debug_log}
+ * {@link System#arraycopy}
+ * {@link #DecodeNumber}
+ * {@link #DecodeAudio}
+ * {@link #GetBits}
+ * {@link #AddBits}
+ * {@link #ReadLastTables}
+ * </pre>
+ */
+ public void Unpack(/*unsigned char*/ byte[] UnpAddr, /*long DestUnpSize, */RARFileEntryStream i_ArcPtr, OutputStream dataOut, NewFileHeader i_NewLhd) throws IOException {
+ // catacombae
+ ArcPtr = i_ArcPtr;
+ NewLhd = i_NewLhd;
+ long DestUnpSize = NewLhd.getUnpSize();
+ UnpPackedSize = NewLhd.getPackSize();
+ FileFound = true; //Otherwise no data will be written, just uncompressed and skipped (for the purpose of extracting solid archives).
+ Encryption = 0;
+ CurUnpRead = CurUnpWrite = 0;
+ // /catacombae
+
+ int Bits; // unsigned int
+
+ byte[] UnpBuf=UnpAddr; /* UnpAddr is a pointer to the unpack buffer */
+ int UnpPtr = 0; // Pointer to where in the buffer we are at present
+ int WrPtr = 0; // Pointer to where in the buffer UnpBuf we were at the last write
+ int Length = 0;
+ int Distance = 0;
+ int LastDist = 0;
+ int LastLength = 0;
+ int Number = 0;
+ final int[] OldDist = new int[4];
+ int OldDistPtr = 0;
+
+ System.out.println("UnpInitData");
+ UnpInitData(UnpBuf);
+ System.out.println("UnpReadBuf");
+ UnpReadBuf(true);
+ if((NewLhd.getFlags() & LHD_SOLID) == 0) {
+ System.out.println("ReadTables");
+ ReadTables();
+ }
+
+ DestUnpSize--;
+
+ int l256 = 0, g269 = 0, e269 = 0, e256 = 0, l261 = 0, l270 = 0; // debug / understanding
+ while(DestUnpSize>=0) {
+ System.out.println("Looping (DestUnpSize=" + DestUnpSize + ")");
+ UnpPtr &= MAXWINMASK;
+
+ if(unsign(InAddr) > InBuf.length-30)
+ UnpReadBuf(false);
+ if(((WrPtr-UnpPtr) & MAXWINMASK)<270 && WrPtr!=UnpPtr) {
+ System.out.println("YO");
+ if(FileFound) {
+ // Flush extracted data to file
+ if(!writeData(UnpBuf, UnpPtr, WrPtr, dataOut))
+ DestUnpSize = -1;
+ }
+ WrPtr=UnpPtr;
+ }
+ else {
+ //System.out.println("((" + WrPtr + "-" + UnpPtr + ") & MAXWINMASK) >= 270");
+ //System.out.println(((WrPtr-UnpPtr) & MAXWINMASK) + "<270");
+ }
+
+ if(UnpAudioBlock != 0) {
+ Number = DecodeNumber(MDPtr[CurChannel]);
+ if (Number==256) {
+ ReadTables();
+ }
+ else {
+ UnpBuf[UnpPtr++]=DecodeAudio(Number);
+ if (++CurChannel==UnpChannels)
+ CurChannel=0;
+ DestUnpSize--;
+ }
+ //continue;
+ }
+ else {
+ Number = DecodeNumber(LD);
+ if(Number<256) { // stored
+ System.out.println(Number);
+ ++l256;
+ UnpBuf[UnpPtr++]=(byte)Number;
+ DestUnpSize--;
+ //continue;
+ }
+ else if(Number>269) {
+ ++g269;
+ Length = LDecode[Number-=270]+3;
+ if ((Bits=LBits[Number])>0) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ Length+=BitField>>>(16-Bits);
+ AddBits(Bits);
+ }
+
+ Number = DecodeNumber(DD);
+ Distance = DDecode[Number]+1;
+ if ((Bits=DBits[Number])>0) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ Distance += BitField >>> (16-Bits);
+ AddBits(Bits);
+ }
+
+ if (Distance>=0x40000)
+ Length++;
+
+ if (Distance>=0x2000)
+ Length++;
+
+ LastDist=OldDist[OldDistPtr++ & 3]=Distance;
+ DestUnpSize-=(LastLength=Length);
+ while (Length-- != 0) {
+ UnpBuf[UnpPtr]=UnpBuf[(UnpPtr-Distance) & MAXWINMASK];
+ UnpPtr=(UnpPtr+1) & MAXWINMASK;
+ }
+
+ //continue;
+ }
+ else if(Number==269) {
+ ++e269;
+ ReadTables();
+ //continue;
+ }
+ else if(Number==256) {
+ ++e256;
+ Length = LastLength;
+ Distance = LastDist;
+ LastDist = OldDist[OldDistPtr++ & 3] = Distance;
+ DestUnpSize -= (LastLength=Length);
+ while(Length-- != 0) {
+ UnpBuf[UnpPtr] = UnpBuf[(UnpPtr-Distance) & MAXWINMASK];
+ UnpPtr = (UnpPtr+1) & MAXWINMASK;
+ }
+ //continue;
+ }
+ else if(Number<261) {
+ ++l261;
+ Distance=OldDist[(OldDistPtr-(Number-256)) & 3];
+ Number = DecodeNumber(RD);
+ Length=LDecode[Number]+2;
+ if ((Bits=LBits[Number])>0) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ Length+=BitField>>>(16-Bits);
+ AddBits(Bits);
+ }
+ if (Distance>=0x40000)
+ Length++;
+ if (Distance>=0x2000)
+ Length++;
+ if (Distance>=0x101)
+ Length++;
+ LastDist=OldDist[OldDistPtr++ & 3]=Distance;
+ DestUnpSize-=(LastLength=Length);
+ while (Length-- != 0) {
+ UnpBuf[UnpPtr]=UnpBuf[(UnpPtr-Distance) & MAXWINMASK];
+ UnpPtr=(UnpPtr+1) & MAXWINMASK;
+ }
+ //continue;
+ }
+ else if(Number<270) {
+ ++l270;
+ Distance=SDDecode[Number-=261]+1;
+ if ((Bits=SDBits[Number])>0) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ Distance+=BitField>>>(16-Bits);
+ AddBits(Bits);
+ }
+ Length=2;
+ LastDist=OldDist[OldDistPtr++ & 3]=Distance;
+ DestUnpSize-=(LastLength=Length);
+ while (Length-- != 0) {
+ UnpBuf[UnpPtr]=UnpBuf[(UnpPtr-Distance) & MAXWINMASK];
+ UnpPtr=(UnpPtr+1) & MAXWINMASK;
+ }
+ //continue;
+ }
+ else
+ debug_log("This message will NEVER be printed. If it is printed anyway, kick the programmer.");
+ }
+ }
+ System.out.println("x < 256: " + l256);
+ System.out.println("x > 269: " + g269);
+ System.out.println("x = 269: " + e269);
+ System.out.println("x = 256: " + e256);
+ System.out.println("x < 261: " + l261);
+ System.out.println("x < 270: " + l270);
+ System.out.println("Total: " + (l256+g269+e269+e256+l261+l270));
+ System.out.println("LD.DecodeLen: 0x" + Util.toHexStringBE(LD.DecodeLen));
+ System.out.println("LD.DecodeNum: 0x" + Util.toHexStringBE(LD.DecodeNum));
+ System.out.println("LD.DecodePos: 0x" + Util.toHexStringBE(LD.DecodePos));
+ System.out.println("LD.MaxNum: 0x" + Util.toHexStringBE(LD.MaxNum));
+ ReadLastTables();
+
+ if (FileFound) { /* flush buffer */
+ // Flush extracted data to file
+ if(!writeData(UnpBuf, UnpPtr, WrPtr, dataOut))
+ DestUnpSize = -1;
+ }
+
+ WrPtr=UnpPtr;
+ }
+
+ /**
+ * Writes the data in <code>UnpBuf</code> to <code>dataOut</code> according to some rules.<p>
+ * <pre>
+ * Global read set:
+ * {@link #NewLhd} ({@link NewFileHeader})
+ *
+ * Global modify set:
+ * *temp_output_buffer (byte[])
+ * temp_output_buffer_offset (int)
+ *
+ * Local use set:
+ * (in) UnpBuf (byte[])
+ * </pre>
+ */
+ private boolean writeData(byte[] UnpBuf, int UnpPtr, int WrPtr, OutputStream dataOut) throws IOException {
+ if (UnpPtr<WrPtr) {
+ debug_log("UnpPtr<WrPtr (" + UnpPtr + "<" + WrPtr + ")");
+ if((temp_output_buffer_offset + UnpPtr) > NewLhd.getUnpSize()) {
+ debug_log("Fatal! Buffer overrun during decompression!");
+ return false; //DestUnpSize=-1;
+ } else if(true) {
+ int firstOutLengthOld = (0-WrPtr) & MAXWINMASK; // Don't understand...
+ int firstOutLength = (UnpBuf.length-1) - WrPtr; //This should be equivalent to the above
+ if(firstOutLengthOld != firstOutLength)
+ debug_log("Assumption broken for firstOutLength :(");
+
+ dataOut.write(UnpBuf, WrPtr, firstOutLength);
+ dataOut.write(UnpBuf, 0, UnpPtr);
+ temp_output_buffer_offset += firstOutLength+UnpPtr;
+ } else {
+ int firstOutLength = (0-WrPtr) & MAXWINMASK; // Don't understand...
+ /* copy extracted data to output buffer */
+ System.arraycopy(UnpBuf, WrPtr, temp_output_buffer, temp_output_buffer_offset, firstOutLength);
+ /* update offset within buffer */
+ temp_output_buffer_offset += firstOutLength;
+ /* copy extracted data to output buffer */
+ System.arraycopy(UnpBuf, 0, temp_output_buffer, temp_output_buffer_offset, UnpPtr);
+ /* update offset within buffer */
+ temp_output_buffer_offset += UnpPtr;
+ }
+ } else {
+ debug_log("UnpPtr>WrPtr (" + UnpPtr + ">" + WrPtr + ")");
+
+ if((temp_output_buffer_offset + (UnpPtr-WrPtr)) > NewLhd.getUnpSize()) {
+ debug_log("Fatal! Buffer overrun during decompression!");
+ //DestUnpSize=-1;
+ return false;
+ } else if(true) {
+ dataOut.write(UnpBuf, WrPtr, UnpPtr-WrPtr);
+ temp_output_buffer_offset += UnpPtr-WrPtr;
+ } else {
+ /* copy extracted data to output buffer */
+ System.arraycopy(UnpBuf, WrPtr, temp_output_buffer, temp_output_buffer_offset, UnpPtr-WrPtr);
+ temp_output_buffer_offset+=UnpPtr-WrPtr; /* update offset within buffer */
+ }
+ }
+ return true;
+ }
+ /**
+ * Reads into the buffer <code>InBuf</code> from the archive stream. If the flag FirstBuf
+ * is set, the method preserves the last 32 bytes of InBuf and copies them to the front
+ * of the buffer, filling the buffer with only <code>InBuf.length-32</code> bytes.<p>
+ * <pre>
+ * Global modify set:
+ * ReadTop (int)
+ * InAddr (int)
+ * Through UnpRead:
+ * UnpPackedSize (int)
+ * CurUnpRead (int)
+ *
+ * Global read set:
+ * InBuf (byte[]) (contents modified in UnpRead)
+ *
+ * Local use set:
+ * (in) FirstBuf (boolean)
+ * RetCode (int)
+ *
+ * Call set:
+ * {@link UnpRead}
+ * </pre>
+ * @param FirstBuf flag indicating whether or not we will treat this read as the first read
+ * to the buffer, thus destroying any previous content
+ */
+ private void UnpReadBuf(boolean FirstBuf) {
+ int RetCode;
+ if(FirstBuf) {
+ ReadTop = UnpRead(InBuf, 0, InBuf.length);
+ InAddr = 0;
+ }
+ else {
+ System.arraycopy(InBuf, InBuf.length-32, InBuf, 0, 32);
+ InAddr &= 0x1f; // discard all but the five least significant bytes... is this modulo 32? think so.
+ RetCode = UnpRead(InBuf, 32, InBuf.length-32);
+ if(RetCode > 0)
+ ReadTop=RetCode+32;
+ else
+ ReadTop=InAddr;
+ }
+ }
+
+ /**
+ * Reads <code>Count</code> bytes from the stream <code>ArcPtr</code> into
+ * the buffer <code>Addr</code> at position <code>offset</code>. If the
+ * data is encrypted, it is automatically decrypted. (The variable
+ * <code>Encryption</code> tells the function whether is shall consider
+ * the data to be encrypted.)<p>
+ * <pre>
+ * Global read set:
+ * ArcPtr ({@link java.io.InputStream})
+ * Encryption (int)
+ *
+ * Global modify set:
+ * UnpPackedSize (int)
+ * CurUnpRead (int)
+ *
+ * Local use set:
+ * (in) Addr (byte[])
+ * (in) offset (int)
+ * (in) Count (int)
+ * RetCode (int)
+ * I (int)
+ * ReadSize (int)
+ * TotalRead (int)
+ * ReadAddr (byte[])
+ * readAddrPointer (int)
+ *
+ * Call set:
+ * tread(File, byte[], int, int)
+ * debug_log(String)
+ * DecryptBlock(byte[], int, int)
+ * </pre>
+ * @return the number of bytes read, or -1 if an error occurred.
+ */
+ private /*unsigned int*/ int UnpRead(/*unsigned char **/ byte[] Addr, int offset, /*unsigned int*/ int Count) {
+ int RetCode=0;
+ /*unsigned int*/ int I,ReadSize,TotalRead=0;
+ /*unsigned char **/ byte[] ReadAddr;
+ int readAddrPointer = offset; // catacombae
+ ReadAddr=Addr;
+ while(Count > 0) {
+ ReadSize=(/*unsigned int*/ int)((Count>(/*unsigned long*/ int)UnpPackedSize) ?
+ UnpPackedSize : Count);
+ if (ArcPtr==null)
+ return(0);
+ RetCode=tread(ArcPtr,ReadAddr,readAddrPointer,ReadSize);
+ debug_log("Read " + RetCode + " from file.");
+
+ CurUnpRead+=RetCode;
+ readAddrPointer+=RetCode;
+ TotalRead+=RetCode;
+ Count-=RetCode;
+ UnpPackedSize-=RetCode;
+ break; // Why the while-loop? if would work just as well.
+ }
+ if (RetCode!= -1) {
+ RetCode=TotalRead;
+ if (Encryption != 0) {
+ if (Encryption<20) {
+ debug_log("Old Crypt() not supported!");
+ }
+ else {
+ for (I=0;I<(/*unsigned int*/ short)RetCode;I+=16)
+ DecryptBlock(/*&Addr[I]*/Addr, I);
+ }
+ }
+ }
+ return(RetCode);
+ }
+
+
+ /**
+ * Reads and initializes the decompression tables.<p>
+ * <pre>
+ * Global read set (constants):
+ * BC (final short)
+ * DC (final short)
+ * MC (final short)
+ * NC (final short)
+ * RC (final short)
+ *
+ * Global read set (variables):
+ *
+ * Global modify set:
+ * BD (BitDecode)
+ * BitField (int)
+ * CurChannel (int)
+ * DD (DistDecode)
+ * InAddr (int)
+ * InBit (int) (through AddBits)
+ * InBuf (final byte[8192])
+ * MDPtr (final MultDecode[4])
+ * RD (RepDecode)
+ * UnpAudioBlock (int)
+ * UnpChannels (int)
+ * UnpOldTable (final byte[MC*4])
+ *
+ *
+ * Local use set:
+ * BitLength (final byte[BC])
+ * Table (final byte[MC*4])
+ * TableSize (int)
+ * N (int)
+ * I (int)
+ *
+ * Call set:
+ * UnpReadBuf
+ * GetBits
+ * Util.zero
+ * AddBits
+ * MakeDecodeTables
+ * DecodeNumber
+ * System.arraycopy
+ * </pre>
+ */
+ private void ReadTables() {
+ System.out.println("ReadTables():");
+ final /*UBYTE*/ byte[] BitLength = new byte[BC];
+ final /*unsigned char*/ byte[] Table = new byte[MC*4];
+ /*int*/ int TableSize,N,I;
+
+ if(InAddr>InBuf.length-25) {
+ System.out.println("InAddr == " + InAddr);
+ UnpReadBuf(false);
+ }
+ BitField = GetBits(InBuf, InAddr, InBit);
+ UnpAudioBlock = (BitField & 0x8000);
+
+ if((BitField & 0x4000) == 0)
+ Util.zero(UnpOldTable);
+ AddBits(2);
+
+
+ if(UnpAudioBlock != 0) {
+ UnpChannels=((BitField >>> 12) & 3)+1;
+ debug_log("WARNING: UnpChannels = " + UnpChannels);
+ if (CurChannel>=UnpChannels)
+ CurChannel=0;
+ AddBits(2);
+ TableSize=(short)(MC*UnpChannels);
+ }
+ else
+ TableSize=NC+DC+RC;
+
+
+ for (I=0;I<BC;I++) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ BitLength[I]=(/*UBYTE*/byte)(BitField >>> 12);
+ AddBits(4);
+ }
+ MakeDecodeTables(BitLength, BD, 0, BC);
+
+ I=0;
+ while(I<TableSize) {
+ if(InAddr>InBuf.length-5)
+ UnpReadBuf(false);
+ int number = DecodeNumber(BD);
+ if(number<16)
+ Table[I++]=(byte)((number+UnpOldTable[I]) & 0xf);
+ else
+ if(number==16) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ N=((BitField >>> 14)+3);
+ AddBits(2);
+ while(N-- > 0 && I<TableSize) {
+ Table[I]=Table[I-1];
+ I++;
+ }
+ }
+ else {
+ if(number==17) {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ N=((BitField >>> 13)+3);
+ AddBits(3);
+ }
+ else {
+ BitField = GetBits(InBuf, InAddr, InBit);
+ N=((BitField >>> 9)+11);
+ AddBits(7);
+ }
+ while(N-- > 0 && I<TableSize)
+ Table[I++]=0;
+ }
+ }
+ if(UnpAudioBlock != 0)
+ for(I=0; I<UnpChannels; I++)
+ MakeDecodeTables(Table, MDPtr[I], (I*MC), MC);
+ else {
+ MakeDecodeTables(Table, LD, 0, NC);
+ MakeDecodeTables(Table, DD, NC, DC);
+ MakeDecodeTables(Table, RD, (NC+DC), RC);
+ }
+
+ System.arraycopy(Table, 0, UnpOldTable, 0, UnpOldTable.length);
+ }
+
+
+ private void ReadLastTables() {
+ if (ReadTop>=InAddr+5) {
+ if (UnpAudioBlock != 0) {
+ int number = DecodeNumber(MDPtr[CurChannel]);
+ if (number==256)
+ ReadTables();
+ }
+ else {
+ int number = DecodeNumber(LD);
+ if (number==269)
+ ReadTables();
+ }
+ }
+ }
+
+ /**
+ * Modifies the <code>Decode</code> object <code>Dec</code> supplied as parameter
+ * according to the data in <code>LenTab</code>. Initializes <code>Dec</code> for
+ * further use in decompression.<p>
+ * <pre>
+ * Global modify set:
+ * <empty>
+ *
+ * Global read set:
+ * <empty>
+ *
+ * Local use set:
+ * (in) LenTab (byte[])
+ * (i/o) Dec ({@link Decode})
+ * (in) offset (int)
+ * (in) Size (int)
+ * LenCount (final int[16])
+ * TmpPos (final int[16])
+ * I (int)
+ * M (int)
+ * N (int)
+ * </pre>
+ */
+ private void MakeDecodeTables(/*unsigned char **/ byte[] LenTab,
+ Decode Dec,
+ int offset,
+ int Size) {
+ final /*int*/int[] LenCount = new int[16];
+ final /*int*/int[] TmpPos = new int[16];
+ /*int*/int I;
+ /*long*/int M, N;
+ //memset(LenCount,0,sizeof(LenCount)); // Java does this automatically
+ for(I=offset; I<offset+Size; I++)
+ LenCount[LenTab[I] & 0xF]++;
+
+ LenCount[0]=0;
+ for(TmpPos[0]=Dec.DecodePos[0]=Dec.DecodeLen[0]=0,N=0,I=1; I<16; I++) {
+ N = 2*(N+LenCount[I]);
+ M = N << (15-I);
+ if(M>0xFFFF)
+ M=0xFFFF;
+ Dec.DecodeLen[I]=(/*unsigned int*/int)M;
+ TmpPos[I]=Dec.DecodePos[I]=((Dec.DecodePos[I-1] & 0xFFFF)+LenCount[I-1]);
+ }
+
+ for(I=offset; I<offset+Size; I++)
+ if(LenTab[I]!=0)
+ Dec.DecodeNum[TmpPos[LenTab[I] & 0xF]++]=I;
+ Dec.MaxNum=Size;
+ }
+
+
+ /* *** 52.6% of all CPU time is spent within this function!!! */
+ /**
+ * Decodes a number from the supplied <code>Decode</code> object.<p>
+ * <pre>
+ * Global modify set:
+ * BitField (int)
+ * In AddBits:
+ * InAddr (int)
+ * InBit (int)
+ *
+ * Local use set:
+ * (in) Deco ({@link Decode})
+ * I (int)
+ * N (int)
+ *
+ * Call set:
+ * GetBits
+ * AddBits
+ * </pre>
+ *
+ * @return the decoded number...?
+ */
+ private int DecodeNumber(Decode Deco) {
+ /*unsigned int*/ int I;
+ /*register unsigned int*/ int N;
+ System.out.println("GetBits(" + InBuf + ", " + InAddr + ", " + InBit + ");");
+ BitField = GetBits(InBuf, InAddr, InBit);
+
+ N=(BitField & 0xFFFE);
+ System.out.println("(1) N == " + N + " (BitField == " + BitField + ")");
+ if(N<Deco.DecodeLen[8]) {
+ if(N<Deco.DecodeLen[4]) {
+ if(N<Deco.DecodeLen[2]) {
+ if(N<Deco.DecodeLen[1])
+ I=1;
+ else
+ I=2;
+ } else {
+ if(N<Deco.DecodeLen[3])
+ I=3;
+ else
+ I=4;
+ }
+ } else {
+ if(N<Deco.DecodeLen[6]) {
+ if(N<Deco.DecodeLen[5])
+ I=5;
+ else
+ I=6;
+ } else {
+ if(N<Deco.DecodeLen[7])
+ I=7;
+ else
+ I=8;
+ }
+ }
+ } else {
+ if(N<Deco.DecodeLen[12]) {
+ if(N<Deco.DecodeLen[10]) {
+ if(N<Deco.DecodeLen[9])
+ I=9;
+ else
+ I=10;
+ } else {
+ if(N<Deco.DecodeLen[11])
+ I=11;
+ else
+ I=12;
+ }
+ } else {
+ if(N<Deco.DecodeLen[14]) {
+ if(N<Deco.DecodeLen[13])
+ I=13;
+ else
+ I=14;
+
+ } else {
+ I=15;
+ }
+ }
+
+ }
+
+ AddBits(I);
+ N = Deco.DecodePos[I] + ((N-Deco.DecodeLen[I-1]) >>> (16-I));
+ System.out.println("(2) N == " + N);
+ if(N >= Deco.MaxNum)
+ N=0;
+ System.out.println("(3) N == " + N);
+
+ System.out.println("Deco.DecodeNum[" + N + "] == " + Deco.DecodeNum[N]);
+ return Deco.DecodeNum[N];
+ }
+
+ /**
+ * Intializes data for the unpack process by setting the variables <code>InAddr</code> and <code>InBit</code> to 0.<br>
+ * If the archive is a solid archive the method also does the following:<br>
+ * <ul>
+ * <li>Set <code>ChannelDelta</code> and <code>CurChannel</code> to 0.</li>
+ * <li>Zero all <code>AudioVariables</code> objects in <code>AudV</code> through {@link AudioVariables#zero}.</li>
+ * <li>Zero the arrays <code>unpBuf</code> and <code>UnpOldTable</code>.</li>
+ * </ul><p>
+ * <pre>
+ * Global read set:
+ * NewLhd ({@link Struct_NewFileHeader})
+ * LHD_SOLID (final int)
+ * MAXWINSIZE (final int)
+ *
+ * Global modify set:
+ * InAddr (int)
+ * InBit (int)
+ * ChannelDelta (int)
+ * CurChannel (int)
+ * AudV ({@link AudioVariables})
+ *
+ * Local use set:
+ * (in) unpBuf (byte[])
+ * </pre>
+ */
+ private void UnpInitData(byte[] unpBuf) {
+ InAddr=InBit=0;
+ if(!((NewLhd.getFlags() & LHD_SOLID) != 0)) {
+ System.out.println("1");
+ ChannelDelta=CurChannel=0;
+
+ //memset(AudV,0,sizeof(AudV));
+ System.out.println("2");
+ for(AudioVariables av : AudV)
+ av.zero();
+ //memset(OldDist,0,sizeof(OldDist));
+ //Util.zero(OldDist);
+ //OldDistPtr=0;
+ //LastDist=LastLength=0;
+ //memset(UnpBuf,0,MAXWINSIZE);
+ System.out.println("3");
+ Util.zero(unpBuf, 0, MAXWINSIZE);
+ //memset(UnpOldTable,0,sizeof(UnpOldTable));
+ System.out.println("4");
+ Util.zero(UnpOldTable);
+ //UnpPtr=WrPtr=0;
+ }
+ }
+
+ /**
+ * Does some kind of audio decoding that I'm not familiar with conceptually.<p>
+ * <pre>
+ * Global read set:
+ * CurChannel (int)
+ *
+ * Global modify set:
+ * AudV ({@link AudioVariables}[])
+ * ChannelDelta (int)
+ *
+ * Local use set:
+ * (in) Delta (int)
+ * V ({@link AudioVariables})
+ * Ch (int)
+ * NumMinDif (int)
+ * MinDif (int)
+ * PCh (int)
+ * I (int)
+ * </pre>
+ */
+ private /*UBYTE*/ byte DecodeAudio(int Delta) {
+ AudioVariables V;
+ /*unsigned */int Ch;
+ /*unsigned */int NumMinDif,MinDif;
+ int PCh,I;
+
+ V=AudV[CurChannel];
+ V.ByteCount++;
+ V.D4=V.D3;
+ V.D3=V.D2;
+ V.D2=V.LastDelta-V.D1;
+ V.D1=V.LastDelta;
+ PCh=8*V.LastChar+V.K1*V.D1+V.K2*V.D2+
+ V.K3*V.D3+V.K4*V.D4+V.K5*ChannelDelta;
+ PCh=(PCh>>3) & 0xFF;
+
+ Ch=PCh-Delta;
+
+ I=((/*signed char*/byte)Delta)<<3;
+
+ V.Dif[0]+=Math.abs(I);
+ V.Dif[1]+=Math.abs(I-V.D1);
+ V.Dif[2]+=Math.abs(I+V.D1);
+ V.Dif[3]+=Math.abs(I-V.D2);
+ V.Dif[4]+=Math.abs(I+V.D2);
+ V.Dif[5]+=Math.abs(I-V.D3);
+ V.Dif[6]+=Math.abs(I+V.D3);
+ V.Dif[7]+=Math.abs(I-V.D4);
+ V.Dif[8]+=Math.abs(I+V.D4);
+ V.Dif[9]+=Math.abs(I-ChannelDelta);
+ V.Dif[10]+=Math.abs(I+ChannelDelta);
+
+ ChannelDelta=V.LastDelta=(/*signed char*/byte)(Ch-V.LastChar);
+ V.LastChar=Ch;
+
+ if((V.ByteCount & 0x1F)==0) {
+ MinDif=V.Dif[0];
+ NumMinDif=0;
+ V.Dif[0]=0;
+ for (I=1;(/*unsigned */int)I<V.Dif.length;I++) {
+ if(V.Dif[I]<MinDif) {
+ MinDif=V.Dif[I];
+ NumMinDif=I;
+ }
+ V.Dif[I]=0;
+ }
+ switch(NumMinDif) {
+ case 1:
+ if(V.K1>=-16)
+ V.K1--;
+ break;
+ case 2:
+ if(V.K1<16)
+ V.K1++;
+ break;
+ case 3:
+ if(V.K2>=-16)
+ V.K2--;
+ break;
+ case 4:
+ if(V.K2<16)
+ V.K2++;
+ break;
+ case 5:
+ if(V.K3>=-16)
+ V.K3--;
+ break;
+ case 6:
+ if(V.K3<16)
+ V.K3++;
+ break;
+ case 7:
+ if(V.K4>=-16)
+ V.K4--;
+ break;
+ case 8:
+ if(V.K4<16)
+ V.K4++;
+ break;
+ case 9:
+ if(V.K5>=-16)
+ V.K5--;
+ break;
+ case 10:
+ if(V.K5<16)
+ V.K5++;
+ break;
+ }
+ }
+ return((/*UBYTE*/byte)Ch);
+ }
+
+
+
+
+
+
+
+ /* ***************************************************
+ * ** CRCCrypt Code - decryption engine starts here **
+ * ***************************************************/
+
+
+
+/* #define rol(x,n) (((x)<<(n)) | ((x)>>(8*sizeof(x)-(n)))) */
+ /** Rotate left. Pure functional behavior, no side effects. */
+ private static byte rol(byte value, int shift) {
+ return (byte) (((value)<<(shift)) | ((value)>>>(8*1-(shift))));
+ }
+ /** @see #rol(byte,int) */
+ private static short rol(short value, int shift) {
+ return (short)(((value)<<(shift)) | ((value)>>>(8*2-(shift))));
+ }
+ /** @see #rol(byte,int) */
+ private static int rol(int value, int shift) {
+ return (((value)<<(shift)) | ((value)>>>(8*4-(shift))));
+ }
+ /** @see #rol(byte,int) */
+ private static long rol(long value, int shift) {
+ return (((value)<<(shift)) | ((value)>>>(8*8-(shift))));
+ }
+
+/* #define ror(x,n) (((x)>>(n)) | ((x)<<(8*sizeof(x)-(n)))) */
+ /** Rotate right. Pure functional behavior, no side effects. */
+ private static byte ror(byte value, int shift) {
+ return (byte) (((value)>>>(shift)) | ((value)<<(8*1-(shift))));
+ }
+ /** @see #ror(byte,int) */
+ private static short ror(short value, int shift) {
+ return (short)(((value)>>>(shift)) | ((value)<<(8*2-(shift))));
+ }
+ /** @see #ror(byte,int) */
+ private static int ror(int value, int shift) {
+ return (((value)>>>(shift)) | ((value)<<(8*4-(shift))));
+ }
+ /** @see #ror(byte,int) */
+ private static long ror(long value, int shift) {
+ return (((value)>>>(shift)) | ((value)<<(8*8-(shift))));
+ }
+
+/*
+#define substLong(t) ( (UDWORD)SubstTable[(int)t&255] | \
+ ((UDWORD)SubstTable[(int)(t>> 8)&255]<< 8) | \
+ ((UDWORD)SubstTable[(int)(t>>16)&255]<<16) | \
+ ((UDWORD)SubstTable[(int)(t>>24)&255]<<24) )
+*/
+ private int substLong(int t) {
+ return ( ((/*UDWORD*/int)SubstTable[(int)(t>> 0)&255]<< 0) |
+ ((/*UDWORD*/int)SubstTable[(int)(t>> 8)&255]<< 8) |
+ ((/*UDWORD*/int)SubstTable[(int)(t>>16)&255]<<16) |
+ ((/*UDWORD*/int)SubstTable[(int)(t>>24)&255]<<24) );
+ }
+
+ // public static final /*UDWORD*/ int[] CRCTab = new int[256]; // Initialized in static constructor
+
+
+ public /*UDWORD*/ int[] Key = new int[4];
+
+
+ public void EncryptBlock(/*UBYTE*/byte[] Buf) {
+ //int I;
+
+ int /*UDWORD*/ A,B,C,D,T,TA,TB;
+// #ifdef NON_INTEL_BYTE_ORDER
+ if(true) {
+ A = ((0xFF & Buf[0] ) | ((0xFF & Buf[1] )<<8) | ((0xFF & Buf[2] )<<16) | ((0xFF & Buf[3] )<<24))^Key[0];
+ B = ((0xFF & Buf[4] ) | ((0xFF & Buf[5] )<<8) | ((0xFF & Buf[6] )<<16) | ((0xFF & Buf[7] )<<24))^Key[1];
+ C = ((0xFF & Buf[8] ) | ((0xFF & Buf[9] )<<8) | ((0xFF & Buf[10])<<16) | ((0xFF & Buf[11])<<24))^Key[2];
+ D = ((0xFF & Buf[12]) | ((0xFF & Buf[13])<<8) | ((0xFF & Buf[14])<<16) | ((0xFF & Buf[15])<<24))^Key[3];
+ }
+ else { // The above code should yield the same result as these lines. Test this assumption.
+ A = Util.readIntLE(Buf, 0 )^Key[0];
+ B = Util.readIntLE(Buf, 4 )^Key[1];
+ C = Util.readIntLE(Buf, 8 )^Key[2];
+ D = Util.readIntLE(Buf, 12)^Key[3];
+ }
+// #else
+// UDWORD *BufPtr;
+// BufPtr=(UDWORD *)Buf;
+// A=BufPtr[0]^Key[0];
+// B=BufPtr[1]^Key[1];
+// C=BufPtr[2]^Key[2];
+// D=BufPtr[3]^Key[3];
+// #endif
+ for(int I=0;I<NROUNDS;I++) {
+ T=((C+rol(D,11))^Key[I&3]);
+ TA=A^substLong(T);
+ T=((D^rol(C,17))+Key[I&3]);
+ TB=B^substLong(T);
+ A=C;
+ B=D;
+ C=TA;
+ D=TB;
+ }
+// #ifdef NON_INTEL_BYTE_ORDER
+ if(true) {
+ C ^= Key[0];
+ Buf[0]=(byte)(0xFF & C);
+ Buf[1]=(byte)(0xFF & (C>>>8));
+ Buf[2]=(byte)(0xFF & (C>>>16));
+ Buf[3]=(byte)(0xFF & (C>>>24));
+ D ^= Key[1];
+ Buf[4]=(byte)(0xFF & D);
+ Buf[5]=(byte)(0xFF & (D>>>8));
+ Buf[6]=(byte)(0xFF & (D>>>16));
+ Buf[7]=(byte)(0xFF & (D>>>24));
+ A ^= Key[2];
+ Buf[8]=(byte)(0xFF & A);
+ Buf[9]=(byte)(0xFF & (A>>>8));
+ Buf[10]=(byte)(0xFF & (A>>>16));
+ Buf[11]=(byte)(0xFF & (A>>>24));
+ B ^= Key[3];
+ Buf[12]=(byte)(0xFF & B);
+ Buf[13]=(byte)(0xFF & (B>>>8));
+ Buf[14]=(byte)(0xFF & (B>>>16));
+ Buf[15]=(byte)(0xFF & (B>>>24));
+ }
+ else {
+ System.arraycopy(Util.toByteArrayLE(C^Key[0]), 0, Buf, 0, 4);
+ System.arraycopy(Util.toByteArrayLE(D^Key[1]), 4, Buf, 0, 4);
+ System.arraycopy(Util.toByteArrayLE(A^Key[2]), 8, Buf, 0, 4);
+ System.arraycopy(Util.toByteArrayLE(B^Key[3]), 12, Buf, 0, 4);
+ }
+// #else
+// BufPtr[0]=C^Key[0];
+// BufPtr[1]=D^Key[1];
+// BufPtr[2]=A^Key[2];
+// BufPtr[3]=B^Key[3];
+// #endif
+ UpdKeys(Buf);
+}
+
+
+ /**
+ * Decrypts a 16 byte block in the buffer <code>Buf</code> at position <code>offset</code>.
+ * The decrypted data is stored at the same place in <code>Buf</code>, thus overwriting the
+ * encrypted contents.<p>
+ * <pre>
+ * Global read set:
+ *
+ * Global modify set:
+ * In UpdKeys:
+ * Key (final int[4])
+ *
+ * Local use set:
+ * (in) Buf (byte[])
+ * (in) offset (int)
+ * InBuf (byte[])
+ * A (int)
+ * B (int)
+ * C (int)
+ * D (int)
+ * T (int)
+ * TA (int)
+ * TB (int)
+ * </pre>
+ */
+ public void DecryptBlock(/*UBYTE*/byte[] Buf, int offset) {
+ //int I;
+ final int n = offset;
+ byte[] /*UBYTE*/ InBuf = new byte[16];
+ int /*UDWORD*/ A,B,C,D,T,TA,TB;
+ System.arraycopy(Buf, n, InBuf, 0, InBuf.length); // memcpy(InBuf,Buf,sizeof(InBuf));
+
+ // Swap all bytes since data is stored in little endian format.
+// if(true) {
+ A = ((0xFF & Buf[n+0] ) | ((0xFF & Buf[n+1] )<<8) | ((0xFF & Buf[n+2] )<<16) | ((0xFF & Buf[n+3] )<<24))^Key[0];
+ B = ((0xFF & Buf[n+4] ) | ((0xFF & Buf[n+5] )<<8) | ((0xFF & Buf[n+6] )<<16) | ((0xFF & Buf[n+7] )<<24))^Key[1];
+ C = ((0xFF & Buf[n+8] ) | ((0xFF & Buf[n+9] )<<8) | ((0xFF & Buf[n+10])<<16) | ((0xFF & Buf[n+11])<<24))^Key[2];
+ D = ((0xFF & Buf[n+12]) | ((0xFF & Buf[n+13])<<8) | ((0xFF & Buf[n+14])<<16) | ((0xFF & Buf[n+15])<<24))^Key[3];
+// }
+// else { // The above code should yield the same result as these lines. Test this assumption.
+ int A2 = Util.readIntLE(Buf, n+0 )^Key[0];
+ int B2 = Util.readIntLE(Buf, n+4 )^Key[1];
+ int C2 = Util.readIntLE(Buf, n+8 )^Key[2];
+ int D2 = Util.readIntLE(Buf, n+12)^Key[3];
+// }
+
+ if(A != A2 || B != B2 || C != C2 || D != D2) {
+ System.out.println("Assumption broken!");
+ System.out.println(" A: 0x" + Util.toHexStringBE(A) + " A2: 0x" + Util.toHexStringBE(A2));
+ System.out.println(" B: 0x" + Util.toHexStringBE(B) + " B2: 0x" + Util.toHexStringBE(B2));
+ System.out.println(" C: 0x" + Util.toHexStringBE(C) + " C2: 0x" + Util.toHexStringBE(C2));
+ System.out.println(" D: 0x" + Util.toHexStringBE(D) + " D2: 0x" + Util.toHexStringBE(D2));
+ }
+ else
+ System.out.println("Assumption correct!");
+
+ for(int I=NROUNDS-1; I>=0; I--) {
+ T=((C+rol(D,11))^Key[I&3]);
+ TA=A^substLong(T);
+ T=((D^rol(C,17))+Key[I&3]);
+ TB=B^substLong(T);
+ A=C;
+ B=D;
+ C=TA;
+ D=TB;
+ }
+
+ if(true) {
+ C ^= Key[0];
+ Buf[n+0 ]=(byte)(0xFF & C);
+ Buf[n+1 ]=(byte)(0xFF & (C>>>8));
+ Buf[n+2 ]=(byte)(0xFF & (C>>>16));
+ Buf[n+3 ]=(byte)(0xFF & (C>>>24));
+ D ^= Key[1];
+ Buf[n+4 ]=(byte)(0xFF & D);
+ Buf[n+5 ]=(byte)(0xFF & (D>>>8));
+ Buf[n+6 ]=(byte)(0xFF & (D>>>16));
+ Buf[n+7 ]=(byte)(0xFF & (D>>>24));
+ A ^= Key[2];
+ Buf[n+8 ]=(byte)(0xFF & A);
+ Buf[n+9 ]=(byte)(0xFF & (A>>>8));
+ Buf[n+10]=(byte)(0xFF & (A>>>16));
+ Buf[n+11]=(byte)(0xFF & (A>>>24));
+ B ^= Key[3];
+ Buf[n+12]=(byte)(0xFF & B);
+ Buf[n+13]=(byte)(0xFF & (B>>>8));
+ Buf[n+14]=(byte)(0xFF & (B>>>16));
+ Buf[n+15]=(byte)(0xFF & (B>>>24));
+ }
+ else {
+ System.arraycopy(Util.toByteArrayLE(C^Key[0]), 0, Buf, n+0 , 4);
+ System.arraycopy(Util.toByteArrayLE(D^Key[1]), 0, Buf, n+4 , 4);
+ System.arraycopy(Util.toByteArrayLE(A^Key[2]), 0, Buf, n+8 , 4);
+ System.arraycopy(Util.toByteArrayLE(B^Key[3]), 0, Buf, n+12, 4);
+ }
+
+ UpdKeys(InBuf);
+ }
+
+
+ /*
+ * As we can't use unsigned data types in Java, we'll have to unsign the data
+ * every time we use it. Modifications: unsign the usage of Buf[I] by "& 0xFF".
+ */
+ /**
+ * Updates keys. ;)
+ * <pre>
+ * Global modify set:
+ * Key (int[4])
+ * Global read set:
+ * CRCTab (int[256])
+ * Local use set:
+ * (in) Buf (byte[])
+ * I (int)
+ * </pre>
+ */
+ public void UpdKeys(byte[]/*UBYTE*/ Buf) {
+ for(int I=0; I<16; I+=4) {
+ Key[0]^=CRCTab[Buf[I] & 0xFF]; /* xxx may be I'll rewrite this */
+ Key[1]^=CRCTab[Buf[I+1] & 0xFF]; /* in asm for speedup */
+ Key[2]^=CRCTab[Buf[I+2] & 0xFF];
+ Key[3]^=CRCTab[Buf[I+3] & 0xFF];
+ }
+ }
+
+ /* Password is supposed to be trimmed to the actual password length and not zero-terminated. */
+ /**
+ * Sets crypt keys. ;)<br>
+ * <code>Password</code> is supposed to be trimmed to the actual
+ * password length and not zero-terminated.<p>
+ * <pre>
+ * Global read set:
+ * InitSubstTable (final short[256])
+ * CRCTab
+ *
+ * Global modify set:
+ * Key (final int[4])
+ * SubstTable (final short[256])
+ * In SetOldKeys:
+ * OldKey (final short[4])
+ * PN1 (byte)
+ * PN2 (byte)
+ * PN3 (byte)
+ *
+ * Local use set:
+ * Password (byte[])
+ * I (int)
+ * J (int)
+ * K (int)
+ * N1 (short)
+ * N2 (short)
+ * Psw (final byte[256])
+ * Ch (short)
+ *
+ * Call set:
+ * SetOldKeys
+ * System.arraycopy
+ * EncryptBlock
+ * </pre>
+ */
+ public void SetCryptKeys(byte[] Password) {
+ /*unsigned int*/ int I,J,K, PswLength;
+ /*unsigned char*/ short N1,N2;
+ final /*unsigned char*/ byte[] Psw = new byte[256];
+
+ /*UBYTE*/ short Ch;
+
+ SetOldKeys(Password);
+
+ Key[0]=0xD3A3B879; // Removed the L at the end (indicates 32 bits in C, 64 in Java)
+ Key[1]=0x3F6D12F7; // -||-
+ Key[2]=0x7515A235; // -||-
+ Key[3]=0xA4E7F123; // -||-
+ //memset(Psw,0,sizeof(Psw)); // Arrays are automatically initialized to 0 in Java.
+ System.arraycopy(Password, 0, Psw, 0, (Password.length<Psw.length?Password.length:Psw.length));
+ PswLength = Password.length;//strlen(Password);
+ System.arraycopy(InitSubstTable, 0, SubstTable, 0, SubstTable.length); // memcpy(SubstTable,InitSubstTable,sizeof(SubstTable));
+
+ for (J=0;J<256;J++) {
+ for (I=0;I<PswLength;I+=2) {
+ N2=/*(unsigned char)*/(byte)CRCTab[((Psw[I+1]&0xFF)+J)&0xFF];
+ for (K=1, N1=/*(unsigned char)*/(byte)CRCTab[((Psw[I]&0xFF)-J)&0xFF]; /* I had to add "&& (N1 < 256)", */
+ (N1!=N2) && (N1 < 256); /* because the system crashed with */
+ N1++, K++) { /* encrypted RARs */
+ /* Swap(&SubstTable[N1],&SubstTable[(N1+I+K)&0xFF]); */
+ Ch=SubstTable[N1];
+ SubstTable[N1]=SubstTable[(N1+I+K)&0xFF];
+ SubstTable[(N1+I+K)&0xFF]=Ch;
+ }
+ }
+ }
+ byte[] currentPswBuf = new byte[16];
+ for (I=0;I<PswLength;I+=16) {
+ System.arraycopy(Psw, I, currentPswBuf, 0, 16);
+ EncryptBlock(currentPswBuf);
+ }
+ }
+
+ /**
+ * Sets old keys. ;)
+ * <pre>
+ * Global read set:
+ * CRCTab (final int[256])
+ *
+ * Global modify set:
+ * OldKey (final short[4])
+ * PN1 (byte)
+ * PN2 (byte)
+ * PN3 (byte)
+ *
+ * Local use set:
+ * (in) Password (byte[])
+ * PswCRC (int)
+ * Ch (byte)
+ *
+ * Call set:
+ * //CalcCRC32
+ * rol
+ * </pre>
+ */
+ private void SetOldKeys(/*char **/byte[] Password) {
+ /*UDWORD*/int PswCRC;
+ /*UBYTE*/byte Ch;
+ crc.reset();
+ crc.update(Password);
+ PswCRC=(int)crc.getValue();//CalcCRC32(0xFFFFFFFF,/*(UBYTE*)*/Password,0,Util.strlen(Password));
+ OldKey[0]=(/*UWORD*/short)PswCRC;
+ OldKey[1]=(/*UWORD*/short)(PswCRC>>>16);
+ OldKey[2]=OldKey[3]=0;
+ PN1=PN2=PN3=0;
+ //while ((Ch=*Password)!=0) {
+ for(int i = 0; (Ch=Password[i])!=0; ++i) {
+ PN1+=Ch;
+ PN2^=Ch;
+ PN3+=Ch;
+ PN3=(/*UBYTE*/byte)rol(PN3,1);
+ OldKey[2]^=((/*UWORD*/short)(Ch^CRCTab[Ch]));
+ OldKey[3]+=((/*UWORD*/short)(Ch+(CRCTab[Ch]>>>16)));
+ //Password++;
+ }
+ }
+
+ /**
+ * Initializes the CRC table <code>crcTab</code>, which has to have a length of 256 elements.
+ * @param crcTab the array where the initialized CRC table is to be stored
+ * @return the same array that was given as input parameter (for convenience)
+ */
+ static int[] InitCRC(int[] crcTab) {
+ int I, J;
+ /*UDWORD*/int C;
+ for (I=0;I<256;I++) {
+ for (C=I,J=0;J<8;J++)
+ C=(C & 1)!=0 ? (C>>>1)^0xEDB88320 : (C>>>1);
+ crcTab[I]=C;
+ }
+ return crcTab;
+ }
+
+
+ /**
+ * Calculates the CRC32 checksum from its arguments.
+ * <pre>
+ * Global read set:
+ * CRCTab ([256])
+ *
+ * Local use set:
+ * (in) StartCRC (int)
+ * (in) Addr (byte[])
+ * (in) offset (int)
+ * (in) Size (int)
+ * I (int)
+ *
+ * Purely functional behavior, no side effects.
+ * </pre>
+ * @return the updated sum
+ */
+ static int CalcCRC32(int StartCRC, byte[] Addr, int offset, int Size) {
+ /*unsigned */int I;
+ for (I=offset; I<Size; I++)
+ StartCRC = CRCTab[(byte)StartCRC ^ Addr[I]] ^ (StartCRC >>> 8);
+ return(StartCRC);
+ }
+
+ /* No side effects. */
+ private static void debug_log(String s) {
+ System.err.println("DEBUG: " + s);
+ }
+
+ /** No side effects (except position in f increasing). */
+ private static int tread(RARFileEntryStream f, byte[] buffer, int offset, int length) {
+ try {
+ return f.read(buffer, offset, length);
+ } catch(Exception e) { e.printStackTrace(); return -1; }
+ }
+}
+
+/* **************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ ************************************************************************** */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* **************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ ******* *******
+ ******* *******
+ ******* *******
+ ******* D E B U G F U N C T I O N S *******
+ ******* *******
+ ******* *******
+ ******* *******
+ ****************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ ************************************************************************** */
+// #ifdef _DEBUG_LOG
+
+
+// /* -- global stuff -------------------------------------------------------- */
+// char log_file_name[256]; /* file name for the log file */
+// DWORD debug_start_time; /* starttime of debug */
+// BOOL debug_started = FALSE; /* debug_log writes only if */
+// /* this is TRUE */
+// /* ------------------------------------------------------------------------ */
+
+
+// /* -- global functions ---------------------------------------------------- */
+// void debug_init_proc(char *file_name)
+// /* Create/Rewrite a log file */
+// {
+// FILE *fp;
+// char date[] = __DATE__;
+// char time[] = __TIME__;
+
+// debug_start_time = GetTickCount(); /* get start time */
+// strcpy(log_file_name, file_name); /* save file name */
+
+// if((fp = fopen(log_file_name, CREATETEXT)) != NULL)
+// {
+// debug_started = TRUE; /* enable debug */
+// fprintf(fp, "Debug log of UniquE's RARFileLib\n"\
+// "~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~\n");
+// fprintf(fp, "(executable compiled on %s at %s)\n\n", date, time);
+// fclose(fp);
+// }
+// }
+
+
+// void debug_log_proc(char *text, char *sourcefile, int sourceline)
+// /* add a line to the log file */
+// {
+// FILE *fp;
+
+// if(debug_started == FALSE) return; /* exit if not initialized */
+
+// if((fp = fopen(log_file_name, APPENDTEXT)) != NULL) /* append to logfile */
+
+// {
+// fprintf(fp, " %8u ms (line %u in %s):\n - %s\n",
+// (/*unsigned */int)(GetTickCount() - debug_start_time),
+// sourceline, sourcefile, text);
+// fclose(fp);
+// }
+// }
+
+// /* ------------------------------------------------------------------------ */
+// #endif
+/* **************************************************************************
+ ****************************************************************************
+ ****************************************************************************
+ ************************************************************************** */
+
diff --git a/src/org/catacombae/rarx/FileAttributes.java b/src/org/catacombae/rarx/FileAttributes.java
new file mode 100644
index 0000000..8e20f2f
--- /dev/null
+++ b/src/org/catacombae/rarx/FileAttributes.java
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public interface FileAttributes {
+ public boolean isDirectory();
+ public void print(PrintStream ps, String prefix);
+ public void printFields(PrintStream ps, String prefix);
+}
diff --git a/src/org/catacombae/rarx/InvalidDataException.java b/src/org/catacombae/rarx/InvalidDataException.java
new file mode 100644
index 0000000..ad0e029
--- /dev/null
+++ b/src/org/catacombae/rarx/InvalidDataException.java
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+public class InvalidDataException extends RuntimeException {
+ private static final long serialVersionUID = 0x2F49A29E49173487L;
+ public InvalidDataException(String message) {
+ super(message);
+ }
+}
+
diff --git a/src/org/catacombae/rarx/Leaf.java b/src/org/catacombae/rarx/Leaf.java
new file mode 100644
index 0000000..87d7f49
--- /dev/null
+++ b/src/org/catacombae/rarx/Leaf.java
@@ -0,0 +1,7 @@
+package org.catacombae.rarx;
+
+public class Leaf<A> extends TreeNode<A> {
+ public Leaf(A value) { super(value); }
+ public Leaf() { super(); }
+}
+
diff --git a/src/org/catacombae/rarx/ListArchiveContents.java b/src/org/catacombae/rarx/ListArchiveContents.java
new file mode 100644
index 0000000..7fac0f1
--- /dev/null
+++ b/src/org/catacombae/rarx/ListArchiveContents.java
@@ -0,0 +1,438 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.*;
+import java.io.*;
+import java.security.MessageDigest;
+import java.util.zip.CRC32;
+
+public class ListArchiveContents {
+
+ /*
+ * Konceptuellt: Vi har en buffer |E4F91...|A81984B....|
+ * ^--------^ <- 32 (0x20) bytes,
+ */
+
+ //public static final byte[] entrySignature = { 0x7C, 0x30, 0x14, 0x30 };
+ public static final String BACKSPACE79 = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
+ public static final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+ public static final short ENTRY_TYPE_DIR = 0x0010;
+ public static final short ENTRY_TYPE_FILE = 0x0020;
+ public static void main(String[] args) throws IOException {
+ boolean deleteOnError = true; // Change this to be decided by a command line parameter.
+ RandomAccessFile raf = new RandomAccessFile(args[0], "r");
+ File outDir = null;
+ if(args.length > 1)
+ outDir = new File(args[1]);
+ byte[] buffer = new byte[4096];
+ byte[] pre = new byte[0x16];
+ byte[] signature = new byte[0x4];
+ byte[] post = new byte[0x6];
+ byte[] postString = new byte[0x5];
+ byte[] staticEntryData = new byte[pre.length+signature.length+
+ post.length+postString.length];
+ for(int i = 0; i < staticEntryData.length; ++i)
+ staticEntryData[i] = 0;
+ ArrayList<byte[]> savedEntries = new ArrayList<byte[]>();
+ ArrayList<byte[]> dirEntries = new ArrayList<byte[]>();
+ ArrayList<byte[]> fileEntries = new ArrayList<byte[]>();
+ LinkedList<String> extractedFiles = new LinkedList<String>();
+ LinkedList<String> badFiles = new LinkedList<String>();
+ LinkedList<String> createErrorFiles = new LinkedList<String>();
+ LinkedList<String> badFilesizeFiles = new LinkedList<String>();
+ int signatureCount = 0;
+ int readAhead = pre.length+4+post.length;
+ int bytesRead = raf.read(buffer);//, readAhead, buffer.length-readAhead);
+ long totalBytesRead = bytesRead;
+ int lastReadPos = bytesRead;
+ //bytesRead += readAhead;
+ while(raf.getFilePointer() < raf.length()) {
+ for(int i = pre.length; i < lastReadPos-(signature.length+post.length); ++i) {
+ if(buffer[i+(0x18-0x16)] == 0x14 &&
+ //buffer[i+(0x19-0x16)] == 0x30 && // This one is not really common for all rars.
+ //buffer[i+(0x16-0x16)] == 0x7c && buffer[i+(0x17-0x16)] == 0x30
+ buffer[i+(0x1b-0x16)] == 0 &&
+ (buffer[i+(0x1c-0x16)] == 0x20 || buffer[i+(0x1c-0x16)] == 0x10) &&
+ buffer[i+(0x1d-0x16)] == 0 &&
+ buffer[i+(0x1e-0x16)] == 0 &&
+ buffer[i+(0x1f-0x16)] == 0 //&&
+ //buffer[i+(0x20-0x16)] == 0
+ ) {
+// System.out.println("buffer[i+(0x1b-0x16)] == " + buffer[i+(0x1b-0x16)]);
+// System.out.println("buffer[i+(0x1c-0x16)] == " + buffer[i+(0x1c-0x16)]);
+// System.out.println("buffer[i+(0x1d-0x16)] == " + buffer[i+(0x1d-0x16)]);
+// System.out.println("buffer[i+(0x1e-0x16)] == " + buffer[i+(0x1e-0x16)]);
+// System.out.println("buffer[i+(0x1f-0x16)] == " + buffer[i+(0x1f-0x16)]);
+ ++signatureCount;
+ System.out.println("File found at position " + ((raf.getFilePointer()-lastReadPos)+i));
+ int fileSize = Util.readIntLE(buffer, i-0xf);
+ System.out.println(" Size: " + fileSize + " bytes (0x" + Util.toHexStringBE(fileSize) + ")");
+ int crc = Util.readIntLE(buffer, i-6);
+ System.out.println(" CRC32: 0x" + Util.toHexStringBE(crc));
+ int stringSize = Util.readShortLE(buffer, i+4) & 0xFFFF;
+ System.out.println(" String size: " + stringSize);
+ short fileType = Util.readShortLE(buffer, i+6);
+ System.out.println(" Some type: 0x" + Util.toHexStringBE(fileType));
+ int difference = i+signature.length+post.length+stringSize+postString.length-lastReadPos;
+
+ if(difference > 0) {
+ System.out.println("Beginning of string: " + new String(buffer, i+signature.length+post.length, stringSize-(difference-postString.length), "US-ASCII"));
+// System.out.println(" i=" + i + " post.length=" + post.length + " bytesRead=" + bytesRead);
+// System.out.println(" String size: " + Util.toHexStringBE(stringSize) + " Difference: " + Util.toHexStringBE(difference));
+ byte[] diffArray = new byte[difference];
+ int tempBytesRead = raf.read(diffArray);
+// System.out.println(" Contents of diffarray: " + new String(diffArray, "US-ASCII"));
+ if(tempBytesRead != diffArray.length)
+ throw new RuntimeException("Read error."); // Orka..
+ totalBytesRead += tempBytesRead;
+
+ System.arraycopy(buffer, difference, buffer, 0, lastReadPos-difference);
+ System.arraycopy(diffArray, 0, buffer, lastReadPos-difference, diffArray.length);
+ i -= difference;
+// System.out.println(" After stringshift: " + new String(buffer, i+4+post.length, stringSize-difference, "US-ASCII"));
+// System.out.println(" " + new String(buffer, i+4+post.length, stringSize, "US-ASCII"));
+ }
+
+ // Save the data for future analysis
+ byte[] data = new byte[staticEntryData.length];
+ System.arraycopy(buffer, i-pre.length, data, 0, pre.length+signature.length+post.length);
+ System.arraycopy(buffer, i+signature.length+post.length+stringSize, data, pre.length+signature.length+post.length, postString.length);
+ savedEntries.add(data);
+
+ try {
+ /*
+ javax.swing.JFrame jf = new javax.swing.JFrame();
+ javax.swing.JTextArea tf = new javax.swing.JTextArea(80, 50);
+ tf.setFont(new java.awt.Font("Monospaced", java.awt.Font.PLAIN, 12));
+ jf.add(new javax.swing.JScrollPane(tf));
+ jf.pack();
+ jf.setVisible(true);
+ for(String charset : java.nio.charset.Charset.availableCharsets().keySet())
+ tf.append("Name: \"" + new String(buffer, i+4+post.length, stringSize, charset) + "\" in charset: " + charset + "\n");
+ if(true)
+ return;
+ */
+
+ String fullFilename = new String(buffer, i+4+post.length, stringSize, "IBM437");
+ String[] filenameComponents = fullFilename.split("\0");
+ String filename = filenameComponents[0];
+ System.out.println(" Name: \"" + filename + "\"");
+ //stdin.readLine();
+ System.out.println(" In hex: 0x" + Util.byteArrayToHexString(buffer, i+4+post.length, stringSize));
+ if(fileType == ENTRY_TYPE_DIR) {
+ System.err.println("Assuming to be dir: " + filename);
+ dirEntries.add(data);
+ }
+// else if(fileSize == 0 || crc == 0 || filename.split("\\.").length == 2) {
+// System.err.println("Suspicious non-10 dir found: " + filename);
+// System.err.println(" fileSize=" + fileSize);
+// System.err.println(" crc=" + crc);
+// System.err.println(" dots in name: " + (filename.split("\\.").length-1));
+// }
+ if(fileType == ENTRY_TYPE_FILE)
+ fileEntries.add(data);
+
+ if(fileType != ENTRY_TYPE_DIR && fileType != ENTRY_TYPE_FILE) {
+ System.err.println("NEW fileType FOUND: 0x" + Util.toHexStringBE(fileType));
+ stdin.readLine();
+ }
+ //System.err.println(" " + filename.split("\\.").length);
+ if(difference > 0)
+ System.out.println(" (array extended)");
+ else
+ System.out.println();
+
+ if(fileType == ENTRY_TYPE_FILE && outDir != null && fileSize != 0 && crc != 0) {
+ File outFile = new File(outDir, filename);
+ File outParent = outFile.getParentFile();
+ if(outParent != null && ((outParent.exists() && outParent.isDirectory()) ||
+ outParent.mkdirs())) {
+ int seekIndexInBuffer = i+signature.length+post.length+stringSize+0x5;
+ long seekPosition = (raf.getFilePointer()-lastReadPos)+seekIndexInBuffer;
+ if(seekPosition+fileSize > raf.length()) {
+ System.out.println("ERROR: Invalid file size for \"" + filename + "\" (" + fileSize + " bytes)");
+ badFilesizeFiles.add(filename);
+ }
+ else {
+ RandomAccessFile outRaf = new RandomAccessFile(outFile, "rw");
+ System.out.println("input.getFilePointer() = " + raf.getFilePointer());
+ System.out.println("readAhead = " + (readAhead));
+ System.out.println("bytesRead = "+ bytesRead);
+ System.out.println("lastReadPos = "+ lastReadPos);
+ System.out.println("i = " + i);
+ byte[] mycrc32 = copyData(raf, seekPosition, outRaf, 0, fileSize);
+ outRaf.close();
+ System.out.println("Their digest: 0x" + Util.toHexStringBE(crc));
+ System.out.println("My digest : 0x" + Util.byteArrayToHexString(mycrc32));
+ //stdin.readLine();
+ if(Util.arraysEqual(Util.toByteArrayBE(crc), mycrc32)) {
+ System.out.println("Digests are equal.");
+ extractedFiles.addLast(filename);
+ /* H�r b�r vi flytta fram filpekaren till precis efter den extraherade filen,
+ f�r att undvika att titta i rar filer som �r lagrade inuti andra rar-filer,
+ samt f�r att snabba upp extraheringen. */
+ long oldFilePointer = raf.getFilePointer();
+ long newFilePointer = seekPosition+fileSize-readAhead;
+ if(newFilePointer > oldFilePointer) {
+ raf.seek(newFilePointer);
+ bytesRead = raf.read(buffer);
+ lastReadPos = bytesRead;
+ totalBytesRead += (raf.getFilePointer() - oldFilePointer);
+ i = pre.length;
+ }
+ else {
+ System.out.print("Situation where newFilePointer <= oldFilePointer har occurred!");
+ //stdin.readLine();
+ }
+ }
+ else {
+ System.out.println("NOT EQUAL! :(:(:(:(");
+ if(deleteOnError) {
+ while(!outFile.delete()) {
+ System.out.print("Could not delete file \"" + outFile + "\" try again (y/n)? ");
+ String answer = stdin.readLine();
+ while(!(answer.trim().equalsIgnoreCase("y") || answer.trim().equalsIgnoreCase("n"))) {
+ System.out.print("Ivalid answer. Answer again: (y/n)? ");
+ answer = stdin.readLine();
+ }
+
+ if(answer.trim().equalsIgnoreCase("n"))
+ break;
+ }
+ }
+ badFiles.addLast(filename);
+ //stdin.readLine();
+ }
+ }
+ //stdin.readLine();
+ }
+ else {
+ System.out.println("Failed to create \"" + outFile + "\"");
+ createErrorFiles.addLast(filename);
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ String answer;
+ do {
+ System.out.print("Do you want to continue (y/n)? ");
+ answer = stdin.readLine();
+ } while(!(answer.trim().equalsIgnoreCase("y") || answer.trim().equalsIgnoreCase("n")));
+
+ if(answer.trim().equalsIgnoreCase("n"))
+ System.exit(0);
+ }
+ }
+ }
+ /* Vi vill nu flytta (readAhead) bytes data fr�n slutet av den fyllda delen av arrayen till b�rjan. */
+ System.arraycopy(buffer, lastReadPos-readAhead, buffer, 0, readAhead);
+
+ /* L�s in nya data i arrayen, men skriv inte �ver dess inledande (readAhead) bytes */
+ bytesRead = raf.read(buffer, readAhead, buffer.length-readAhead);
+ totalBytesRead += bytesRead;
+ lastReadPos = readAhead+bytesRead;
+ }
+ System.out.println();
+ System.err.println("Found " + signatureCount + " signatures.");
+ System.err.println("Found " + savedEntries.size() + " entries.");
+ if(savedEntries.size() > 0) {
+ System.err.println("Running analysis on all entries...");
+ Region[] similarRegions = findSimilarities(savedEntries);
+ byte[] entry = savedEntries.get(0);
+ System.out.println("First entry: 0x" + Util.byteArrayToHexString(entry));
+ LinkedList<byte[]> regionData = new LinkedList<byte[]>();
+ for(Region r : similarRegions) {
+ byte[] currentRegion = new byte[(int)((r.endOffset+1)-r.startOffset)];
+ System.arraycopy(entry, (int)r.startOffset, currentRegion, 0, currentRegion.length);
+ System.out.println(r + ": 0x" + Util.byteArrayToHexString(currentRegion));
+ regionData.addLast(currentRegion);
+ }
+ }
+ if(dirEntries.size() > 0) {
+ System.err.println("Running analysis on directories only...");
+ Region[] similarRegions = findSimilarities(dirEntries);
+ byte[] entry = dirEntries.get(0);
+ LinkedList<byte[]> regionData = new LinkedList<byte[]>();
+ for(Region r : similarRegions) {
+ byte[] currentRegion = new byte[(int)((r.endOffset+1)-r.startOffset)];
+ System.arraycopy(entry, (int)r.startOffset, currentRegion, 0, currentRegion.length);
+ System.out.println(r + ": 0x" + Util.byteArrayToHexString(currentRegion));
+ regionData.addLast(currentRegion);
+ }
+ }
+ if(fileEntries.size() > 0) {
+ System.err.println("Running analysis on files only...");
+ Region[] similarRegions = findSimilarities(fileEntries);
+ byte[] entry = fileEntries.get(0);
+ LinkedList<byte[]> regionData = new LinkedList<byte[]>();
+ for(Region r : similarRegions) {
+ byte[] currentRegion = new byte[(int)((r.endOffset+1)-r.startOffset)];
+ System.arraycopy(entry, (int)r.startOffset, currentRegion, 0, currentRegion.length);
+ System.out.println(r + ": 0x" + Util.byteArrayToHexString(currentRegion));
+ regionData.addLast(currentRegion);
+ }
+ }
+
+ if(badFiles.size() > 0) {
+ System.out.println("The following " + badFiles.size() + " files failed the CRC-32 checksum test" + (deleteOnError?" and were not extracted":"") + ":");
+ for(String filename : badFiles)
+ System.out.println(" \"" + filename + "\"");
+ }
+ if(badFilesizeFiles.size() > 0) {
+ System.out.println("The following " + badFilesizeFiles.size() + " files could not be extracted due to incorrectly specified file size:");
+ for(String filename : badFilesizeFiles)
+ System.out.println(" \"" + filename + "\"");
+ }
+ if(createErrorFiles.size() > 0) {
+ System.out.println("The following " + createErrorFiles.size() + " files could not be created:");
+ for(String filename : createErrorFiles)
+ System.out.println(" \"" + filename + "\"");
+ }
+
+ System.out.println("Read " + totalBytesRead + " bytes from input file.");
+ }
+
+ /** Returns the calculated CRC-32 checksum of the copied data. */
+ public static byte[] copyData(RandomAccessFile input, long inputPos, RandomAccessFile output, long outputPos, long fileSize) throws IOException {
+ CRC32 crc32Digest = new CRC32();//MessageDigest.getInstance("CRC32");
+ long oldFilePointer = input.getFilePointer();
+ byte[] otherBuffer = new byte[4096];
+ input.seek(inputPos);
+ System.out.println("Seeking to " + input.getFilePointer() + ".");
+ int totalBytesRead2 = 0;
+ System.out.println("otherBuffer.length="+otherBuffer.length+" fileSize="+fileSize+" totalBytesRead2="+totalBytesRead2);
+ System.out.println("input.read("+otherBuffer+", "+0+", " + (totalBytesRead2+otherBuffer.length<fileSize?otherBuffer.length:fileSize-totalBytesRead2) + ");");
+ int bytesRead2 = input.read(otherBuffer, 0, (totalBytesRead2+otherBuffer.length<fileSize?otherBuffer.length:(int)(fileSize-totalBytesRead2)));
+ totalBytesRead2 += bytesRead2;
+ while(totalBytesRead2 < fileSize) {
+ crc32Digest.update(otherBuffer, 0, bytesRead2);
+ output.write(otherBuffer, 0, bytesRead2);
+ bytesRead2 = input.read(otherBuffer, 0, (totalBytesRead2+otherBuffer.length<fileSize?otherBuffer.length:(int)(fileSize-totalBytesRead2)));
+ totalBytesRead2 += bytesRead2;
+ }
+
+ crc32Digest.update(otherBuffer, 0, bytesRead2);
+ output.write(otherBuffer, 0, bytesRead2);
+ System.out.println("Extracted contents!");// Check them and press enter...");
+ input.seek(oldFilePointer);
+ return Util.toByteArrayBE((int)(crc32Digest.getValue() & 0xFFFFFFFF));
+ }
+ public static Region[] findSimilarities(List<byte[]> arrays) throws IOException {
+ if(arrays.size() < 1)
+ return new Region[0];
+
+ long minLength = Long.MAX_VALUE;
+ boolean differentLengths = false;
+ for(byte[] currentData : arrays) {
+ if(currentData.length < minLength) {
+ if(minLength != Long.MAX_VALUE)
+ differentLengths = true;
+ minLength = currentData.length;
+ }
+ }
+ if(differentLengths)
+ System.err.println("WARNING: Input arrays are not of equal length...");
+
+ LinkedList<Region> regions = new LinkedList<Region>();
+
+ long regionStart = -1;
+ for(int i = 0; i < minLength; ++i) {
+ int lastByte = -1;
+ int[] values = new int[arrays.size()];
+ for(int j = 0; j < arrays.size(); ++j)
+ values[j] = arrays.get(j)[i];//files[j].read();
+
+ for(int currentByte : values) {
+ //System.out.print("0x" + Integer.toHexString(currentByte) + " == ");
+ if(lastByte == -1)
+ lastByte = currentByte;
+ else if(currentByte != lastByte) {
+ lastByte = -1;
+ //System.out.println("FALSE");
+ break;
+ }
+ }
+// if(lastByte != -1)
+// System.out.println("TRUE");
+ if(lastByte == -1 && regionStart != -1) {
+ //System.out.println("Region concluded at 0x" + Long.toHexString(i));
+ regions.add(new Region(regionStart, i-1));
+ regionStart = -1;
+ }
+ else if(lastByte != -1 && regionStart == -1) {
+ //System.out.println("New region started at 0x" + Long.toHexString(i));
+ regionStart = i;
+ }
+ }
+
+ System.out.println("Found " + regions.size() + " similar regions.");
+ long totalSimilarBytes = 0;
+ for(Region r : regions) {
+ long regionLength = r.endOffset-r.startOffset+1;
+ System.out.println(" 0x" + Long.toHexString(r.startOffset) + "-0x" + Long.toHexString(r.endOffset) + " (" + regionLength + " B)");
+ totalSimilarBytes += regionLength;
+ }
+ System.out.println("Total matching bytes: " + totalSimilarBytes);
+ return regions.toArray(new Region[regions.size()]);
+ }
+
+ private static class Region {
+ public long startOffset;
+ public long endOffset;
+
+ public Region(long startOffset, long endOffset) {
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ }
+ }
+}
+
+class TestModel {
+ public static void main(String[] args) {
+ byte[] buffer = new byte[4096];
+ int readAhead = 32;
+ fill(buffer);
+ while(true) {
+ for(int i = 0; i < buffer.length-readAhead; ++i) {
+
+ }
+
+ // Vi har g�tt igenom buffer fr�n 0 till n-readAhead
+ // Kopiera d� readAhead bytes fr�n slutet till b�rjan och fyll
+ System.arraycopy(buffer, buffer.length-readAhead, buffer, 0, readAhead);
+ fill(buffer, readAhead, buffer.length-readAhead);
+ }
+ }
+
+ public static int alternate(byte[] buffer) {
+ fill(buffer);
+ while(true) {
+ for(int i = 0; i < buffer.length; ++i)
+ dosomething(buffer[i]);
+ fill(buffer);
+ }
+ }
+
+ public static void fill(byte[] buffer) {}
+ public static void fill(byte[] buffer, int i, int j) {}
+ public static void dosomething(byte b) {}
+}
diff --git a/src/org/catacombae/rarx/ListFilesInArchive.java b/src/org/catacombae/rarx/ListFilesInArchive.java
new file mode 100644
index 0000000..1f04825
--- /dev/null
+++ b/src/org/catacombae/rarx/ListFilesInArchive.java
@@ -0,0 +1,173 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.*;
+import java.io.*;
+
+public class ListFilesInArchive {
+ public static void main(String[] args) {
+ try {
+ File f = new File(args[0]);
+ RARFileEntry[] entries = listFiles(f);
+ for(RARFileEntry entry : entries) {
+ NewFileHeader nfh = entry.getHeader(0);
+ System.out.println(nfh.getFilenameAsString());
+ }
+ } catch(InvalidDataException e) {
+ System.out.println("Invalid RAR-file. Message: " + e.getMessage());
+ System.out.println("File: \"" + args[0] + "\"");
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ public static Tree<RARFileEntry> listFilesAsTree(File rarFile) throws IOException, InvalidDataException {
+ return listFilesAsTree(rarFile, false);
+ }
+ public static Tree<RARFileEntry> listFilesAsTree(File rarFile, boolean includeComments) throws IOException, InvalidDataException {
+ RARFileEntry[] contents = listFiles(rarFile, includeComments);
+
+ // Create the empty directory tree
+ Tree<RARFileEntry> rarTree = new Tree<RARFileEntry>();
+ for(RARFileEntry currentEntry : contents) {
+ NewFileHeader nfh = currentEntry.getHeader(0);
+
+ String[] filenameComponents = nfh.getFilenameAsString().split("\\" + NewFileHeader.PATH_SEPARATOR);
+ Tree<RARFileEntry> currentNode = rarTree;
+
+ // Loop through the first n-1 components and create tree entries when needed
+ for(int i = 0; i < filenameComponents.length-1; ++i) {
+ TreeNode<RARFileEntry> childNode = currentNode.get(filenameComponents[i]);
+ if(childNode == null) {
+ childNode = new Tree<RARFileEntry>();
+ currentNode.put(filenameComponents[i], childNode);
+ }
+
+ if(childNode instanceof Tree)
+ currentNode = (Tree<RARFileEntry>)childNode;
+ else
+ throw new RuntimeException("Duplicate entries in RAR file");
+ }
+
+ // The last component is mapped to the current RARFileEntry
+ TreeNode<RARFileEntry> childNode = currentNode.get(filenameComponents[filenameComponents.length-1]);
+ if(childNode == null) {
+ if(nfh.getFileAttributesStructured().isDirectory())
+ childNode = new Tree<RARFileEntry>(currentEntry);
+ else
+ childNode = new Leaf<RARFileEntry>(currentEntry);
+ currentNode.put(filenameComponents[filenameComponents.length-1], childNode);
+ }
+ else if(nfh.getFileAttributesStructured().isDirectory()) {
+ if(childNode.getValue() == null)
+ childNode.setValue(currentEntry);
+ else
+ throw new RuntimeException("Duplicate entries in RAR file");
+ }
+ else
+ throw new RuntimeException("Duplicate entries in RAR file");
+ }
+
+ // Should probably check that the directory structure is complete before passing it on
+
+ return rarTree;
+ }
+
+
+ public static RARFileEntry[] listFiles(File rarFile) throws IOException, InvalidDataException {
+ return listFiles(rarFile, false);
+ }
+ public static RARFileEntry[] listFiles(File rarFile, boolean includeComments) throws IOException, InvalidDataException {
+ LinkedList<RARFileEntry> entries = new LinkedList<RARFileEntry>();
+ RARFileEntry outgoingEntry = null;
+ mainLoop:
+ do {
+ //outgoingEntry = null;
+ RandomAccessFile raf = new RandomAccessFile(rarFile, "r");
+
+ RARFile.readMarkHeader(raf);
+ //NewMainArchiveHeader nmah = RARFile.readNewMainArchiveHeader(raf);
+ //nmah.print(System.out, "");
+ long offset = raf.getFilePointer();
+ RARHeader rh = RARFile.readHeader(raf);
+ while(rh != null) {
+ try {
+ if(rh instanceof NewFileHeader && (includeComments || !(rh instanceof CommentHeader))) {
+ NewFileHeader nfh = (NewFileHeader)rh;
+ if(!nfh.hasIncomingData()) {
+ RARFileEntry rfe = new RARFileEntry(rarFile, offset, nfh);
+ if(outgoingEntry != null && nfh.hasOutgoingData()) {
+ throw new RuntimeException("Found two outgoing files in the same archive!");
+ }
+ else if(nfh.hasOutgoingData())
+ outgoingEntry = rfe;
+
+ entries.add(rfe);
+ }
+ else {
+ if(outgoingEntry != null) {
+ outgoingEntry.addPart(rarFile, offset, nfh);
+ if(!nfh.hasOutgoingData())
+ outgoingEntry = null;
+ }
+ else {
+ // Reading incomplete entry.
+ RARFileEntry rfe = new RARFileEntry(rarFile, offset, nfh);
+ entries.add(rfe);
+ if(nfh.hasOutgoingData())
+ outgoingEntry = rfe;
+ }
+ }
+ /*
+ if(!nfh.hasIncomingData())
+ entries.add(nfh);
+ if(nfh.hasOutgoingData()) {
+ if(outgoingEntry == null)
+ outgoingEntry = nfh;
+ else
+ throw new RuntimeException("Found two outgoing files in the same archive!");
+ }
+ */
+ //nfh.print(System.out, "");
+ }
+ offset = raf.getFilePointer();
+ rh = RARFile.readHeader(raf);
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ break mainLoop;
+ }
+ }
+ if(outgoingEntry != null) {
+ File oldRarFile = rarFile;
+ rarFile = RARFileEntryStream.getNextFile(rarFile);
+ if(rarFile == null)
+ throw new FileNotFoundException("Could not find successor to \"" + oldRarFile + "\"");
+// long posInNextFile = RARFileEntryStream.getMatchingPosInFile(nextFile, nfh);
+// if(posInNextFile == -1)
+// throw new FileNotFoundException();
+// getSegments(nextFile, posInNextFile, list, true);
+ }
+ } while(outgoingEntry != null);
+
+ return entries.toArray(new RARFileEntry[entries.size()]);
+ }
+}
diff --git a/src/org/catacombae/rarx/ListRecursive.java b/src/org/catacombae/rarx/ListRecursive.java
new file mode 100644
index 0000000..a0d34d8
--- /dev/null
+++ b/src/org/catacombae/rarx/ListRecursive.java
@@ -0,0 +1,92 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.*;
+import java.util.regex.Pattern;
+
+public class ListRecursive {
+ public static class RegexpFileFilter implements FileFilter {
+ private String pattern;
+ public RegexpFileFilter(String pattern) {
+ this.pattern = pattern;
+ }
+ public boolean accept(File f) {
+ return Pattern.matches(pattern, f.getName());
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ File rootDir = null;
+ String pattern = null;
+ File outFile = null;
+ System.out.println("args.length=" + args.length);
+ if(args.length > 2) {
+ rootDir = new File(args[0]);
+ pattern = args[1];
+ outFile = new File(args[2]);
+ System.out.println("Writing to file " + outFile.getCanonicalPath());
+ }
+ else if(args.length > 1) {
+ rootDir = new File(args[0]);
+ pattern = args[1];
+ }
+ else if(args.length > 0)
+ pattern = args[0];
+ else {
+ System.out.println("Could not parse arguments.");
+ return;
+ }
+
+ if(rootDir == null)
+ rootDir = new File(System.getProperty("user.dir"));
+
+ FileFilter fnf;
+ if(pattern == null)
+ fnf = null;
+ else
+ fnf = new RegexpFileFilter(pattern);
+
+ if(outFile == null)
+ listRecursive(rootDir, fnf, System.out);
+ else
+ listRecursive(rootDir, fnf, new PrintStream(new FileOutputStream(outFile), false, "UTF-8"));
+ }
+
+ public static void listRecursive(File rootDir, FileFilter fnf, PrintStream ps) {
+ File[] allChildren = rootDir.listFiles();
+ File[] matchingChildren;
+ if(fnf == null)
+ matchingChildren = allChildren;
+ else
+ matchingChildren = rootDir.listFiles(fnf);
+
+ if(matchingChildren != null) {
+ for(File child : matchingChildren)
+ ps.println(child);
+ }
+
+ if(allChildren != null) {
+ for(File child : allChildren)
+ listRecursive(child, fnf, ps);
+ }
+ }
+}
diff --git a/src/org/catacombae/rarx/MarkHeader.java b/src/org/catacombae/rarx/MarkHeader.java
new file mode 100644
index 0000000..76c17ef
--- /dev/null
+++ b/src/org/catacombae/rarx/MarkHeader.java
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+public class MarkHeader extends RARHeader {
+ private static final byte[] oldRarHeader = { 0x52, 0x45, 0x7e, 0x5e };
+ private static final byte[] rar2Header = { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 };
+ private static final byte[] uniqueHeader = { 0x55, 0x6e, 0x69, 0x71, 0x75, 0x45, 0x21 }; // "UniquE!" in ASCII
+
+ public MarkHeader(byte[] data, int offset) {
+ super(data, offset);
+ validateData();
+ }
+
+ protected void validateData() {
+ byte[] mark = getHeaderData();
+ if(Util.arraysEqual(Util.createCopy(mark, 0, 4), oldRarHeader)) {
+ throw new InvalidDataException("An older, unsupported RAR format was detected.");
+ }
+ else {
+ /* original RAR v2.0 */
+ if(Util.arraysEqual(mark, rar2Header) ||
+ Util.arraysEqual(mark, uniqueHeader)) {
+
+ }
+ else
+ throw new InvalidDataException("Not a RAR file.");
+ }
+ }
+}
diff --git a/src/org/catacombae/rarx/NewFileHeader.java b/src/org/catacombae/rarx/NewFileHeader.java
new file mode 100644
index 0000000..104bed1
--- /dev/null
+++ b/src/org/catacombae/rarx/NewFileHeader.java
@@ -0,0 +1,428 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.Calendar;
+import java.util.Date;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+
+public class NewFileHeader extends RARHeader {
+ /*
+ * struct NewFileHeader
+ * size: 32 bytes
+ *
+ * BP Size Description
+ * 0 2 HeadCRC
+ * 2 1 HeadType
+ * 3 2 Flags
+ * 5 2 HeadSize
+ * 7 4 PackSize
+ * 11 4 UnpSize
+ * 15 1 HostOS
+ * 16 4 FileCRC
+ * 20 4 FileTime
+ * 24 1 UnpVer
+ * 25 1 Method
+ * 26 2 NameSize
+ * 28 4 FileAttr
+ */
+
+ // Possible values for the hostOS field in NewFileHeader, and possibly elsewhere...
+ public static final int HOSTOS_MS_DOS = 0;
+ public static final int HOSTOS_OS2 = 1;
+ public static final int HOSTOS_WIN_32 = 2;
+ public static final int HOSTOS_UNIX = 3;
+ public static final String[] hostOSStrings = { "MS-DOS", "OS/2", "Win32", "UNIX" };
+
+ public static final int FM_NORMAL = 0x00;
+ public static final int FM_RDONLY = 0x01;
+ public static final int FM_HIDDEN = 0x02;
+ public static final int FM_SYSTEM = 0x04;
+ public static final int FM_LABEL = 0x08;
+ public static final int FM_DIREC = 0x10;
+ public static final int FM_ARCH = 0x20;
+
+ public static final String PATH_SEPARATOR = "\\";
+
+ private final byte[] packSize = new byte[4];
+ private final byte[] unpSize = new byte[4];
+ private final byte[] hostOS = new byte[1];
+ private final byte[] fileCRC = new byte[4];
+ private final byte[] fileTime = new byte[4];
+ private final byte[] unpVer = new byte[1];
+ private final byte[] method = new byte[1];
+ private final byte[] nameSize = new byte[2];
+ private final byte[] fileAttr = new byte[4];
+ private final byte[] packSizeExtended;
+ private final byte[] unpSizeExtended;
+ private final byte[] filename;
+ private final byte[] trailingData;
+
+ public NewFileHeader(byte[] data, int offset) {
+ super(data, offset);
+ System.arraycopy(data, offset+7, packSize, 0, 4);
+ System.arraycopy(data, offset+11, unpSize, 0, 4);
+ System.arraycopy(data, offset+15, hostOS, 0, 1);
+ System.arraycopy(data, offset+16, fileCRC, 0, 4);
+ System.arraycopy(data, offset+20, fileTime, 0, 4);
+ System.arraycopy(data, offset+24, unpVer, 0, 1);
+ System.arraycopy(data, offset+25, method, 0, 1);
+ System.arraycopy(data, offset+26, nameSize, 0, 2);
+ System.arraycopy(data, offset+28, fileAttr, 0, 4);
+ if(hasExtendedHeader()) {
+ packSizeExtended = new byte[4];
+ unpSizeExtended = new byte[4];
+ System.arraycopy(data, offset+32, packSizeExtended, 0, 4);
+ System.arraycopy(data, offset+36, unpSizeExtended, 0, 4);
+ }
+ else {
+ packSizeExtended = new byte[0];
+ unpSizeExtended = new byte[0];
+ }
+ final int extSize = packSizeExtended.length + unpSizeExtended.length;
+ filename = new byte[getNameSize()];
+ trailingData = new byte[getHeadSize()-(getStaticSize()+extSize+filename.length)];
+ System.arraycopy(data, offset+32+extSize, filename, 0, filename.length);
+ System.arraycopy(data, offset+32+extSize+filename.length, trailingData, 0, trailingData.length);
+ validateData();
+ }
+
+ public static int getStaticSize() { return 32; }
+ public int getSize() { return _getSize(); }
+ private int _getSize() { return getHeadSize(); }
+
+ protected void validateData() {
+ //print(System.out, "");
+ super.validateData();
+ if(getHeadType() != FILE_HEAD)
+ throw new InvalidDataException("Incorrect head type! (headType=" + getHeadType() + ")");
+ if(getHeadSize() < getStaticSize())
+ throw new InvalidDataException("Invalid size! (size=" + getHeadSize() + ")");
+ if(getHostOSAsString() == null)
+ throw new InvalidDataException("Host OS value invalid.");
+ }
+
+ public long getPackSize() {
+ long result = Util.readIntLE(packSize) & 0xFFFFFFFFL;
+ if(packSizeExtended.length == 4)
+ result |= (Util.readIntLE(packSizeExtended) & 0xFFFFFFFFL) << 32;
+ return result;
+ }
+ public long getUnpSize() {
+ long result = Util.readIntLE(unpSize) & 0xFFFFFFFFL;
+ if(unpSizeExtended.length == 4)
+ result |= (Util.readIntLE(unpSizeExtended) & 0xFFFFFFFFL) << 32;
+ return result;
+// if(unpSizeExtended.length == 4) {
+// return Util.readIntLE(unpSize);
+ }
+ public byte getHostOS() { return Util.readByteLE(hostOS); }
+ public int getFileCRC() { return Util.readIntLE(fileCRC); }
+ public int getFileTime() { return Util.readIntLE(fileTime); }
+ public byte getUnpVer() { return Util.readByteLE(unpVer); }
+ public byte getMethod() { return Util.readByteLE(method); }
+ public short getNameSize() { return Util.readShortLE(nameSize); }
+ public int getFileAttr() { return Util.readIntLE(fileAttr); }
+ public byte[] getFilename() { return Util.createCopy(filename); }
+ public byte[] getTrailingData() { return Util.createCopy(trailingData); }
+
+ public byte[] getHeaderData() {
+ byte[] data = new byte[_getSize()];
+ System.arraycopy(super.getHeaderData(), 0, data, 0, super.getSize());
+ System.arraycopy(packSize, 0, data, 7, packSize.length);
+ System.arraycopy(unpSize, 0, data, 11, unpSize.length);
+ System.arraycopy(hostOS, 0, data, 15, hostOS.length);
+ System.arraycopy(fileCRC, 0, data, 16, fileCRC.length);
+ System.arraycopy(fileTime, 0, data, 20, fileTime.length);
+ System.arraycopy(unpVer, 0, data, 24, unpVer.length);
+ System.arraycopy(method, 0, data, 25, method.length);
+ System.arraycopy(nameSize, 0, data, 26, nameSize.length);
+ System.arraycopy(fileAttr, 0, data, 28, fileAttr.length);
+ if(hasExtendedHeader()) {
+ System.arraycopy(packSizeExtended, 0, data, 32, 4);
+ System.arraycopy(unpSizeExtended, 0, data, 36, 4);
+ }
+ final int extSize = packSizeExtended.length + unpSizeExtended.length;
+ System.arraycopy(filename, 0, data, 32+extSize, filename.length);
+ System.arraycopy(trailingData, 0, data, 32+extSize+filename.length, trailingData.length);
+ return data;
+ }
+
+ /**
+ * true if the header is 40 bytes long, false if it is 32 bytes long
+ * The 40 byte header exists to accommodate filesizes > 2^32
+ */
+ public boolean hasExtendedHeader() {
+ return getFlag(8);
+ }
+
+ /**
+ * true if this file is a continuation of a previous file (split archive)
+ */
+ public boolean hasIncomingData() {
+ return getFlag(0);
+ }
+
+ /**
+ * true if this file continues in another file (split archive)
+ */
+ public boolean hasOutgoingData() {
+ return getFlag(1);
+ }
+
+ public Date getFileTimeAsDate() {
+ int data = getFileTime();
+ int year = 1980 + ((data >> (4+5+5+6+5)) & 0x7F);
+ int month = ((data >> (5+5+6+5)) & 0xF)-1;
+ int day = (data >> (5+6+5)) & 0x1F;
+ int hour = (data >> (6+5)) & 0x1F;
+ int minute = (data >> (5)) & 0x3F;
+ int doublesecond = data & 0x1F; // the archive format only supports 2-second precision (probably due to simpler design)
+
+// System.out.println("year = " + year);
+// System.out.println("month = " + month);
+// System.out.println("day = " + day);
+// System.out.println("hour = " + hour);
+// System.out.println("minute = " + minute);
+// System.out.println("doublesecond = " + doublesecond);
+
+ Calendar c = Calendar.getInstance();
+ c.setTime(new Date(0));
+ c.set(Calendar.YEAR, year);
+ c.set(Calendar.MONTH, month);
+ c.set(Calendar.DAY_OF_MONTH, day);
+ c.set(Calendar.HOUR, hour);
+ c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.SECOND, doublesecond*2);
+ return c.getTime();
+ }
+
+ public FileAttributes getFileAttributesStructured() {
+ byte hostOS = getHostOS();
+ if(hostOS == HOSTOS_WIN_32)
+ return new Win32FileAttributes(getFileAttr());
+ else if(hostOS == HOSTOS_UNIX)
+ return new UNIXFileAttributes(getFileAttr());
+ else if(hostOS == HOSTOS_MS_DOS || hostOS == HOSTOS_OS2)
+ return new Win32FileAttributes(getFileAttr()); // Win, DOS and OS/2 seems to have a lot in common, but I don't really know what the file attributes will look like under DOS and OS/2. Hopefully something like the Win32-attributes (:
+ else
+ throw new RuntimeException("Unknown OS type!");
+ }
+
+// public boolean isFile() {
+// return getFlag(getFileAttr(), 13);
+// }
+
+ public String getHostOSAsString() {
+ return hostOSToString(getHostOS());
+ }
+
+ public String getUnpackVersionAsString() {
+ byte unpVer = getUnpVer();
+ return (unpVer/10) + "." + (unpVer%10);
+ }
+
+ public String getFilenameAsString() {
+ String result = getUnicodeFilenameAsString();
+ if(result == null)
+ result = getLegacyFilenameAsString();
+ return result;
+ }
+ public String getLegacyFilenameAsString() {
+ /* After testing, it seems that one of the old MS-DOS charsets
+ are used to store the file name in this structure. If
+ different charsets are supported, I don't know how to detect
+ which charset will be used yet. Code Page 850 is used for now. */
+ try {
+ return new String(filename, /*"IBM437"*/"iso-8859-1").split("\0")[0];
+ } catch(UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+ }
+
+ public String getUnicodeFilenameAsString() {
+ /* To cope with characters outside the old IBM charset originally
+ used for filenames, RAR embeds extra data after the area
+ containing the IBM charset filename. The old style filename is
+ terminated by null, and after that follows the characters that
+ could not be encoded properly with the IBM charset. */
+ boolean debug = false;
+ if(debug) System.out.println("getExtendedCharsetFilenameAsString();");
+ char[] legacyFilename = getLegacyFilenameAsString().toCharArray();
+ char[] unicodeFilename = new char[legacyFilename.length];
+ int unicodeFilenamePointer = 0;
+ //LinkedList<Character> codeUnitList = new LinkedList<Character>();
+
+ // Find the index at which the extended filename characters begin (first occurrence of 0x00).
+ int zeroIndex = 0;
+ for(; zeroIndex < filename.length; ++zeroIndex)
+ if(filename[zeroIndex] == 0)
+ break;
+
+ int codeUnitsRead = 0;;
+
+ int extendedDataPointer = zeroIndex+1;
+ if(extendedDataPointer+2 >= filename.length) // Must have compressedLSB (1 bytes) and at least one byte
+ return null;
+ byte compressedLSB = Util.readByteLE(filename, extendedDataPointer++);
+
+ mainLoop:
+ while(extendedDataPointer < filename.length) {
+ if(debug) System.out.println(" Reading 4 bytes... (found " + codeUnitsRead +
+ " code points, extendedDataPointer=" + extendedDataPointer + ")");
+ byte groupDescriptor = Util.readByteLE(filename, extendedDataPointer++);
+ for(int i = 0; (i < 4) && (extendedDataPointer < filename.length); ++i) {
+ byte current = (byte)((groupDescriptor >>> ((3-i)*2)) & 0x3); // 0b11
+ if(current == 0x0) { // 0b00
+ //if(extendedDataPointer >= filename.length) break;
+ char codeUnit = (char)(Util.readByteLE(filename, extendedDataPointer++) & 0xFF);
+ if(debug) System.out.println(" Encountered 8-bit character: 0x" + Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x1) {
+ //if(extendedDataPointer >= filename.length) break;
+ char codeUnit = (char)(((compressedLSB & 0xFF) << 8) |
+ (Util.readByteLE(filename, extendedDataPointer++) & 0xFF));
+ if(debug) System.out.println(" Encountered compressed 16-bit character: 0x" +
+ Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x2) { // 0b10
+ //if(extendedDataPointer+1 >= filename.length) break;
+ char codeUnit = (char)Util.readShortLE(filename, extendedDataPointer);
+ extendedDataPointer += 2;
+ if(debug) System.out.println(" Encountered 16-bit character: 0x" + Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x3) {
+ //if(extendedDataPointer >= filename.length) break;
+ byte indicator = Util.readByteLE(filename, extendedDataPointer++);
+
+ /* Detta �r bullshit. Det �r INTE en indikator utan ett "skip"-kommando.
+ * Ex. skip(0x02) => Spola f�rbi 4 characters dvs. ".txt" (n�stan alla test*+.rar)
+ * skip(0x05) => Spola f�rbi 7 characters ex. "bok.txt" (ctest6.rar)
+ * skip(0x01) => Spola f�rbi 3 characters ex. "Hej" (ctest16.rar)
+ * skip(0x03) => Spola f�rbi 5 characters ex. "laban" (ctest16.rar)
+ * Specialfall:
+ * - Man kan som minst spola f�rbi 2 characters (skip(0x00)). Om str�ngen inleds med
+ * en ASCII-character med Unicode-char direkt f�ljande s� kodas ASCII-charactern ner
+ * som Unicode.
+ * - P� samma s�tt kan man inte spola f�rbi 131 characters, utan �r tvungen att koda
+ * skip(0x7F)... eller n�t. iaf skitsamma. orka.
+ */
+
+ if((indicator & 0xFF) > 0x7F)
+ System.out.println(" Encountered unexpected skip value! 0x" + Util.toHexStringBE(indicator));
+ if(debug) System.out.println(" Skipping " + (indicator+2) + " bytes.");
+
+ System.arraycopy(legacyFilename, unicodeFilenamePointer, unicodeFilename, unicodeFilenamePointer, (indicator+2));
+ unicodeFilenamePointer += (indicator+2);
+ }
+ }
+ }
+ if(unicodeFilenamePointer != legacyFilename.length)
+ if(debug) System.out.println(" ERROR! Did not read enough characters. Read " + unicodeFilenamePointer + " characters while legacyFilename.length==" + legacyFilename.length + ".");
+ else
+ if(debug) System.out.println(" Read " + unicodeFilenamePointer + " characters.");
+ return new String(unicodeFilename);
+ }
+
+ public byte[] getTrailingFilenameData() {
+ for(int i = 0; i < filename.length; ++i) {
+ if(filename[i] == 0)
+ return Util.createCopy(filename, i, filename.length-i);
+ }
+ return new byte[0];
+ }
+
+ public static String hostOSToString(byte hostOS) {
+ if(hostOS >= 0 && hostOS <= hostOSStrings.length)
+ return hostOSStrings[hostOS];
+ else
+ return null;
+ }
+
+ /* // An attempt at porting the CRC-function from unrarlib to Java. Seems to work poorly.
+ private static class MyCRC32 {
+ private final int[] CRCTab = new int[256];
+ private int currentCRC = 0xFFFFFFFF;
+ public MyCRC32() {
+ short I, J;
+ int C;
+ for(I=0;(I&0xFFFF)<256;I++) {
+ for(C=I,J=0;J<8;J++)
+ C=((C & 1) != 0)? (C>>1)^0xEDB88320 : (C>>1);
+ CRCTab[I&0xFFFF]=C;
+ }
+ }
+
+ public void update(byte[] data) {
+ currentCRC = CalcCRC32(currentCRC, data);
+ }
+
+ public int getValue() {
+ return currentCRC;
+ }
+
+ private int CalcCRC32(int StartCRC, byte[] Addr) {
+ short I;
+ for(I=0; I<Addr.length; I++)
+ StartCRC = CRCTab[((byte)StartCRC ^ Addr[I])&0xFF] ^ (StartCRC >> 8);
+ return(StartCRC);
+ }
+ }
+ */
+
+
+ protected void printFields(PrintStream ps, String prefix) {
+ super.printFields(ps, prefix);
+ ps.println(prefix + " PackSize: " + getPackSize() + " bytes");
+ ps.println(prefix + " UnpSize: " + getUnpSize() + " bytes");
+ ps.println(prefix + " HostOS: \"" + getHostOSAsString() + "\"");
+ ps.println(prefix + " FileCRC: 0x" + Util.toHexStringBE(getFileCRC()));
+ ps.println(prefix + " FileTime: " + getFileTimeAsDate() + " (0x" + Util.toHexStringBE(getFileTime()) + ")");
+ ps.println(prefix + " UnpVer: \"" + getUnpackVersionAsString() + "\"");
+ ps.println(prefix + " Method: 0x" + Util.toHexStringBE(getMethod()));
+ ps.println(prefix + " NameSize: " + getNameSize() + " bytes");
+ ps.println(prefix + " FileAttr: 0x" + Util.toHexStringBE(getFileAttr()));
+ ps.println(prefix + " Structured file attributes:");
+ getFileAttributesStructured().print(ps, prefix + " ");
+ ps.println(prefix + " Filename: \"" + getFilenameAsString() + "\"");
+ byte[] trailingFilenameData = getTrailingFilenameData();
+ if(trailingFilenameData.length > 0)
+ ps.println(prefix + " (Trailing filename data: 0x" + Util.byteArrayToHexString(trailingFilenameData) + ")");
+ ps.println(prefix + " Trailing data: 0x" + Util.byteArrayToHexString(getTrailingData()));
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "NewFileHeader: ");
+ printFields(ps, prefix);
+ }
+}
diff --git a/src/org/catacombae/rarx/NewMainArchiveHeader.java b/src/org/catacombae/rarx/NewMainArchiveHeader.java
new file mode 100644
index 0000000..caf09db
--- /dev/null
+++ b/src/org/catacombae/rarx/NewMainArchiveHeader.java
@@ -0,0 +1,94 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class NewMainArchiveHeader extends RARHeader {
+ /*
+ * struct NewMainArchiveHeader
+ * size: 13 bytes
+ *
+ * BP Size Description
+ * 0 2 HeadCRC
+ * 2 1 HeadType
+ * 3 2 Flags
+ * 5 2 HeadSize
+ * 7 2 Reserved
+ * 9 4 Reserved1
+ */
+
+ private final byte[] reserved = new byte[2];
+ private final byte[] reserved1 = new byte[4];
+
+ public NewMainArchiveHeader(byte[] data, int offset) {
+ super(data, offset);
+ System.arraycopy(data, offset+7, reserved, 0, 2);
+ System.arraycopy(data, offset+9, reserved1, 0, 4);
+ validateData();
+ }
+
+ public int getSize() { return _getSize(); }
+ private int _getSize() { return getHeadSize();/*13;*/ }
+
+ protected void validateData() {
+ super.validateData();
+ if(getHeadType() != MAIN_HEAD)
+ throw new InvalidDataException("Incorrect head type! (headType=" + getHeadType() + ")");
+// if(getHeadSize() != getSize())
+// throw new InvalidDataException("Wrong size! (size=" + getHeadSize() + ")");
+ }
+
+ public short getReserved() { return Util.readShortLE(reserved); }
+ public int getReserved1() { return Util.readIntLE(reserved1); }
+
+ public byte[] getHeaderData() {
+ byte[] data = new byte[_getSize()];
+ System.arraycopy(super.getHeaderData(), 0, data, 0, super.getSize());
+ System.arraycopy(reserved, 0, data, 7, 2);
+ System.arraycopy(reserved1, 0, data, 9, 4);
+ return data;
+ }
+// public short calculateHeadCRC() {
+// CRC32 crc = new CRC32();
+// //MyCRC32 crc = new MyCRC32();
+// // crc.update(headCRC);
+// crc.update(headType);
+// crc.update(flags);
+// crc.update(headSize);
+// crc.update(reserved);
+// crc.update(reserved1);
+// //crc.(trailingData);
+// return (short)(crc.getValue());
+// }
+
+ protected void printFields(PrintStream ps, String prefix) {
+ super.printFields(ps, prefix);
+ ps.println(prefix + " Reserved: " + Util.toHexStringBE(getReserved()));
+ ps.println(prefix + " Reserved1: " + Util.toHexStringBE(getReserved1()));
+ }
+
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "NewMainArchiveHeader: ");
+ printFields(ps, prefix);
+ }
+}
diff --git a/src/org/catacombae/rarx/ProtectedHeader.java b/src/org/catacombae/rarx/ProtectedHeader.java
new file mode 100644
index 0000000..0f7df6d
--- /dev/null
+++ b/src/org/catacombae/rarx/ProtectedHeader.java
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class ProtectedHeader extends RARHeader {
+ private final byte[] dataSize = new byte[4];
+ private final byte[] unknownData;
+ public ProtectedHeader(byte[] data, int offset) {
+ super(data, offset);
+ System.arraycopy(data, super.getSize(), dataSize, 0, 4);
+ unknownData = new byte[getHeadSize()-(super.getSize()+4)];
+ System.arraycopy(data, super.getSize()+4, unknownData, 0, unknownData.length);
+ validateData();
+ }
+
+ public int getSize() {
+ return _getSize();
+ }
+
+ private int _getSize() {
+ return super.getSize()+dataSize.length+unknownData.length;
+ }
+
+ public void validateData() {
+ super.validateData();
+ if(getHeadType() != PROTECT_HEAD)
+ throw new InvalidDataException("Incorrect head type! (headType=" + getHeadType() + ")");
+ }
+
+ public byte[] getHeaderData() {
+ byte[] outData = new byte[_getSize()];
+ byte[] superData = super.getHeaderData();
+ System.arraycopy(superData, 0, outData, 0, superData.length);
+ System.arraycopy(dataSize, 0, outData, superData.length, dataSize.length);
+ System.arraycopy(unknownData, 0, outData, superData.length+dataSize.length, unknownData.length);
+ return outData;
+ }
+
+ public long getDataSize() {
+ return Util.readIntLE(dataSize, 0) & 0xFFFFFFFFL;
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ System.out.println("Protected header:");
+ printFields(ps, prefix);
+ }
+}
diff --git a/src/org/catacombae/rarx/RARFile.java b/src/org/catacombae/rarx/RARFile.java
new file mode 100644
index 0000000..3c63a22
--- /dev/null
+++ b/src/org/catacombae/rarx/RARFile.java
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.Vector;
+import java.io.*;
+import java.util.zip.CRC32;
+
+public class RARFile {
+ public static final int SIZEOF_MARKHEAD = 7;
+ public static final int SIZEOF_OLDMHD = 7;
+ public static final int SIZEOF_NEWMHD = 13;
+ public static final int SIZEOF_OLDLHD = 21;
+ public static final int SIZEOF_NEWLHD = 32;
+ public static final int SIZEOF_SHORTBLOCKHEAD = 7;
+ public static final int SIZEOF_LONGBLOCKHEAD = 11;
+ public static final int SIZEOF_COMMHEAD = 13;
+ public static final int SIZEOF_PROTECTHEAD = 26;
+
+ private static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
+
+ public static void main(String[] args) throws IOException {
+ File fileListFile = new File(args[0]);
+ BufferedReader fileListIn = new BufferedReader(new InputStreamReader(new FileInputStream(fileListFile), "UTF-8"));
+ String currentRar = fileListIn.readLine();
+ while(currentRar != null) {
+ if(true) {
+ System.out.println("Processing file " + currentRar + "...");
+ try {
+ RARFileEntry[] entries = ListFilesInArchive.listFiles(new File(currentRar));
+ for(RARFileEntry entry : entries) {
+ NewFileHeader nfh = entry.getHeader(0);
+ if(nfh instanceof CommentHeader)
+ continue;
+ //if(nfh.hasOutgoingData())
+ System.out.print((nfh.hasIncomingData()?"<--":" ") + nfh.getFilenameAsString() + (nfh.hasOutgoingData()?"-->":""));
+ if(nfh.getFileAttributesStructured().isDirectory())
+ System.out.print(" (Dir)");
+ else
+ System.out.print(" (File)");
+ System.out.println();
+ }
+ } catch(InvalidDataException ide) {
+ System.out.println("Invalid RAR-file. Message: " + ide.getMessage());
+ System.out.println("File: \"" + currentRar + "\"");
+ }
+ }
+ else {
+ RandomAccessFile raf = new RandomAccessFile(currentRar, "r");
+ try {
+ readMarkHeader(raf);
+ NewMainArchiveHeader nmah = readNewMainArchiveHeader(raf);
+ NewFileHeader nfh = readNewFileHeader(raf);
+ } catch(InvalidDataException e) {
+ System.out.println("Invalid RAR-file. Message: " + e.getMessage());
+ System.out.println("File: \"" + currentRar + "\"");
+ //System.exit(1);
+ }
+ }
+ System.out.print("Press enter to continue...");
+ stdin.readLine();
+ System.out.println();
+ currentRar = fileListIn.readLine();
+ }
+ }
+ public static void main2(String[] args) throws IOException {
+ RandomAccessFile raf = new RandomAccessFile(args[0], "r");
+ try {
+ readMarkHeader(raf);
+ } catch(InvalidDataException e) {
+ System.out.println("Invalid RAR-file. Message: " + e.getMessage());
+ System.exit(1);
+ }
+
+ NewMainArchiveHeader nmah = readNewMainArchiveHeader(raf);
+ nmah.print(System.out, "");
+ NewFileHeader nfh = readNewFileHeader(raf);
+ nfh.print(System.out, "");
+ }
+
+ public static Vector list(String path, boolean fullPath) throws IOException {
+ RandomAccessFile raf = new RandomAccessFile(path, "r");
+ raf.seek(7);
+ NewMainArchiveHeader nmah = readNewMainArchiveHeader(raf);
+ return null;
+ }
+
+ public static boolean isRAR(RandomAccessFile raf) throws IOException {
+ long oldFilePointer = raf.getFilePointer();
+ raf.seek(0);
+ byte[] rarHeader = new byte[7];
+ raf.read(rarHeader);
+ /* Old archive => error */
+ if(rarHeader[0]==0x52 && rarHeader[1]==0x45 &&
+ rarHeader[2]==0x7e && rarHeader[3]==0x5e) {
+ //debug_log("Attention: format as OLD detected! Can't handle archive!");
+ }
+ else {
+ /* original RAR v2.0 */
+ if((rarHeader[0]==0x52 && rarHeader[1]==0x61 && /* original */
+ rarHeader[2]==0x72 && rarHeader[3]==0x21 && /* RAR header*/
+ rarHeader[4]==0x1a && rarHeader[5]==0x07 &&
+ rarHeader[6]==0x00) ||
+ /* "UniquE!" - header */
+ (rarHeader[0]=='U' && rarHeader[1]=='n' && /* "UniquE!" */
+ rarHeader[2]=='i' && rarHeader[3]=='q' && /* header */
+ rarHeader[4]=='u' && rarHeader[5]=='E' &&
+ rarHeader[6]=='!')) {
+ if(readNewMainArchiveHeader(raf) != null)
+ return true;
+ } else {
+ // Skriv ut n�t felmedd...
+ }
+ }
+ return false;
+ }
+
+ public static MarkHeader readMarkHeader(RandomAccessFile raf) throws IOException {
+ byte[] header = new byte[7];
+ raf.read(header);
+ MarkHeader nfh = new MarkHeader(header, 0);
+ return nfh;
+ }
+ public static NewMainArchiveHeader readNewMainArchiveHeader(RandomAccessFile raf) throws IOException {
+ byte[] mainHeader = new byte[13];
+ raf.read(mainHeader);
+ NewMainArchiveHeader nmah = new NewMainArchiveHeader(mainHeader, 0);
+ return nmah;
+ }
+
+ public static NewFileHeader readNewFileHeader(RandomAccessFile raf) throws IOException {
+ byte[] header = new byte[32];
+ raf.read(header);
+ int headerSize = Util.readShortLE(header, 5);
+ byte[] fullHeader = new byte[headerSize];
+ System.arraycopy(header, 0, fullHeader, 0, header.length);
+ raf.read(fullHeader, header.length, fullHeader.length-header.length);
+ NewFileHeader nfh = new NewFileHeader(fullHeader, 0);
+ byte[] reconstruction = nfh.getHeaderData();
+ if(!Util.arraysEqual(fullHeader, reconstruction))
+ System.out.println("ERROR: Data after reconstruction has changed!");
+ return nfh;
+ }
+
+ public static RARHeader readHeader(RandomAccessFile raf) throws IOException {
+ byte[] header = new byte[7];
+ if(raf.read(header) == -1)
+ return null;
+ int headerSize = Util.readShortLE(header, 5);
+ byte headerType = Util.readByteLE(header, 2);
+ try {
+ byte[] fullHeader = new byte[headerSize];
+ System.arraycopy(header, 0, fullHeader, 0, header.length);
+ raf.read(fullHeader, header.length, fullHeader.length-header.length);
+ //System.out.println("Reading... headerSize=" + headerSize + " headerType=" + headerType);
+ if(headerType == RARHeader.FILE_HEAD) {
+ NewFileHeader nfh = new NewFileHeader(fullHeader, 0);
+ raf.seek(raf.getFilePointer()+nfh.getPackSize());
+ return nfh;
+ }
+ else if(headerType == RARHeader.MAIN_HEAD)
+ return new NewMainArchiveHeader(fullHeader, 0);
+ else if(headerType == RARHeader.COMMENT_HEAD) {
+ CommentHeader ch = new CommentHeader(fullHeader, 0);
+ raf.seek(raf.getFilePointer()+ch.getPackSize());
+ return ch;
+ }
+ else if(headerType == RARHeader.PROTECT_HEAD) {
+ ProtectedHeader ph = new ProtectedHeader(fullHeader, 0);
+ raf.seek(raf.getFilePointer()+ph.getDataSize());
+ return ph;
+ }
+ else if(headerType == RARHeader.EOF_HEAD)
+ return null;
+ else {
+ System.out.println("UNRECOGNIZED HEADER TYPE: 0x" + Util.toHexStringBE(headerType) + " for header at 0x" + Util.toHexStringBE(raf.getFilePointer()-headerSize));
+ UnknownHeader uh = new UnknownHeader(fullHeader, 0);
+ uh.print(System.out, "");
+ return uh;
+ }
+ } catch(ArrayIndexOutOfBoundsException aioobe) {
+ aioobe.printStackTrace();
+ System.err.println("headerSize=" + headerSize);
+ System.err.println("headerType=" + headerType);
+ System.err.println("filePointer: " + raf.getFilePointer());
+ return null;
+ }
+ }
+
+ public static long findEntry(String entryName, RandomAccessFile rarFile) throws IOException {
+ readMarkHeader(rarFile);
+ RARHeader currentHeader = readHeader(rarFile);
+ while(currentHeader != null) {
+ if(currentHeader instanceof NewFileHeader &&
+ !(currentHeader instanceof CommentHeader)) {
+ NewFileHeader nfh = (NewFileHeader)currentHeader;
+ if(nfh.getFilenameAsString().equals(entryName)) {
+ return rarFile.getFilePointer() - (nfh.getHeadSize()+nfh.getPackSize());
+ }
+ }
+ currentHeader = readHeader(rarFile);
+ }
+ return -1;
+ }
+
+ // Utility methods
+
+// public static int readIntLE(byte[] data) {
+// return readIntLE(data, 0);
+// }
+// public static int readIntLE(byte[] data, int offset) {
+// return ((data[offset+3] & 0xFF) << 24 |
+// (data[offset+2] & 0xFF) << 16 |
+// (data[offset+1] & 0xFF) << 8 |
+// (data[offset+0] & 0xFF) << 0);
+// }
+// public static short readShortLE(byte[] data) {
+// return readShortLE(data, 0);
+// }
+// public static short readShortLE(byte[] data, int offset) {
+// return (short) ((data[offset+1] & 0xFF) << 8 |
+// (data[offset+0] & 0xFF) << 0);
+// }
+// public static byte readByteLE(byte[] data) {
+// return readByteLE(data, 0);
+// }
+// public static byte readByteLE(byte[] data, int offset) {
+// return data[offset];
+// }
+}
diff --git a/src/org/catacombae/rarx/RARFile2.java b/src/org/catacombae/rarx/RARFile2.java
new file mode 100644
index 0000000..df7f706
--- /dev/null
+++ b/src/org/catacombae/rarx/RARFile2.java
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.IOException;
+
+public class RARFile2 {
+ public static void main(String[] args) throws IOException {
+ RARFile.main2(args);
+ }
+}
diff --git a/src/org/catacombae/rarx/RARFileEntry.java b/src/org/catacombae/rarx/RARFileEntry.java
new file mode 100644
index 0000000..ff8410d
--- /dev/null
+++ b/src/org/catacombae/rarx/RARFileEntry.java
@@ -0,0 +1,76 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.ArrayList;
+import java.io.File;
+
+public class RARFileEntry {
+ public static class RARFileEntryPart {
+ public File f;
+ public long offset;
+ public NewFileHeader header;
+
+ public RARFileEntryPart(File f, long offset, NewFileHeader header) {
+ this.f = f;
+ this.offset = offset;
+ this.header = header;
+ }
+ }
+
+ private ArrayList<RARFileEntryPart> parts = new ArrayList<RARFileEntryPart>();
+
+ public RARFileEntry(File f, long offset, NewFileHeader header) {
+ RARFileEntryPart firstPart = new RARFileEntryPart(f, offset, header);
+ parts.add(firstPart);
+ }
+
+ public void addPart(File f, long offset, NewFileHeader header) {
+ parts.add(new RARFileEntryPart(f, offset, header));
+ }
+
+ public int getPartCount() { return parts.size(); }
+ public long getPackedSize() {
+ long size = 0;
+ for(RARFileEntryPart current : parts)
+ size += current.header.getPackSize();
+ return size;
+ }
+
+ public boolean hasIncomingData() {
+ return parts.get(0).header.hasIncomingData();
+ }
+ public boolean hasOutgoingData() {
+ return parts.get(parts.size()-1).header.hasOutgoingData();
+ }
+
+ public boolean isComplete() {
+ return (!parts.get(0).header.hasIncomingData() && !parts.get(parts.size()-1).header.hasOutgoingData());
+ }
+
+ public int getFinalFileCRC() {
+ return parts.get(parts.size()-1).header.getFileCRC();
+ }
+
+ public File getFile(int partNumber) { return parts.get(partNumber).f; }
+ public long getOffset(int partNumber) { return parts.get(partNumber).offset; }
+ public NewFileHeader getHeader(int partNumber) { return parts.get(partNumber).header; }
+}
diff --git a/src/org/catacombae/rarx/RARFileEntryStream.java b/src/org/catacombae/rarx/RARFileEntryStream.java
new file mode 100644
index 0000000..c632900
--- /dev/null
+++ b/src/org/catacombae/rarx/RARFileEntryStream.java
@@ -0,0 +1,550 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import org.catacombae.io.*;
+import java.io.*;
+import java.util.LinkedList;
+import java.util.regex.Pattern;
+
+public class RARFileEntryStream implements RandomAccessStream {
+ public static class InvalidRARFilenameException extends RuntimeException {
+ private static final long serialVersionUID = 0x2F49329E49173487L;
+
+ public InvalidRARFilenameException(String message) {
+ super(message);
+ }
+ }
+
+ public static class RAREntrySegment {
+ public File file;
+ public RandomAccessFile raf;
+ public long headerPos;
+ public long headerLength;
+ public long dataPos;
+ public long dataLength;
+ public RAREntrySegment nextSegment = null;
+
+ public RAREntrySegment(File file, RandomAccessFile raf, long headerPos, long headerLength, long dataPos, long dataLength/*, RAREntrySegment nextSegment*/) {
+ this.file = file;
+ this.raf = raf;
+ this.headerPos = headerPos;
+ this.headerLength = headerLength;
+ this.dataPos = dataPos;
+ this.dataLength = dataLength;
+ //this.nextSegment = nextSegment;
+ }
+ }
+
+ public class Interval {
+ public RAREntrySegment segment;
+ public long startOffset;
+ public long endOffset;
+
+ public Interval(RAREntrySegment segment, long startOffset, long endOffset) {
+ this.segment = segment;
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ }
+ }
+
+ public class IntervalStructure {
+ private LinkedList<Interval> intervals = new LinkedList<Interval>();
+
+ public void addSegment(RAREntrySegment res, long startOffset, long endOffset) {
+ Interval newInterval = new Interval(res, startOffset, endOffset);
+ if(intervals.size() > 0) {
+ Interval previous = intervals.getLast();
+ previous.segment.nextSegment = newInterval.segment;
+ }
+ intervals.addLast(newInterval);
+ }
+
+ public RAREntrySegment getSegment(long offset) {
+ for(Interval i : intervals)
+ if(offset >= i.startOffset && offset < i.endOffset)
+ return i.segment;
+ return null;
+ }
+
+ public Interval getInterval(long offset) {
+ for(Interval i : intervals)
+ if(offset >= i.startOffset && offset < i.endOffset)
+ return i;
+ return null;
+ }
+
+ public RAREntrySegment[] getAllSegmentsUnordered() {
+ RAREntrySegment[] result = new RAREntrySegment[intervals.size()];
+ int j = 0;
+ for(Interval i : intervals)
+ result[j++] = i.segment;
+ return result;
+ }
+ }
+
+ public static boolean debug = false;
+ private IntervalStructure intervalStructure;
+ private RAREntrySegment currentSegment;
+ private boolean closed = false;
+ private long seekPointer;
+ private long globalPointer = 0;
+ private long segmentPointer = 0;
+ private final long length;
+
+ public RARFileEntryStream(RARFileEntry entry) throws FileNotFoundException, IOException {
+ this(entry.getFile(0), entry.getOffset(0));
+ }
+
+ public RARFileEntryStream(File startFile, long pos) throws FileNotFoundException, IOException {
+ //this.startFile = startFile;
+ //this.pos = pos;
+
+ LinkedList<RAREntrySegment> entrySegments = getSegments(startFile, pos, new LinkedList<RAREntrySegment>(), false);
+// for(RAREntrySegment res : entrySegments)
+// res.raf.seek(0);
+ if(entrySegments == null || entrySegments.size() < 1)
+ throw new FileNotFoundException();
+ this.currentSegment = entrySegments.getFirst();
+
+ long currentPos = 0;
+ this.intervalStructure = new IntervalStructure();
+ for(RAREntrySegment res : entrySegments) {
+ if(debug) System.out.println("Adding \"" + res.file + "\" at " + currentPos);
+ intervalStructure.addSegment(res, currentPos, currentPos+res.dataLength);
+ currentPos += res.dataLength;
+ }
+ length = currentPos;
+ if(debug) System.out.println("Length of entry: " + length + " bytes");
+
+ seekPointer = -2;
+ updateSeekPosition();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized void close() throws IOException {
+ if(debug) System.out.println("RARFileEntryStream.close()");
+ if(closed) throw new IOException("Stream closed.");
+
+ closed = true;
+ for(RAREntrySegment res : intervalStructure.getAllSegmentsUnordered())
+ res.raf.close();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized long getFilePointer() throws IOException {
+ return ((seekPointer == -1)?globalPointer:seekPointer);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized long length() throws IOException {
+ return length;
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized int read() throws IOException {
+ if(closed) throw new IOException("Stream closed.");
+ byte[] b = new byte[1];
+ read(b, 0, 1);
+ return b[0] & 0xFF;
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized int read(byte[] b) throws IOException {
+ if(closed) throw new IOException("Stream closed.");
+ return read(b, 0, b.length);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized int read(byte[] b, int off, int len) throws IOException {
+// boolean debug = true;
+ if(debug) System.out.print("RARFileEntryStream.read(b, " + off + ", " + len + ") = ");
+// System.out.println();
+// System.out.println("(1)currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer());
+// System.out.println("(1)=seekPointer=" + seekPointer);
+ if(closed) throw new IOException("Stream closed.");
+ try {
+ updateSeekPosition();
+ } catch(IOException ioe) {
+ if(debug) System.out.println("-1 (Couldn't update seek position)");
+ return -1;
+ }
+
+ long bytesRemainingInSegment = currentSegment.dataLength - segmentPointer;
+ while(bytesRemainingInSegment == 0) {
+ if(nextSegment())
+ bytesRemainingInSegment = currentSegment.dataLength - segmentPointer;
+ else {
+ if(debug) System.out.println("-1 (Nothing to read)");
+ return -1; // Nothing to read!
+ }
+ }
+ int bytesRead = 0;
+ mainLoop:
+ while(bytesRead < len) {
+ int remainingBytes = len-bytesRead;
+ int bytesToRead = (remainingBytes < bytesRemainingInSegment)?remainingBytes:(int)bytesRemainingInSegment;
+
+// System.out.println("(2)currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer());
+ int currentBytesRead = currentSegment.raf.read(b, off+bytesRead, bytesToRead);
+ if(currentBytesRead == -1)
+ throw new IOException("Reached end of file unexpectedly for file \"" + currentSegment.file + "\"! (segmentPointer=" + segmentPointer + " globalPointer=" + globalPointer + " bytesRemainingInSegment=" + bytesRemainingInSegment + " bytesRead=" + bytesRead + " bytesToRead=" + bytesToRead + " currentSegment.raf.getFilePointer()=" + currentSegment.raf.getFilePointer() + " currentSegment.dataLength=" + currentSegment.dataLength + ")");
+
+ bytesRead += currentBytesRead;
+ segmentPointer += currentBytesRead;
+ globalPointer += currentBytesRead;
+ //seekPointer += currentBytesRead;
+
+ bytesRemainingInSegment = currentSegment.dataLength - segmentPointer;
+ while(bytesRemainingInSegment == 0) {
+ if(nextSegment())
+ bytesRemainingInSegment = currentSegment.dataLength - segmentPointer;
+ else
+ break mainLoop; // No more bytes to read;
+ }
+ }
+ if(debug) System.out.println(bytesRead);
+ return bytesRead;
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized void seek(long pos) throws IOException {
+ if(debug) System.out.println("RARFileEntryStream.seek(" + pos + ")");
+ if(closed) throw new IOException("Stream closed.");
+ if(pos < 0) throw new IOException("Invalid position.");
+ seekPointer = pos;
+ }
+
+ private synchronized void updateSeekPosition() throws IOException {
+ if(debug) System.out.println("updateSeekPosition()");
+// System.out.println("seekPointer=" + seekPointer);
+// System.out.println("globalPointer=" + globalPointer);
+// System.out.println("segmentPointer=" + segmentPointer);
+ if(seekPointer == globalPointer)
+ seekPointer = -1;
+ else if(seekPointer != -1) {
+ //if(seekPointer > length) seek(length-1);
+ if(seekPointer == -2)
+ seekPointer = 0;
+
+ long posDiff = seekPointer-globalPointer;
+ long newSegmentPointer = segmentPointer + posDiff;
+ if(newSegmentPointer >= 0 &&
+ newSegmentPointer < currentSegment.dataLength) {
+ if(debug) System.out.println(" Pointer is within segment.");
+ globalPointer += posDiff;
+ segmentPointer += posDiff;
+ if(debug) System.out.println(" Seeking to " + (currentSegment.dataPos + segmentPointer) +".");
+ currentSegment.raf.seek(currentSegment.dataPos + segmentPointer);
+ if(debug) System.out.println(" Done.");
+ }
+ else {
+ if(debug) System.out.println(" Pointer is OUTSIDE segment. We need to get a new one.");
+ Interval newInterval = intervalStructure.getInterval(seekPointer);
+ if(newInterval == null)
+ throw new IOException("No more data to read.");
+ RAREntrySegment newSegment = newInterval.segment;
+ newSegmentPointer = (seekPointer-newInterval.startOffset);
+ if(debug) System.out.println(" Seeking to " + (currentSegment.dataPos + segmentPointer) +".");
+ newSegment.raf.seek(newSegment.dataPos + newSegmentPointer);
+ if(debug) System.out.println(" Done.");
+
+ // If we got here, all is well.
+ globalPointer = seekPointer;
+ segmentPointer = newSegmentPointer;
+ currentSegment = newSegment;
+ }
+ seekPointer = -1;
+ }
+ }
+
+ public synchronized boolean isClosed() {
+ return closed;
+ }
+
+ public synchronized boolean reopen() {
+ if(debug) System.out.println("RARFileEntryStream.reopen()");
+ if(closed) {
+ try {
+ for(RAREntrySegment res : intervalStructure.getAllSegmentsUnordered())
+ res.raf = new RandomAccessFile(res.file, "r");
+ closed = false;
+ seekPointer = -2;
+ updateSeekPosition();
+ //seek(0);
+ return true;
+ } catch(Exception e) {}
+ }
+ return false;
+ }
+
+ /**
+ * Changes forward to the next segment and sets the pointers.
+ */
+ private boolean nextSegment() throws IOException {
+ if(debug) System.out.println("RARFileEntryStream.nextSegment()");
+ RAREntrySegment nextSegment = currentSegment.nextSegment;
+ if(nextSegment != null) {
+ if(debug) System.out.println(" nextSegment != null");
+ if(debug) System.out.println(" currentSegment: " + currentSegment);
+ if(debug) System.out.println(" file: " + currentSegment.file);
+ if(debug) System.out.println(" headerPos: " + currentSegment.headerPos);
+ if(debug) System.out.println(" headerLength: " + currentSegment.headerLength);
+ if(debug) System.out.println(" dataPos: " + currentSegment.dataPos);
+ if(debug) System.out.println(" dataLength: " + currentSegment.dataLength);
+ if(debug) System.out.println(" nextSegment: " + nextSegment);
+ if(debug) System.out.println(" file: " + nextSegment.file);
+ if(debug) System.out.println(" headerPos: " + nextSegment.headerPos);
+ if(debug) System.out.println(" headerLength: " + nextSegment.headerLength);
+ if(debug) System.out.println(" dataPos: " + nextSegment.dataPos);
+ if(debug) System.out.println(" dataLength: " + nextSegment.dataLength);
+ globalPointer += currentSegment.dataLength - segmentPointer;
+ segmentPointer = 0;
+ nextSegment.raf.seek(nextSegment.dataPos);
+ currentSegment = nextSegment;
+ return true;
+ }
+ else {
+ if(debug) System.out.println(" nextSegment == null");
+ return false;
+ }
+ }
+
+ private static LinkedList<RAREntrySegment> getSegments(File file, long position, LinkedList<RAREntrySegment> list, boolean needsIncoming) throws IOException, FileNotFoundException {
+ if(debug) System.out.println("Getting new entry segment: " + file);
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+ raf.seek(position);
+ RARHeader rh = RARFile.readHeader(raf);
+ if(rh instanceof NewFileHeader) {
+ NewFileHeader nfh = (NewFileHeader)rh;
+ if(//nfh.getMethod() == RARHeader.METHOD_UNCOMPRESSED &&
+ (needsIncoming?nfh.hasIncomingData():!nfh.hasIncomingData()) &&
+ !(nfh instanceof CommentHeader)) {
+ RAREntrySegment res = new RAREntrySegment(file, raf, position, nfh.getSize(), position+nfh.getSize(), nfh.getPackSize());
+ list.addLast(res);
+ if(nfh.hasOutgoingData()) {
+ File nextFile = getNextFile(file);
+ if(nextFile == null)
+ throw new FileNotFoundException();
+ long posInNextFile = getMatchingPosInFile(nextFile, nfh);
+ if(posInNextFile == -1)
+ throw new FileNotFoundException();
+ getSegments(nextFile, posInNextFile, list, true);
+ }
+ }
+ }
+ return list;
+ }
+
+ protected static File getNextFile(File rarFile) {
+ String filename = rarFile.getName();
+ String newFilename;
+ if(Pattern.matches(".+\\.[P|p]art\\d+\\.rar", filename)) {
+ String numberString = "";
+ long partNumber = 0; // I'm not serious... (:
+ int charstep = 0;
+ while(true) {
+ String currentNumber = filename.substring(filename.length()-5-charstep, filename.length()-4-charstep);
+ try {
+ partNumber += Integer.parseInt(currentNumber)*Util.pow(10, charstep);
+ } catch(NumberFormatException nfe) {
+ break;
+ }
+ ++charstep;
+ }
+ int numberLength = charstep;
+ String newPartNumberString = "" + (partNumber + 1);
+
+ // Add leading zeroes
+ while(newPartNumberString.length() < numberLength)
+ newPartNumberString = "0" + newPartNumberString;
+
+ newFilename = filename.substring(0, filename.length()-4-numberLength) + newPartNumberString + ".rar";
+ }
+ else if(Pattern.matches(".+\\.r((ar)|(\\d\\d)){1}", filename)) {
+ String lastTwo = filename.substring(filename.length()-2, filename.length());
+ String newLastTwo = "";
+ if(lastTwo.equals("ar"))
+ newLastTwo = "00";
+ else {
+ try {
+ int archiveNumber = Integer.parseInt(lastTwo);
+ newLastTwo = "" + (archiveNumber+1);
+ if(newLastTwo.length() < 2)
+ newLastTwo = "0" + newLastTwo;
+ } catch(NumberFormatException nfe) {
+ throw new RuntimeException(nfe);
+ }
+ }
+ if(newLastTwo.length() != 2)
+ throw new InvalidRARFilenameException("Incremented filename invalid.");
+
+ newFilename = filename.substring(0, filename.length()-2) + newLastTwo;
+ }
+ else if(Pattern.matches(".+\\.\\d\\d\\d", filename)) {
+ String lastThree = filename.substring(filename.length()-3, filename.length());
+ String newLastThree = "";
+ try {
+ int archiveNumber = Integer.parseInt(lastThree);
+ newLastThree = "" + (archiveNumber+1);
+ while(newLastThree.length() < 3)
+ newLastThree = "0" + newLastThree;
+ } catch(NumberFormatException nfe) {
+ throw new RuntimeException(nfe);
+ }
+ if(newLastThree.length() != 3)
+ throw new InvalidRARFilenameException("Incremented filename invalid.");
+
+ newFilename = filename.substring(0, filename.length()-3) + newLastThree;
+ }
+ else
+ throw new InvalidRARFilenameException("Filename in argument invalid for a RAR-file.");
+
+ File newFile = new File(rarFile.getParent(), newFilename);
+ if(newFile.exists())
+ return newFile;
+ else {
+ if(debug) System.out.println("Couldn't find \"" + newFile + "\"");
+ return null;
+ }
+ }
+
+ private static long getMatchingPosInFile(File f, NewFileHeader currentHeader) {
+ try {
+ RandomAccessFile raf = new RandomAccessFile(f, "r");
+ RARFile.readMarkHeader(raf);
+ RARHeader rh = RARFile.readHeader(raf);
+ while(rh != null) {
+ if(rh instanceof NewFileHeader) {
+ NewFileHeader nextHeader = (NewFileHeader)rh;
+ if(nextHeader.getFilenameAsString().equals(currentHeader.getFilenameAsString()) &&
+ nextHeader.getUnpSize() == currentHeader.getUnpSize()) {
+ long pos = raf.getFilePointer()-nextHeader.getPackSize()-nextHeader.getHeadSize();
+ raf.close();
+ return pos;
+ }
+ }
+ rh = RARFile.readHeader(raf);
+ }
+ raf.close();
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ return -1;
+ }
+
+ /** Testcode */
+ public static void main(String[] args) throws IOException {
+ if(args.length != 3)
+ System.out.println("RARFileEntryStream self test needs exactly 3 arguments...");
+ String rarFilename = args[0];
+ String entryName = args[1];
+ String referenceFilename = args[2];
+
+ System.out.println("Initiating self test of RARFileEntryStream...");
+ System.out.println(" RAR file: " + rarFilename);
+ System.out.println(" Entry name: " + entryName);
+ System.out.println(" Reference file: " + referenceFilename);
+
+ File rarFile = new File(rarFilename);
+ File referenceFile = new File(referenceFilename);
+
+ long position = RARFile.findEntry(entryName, new RandomAccessFile(rarFile, "r"));
+ RARFileEntryStream stream = new RARFileEntryStream(rarFile, position);
+ RandomAccessFile ref = new RandomAccessFile(referenceFile, "r");
+ byte[] buffer1 = new byte[4096];
+ byte[] buffer2 = new byte[4096];
+ int bytesRead1;
+ int bytesRead2;
+ long totalBytesRead1;
+ long totalBytesRead2;
+
+ System.out.println("1. Deterministic tests...");
+ if(stream.length() != ref.length())
+ System.out.println("Lengths not equal! stream: " + stream.length() + " reference file: " + ref.length());
+
+ // Testing reading from the beginning
+ ref.seek(0);
+ stream.seek(0);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ bytesRead1 = ref.read(buffer1);
+ bytesRead2 = stream.read(buffer2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")");
+ if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!");
+
+ // Going to the middle of the file
+ ref.seek(ref.length()/2);
+ stream.seek(stream.length()/2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ bytesRead1 = ref.read(buffer1);
+ bytesRead2 = stream.read(buffer2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")");
+ if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!");
+
+ // Going to the end of the file
+ ref.seek(ref.length());
+ stream.seek(stream.length());
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ bytesRead1 = ref.read(buffer1);
+ bytesRead2 = stream.read(buffer2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")");
+ if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!");
+
+ // Going beyond the end of the file
+ ref.seek(ref.length()*2);
+ stream.seek(stream.length()*2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ bytesRead1 = ref.read(buffer1);
+ bytesRead2 = stream.read(buffer2);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ if(bytesRead1 != bytesRead2) throw new RuntimeException("Did not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")");
+ if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!");
+
+ // Comparing files byte for byte
+ System.out.println("Comparing file data...");
+ ref.seek(0);
+ stream.seek(0);
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ totalBytesRead1 = 0;
+ totalBytesRead2 = 0;
+ while(totalBytesRead1 < ref.length() && totalBytesRead2 < stream.length()) {
+ System.out.print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
+ System.out.print(" " + totalBytesRead1 + " bytes processed");
+ long bytesRemaining1 = ref.length()-totalBytesRead1;
+ long bytesRemaining2 = ref.length()-totalBytesRead2;
+ bytesRead1 = ref.read(buffer1, 0, (bytesRemaining1 < buffer1.length?(int)bytesRemaining1:buffer1.length));
+ bytesRead2 = stream.read(buffer2, 0, (bytesRemaining2 < buffer2.length?(int)bytesRemaining2:buffer2.length));
+ if(bytesRead1 != bytesRead2) throw new RuntimeException("Have not read equal amount of bytes. (bytesRead1="+bytesRead1+" bytesRead2="+bytesRead2+")");
+ if(bytesRead1 == -1)
+ break;
+ totalBytesRead1 += bytesRead1;
+ totalBytesRead2 += bytesRead2;
+ if(ref.getFilePointer() != stream.getFilePointer()) throw new RuntimeException("File pointers not equal! ref: " + ref.getFilePointer() + " stream: " + stream.getFilePointer());
+ if(totalBytesRead1 != totalBytesRead2) throw new RuntimeException("Have not read equal amount of bytes. (totalBytesRead1="+totalBytesRead1+" totalBytesRead2="+totalBytesRead2+")");
+ if(!Util.arraysEqual(buffer1, buffer2)) throw new RuntimeException("Arrays not equal!");
+ }
+ System.out.print("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
+ System.out.print(" " + totalBytesRead1 + " bytes processed");
+ }
+}
diff --git a/src/org/catacombae/rarx/RARHeader.java b/src/org/catacombae/rarx/RARHeader.java
new file mode 100644
index 0000000..c631f5f
--- /dev/null
+++ b/src/org/catacombae/rarx/RARHeader.java
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+import java.util.zip.CRC32;
+
+public abstract class RARHeader {
+ /*
+ * Common fields for all RAR headers
+ * size: 7 bytes
+ *
+ * BP Size Description
+ * 0 2 HeadCRC
+ * 2 1 HeadType
+ * 3 2 Flags
+ * 5 2 HeadSize
+ */
+ // Possible values for the fields headType in various classes
+ //public static final int ALL_HEAD = 0;
+ public static final int MARK_HEAD = 0x72; // 0x69 for UniquE! ones..? However, this field isn't really needed.
+ public static final int MAIN_HEAD = 0x73;
+ public static final int FILE_HEAD = 0x74;
+ public static final int COMM_HEAD = 0x75;
+ public static final int AV_HEAD = 0x76;
+ public static final int SUB_HEAD = 0x77;
+ public static final int PROTECT_HEAD = 0x78;
+ public static final int COMMENT_HEAD = 0x7a;
+ public static final int EOF_HEAD = 0x7b;
+
+ /* The versioning seems very simple. version 2.0 is represented
+ by the value 20 (0x14), 2.6 by the value 26 (0x1a) etc. */
+ public static final int UNPACK_VERSION_2_0 = 0x14;
+ public static final int UNPACK_VERSION_2_6 = 0x1a; // efterlyst-bfc.rar
+ public static final int UNPACK_VERSION_2_9 = 0x1d;
+
+ public static final int METHOD_UNCOMPRESSED = 0x30; // Observerad i 2.0, 2.9
+ public static final int METHOD_COMPRESSION_1 = 0x35; // Observerad i 2.0, 2.9
+ public static final int METHOD_COMPRESSION_2 = 0x33; // Observerad i 2.0 (flera g�nger, men ovanligt), 2.9
+
+ /* Hypotes: de tv� minst signifikanta bitarna i Flags indikerar om en fil
+ forts�tter p� nytt medium. Om bit 0 �r satt �r den aktuella filen en
+ forts�ttning p� en tidigare fil. Om bit 1 �r satt forts�tter den
+ aktuella filen p� ytterligare nya media.
+ Hypotes bekr�ftad. */
+
+
+ private final byte[] headCRC = new byte[2];
+ private final byte[] headType = new byte[1];
+ private final byte[] flags = new byte[2];
+ private final byte[] headSize = new byte[2];
+ private final CRC32 crc = new CRC32();
+
+ public RARHeader(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, headCRC, 0, 2);
+ System.arraycopy(data, offset+2, headType, 0, 1);
+ System.arraycopy(data, offset+3, flags, 0, 2);
+ System.arraycopy(data, offset+5, headSize, 0, 2);
+ }
+
+ public int getSize() { return _getSize(); }
+ private int _getSize() { return 7; }
+
+ protected void validateData() {
+ if(calculateHeadCRC() != getHeadCRC())
+ throw new InvalidDataException("Incorrect header CRC!");
+ }
+
+ public short getHeadCRC() { return Util.readShortLE(headCRC); }
+ public byte getHeadType() { return Util.readByteLE(headType); }
+ public short getFlags() { return Util.readShortLE(flags); }
+ public short getHeadSize() { return Util.readShortLE(headSize); }
+
+ public boolean getFlag(int bitNumber) {
+ return Util.getBit(getFlags(), bitNumber);//((getFlags() >> byteNumber) & 0x1) == 0x1;
+ }
+
+
+ public byte[] getHeaderData() {
+ byte[] data = new byte[_getSize()];
+ System.arraycopy(headCRC, 0, data, 0, headCRC.length);
+ System.arraycopy(headType, 0, data, 2, headType.length);
+ System.arraycopy(flags, 0, data, 3, flags.length);
+ System.arraycopy(headSize, 0, data, 5, headSize.length);
+ return data;
+ }
+
+ public short calculateHeadCRC() {
+ crc.reset();
+ crc.update(getHeaderData(), 2, getSize()-2);
+ return (short)(crc.getValue());
+ }
+ protected void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " HeadCRC: " + Util.toHexStringBE(getHeadCRC()));
+ if(calculateHeadCRC() != getHeadCRC())
+ ps.println(prefix + " CHECKSUMS NOT MATCHING! (0x" +
+ Util.toHexStringBE(getHeadCRC()) + " != 0x" +
+ Util.toHexStringBE(calculateHeadCRC()) + "))");
+ ps.println(prefix + " HeadType: " + Util.toHexStringBE(getHeadType()));
+ ps.println(prefix + " Flags: " + Util.toHexStringBE(getFlags()));
+ ps.println(prefix + " HeadSize: " + getHeadSize() + " bytes");
+ }
+}
diff --git a/src/org/catacombae/rarx/README.txt b/src/org/catacombae/rarx/README.txt
new file mode 100644
index 0000000..5e70dfd
--- /dev/null
+++ b/src/org/catacombae/rarx/README.txt
@@ -0,0 +1,10 @@
+Acknowledgements
+----------------
+
+This code has been written with the UniquE RAR File Library v0.4.0 as a reference.
+Thus the author of this library should have a lot of credit for enabling me to write this code.
+I have not attributed him in the source files, as they have been written by myself in Java after
+studying the details of the UniquE implementation (in C), but here's a more general attribution;
+This would not have been possible without the GPL:d work of Christian Scheurer!
+
+ Erik Larsson, 2007
diff --git a/src/org/catacombae/rarx/TestRAFProperties.java b/src/org/catacombae/rarx/TestRAFProperties.java
new file mode 100644
index 0000000..ffbb955
--- /dev/null
+++ b/src/org/catacombae/rarx/TestRAFProperties.java
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+public class TestRAFProperties {
+ public static void main(String[] args) {
+ //723152686 32768
+ int number = 30197;
+ System.out.println(number + " (0x" + Util.toHexStringBE(number) + ")");
+ System.out.println((0-number) + " (0x" + Util.toHexStringBE((0-number)) + ")");
+ System.out.println(((0-number) & 0xFFFFF) + " (0x" + Util.toHexStringBE(((0-number) & 0xFFFFF)) + ")");
+ System.out.println((0xFFFFF-number) + " (0x" + Util.toHexStringBE((0xFFFFF-number)) + ")");
+ }
+}
diff --git a/src/org/catacombae/rarx/Tree.java b/src/org/catacombae/rarx/Tree.java
new file mode 100644
index 0000000..1ec9eb0
--- /dev/null
+++ b/src/org/catacombae/rarx/Tree.java
@@ -0,0 +1,55 @@
+package org.catacombae.rarx;
+import java.util.LinkedList;
+
+public class Tree<A> extends TreeNode<A> {
+ public Tree(A value) { super(value); }
+ public Tree() { super(); }
+
+ private LinkedList<Pair<String, TreeNode<A>>> children = new LinkedList<Pair<String, TreeNode<A>>>();
+
+ private static class Pair<A, B> {
+ public A a;
+ public B b;
+ public Pair(A a, B b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
+ public void put(String entryName, TreeNode<A> tn) {
+ children.addLast(new Pair<String, TreeNode<A>>(entryName, tn));
+ }
+ public TreeNode<A> get(String entryName) {
+ for(Pair<String, TreeNode<A>> p : children) {
+ if(p.a.equals(entryName))
+ return p.b;
+ }
+ return null;
+ }
+
+ public int childCount() {
+ return children.size();
+ }
+
+ public String[] listChildrenNames() {
+ String[] targetArray = new String[children.size()];
+ int i = 0;
+ for(Pair<String, TreeNode<A>> p : children)
+ targetArray[i++] = p.a;
+
+ return targetArray;
+
+ }
+
+ public A[] listChildren(A[] targetArray) {
+ if(targetArray.length != children.size())
+ throw new IllegalArgumentException("target array not matching number of children");
+ else {
+ int i = 0;
+ for(Pair<String, TreeNode<A>> p : children) {
+ targetArray[i++] = p.b.getValue();
+ }
+ return targetArray;
+ }
+ }
+}
diff --git a/src/org/catacombae/rarx/TreeNode.java b/src/org/catacombae/rarx/TreeNode.java
new file mode 100644
index 0000000..1da80c9
--- /dev/null
+++ b/src/org/catacombae/rarx/TreeNode.java
@@ -0,0 +1,17 @@
+package org.catacombae.rarx;
+
+public abstract class TreeNode<A> {
+ private A value;
+ public TreeNode(A value) {
+ this.value = value;
+ }
+ public TreeNode() {
+ this.value = null;
+ }
+ public A getValue() {
+ return value;
+ }
+ public void setValue(A a) {
+ value = a;
+ }
+}
diff --git a/src/org/catacombae/rarx/UNIXFileAttributes.java b/src/org/catacombae/rarx/UNIXFileAttributes.java
new file mode 100644
index 0000000..175e8d2
--- /dev/null
+++ b/src/org/catacombae/rarx/UNIXFileAttributes.java
@@ -0,0 +1,66 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class UNIXFileAttributes implements FileAttributes {
+ private int attributes;
+
+ public UNIXFileAttributes(int attributes) {
+ this.attributes = attributes;
+ }
+
+ public boolean isDirectory() { return isDirectoryFlagSet(); }
+
+ public boolean isFileFlagSet() { return Util.getBit(attributes, 15); }
+ public boolean isDirectoryFlagSet() { return Util.getBit(attributes, 14); }
+ public boolean isSpecialFileFlagSet() { return Util.getBit(attributes, 13); }
+ public boolean isOwnerReadFlagSet() { return Util.getBit(attributes, 8); }
+ public boolean isOwnerWriteFlagSet() { return Util.getBit(attributes, 7); }
+ public boolean isOwnerExecuteFlagSet() { return Util.getBit(attributes, 6); }
+ public boolean isGroupReadFlagSet() { return Util.getBit(attributes, 5); }
+ public boolean isGroupWriteFlagSet() { return Util.getBit(attributes, 4); }
+ public boolean isGroupExecuteFlagSet() { return Util.getBit(attributes, 3); }
+ public boolean isOthersReadFlagSet() { return Util.getBit(attributes, 2); }
+ public boolean isOthersWriteFlagSet() { return Util.getBit(attributes, 1); }
+ public boolean isOthersExecuteFlagSet() { return Util.getBit(attributes, 0); }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "UNIXFileAttributes:");
+ printFields(ps, prefix);
+ }
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " isFileFlagSet: " + isFileFlagSet());
+ ps.println(prefix + " isDirectoryFlagSet: " + isDirectoryFlagSet());
+ ps.println(prefix + " isSpecialFileFlagSet: " + isSpecialFileFlagSet());
+ ps.println(prefix + " isOwnerReadFlagSet: " + isOwnerReadFlagSet());
+ ps.println(prefix + " isOwnerWriteFlagSet: " + isOwnerWriteFlagSet());
+ ps.println(prefix + " isOwnerExecuteFlagSet: " + isOwnerExecuteFlagSet());
+ ps.println(prefix + " isGroupReadFlagSet: " + isGroupReadFlagSet());
+ ps.println(prefix + " isGroupWriteFlagSet: " + isGroupWriteFlagSet());
+ ps.println(prefix + " isGroupExecuteFlagSet: " + isGroupExecuteFlagSet());
+ ps.println(prefix + " isOthersReadFlagSet: " + isOthersReadFlagSet());
+ ps.println(prefix + " isOthersWriteFlagSet: " + isOthersWriteFlagSet());
+ ps.println(prefix + " isOthersExecuteFlagSet: " + isOthersExecuteFlagSet());
+// ps.println(prefix + " isFlagSet: " + isFlagSet());
+ }
+}
diff --git a/src/org/catacombae/rarx/UnknownHeader.java b/src/org/catacombae/rarx/UnknownHeader.java
new file mode 100644
index 0000000..857ba4d
--- /dev/null
+++ b/src/org/catacombae/rarx/UnknownHeader.java
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class UnknownHeader extends RARHeader {
+ private final byte[] data;
+
+ public UnknownHeader(byte[] inData, int offset) {
+ super(inData, offset);
+ data = new byte[getHeadSize()-super.getSize()];
+ System.arraycopy(inData, super.getSize(), data, 0, data.length);
+ super.validateData();
+ }
+
+ public int getSize() {
+ return _getSize();
+ }
+ private int _getSize() {
+ return super.getSize()+data.length;
+ }
+
+ public byte[] getHeaderData() {
+ byte[] outData = new byte[_getSize()];
+ byte[] superData = super.getHeaderData();
+ System.arraycopy(superData, 0, outData, 0, superData.length);
+ System.arraycopy(data, 0, outData, superData.length, data.length);
+ return outData;
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ System.out.println("Unknown header:");
+ printFields(ps, prefix);
+ }
+}
diff --git a/src/org/catacombae/rarx/Util.java b/src/org/catacombae/rarx/Util.java
new file mode 100644
index 0000000..80cb47b
--- /dev/null
+++ b/src/org/catacombae/rarx/Util.java
@@ -0,0 +1,305 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+public class Util {
+ public static int sectorSize = 0x800;
+
+ public static String byteArrayToHexString(byte[] array) {
+ return byteArrayToHexString(array, 0, array.length);
+ }
+ public static String byteArrayToHexString(byte[] array, int offset, int length) {
+ String result = "";
+ for(int i = offset; i < (offset+length); ++i) {
+ byte b = array[i];
+ String currentHexString = Integer.toHexString(b & 0xFF);
+ if(currentHexString.length() == 1)
+ currentHexString = "0" + currentHexString;
+ result += currentHexString;
+ }
+ return result;
+ }
+ public static String toHexStringBE(int[] array) {
+ return toHexStringBE(array, 0, array.length);
+ }
+ public static String toHexStringBE(int[] array, int offset, int length) {
+ StringBuilder result = new StringBuilder();
+ for(int i : array)
+ result.append(toHexStringBE(i));
+ return result.toString();
+ }
+
+ public static String toHexStringLE(byte n) { return byteArrayToHexString(toByteArrayLE(n)); }
+ public static String toHexStringLE(short n) { return byteArrayToHexString(toByteArrayLE(n)); }
+ public static String toHexStringLE(int n) { return byteArrayToHexString(toByteArrayLE(n)); }
+ public static String toHexStringLE(long n) { return byteArrayToHexString(toByteArrayLE(n)); }
+ public static String toHexStringBE(byte n) { return byteArrayToHexString(toByteArrayBE(n)); }
+ public static String toHexStringBE(short n) { return byteArrayToHexString(toByteArrayBE(n)); }
+ public static String toHexStringBE(int n) { return byteArrayToHexString(toByteArrayBE(n)); }
+ public static String toHexStringBE(long n) { return byteArrayToHexString(toByteArrayBE(n)); }
+
+ public static byte[] invert(byte[] array) {
+ byte[] newArray = new byte[array.length];
+ for(int i = 0; i < array.length; ++i)
+ newArray[newArray.length-i-1] = array[i];
+ return newArray;
+ }
+
+ public static int readIntLE(byte[] data) {
+ return readIntLE(data, 0);
+ }
+ public static int readIntLE(byte[] data, int offset) {
+ return ((data[offset+3] & 0xFF) << 24 |
+ (data[offset+2] & 0xFF) << 16 |
+ (data[offset+1] & 0xFF) << 8 |
+ (data[offset+0] & 0xFF) << 0);
+ }
+ public static short readShortLE(byte[] data) {
+ return readShortLE(data, 0);
+ }
+ public static short readShortLE(byte[] data, int offset) {
+ return (short) ((data[offset+1] & 0xFF) << 8 |
+ (data[offset+0] & 0xFF) << 0);
+ }
+ public static byte readByteLE(byte[] data) {
+ return readByteLE(data, 0);
+ }
+ public static byte readByteLE(byte[] data, int offset) {
+ return data[offset];
+ }
+
+ public static byte[] toByteArrayLE(byte b) {
+ byte[] result = new byte[1];
+ result[0] = b;
+ return result;
+ }
+ public static byte[] toByteArrayLE(short s) {
+ byte[] result = new byte[2];
+ result[0] = (byte) ((s >> 0) & 0xFF);
+ result[1] = (byte) ((s >> 8) & 0xFF);
+ return result;
+ }
+ public static byte[] toByteArrayLE(int i) {
+ byte[] result = new byte[4];
+ result[0] = (byte) ((i >> 0) & 0xFF);
+ result[1] = (byte) ((i >> 8) & 0xFF);
+ result[2] = (byte) ((i >> 16) & 0xFF);
+ result[3] = (byte) ((i >> 24) & 0xFF);
+ return result;
+ }
+ public static byte[] toByteArrayLE(long l) {
+ byte[] result = new byte[8];
+ result[0] = (byte) ((l >> 0) & 0xFF);
+ result[1] = (byte) ((l >> 8) & 0xFF);
+ result[2] = (byte) ((l >> 16) & 0xFF);
+ result[3] = (byte) ((l >> 24) & 0xFF);
+ result[4] = (byte) ((l >> 32) & 0xFF);
+ result[5] = (byte) ((l >> 40) & 0xFF);
+ result[6] = (byte) ((l >> 48) & 0xFF);
+ result[7] = (byte) ((l >> 56) & 0xFF);
+ return result;
+ }
+ public static byte[] toByteArrayBE(byte b) {
+ byte[] result = new byte[1];
+ result[0] = b;
+ return result;
+ }
+ public static byte[] toByteArrayBE(short s) {
+ byte[] result = new byte[2];
+ result[0] = (byte) ((s >> 8) & 0xFF);
+ result[1] = (byte) ((s >> 0) & 0xFF);
+ return result;
+ }
+ public static byte[] toByteArrayBE(int i) {
+ byte[] result = new byte[4];
+ result[0] = (byte) ((i >> 24) & 0xFF);
+ result[1] = (byte) ((i >> 16) & 0xFF);
+ result[2] = (byte) ((i >> 8) & 0xFF);
+ result[3] = (byte) ((i >> 0) & 0xFF);
+ return result;
+ }
+ public static byte[] toByteArrayBE(long l) {
+ byte[] result = new byte[8];
+ result[0] = (byte) ((l >> 56) & 0xFF);
+ result[1] = (byte) ((l >> 48) & 0xFF);
+ result[2] = (byte) ((l >> 40) & 0xFF);
+ result[3] = (byte) ((l >> 32) & 0xFF);
+ result[4] = (byte) ((l >> 24) & 0xFF);
+ result[5] = (byte) ((l >> 16) & 0xFF);
+ result[6] = (byte) ((l >> 8) & 0xFF);
+ result[7] = (byte) ((l >> 0) & 0xFF);
+ return result;
+ }
+
+ public static boolean zeroed(byte[] ba) {
+ for(byte b : ba)
+ if(b != 0)
+ return false;
+ return true;
+ }
+
+ public static void zero(byte[] ba) {
+ set(ba, 0, ba.length, (byte)0);
+ }
+ public static void zero(byte[] ba, int offset, int length) {
+ set(ba, offset, length, (byte)0);
+ }
+ public static void zero(short[] ba) {
+ set(ba, 0, ba.length, (short)0);
+ }
+ public static void zero(short[] ba, int offset, int length) {
+ set(ba, offset, length, (short)0);
+ }
+ public static void zero(int[] ba) {
+ set(ba, 0, ba.length, (int)0);
+ }
+ public static void zero(int[] ba, int offset, int length) {
+ set(ba, offset, length, (int)0);
+ }
+ public static void zero(long[] ba) {
+ set(ba, 0, ba.length, (long)0);
+ }
+ public static void zero(long[] ba, int offset, int length) {
+ set(ba, offset, length, (long)0);
+ }
+
+ public static void set(byte[] ba, int offset, int length, byte value) {
+ for(int i = offset; i < length; ++i)
+ ba[i] = value;
+ }
+ public static void set(short[] ba, int offset, int length, short value) {
+ for(int i = offset; i < length; ++i)
+ ba[i] = value;
+ }
+ public static void set(int[] ba, int offset, int length, int value) {
+ for(int i = offset; i < length; ++i)
+ ba[i] = value;
+ }
+ public static void set(long[] ba, int offset, int length, long value) {
+ for(int i = offset; i < length; ++i)
+ ba[i] = value;
+ }
+
+ public static byte[] createCopy(byte[] data) {
+ return createCopy(data, 0, data.length);
+ }
+
+ public static byte[] createCopy(byte[] data, int offset, int length) {
+ byte[] copy = new byte[length];
+ System.arraycopy(data, offset, copy, 0, length);
+ return copy;
+ }
+
+ public static boolean arraysEqual(boolean[] a, boolean[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(byte[] a, byte[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(char[] a, char[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(short[] a, short[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(int[] a, int[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(long[] a, long[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(a[i] != b[i])
+ return false;
+ return true;
+ }
+ }
+ public static boolean arraysEqual(Object[] a, Object[] b) {
+ if(a.length != b.length)
+ return false;
+ else {
+ for(int i = 0; i < a.length; ++i)
+ if(!a[i].equals(b[i]))
+ return false;
+ return true;
+ }
+ }
+
+ public static long pow(long a, long b) {
+ if(b < 0) throw new IllegalArgumentException("b can not be negative");
+
+ long result = 1;
+ for(long i = 0; i < b; ++i)
+ result *= a;
+ return result;
+ }
+
+ public static int strlen(byte[] data) {
+ int length = 0;
+ for(byte b : data) {
+ if(b == 0)
+ break;
+ else
+ ++length;
+ }
+ return length;
+ }
+
+ public static boolean getBit(long data, int bitNumber) {
+ return ((data >>> bitNumber) & 0x1) == 0x1;
+ }
+}
diff --git a/src/org/catacombae/rarx/Win32FileAttributes.java b/src/org/catacombae/rarx/Win32FileAttributes.java
new file mode 100644
index 0000000..957f40a
--- /dev/null
+++ b/src/org/catacombae/rarx/Win32FileAttributes.java
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.io.PrintStream;
+
+public class Win32FileAttributes implements FileAttributes {
+ private int attributes;
+
+ public Win32FileAttributes(int attributes) {
+ this.attributes = attributes;
+ }
+
+ // Inteface methods
+ public boolean isDirectory() {
+ return isDirectoryFlagSet();
+ }
+
+ public boolean isReadOnlyFlagSet() {
+ return Util.getBit(attributes, 0);
+ }
+ public boolean isHiddenFlagSet() {
+ return Util.getBit(attributes, 1);
+ }
+ public boolean isSystemFlagSet() {
+ return Util.getBit(attributes, 2);
+ }
+ public boolean isLabelFlagSet() {
+ return Util.getBit(attributes, 3);
+ }
+ public boolean isDirectoryFlagSet() {
+ return Util.getBit(attributes, 4);
+ }
+ public boolean isArchiveFlagSet() {
+ return Util.getBit(attributes, 5);
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "Win32FileAttributes:");
+ printFields(ps, prefix);
+ }
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " isReadOnlyFlagSet: " + isReadOnlyFlagSet());
+ ps.println(prefix + " isHiddenFlagSet: " + isHiddenFlagSet());
+ ps.println(prefix + " isSystemFlagSet: " + isSystemFlagSet());
+ ps.println(prefix + " isLabelFlagSet: " + isLabelFlagSet());
+ ps.println(prefix + " isDirectoryFlagSet: " + isDirectoryFlagSet());
+ ps.println(prefix + " isArchiveFlagSet: " + isArchiveFlagSet());
+ }
+}
diff --git a/src/org/fox/ttcomics/CbrComicArchive.java b/src/org/fox/ttcomics/CbrComicArchive.java
new file mode 100644
index 0000000..059e070
--- /dev/null
+++ b/src/org/fox/ttcomics/CbrComicArchive.java
@@ -0,0 +1,74 @@
+package org.fox.ttcomics;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.zip.ZipFile;
+
+import org.catacombae.rarx.ListFilesInArchive;
+import org.catacombae.rarx.NewFileHeader;
+import org.catacombae.rarx.RARFileEntry;
+import org.catacombae.rarx.RARFileEntryStream;
+
+import android.util.Log;
+
+public class CbrComicArchive extends ComicArchive {
+ private final String TAG = this.getClass().getSimpleName();
+
+ private ZipFile m_zipFile;
+ private ArrayList<RARFileEntry> m_entries = new ArrayList<RARFileEntry>();
+
+ @Override
+ public int getCount() {
+ return m_entries.size();
+ }
+
+ @Override
+ public InputStream getItem(int index) throws IOException {
+ //return m_zipFile.getInputStream(m_entries.get(index));
+ RARFileEntryStream rfes = new RARFileEntryStream(m_entries.get(index));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+
+
+ return new ByteArrayInputStream(baos.toByteArray());
+ }
+
+ public CbrComicArchive(String fileName) throws IOException {
+ Log.d(TAG, "CbrComicArchive: " + fileName);
+
+ /* m_zipFile = new ZipFile(fileName);
+
+ Enumeration<? extends ZipEntry> e = m_zipFile.entries();
+
+ while (e.hasMoreElements()) {
+ ZipEntry ze = e.nextElement();
+ if (!ze.isDirectory() && ze.getName().toLowerCase().matches(".*\\.(jpg|bmp|gif)$")) {
+ m_entries.add(ze);
+ m_count++;
+ }
+ }
+
+ Collections.sort(m_entries, new Comparator<ZipEntry>() {
+ public int compare(ZipEntry a, ZipEntry b) {
+ return a.getName().compareTo(b.getName());
+ }
+ }); */
+
+ File file = new File(fileName);
+
+ for (RARFileEntry entry : ListFilesInArchive.listFiles(file)) {
+ NewFileHeader header = entry.getHeader(0);
+
+ if (header.getFilenameAsString().toLowerCase().matches(".*\\.(jpg|bmp|gif)$")) {
+ m_entries.add(entry);
+ }
+ }
+
+ }
+
+}
diff --git a/src/org/fox/ttcomics/ComicListFragment.java b/src/org/fox/ttcomics/ComicListFragment.java
index b2dc999..407e96d 100644
--- a/src/org/fox/ttcomics/ComicListFragment.java
+++ b/src/org/fox/ttcomics/ComicListFragment.java
@@ -295,14 +295,20 @@ public class ComicListFragment extends Fragment implements OnItemClickListener {
if (archive.isDirectory()) {
m_activity.setSize(filePath, SIZE_DIR);
- } else if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip)") && isAdded() && m_activity != null &&
+ } else if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip|cbr|rar)") && isAdded() && m_activity != null &&
m_activity.getWritableDb() != null && m_activity.getWritableDb().isOpen()) {
try {
int size = m_activity.getSize(filePath);
if (size == -1 || fullRescan) {
- CbzComicArchive cba = new CbzComicArchive(filePath);
+ ComicArchive cba;
+
+ if (archive.getName().toLowerCase().matches(".*\\.(cbz|zip)")) {
+ cba = new CbzComicArchive(filePath);
+ } else {
+ cba = new CbrComicArchive(filePath);
+ }
if (cba.getCount() > 0) {
// Get cover
@@ -313,16 +319,18 @@ public class ComicListFragment extends Fragment implements OnItemClickListener {
if (m_activity.isStorageWritable() && (!thumbnailFile.exists() || fullRescan)) {
InputStream is = cba.getItem(0);
- FileOutputStream fos = new FileOutputStream(thumbnailFile);
-
- byte[] buffer = new byte[1024];
- int len = 0;
- while ((len = is.read(buffer)) != -1) {
- fos.write(buffer, 0, len);
+ if (is != null) {
+ FileOutputStream fos = new FileOutputStream(thumbnailFile);
+
+ byte[] buffer = new byte[1024];
+ int len = 0;
+ while ((len = is.read(buffer)) != -1) {
+ fos.write(buffer, 0, len);
+ }
+
+ fos.close();
+ is.close();
}
-
- fos.close();
- is.close();
}
} catch (IOException e) {