pvz一代特殊图像格式分析——texTV系列

声明:本教程内容包含:ptx2X系列编码解码、ptxPS3系列图像查看、texTV系列编码解码、texiOS系列编码解码、txz图像编码解码,若你根据本教程写出了对应工具,请务必注明是参考了本教程。
一代在TV版和iOS版均出现过tex格式,不过两种tex文件结构并不相同,这里介绍TV版的tex格式,后续更新iOS版格式介绍。
使用十六进制编辑器打开TV版中的crazydave-atlas1.tex,可以看到0x0开始的8个字节显然是文件魔数(在文件起始位置用于标识文件类型的内容),0x8开始4个字节应该都是00,猜测0xC开始4字节和0x10开始4字节的内容是图像宽度或高度,0x14开始的12个字节暂时不知道是什么意思,0x20开始的4字节内容正好是文件大小-0x30,也就是从0x30开始到文件末的大小,后面跟着12字节的00。

0x30出现了78 9C,正好是zlib压缩的文件魔数,于是删除掉前面0x30的数据,对整个后续内容进行zlib解压,得到了一个大小为1708032字节的文件,1708032=512*1668*2,也就是说2字节=1像素rgba颜色,而根据图像名字来看应该是戴夫图像,这张图像在dz解包中出现过半透明的像素,使用rgb565或rgba5551来存储不合适,所以猜测图像为rgba4444压缩。用此格式将其转为png后,得到图像如图,发现图像轮廓没问题,但是颜色有问题。

于是使用十六进制编辑器打开解压后的文件,发现开头都是FF 0F,而此图像左上角一开始就是透明的,所以alpha通道肯定对应那个0,猜测其为argb4444存储,对应方式解码后,得到正确图像。

texTV共有两种格式,02为argb8888,03为argb4444,格式02的只有一张图片。
C#代码实现:
//编码
public static void Save(Bitmap Pic, string outFile, int format)
{
if (format > 3 || format < 2)
{
throw new Exception("错误的格式类型:texTV" + format);
}
HexWriter HW = HexWriter.Create("temp");
HW.SetPosition(0x0);
switch (format)
{
case 2:
Save_A8R8G8B8(HW, Pic);
break;
case 3:
Save_A4R4G4B4(HW, Pic);
break;
}
HW.Close();
Zlib.Compress("temp", "temp2");
HexWriter HW2 = HexWriter.Create(outFile);
HW2.SetPosition(0x0);
HW2.WriteUInt16(0x4553);
HW2.WriteUInt16(0x5958);
HW2.WriteUInt16(0x4554);
HW2.WriteUInt16(0x58);
HW2.WriteUInt32(0x0);
HW2.WriteUInt32((ushort)Pic.Width);
HW2.WriteUInt32((ushort)Pic.Height);
HW2.WriteUInt32((ushort)format);
HW2.WriteUInt32(0x1);
HW2.WriteUInt32(0x1);
HexReader HR = HexReader.Create("temp2");
HW2.WriteInt64(HR.GetSize());
HW2.WriteInt64(0x0);
HR.SetPosition(0x0);
HW2.WriteBytes(HR.ReadAll());
HR.Close();
HW2.Close();
File.Delete("temp");
File.Delete("temp2");
}
static void Save_A4R4G4B4(HexWriter HW, Bitmap img)
{
int height = img.Height;
int width = img.Width;
Color temp2;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
temp2 = img.GetPixel(j, i);
HW.WriteUInt16((ushort)(((temp2.A & 0xF0) << 8) + ((temp2.B & 0xF0) >> 4) + (temp2.G & 0xF0) + ((temp2.R & 0xF0) << 4)));
}
}
}
static void Save_A8R8G8B8(HexWriter HW, Bitmap img)
{
int height = img.Height;
int width = img.Width;
Color temp2;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
temp2 = img.GetPixel(j, i);
HW.WriteByte(temp2.B);
HW.WriteByte(temp2.G);
HW.WriteByte(temp2.R);
HW.WriteByte(temp2.A);
}
}
}
//编码
public static Bitmap Get(string fileName)
{
HexReader HR = HexReader.Create(fileName);
//这是TV版tex
HR.SetPosition(0xC);
int width = HR.ReadInt32();
int height = HR.ReadInt32();
int format = HR.ReadInt32();
HR.AddPosition(0x18);
FileStream a = new FileStream("temp", FileMode.Create);
HR.CopyTo(a);
a.Close();
Zlib.Decompress("temp", "temp2");
Bitmap ans;
HexReader c = HexReader.Create("temp2");
switch (format)
{
case 2:
ans = Get_A8R8G8B8(c, height, width);
break;
case 3:
ans = Get_A4R4G4B4(c, height, width);
break;
default:
ans = null;
break;
}
c.Close();
File.Delete("temp");
File.Delete("temp2");
return ans;
}
static Bitmap Get_A8R8G8B8(HexReader HR, int height, int width)
{
Bitmap img = new Bitmap(width, height);
byte a;
byte r;
byte g;
byte b;
LockBitmap img2 = new LockBitmap(img);
img2.LockBits();
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
b = HR.ReadByte();
g = HR.ReadByte();
r = HR.ReadByte();
a = HR.ReadByte();
img2.SetPixel(j, i, Color.FromArgb(a, r, g, b));
}
}
img2.UnlockBits();
return img;
}
static Bitmap Get_A4R4G4B4(HexReader HR, int height, int width)
{
Bitmap img = new Bitmap(width, height);
int a;
int r;
int g;
int b;
byte temp;
LockBitmap img2 = new LockBitmap(img);
img2.LockBits();
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
temp = HR.ReadByte();
g = ((temp & 0xF0) >> 4) * 17;
b = (temp & 0xF) * 17;
temp = HR.ReadByte();
a = ((temp & 0xF0) >> 4) * 17;
r = (temp & 0xF) * 17;
img2.SetPixel(j, i, Color.FromArgb(a, r, g, b));
}
}
img2.UnlockBits();
return img;
}