`
RednaxelaFX
  • 浏览: 3052561 次
  • 性别: 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();
    }
}
分享到:
评论

相关推荐

    ARC文件解压缩工具PKXARC

    然而,通过特定的工具,比如"PKXARC",仍然可以对ARC文件进行解压缩。 "PKXARC"是一个专门用于处理ARC文件的工具,它允许用户打开、查看和提取ARC压缩包中的内容。这个程序的使用至关重要,因为没有它,那些包含ARC...

    Lost.Planet.ARC.Exctactor

    1. 安装:解压缩下载的"Lost.Planet.ARC.Exctactor.exe"文件,运行执行程序。 2. 打开ARC文件:通过软件界面选择要操作的ARC文件,进行加载。 3. 浏览与提取:工具会显示ARC文件内的所有内容,用户可以选择需要的...

    尝试在窗体上画一张笑脸.Arc,Area(JAVA语言实现)

    尝试在窗体上画一张笑脸.Arc,Area 共有两种不同图案

    arc是什么文件格式?arc文件怎么打开?.docx

    对于压缩文件类型,`.arc`可以通过专业的解压缩软件如`Freearc`进行处理。理解`.arc`文件的性质和如何打开它,有助于你在遇到这类文件时能够有效地管理和使用其中的数据。无论是游戏资源还是其他类型的压缩数据,...

    noki查看诺基亚backup.arc还原失败

    `.arc`文件是诺基亚Backup应用程序创建的一种压缩格式,它包含了用户的联系人、短信、通话记录、设置等个人信息。这种格式的备份文件旨在方便用户在更换设备或系统恢复时快速恢复数据。然而,由于文件损坏、不兼容或...

    SINUCOM-ARC.rar

    标题"SINUCOM-ARC.rar"表明这是一份与Siemens数控系统相关的压缩文件,其扩展名".rar"是常见的压缩文件格式,通常用于存储多个文件或文件夹以方便传输和存储。描述中提到".arc"格式的文档,这可能是指Siemens数控...

    package:打包.arc应用程序以通过Cloudformation进行部署

    @architect/package ...// fake out an .arc file as a string let arcString = ` @app mybasicapp @http get / ` // parse .arc string into a plain javascript object let arc = parse ( arcString ) // expo

    Arc2D.Float定义float 精度、两端点到圆心闭合的弧

    ### Arc2D.Float 类在Java中的应用 #### 一、Arc2D.Float 概述 在Java图形绘制中,`Arc2D.Float`是`Arc2D`类的一个子类,用于表示用浮点精度定义的弧形。该类允许开发者以较高的精度创建并操作二维图形中的弧线。...

    ensembleUnpacker:用于提取集合* .arc文件的解包器

    2. **解压缩数据**:根据头部信息,解包器选择合适的解压缩算法(如DEFLATE、LZMA等)来解压文件内容。 3. **解析内部结构**:arc文件可能包含复杂的内部结构,如索引、目录或者特定的游戏资源格式,解包器需要理解...

    ArcEngine开发集锦

    2. 用ArcEngine的工具条添加图层要素 2 3. ArcEngine中对Feature的编辑 5 4. Feature的概念 6 5. 如何实现经度纬度到平面坐标的相互转换?(转载) 7 6. ArcEngine 最短路径分析(源码) 9 7. ArcEngine中使用上下...

    ArcEngine for java开发demo源码(个人开发)

    ArcEngine是Esri公司推出的用于构建地理信息系统(GIS)应用程序的强大开发工具,它提供了丰富的API,允许开发者使用多种编程语言,包括Java,来创建具有地图显示、空间分析等功能的应用。在这个名为“ArcEngine for...

    common.arc

    这是关于fpga架构文件中的common文件,如有需要请下载

    ae(ArcEngine) java swing开发入门系列 代码

    在IT行业中,ArcEngine是Esri公司开发的一款强大的地理信息系统(GIS)开发组件,它允许开发者利用Java、.NET等多种编程语言构建具有地图显示、空间分析、数据管理等功能的应用程序。ArcGIS是Esri的核心产品,它包含...

    ArcEngine示例程序

    《ArcEngine示例程序:深度探索GIS开发技术》 ArcEngine是Esri公司推出的一款强大的地理信息系统(GIS)开发工具,它允许开发者利用C#.NET等编程语言构建桌面、Web和移动应用程序,实现对地理数据的高效管理和分析...

    ArcEngine_java_plugin

    ArcEngine_java_plugin是一个专为开发者设计的工具,主要用于调试基于Java的ArcEngine程序。ArcEngine是Esri公司提供的一个强大的地理信息系统(GIS)开发平台,它允许开发人员创建桌面、服务器以及Web应用程序,以...

    bgd.arc

    bgd.arc

    java+Arcengine简单的地图操作

    在IT行业中,Java和ArcEngine的结合是一种常见的技术实践,特别是在地理信息系统(GIS)开发领域。ArcEngine是由Esri公司提供的一个强大的开发平台,用于构建地理空间应用。它提供了丰富的API,可以与多种编程语言...

    SQL反模式.arc

    使用freearc解压

    程序实现用java编写的基于Arcengine的gis中图形浏览编辑功能

    本项目标题提及的是“程序实现用Java编写的基于ArcEngine的GIS中图形浏览编辑功能”,这暗示我们将讨论如何利用Java语言与ArcEngine API进行交互,实现地图的查看和编辑功能。 1. **ArcEngine简介** ArcEngine是...

    使用WinRar打包ArcEngine程序

    ### 使用WinRar打包ArcEngine程序 在IT领域中,软件开发完成后往往需要进行打包处理以便于分发或部署到目标环境中。对于基于ArcEngine的应用程序来说,使用WinRAR进行打包是一个常见且实用的方法。本文将详细介绍...

Global site tag (gtag.js) - Google Analytics