404 lines
13 KiB
C#
404 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Linq;
|
|
|
|
namespace WzComparerR2.WzLib
|
|
{
|
|
public class Wz_Image
|
|
{
|
|
public Wz_Image(string name, int size, int cs32, uint hashOff, uint hashPos, Wz_File wz_f)
|
|
{
|
|
this.Name = name;
|
|
this.WzFile = wz_f;
|
|
this.Size = size;
|
|
this.Checksum = cs32;
|
|
this.HashedOffset = hashOff;
|
|
this.HashedOffsetPosition = hashPos;
|
|
this.Node = new Wz_ImageNode(name, this);
|
|
this.Encryption = null;
|
|
|
|
this.extr = false;
|
|
this.chec = false;
|
|
}
|
|
|
|
private bool extr;
|
|
private bool chec;
|
|
|
|
public string Name { get; set; }
|
|
public Wz_File WzFile { get; set; }
|
|
public int Size { get; set; }
|
|
public int Checksum { get; set; }
|
|
public uint HashedOffset { get; set; }
|
|
public uint HashedOffsetPosition { get; set; }
|
|
public long Offset { get; set; }
|
|
|
|
public Wz_Node Node { get; private set; }
|
|
|
|
public Wz_Node OwnerNode { get; set; }
|
|
|
|
public Wz_Crypto Encryption { get; private set; }
|
|
|
|
public bool IsChecksumChecked
|
|
{
|
|
get { return this.chec; }
|
|
internal set { this.chec = value; }
|
|
}
|
|
public bool IsLuaImage
|
|
{
|
|
get { return this.Name.EndsWith(".lua"); }
|
|
}
|
|
|
|
public bool TryExtract()
|
|
{
|
|
Exception ex;
|
|
return TryExtract(out ex);
|
|
}
|
|
|
|
public bool TryExtract(out Exception e)
|
|
{
|
|
if (!this.extr)
|
|
{
|
|
bool disabledChec = this.WzFile?.WzStructure?.ImgCheckDisabled ?? false;
|
|
if (!disabledChec && !this.chec)
|
|
{
|
|
if (this.Checksum != CalcCheckSum())
|
|
{
|
|
e = new ArgumentException("checksum error");
|
|
return false;
|
|
}
|
|
this.chec = true;
|
|
}
|
|
if (this.Encryption == null)
|
|
{
|
|
if (!this.IsLuaImage)
|
|
{
|
|
try
|
|
{
|
|
this.TryDetectEnc();
|
|
if (this.Encryption == null)
|
|
{
|
|
e = null;
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
e = ex;
|
|
this.Unextract();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
lock (this.WzFile.ReadLock)
|
|
{
|
|
this.WzFile.FileStream.Position = this.Offset;
|
|
if (!this.IsLuaImage)
|
|
{
|
|
ExtractImg(this.Offset, this.Node, 0);
|
|
this.WzFile.stringTable.Clear();
|
|
}
|
|
else
|
|
{
|
|
ExtractLua();
|
|
}
|
|
this.extr = true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
e = ex;
|
|
this.Unextract();
|
|
return false;
|
|
}
|
|
}
|
|
e = null;
|
|
return true;
|
|
}
|
|
|
|
public void Unextract()
|
|
{
|
|
this.extr = false;
|
|
this.Node.Nodes.Clear();
|
|
}
|
|
|
|
public unsafe int CalcCheckSum()
|
|
{
|
|
lock (this.WzFile.ReadLock)
|
|
{
|
|
this.WzFile.FileStream.Position = this.Offset;
|
|
int cs = 0;
|
|
byte[] buffer = new byte[4096];
|
|
int count;
|
|
int size = this.Size;
|
|
while ((count = this.WzFile.FileStream.Read(buffer, 0, Math.Min(size, buffer.Length))) > 0)
|
|
{
|
|
fixed (byte* pBuffer = buffer)
|
|
{
|
|
int* p = (int*)pBuffer;
|
|
int i, j = count / 4;
|
|
for (i = 0; i < j; i++)
|
|
{
|
|
int data = *(p + i);
|
|
cs += (data & 0xff) + (data >> 8 & 0xff) + (data >> 16 & 0xff) + (data >> 24 & 0xff);
|
|
}
|
|
for (i = i * 4; i < count; i++)
|
|
{
|
|
cs += buffer[i];
|
|
}
|
|
}
|
|
|
|
size -= count;
|
|
}
|
|
return cs;
|
|
}
|
|
}
|
|
|
|
private void ExtractImg(long offset, Wz_Node parent, long eob)
|
|
{
|
|
int entries = 0;
|
|
string tag = this.WzFile.ReadString(offset);
|
|
switch (tag)
|
|
{
|
|
case "Property":
|
|
this.WzFile.FileStream.Position += 2;
|
|
entries = this.WzFile.ReadInt32();
|
|
for (int i = 0; i < entries; i++)
|
|
{
|
|
ExtractValue(offset, parent);
|
|
}
|
|
break;
|
|
|
|
case "Shape2D#Vector2D":
|
|
parent.Value = new Wz_Vector(this.WzFile.ReadInt32(), this.WzFile.ReadInt32());
|
|
break;
|
|
|
|
case "Canvas":
|
|
this.WzFile.FileStream.Position++;
|
|
if (this.WzFile.BReader.ReadByte() == 0x01)
|
|
{
|
|
this.WzFile.FileStream.Position += 2;
|
|
entries = this.WzFile.ReadInt32();
|
|
for (int i = 0; i < entries; i++)
|
|
{
|
|
ExtractValue(offset, parent);
|
|
}
|
|
}
|
|
int w = this.WzFile.ReadInt32();
|
|
int h = this.WzFile.ReadInt32();
|
|
int form = this.WzFile.ReadInt32() + this.WzFile.BReader.ReadByte();
|
|
this.WzFile.FileStream.Position += 4;
|
|
int bufsize = this.WzFile.BReader.ReadInt32();
|
|
parent.Value = new Wz_Png(w, h, bufsize - 1, form, (uint)this.WzFile.FileStream.Position + 1, this);
|
|
this.WzFile.FileStream.Position += bufsize;
|
|
break;
|
|
|
|
case "Shape2D#Convex2D":
|
|
entries = this.WzFile.ReadInt32();
|
|
for (int i = 0; i < entries; i++)
|
|
{
|
|
ExtractImg(offset, parent, 0);
|
|
}
|
|
break;
|
|
|
|
case "Sound_DX8":
|
|
this.WzFile.FileStream.Position++;
|
|
int len = this.WzFile.ReadInt32();
|
|
int ms = this.WzFile.ReadInt32();
|
|
int headerLen = (int)(eob - len - this.WzFile.FileStream.Position);
|
|
byte[] header = this.WzFile.BReader.ReadBytes(headerLen);
|
|
parent.Value = new Wz_Sound((uint)(eob - len), len, header, ms, this);
|
|
this.WzFile.FileStream.Position = eob;
|
|
break;
|
|
|
|
case "UOL":
|
|
this.WzFile.FileStream.Position++;
|
|
parent.Value = new Wz_Uol(this.WzFile.ReadString(offset));
|
|
break;
|
|
|
|
default:
|
|
throw new Exception("unknown wz tag: " + tag);
|
|
}
|
|
}
|
|
|
|
private void TryDetectEnc()
|
|
{
|
|
Wz_Crypto crypto = this.WzFile.WzStructure.encryption;
|
|
|
|
if (crypto.EncType != Wz_Crypto.Wz_CryptoKeyType.Unknown)
|
|
{
|
|
if (IsIllegalTag())
|
|
{
|
|
this.Encryption = new Wz_Crypto() { EncType = crypto.EncType };
|
|
return;
|
|
}
|
|
}
|
|
var oldenc = crypto.EncType;
|
|
crypto.EncType = Wz_Crypto.Wz_CryptoKeyType.KMS;
|
|
if (IsIllegalTag())
|
|
{
|
|
this.Encryption = new Wz_Crypto() { EncType = crypto.EncType };
|
|
return;
|
|
}
|
|
|
|
crypto.EncType = Wz_Crypto.Wz_CryptoKeyType.GMS;
|
|
if (IsIllegalTag())
|
|
{
|
|
this.Encryption = new Wz_Crypto() { EncType = crypto.EncType };
|
|
return;
|
|
}
|
|
|
|
crypto.EncType = Wz_Crypto.Wz_CryptoKeyType.BMS;
|
|
if (IsIllegalTag())
|
|
{
|
|
this.Encryption = new Wz_Crypto() { EncType = crypto.EncType };
|
|
return;
|
|
}
|
|
|
|
crypto.EncType = oldenc;
|
|
}
|
|
|
|
private bool IsIllegalTag()
|
|
{
|
|
this.WzFile.FileStream.Position = this.Offset;
|
|
this.WzFile.stringTable.Remove(Offset);
|
|
switch (this.WzFile.ReadString(Offset))
|
|
{
|
|
case "Property":
|
|
case "Shape2D#Vector2D":
|
|
case "Canvas":
|
|
case "Shape2D#Convex2D":
|
|
case "Sound_DX8":
|
|
case "UOL":
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
private void ExtractValue(long offset, Wz_Node parent)
|
|
{
|
|
parent = parent.Nodes.Add(this.WzFile.ReadString(offset));
|
|
byte flag = this.WzFile.BReader.ReadByte();
|
|
switch (flag)
|
|
{
|
|
case 0x00:
|
|
parent.Value = null;
|
|
break;
|
|
|
|
case 0x02:
|
|
case 0x0B:
|
|
parent.Value = this.WzFile.BReader.ReadInt16();
|
|
break;
|
|
|
|
case 0x03:
|
|
case 0x13:
|
|
// case 0x14:
|
|
parent.Value = this.WzFile.ReadInt32();
|
|
break;
|
|
|
|
case 0x14:
|
|
parent.Value = this.WzFile.ReadInt64();
|
|
break;
|
|
|
|
case 0x04:
|
|
parent.Value = this.WzFile.ReadSingle();
|
|
break;
|
|
|
|
case 0x05:
|
|
parent.Value = this.WzFile.BReader.ReadDouble();
|
|
break;
|
|
|
|
case 0x08:
|
|
parent.Value = this.WzFile.ReadString(offset);
|
|
break;
|
|
|
|
case 0x09:
|
|
ExtractImg(offset, parent, this.WzFile.BReader.ReadInt32() + this.WzFile.FileStream.Position);
|
|
break;
|
|
|
|
default:
|
|
throw new Exception("读取值错误." + flag + " at Offset: " + this.WzFile.FileStream.Position);
|
|
}
|
|
}
|
|
|
|
private void ExtractLua()
|
|
{
|
|
while(this.WzFile.FileStream.Position < this.Offset + this.Size)
|
|
{
|
|
var flag = this.WzFile.BReader.ReadByte();
|
|
|
|
switch (flag)
|
|
{
|
|
case 0x01:
|
|
ExtractLuaValue(this.Node);
|
|
break;
|
|
|
|
default:
|
|
throw new Exception("读取Lua错误." + flag + " at Offset: " + this.WzFile.FileStream.Position);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExtractLuaValue(Wz_Node parent)
|
|
{
|
|
int len = this.WzFile.ReadInt32();
|
|
byte[] data = this.WzFile.BReader.ReadBytes(len);
|
|
if (this.Encryption == null)
|
|
{
|
|
TryDetectLuaEnc(data);
|
|
}
|
|
this.Encryption.keys.Decrypt(data, 0, data.Length);
|
|
string luaCode = Encoding.UTF8.GetString(data);
|
|
parent.Value = luaCode;
|
|
}
|
|
|
|
private void TryDetectLuaEnc(byte[] luaBinary)
|
|
{
|
|
Wz_Crypto crypto = this.WzFile.WzStructure.encryption;
|
|
byte[] tempBuffer = new byte[Math.Min(luaBinary.Length, 64)];
|
|
char[] tempStr = new char[tempBuffer.Length];
|
|
|
|
//测试各种加密方式 判断符合度最高的
|
|
int maxCharCount = 0;
|
|
var maxCharEnc = Wz_Crypto.Wz_CryptoKeyType.Unknown;
|
|
|
|
foreach (var enc in new[] {
|
|
Wz_Crypto.Wz_CryptoKeyType.GMS,
|
|
Wz_Crypto.Wz_CryptoKeyType.KMS,
|
|
Wz_Crypto.Wz_CryptoKeyType.BMS
|
|
})
|
|
{
|
|
Buffer.BlockCopy(luaBinary, 0, tempBuffer, 0, tempBuffer.Length);
|
|
crypto.EncType = enc;
|
|
crypto.keys.Decrypt(tempBuffer, 0, tempBuffer.Length);
|
|
int count = Encoding.UTF8.GetChars(tempBuffer, 0, tempBuffer.Length, tempStr, 0);
|
|
int asciiCount = tempStr.Take(count).Count(chr => 32 <= chr && chr <= 127);
|
|
|
|
if (maxCharCount < asciiCount)
|
|
{
|
|
maxCharEnc = enc;
|
|
maxCharCount = asciiCount;
|
|
}
|
|
}
|
|
|
|
crypto.EncType = maxCharEnc;
|
|
this.Encryption = new Wz_Crypto() { EncType = crypto.EncType };
|
|
}
|
|
|
|
internal class Wz_ImageNode : Wz_Node
|
|
{
|
|
public Wz_ImageNode(string nodeText, Wz_Image image) : base(nodeText)
|
|
{
|
|
this.Image = image;
|
|
}
|
|
|
|
public Wz_Image Image { get; private set; }
|
|
}
|
|
}
|
|
}
|