From 354cef3a9c4e020aef9afeee12f846e4554ec7b7 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 19 Oct 2012 10:53:02 +0400 Subject: add rarlib stuff --- src/org/catacombae/.cvsignore | 5 + src/org/catacombae/io/.cvsignore | 5 + src/org/catacombae/io/RandomAccessFileStream.java | 66 + src/org/catacombae/io/RandomAccessStream.java | 54 + src/org/catacombae/rar/.cvsignore | 5 + src/org/catacombae/rar/RARFile.java | 32 + src/org/catacombae/rarx/.cvsignore | 5 + src/org/catacombae/rarx/CommentHeader.java | 54 + src/org/catacombae/rarx/DecompressionCode.java | 1715 +++++++++++++++++++++ src/org/catacombae/rarx/FileAttributes.java | 29 + src/org/catacombae/rarx/InvalidDataException.java | 29 + src/org/catacombae/rarx/Leaf.java | 7 + src/org/catacombae/rarx/ListArchiveContents.java | 438 ++++++ src/org/catacombae/rarx/ListFilesInArchive.java | 173 +++ src/org/catacombae/rarx/ListRecursive.java | 92 ++ src/org/catacombae/rarx/MarkHeader.java | 48 + src/org/catacombae/rarx/NewFileHeader.java | 428 +++++ src/org/catacombae/rarx/NewMainArchiveHeader.java | 94 ++ src/org/catacombae/rarx/ProtectedHeader.java | 67 + src/org/catacombae/rarx/RARFile.java | 248 +++ src/org/catacombae/rarx/RARFile2.java | 29 + src/org/catacombae/rarx/RARFileEntry.java | 76 + src/org/catacombae/rarx/RARFileEntryStream.java | 550 +++++++ src/org/catacombae/rarx/RARHeader.java | 121 ++ src/org/catacombae/rarx/README.txt | 10 + src/org/catacombae/rarx/TestRAFProperties.java | 32 + src/org/catacombae/rarx/Tree.java | 55 + src/org/catacombae/rarx/TreeNode.java | 17 + src/org/catacombae/rarx/UNIXFileAttributes.java | 66 + src/org/catacombae/rarx/UnknownHeader.java | 54 + src/org/catacombae/rarx/Util.java | 305 ++++ src/org/catacombae/rarx/Win32FileAttributes.java | 68 + src/org/fox/ttcomics/CbrComicArchive.java | 74 + src/org/fox/ttcomics/ComicListFragment.java | 30 +- 34 files changed, 5070 insertions(+), 11 deletions(-) create mode 100644 src/org/catacombae/.cvsignore create mode 100644 src/org/catacombae/io/.cvsignore create mode 100644 src/org/catacombae/io/RandomAccessFileStream.java create mode 100644 src/org/catacombae/io/RandomAccessStream.java create mode 100644 src/org/catacombae/rar/.cvsignore create mode 100644 src/org/catacombae/rar/RARFile.java create mode 100644 src/org/catacombae/rarx/.cvsignore create mode 100644 src/org/catacombae/rarx/CommentHeader.java create mode 100644 src/org/catacombae/rarx/DecompressionCode.java create mode 100644 src/org/catacombae/rarx/FileAttributes.java create mode 100644 src/org/catacombae/rarx/InvalidDataException.java create mode 100644 src/org/catacombae/rarx/Leaf.java create mode 100644 src/org/catacombae/rarx/ListArchiveContents.java create mode 100644 src/org/catacombae/rarx/ListFilesInArchive.java create mode 100644 src/org/catacombae/rarx/ListRecursive.java create mode 100644 src/org/catacombae/rarx/MarkHeader.java create mode 100644 src/org/catacombae/rarx/NewFileHeader.java create mode 100644 src/org/catacombae/rarx/NewMainArchiveHeader.java create mode 100644 src/org/catacombae/rarx/ProtectedHeader.java create mode 100644 src/org/catacombae/rarx/RARFile.java create mode 100644 src/org/catacombae/rarx/RARFile2.java create mode 100644 src/org/catacombae/rarx/RARFileEntry.java create mode 100644 src/org/catacombae/rarx/RARFileEntryStream.java create mode 100644 src/org/catacombae/rarx/RARHeader.java create mode 100644 src/org/catacombae/rarx/README.txt create mode 100644 src/org/catacombae/rarx/TestRAFProperties.java create mode 100644 src/org/catacombae/rarx/Tree.java create mode 100644 src/org/catacombae/rarx/TreeNode.java create mode 100644 src/org/catacombae/rarx/UNIXFileAttributes.java create mode 100644 src/org/catacombae/rarx/UnknownHeader.java create mode 100644 src/org/catacombae/rarx/Util.java create mode 100644 src/org/catacombae/rarx/Win32FileAttributes.java create mode 100644 src/org/fox/ttcomics/CbrComicArchive.java (limited to 'src') 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 Bits 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.

+ * + *

+     * 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}
+     * 
+ */ + 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 UnpBuf to dataOut according to some rules.

+ *

+     * 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[])
+     * 
+ */ + private boolean writeData(byte[] UnpBuf, int UnpPtr, int WrPtr, OutputStream dataOut) throws IOException { + if (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 InBuf 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 InBuf.length-32 bytes.

+ *

+     * 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}
+     * 
+ * @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 Count bytes from the stream ArcPtr into + * the buffer Addr at position offset. If the + * data is encrypted, it is automatically decrypted. (The variable + * Encryption tells the function whether is shall consider + * the data to be encrypted.)

+ *

+     * 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)
+     * 
+ * @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.

+ *

+     * 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
+     * 
+ */ + 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>> 12); + AddBits(4); + } + MakeDecodeTables(BitLength, BD, 0, BC); + + I=0; + while(IInBuf.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>> 13)+3); + AddBits(3); + } + else { + BitField = GetBits(InBuf, InAddr, InBit); + N=((BitField >>> 9)+11); + AddBits(7); + } + while(N-- > 0 && I=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 Decode object Dec supplied as parameter + * according to the data in LenTab. Initializes Dec for + * further use in decompression.

+ *

+     * Global modify set:
+     *       
+     *
+     * Global read set:
+     *       
+     * 
+     * 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)
+     * 
+ */ + 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; I0xFFFF) + 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; IDecode object.

+ *

+     * 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
+     * 
+ * + * @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>> (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 InAddr and InBit to 0.
+ * If the archive is a solid archive the method also does the following:
+ *
    + *
  • Set ChannelDelta and CurChannel to 0.
  • + *
  • Zero all AudioVariables objects in AudV through {@link AudioVariables#zero}.
  • + *
  • Zero the arrays unpBuf and UnpOldTable.
  • + *

+ *

+     * 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[])
+     * 
+ */ + 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.

+ *

 
+     * 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)
+     * 
+ */ + 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=-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>>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 Buf at position offset. + * The decrypted data is stored at the same place in Buf, thus overwriting the + * encrypted contents.

+ *

+     * 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)
+     * 
+ */ + 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. ;) + *
+     * Global modify set:
+     *       Key                        (int[4])
+     * Global read set:
+     *       CRCTab                     (int[256])
+     * Local use set:
+     * (in)  Buf                        (byte[])
+     *       I                          (int)
+     * 
+ */ + 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. ;)
+ * Password is supposed to be trimmed to the actual + * password length and not zero-terminated.

+ *

+     * 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
+     * 
+ */ + 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 + * 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 + * + */ + 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 crcTab, 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. + *
+     * 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.
+     * 
+ * @return the updated sum + */ + static int CalcCRC32(int StartCRC, byte[] Addr, int offset, int Size) { + /*unsigned */int I; + for (I=offset; I>> 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 extends TreeNode { + 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 savedEntries = new ArrayList(); + ArrayList dirEntries = new ArrayList(); + ArrayList fileEntries = new ArrayList(); + LinkedList extractedFiles = new LinkedList(); + LinkedList badFiles = new LinkedList(); + LinkedList createErrorFiles = new LinkedList(); + LinkedList badFilesizeFiles = new LinkedList(); + 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 regionData = new LinkedList(); + 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 regionData = new LinkedList(); + 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 regionData = new LinkedList(); + 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 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 regions = new LinkedList(); + + 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 listFilesAsTree(File rarFile) throws IOException, InvalidDataException { + return listFilesAsTree(rarFile, false); + } + public static Tree listFilesAsTree(File rarFile, boolean includeComments) throws IOException, InvalidDataException { + RARFileEntry[] contents = listFiles(rarFile, includeComments); + + // Create the empty directory tree + Tree rarTree = new Tree(); + for(RARFileEntry currentEntry : contents) { + NewFileHeader nfh = currentEntry.getHeader(0); + + String[] filenameComponents = nfh.getFilenameAsString().split("\\" + NewFileHeader.PATH_SEPARATOR); + Tree 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 childNode = currentNode.get(filenameComponents[i]); + if(childNode == null) { + childNode = new Tree(); + currentNode.put(filenameComponents[i], childNode); + } + + if(childNode instanceof Tree) + currentNode = (Tree)childNode; + else + throw new RuntimeException("Duplicate entries in RAR file"); + } + + // The last component is mapped to the current RARFileEntry + TreeNode childNode = currentNode.get(filenameComponents[filenameComponents.length-1]); + if(childNode == null) { + if(nfh.getFileAttributesStructured().isDirectory()) + childNode = new Tree(currentEntry); + else + childNode = new Leaf(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 entries = new LinkedList(); + 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 codeUnitList = new LinkedList(); + + // 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> 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 parts = new ArrayList(); + + 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 intervals = new LinkedList(); + + 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 entrySegments = getSegments(startFile, pos, new LinkedList(), 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 getSegments(File file, long position, LinkedList 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 extends TreeNode { + public Tree(A value) { super(value); } + public Tree() { super(); } + + private LinkedList>> children = new LinkedList>>(); + + private static class Pair { + public A a; + public B b; + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + } + + public void put(String entryName, TreeNode tn) { + children.addLast(new Pair>(entryName, tn)); + } + public TreeNode get(String entryName) { + for(Pair> 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> 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> 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 { + 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 m_entries = new ArrayList(); + + @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 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() { + 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) { -- cgit v1.2.3