- 浏览: 85973 次
- 性别:
- 来自: 南京
最新评论
-
qian_xin:
...
quoted-printable 编码与解码 -
maoxy:
请问楼主,怎么设置应用程序的字体样式为arial,您介绍的是在 ...
Android改变字体方法——Typeface -
稻-草:
你这个(1) 字符串反转 递归方法字符串长一点,就Outofm ...
某外企面试总结 -
ai4life:
其实对于这种应用层协议的发展还是在于大型企业的推动啊
DLNA中的UPnP技术浅析
序言:
由于前些时间,一些matrixer常问关于j2me中使用Pak文件的问题。本人虽学艺不深,但满怀热心的做了一番探索,现将制作Pak文件的看法和方法公布出来,大家多多提意见。
一、什么是Pak文件:
Pak文件就是将多个文件打包为一个单独文件,在这个文件中保存着多个文件的数据,当然还有一些描述文件结构的数据。所以将“Pak”作为文件的后缀是一种常规的用法,大家可以自定义其它的文件后缀。
二、为什么使用Pak文件:
由于MIDP对发布安装的j2me程序大小进行了限制,所以缩小发布程序就意味着能够提供更多的程序或者内容(如图片、音乐)给用户。而通过研究发现zip/jar算法对大文件的压缩率高于对等量的多个小文件的压缩率。
当然还有其它方法,这里简单做一下讨论比如使用混淆器ProGuard的“-overloadaggressively”选项使jar文件缩小,但也会 导致一些错误,因为这种方法生成jar中的class符合java byte code标准,但是与java语法相悖,严重的可能造成一些jre对Object的序列化错误。
所以使用Pak方法将程序中要用 到的资源(图片、音乐、文本)组合为单一文件是一个安全有效的方法。而且对于一些商用程序,完全可以在pak文件中对文件数据进行加密,很好的保护了作者 和公司的权益。本人的sample中使用了简单的“加减法”加密,对于手机这类设备来讲是一个效率较高的选择。
三、Pak文件的结构:
大家可以自己设计Pak文件结构,本人这里只是抛砖引玉的作个sample。下面就是本人设计的Pak文件结构:
PAK File Header:Pak文件的头部
* 签名:6字节char数组 * 版本号:32位float * 文件table数量:32位整数 * 密码行为:8位字节 * 密码:8位字节 * 文件唯一ID:10字节char数组 * 保留位:32位整数(4字节)
File Table:Pak文件中包含文件的列表,在一个Pak文件中一个被包含的文件对应一个File Table。
* 文件名:30字节char数组 * 文件大小:32位整型 * 文件在pak文件中的位移:32位整数
Concatenated File Data:按File Table的顺序连接在一起的文件数据。
* 文件数据
四、程序框架:
说明:由于Pak文件的制作和使用分别要使用两个java应用领域:j2se和j2me,所以本人将PakUtil类制作了2个版本(j2se和j2me)。
程序框架如下:
1。PakHeader类,定义了Pak文件头。
2。PakFileTable类,定义Pak文件table。
3。PakUtil类(j2se版),具备两个功能:将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件。
PakUtil类(j2me版),具备的功能:从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)。
五、PakHeader和PakFileTable类:
PakHeader.java:
package cn.org.matrix.gmatrix.gameLab.util.pak;/** * Pak文件头: * 结构: *
签名:6字节char数组 * 版本号:32位float *
文件table数量:32位整数 *
密码行为:8位字节 * 密码:8位字节 *
文件唯一ID:10字节char数组 *
保留位:32位整数(4字节) * @author cleverpig * */class PakHeader {
//定义文件唯一ID长度
public static final int UNIQUEID_LENGTH=10;
//定义文件签名长度
public static final int SIGNATURE_LENGTH=6;
//定义加法运算
public static final int ADDITION_CIPHERACTION=0;
//定义减法运算
public static final int SUBTRACT_CIHOERACTION=1;
//文件签名
private char[] signature=new char[SIGNATURE_LENGTH];
//版本号
private float version=0f;
//文件table数量
private long numFileTableEntries=0;
//密码使用方法:在原数据上进行加法还是减法
private byte cipherAction=ADDITION_CIPHERACTION;
//密码值
private byte cipherValue=0x00;
//唯一ID
private char[] uniqueID=new char[UNIQUEID_LENGTH];
//保留的4字节
private long reserved=0;
public PakHeader(){
}
/**
* 构造方法
* @param signature 签名
* @param version 版本
* @param numFileTableEntries 文件table数量
* @param cipherAction 密码使用方法
* @param cipherValue 密码值
* @param uniqueID 唯一ID
* @param reserved 保留的2字节
*/
public PakHeader(char[] signature,float version,
long numFileTableEntries,byte cipherAction,
byte cipherValue,char[] uniqueID,long reserved){
for(int i=0;i<SIGNATURE_LENGTH;this.signature[i]=signature[i],i++)
;
this.version=version;
this.cipherAction=cipherAction;
this.numFileTableEntries=numFileTableEntries;
this.cipherValue=cipherValue;
for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++);
this.reserved=reserved;
}
public byte getCipherValue() {
return cipherValue;
}
public void setCipherValue(byte cipherValue) {
this.cipherValue = cipherValue;
}
public long getNumFileTableEntries() {
return numFileTableEntries;
}
public void setNumFileTableEntries(long numFileTableEntries) {
this.numFileTableEntries = numFileTableEntries;
}
public long getReserved() {
return reserved;
}
public void setReserved(long reserved) {
this.reserved = reserved;
}
public char[] getUniqueID() {
return uniqueID;
}
public void setUniqueID(char[] uniqueID) {
for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++)
;
}
public float getVersion() {
return version;
}
public void setVersion(float version) {
this.version = version;
}
public byte getCipherAction() {
return cipherAction;
}
public void setCipherAction(byte cipherAction) {
this.cipherAction = cipherAction;
}
public char[] getSignature() {
return signature;
}
public void setSignature(char[] signature) {
for(int i=0;i<SIGNATURE_LENGTH;this.signature[i] = signature[i],i++)
;
}
/**
* 返回PakHeader的大小
* @return 返回PakHeader的大小
*/
public static int size(){
return SIGNATURE_LENGTH+4+4+1+1+UNIQUEID_LENGTH+4;
}
public String toString(){
String result="";
result+="\t签名:"+new String(this.signature).trim()
+"\t版本号:"+this.version
+"\t文件table数量:"+this.numFileTableEntries
+"\t密码行为:" +this.cipherAction
+"\t密码:"+this.cipherValue
+"\t文件唯一ID:"+new String(this.uniqueID).trim() +"\t保留位:"+this.reserved;
return result;
}}
PakFileTable.java
package cn.org.matrix.gmatrix.gameLab.util.pak;/** * Pak文件table类 * 文件table结构: *
文件名:30字节char数组 *
文件大小:32位整型 *
文件在pak文件中的位移:32位整数 * @author cleverpig * */class PakFileTable {
public static final int FILENAME_LENGTH=30;
//文件名
private char[] fileName=new char[FILENAME_LENGTH];
//文件大小
private long fileSize=0L;
//文件在pak文件中的位移
private long offSet=0L;
public PakFileTable(){
}
/**
* 构造方法
* @param fileName 文件名
* @param fileSize 文件大小
* @param offSet 文件在Pak文件中的位移
*/
public PakFileTable(char[] fileName,
long fileSize,long offSet){
for(int i=0;i<FILENAME_LENGTH;this.fileName[i]=fileName[i],i++)
; this.fileSize=fileSize;
this.offSet=offSet;
}
public char[] getFileName() {
return fileName;
}
public void setFileName(char[] fileName) {
for(int i=0;i<fileName.length;this.fileName[i]=fileName[i],i++)
;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public long getOffSet() {
return offSet;
}
public void setOffSet(long offSet) {
this.offSet = offSet;
}
/**
* 返回文件Table的大小
* @return 返回文件Table的大小
*/
public static int size(){
return FILENAME_LENGTH+4+4;
}
public String toString(){
return "\t文件名:"+new String(this.fileName).trim()
+"\t文件大小:"+this.fileSize
+"\t文件位移:"+this.offSet;
}}
六、PakUtil类(j2se版):
PakUtil.java
package cn.org.matrix.gmatrix.gameLab.util.pak;import java.io.*;
import java.util.Vector;
/** * Pak工具类 * 功能:
*1.将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;
* 2.从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件 * @author cleverpig * */public class PakUtil {
public PakUtil(){
}
/**
* 返回文件长度
* @param filePath 文件路径
* @return 文件长度
*/
private long getFileSize(String filePath){
File file=new File(filePath);
return file.length();
}
/**
* 返回文件名
* @param filePath 文件路径
* @return 文件名
*/
private String getFileName(String filePath){
File file=new File(filePath);
return file.getName();
}
/**
* 计算文件位移的起始点
* @return 文件位移的起始点
*/
private long workOutOffsetStart(PakHeader header){
//计算出文件头+文件table的长度
return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
}
/**
* 计算文件位移
* @param fileIndex 文件序号
* @param lastFileOffset 上一个文件位移
* @return 文件在pak文件中的位移
*/
private long workOutNextOffset(long sourceFileSize,long lastFileOffset){
return lastFileOffset+sourceFileSize;
}
/**
* 生成文件table
* @param sourceFileName 源文件名
* @param sourceFileSize 源文件长度
* @param currentFileOffset 当前文件位移
* @return 生成的PakFileTable对象
*/
private PakFileTable generateFileTable(String sourceFileName,
long sourceFileSize,long currentFileOffset){
PakFileTable ft=new PakFileTable();
ft.setFileName(sourceFileName.toCharArray());
ft.setFileSize(sourceFileSize);
ft.setOffSet(currentFileOffset);
return ft;
}
/**
* 将char字符数组写入到DataOutputStream中
* @param toWriteCharArray 被写入的char数组
* @param dos DataOutputStream
* @throws Exception
*/
private void writeCharArray(char[] toWriteCharArray,DataOutputStream dos) throws Exception{
for(int i=0;i<toWriteCharArray.length;dos.writeChar(toWriteCharArray[i]),i++);
}
/**
* 使用文件头中的密码对数据进行加密
* @param buff 被加密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void encryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]+=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]-=header.getCipherValue();
break;
}
}
}
/**
* 使用文件头中的密码对数据进行解密
* @param buff 被解密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]-=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]+=header.getCipherValue();
break;
}
}
}
/**
* 制作Pak文件
* @param sourceFilePath 源文件路径数组
* @param destinateFilePath 目的文件路径(Pak文件)
* @param cipherAction 密码行为
* @param cipherValue 密码
* @throws Exception
*/
public void makePakFile(String[] sourceFilePath,
String destinateFilePath,PakHeader header) throws Exception{
PakFileTable[] fileTable=new PakFileTable[sourceFilePath.length];
//计算文件位移起始点
long fileOffset=workOutOffsetStart(header);
//逐个建立文件table
for(int i=0;i<sourceFilePath.length;i++){
String sourceFileName=getFileName(sourceFilePath[i]);
long sourceFileSize=getFileSize(sourceFilePath[i]);
PakFileTable ft=generateFileTable(sourceFileName,sourceFileSize,fileOffset);
//计算下一个文件位移
fileOffset=workOutNextOffset(sourceFileSize,fileOffset);
fileTable[i]=ft;
}
//写入文件头
File wFile=new File(destinateFilePath);
FileOutputStream fos=new FileOutputStream(wFile);
DataOutputStream dos=new DataOutputStream(fos);
writeCharArray(header.getSignature(),dos);
dos.writeFloat(header.getVersion());
dos.writeLong(header.getNumFileTableEntries());
dos.writeByte(header.getCipherAction());
dos.writeByte(header.getCipherValue());
writeCharArray(header.getUniqueID(),dos);
dos.writeLong(header.getReserved());
//写入文件table
for(int i=0;i<fileTable.length;i++){
writeCharArray(fileTable[i].getFileName(),dos);
dos.writeLong(fileTable[i].getFileSize());
dos.writeLong(fileTable[i].getOffSet());
}
//写入文件数据
for(int i=0;i<fileTable.length;i++){
File ftFile=new File(sourceFilePath[i]);
FileInputStream ftFis=new FileInputStream(ftFile);
DataInputStream ftDis=new DataInputStream(ftFis);
byte[] buff=new byte[256];
int readLength=0;
while((readLength=ftDis.read(buff))!=-1){
encryptBuff(buff,readLength,header);
dos.write(buff,0,readLength);
}
ftDis.close();
ftFis.close();
}
dos.close();
}
/**
* 从DataInputStream读取char数组 * @param dis DataInputStream
* @param readLength 读取长度
* @return char数组
* @throws Exception
*/
private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
char[] readCharArray=new char[readLength];
for(int i=0;i<readLength;i++){
readCharArray[i]=dis.readChar();
}
return readCharArray;
}
/**
* 从PAK文件中读取文件头
* @param dis DataInputStream
* @return PakHeader
* @throws Exception
*/
private PakHeader readHeader(DataInputStream dis) throws Exception{
PakHeader header=new PakHeader();
char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
header.setSignature(signature);
header.setVersion(dis.readFloat());
header.setNumFileTableEntries(dis.readLong());
header.setCipherAction(dis.readByte());
header.setCipherValue(dis.readByte());
char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
header.setUniqueID(uniqueID);
header.setReserved(dis.readLong());
return header;
}
/**
* 读取所有的文件table
* @param dis DataInputStream
* @param fileTableNumber 文件表总数
* @return 文件table数组
* @throws Exception
*/
private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
for(int i=0;i<fileTableNumber;i++){
PakFileTable ft=new PakFileTable();
ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
ft.setFileSize(dis.readLong());
ft.setOffSet(dis.readLong());
fileTable[i]=ft;
}
return fileTable;
}
/**
* 从pak文件读取文件到byte数组
* @param dis DataInputStream
* @param fileTable PakFileTable
* @return byte数组
* @throws Exception
*/
private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
//
int fileLength=(int)fileTable.getFileSize();
byte[] fileBuff=new byte[fileLength];
int readLength=dis.read(fileBuff,0,fileLength);
if (readLength<fileLength){
System.out.println("读取数据长度不正确");
return null;
}
else{
decryptBuff(fileBuff,readLength,header);
return fileBuff;
}
}
/**
* 将buffer中的内容写入到文件
* @param fileBuff 保存文件内容的buffer
* @param fileName 文件名
* @param extractDir 文件导出目录
* @throws Exception
*/
private void writeFileFromByteBuffer(byte[] fileBuff,String fileName,String extractDir) throws Exception{
String extractFilePath=extractDir+fileName;
File wFile=new File(extractFilePath);
FileOutputStream fos=new FileOutputStream(wFile);
DataOutputStream dos=new DataOutputStream(fos);
dos.write(fileBuff);
dos.close();
fos.close();
}
/**
* 从pak文件中取出指定的文件到byte数组,如果需要的话可以将byte数组写为文件
* @param pakFilePath pak文件路径
* @param extractFileName pak文件中将要被取出的文件名
* @param writeFile 是否需要将byte数组写为文件
* @param extractDir 如果需要的话可以将byte数组写为文件,extractDir为取出数据被写的目录文件
* @return byte数组
* @throws Exception
*/
public byte[] extractFileFromPak(String pakFilePath,
String extractFileName,boolean writeFile,String extractDir) throws Exception{
File rFile=new File(pakFilePath);
FileInputStream fis=new FileInputStream(rFile);
DataInputStream dis=new DataInputStream(fis);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());
boolean find=false; int fileIndex=0;
for(int i=0;i<fileTable.length;i++){
String fileName=new String(fileTable[i].getFileName()).trim();
if (fileName.equals(extractFileName)){
find=true;
fileIndex=i;
break;
}
}
if (find==false){
System.out.println("没有找到指定的文件");
return null;
}
else{
byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
if (writeFile){
writeFileFromByteBuffer(buff,extractFileName,extractDir);
}
else{
dis.close();
fis.close();
}
return buff;
}
}
/**
* 从pak文件中取出指定的Pak文件的信息
* @param pakFilePath pak文件路径
* @return 装载文件头和文件table数组的Vector
* @throws Exception
*/
public Vector showPakFileInfo(String pakFilePath) throws Exception{
File rFile=new File(pakFilePath);
FileInputStream fis=new FileInputStream(rFile);
DataInputStream dis=new DataInputStream(fis);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries()); Vector result=new Vector();
result.add(header);
result.add(fileTable);
return result;
}
public static void main(String[] argv) throws Exception{
PakUtil pu=new PakUtil();
//构造文件头
char[] signature=new char[PakHeader.SIGNATURE_LENGTH];
signature=new String("012345").toCharArray();
char[] uniqueID=new char[PakHeader.UNIQUEID_LENGTH];
uniqueID=new String("0123456789").toCharArray();
PakHeader header=new PakHeader();
header.setSignature(signature);
header.setNumFileTableEntries(3);
header.setCipherAction((byte)PakHeader.ADDITION_CIPHERACTION);
header.setCipherValue((byte)0x0f);
header.setUniqueID(uniqueID);
header.setVersion(1.0f);
header.setReserved(0L);
String[] filePathArray={"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\apple.png",
"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\cushaw.png",
"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\flash.png"};
String extractFilePath="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\test.pak";
//制作Pak文件
System.out.println("制作Pak文件...");
pu.makePakFile(filePathArray,extractFilePath,header);
System.out.println("制作Pak文件完成");
//从Pak文件中取出所有的图片文件
Vector pakInfo=pu.showPakFileInfo(extractFilePath);
header=(PakHeader)pakInfo.elementAt(0);
System.out.println("Pak文件信息:");
System.out.println("文件头:");
System.out.println(header);
PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
for(int i=0;i<fileTable.length;i++){
System.out.println("文件table["+i+"]:");
System.out.println(fileTable[i]);
}
String restoreDir="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\extract\\";
String restoreFileName=null;
byte[] fileBuff=null;
for(int i=0;i<fileTable.length;i++){
restoreFileName=new String(fileTable[i].getFileName()).trim();
System.out.println("从Pak文件中取出"+restoreFileName+"文件...");
fileBuff=pu.extractFileFromPak(extractFilePath,restoreFileName,true,restoreDir);
System.out.println("从Pak文件中取出"+restoreFileName+"文件保存在"+restoreDir+"目录");
}
}}
七、PakUtil类(j2me版):
PakUtil.java
package cn.org.matrix.gmatrix.gameLab.util.pak;
import java.io.*;
import java.util.Vector;
/** * Pak工具类 * 功能: * 从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象) * @author cleverpig * */public class PakUtil {
public PakUtil(){
}
/**
* 计算文件位移的起始点
* @return 文件位移的起始点
*/
private long workOutOffsetStart(PakHeader header){
//计算出文件头+文件table的长度
return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
}
/**
* 从DataInputStream读取char数组
* @param dis DataInputStream
* @param readLength 读取长度
* @return char数组
* @throws Exception
*/
private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
char[] readCharArray=new char[readLength];
for(int i=0;i<readLength;i++){
readCharArray[i]=dis.readChar();
}
return readCharArray;
}
/**
* 从PAK文件中读取文件头
* @param dis DataInputStream
* @return PakHeader
* @throws Exception
*/
private PakHeader readHeader(DataInputStream dis) throws Exception{
PakHeader header=new PakHeader();
char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
header.setSignature(signature);
header.setVersion(dis.readFloat());
header.setNumFileTableEntries(dis.readLong());
header.setCipherAction(dis.readByte());
header.setCipherValue(dis.readByte());
char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
header.setUniqueID(uniqueID);
header.setReserved(dis.readLong());
return header;
}
/**
* 读取所有的文件table
* @param dis DataInputStream
* @param fileTableNumber 文件表总数
* @return 文件table数组
* @throws Exception
*/
private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
for(int i=0;i<fileTableNumber;i++){
PakFileTable ft=new PakFileTable();
ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
ft.setFileSize(dis.readLong());
ft.setOffSet(dis.readLong());
fileTable[i]=ft;
}
return fileTable;
}
/**
* 从pak文件读取文件到byte数组
* @param dis DataInputStream
* @param fileTable PakFileTable
* @return byte数组
* @throws Exception
*/
private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
//
int fileLength=(int)fileTable.getFileSize();
byte[] fileBuff=new byte[fileLength];
int readLength=dis.read(fileBuff,0,fileLength);
if (readLength<fileLength){
System.out.println("读取数据长度不正确");
return null;
}
else{
decryptBuff(fileBuff,readLength,header);
}
return fileBuff;
}
/**
* 使用文件头中的密码对数据进行解密
* @param buff 被解密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]-=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]+=header.getCipherValue();
break;
}
}
}
/**
* 从pak文件中取出指定的文件到byte数组
* @param pakResourceURL pak文件的资源路径
* @param extractResourceName pak文件中将要被取出的文件名
* @return byte数组
* @throws Exception
*/
public byte[] extractResourceFromPak(String pakResourceURL
,String extractResourceName) throws Exception{
InputStream is=this.getClass().getResourceAsStream(pakResourceURL);
DataInputStream dis=new DataInputStream(is);
PakHeader header=readHeader(dis);//
System.out.println("文件头:");//
System.out.println(header);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());//
for(int i=0;i<fileTable.length;i++){//
System.out.println("文件table["+i+"]:");//
System.out.println(fileTable[i]);//
}
boolean find=false;
int fileIndex=0;
for(int i=0;i<fileTable.length;i++){
String fileName=new String(fileTable[i].getFileName()).trim();
if (fileName.equals(extractResourceName)){
find=true;
fileIndex=i;
break;
}
}
if (find==false){
System.out.println("没有找到指定的文件");
return null;
}
else{
byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
return buff;
}
}
/**
* 从pak文件中取出指定的Pak文件的信息
* @param pakResourcePath pak文件资源路径
* @return 装载文件头和文件table数组的Vector
* @throws Exception
*/
public Vector showPakFileInfo(String pakResourcePath) throws Exception{
InputStream is=this.getClass().getResourceAsStream(pakResourcePath);
DataInputStream dis=new DataInputStream(is);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());
Vector result=new Vector();
result.addElement(header);
result.addElement(fileTable);
return result;
}
public static void main(String[] argv) throws Exception{
PakUtil pu=new PakUtil();
String extractResourcePath="/test.pak";
//从Pak文件中取出所有的图片文件
Vector pakInfo=pu.showPakFileInfo(extractResourcePath);
PakHeader header=(PakHeader)pakInfo.elementAt(0);
System.out.println("Pak文件信息:");
System.out.println("文件头:");
System.out.println(header);
PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
for(int i=0;i<fileTable.length;i++){
System.out.println("文件table["+i+"]:");
System.out.println(fileTable[i]);
}
String restoreFileName=null;
byte[] fileBuff=null;
for(int i=0;i<fileTable.length;i++){
restoreFileName=new String(fileTable[i].getFileName()).trim();
System.out.println("从Pak文件中取出"+restoreFileName+"文件数据...");
fileBuff=pu.extractResourceFromPak(extractResourcePath,restoreFileName);
System.out.println("从Pak文件中取出"+restoreFileName+"文件数据完成");
}
}}
八、源代码使用简介:
Pak过程:j2se版的PakUtil将testFiles目录中的三个png文件Pak成为test.pak文件。
UnPak过程:j2se版的PakUtil将testFiles目录中test.pak文件释放到testFiles\extract目录下; j2me版的PakUtil从res目录中的test.pak文件读取出其中所包含的3个png文件数据并装入到byte数据,用来构造Image对象, 大家请运行PakUtilTestMIDlet.java便可看到输出的信息。
由于前些时间,一些matrixer常问关于j2me中使用Pak文件的问题。本人虽学艺不深,但满怀热心的做了一番探索,现将制作Pak文件的看法和方法公布出来,大家多多提意见。
一、什么是Pak文件:
Pak文件就是将多个文件打包为一个单独文件,在这个文件中保存着多个文件的数据,当然还有一些描述文件结构的数据。所以将“Pak”作为文件的后缀是一种常规的用法,大家可以自定义其它的文件后缀。
二、为什么使用Pak文件:
由于MIDP对发布安装的j2me程序大小进行了限制,所以缩小发布程序就意味着能够提供更多的程序或者内容(如图片、音乐)给用户。而通过研究发现zip/jar算法对大文件的压缩率高于对等量的多个小文件的压缩率。
当然还有其它方法,这里简单做一下讨论比如使用混淆器ProGuard的“-overloadaggressively”选项使jar文件缩小,但也会 导致一些错误,因为这种方法生成jar中的class符合java byte code标准,但是与java语法相悖,严重的可能造成一些jre对Object的序列化错误。
所以使用Pak方法将程序中要用 到的资源(图片、音乐、文本)组合为单一文件是一个安全有效的方法。而且对于一些商用程序,完全可以在pak文件中对文件数据进行加密,很好的保护了作者 和公司的权益。本人的sample中使用了简单的“加减法”加密,对于手机这类设备来讲是一个效率较高的选择。
三、Pak文件的结构:
大家可以自己设计Pak文件结构,本人这里只是抛砖引玉的作个sample。下面就是本人设计的Pak文件结构:
PAK File Header:Pak文件的头部
* 签名:6字节char数组 * 版本号:32位float * 文件table数量:32位整数 * 密码行为:8位字节 * 密码:8位字节 * 文件唯一ID:10字节char数组 * 保留位:32位整数(4字节)
File Table:Pak文件中包含文件的列表,在一个Pak文件中一个被包含的文件对应一个File Table。
* 文件名:30字节char数组 * 文件大小:32位整型 * 文件在pak文件中的位移:32位整数
Concatenated File Data:按File Table的顺序连接在一起的文件数据。
* 文件数据
四、程序框架:
说明:由于Pak文件的制作和使用分别要使用两个java应用领域:j2se和j2me,所以本人将PakUtil类制作了2个版本(j2se和j2me)。
程序框架如下:
1。PakHeader类,定义了Pak文件头。
2。PakFileTable类,定义Pak文件table。
3。PakUtil类(j2se版),具备两个功能:将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件。
PakUtil类(j2me版),具备的功能:从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)。
五、PakHeader和PakFileTable类:
PakHeader.java:
package cn.org.matrix.gmatrix.gameLab.util.pak;/** * Pak文件头: * 结构: *
签名:6字节char数组 * 版本号:32位float *
文件table数量:32位整数 *
密码行为:8位字节 * 密码:8位字节 *
文件唯一ID:10字节char数组 *
保留位:32位整数(4字节) * @author cleverpig * */class PakHeader {
//定义文件唯一ID长度
public static final int UNIQUEID_LENGTH=10;
//定义文件签名长度
public static final int SIGNATURE_LENGTH=6;
//定义加法运算
public static final int ADDITION_CIPHERACTION=0;
//定义减法运算
public static final int SUBTRACT_CIHOERACTION=1;
//文件签名
private char[] signature=new char[SIGNATURE_LENGTH];
//版本号
private float version=0f;
//文件table数量
private long numFileTableEntries=0;
//密码使用方法:在原数据上进行加法还是减法
private byte cipherAction=ADDITION_CIPHERACTION;
//密码值
private byte cipherValue=0x00;
//唯一ID
private char[] uniqueID=new char[UNIQUEID_LENGTH];
//保留的4字节
private long reserved=0;
public PakHeader(){
}
/**
* 构造方法
* @param signature 签名
* @param version 版本
* @param numFileTableEntries 文件table数量
* @param cipherAction 密码使用方法
* @param cipherValue 密码值
* @param uniqueID 唯一ID
* @param reserved 保留的2字节
*/
public PakHeader(char[] signature,float version,
long numFileTableEntries,byte cipherAction,
byte cipherValue,char[] uniqueID,long reserved){
for(int i=0;i<SIGNATURE_LENGTH;this.signature[i]=signature[i],i++)
;
this.version=version;
this.cipherAction=cipherAction;
this.numFileTableEntries=numFileTableEntries;
this.cipherValue=cipherValue;
for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++);
this.reserved=reserved;
}
public byte getCipherValue() {
return cipherValue;
}
public void setCipherValue(byte cipherValue) {
this.cipherValue = cipherValue;
}
public long getNumFileTableEntries() {
return numFileTableEntries;
}
public void setNumFileTableEntries(long numFileTableEntries) {
this.numFileTableEntries = numFileTableEntries;
}
public long getReserved() {
return reserved;
}
public void setReserved(long reserved) {
this.reserved = reserved;
}
public char[] getUniqueID() {
return uniqueID;
}
public void setUniqueID(char[] uniqueID) {
for(int i=0;i<UNIQUEID_LENGTH;this.uniqueID[i]=uniqueID[i],i++)
;
}
public float getVersion() {
return version;
}
public void setVersion(float version) {
this.version = version;
}
public byte getCipherAction() {
return cipherAction;
}
public void setCipherAction(byte cipherAction) {
this.cipherAction = cipherAction;
}
public char[] getSignature() {
return signature;
}
public void setSignature(char[] signature) {
for(int i=0;i<SIGNATURE_LENGTH;this.signature[i] = signature[i],i++)
;
}
/**
* 返回PakHeader的大小
* @return 返回PakHeader的大小
*/
public static int size(){
return SIGNATURE_LENGTH+4+4+1+1+UNIQUEID_LENGTH+4;
}
public String toString(){
String result="";
result+="\t签名:"+new String(this.signature).trim()
+"\t版本号:"+this.version
+"\t文件table数量:"+this.numFileTableEntries
+"\t密码行为:" +this.cipherAction
+"\t密码:"+this.cipherValue
+"\t文件唯一ID:"+new String(this.uniqueID).trim() +"\t保留位:"+this.reserved;
return result;
}}
PakFileTable.java
package cn.org.matrix.gmatrix.gameLab.util.pak;/** * Pak文件table类 * 文件table结构: *
文件名:30字节char数组 *
文件大小:32位整型 *
文件在pak文件中的位移:32位整数 * @author cleverpig * */class PakFileTable {
public static final int FILENAME_LENGTH=30;
//文件名
private char[] fileName=new char[FILENAME_LENGTH];
//文件大小
private long fileSize=0L;
//文件在pak文件中的位移
private long offSet=0L;
public PakFileTable(){
}
/**
* 构造方法
* @param fileName 文件名
* @param fileSize 文件大小
* @param offSet 文件在Pak文件中的位移
*/
public PakFileTable(char[] fileName,
long fileSize,long offSet){
for(int i=0;i<FILENAME_LENGTH;this.fileName[i]=fileName[i],i++)
; this.fileSize=fileSize;
this.offSet=offSet;
}
public char[] getFileName() {
return fileName;
}
public void setFileName(char[] fileName) {
for(int i=0;i<fileName.length;this.fileName[i]=fileName[i],i++)
;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public long getOffSet() {
return offSet;
}
public void setOffSet(long offSet) {
this.offSet = offSet;
}
/**
* 返回文件Table的大小
* @return 返回文件Table的大小
*/
public static int size(){
return FILENAME_LENGTH+4+4;
}
public String toString(){
return "\t文件名:"+new String(this.fileName).trim()
+"\t文件大小:"+this.fileSize
+"\t文件位移:"+this.offSet;
}}
六、PakUtil类(j2se版):
PakUtil.java
package cn.org.matrix.gmatrix.gameLab.util.pak;import java.io.*;
import java.util.Vector;
/** * Pak工具类 * 功能:
*1.将多个png图片合成一个Pak文件,并使用简单的加减加密法对其进行加密;
* 2.从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象)或者写为文件 * @author cleverpig * */public class PakUtil {
public PakUtil(){
}
/**
* 返回文件长度
* @param filePath 文件路径
* @return 文件长度
*/
private long getFileSize(String filePath){
File file=new File(filePath);
return file.length();
}
/**
* 返回文件名
* @param filePath 文件路径
* @return 文件名
*/
private String getFileName(String filePath){
File file=new File(filePath);
return file.getName();
}
/**
* 计算文件位移的起始点
* @return 文件位移的起始点
*/
private long workOutOffsetStart(PakHeader header){
//计算出文件头+文件table的长度
return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
}
/**
* 计算文件位移
* @param fileIndex 文件序号
* @param lastFileOffset 上一个文件位移
* @return 文件在pak文件中的位移
*/
private long workOutNextOffset(long sourceFileSize,long lastFileOffset){
return lastFileOffset+sourceFileSize;
}
/**
* 生成文件table
* @param sourceFileName 源文件名
* @param sourceFileSize 源文件长度
* @param currentFileOffset 当前文件位移
* @return 生成的PakFileTable对象
*/
private PakFileTable generateFileTable(String sourceFileName,
long sourceFileSize,long currentFileOffset){
PakFileTable ft=new PakFileTable();
ft.setFileName(sourceFileName.toCharArray());
ft.setFileSize(sourceFileSize);
ft.setOffSet(currentFileOffset);
return ft;
}
/**
* 将char字符数组写入到DataOutputStream中
* @param toWriteCharArray 被写入的char数组
* @param dos DataOutputStream
* @throws Exception
*/
private void writeCharArray(char[] toWriteCharArray,DataOutputStream dos) throws Exception{
for(int i=0;i<toWriteCharArray.length;dos.writeChar(toWriteCharArray[i]),i++);
}
/**
* 使用文件头中的密码对数据进行加密
* @param buff 被加密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void encryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]+=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]-=header.getCipherValue();
break;
}
}
}
/**
* 使用文件头中的密码对数据进行解密
* @param buff 被解密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]-=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]+=header.getCipherValue();
break;
}
}
}
/**
* 制作Pak文件
* @param sourceFilePath 源文件路径数组
* @param destinateFilePath 目的文件路径(Pak文件)
* @param cipherAction 密码行为
* @param cipherValue 密码
* @throws Exception
*/
public void makePakFile(String[] sourceFilePath,
String destinateFilePath,PakHeader header) throws Exception{
PakFileTable[] fileTable=new PakFileTable[sourceFilePath.length];
//计算文件位移起始点
long fileOffset=workOutOffsetStart(header);
//逐个建立文件table
for(int i=0;i<sourceFilePath.length;i++){
String sourceFileName=getFileName(sourceFilePath[i]);
long sourceFileSize=getFileSize(sourceFilePath[i]);
PakFileTable ft=generateFileTable(sourceFileName,sourceFileSize,fileOffset);
//计算下一个文件位移
fileOffset=workOutNextOffset(sourceFileSize,fileOffset);
fileTable[i]=ft;
}
//写入文件头
File wFile=new File(destinateFilePath);
FileOutputStream fos=new FileOutputStream(wFile);
DataOutputStream dos=new DataOutputStream(fos);
writeCharArray(header.getSignature(),dos);
dos.writeFloat(header.getVersion());
dos.writeLong(header.getNumFileTableEntries());
dos.writeByte(header.getCipherAction());
dos.writeByte(header.getCipherValue());
writeCharArray(header.getUniqueID(),dos);
dos.writeLong(header.getReserved());
//写入文件table
for(int i=0;i<fileTable.length;i++){
writeCharArray(fileTable[i].getFileName(),dos);
dos.writeLong(fileTable[i].getFileSize());
dos.writeLong(fileTable[i].getOffSet());
}
//写入文件数据
for(int i=0;i<fileTable.length;i++){
File ftFile=new File(sourceFilePath[i]);
FileInputStream ftFis=new FileInputStream(ftFile);
DataInputStream ftDis=new DataInputStream(ftFis);
byte[] buff=new byte[256];
int readLength=0;
while((readLength=ftDis.read(buff))!=-1){
encryptBuff(buff,readLength,header);
dos.write(buff,0,readLength);
}
ftDis.close();
ftFis.close();
}
dos.close();
}
/**
* 从DataInputStream读取char数组 * @param dis DataInputStream
* @param readLength 读取长度
* @return char数组
* @throws Exception
*/
private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
char[] readCharArray=new char[readLength];
for(int i=0;i<readLength;i++){
readCharArray[i]=dis.readChar();
}
return readCharArray;
}
/**
* 从PAK文件中读取文件头
* @param dis DataInputStream
* @return PakHeader
* @throws Exception
*/
private PakHeader readHeader(DataInputStream dis) throws Exception{
PakHeader header=new PakHeader();
char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
header.setSignature(signature);
header.setVersion(dis.readFloat());
header.setNumFileTableEntries(dis.readLong());
header.setCipherAction(dis.readByte());
header.setCipherValue(dis.readByte());
char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
header.setUniqueID(uniqueID);
header.setReserved(dis.readLong());
return header;
}
/**
* 读取所有的文件table
* @param dis DataInputStream
* @param fileTableNumber 文件表总数
* @return 文件table数组
* @throws Exception
*/
private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
for(int i=0;i<fileTableNumber;i++){
PakFileTable ft=new PakFileTable();
ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
ft.setFileSize(dis.readLong());
ft.setOffSet(dis.readLong());
fileTable[i]=ft;
}
return fileTable;
}
/**
* 从pak文件读取文件到byte数组
* @param dis DataInputStream
* @param fileTable PakFileTable
* @return byte数组
* @throws Exception
*/
private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
//
int fileLength=(int)fileTable.getFileSize();
byte[] fileBuff=new byte[fileLength];
int readLength=dis.read(fileBuff,0,fileLength);
if (readLength<fileLength){
System.out.println("读取数据长度不正确");
return null;
}
else{
decryptBuff(fileBuff,readLength,header);
return fileBuff;
}
}
/**
* 将buffer中的内容写入到文件
* @param fileBuff 保存文件内容的buffer
* @param fileName 文件名
* @param extractDir 文件导出目录
* @throws Exception
*/
private void writeFileFromByteBuffer(byte[] fileBuff,String fileName,String extractDir) throws Exception{
String extractFilePath=extractDir+fileName;
File wFile=new File(extractFilePath);
FileOutputStream fos=new FileOutputStream(wFile);
DataOutputStream dos=new DataOutputStream(fos);
dos.write(fileBuff);
dos.close();
fos.close();
}
/**
* 从pak文件中取出指定的文件到byte数组,如果需要的话可以将byte数组写为文件
* @param pakFilePath pak文件路径
* @param extractFileName pak文件中将要被取出的文件名
* @param writeFile 是否需要将byte数组写为文件
* @param extractDir 如果需要的话可以将byte数组写为文件,extractDir为取出数据被写的目录文件
* @return byte数组
* @throws Exception
*/
public byte[] extractFileFromPak(String pakFilePath,
String extractFileName,boolean writeFile,String extractDir) throws Exception{
File rFile=new File(pakFilePath);
FileInputStream fis=new FileInputStream(rFile);
DataInputStream dis=new DataInputStream(fis);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());
boolean find=false; int fileIndex=0;
for(int i=0;i<fileTable.length;i++){
String fileName=new String(fileTable[i].getFileName()).trim();
if (fileName.equals(extractFileName)){
find=true;
fileIndex=i;
break;
}
}
if (find==false){
System.out.println("没有找到指定的文件");
return null;
}
else{
byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
if (writeFile){
writeFileFromByteBuffer(buff,extractFileName,extractDir);
}
else{
dis.close();
fis.close();
}
return buff;
}
}
/**
* 从pak文件中取出指定的Pak文件的信息
* @param pakFilePath pak文件路径
* @return 装载文件头和文件table数组的Vector
* @throws Exception
*/
public Vector showPakFileInfo(String pakFilePath) throws Exception{
File rFile=new File(pakFilePath);
FileInputStream fis=new FileInputStream(rFile);
DataInputStream dis=new DataInputStream(fis);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries()); Vector result=new Vector();
result.add(header);
result.add(fileTable);
return result;
}
public static void main(String[] argv) throws Exception{
PakUtil pu=new PakUtil();
//构造文件头
char[] signature=new char[PakHeader.SIGNATURE_LENGTH];
signature=new String("012345").toCharArray();
char[] uniqueID=new char[PakHeader.UNIQUEID_LENGTH];
uniqueID=new String("0123456789").toCharArray();
PakHeader header=new PakHeader();
header.setSignature(signature);
header.setNumFileTableEntries(3);
header.setCipherAction((byte)PakHeader.ADDITION_CIPHERACTION);
header.setCipherValue((byte)0x0f);
header.setUniqueID(uniqueID);
header.setVersion(1.0f);
header.setReserved(0L);
String[] filePathArray={"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\apple.png",
"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\cushaw.png",
"F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\flash.png"};
String extractFilePath="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\test.pak";
//制作Pak文件
System.out.println("制作Pak文件...");
pu.makePakFile(filePathArray,extractFilePath,header);
System.out.println("制作Pak文件完成");
//从Pak文件中取出所有的图片文件
Vector pakInfo=pu.showPakFileInfo(extractFilePath);
header=(PakHeader)pakInfo.elementAt(0);
System.out.println("Pak文件信息:");
System.out.println("文件头:");
System.out.println(header);
PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
for(int i=0;i<fileTable.length;i++){
System.out.println("文件table["+i+"]:");
System.out.println(fileTable[i]);
}
String restoreDir="F:\\eclipse3.1RC3\\workspace\\gmatrixProject_j2se\\testFiles\\extract\\";
String restoreFileName=null;
byte[] fileBuff=null;
for(int i=0;i<fileTable.length;i++){
restoreFileName=new String(fileTable[i].getFileName()).trim();
System.out.println("从Pak文件中取出"+restoreFileName+"文件...");
fileBuff=pu.extractFileFromPak(extractFilePath,restoreFileName,true,restoreDir);
System.out.println("从Pak文件中取出"+restoreFileName+"文件保存在"+restoreDir+"目录");
}
}}
七、PakUtil类(j2me版):
PakUtil.java
package cn.org.matrix.gmatrix.gameLab.util.pak;
import java.io.*;
import java.util.Vector;
/** * Pak工具类 * 功能: * 从Pak文件中取出png图片,构造byte数组(可以用来构造Image对象) * @author cleverpig * */public class PakUtil {
public PakUtil(){
}
/**
* 计算文件位移的起始点
* @return 文件位移的起始点
*/
private long workOutOffsetStart(PakHeader header){
//计算出文件头+文件table的长度
return PakHeader.size()+header.getNumFileTableEntries()*PakFileTable.size();
}
/**
* 从DataInputStream读取char数组
* @param dis DataInputStream
* @param readLength 读取长度
* @return char数组
* @throws Exception
*/
private char[] readCharArray(DataInputStream dis,int readLength) throws Exception{
char[] readCharArray=new char[readLength];
for(int i=0;i<readLength;i++){
readCharArray[i]=dis.readChar();
}
return readCharArray;
}
/**
* 从PAK文件中读取文件头
* @param dis DataInputStream
* @return PakHeader
* @throws Exception
*/
private PakHeader readHeader(DataInputStream dis) throws Exception{
PakHeader header=new PakHeader();
char[] signature=readCharArray(dis,PakHeader.SIGNATURE_LENGTH);
header.setSignature(signature);
header.setVersion(dis.readFloat());
header.setNumFileTableEntries(dis.readLong());
header.setCipherAction(dis.readByte());
header.setCipherValue(dis.readByte());
char[] uniqueID=readCharArray(dis,PakHeader.UNIQUEID_LENGTH);
header.setUniqueID(uniqueID);
header.setReserved(dis.readLong());
return header;
}
/**
* 读取所有的文件table
* @param dis DataInputStream
* @param fileTableNumber 文件表总数
* @return 文件table数组
* @throws Exception
*/
private PakFileTable[] readFileTable(DataInputStream dis,int fileTableNumber) throws Exception{
PakFileTable[] fileTable=new PakFileTable[fileTableNumber];
for(int i=0;i<fileTableNumber;i++){
PakFileTable ft=new PakFileTable();
ft.setFileName(readCharArray(dis,PakFileTable.FILENAME_LENGTH));
ft.setFileSize(dis.readLong());
ft.setOffSet(dis.readLong());
fileTable[i]=ft;
}
return fileTable;
}
/**
* 从pak文件读取文件到byte数组
* @param dis DataInputStream
* @param fileTable PakFileTable
* @return byte数组
* @throws Exception
*/
private byte[] readFileFromPak(DataInputStream dis,PakHeader header,PakFileTable fileTable) throws Exception{
dis.skip(fileTable.getOffSet()-workOutOffsetStart(header));
//
int fileLength=(int)fileTable.getFileSize();
byte[] fileBuff=new byte[fileLength];
int readLength=dis.read(fileBuff,0,fileLength);
if (readLength<fileLength){
System.out.println("读取数据长度不正确");
return null;
}
else{
decryptBuff(fileBuff,readLength,header);
}
return fileBuff;
}
/**
* 使用文件头中的密码对数据进行解密
* @param buff 被解密的数据
* @param buffLength 数据的长度
* @param header 文件头
*/
private void decryptBuff(byte[] buff,int buffLength,PakHeader header){
for(int i=0;i<buffLength;i++){
switch(header.getCipherAction()){
case PakHeader.ADDITION_CIPHERACTION:
buff[i]-=header.getCipherValue();
break;
case PakHeader.SUBTRACT_CIHOERACTION:
buff[i]+=header.getCipherValue();
break;
}
}
}
/**
* 从pak文件中取出指定的文件到byte数组
* @param pakResourceURL pak文件的资源路径
* @param extractResourceName pak文件中将要被取出的文件名
* @return byte数组
* @throws Exception
*/
public byte[] extractResourceFromPak(String pakResourceURL
,String extractResourceName) throws Exception{
InputStream is=this.getClass().getResourceAsStream(pakResourceURL);
DataInputStream dis=new DataInputStream(is);
PakHeader header=readHeader(dis);//
System.out.println("文件头:");//
System.out.println(header);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());//
for(int i=0;i<fileTable.length;i++){//
System.out.println("文件table["+i+"]:");//
System.out.println(fileTable[i]);//
}
boolean find=false;
int fileIndex=0;
for(int i=0;i<fileTable.length;i++){
String fileName=new String(fileTable[i].getFileName()).trim();
if (fileName.equals(extractResourceName)){
find=true;
fileIndex=i;
break;
}
}
if (find==false){
System.out.println("没有找到指定的文件");
return null;
}
else{
byte[] buff=readFileFromPak(dis,header,fileTable[fileIndex]);
return buff;
}
}
/**
* 从pak文件中取出指定的Pak文件的信息
* @param pakResourcePath pak文件资源路径
* @return 装载文件头和文件table数组的Vector
* @throws Exception
*/
public Vector showPakFileInfo(String pakResourcePath) throws Exception{
InputStream is=this.getClass().getResourceAsStream(pakResourcePath);
DataInputStream dis=new DataInputStream(is);
PakHeader header=readHeader(dis);
PakFileTable[] fileTable=readFileTable(dis,(int)header.getNumFileTableEntries());
Vector result=new Vector();
result.addElement(header);
result.addElement(fileTable);
return result;
}
public static void main(String[] argv) throws Exception{
PakUtil pu=new PakUtil();
String extractResourcePath="/test.pak";
//从Pak文件中取出所有的图片文件
Vector pakInfo=pu.showPakFileInfo(extractResourcePath);
PakHeader header=(PakHeader)pakInfo.elementAt(0);
System.out.println("Pak文件信息:");
System.out.println("文件头:");
System.out.println(header);
PakFileTable[] fileTable=(PakFileTable[])pakInfo.elementAt(1);
for(int i=0;i<fileTable.length;i++){
System.out.println("文件table["+i+"]:");
System.out.println(fileTable[i]);
}
String restoreFileName=null;
byte[] fileBuff=null;
for(int i=0;i<fileTable.length;i++){
restoreFileName=new String(fileTable[i].getFileName()).trim();
System.out.println("从Pak文件中取出"+restoreFileName+"文件数据...");
fileBuff=pu.extractResourceFromPak(extractResourcePath,restoreFileName);
System.out.println("从Pak文件中取出"+restoreFileName+"文件数据完成");
}
}}
八、源代码使用简介:
Pak过程:j2se版的PakUtil将testFiles目录中的三个png文件Pak成为test.pak文件。
UnPak过程:j2se版的PakUtil将testFiles目录中test.pak文件释放到testFiles\extract目录下; j2me版的PakUtil从res目录中的test.pak文件读取出其中所包含的3个png文件数据并装入到byte数据,用来构造Image对象, 大家请运行PakUtilTestMIDlet.java便可看到输出的信息。
相关推荐
在J2ME(Java 2 Micro Edition)开发中,Pak文件是一种常见的资源打包格式,主要用于存储游戏或应用程序中的图片、音频、配置文件等非代码数据。本教程将引导你了解如何在J2ME环境中创建Pak文件,以及如何在程序中...
本文将深入探讨一个基于J2ME的项目——蓝牙联网五子棋游戏,旨在帮助开发者理解和掌握如何利用J2ME技术实现设备间的蓝牙通信,并构建具有联网功能的游戏。 首先,我们需要理解J2ME的基本架构。J2ME由配置...
在J2ME中播放声音,尤其是播放内嵌在JAR文件中的声音文件,主要涉及以下几个步骤: 1. **读取声音文件**: 首先,你需要获取到声音文件的输入流。这通常通过 `getClass().getResourceAsStream()` 方法完成,该方法...
《j2me手机游戏——扫雷》是一款基于Java 2 Micro Edition (J2ME) 平台开发的经典游戏,旨在为手机用户提供与Windows系统扫雷相似的娱乐体验。这款扫雷游戏具有高度的适应性,能够自动适应不同屏幕尺寸,同时配备了...
综上所述,这个J2ME版本的PAK加压解压代码集成了PAK文件的读取、解析和解压功能,是为J2ME平台上的游戏或其他应用设计的资源管理工具。通过这些类的协同工作,开发者可以在有限的资源条件下,有效地管理和加载PAK...
1. **JAR and WAP Push**:J2ME应用打包为JAR文件,可通过WAP推送技术分发到设备。 2. **Signed Applications**:为了访问设备的受限功能(如联系人、短信),应用需要签名。 3. **Device Compatibility**:开发者...
根据给定的信息,我们可以分析并总结出关于J2ME(Java 2 Micro Edition)手机游戏——俄罗斯方块的重要知识点。 ### J2ME简介 J2ME(Java 2 Platform, Micro Edition)是Sun Microsystems为嵌入式设备和移动设备...
### 基于J2ME的主要技术——以“贪吃蛇”游戏为例 #### 引言 随着通信技术的快速发展,移动通信技术的规模不断扩大,同时也带动了手机性能的显著提升。这一变化促进了手机休闲娱乐市场的蓬勃发展。在众多手机应用...
《J2ME程序设计——手机游戏与应用程序》是由谢晓勇和黄奇合著的一本专业书籍,主要探讨了如何利用Java 2 Micro Edition (J2ME) 平台进行手机游戏和应用程序的开发。J2ME是Java平台的一个子集,特别针对嵌入式设备和...
在本文中,我们将深入探讨如何使用Java 2 Micro Edition(J2ME)技术来创建一个经典的游戏——贪吃蛇。J2ME是一个适用于移动设备和嵌入式系统的Java平台,它允许开发者创建丰富的应用程序和游戏。 一、环境搭建 ...
内含J2ME游戏源码 和 相关论文一篇 ...论文内容:第一章、开发平台与技术概述;第二章 J2ME及其体系结构概述; 第三章 手机游戏开发过程;第四章 程序的调试与运行;第五章 总结;致 谢;参考文献
数独,一款深受全球玩家喜爱的逻辑推理游戏,如今已移植到移动平台,尤其在Java J2ME技术的支持下,使得数独游戏在早期的智能手机上也能轻松体验。本资源提供了一个基于J2ME的手机数独游戏源码,对于初学者而言,这...
"java基于J2ME的游戏开发——贪吃蛇论文" J2ME(Java 2 Micro Edition)は、近年来随着各种不同设备、尤其是移动通信设备的飞速发展而诞生的新的开发技术。J2ME 是Java平台的微型版,专门为资源有限的设备设计,...
J2ME技术手册J2ME技术手册J2ME技术手册J2ME技术手册J2ME技术手册J2ME技术手册J2ME技术手册J2ME技术手册
J2ME技术手册.part1
**J2ME技术手册概述** Java 2 Micro Edition(J2ME)是Java平台的一个子集,主要用于开发和部署在嵌入式设备、移动电话、智能家电等资源有限的环境中运行的应用程序。J2ME提供了丰富的功能,允许开发者创建功能丰富...
J2ME作业2,界面上每秒钟在随机位置出现随机颜色的数字0—9 ,要求用户快速反应,按下按键,如果按对,加1分,错误减2分,不按减1分,初始5分,减到0分,提示用户输了,有两个command可以退出或者重玩。 PS:不是工程...
在Java Micro Edition (J2ME) 平台上,文件操作是移动设备应用程序开发中的常见任务。J2ME 提供了基本的文件系统访问能力,但与标准 Java SE 平台相比,其功能较为有限。本篇文章将深入探讨如何在 J2ME 中进行文件的...
### J2ME程序读取文件知识点详解 #### 一、J2ME简介 J2ME(Java 2 Platform Micro Edition)是Sun ...通过对示例代码的深入分析,可以帮助开发者更好地理解和掌握J2ME环境下文件操作的基本原理和技术要点。