summaryrefslogtreecommitdiff
path: root/src/org/catacombae/rarx/NewFileHeader.java
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2012-10-19 10:53:02 +0400
committerAndrew Dolgov <[email protected]>2012-10-19 10:53:02 +0400
commit354cef3a9c4e020aef9afeee12f846e4554ec7b7 (patch)
treeadee32a85721ae5afadf4440e206a0e74b077f4f /src/org/catacombae/rarx/NewFileHeader.java
parent7923b86a935343bd0bc990b392fc85ac733518cf (diff)
add rarlib stuff
Diffstat (limited to 'src/org/catacombae/rarx/NewFileHeader.java')
-rw-r--r--src/org/catacombae/rarx/NewFileHeader.java428
1 files changed, 428 insertions, 0 deletions
diff --git a/src/org/catacombae/rarx/NewFileHeader.java b/src/org/catacombae/rarx/NewFileHeader.java
new file mode 100644
index 0000000..104bed1
--- /dev/null
+++ b/src/org/catacombae/rarx/NewFileHeader.java
@@ -0,0 +1,428 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+package org.catacombae.rarx;
+
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.Calendar;
+import java.util.Date;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+
+public class NewFileHeader extends RARHeader {
+ /*
+ * struct NewFileHeader
+ * size: 32 bytes
+ *
+ * BP Size Description
+ * 0 2 HeadCRC
+ * 2 1 HeadType
+ * 3 2 Flags
+ * 5 2 HeadSize
+ * 7 4 PackSize
+ * 11 4 UnpSize
+ * 15 1 HostOS
+ * 16 4 FileCRC
+ * 20 4 FileTime
+ * 24 1 UnpVer
+ * 25 1 Method
+ * 26 2 NameSize
+ * 28 4 FileAttr
+ */
+
+ // Possible values for the hostOS field in NewFileHeader, and possibly elsewhere...
+ public static final int HOSTOS_MS_DOS = 0;
+ public static final int HOSTOS_OS2 = 1;
+ public static final int HOSTOS_WIN_32 = 2;
+ public static final int HOSTOS_UNIX = 3;
+ public static final String[] hostOSStrings = { "MS-DOS", "OS/2", "Win32", "UNIX" };
+
+ public static final int FM_NORMAL = 0x00;
+ public static final int FM_RDONLY = 0x01;
+ public static final int FM_HIDDEN = 0x02;
+ public static final int FM_SYSTEM = 0x04;
+ public static final int FM_LABEL = 0x08;
+ public static final int FM_DIREC = 0x10;
+ public static final int FM_ARCH = 0x20;
+
+ public static final String PATH_SEPARATOR = "\\";
+
+ private final byte[] packSize = new byte[4];
+ private final byte[] unpSize = new byte[4];
+ private final byte[] hostOS = new byte[1];
+ private final byte[] fileCRC = new byte[4];
+ private final byte[] fileTime = new byte[4];
+ private final byte[] unpVer = new byte[1];
+ private final byte[] method = new byte[1];
+ private final byte[] nameSize = new byte[2];
+ private final byte[] fileAttr = new byte[4];
+ private final byte[] packSizeExtended;
+ private final byte[] unpSizeExtended;
+ private final byte[] filename;
+ private final byte[] trailingData;
+
+ public NewFileHeader(byte[] data, int offset) {
+ super(data, offset);
+ System.arraycopy(data, offset+7, packSize, 0, 4);
+ System.arraycopy(data, offset+11, unpSize, 0, 4);
+ System.arraycopy(data, offset+15, hostOS, 0, 1);
+ System.arraycopy(data, offset+16, fileCRC, 0, 4);
+ System.arraycopy(data, offset+20, fileTime, 0, 4);
+ System.arraycopy(data, offset+24, unpVer, 0, 1);
+ System.arraycopy(data, offset+25, method, 0, 1);
+ System.arraycopy(data, offset+26, nameSize, 0, 2);
+ System.arraycopy(data, offset+28, fileAttr, 0, 4);
+ if(hasExtendedHeader()) {
+ packSizeExtended = new byte[4];
+ unpSizeExtended = new byte[4];
+ System.arraycopy(data, offset+32, packSizeExtended, 0, 4);
+ System.arraycopy(data, offset+36, unpSizeExtended, 0, 4);
+ }
+ else {
+ packSizeExtended = new byte[0];
+ unpSizeExtended = new byte[0];
+ }
+ final int extSize = packSizeExtended.length + unpSizeExtended.length;
+ filename = new byte[getNameSize()];
+ trailingData = new byte[getHeadSize()-(getStaticSize()+extSize+filename.length)];
+ System.arraycopy(data, offset+32+extSize, filename, 0, filename.length);
+ System.arraycopy(data, offset+32+extSize+filename.length, trailingData, 0, trailingData.length);
+ validateData();
+ }
+
+ public static int getStaticSize() { return 32; }
+ public int getSize() { return _getSize(); }
+ private int _getSize() { return getHeadSize(); }
+
+ protected void validateData() {
+ //print(System.out, "");
+ super.validateData();
+ if(getHeadType() != FILE_HEAD)
+ throw new InvalidDataException("Incorrect head type! (headType=" + getHeadType() + ")");
+ if(getHeadSize() < getStaticSize())
+ throw new InvalidDataException("Invalid size! (size=" + getHeadSize() + ")");
+ if(getHostOSAsString() == null)
+ throw new InvalidDataException("Host OS value invalid.");
+ }
+
+ public long getPackSize() {
+ long result = Util.readIntLE(packSize) & 0xFFFFFFFFL;
+ if(packSizeExtended.length == 4)
+ result |= (Util.readIntLE(packSizeExtended) & 0xFFFFFFFFL) << 32;
+ return result;
+ }
+ public long getUnpSize() {
+ long result = Util.readIntLE(unpSize) & 0xFFFFFFFFL;
+ if(unpSizeExtended.length == 4)
+ result |= (Util.readIntLE(unpSizeExtended) & 0xFFFFFFFFL) << 32;
+ return result;
+// if(unpSizeExtended.length == 4) {
+// return Util.readIntLE(unpSize);
+ }
+ public byte getHostOS() { return Util.readByteLE(hostOS); }
+ public int getFileCRC() { return Util.readIntLE(fileCRC); }
+ public int getFileTime() { return Util.readIntLE(fileTime); }
+ public byte getUnpVer() { return Util.readByteLE(unpVer); }
+ public byte getMethod() { return Util.readByteLE(method); }
+ public short getNameSize() { return Util.readShortLE(nameSize); }
+ public int getFileAttr() { return Util.readIntLE(fileAttr); }
+ public byte[] getFilename() { return Util.createCopy(filename); }
+ public byte[] getTrailingData() { return Util.createCopy(trailingData); }
+
+ public byte[] getHeaderData() {
+ byte[] data = new byte[_getSize()];
+ System.arraycopy(super.getHeaderData(), 0, data, 0, super.getSize());
+ System.arraycopy(packSize, 0, data, 7, packSize.length);
+ System.arraycopy(unpSize, 0, data, 11, unpSize.length);
+ System.arraycopy(hostOS, 0, data, 15, hostOS.length);
+ System.arraycopy(fileCRC, 0, data, 16, fileCRC.length);
+ System.arraycopy(fileTime, 0, data, 20, fileTime.length);
+ System.arraycopy(unpVer, 0, data, 24, unpVer.length);
+ System.arraycopy(method, 0, data, 25, method.length);
+ System.arraycopy(nameSize, 0, data, 26, nameSize.length);
+ System.arraycopy(fileAttr, 0, data, 28, fileAttr.length);
+ if(hasExtendedHeader()) {
+ System.arraycopy(packSizeExtended, 0, data, 32, 4);
+ System.arraycopy(unpSizeExtended, 0, data, 36, 4);
+ }
+ final int extSize = packSizeExtended.length + unpSizeExtended.length;
+ System.arraycopy(filename, 0, data, 32+extSize, filename.length);
+ System.arraycopy(trailingData, 0, data, 32+extSize+filename.length, trailingData.length);
+ return data;
+ }
+
+ /**
+ * true if the header is 40 bytes long, false if it is 32 bytes long
+ * The 40 byte header exists to accommodate filesizes > 2^32
+ */
+ public boolean hasExtendedHeader() {
+ return getFlag(8);
+ }
+
+ /**
+ * true if this file is a continuation of a previous file (split archive)
+ */
+ public boolean hasIncomingData() {
+ return getFlag(0);
+ }
+
+ /**
+ * true if this file continues in another file (split archive)
+ */
+ public boolean hasOutgoingData() {
+ return getFlag(1);
+ }
+
+ public Date getFileTimeAsDate() {
+ int data = getFileTime();
+ int year = 1980 + ((data >> (4+5+5+6+5)) & 0x7F);
+ int month = ((data >> (5+5+6+5)) & 0xF)-1;
+ int day = (data >> (5+6+5)) & 0x1F;
+ int hour = (data >> (6+5)) & 0x1F;
+ int minute = (data >> (5)) & 0x3F;
+ int doublesecond = data & 0x1F; // the archive format only supports 2-second precision (probably due to simpler design)
+
+// System.out.println("year = " + year);
+// System.out.println("month = " + month);
+// System.out.println("day = " + day);
+// System.out.println("hour = " + hour);
+// System.out.println("minute = " + minute);
+// System.out.println("doublesecond = " + doublesecond);
+
+ Calendar c = Calendar.getInstance();
+ c.setTime(new Date(0));
+ c.set(Calendar.YEAR, year);
+ c.set(Calendar.MONTH, month);
+ c.set(Calendar.DAY_OF_MONTH, day);
+ c.set(Calendar.HOUR, hour);
+ c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.SECOND, doublesecond*2);
+ return c.getTime();
+ }
+
+ public FileAttributes getFileAttributesStructured() {
+ byte hostOS = getHostOS();
+ if(hostOS == HOSTOS_WIN_32)
+ return new Win32FileAttributes(getFileAttr());
+ else if(hostOS == HOSTOS_UNIX)
+ return new UNIXFileAttributes(getFileAttr());
+ else if(hostOS == HOSTOS_MS_DOS || hostOS == HOSTOS_OS2)
+ return new Win32FileAttributes(getFileAttr()); // Win, DOS and OS/2 seems to have a lot in common, but I don't really know what the file attributes will look like under DOS and OS/2. Hopefully something like the Win32-attributes (:
+ else
+ throw new RuntimeException("Unknown OS type!");
+ }
+
+// public boolean isFile() {
+// return getFlag(getFileAttr(), 13);
+// }
+
+ public String getHostOSAsString() {
+ return hostOSToString(getHostOS());
+ }
+
+ public String getUnpackVersionAsString() {
+ byte unpVer = getUnpVer();
+ return (unpVer/10) + "." + (unpVer%10);
+ }
+
+ public String getFilenameAsString() {
+ String result = getUnicodeFilenameAsString();
+ if(result == null)
+ result = getLegacyFilenameAsString();
+ return result;
+ }
+ public String getLegacyFilenameAsString() {
+ /* After testing, it seems that one of the old MS-DOS charsets
+ are used to store the file name in this structure. If
+ different charsets are supported, I don't know how to detect
+ which charset will be used yet. Code Page 850 is used for now. */
+ try {
+ return new String(filename, /*"IBM437"*/"iso-8859-1").split("\0")[0];
+ } catch(UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+ }
+
+ public String getUnicodeFilenameAsString() {
+ /* To cope with characters outside the old IBM charset originally
+ used for filenames, RAR embeds extra data after the area
+ containing the IBM charset filename. The old style filename is
+ terminated by null, and after that follows the characters that
+ could not be encoded properly with the IBM charset. */
+ boolean debug = false;
+ if(debug) System.out.println("getExtendedCharsetFilenameAsString();");
+ char[] legacyFilename = getLegacyFilenameAsString().toCharArray();
+ char[] unicodeFilename = new char[legacyFilename.length];
+ int unicodeFilenamePointer = 0;
+ //LinkedList<Character> codeUnitList = new LinkedList<Character>();
+
+ // Find the index at which the extended filename characters begin (first occurrence of 0x00).
+ int zeroIndex = 0;
+ for(; zeroIndex < filename.length; ++zeroIndex)
+ if(filename[zeroIndex] == 0)
+ break;
+
+ int codeUnitsRead = 0;;
+
+ int extendedDataPointer = zeroIndex+1;
+ if(extendedDataPointer+2 >= filename.length) // Must have compressedLSB (1 bytes) and at least one byte
+ return null;
+ byte compressedLSB = Util.readByteLE(filename, extendedDataPointer++);
+
+ mainLoop:
+ while(extendedDataPointer < filename.length) {
+ if(debug) System.out.println(" Reading 4 bytes... (found " + codeUnitsRead +
+ " code points, extendedDataPointer=" + extendedDataPointer + ")");
+ byte groupDescriptor = Util.readByteLE(filename, extendedDataPointer++);
+ for(int i = 0; (i < 4) && (extendedDataPointer < filename.length); ++i) {
+ byte current = (byte)((groupDescriptor >>> ((3-i)*2)) & 0x3); // 0b11
+ if(current == 0x0) { // 0b00
+ //if(extendedDataPointer >= filename.length) break;
+ char codeUnit = (char)(Util.readByteLE(filename, extendedDataPointer++) & 0xFF);
+ if(debug) System.out.println(" Encountered 8-bit character: 0x" + Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x1) {
+ //if(extendedDataPointer >= filename.length) break;
+ char codeUnit = (char)(((compressedLSB & 0xFF) << 8) |
+ (Util.readByteLE(filename, extendedDataPointer++) & 0xFF));
+ if(debug) System.out.println(" Encountered compressed 16-bit character: 0x" +
+ Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x2) { // 0b10
+ //if(extendedDataPointer+1 >= filename.length) break;
+ char codeUnit = (char)Util.readShortLE(filename, extendedDataPointer);
+ extendedDataPointer += 2;
+ if(debug) System.out.println(" Encountered 16-bit character: 0x" + Util.toHexStringBE(codeUnit));
+ //codeUnitList.add(codeUnit);
+ unicodeFilename[unicodeFilenamePointer++] = codeUnit;
+ ++codeUnitsRead;
+ }
+ else if(current == 0x3) {
+ //if(extendedDataPointer >= filename.length) break;
+ byte indicator = Util.readByteLE(filename, extendedDataPointer++);
+
+ /* Detta �r bullshit. Det �r INTE en indikator utan ett "skip"-kommando.
+ * Ex. skip(0x02) => Spola f�rbi 4 characters dvs. ".txt" (n�stan alla test*+.rar)
+ * skip(0x05) => Spola f�rbi 7 characters ex. "bok.txt" (ctest6.rar)
+ * skip(0x01) => Spola f�rbi 3 characters ex. "Hej" (ctest16.rar)
+ * skip(0x03) => Spola f�rbi 5 characters ex. "laban" (ctest16.rar)
+ * Specialfall:
+ * - Man kan som minst spola f�rbi 2 characters (skip(0x00)). Om str�ngen inleds med
+ * en ASCII-character med Unicode-char direkt f�ljande s� kodas ASCII-charactern ner
+ * som Unicode.
+ * - P� samma s�tt kan man inte spola f�rbi 131 characters, utan �r tvungen att koda
+ * skip(0x7F)... eller n�t. iaf skitsamma. orka.
+ */
+
+ if((indicator & 0xFF) > 0x7F)
+ System.out.println(" Encountered unexpected skip value! 0x" + Util.toHexStringBE(indicator));
+ if(debug) System.out.println(" Skipping " + (indicator+2) + " bytes.");
+
+ System.arraycopy(legacyFilename, unicodeFilenamePointer, unicodeFilename, unicodeFilenamePointer, (indicator+2));
+ unicodeFilenamePointer += (indicator+2);
+ }
+ }
+ }
+ if(unicodeFilenamePointer != legacyFilename.length)
+ if(debug) System.out.println(" ERROR! Did not read enough characters. Read " + unicodeFilenamePointer + " characters while legacyFilename.length==" + legacyFilename.length + ".");
+ else
+ if(debug) System.out.println(" Read " + unicodeFilenamePointer + " characters.");
+ return new String(unicodeFilename);
+ }
+
+ public byte[] getTrailingFilenameData() {
+ for(int i = 0; i < filename.length; ++i) {
+ if(filename[i] == 0)
+ return Util.createCopy(filename, i, filename.length-i);
+ }
+ return new byte[0];
+ }
+
+ public static String hostOSToString(byte hostOS) {
+ if(hostOS >= 0 && hostOS <= hostOSStrings.length)
+ return hostOSStrings[hostOS];
+ else
+ return null;
+ }
+
+ /* // An attempt at porting the CRC-function from unrarlib to Java. Seems to work poorly.
+ private static class MyCRC32 {
+ private final int[] CRCTab = new int[256];
+ private int currentCRC = 0xFFFFFFFF;
+ public MyCRC32() {
+ short I, J;
+ int C;
+ for(I=0;(I&0xFFFF)<256;I++) {
+ for(C=I,J=0;J<8;J++)
+ C=((C & 1) != 0)? (C>>1)^0xEDB88320 : (C>>1);
+ CRCTab[I&0xFFFF]=C;
+ }
+ }
+
+ public void update(byte[] data) {
+ currentCRC = CalcCRC32(currentCRC, data);
+ }
+
+ public int getValue() {
+ return currentCRC;
+ }
+
+ private int CalcCRC32(int StartCRC, byte[] Addr) {
+ short I;
+ for(I=0; I<Addr.length; I++)
+ StartCRC = CRCTab[((byte)StartCRC ^ Addr[I])&0xFF] ^ (StartCRC >> 8);
+ return(StartCRC);
+ }
+ }
+ */
+
+
+ protected void printFields(PrintStream ps, String prefix) {
+ super.printFields(ps, prefix);
+ ps.println(prefix + " PackSize: " + getPackSize() + " bytes");
+ ps.println(prefix + " UnpSize: " + getUnpSize() + " bytes");
+ ps.println(prefix + " HostOS: \"" + getHostOSAsString() + "\"");
+ ps.println(prefix + " FileCRC: 0x" + Util.toHexStringBE(getFileCRC()));
+ ps.println(prefix + " FileTime: " + getFileTimeAsDate() + " (0x" + Util.toHexStringBE(getFileTime()) + ")");
+ ps.println(prefix + " UnpVer: \"" + getUnpackVersionAsString() + "\"");
+ ps.println(prefix + " Method: 0x" + Util.toHexStringBE(getMethod()));
+ ps.println(prefix + " NameSize: " + getNameSize() + " bytes");
+ ps.println(prefix + " FileAttr: 0x" + Util.toHexStringBE(getFileAttr()));
+ ps.println(prefix + " Structured file attributes:");
+ getFileAttributesStructured().print(ps, prefix + " ");
+ ps.println(prefix + " Filename: \"" + getFilenameAsString() + "\"");
+ byte[] trailingFilenameData = getTrailingFilenameData();
+ if(trailingFilenameData.length > 0)
+ ps.println(prefix + " (Trailing filename data: 0x" + Util.byteArrayToHexString(trailingFilenameData) + ")");
+ ps.println(prefix + " Trailing data: 0x" + Util.byteArrayToHexString(getTrailingData()));
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "NewFileHeader: ");
+ printFields(ps, prefix);
+ }
+}