当前位置 : 145z游戏站 | 热血传奇 | 技术教程 | 

热血传奇WIX与WIL文件Java解析实现:文件头偏移表调色板转BufferedImage

热度:
WIX是WEMADEImageIndex,纯索引文件;WIL是WEMADEImageLibrary,存调色板与全部8bit索引像素。一组资源必然是xxx.wix+xxx.wil成对出现,单独读WIL也能抠图,但定位第N张图的正确起点必须靠WIX的偏移数组。

一、WIX文件结构(原版Mir2)

•0x00–0x2B(44字节):标题#INDXv1.0-WEMADEEntertainmentinc.(实际常见为40字节标题+4字节填充,社区实现里按44字节处理最稳)

-0x2C–0x2F(4字节int32LE):图片数量imageCount
•0x30–0x33(4字节int32LE):首图数据偏移,固定0x438=1080

•接着imageCount×4字节int32LE数组:第i张图在WIL中的绝对字节偏移offset[i]

Mir3/新格式WIX标题区缩成20字节,首偏移常在0x1C,解析前先读标题串判断分支。

二、WIL文件结构(原版256色版)

•0x00–0x2B(44字节):标题#ILIBv1.0-WEMADEEntertainmentinc.

-0x2C–0x2F:图片数量(与WIX一致)
•0x30–0x33:色深度(256)

•0x34–0x37:调色板字节数(1024)

•0x38–0x437(共1024字节):256色调色板,每色4字节BGRA(A常置0xFF)

-从0x438开始:按WIX偏移数组切分每张图:
•2字节shortLE:width

•2字节shortLE:height

•2字节shortLE:offsetX

-2字节shortLE:offsetY
•4字节固定标志0700D4FF

•紧跟width×height字节:8bit索引像素(每字节是调色板下标,未压缩)

真彩版(16bit/32bit/带WZL压缩)头部换成56字节Title(44)+ImageCount+ColorCount+PaletteSize,像素改RGB565或RGBA,部分还套LZH变种压缩,需先解压再解码。

三、Java基础工具类

publicfinalclassLeBuf{
publicstaticintreadIntLE(byte[]bintoff){
return(b[off]&0xFF)
|((b[off+1]&0xFF)<<8)
|((b[off+2]&0xFF)<<16)
|((b[off+3]&0xFF)<<24);
}
publicstaticshortreadShortLE(byte[]bintoff){
return(short)((b[off]&0xFF)|((b[off+1]&0xFF)<<8));
}
}


四、WIX解析

publicclassWixIndex{
publicStringtitle;
publicintimageCount;
publicintbaseOffset;//通常0x438
publicint[]offsets;//每张图在wil中的绝对偏移

publicstaticWixIndexparse(byte[]wix){
WixIndexidx=newWixIndex();
idx.title=newString(wix044).trim();
idx.imageCount=LeBuf.readIntLE(wix0x2C);
idx.baseOffset=LeBuf.readIntLE(wix0x30);
idx.offsets=newint[idx.imageCount];
for(inti=0;i<idx.imageCount;i++){
idx.offsets[i]=LeBuf.readIntLE(wix0x34+i*4);
}
returnidx;
}
}


五、WIL解析与转BufferedImage

importjava.awt.image.BufferedImage;
importjava.io.IOException;
importjava.nio.file.Files;
importjava.nio.file.Path;

publicclassWilArchive{
privatefinalbyte[]wil;
privateint[]palette=newint[256];//解好的ARGB
privateWixIndexindex;

publicWilArchive(PathwixPathPathwilPath)throwsIOException{
this.index=WixIndex.parse(Files.readAllBytes(wixPath));
this.wil=Files.readAllBytes(wilPath);
parsePalette();
}

privatevoidparsePalette(){
//调色板从0x38开始,256×4字节BGRA
intp=0x38;
for(inti=0;i<256;i++){
intb=wil[p]&0xFF;
intg=wil[p+1]&0xFF;
intr=wil[p+2]&0xFF;
inta=wil[p+3]&0xFF;
palette[i]=(a<<24)|(r<<16)|(g<<8)|b;
p+=4;
}
}

publicBufferedImagegetImage(intn){
if(n<0||n>=index.imageCount)returnnull;
intpos=index.offsets[n];
intw=LeBuf.readShortLE(wilpos)&0xFFFF;
inth=LeBuf.readShortLE(wilpos+2)&0xFFFF;
//pos+4offsetXpos+6offsetYpos+8~11固定标志0700D4FF
intpxStart=pos+12;
BufferedImageimg=newBufferedImage(whBufferedImage.TYPE_INT_ARGB);
for(inty=0;y<h;y++){
for(intx=0;x<w;x++){
intpalIdx=wil[pxStart+y*w+x]&0xFF;
img.setRGB(xypalette[palIdx]);
}
}
returnimg;
}

publicintimageCount(){returnindex.imageCount;}
}


六、调用示例

WilArchivearch=newWilArchive(
Path.of("Data/Objects1.wix")
Path.of("Data/Objects1.wil"));
BufferedImageframe0=arch.getImage(0);
System.out.println(arch.imageCount()+"framesloaded");
//交给Swing/JavaFX画,或ImageIO.write导出png


七、踩坑点

•标题长度别死磕40,原版WIX实际头到0x2B共44字节,图片数在0x2C;写错偏移全图错位。

•像素是8bit调色板索引,不是直接RGB;真彩WIL需按ColorCount分支处理RGB565/RGBA。

-新格式/Mir3/GEE的WZL封装是在WIL外再套LZH变种压缩,要先整包解压再按上面逻辑走。
•偏移数组里值为0或指向0x438的是空帧(透明占位),直接返回null或透明图。

-大库(Objects1.wil几十MB)别整库解所有帧,按需要懒加载getImage(n)即可。
•JMir等开源实现里LibHeader/ImageInfo实体就是上述结构的对象化,可直接参照复用。
[顶部]