`
RednaxelaFX
  • 浏览: 3022970 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

ケータイ少女 script.arc的解压缩程序 (Java)

阅读更多
嗯,这个也是快一年前写的了。当时在澄空看到有人想要解手机少女的文件,就随便写了个程序来处理这个script.arc与string.arc。另外几个arc文件用下面的程序处理不了,因为文件头里的信息没有被正确的处理。嘛,后来asmodean也放出了处理这个游戏的工具而且是用C++写的,我就没必要继续把这个程序做完。

也是由于使用了BlowfishJ的BinConverter.java,所以以下代码以LGPL许可证发布。

Archiver.java:
/*
 * @(#)Archiver.java  2007/05/15
 * Written by rednaxela / FX
 */

import java.io.*;
import java.util.*;

/*

Data structures:

//--------------------------------
typedef struct tagHEADER {
    DWORD id;       // IDENTIFIER, BE AD CA A8, little-endian ("ARCW" xor 0xFF)
    DWORD ver;      // VERSION, locked to 0x00
    DWORD fileSize; // total size of current archive
    DWORD data_offset;  // begin offset of file data
} HEADER; // 16 bytes

typedef struct tagSECINFO {
    DWORD sectionLength; // sectionLength = len(SECINFO)(=0x0C) + len(SECDATA)
    DWORD flags;         // flags: NOT_MSK = 1, LZSS_MSK = 2
    DWORD origSize;      // original size of SECDATA after inflation
} SECINFO; // 12 bytes

typedef struct tagSECDATA {
    BYTE data[SECINFO.sectionLength - 12];
    // when SECINFO.flags = NOT_MSK | LZSS_MSK, the contents is both encrypted by NOT
    // and compressed by LZSS.
} SECDATA;

struct INDEXBLOCK1 {
    
    struct INDEXENTRY {
        DWORD offset; // actualOfs = offset + totalHeaderLength - 0x0C
        DWORD length; // length in archive
        DWORD flags;  // NOT_MSK = 1, LZSS_MSK = 2
        DWORD origLen; // original file length
    };
    
    DWORD reserved1;   // some sort of flag I guess
    DWORD reserved2;   // seems to be always zero
    DWORD entryCount;  // number of entries in this block
    DWORD blockLength; // length of the remaining part of this block
    
    DWORD nameOffset[entryCount]; // offset relative to nameSecData after decode and decompression
    INDEXENTRY entry[entryCount]; // TOC entrys
};

struct INDEXBLOCK2 {
}; // structure is unknown


//--------------------------------

arc file:

HEADER header;       // 1 header per archive
SECINFO nameSecInfo; // 1st of SECINFO in archive
SECDATA nameSecData; // 1st of SECDATA in archive, contents of which are filenames in UNICODE
SECINFO indexSecInfo; // 2nd of SECINFO in archive
SECDATA indexSecData; // 2nd of SECDATA in archive, contents of which indicates offset, length, flag info of file data.
bytes data[]; // remaining part of archive, which is file data

//--------------------------------

indexSecData after decode and decompression:

DWORD blockCount;
INDEXBLOCK1 block1;
// INDEXBLOCK2 block2; // this is where I've got trouble figuring out.

*/

/**
 * Demonstrating archive operations on *.arc files as seen in Ketai-s.
 */
public class Archiver {
    
    static final int IDENTIFIER = 0xA8BCADBE;
    static final int VERSION = 0x00;
    static final int NOT_MSK = 1;
    static final int LZSS_MSK = 2;

    static final String USAGE = "Usage: java Archiver [option] srcPath dstPath\n"
                                  + "options:\n"
                                  + "[l]ist\n"
                                  + "[e]xtract\n"
                                  + "[a]rchive";

    static ArrayList<IndexEntry> index = null;
    /**
     * the application entry point
     * @param args (command line) parameters
     */
    public static void main(String[] args) throws Exception {
        
        // check command line arguments
        if (args.length != 3) error(USAGE);
        
        if ("l".equals(args[0].trim())) { // extract files from archive
        
            String srcPath = args[1].trim();
            String dstPath = args[2].trim();
            if (srcPath.length() == 0) error("2nd argument not exist.");
            if (dstPath.length() == 0) error("3rd argument not exist.");
            
            listFiles(srcPath, dstPath);
            
        } else if ("e".equals(args[0].trim())) { // extract files from archive
        
            String srcPath = args[1].trim();
            String dstPath = args[2].trim();
            if (srcPath.length() == 0) error("2nd argument not exist.");
            if (dstPath.length() == 0) error("3rd argument not exist.");
            
            extractFiles(srcPath, dstPath);
            
        } else if ("a".equals(args[0].trim())) { // pack files into archive
            
            String srcPath = args[1].trim();
            String dstPath = args[2].trim();
            if (srcPath.length() == 0) error("2nd argument not exist.");
            if (dstPath.length() == 0) error("3rd argument not exist.");
            
            packFiles(srcPath, dstPath);
            
        } else error(USAGE);
    }
    
    private static void listFiles(String srcFile, String listFile) throws Exception {
        // open source archive
        File arc = new File(srcFile);
        if (!arc.exists()) error("Archive " + srcFile + " doesn't exist");
        
        long contentOfs = -1L;
        FileInputStream fis = new FileInputStream(arc);
        DataInputStream dis = new DataInputStream(fis);
        index = new ArrayList<IndexEntry>();
        
        // match archive IDENTIFIER
        int id = flipEndian(dis.readInt());
        if (IDENTIFIER != id) error("Archive file not supported. Unexpected identifier 0x" + Integer.toHexString(id).toUpperCase());
        
        // match archive version
        int ver = flipEndian(dis.readInt());
        if (VERSION != ver) error("Archive file not supported. Unexpected version.");
        
        // match file size
        int fsize = flipEndian(dis.readInt());
        if ((int)(arc.length()) != fsize) error("Archive file not supported. Unexpected file size.");
        
        // probable checksum
        int checksum = flipEndian(dis.readInt());
        // if (checksum(arc) != checksum)) error("Archive file not supported. Unexpected checksum.");
        System.err.println("CHECKSUM: 0x" + Integer.toHexString(checksum).toUpperCase());
        
        // read index entries
        byte[] nameBuf = readIndexBlock(fis);
        byte[] infoBuf = readIndexBlock(fis);
        parseIndex(nameBuf, infoBuf);
        
        // extract files
        int baseOfs = (int)(fis.getChannel().position()); // check this, might not be correct
        PrintStream out = new PrintStream(new FileOutputStream(listFile));
        System.setOut(out); // stdout redirection
        for (IndexEntry entry : index) {
            // print file info
            printFileInfo(entry, baseOfs);
        }
    }
    
    private static void extractFiles(String srcFile, String dstDir) throws Exception {
        // open source archive
        File arc = new File(srcFile);
        if (!arc.exists()) error("Archive " + srcFile + " doesn't exist");
        
        long contentOfs = -1L;
        FileInputStream fis = new FileInputStream(arc);
        DataInputStream dis = new DataInputStream(fis);
        index = new ArrayList<IndexEntry>();
        
        // match archive IDENTIFIER
        int id = flipEndian(dis.readInt());
        if (IDENTIFIER != id) error("Archive file not supported. Unexpected identifier 0x" + Integer.toHexString(id).toUpperCase());
        
        // match archive version
        int ver = flipEndian(dis.readInt());
        if (VERSION != ver) error("Archive file not supported. Unexpected version.");
        
        // match file size
        int fsize = flipEndian(dis.readInt());
        if ((int)(arc.length()) != fsize) error("Archive file not supported. Unexpected file size.");
        
        // probable checksum
        int checksum = flipEndian(dis.readInt());
        // if (checksum(arc) != checksum)) error("Archive file not supported. Unexpected checksum.");
        System.err.println("CHECKSUM: 0x" + Integer.toHexString(checksum).toUpperCase());
        
        // read index entries
        byte[] nameBuf = readIndexBlock(fis);
        byte[] infoBuf = readIndexBlock(fis);
        parseIndex(nameBuf, infoBuf);
        
        // extract files
        int baseOfs = (int)(fis.getChannel().position()); // check this, might not be correct
        for (IndexEntry entry : index) {
            
            // print file info
            printFileInfo(entry, baseOfs);
            
            // data correctness check - this support ordered file archive only
            // to support out-of-order archives, use RandomAccessFile instead.
            if ((int)(fis.getChannel().position()) != entry.getOffset() + baseOfs) {
//              error("Bad file content order "
//              + entry.getFilename() + " @ 0x"
//              + Integer.toHexString((int)(fis.getChannel().position())));
                System.err.println("Out-of-order. " + entry.getFilename() + " @ 0x"
                + Integer.toHexString((int)(fis.getChannel().position())));
                fis.getChannel().position(entry.getOffset() + baseOfs);
            }
                
            // get buffer and read file
            byte[] writeBuf = readBlock(fis, entry.getLength(), entry.getFlags(), entry.getOrigLength());
            // write file
            writeFile(writeBuf, entry.getFilename(), dstDir);
        }
        fis.close();
    }
    
    private static void packFiles(String srcDir, String dstFile) throws Exception {
        // TODO
        // not implemented yet
    }
    
    private static void parseIndex(byte[] nameBuf, byte[] infoBuf) throws IOException {
        DataInputStream dis = new DataInputStream(
            new ByteArrayInputStream(infoBuf));
        int blockCount = flipEndian(dis.readInt());
        
        // PROBLEM 1
        // This is where my program doesn't go compatible with the image.arc
        // and sound.arc files. Their blockCount is 2. The 1st block seem
        // to be the same as string.arc and script.arc, but the 2nd block is severely
        // obsfucated that I don't know what the program is doing.
        
        // if (blockCount != 1) error("Archive not supported. BlockCount is " + blockCount);
        
        for (int i = 0; i < blockCount; ++i) {
            int reserved1 = flipEndian(dis.readInt()); // unknown usage
            int reserved2 = flipEndian(dis.readInt()); // not used
            int entryCount = flipEndian(dis.readInt()); // entry count in current block
            int blockSize = flipEndian(dis.readInt()); // current block size
            
            // System.err.println(Arrays.toString(nameBuf));
            System.err.println("reserved1: " + reserved1);
            System.err.println("reserved2: " + reserved2);
            System.err.println("entry count: " + entryCount);
            System.err.println("block size: " + blockSize);
            
            // PROBLEM 2
            
            // if (4 * 5 * entryCount != blockSize) error("Index error. Wrong block size.\n"
            //  + "Count=" + entryCount + " BlockSize=" + blockSize);
            
            int[] nameOfs = new int[entryCount];
            for (int j = 0; j < entryCount; ++j) {
                nameOfs[j] = flipEndian(dis.readInt());
            }
            
            for (int j = 0; j < entryCount; ++j) {
                IndexEntry entry = new IndexEntry();
                entry.setFilename(readWString(nameBuf, nameOfs[j]));
                entry.setOffset(flipEndian(dis.readInt()));
                entry.setLength(flipEndian(dis.readInt()) - 12);
                entry.setFlags(flipEndian(dis.readInt()));
                entry.setOrigLength(flipEndian(dis.readInt()));
                
                index.add(entry);
            }
        }
    }
    
    private static int flipEndian(int i) {
        byte[] bytes = new byte[4];
        bytes[0] = (byte)(i >>> 24);
        bytes[1] = (byte)(i >>> 16);
        bytes[2] = (byte)(i >>> 8);
        bytes[3] = (byte) i;
        
        i  =  bytes[3] << 24;
        i |= (bytes[2] << 16) & 0x0ff0000;
        i |= (bytes[1] <<  8) & 0x000ff00;
        i |=  bytes[0]        & 0x00000ff;
        
        return i;
    }
    
    private static void invertBitsArray(byte[] arr) {
        for (int i = 0; i < arr.length; ++i) {
            arr[i] = (byte)~arr[i];
        }
    }
    private static void error(String cause) {
        System.err.println("Error " + cause);
        System.exit(1);
    }
    
    private static void printFileInfo(IndexEntry entry, int baseOfs) {
        System.out.print("File " + entry.getFilename());
        System.out.print(" @ 0x" + Integer.toHexString(entry.getOffset() + baseOfs).toUpperCase());
        System.out.print(" size: 0x" + Integer.toHexString(entry.getLength()).toUpperCase());
        System.out.println(" origSize: 0x" + Integer.toHexString(entry.getOrigLength()).toUpperCase());
        System.out.print("Masks: ");
        
        boolean doPrint = (entry.getFlags() | NOT_MSK) != 0;
        if (doPrint) System.out.print("NOT_MSK");
        if ((entry.getFlags() | LZSS_MSK) != 0 && doPrint) System.out.print(" | ");
        else System.out.println();
        if ((entry.getFlags() | LZSS_MSK) != 0) System.out.println("LZSS_MSK");
    }
    
    private static byte[] readIndexBlock(InputStream in) throws IOException {
        DataInputStream dis = new DataInputStream(in);
        
        int len = flipEndian(dis.readInt()) - 12;
        int flags = flipEndian(dis.readInt());
        int origLen = flipEndian(dis.readInt());
        
        return readBlock(in, len, flags, origLen);
    }
    
    private static byte[] readBlock(InputStream in, int len, int flags, int origLen) throws IOException {
        byte[] buf = new byte[len];
        in.read(buf);
        
        if ((flags | NOT_MSK) != 0) invertBitsArray(buf);
        if (((flags | LZSS_MSK) != 0) && (len < origLen)) buf = LZSS.decompress(buf, len, null, origLen);
        // TODO - other flag status are not supported yet
        
        return buf;
    }
    
    private static String readWString(byte[] buf, int pos) throws IOException {
        int len = 0;
        int i = pos;
        while ((buf[i] | buf[i+1]) != 0) {
            len += 2;
            i += 2;
        }
        
        return new String(buf, pos, len, "UTF-16LE");
    }
    
    private static void writeFile(byte[] buf, String name, String dstDir) throws IOException {
        if (dstDir == null) dstDir = "./output";
        File outfile = new File(dstDir + "/" + name);
        File parentDir = outfile.getParentFile();
        if (!parentDir.exists()) parentDir.mkdirs();
        
        FileOutputStream fos = new FileOutputStream(outfile);
        BufferedOutputStream bos = new BufferedOutputStream(fos, 0x100000);
        ByteArrayInputStream bais = new ByteArrayInputStream(buf);
        
        int remainder = buf.length; // keep track of the amount of remaining bytes
        byte[] bytes16 = new byte[16];
        while (bais.available() != 0 && remainder / 16 != 0) {
            bais.read(bytes16);
            bos.write(bytes16);
            remainder -= 16;
        }
        
        if (remainder != 0) {
            byte[] remains = new byte[remainder];
            bais.read(remains);
            bos.write(remains);
        }
        
        bos.flush();
        bos.close();
    }
}


IndexEntry.java:
/*
 * @(#)IndexEntry.java  2007/05/15
 * Written by rednaxela / FX
 */

public class IndexEntry {
    
    static final int ENTRY_LENGTH = 16;
    static final int MAX_FILENAME_LENGTH = 256;
    static final int OFFSET_OFS = 0;
    static final int LENGTH_OFS = 4;
    static final int FLAGS_OFS = 8;
    static final int ORIGLENGTH_OFS = 12;
    
    private String filename;
    private int length;
    private int offset;
    private int flags; // NOT_MSK = 1, LZSS_MSK = 2
    private int origLength;
    
    /**
     * @return the filename
     */
    public String getFilename() {
        return filename;
    }
    
    /**
     * @param filename the filename to set
     */
    public void setFilename(String filename) {
        this.filename = filename;
    }
    
    /**
     * @return the length
     */
    public int getLength() {
        return length;
    }
    
    /**
     * @param length the length to set
     */
    public void setLength(int length) {
        this.length = length;
    }
    
    /**
     * @return the offset
     */
    public int getOffset() {
        return offset;
    }
    
    /**
     * @param offset the offset to set
     */
    public void setOffset(int offset) {
        this.offset = offset;
    }
    
    /**
     * @return the offset
     */
    public int getFlags() {
        return offset;
    }
    
    /**
     * @param offset the offset to set
     */
    public void setFlags(int flags) {
        this.flags = flags;
    }
    
    /**
     * @return the compressedLength
     */
    public int getOrigLength() {
        return origLength;
    }
    
    /**
     * @param offset the compressedLength to set
     */
    public void setOrigLength(int origLength) {
        this.origLength = origLength;
    }
}


LZSS.java:
/*
 * @(#)LZSS.java  2007/05/15
 * Written by rednaxela / FX
 */

public class LZSS {
    
    static final int WINDOW_LENGTH = 4096;
    
    public static byte[] decompress(byte[] from, int compLen, byte[] to, int origLen) {
        return decompress(from, compLen, to, 0, origLen);
    }
    
    public static byte[] decompress(byte[] from, int compLen, byte[] to, int pos, int origLen) {
        if (to == null) to = new byte[origLen];
        
        byte[] window = new byte[WINDOW_LENGTH];
        int readOffset = 0;
        int writeOffset = pos;
        int marker = 0; // read marker, 8-bits, 1 for raw byte, 0 for back ref
        int windowWriteOffset = 0x0FEE;
        int windowReadOffset = 0;
        int backRefLength = 0;
        int current = 0;
                
        while (readOffset != from.length) {
            marker >>= 1;
            
            if ((marker & 0x0100) == 0) {
                current = from[readOffset++] & 0x0FF;
                marker = 0x0FF00 | current;
            }
            
            if(readOffset == from.length) break;
            if ((marker & 0x01) == 1) { // copy raw bytes
                current = from[readOffset++] & 0x0FF;
                to[writeOffset++] = (byte)current;
                window[windowWriteOffset++] = (byte)current;
                windowWriteOffset &= 0x0FFF;
            } else { // copy from slide window
                windowReadOffset = from[readOffset++] & 0x0FF;
                if(readOffset == from.length) break;
                current = from[readOffset++] & 0x0FF;
                windowReadOffset |= (current & 0x0F0) << 4;
                backRefLength = (current & 0x0F) + 2;
                if (backRefLength < 0) continue;
                
                int addOffset = 0;
                while (addOffset <= backRefLength) {
                    int curOfs = (windowReadOffset + addOffset++) & 0x0FFF;
                    current = window[curOfs] & 0x0FF;
                    windowReadOffset &= 0x0FFF;
                    to[writeOffset++] = (byte)current;
                    window[windowWriteOffset++] = (byte)current;
                    windowWriteOffset &= 0x0FFF;
                } // while
            } // if-else    
        } // while
        
        return to;
    }
}


Binconverter.java:
/*
 * @(#)BinConverter.java
 */

/**
 * Some helper routines for data conversion, all data is treated in network
 * byte order.
 */
public class BinConverter
{
    /**
     * Gets bytes from an array into an integer, in big-endian.
     * @param buf where to get the bytes
     * @param ofs index from where to read the data
     * @return the 32bit integer
     */
    public final static int byteArrayToIntBE(
        byte[] buf,
        int ofs)
    {
        return (buf[ofs    ]          << 24)
            | ((buf[ofs + 1] & 0x0ff) << 16)
            | ((buf[ofs + 2] & 0x0ff) <<  8)
            | ( buf[ofs + 3] & 0x0ff);
    }
    
    /**
     * Gets bytes from an array into an integer, in little-endian.
     * @param buf where to get the bytes
     * @param ofs index from where to read the data
     * @return the 32bit integer
     */
    public final static int byteArrayToIntLE(
        byte[] buf,
        int ofs)
    {
        return (buf[ofs + 3]          << 24)
            | ((buf[ofs + 2] & 0x0ff) << 16)
            | ((buf[ofs + 1] & 0x0ff) <<  8)
            | ( buf[ofs    ] & 0x0ff);
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts an integer to bytes in big-endian, which are put into an array.
     * @param value the 32bit integer to convert
     * @param buf the target buf
     * @param ofs where to place the bytes in the buf
     */
    public final static void intToByteArrayBE(
        int value,
        byte[] buf,
        int ofs)
    {
        buf[ofs    ] = (byte)((value >>> 24) & 0x0ff);
        buf[ofs + 1] = (byte)((value >>> 16) & 0x0ff);
        buf[ofs + 2] = (byte)((value >>>  8) & 0x0ff);
        buf[ofs + 3] = (byte)  value;
    }
    
    /**
     * Converts an integer to bytes in little-endian, which are put into an array.
     * @param value the 32bit integer to convert
     * @param buf the target buf
     * @param ofs where to place the bytes in the buf
     */
    public final static void intToByteArrayLE(
        int value,
        byte[] buf,
        int ofs)
    {
        buf[ofs + 3] = (byte)((value >>> 24) & 0x0ff);
        buf[ofs + 2] = (byte)((value >>> 16) & 0x0ff);
        buf[ofs + 1] = (byte)((value >>>  8) & 0x0ff);
        buf[ofs    ] = (byte)  value;
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Gets bytes from an array into a long.
     * @param buf where to get the bytes
     * @param ofs index from where to read the data
     * @return the 64bit integer
     */
    public final static long byteArrayToLong(
        byte[] buf,
        int ofs)
    {
        // Looks more complex - but it is faster (at least on 32bit platforms).

        return
            ((long)(( buf[ofs    ]          << 24) |
                    ((buf[ofs + 1] & 0x0ff) << 16) |
                    ((buf[ofs + 2] & 0x0ff) <<  8) |
                    ( buf[ofs + 3] & 0x0ff       )) << 32) |
            ((long)(( buf[ofs + 4]          << 24) |
                    ((buf[ofs + 5] & 0x0ff) << 16) |
                    ((buf[ofs + 6] & 0x0ff) <<  8) |
                    ( buf[ofs + 7] & 0x0ff       )) & 0x0ffffffffL);
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts a long to bytes, which are put into an array.
     * @param value the 64bit integer to convert
     * @param buf the target buf
     * @param ofs where to place the bytes in the buf
     */
    public final static void longToByteArray(
        long value,
        byte[] buf,
        int ofs)
    {
        int tmp = (int)(value >>> 32);

        buf[ofs    ] = (byte) (tmp >>> 24);
        buf[ofs + 1] = (byte)((tmp >>> 16) & 0x0ff);
        buf[ofs + 2] = (byte)((tmp >>>  8) & 0x0ff);
        buf[ofs + 3] = (byte)  tmp;

        tmp = (int)value;

        buf[ofs + 4] = (byte) (tmp >>> 24);
        buf[ofs + 5] = (byte)((tmp >>> 16) & 0x0ff);
        buf[ofs + 6] = (byte)((tmp >>>  8) & 0x0ff);
        buf[ofs + 7] = (byte)  tmp;
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts values from an integer array to a long.
     * @param buf where to get the bytes
     * @param ofs index from where to read the data
     * @return the 64bit integer
     */
    public final static long intArrayToLong(
        int[] buf,
        int ofs)
    {
        return (((long) buf[ofs    ]) << 32) |
               (((long) buf[ofs + 1]) & 0x0ffffffffL);
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts a long to integers which are put into an array.
     * @param value the 64bit integer to convert
     * @param buf the target buf
     * @param ofs where to place the bytes in the buf
     */
    public final static void longToIntArray(
        long value,
        int[] buf,
        int ofs)
    {
        buf[ofs    ] = (int)(value >>> 32);
        buf[ofs + 1] = (int) value;
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Makes a long from two integers (treated unsigned).
     * @param lo lower 32bits
     * @param hi higher 32bits
     * @return the built long
     */
    public final static long makeLong(
        int lo,
        int hi)
    {
        return (((long) hi << 32) |
                ((long) lo & 0x00000000ffffffffL));
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Gets the lower 32 bits of a long.
     * @param val the long integer
     * @return lower 32 bits
     */
    public final static int longLo32(
        long val)
    {
        return (int)val;
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Gets the higher 32 bits of a long.
     * @param val the long integer
     * @return higher 32 bits
     */
    public final static int longHi32(
        long val)
    {
        return (int)(val >>> 32);
    }

    ///////////////////////////////////////////////////////////////////////////

    // our table for hex conversion
    final static char[] HEXTAB =
    {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
    };

    /**
     * Converts a byte array to a hex string.
     * @param data the byte array
     * @return the hex string
     */
    public final static String bytesToHexStr(
        byte[] data)
    {
        return bytesToHexStr(data, 0, data.length);
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts a byte array to a hex string.
     * @param data the byte array
     * @param ofs start index where to get the bytes
     * @param len number of bytes to convert
     * @return the hex string
     */
    public final static String bytesToHexStr(
        byte[] data,
        int ofs,
        int len)
    {
        int pos, c;
        StringBuffer sbuf;


        sbuf = new StringBuffer();
        sbuf.setLength(len << 1);

        pos = 0;
        c = ofs + len;

        while (ofs < c)
        {
            sbuf.setCharAt(pos++, HEXTAB[(data[ofs  ] >> 4) & 0x0f]);
            sbuf.setCharAt(pos++, HEXTAB[ data[ofs++]       & 0x0f]);
        }
        return sbuf.toString();
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts a hex string back into a byte array (invalid codes will be
     * skipped).
     * @param hex hex string
     * @param data the target array
     * @param srcofs from which character in the string the conversion should
     * begin, remember that (nSrcPos modulo 2) should equals 0 normally
     * @param dstofs to store the bytes from which position in the array
     * @param len number of bytes to extract
     * @return number of extracted bytes
     */
    public final static int hexStrToBytes(
        String hex,
        byte[] data,
        int srcofs,
        int dstofs,
        int len)
    {
        int i, j, strlen, avail_bytes, dstofs_bak;
        byte abyte;
        boolean convertOK;


        // check for correct ranges
        strlen = hex.length();

        avail_bytes = (strlen - srcofs) >> 1;
        if (avail_bytes < len)
        {
            len = avail_bytes;
        }

        int nOutputCapacity = data.length - dstofs;
        if (len > nOutputCapacity)
        {
            len = nOutputCapacity;
        }

        // convert now

        dstofs_bak = dstofs;

        for (i = 0; i < len; i++)
        {
            abyte = 0;
            convertOK = true;

            for (j = 0; j < 2; j++)
            {
                abyte <<= 4;
                char cActChar = hex.charAt(srcofs++);

                if ((cActChar >= 'a') && (cActChar <= 'f'))
                {
                    abyte |= (byte) (cActChar - 'a') + 10;
                }
                else
                {
                    if ((cActChar >= '0') && (cActChar <= '9'))
                    {
                        abyte |= (byte) (cActChar - '0');
                    }
                    else
                    {
                        convertOK = false;
                    }
                }
            }
            if (convertOK)
            {
                data[dstofs++] = abyte;
            }
        }

        return (dstofs - dstofs_bak);
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Converts a byte array into a Unicode string.
     * @param data the byte array
     * @param ofs where to begin the conversion
     * @param len number of bytes to handle
     * @return the string
     */
    public final static String byteArrayToStr(
        byte[] data,
        int ofs,
        int len)
    {
        int avail_capacity, sbuf_pos;
        StringBuffer sbuf;


        // we need two bytes for every character
        len &= ~1;

        // enough bytes in the buf?
        avail_capacity = data.length - ofs;

        if (avail_capacity < len)
        {
            len = avail_capacity;
        }

        sbuf = new StringBuffer();
        sbuf.setLength(len >> 1);

        sbuf_pos = 0;

        while (0 < len)
        {
            sbuf.setCharAt(
                sbuf_pos++,
                (char)((data[ofs    ] << 8)
                    |  (data[ofs + 1] & 0x0ff)));
            ofs += 2;
            len -= 2;
        }

        return sbuf.toString();
    }
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics