From 4c6b53aa26185dafe4851ae1379f18fa588ef969 Mon Sep 17 00:00:00 2001
From: Elem8100 <49195287+Elem8100@users.noreply.github.com>
Date: Fri, 14 Jan 2022 20:35:16 +0800
Subject: [PATCH] Add files via upload
---
WzComparerR2.WzLib/WzComparerR2.WzLib.csproj | 70 ++
WzComparerR2.WzLib/Wz_Crypto.cs | 329 ++++++++
WzComparerR2.WzLib/Wz_Directory.cs | 28 +
WzComparerR2.WzLib/Wz_File.cs | 762 ++++++++++++++++++
WzComparerR2.WzLib/Wz_Header.cs | 154 ++++
WzComparerR2.WzLib/Wz_Image.cs | 403 ++++++++++
WzComparerR2.WzLib/Wz_Node.cs | 770 +++++++++++++++++++
WzComparerR2.WzLib/Wz_Png.cs | 562 ++++++++++++++
WzComparerR2.WzLib/Wz_Sound.cs | 196 +++++
WzComparerR2.WzLib/Wz_SoundType.cs | 13 +
WzComparerR2.WzLib/Wz_Structure.cs | 269 +++++++
WzComparerR2.WzLib/Wz_Type.cs | 30 +
WzComparerR2.WzLib/Wz_Uol.cs | 78 ++
WzComparerR2.WzLib/Wz_Vector.cs | 42 +
14 files changed, 3706 insertions(+)
create mode 100644 WzComparerR2.WzLib/WzComparerR2.WzLib.csproj
create mode 100644 WzComparerR2.WzLib/Wz_Crypto.cs
create mode 100644 WzComparerR2.WzLib/Wz_Directory.cs
create mode 100644 WzComparerR2.WzLib/Wz_File.cs
create mode 100644 WzComparerR2.WzLib/Wz_Header.cs
create mode 100644 WzComparerR2.WzLib/Wz_Image.cs
create mode 100644 WzComparerR2.WzLib/Wz_Node.cs
create mode 100644 WzComparerR2.WzLib/Wz_Png.cs
create mode 100644 WzComparerR2.WzLib/Wz_Sound.cs
create mode 100644 WzComparerR2.WzLib/Wz_SoundType.cs
create mode 100644 WzComparerR2.WzLib/Wz_Structure.cs
create mode 100644 WzComparerR2.WzLib/Wz_Type.cs
create mode 100644 WzComparerR2.WzLib/Wz_Uol.cs
create mode 100644 WzComparerR2.WzLib/Wz_Vector.cs
diff --git a/WzComparerR2.WzLib/WzComparerR2.WzLib.csproj b/WzComparerR2.WzLib/WzComparerR2.WzLib.csproj
new file mode 100644
index 0000000..a723bc2
--- /dev/null
+++ b/WzComparerR2.WzLib/WzComparerR2.WzLib.csproj
@@ -0,0 +1,70 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}
+ Library
+ Properties
+ WzComparerR2.WzLib
+ WzComparerR2.WzLib
+ v4.0
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ TRACE;DEBUG
+ prompt
+ 4
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+
+
+
+
+
+
+ Properties\CommonAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WzComparerR2.WzLib/Wz_Crypto.cs b/WzComparerR2.WzLib/Wz_Crypto.cs
new file mode 100644
index 0000000..f4f0bbc
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Crypto.cs
@@ -0,0 +1,329 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Security.Cryptography;
+using System.Collections.Specialized;
+using System.Text.RegularExpressions;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Crypto
+ {
+ public Wz_Crypto()
+ {
+ this.keys_bms = new Wz_CryptoKey(iv_bms);
+ this.keys_kms = new Wz_CryptoKey(iv_kms);
+ this.keys_gms = new Wz_CryptoKey(iv_gms);
+ this.listwz = false;
+ this.EncType = Wz_CryptoKeyType.Unknown;
+ this.List = new StringCollection();
+ }
+
+ public void Reset()
+ {
+ this.encryption_detected = false;
+ this.listwz = false;
+ this.EncType = Wz_CryptoKeyType.Unknown;
+ this.List.Clear();
+ }
+
+ public bool list_contains(string name)
+ {
+ bool contains = this.List.Contains(name);
+ if (contains)
+ this.List.Remove(name);
+ return contains;
+ // foreach (string list_entry in this.list)
+ // {
+ // // if (list_entry.Contains(Name))
+ // if (list_entry == Name)
+ // {
+ // this.list.Remove(list_entry);
+ // return true;
+ // }
+ // }
+ // return false;
+ }
+
+ public void LoadListWz(string path)
+ {
+ path = Path.Combine(path, "List.wz");
+ if (File.Exists(path))
+ {
+ this.listwz = true;
+ using (FileStream list_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ BinaryReader listwz = new BinaryReader(list_file);
+ int length = (int)list_file.Length;
+ int len = 0;
+ byte b = 0;
+ string folder = "";
+ list_file.Position += 4;
+ byte check_for_d = listwz.ReadByte();
+
+ if ((char)(check_for_d ^ this.keys_gms[0]) == 'd')
+ {
+ this.EncType = Wz_CryptoKeyType.GMS;
+ }
+ else if ((char)(check_for_d ^ this.keys_kms[0]) == 'd')
+ {
+ this.EncType = Wz_CryptoKeyType.KMS;
+ }
+
+ list_file.Position = 0;
+ while (list_file.Position < length)
+ {
+ len = listwz.ReadInt32() * 2;
+ for (int i = 0; i < len; i += 2)
+ {
+ b = (byte)(listwz.ReadByte() ^ this.keys[i]);
+ folder += (char)(b);
+ list_file.Position++;
+ }
+ list_file.Position += 2;
+ folder.Replace(".im/", ".img");
+ this.List.Add(folder);
+ folder = "";
+ }
+ this.List.Remove("dummy");
+ }
+ }
+ }
+
+ public void DetectEncryption(Wz_File f)
+ {
+ int old_off = (int)f.FileStream.Position;
+ f.FileStream.Position = f.Header.DataStartPosition;
+ if (f.ReadInt32() <= 0) //只有文件头 无法预判
+ {
+ return;
+ }
+ f.FileStream.Position++;
+ int len = (int)(-f.BReader.ReadSByte());
+ byte[] bytes = f.BReader.ReadBytes(len);
+
+ for (int i = 0; i < len; i++)
+ {
+ bytes[i] ^= (byte)(0xAA + i);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ if (!this.encryption_detected)
+ {
+ //测试bms
+ sb.Clear();
+ for (int i = 0; i < len; i++)
+ {
+ sb.Append((char)(keys_bms[i] ^ bytes[i]));
+ }
+ if (IsLegalNodeName(sb.ToString()))
+ {
+ this.EncType = Wz_CryptoKeyType.BMS;
+ this.encryption_detected = true;
+ goto lbl_end;
+ }
+
+ //测试kms
+ sb.Clear();
+ for (int i = 0; i < len; i++)
+ {
+ sb.Append((char)(keys_kms[i] ^ bytes[i]));
+ }
+ if (IsLegalNodeName(sb.ToString()))
+ {
+ this.EncType = Wz_CryptoKeyType.KMS;
+ this.encryption_detected = true;
+ goto lbl_end;
+ }
+
+ //测试gms
+ sb.Clear();
+ for (int i = 0; i < len; i++)
+ {
+ sb.Append((char)(keys_gms[i] ^ bytes[i]));
+ }
+ if (IsLegalNodeName(sb.ToString()))
+ {
+ this.EncType = Wz_CryptoKeyType.GMS;
+ this.encryption_detected = true;
+ goto lbl_end;
+ }
+ }
+
+ lbl_end:
+ f.FileStream.Position = old_off;
+ }
+
+ public bool IsLegalNodeName(string nodeName)
+ {
+ return nodeName.EndsWith(".img") || nodeName.EndsWith(".lua") || Regex.IsMatch(nodeName, @"^[A-Za-z-9_]+$");
+ }
+
+ static readonly byte[] iv_gms = { 0x4d, 0x23, 0xc7, 0x2b };
+ static readonly byte[] iv_kms = { 0xb9, 0x7d, 0x63, 0xe9 };
+ static readonly byte[] iv_bms = { 0x00, 0x00, 0x00, 0x00 };
+
+ private Wz_CryptoKey keys_bms, keys_gms, keys_kms;
+ private Wz_CryptoKeyType enc_type;
+
+ public bool encryption_detected = false;
+ public bool listwz = false;
+
+ public Wz_CryptoKey keys { get; private set; }
+ public StringCollection List { get; private set; }
+
+ public Wz_CryptoKeyType EncType
+ {
+ get { return enc_type; }
+ set
+ {
+ enc_type = value;
+ switch (enc_type)
+ {
+ case Wz_CryptoKeyType.Unknown:
+ this.keys = null;
+ break;
+
+ case Wz_CryptoKeyType.BMS:
+ this.keys = keys_bms;
+ break;
+
+ case Wz_CryptoKeyType.KMS:
+ this.keys = keys_kms;
+ break;
+
+ case Wz_CryptoKeyType.GMS:
+ this.keys = keys_gms;
+ break;
+ }
+ }
+ }
+
+ public enum Wz_CryptoKeyType
+ {
+ Unknown = 0,
+ BMS = 1,
+ KMS = 2,
+ GMS = 3
+ }
+
+ public class Wz_CryptoKey
+ {
+ public Wz_CryptoKey(byte[] iv)
+ {
+ this.iv = iv;
+ if (iv == null || BitConverter.ToInt32(iv, 0) == 0)
+ {
+ this.isEmptyIV = true;
+ }
+ }
+
+ private byte[] keys;
+ private byte[] iv;
+ private bool isEmptyIV;
+
+ public byte this[int index]
+ {
+ get
+ {
+ if (isEmptyIV)
+ {
+ return 0;
+ }
+ if (keys == null || keys.Length <= index)
+ {
+ EnsureKeySize(index + 1);
+ }
+ return this.keys[index];
+ }
+ }
+
+ public void EnsureKeySize(int size)
+ {
+ if (isEmptyIV)
+ {
+ return;
+ }
+ if (this.keys != null && this.keys.Length >= size)
+ {
+ return;
+ }
+
+ size = (int)Math.Ceiling(1.0 * size / 4096) * 4096;
+ int startIndex = 0;
+
+ if (this.keys == null)
+ {
+ keys = new byte[size];
+ }
+ else
+ {
+ startIndex = this.keys.Length;
+ Array.Resize(ref this.keys, size);
+ }
+
+ Rijndael aes = Rijndael.Create();
+ aes.KeySize = 256;
+ aes.BlockSize = 128;
+ aes.Key = aesKey;
+ aes.Mode = CipherMode.ECB;
+ MemoryStream ms = new MemoryStream(keys, startIndex, keys.Length - startIndex, true);
+ CryptoStream s = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);
+
+ for (int i = startIndex; i < size; i += 16)
+ {
+ if (i == 0)
+ {
+ byte[] block = new byte[16];
+ for (int j = 0; j < block.Length; j++)
+ {
+ block[j] = iv[j % 4];
+ }
+ s.Write(block, 0, block.Length);
+ }
+ else
+ {
+ s.Write(keys, i - 16, 16);
+ }
+ }
+
+ s.Flush();
+ ms.Close();
+ }
+
+ public unsafe void Decrypt(byte[] buffer, int startIndex, int length)
+ {
+ if (isEmptyIV)
+ return;
+
+ this.EnsureKeySize(length);
+
+ fixed (byte* pBuffer = buffer, pKeys = this.keys)
+ {
+ int i = 0;
+ byte* pData = pBuffer + startIndex;
+
+ for (int i1 = length / 4 * 4; i < i1; i += 4, pData += 4)
+ {
+ *((int*)pData) ^= *(int*)(pKeys + i);
+ }
+
+ for (; i < length; i++, pData++)
+ {
+ *pData ^= *(pKeys + i);
+ }
+ }
+ }
+
+ static readonly byte[] aesKey = {0x13, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00,
+ 0xB4, 0x00, 0x00, 0x00,
+ 0x1B, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00,
+ 0x33, 0x00, 0x00, 0x00,
+ 0x52, 0x00, 0x00, 0x00 };
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Directory.cs b/WzComparerR2.WzLib/Wz_Directory.cs
new file mode 100644
index 0000000..1354ace
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Directory.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Directory
+ {
+ public Wz_Directory(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;
+ }
+
+ 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; }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_File.cs b/WzComparerR2.WzLib/Wz_File.cs
new file mode 100644
index 0000000..42b313f
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_File.cs
@@ -0,0 +1,762 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_File : IDisposable
+ {
+ public Wz_File(string fileName, Wz_Structure wz)
+ {
+ this.imageCount = 0;
+ this.wzStructure = wz;
+ this.fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ this.bReader = new BinaryReader(this.FileStream);
+ this.loaded = this.GetHeader(fileName);
+ this.stringTable = new Dictionary();
+ this.directories = new List();
+ }
+
+ private FileStream fileStream;
+ private BinaryReader bReader;
+ private Wz_Structure wzStructure;
+ private Wz_Header header;
+ private Wz_Node node;
+ private int imageCount;
+ private bool loaded;
+ private bool isSubDir;
+ private Wz_Type type;
+ private List mergedWzFiles;
+ private Wz_File ownerWzFile;
+ private readonly List directories;
+
+ public Encoding TextEncoding { get; set; }
+
+ public readonly object ReadLock = new object();
+
+ internal Dictionary stringTable;
+ internal byte[] tempBuffer;
+
+ public FileStream FileStream
+ {
+ get { return fileStream; }
+ }
+
+ public BinaryReader BReader
+ {
+ get { return bReader; }
+ }
+
+ public Wz_Structure WzStructure
+ {
+ get { return wzStructure; }
+ set { wzStructure = value; }
+ }
+
+ public Wz_Header Header
+ {
+ get { return header; }
+ private set { header = value; }
+ }
+
+ public Wz_Node Node
+ {
+ get { return node; }
+ set { node = value; }
+ }
+
+ public int ImageCount
+ {
+ get { return imageCount; }
+ }
+
+ public bool Loaded
+ {
+ get { return loaded; }
+ }
+
+ public bool IsSubDir
+ {
+ get { return this.isSubDir; }
+ }
+
+ public Wz_Type Type
+ {
+ get { return type; }
+ set { type = value; }
+ }
+
+ public IEnumerable MergedWzFiles
+ {
+ get { return this.mergedWzFiles ?? Enumerable.Empty(); }
+ }
+
+ public Wz_File OwnerWzFile
+ {
+ get { return this.ownerWzFile; }
+ }
+
+ public void Close()
+ {
+ if (this.bReader != null)
+ this.bReader.Close();
+ if (this.fileStream != null)
+ this.fileStream.Close();
+ }
+
+ void IDisposable.Dispose()
+ {
+ this.Close();
+ }
+
+ private bool GetHeader(string fileName)
+ {
+ this.fileStream.Position = 0;
+ long filesize = this.FileStream.Length;
+ if (filesize < 4) { goto __failed; }
+
+ string signature = new string(this.BReader.ReadChars(4));
+ if (signature != "PKG1") { goto __failed; }
+
+ long dataSize = this.BReader.ReadInt64();
+ int headerSize = this.BReader.ReadInt32();
+ string copyright = new string(this.BReader.ReadChars(headerSize - (int)this.FileStream.Position));
+
+ // encver detecting:
+ // Since KMST1132, wz removed the 2 bytes encver, and use a fixed wzver '777'.
+ // Here we try to read the first 2 bytes from data part and guess if it looks like an encver.
+ bool encverMissing = false;
+ int encver = -1;
+ if (dataSize >= 2)
+ {
+ this.fileStream.Position = headerSize;
+ encver = this.BReader.ReadUInt16();
+ // encver always less than 256
+ if (encver > 0xff)
+ {
+ encverMissing = true;
+ }
+ else if (encver == 0x80)
+ {
+ // there's an exceptional case that the first field of data part is a compressed int which determined property count,
+ // if the value greater than 127 and also to be a multiple of 256, the first 5 bytes will become to
+ // 80 00 xx xx xx
+ // so we additional check the int value, at most time the child node count in a wz won't greater than 65536.
+ if (dataSize >= 5)
+ {
+ this.fileStream.Position = headerSize;
+ int propCount = this.ReadInt32();
+ if (propCount > 0 && (propCount & 0xff) == 0 && propCount <= 0xffff)
+ {
+ encverMissing = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Obviously, if data part have only 1 byte, encver must be deleted.
+ encverMissing = true;
+ }
+
+ int dataStartPos = headerSize + (encverMissing ? 0 : 2);
+ this.Header = new Wz_Header(signature, copyright, fileName, headerSize, dataSize, filesize, dataStartPos);
+
+ if (encverMissing)
+ {
+ // not sure if nexon will change this magic version, just hard coded.
+ this.Header.SetWzVersion(777);
+ this.Header.VersionChecked = true;
+ }
+ else
+ {
+ this.Header.SetOrdinalVersionDetector(encver);
+ }
+
+ return true;
+
+ __failed:
+ this.header = new Wz_Header(null, null, fileName, 0, 0, filesize, 0);
+ return false;
+ }
+
+ public int ReadInt32()
+ {
+ int s = this.BReader.ReadSByte();
+ return (s == -128) ? this.BReader.ReadInt32() : s;
+ }
+
+ public long ReadInt64()
+ {
+ int s = this.BReader.ReadSByte();
+ return (s == -128) ? this.BReader.ReadInt64() : s;
+ }
+
+ public float ReadSingle()
+ {
+ float fl = this.BReader.ReadSByte();
+ return (fl == -128) ? this.BReader.ReadSingle() : fl;
+ }
+
+ public string ReadString(long offset)
+ {
+ byte b = this.BReader.ReadByte();
+ switch (b)
+ {
+ case 0x00:
+ case 0x73:
+ return ReadString();
+
+ case 0x01:
+ case 0x1B:
+ return ReadStringAt(offset + this.BReader.ReadInt32());
+
+ case 0x04:
+ this.FileStream.Position += 8;
+ break;
+
+ default:
+ throw new Exception("문자열을 읽는 중 오류: " + this.FileStream.Name + " " + this.FileStream.Position);
+ }
+ return string.Empty;
+ }
+
+ public string ReadStringAt(long offset)
+ {
+ long oldoffset = this.FileStream.Position;
+ string str;
+ if (!stringTable.TryGetValue(offset, out str))
+ {
+ this.FileStream.Position = offset;
+ str = ReadString();
+ stringTable[offset] = str;
+ this.FileStream.Position = oldoffset;
+ }
+ return str;
+ }
+
+ public unsafe string ReadString()
+ {
+ int size = this.BReader.ReadSByte();
+ string result = null;
+ if (size < 0)
+ {
+ byte mask = 0xAA;
+ size = (size == -128) ? this.BReader.ReadInt32() : -size;
+
+ var buffer = GetStringBuffer(size);
+ this.fileStream.Read(buffer, 0, size);
+ this.WzStructure.encryption.keys.Decrypt(buffer, 0, size);
+
+ fixed (byte* pData = buffer)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ pData[i] ^= mask;
+ unchecked { mask++; }
+ }
+
+ var enc = this.TextEncoding ?? Encoding.Default;
+ result = enc.GetString(buffer, 0, size);
+ }
+ }
+ else if (size > 0)
+ {
+ ushort mask = 0xAAAA;
+ if (size == 127)
+ {
+ size = this.BReader.ReadInt32();
+ }
+
+ var buffer = GetStringBuffer(size * 2);
+ this.fileStream.Read(buffer, 0, size * 2);
+ this.WzStructure.encryption.keys.Decrypt(buffer, 0, size * 2);
+
+ fixed (byte* pData = buffer)
+ {
+ ushort* pChar = (ushort*)pData;
+ for (int i = 0; i < size; i++)
+ {
+ pChar[i] ^= mask;
+ unchecked { mask++; }
+ }
+
+ result = new string((char*)pChar, 0, size);
+ }
+ }
+ else
+ {
+ return string.Empty;
+ }
+
+ //memory optimize
+ if (result.Length <= 4)
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ if (result[i] >= 0x80)
+ {
+ return result;
+ }
+ }
+ return string.Intern(result);
+ }
+ else
+ {
+ return result;
+ }
+ }
+
+ ///
+ /// 为字符串解密提供缓冲区。
+ ///
+ ///
+ ///
+ private byte[] GetStringBuffer(int size)
+ {
+ if (size <= 4096)
+ {
+ if (tempBuffer == null || tempBuffer.Length < size)
+ {
+ Array.Resize(ref tempBuffer, size);
+ }
+ return tempBuffer;
+ }
+ else
+ {
+ return new byte[size];
+ }
+ }
+
+ public uint CalcOffset(uint filePos, uint hashedOffset)
+ {
+ uint offset = (uint)(filePos - 0x3C) ^ 0xFFFFFFFF;
+ int distance;
+
+ offset *= this.Header.HashVersion;
+ offset -= 0x581C3F6D;
+ distance = (int)offset & 0x1F;
+ offset = (offset << distance) | (offset >> (32 - distance));
+ offset ^= hashedOffset;
+ offset += 0x78;
+
+ return offset;
+ }
+
+ public void GetDirTree(Wz_Node parent, bool useBaseWz = false, bool loadWzAsFolder = false)
+ {
+ List dirs = new List();
+ string name = null;
+ int size = 0;
+ int cs32 = 0;
+ uint pos = 0, hashOffset = 0;
+ //int offs = 0;
+
+ int count = ReadInt32();
+
+ for (int i = 0; i < count; i++)
+ {
+ switch ((int)this.BReader.ReadByte())
+ {
+ case 0x02:
+ foreach (Wz_Crypto.Wz_CryptoKeyType encType in new[] { Wz_Crypto.Wz_CryptoKeyType.BMS, Wz_Crypto.Wz_CryptoKeyType.KMS, Wz_Crypto.Wz_CryptoKeyType.GMS, this.WzStructure.encryption.EncType })
+ {
+ long oldoffset = this.FileStream.Position;
+ int stroffset = this.Header.HeaderSize + 1 + this.BReader.ReadInt32();
+ name = this.ReadStringAt(stroffset);
+ if (this.WzStructure.encryption.IsLegalNodeName(name))
+ {
+ break;
+ }
+ this.FileStream.Position = oldoffset;
+ stringTable.Remove(stroffset);
+ this.WzStructure.encryption.EncType = encType;
+ }
+ goto case 0xffff;
+ case 0x04:
+ foreach (Wz_Crypto.Wz_CryptoKeyType encType in new[] { Wz_Crypto.Wz_CryptoKeyType.BMS, Wz_Crypto.Wz_CryptoKeyType.KMS, Wz_Crypto.Wz_CryptoKeyType.GMS, this.WzStructure.encryption.EncType })
+ {
+ long oldoffset = this.FileStream.Position;
+ name = this.ReadString();
+ if (this.WzStructure.encryption.IsLegalNodeName(name))
+ {
+ break;
+ }
+ this.FileStream.Position = oldoffset;
+ this.WzStructure.encryption.EncType = encType;
+ }
+ goto case 0xffff;
+
+ case 0xffff:
+ size = this.ReadInt32();
+ cs32 = this.ReadInt32();
+ pos = (uint)this.bReader.BaseStream.Position;
+ hashOffset = this.bReader.ReadUInt32();
+
+ Wz_Image img = new Wz_Image(name, size, cs32, hashOffset, pos, this);
+ Wz_Node childNode = parent.Nodes.Add(name);
+ childNode.Value = img;
+ img.OwnerNode = childNode;
+
+ this.imageCount++;
+ break;
+
+ case 0x03:
+ name = this.ReadString();
+ size = this.ReadInt32();
+ cs32 = this.ReadInt32();
+ pos = (uint)this.bReader.BaseStream.Position;
+ hashOffset = this.bReader.ReadUInt32();
+ this.directories.Add(new Wz_Directory(name, size, cs32, hashOffset, pos, this));
+ dirs.Add(name);
+ break;
+ }
+ }
+
+ int dirCount = dirs.Count;
+ bool willLoadBaseWz = useBaseWz ? parent.Text.Equals("base.wz", StringComparison.OrdinalIgnoreCase) : false;
+
+ var baseFolder = Path.GetDirectoryName(this.header.FileName);
+
+ if (willLoadBaseWz && this.WzStructure.AutoDetectExtFiles)
+ {
+ for (int i = 0; i < dirCount; i++)
+ {
+ //检测文件名
+ var m = Regex.Match(dirs[i], @"^([A-Za-z]+)$");
+ if (m.Success)
+ {
+ string wzTypeName = m.Result("$1");
+
+ //检测扩展wz文件
+ for (int fileID = 2; ; fileID++)
+ {
+ string extDirName = wzTypeName + fileID;
+ string extWzFile = Path.Combine(baseFolder, extDirName + ".wz");
+ if (File.Exists(extWzFile))
+ {
+ if (!dirs.Take(dirCount).Any(dir => extDirName.Equals(dir, StringComparison.OrdinalIgnoreCase)))
+ {
+ dirs.Add(extDirName);
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ //检测KMST1058的wz文件
+ for (int fileID = 1; ; fileID++)
+ {
+ string extDirName = wzTypeName + fileID.ToString("D3");
+ string extWzFile = Path.Combine(baseFolder, extDirName + ".wz");
+ if (File.Exists(extWzFile))
+ {
+ if (!dirs.Take(dirCount).Any(dir => extDirName.Equals(dir, StringComparison.OrdinalIgnoreCase)))
+ {
+ dirs.Add(extDirName);
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < dirs.Count; i++)
+ {
+ string dir = dirs[i];
+ Wz_Node t = parent.Nodes.Add(dir);
+ if (i < dirCount)
+ {
+ GetDirTree(t, false);
+ }
+
+ if (t.Nodes.Count == 0)
+ {
+ this.WzStructure.has_basewz |= willLoadBaseWz;
+
+ try
+ {
+ if (loadWzAsFolder)
+ {
+ string wzFolder = willLoadBaseWz ? Path.Combine(Path.GetDirectoryName(baseFolder), dir) : Path.Combine(baseFolder, dir);
+ if (Directory.Exists(wzFolder))
+ {
+ this.wzStructure.LoadWzFolder(wzFolder, ref t, false);
+ if (!willLoadBaseWz)
+ {
+ var dirWzFile = t.GetValue();
+ dirWzFile.Type = Wz_Type.Unknown;
+ dirWzFile.isSubDir = true;
+ }
+ }
+ }
+ else if (willLoadBaseWz)
+ {
+ string filePath = Path.Combine(baseFolder, dir + ".wz");
+ if (File.Exists(filePath))
+ {
+ this.WzStructure.LoadFile(filePath, t, false, loadWzAsFolder);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+
+ parent.Nodes.Trim();
+ }
+
+ private string getFullPath(Wz_Node parent, string name)
+ {
+ List path = new List(5);
+ path.Add(name.ToLower());
+ while (parent != null && !(parent.Value is Wz_File))
+ {
+ path.Insert(0, parent.Text.ToLower());
+ parent = parent.ParentNode;
+ }
+ if (parent != null)
+ {
+ path.Insert(0, parent.Text.ToLower().Replace(".wz", ""));
+ }
+ return string.Join("/", path.ToArray());
+ }
+
+ public void DetectWzType()
+ {
+ this.type = Wz_Type.Unknown;
+ if (this.node == null)
+ {
+ return;
+ }
+
+ if (this.node.Nodes["smap.img"] != null
+ || this.node.Nodes["zmap.img"] != null)
+ {
+ this.type = Wz_Type.Base;
+ }
+ else if (this.node.Nodes["00002000.img"] != null
+ || this.node.Nodes["Accessory"] != null
+ || this.node.Nodes["Weapon"] != null)
+ {
+ this.type = Wz_Type.Character;
+ }
+ else if (this.node.Nodes["BasicEff.img"] != null
+ || this.node.Nodes["SetItemInfoEff.img"] != null)
+ {
+ this.type = Wz_Type.Effect;
+ }
+ else if (this.node.Nodes["Commodity.img"] != null
+ || this.node.Nodes["Curse.img"] != null)
+ {
+ this.type = Wz_Type.Etc;
+ }
+ else if (this.node.Nodes["Cash"] != null
+ || this.node.Nodes["Consume"] != null)
+ {
+ this.type = Wz_Type.Item;
+ }
+ else if (this.node.Nodes["Back"] != null
+ || this.node.Nodes["Obj"] != null
+ || this.node.Nodes["Physics.img"] != null)
+ {
+ this.type = Wz_Type.Map;
+ }
+ else if (this.node.Nodes["PQuest.img"] != null
+ || this.node.Nodes["QuestData"] != null)
+ {
+ this.type = Wz_Type.Quest;
+ }
+ else if (this.node.Nodes["Attacktype.img"] != null
+ || this.node.Nodes["Recipe_9200.img"] != null)
+ {
+ this.type = Wz_Type.Skill;
+ }
+ else if (this.node.Nodes["Bgm00.img"] != null
+ || this.node.Nodes["BgmUI.img"] != null)
+ {
+ this.type = Wz_Type.Sound;
+ }
+ else if (this.node.Nodes["MonsterBook.img"] != null
+ || this.node.Nodes["EULA.img"] != null)
+ {
+ this.type = Wz_Type.String;
+ }
+ else if (this.node.Nodes["CashShop.img"] != null
+ || this.node.Nodes["UIWindow.img"] != null)
+ {
+ this.type = Wz_Type.UI;
+ }
+
+ if (this.type == Wz_Type.Unknown) //用文件名来判断
+ {
+ string wzName = this.node.Text;
+
+ Match m = Regex.Match(wzName, @"^([A-Za-z]+)_?(\d+)?(?:\.wz)?$");
+ if (m.Success)
+ {
+ wzName = m.Result("$1");
+ }
+ this.type = Enum.TryParse(wzName, true, out var result) ? result : Wz_Type.Unknown;
+ }
+ }
+
+ public void DetectWzVersion()
+ {
+ bool DetectWithWzImage(Wz_Image testWzImg)
+ {
+ while (this.header.TryGetNextVersion())
+ {
+ uint offs = CalcOffset(testWzImg.HashedOffsetPosition, testWzImg.HashedOffset);
+
+ if (offs < this.header.HeaderSize || offs + testWzImg.Size > this.fileStream.Length) //img块越界
+ {
+ continue;
+ }
+
+ this.fileStream.Position = offs;
+ switch (this.fileStream.ReadByte())
+ {
+ case 0x73:
+ case 0x1b:
+ //试读img第一个string
+ break;
+ default:
+ continue;
+ }
+
+ testWzImg.Offset = offs;
+ if (testWzImg.TryExtract()) //试读成功
+ {
+ testWzImg.Unextract();
+ this.header.VersionChecked = true;
+ break;
+ }
+ }
+ return this.header.VersionChecked;
+ }
+
+ bool DetectWithAllWzDir()
+ {
+ while (this.header.TryGetNextVersion())
+ {
+ bool isSuccess = true;
+ foreach (var testDir in this.directories)
+ {
+ uint offs = CalcOffset(testDir.HashedOffsetPosition, testDir.HashedOffset);
+
+ if (offs < this.header.HeaderSize || offs + 1 > this.fileStream.Length) // dir offset out of file size.
+ {
+ isSuccess = false;
+ break;
+ }
+
+ this.fileStream.Position = offs;
+ if (this.fileStream.ReadByte() != 0) // dir data only contains one byte: 0x00
+ {
+ isSuccess = false;
+ break;
+ }
+ }
+
+ if (isSuccess)
+ {
+ this.header.VersionChecked = true;
+ break;
+ }
+ }
+
+ return this.header.VersionChecked;
+ }
+
+ List imgList = EnumerableAllWzImage(this.node).Where(_img => _img.WzFile == this).ToList();
+
+ if (this.header.VersionChecked)
+ {
+ foreach (var img in imgList)
+ {
+ img.Offset = CalcOffset(img.HashedOffsetPosition, img.HashedOffset);
+ }
+ }
+ else
+ {
+ //选择最小的img作为实验品
+ Wz_Image minSizeImg = imgList.Where(_img => _img.Size >= 20).DefaultIfEmpty().Aggregate((_img1, _img2) => _img1.Size < _img2.Size ? _img1 : _img2);
+
+ if (minSizeImg == null && imgList.Count > 0)
+ {
+ minSizeImg = imgList[0];
+ }
+
+ if (minSizeImg != null)
+ {
+ DetectWithWzImage(minSizeImg);
+ }
+ else if (this.directories.Count > 0)
+ {
+ DetectWithAllWzDir();
+ }
+
+ if (this.header.VersionChecked) //重新计算全部img
+ {
+ foreach (var img in imgList)
+ {
+ img.Offset = CalcOffset(img.HashedOffsetPosition, img.HashedOffset);
+ }
+ }
+ else //最终测试失败 那就失败吧..
+ {
+ this.header.VersionChecked = true;
+ }
+ }
+ }
+
+ public void MergeWzFile(Wz_File wz_File)
+ {
+ var children = wz_File.node.Nodes.ToList();
+ wz_File.node.Nodes.Clear();
+ foreach (var child in children)
+ {
+ this.node.Nodes.Add(child);
+ }
+
+ if (this.mergedWzFiles == null)
+ {
+ this.mergedWzFiles = new List();
+ }
+ this.mergedWzFiles.Add(wz_File);
+
+ wz_File.ownerWzFile = this;
+ }
+
+ private IEnumerable EnumerableAllWzImage(Wz_Node parentNode)
+ {
+ foreach (var node in parentNode.Nodes)
+ {
+ Wz_Image img = node.Value as Wz_Image;
+ if (img != null)
+ {
+ yield return img;
+ }
+
+ if (!(node.Value is Wz_File) && node.Nodes.Count > 0)
+ {
+ foreach (var imgChild in EnumerableAllWzImage(node))
+ {
+ yield return imgChild;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Header.cs b/WzComparerR2.WzLib/Wz_Header.cs
new file mode 100644
index 0000000..8e0ada8
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Header.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Header
+ {
+ public Wz_Header(string signature, string copyright, string file_name, int head_size, long data_size, long file_size, long dataStartPosition)
+ {
+ this.Signature = signature;
+ this.Copyright = copyright;
+ this.FileName = file_name;
+ this.HeaderSize = head_size;
+ this.DataSize = data_size;
+ this.FileSize = file_size;
+ this.DataStartPosition = dataStartPosition;
+ this.VersionChecked = false;
+ }
+
+ public string Signature { get; private set; }
+ public string Copyright { get; private set; }
+ public string FileName { get; private set; }
+
+ public int HeaderSize { get; private set; }
+ public long DataSize { get; private set; }
+ public long FileSize { get; private set; }
+ public long DataStartPosition { get; private set; }
+
+ public bool VersionChecked { get; set; }
+
+ public int WzVersion => this.versionDetector?.WzVersion ?? 0;
+ public uint HashVersion => this.versionDetector?.HashVersion ?? 0;
+ public bool TryGetNextVersion() => this.versionDetector?.TryGetNextVersion() ?? false;
+
+ private IWzVersionDetector versionDetector;
+
+ public void SetWzVersion(int wzVersion)
+ {
+ this.versionDetector = new FixedVersion(wzVersion);
+ }
+
+ public void SetOrdinalVersionDetector(int encryptedVersion)
+ {
+ this.versionDetector = new OrdinalVersionDetector(encryptedVersion);
+ }
+
+ public static int CalcHashVersion(int wzVersion)
+ {
+ int sum = 0;
+ string versionStr = wzVersion.ToString(System.Globalization.CultureInfo.InvariantCulture);
+ for (int j = 0; j < versionStr.Length; j++)
+ {
+ sum <<= 5;
+ sum += (int)versionStr[j] + 1;
+ }
+
+ return sum;
+ }
+
+ public interface IWzVersionDetector
+ {
+ bool TryGetNextVersion();
+ int WzVersion { get; }
+ uint HashVersion { get; }
+ }
+
+ public class FixedVersion : IWzVersionDetector
+ {
+ public FixedVersion(int wzVersion)
+ {
+ this.WzVersion = wzVersion;
+ this.HashVersion = (uint)CalcHashVersion(wzVersion);
+ }
+
+ private bool hasReturned;
+
+
+ public int WzVersion { get; private set; }
+
+ public uint HashVersion { get; private set; }
+
+ public bool TryGetNextVersion()
+ {
+ if (!hasReturned)
+ {
+ hasReturned = true;
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ public class OrdinalVersionDetector : IWzVersionDetector
+ {
+ public OrdinalVersionDetector(int encryptVersion)
+ {
+ this.EncryptedVersion = encryptVersion;
+ this.versionTest = new List();
+ this.hasVersionTest = new List();
+ this.startVersion = -1;
+ }
+
+ public int EncryptedVersion { get; private set; }
+
+ private int startVersion;
+ private List versionTest;
+ private List hasVersionTest;
+
+ public int WzVersion
+ {
+ get
+ {
+ int idx = this.versionTest.Count - 1;
+ return idx > -1 ? this.versionTest[idx] : 0;
+ }
+ }
+
+ public uint HashVersion
+ {
+ get
+ {
+ int idx = this.hasVersionTest.Count - 1;
+ return idx > -1 ? this.hasVersionTest[idx] : 0;
+ }
+ }
+
+ public bool TryGetNextVersion()
+ {
+ for (int i = startVersion + 1; i < Int16.MaxValue; i++)
+ {
+ int sum = CalcHashVersion(i);
+ int enc = 0xff
+ ^ ((sum >> 24) & 0xFF)
+ ^ ((sum >> 16) & 0xFF)
+ ^ ((sum >> 8) & 0xFF)
+ ^ ((sum) & 0xFF);
+
+ // if encver does not passed, try every version one by one
+ if (enc == this.EncryptedVersion)
+ {
+ this.versionTest.Add(i);
+ this.hasVersionTest.Add((uint)sum);
+ startVersion = i;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Image.cs b/WzComparerR2.WzLib/Wz_Image.cs
new file mode 100644
index 0000000..757b16d
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Image.cs
@@ -0,0 +1,403 @@
+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; }
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Node.cs b/WzComparerR2.WzLib/Wz_Node.cs
new file mode 100644
index 0000000..b3170d4
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Node.cs
@@ -0,0 +1,770 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Xml;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Node : ICloneable, IComparable, IComparable
+ {
+ public Wz_Node()
+ {
+ this.nodes = new WzNodeCollection(this);
+ }
+
+ public Wz_Node(string nodeText)
+ : this()
+ {
+ this.text = nodeText;
+ }
+
+ //fields
+ private object value;
+ private string text;
+ private WzNodeCollection nodes;
+ private Wz_Node parentNode;
+
+ //properties
+ public object Value
+ {
+ get { return value; }
+ set { this.value = value; }
+ }
+
+ public string Text
+ {
+ get { return this.text; }
+ set { this.text = value; }
+ }
+
+ public string FullPath
+ {
+ get
+ {
+ Stack path = new Stack();
+ Wz_Node node = this;
+ do
+ {
+ path.Push(node.text);
+ node = node.parentNode;
+ } while (node != null);
+ return string.Join("\\", path.ToArray());
+ }
+ }
+
+ public string FullPathToFile
+ {
+ get
+ {
+ Stack path = new Stack();
+ Wz_Node node = this;
+ do
+ {
+ if (node.value is Wz_File wzf && !wzf.IsSubDir)
+ {
+ if (node.text.EndsWith(".wz", StringComparison.OrdinalIgnoreCase))
+ {
+ path.Push(node.text.Substring(0, node.text.Length - 3));
+ }
+ else
+ {
+ path.Push(node.text);
+ }
+ break;
+ }
+
+ path.Push(node.text);
+
+ var img = node.GetValue();
+ if (img != null)
+ {
+ node = img.OwnerNode;
+ }
+
+ if (node != null)
+ {
+ node = node.parentNode;
+ }
+ } while (node != null);
+ return string.Join("\\", path.ToArray());
+ }
+ }
+
+ public WzNodeCollection Nodes
+ {
+ get { return this.nodes; }
+ }
+
+ public Wz_Node ParentNode
+ {
+ get { return parentNode; }
+ private set { parentNode = value; }
+ }
+
+ //methods
+ public override string ToString()
+ {
+ return this.Text + " " + (this.value != null ? this.value.ToString() : "-") + " " + this.nodes.Count;
+ }
+
+ public Wz_Node FindNodeByPath(string fullPath)
+ {
+ return FindNodeByPath(fullPath, false);
+ }
+
+ public Wz_Node FindNodeByPath(string fullPath, bool extractImage)
+ {
+ string[] patten = fullPath.Split('\\');
+ return FindNodeByPath(extractImage, patten);
+ }
+
+ public Wz_Node FindNodeByPath(bool extractImage, params string[] fullPath)
+ {
+ return FindNodeByPath(extractImage, false, fullPath);
+ }
+
+ public Wz_Node FindNodeByPath(bool extractImage, bool ignoreCase, params string[] fullPath)
+ {
+ Wz_Node node = this;
+
+ Wz_Image img;
+
+ //首次解压
+ if (extractImage && (img = this.GetValue()) != null)
+ {
+ if (img.TryExtract())
+ {
+ node = img.Node;
+ }
+ }
+
+ foreach (string txt in fullPath)
+ {
+ if (ignoreCase)
+ {
+ bool find = false;
+
+ foreach (Wz_Node subNode in node.nodes)
+ {
+ if (string.Equals(subNode.text, txt, StringComparison.OrdinalIgnoreCase))
+ {
+ find = true;
+ node = subNode;
+ }
+ }
+ if (!find)
+ node = null;
+ }
+ else
+ {
+ node = node.nodes[txt];
+ }
+
+ if (node == null)
+ return null;
+
+ if (extractImage)
+ {
+ img = node.GetValue();
+ if (img != null && img.TryExtract()) //判断是否是img
+ {
+ node = img.Node;
+ }
+ }
+ }
+ return node;
+ }
+
+ public T GetValue(T defaultValue)
+ {
+ var typeT = typeof(T);
+ if (typeof(Wz_Image) == typeT)
+ {
+ if (this is Wz_Image.Wz_ImageNode)
+ {
+ return (T)(object)(((Wz_Image.Wz_ImageNode)this).Image);
+ }
+ else
+ {
+ return (this.value is T) ? (T)this.value : default(T);
+ }
+ }
+ if (this.value == null)
+ return defaultValue;
+ if (this.value is T)
+ return (T)this.value;
+
+
+ if (this.value is string s)
+ {
+ if (ObjectConverter.TryParse(s, out T result, out bool hasTryParse))
+ {
+ return result;
+ }
+ if (hasTryParse)
+ {
+ return defaultValue;
+ }
+ }
+
+ if (this.value is IConvertible iconvertible)
+ {
+ if (typeT.IsGenericType && typeT.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ typeT = typeT.GetGenericArguments()[0];
+ }
+
+ try
+ {
+ T result = (T)iconvertible.ToType(typeT, null);
+ return result;
+ }
+ catch
+ {
+ }
+ }
+ return defaultValue;
+ }
+
+ public T GetValue()
+ {
+ return GetValue(default(T));
+ }
+
+ //innerClass
+ public class WzNodeCollection : IEnumerable
+ {
+ public WzNodeCollection(Wz_Node owner)
+
+ {
+ this.owner = owner;
+ this.innerCollection = null;
+ }
+
+ private readonly Wz_Node owner;
+ private InnerCollection innerCollection;
+
+ public Wz_Node this[int index]
+ {
+ get { return this.innerCollection?[index]; }
+ }
+
+ public Wz_Node this[string key]
+ {
+ get { return this.innerCollection?[key]; }
+ }
+
+ public int Count
+ {
+ get { return this.innerCollection?.Count ?? 0; }
+ }
+
+ public Wz_Node Add(string nodeText)
+ {
+ this.EnsureInnerCollection();
+ return this.innerCollection.Add(nodeText);
+ }
+
+ public void Add(Wz_Node item)
+ {
+ this.EnsureInnerCollection();
+ this.innerCollection.Add(item);
+ }
+
+ public void Sort()
+ {
+ this.innerCollection?.Sort();
+ }
+
+ public void Sort(Func getKeyFunc) where T : IComparable
+ {
+ if (getKeyFunc == null)
+ {
+ this.Sort();
+ }
+ else if (this.innerCollection != null)
+ {
+ this.innerCollection.Sort(getKeyFunc);
+ }
+ }
+
+ public void Trim()
+ {
+ this.innerCollection?.Trim();
+ }
+
+ public void Clear()
+ {
+ this.innerCollection?.Clear();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return this.innerCollection?.GetEnumerator() ?? System.Linq.Enumerable.Empty().GetEnumerator();
+ }
+
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.GetEnumerator();
+ }
+
+ private void EnsureInnerCollection()
+ {
+ if (this.innerCollection == null)
+ {
+ this.innerCollection = new InnerCollection(this.owner);
+ }
+ }
+
+ private class InnerCollection : KeyedCollection
+ {
+ public InnerCollection(Wz_Node owner)
+ : base(null, 12)
+ {
+ this.parentNode = owner;
+ }
+
+ public Wz_Node Add(string nodeText)
+ {
+ Wz_Node newNode = new Wz_Node(nodeText);
+ this.Add(newNode);
+ return newNode;
+ }
+
+ public new void Add(Wz_Node item)
+ {
+ base.Add(item);
+ if (item.parentNode != null)
+ {
+ int index = item.parentNode.nodes.innerCollection.Items.IndexOf(item);
+ if (index > -1)
+ {
+ item.parentNode.nodes.innerCollection.RemoveItem(index);
+ }
+ }
+ item.parentNode = this.parentNode;
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ var item = this[index];
+ if (item != null)
+ {
+ item.parentNode = null;
+ }
+ base.RemoveItem(index);
+ }
+
+ private readonly Wz_Node parentNode;
+
+ public void Sort()
+ {
+ (base.Items as List)?.Sort();
+ }
+
+ public void Sort(Func getKeyFunc) where T : IComparable
+ {
+ ListSorter.Sort(base.Items as List, getKeyFunc);
+ }
+
+ public void Trim()
+ {
+ (base.Items as List)?.TrimExcess();
+ }
+
+ public new Wz_Node this[string key]
+ {
+ get
+ {
+ if (key == null)
+ {
+ return null;
+ }
+ if (this.Dictionary != null)
+ {
+ Wz_Node node;
+ this.Dictionary.TryGetValue(key, out node);
+ return node;
+ }
+ else
+ {
+ List list = this.Items as List;
+ foreach (var node in list)
+ {
+ if (this.Comparer.Equals(this.GetKeyForItem(node), key))
+ {
+ return node;
+ }
+ }
+ return null;
+ }
+ }
+ }
+
+ protected override string GetKeyForItem(Wz_Node item)
+ {
+ return item.text;
+ }
+ }
+
+ internal static class ListSorter
+ {
+ public static void Sort(List list, Func getKeyFunc)
+ {
+ T[] innerArray = list.GetType()
+ .GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)
+ .GetValue(list) as T[];
+
+ TKey[] keys = new TKey[list.Count];
+
+ for (int i = 0; i < keys.Length; i++)
+ {
+ keys[i] = getKeyFunc(innerArray[i]);
+ }
+
+ Array.Sort(keys, innerArray, 0, keys.Length);
+ }
+ }
+ }
+
+ public object Clone()
+ {
+ Wz_Node newNode = new Wz_Node(this.text);
+ newNode.value = this.value;
+ foreach (Wz_Node node in this.nodes)
+ {
+ Wz_Node newChild = node.Clone() as Wz_Node;
+ newNode.nodes.Add(newChild);
+ }
+ return newNode;
+ }
+
+ int IComparable.CompareTo(object obj)
+ {
+ return ((IComparable)this).CompareTo(obj as Wz_Node);
+ }
+
+ int IComparable.CompareTo(Wz_Node other)
+ {
+ if (other != null)
+ {
+ //return string.Compare(this.Text, other.Text, StringComparison.Ordinal);
+ return StrCmpLogicalW(this.Text, other.Text);
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
+ static extern int StrCmpLogicalW(string psz1, string psz2);
+ }
+
+ public static class Wz_NodeExtension
+ {
+ public static T GetValueEx(this Wz_Node node, T defaultValue)
+ {
+ if (node == null)
+ return defaultValue;
+ return node.GetValue(defaultValue);
+ }
+
+ public static T? GetValueEx(this Wz_Node node) where T : struct
+ {
+ if (node == null)
+ return null;
+ return node.GetValue();
+ }
+
+ public static Wz_Node ResolveUol(this Wz_Node node)
+ {
+ if (node == null)
+ return null;
+ Wz_Uol uol;
+ while ((uol = node?.GetValueEx(null)) != null)
+ {
+ node = uol.HandleUol(node);
+ }
+ return node;
+ }
+
+ ///
+ /// 搜索node所属的wz_file,若搜索不到则返回null。
+ ///
+ /// 要搜索的wznode。
+ ///
+ public static Wz_File GetNodeWzFile(this Wz_Node node, bool returnClosestWzFile = false)
+ {
+ Wz_File wzfile = null;
+ while (node != null)
+ {
+ if ((wzfile = node.Value as Wz_File) != null)
+ {
+ if (wzfile.OwnerWzFile != null)
+ {
+ wzfile = wzfile.OwnerWzFile;
+ node = wzfile.Node;
+ }
+ if (!wzfile.IsSubDir || returnClosestWzFile)
+ {
+ break;
+ }
+ }
+ else if (node.Value is Wz_Image wzImg
+ || (wzImg = (node as Wz_Image.Wz_ImageNode)?.Image) != null)
+ {
+ wzfile = GetImageWzFile(wzImg, returnClosestWzFile);
+ break;
+ }
+ node = node.ParentNode;
+ }
+ return wzfile;
+ }
+
+ public static Wz_File GetImageWzFile(this Wz_Image wzImg, bool returnClosestWzFile = false)
+ {
+ if (!returnClosestWzFile && wzImg.WzFile != null)
+ {
+ return GetNodeWzFile(wzImg.WzFile.Node, returnClosestWzFile);
+ }
+
+ return wzImg.WzFile;
+ }
+
+ public static int GetMergedVersion(this Wz_File wzFile)
+ {
+ if (wzFile.Header.WzVersion != 0)
+ {
+ return wzFile.Header.WzVersion;
+ }
+ foreach (var subFile in wzFile.MergedWzFiles)
+ {
+ if (subFile.Header.WzVersion != 0)
+ {
+ return subFile.Header.WzVersion;
+ }
+ }
+ return 0;
+ }
+
+ public static Wz_Image GetNodeWzImage(this Wz_Node node)
+ {
+ Wz_Image wzImg = null;
+ while (node != null)
+ {
+ if ((wzImg = node.Value as Wz_Image) != null
+ || (wzImg = (node as Wz_Image.Wz_ImageNode)?.Image) != null)
+ {
+ break;
+ }
+ node = node.ParentNode;
+ }
+ return wzImg;
+ }
+
+ public static void DumpAsXml(this Wz_Node node, XmlWriter writer)
+ {
+ object value = node.Value;
+
+ if (value == null || value is Wz_Image)
+ {
+ writer.WriteStartElement("dir");
+ writer.WriteAttributeString("name", node.Text);
+ }
+ else if (value is Wz_Png)
+ {
+ var png = (Wz_Png)value;
+ writer.WriteStartElement("png");
+ writer.WriteAttributeString("name", node.Text);
+ using (var bmp = png.ExtractPng())
+ {
+ using (var ms = new MemoryStream())
+ {
+ bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
+ byte[] data = ms.ToArray();
+ writer.WriteAttributeString("value", Convert.ToBase64String(data));
+ }
+ }
+ }
+ else if (value is Wz_Uol)
+ {
+ var uol = (Wz_Uol)value;
+ writer.WriteStartElement("uol");
+ writer.WriteAttributeString("name", node.Text);
+ writer.WriteAttributeString("value", uol.Uol);
+ }
+ else if (value is Wz_Vector)
+ {
+ var vector = (Wz_Vector)value;
+ writer.WriteStartElement("vector");
+ writer.WriteAttributeString("name", node.Text);
+ writer.WriteAttributeString("value", $"{vector.X}, {vector.Y}");
+ }
+ else if (value is Wz_Sound)
+ {
+ var sound = (Wz_Sound)value;
+ writer.WriteStartElement("sound");
+ writer.WriteAttributeString("name", node.Text);
+ byte[] data = sound.ExtractSound();
+ if (data == null)
+ {
+ data = new byte[sound.DataLength];
+ sound.WzFile.FileStream.Seek(sound.Offset, SeekOrigin.Begin);
+ sound.WzFile.FileStream.Read(data, 0, sound.DataLength);
+ }
+ writer.WriteAttributeString("value", Convert.ToBase64String(data));
+ }
+ else
+ {
+ var tag = value.GetType().Name.ToLower();
+ writer.WriteStartElement(tag);
+ writer.WriteAttributeString("name", node.Text);
+ writer.WriteAttributeString("value", value.ToString());
+ }
+
+ //输出子节点
+ foreach (var child in node.Nodes)
+ {
+ DumpAsXml(child, writer);
+ }
+
+ //结束标识
+ writer.WriteEndElement();
+ }
+
+ public static void SortByImgID(this Wz_Node.WzNodeCollection nodes)
+ {
+ if (regexImgID == null)
+ {
+ regexImgID = new Regex(@"^(\d+)\.img$", RegexOptions.Compiled);
+ }
+
+ nodes.Sort(GetKey);
+ }
+
+ private static Regex regexImgID;
+
+ private static SortKey GetKey(Wz_Node node)
+ {
+ var key = new SortKey();
+ var m = regexImgID.Match(node.Text);
+ if (m.Success)
+ {
+ key.HasID = Int32.TryParse(m.Result("$1"), out key.ImgID);
+ }
+ key.Text = node.Text;
+ return key;
+ }
+
+ private struct SortKey : IComparable
+ {
+ public bool HasID;
+ public int ImgID;
+ public string Text;
+
+ public int CompareTo(SortKey other)
+ {
+ if (this.HasID && other.HasID) return this.ImgID.CompareTo(other.ImgID);
+ return StringComparer.Ordinal.Compare(this.Text, other.Text);
+ }
+ }
+ }
+
+ public static class ObjectConverter
+ {
+ private static readonly Dictionary cache = new Dictionary();
+ private delegate bool TryParseFunc(string s, out T value);
+
+ public static bool TryParse(string s, out T value, out bool hasTryParse)
+ {
+ var typeT = typeof(T);
+
+ TryParseFunc tryParseFunc = null;
+ if (!cache.TryGetValue(typeT, out var dele))
+ {
+ bool isNullable = false;
+ Type innerType;
+ if (typeT.IsGenericType && typeT.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ isNullable = true;
+ innerType = typeT.GetGenericArguments()[0];
+ }
+ else
+ {
+ innerType = typeT;
+ }
+
+ var methodInfo = innerType.GetMethod("TryParse",
+ BindingFlags.Static | BindingFlags.Public,
+ null,
+ new[] { typeof(string), innerType.MakeByRefType() },
+ null);
+
+ if (methodInfo != null && methodInfo.ReturnType == typeof(bool))
+ {
+ if (isNullable)
+ {
+ dele = Delegate.CreateDelegate(typeof(TryParseFunc<>).MakeGenericType(innerType), methodInfo);
+ var proxyType = typeof(NullableTryParse<>).MakeGenericType(innerType);
+ var proxyInstance = Activator.CreateInstance(proxyType, dele);
+ var proxyParseFunc = proxyType.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Instance);
+ cache[typeT] = tryParseFunc = (TryParseFunc)Delegate.CreateDelegate(typeof(TryParseFunc), proxyInstance, proxyParseFunc);
+ }
+ else
+ {
+ cache[typeT] = tryParseFunc = (TryParseFunc)Delegate.CreateDelegate(typeof(TryParseFunc), methodInfo);
+ }
+ }
+ else
+ {
+ cache[typeT] = null;
+ }
+ }
+ else
+ {
+ tryParseFunc = dele as TryParseFunc;
+ }
+
+ if (tryParseFunc != null)
+ {
+ hasTryParse = true;
+ return tryParseFunc(s, out value);
+ }
+ else
+ {
+ hasTryParse = false;
+ value = default(T);
+ return false;
+ }
+ }
+
+ private class NullableTryParse where T : struct
+ {
+ public NullableTryParse(TryParseFunc func)
+ {
+ this.func = func;
+ }
+
+ private readonly TryParseFunc func;
+
+ public bool TryParse(string s, out T? value)
+ {
+ if (this.func(s, out var v))
+ {
+ value = v;
+ return true;
+ }
+ else
+ {
+ value = default(T?);
+ return false;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/WzComparerR2.WzLib/Wz_Png.cs b/WzComparerR2.WzLib/Wz_Png.cs
new file mode 100644
index 0000000..44f0cad
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Png.cs
@@ -0,0 +1,562 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Drawing;
+using System.IO;
+using System.IO.Compression;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Png
+ {
+ public Wz_Png(int w, int h, int data_length, int form, uint offs, Wz_Image wz_i)
+ {
+ this.w = w;
+ this.h = h;
+ this.data_length = data_length;
+ this.form = form;
+ this.offs = offs;
+ this.wz_i = wz_i;
+ }
+
+ private int w;
+ private int h;
+ private int data_length;
+ private int form;
+ private uint offs;
+ private Wz_Image wz_i;
+
+ ///
+ /// 获取或设置图片的宽度。
+ ///
+ public int Width
+ {
+ get { return w; }
+ set { w = value; }
+ }
+
+ ///
+ /// 获取或设置图片的高度。
+ ///
+ public int Height
+ {
+ get { return h; }
+ set { h = value; }
+ }
+
+ ///
+ /// 获取或设置数据块的长度。
+ ///
+ public int DataLength
+ {
+ get { return data_length; }
+ set { data_length = value; }
+ }
+
+ ///
+ /// 获取或设置数据块对于文件的偏移。
+ ///
+ public uint Offset
+ {
+ get { return offs; }
+ set { offs = value; }
+ }
+
+ ///
+ /// 获取或设置图片的数据压缩方式。
+ ///
+ public int Form
+ {
+ get { return form; }
+ set { form = value; }
+ }
+
+ ///
+ /// 获取或设置图片所属的WzFile
+ ///
+ public Wz_File WzFile
+ {
+ get { return wz_i.WzFile; }
+ set { wz_i.WzFile = value; }
+ }
+
+ ///
+ /// 获取或设置图片所属的WzImage
+ ///
+ public Wz_Image WzImage
+ {
+ get { return wz_i; }
+ set { wz_i = value; }
+ }
+
+ public byte[] GetRawData()
+ {
+ lock (this.WzFile.ReadLock)
+ {
+ DeflateStream zlib;
+ byte[] plainData = null;
+
+ this.WzFile.FileStream.Position = this.Offset;
+
+ if (this.WzFile.BReader.ReadUInt16() == 0x9C78)
+ {
+ byte[] buffer = this.WzFile.BReader.ReadBytes(this.data_length - 2);
+ MemoryStream dataStream = new MemoryStream(buffer);
+
+ zlib = new DeflateStream(dataStream, CompressionMode.Decompress);
+ }
+ else
+ {
+ this.WzFile.FileStream.Position -= 2;
+ MemoryStream dataStream = new MemoryStream(this.DataLength);
+ int blocksize = 0;
+ int endPosition = (int)(this.DataLength + this.WzFile.FileStream.Position);
+
+ var encKeys = this.WzImage.Encryption.keys;
+
+ while (this.WzFile.FileStream.Position < endPosition)
+ {
+ blocksize = this.WzFile.BReader.ReadInt32();
+ byte[] dataBlock = this.WzFile.BReader.ReadBytes(blocksize);
+ encKeys.Decrypt(dataBlock, 0, dataBlock.Length);
+
+ dataStream.Write(dataBlock, 0, dataBlock.Length);
+ }
+ dataStream.Position = 2;
+ zlib = new DeflateStream(dataStream, CompressionMode.Decompress);
+ }
+
+ switch (this.Form)
+ {
+ case 1:
+ case 257:
+ case 513:
+ plainData = new byte[this.w * this.h * 2];
+ zlib.Read(plainData, 0, plainData.Length);
+ break;
+
+ case 2:
+ plainData = new byte[this.w * this.h * 4];
+ zlib.Read(plainData, 0, plainData.Length);
+ break;
+
+ case 3:
+ plainData = new byte[((int)Math.Ceiling(this.w / 4.0)) * 4 * ((int)Math.Ceiling(this.h / 4.0)) * 4 / 8];
+ zlib.Read(plainData, 0, plainData.Length);
+ break;
+
+ case 517:
+ plainData = new byte[this.w * this.h / 128];
+ zlib.Read(plainData, 0, plainData.Length);
+ break;
+
+ case 1026:
+ case 2050:
+ plainData = new byte[this.w * this.h];
+ zlib.Read(plainData, 0, plainData.Length);
+ break;
+
+ default:
+ var msOut = new MemoryStream();
+ zlib.CopyTo(msOut);
+ plainData = msOut.ToArray();
+ break;
+ }
+ if (zlib != null)
+ {
+ zlib.Close();
+ }
+ return plainData;
+ }
+ }
+
+ public Bitmap ExtractPng()
+ {
+ byte[] pixel = this.GetRawData();
+ if (pixel == null)
+ {
+ return null;
+ }
+ Bitmap pngDecoded = null;
+ BitmapData bmpdata;
+ byte[] argb;
+
+ switch (this.form)
+ {
+ case 1: //16位argba4444
+ argb = GetPixelDataBgra4444(pixel, this.w, this.h);
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format32bppArgb);
+ bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(argb, 0, bmpdata.Scan0, argb.Length);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 2: //32位argb8888
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format32bppArgb);
+ bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(pixel, 0, bmpdata.Scan0, pixel.Length);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 3: //黑白缩略图
+ argb = GetPixelDataForm3(pixel, this.w, this.h);
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format32bppArgb);
+ bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(argb, 0, bmpdata.Scan0, argb.Length);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 257: //16位argb1555
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format16bppArgb1555);
+ bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);
+ CopyBmpDataWithStride(pixel, pngDecoded.Width * 2, bmpdata);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 513: //16位rgb565
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format16bppRgb565);
+ bmpdata = pngDecoded.LockBits(new Rectangle(new Point(), pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb565);
+ CopyBmpDataWithStride(pixel, pngDecoded.Width * 2, bmpdata);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 517: //16位rgb565缩略图
+ argb = GetPixelDataForm517(pixel, this.w, this.h);
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format16bppRgb565);
+ bmpdata = pngDecoded.LockBits(new Rectangle(0, 0, this.w, this.h), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb565);
+ CopyBmpDataWithStride(pixel, pngDecoded.Width * 2, bmpdata);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+ /* pngDecoded = new Bitmap(this.w, this.h);
+ pngSize = this.w * this.h / 128;
+ plainData = new byte[pngSize];
+ zlib.Read(plainData, 0, pngSize);
+ byte iB = 0;
+ for (int i = 0; i < pngSize; i++)
+ {
+ for (byte j = 0; j < 8; j++)
+ {
+ iB = Convert.ToByte(((plainData[i] & (0x01 << (7 - j))) >> (7 - j)) * 0xFF);
+ for (int k = 0; k < 16; k++)
+ {
+ if (x == this.w) { x = 0; y++; }
+ pngDecoded.SetPixel(x, y, Color.FromArgb(0xFF, iB, iB, iB));
+ x++;
+ }
+ }
+ }
+ break;*/
+
+ case 1026: //dxt3
+ argb = GetPixelDataDXT3(pixel, this.w, this.h);
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format32bppArgb);
+ bmpdata = pngDecoded.LockBits(new Rectangle(new Point(), pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(argb, 0, bmpdata.Scan0, argb.Length);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+
+ case 2050: //dxt5
+ argb = GetPixelDataDXT5(pixel, this.w, this.h);
+ pngDecoded = new Bitmap(this.w, this.h, PixelFormat.Format32bppArgb);
+ bmpdata = pngDecoded.LockBits(new Rectangle(new Point(), pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(argb, 0, bmpdata.Scan0, argb.Length);
+ pngDecoded.UnlockBits(bmpdata);
+ break;
+ }
+
+ return pngDecoded;
+ }
+
+ public static byte[] GetPixelDataBgra4444(byte[] rawData, int width, int height)
+ {
+ byte[] argb = new byte[width * height * 4];
+ {
+ int p;
+ for (int i = 0; i < rawData.Length; i++)
+ {
+ p = rawData[i] & 0x0F; p |= (p << 4); argb[i * 2] = (byte)p;
+ p = rawData[i] & 0xF0; p |= (p >> 4); argb[i * 2 + 1] = (byte)p;
+ }
+ }
+ return argb;
+ }
+
+ public static byte[] GetPixelDataDXT3(byte[] rawData, int width, int height)
+ {
+ byte[] pixel = new byte[width * height * 4];
+
+ Color[] colorTable = new Color[4];
+ int[] colorIdxTable = new int[16];
+ byte[] alphaTable = new byte[16];
+ for (int y = 0; y < height; y += 4)
+ {
+ for (int x = 0; x < width; x += 4)
+ {
+ int off = x * 4 + y * width;
+ ExpandAlphaTableDXT3(alphaTable, rawData, off);
+ ushort u0 = BitConverter.ToUInt16(rawData, off + 8);
+ ushort u1 = BitConverter.ToUInt16(rawData, off + 10);
+ ExpandColorTable(colorTable, u0, u1);
+ ExpandColorIndexTable(colorIdxTable, rawData, off + 12);
+
+ for (int j = 0; j < 4; j++)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ SetPixel(pixel,
+ x + i,
+ y + j,
+ width,
+ colorTable[colorIdxTable[j * 4 + i]],
+ alphaTable[j * 4 + i]);
+ }
+ }
+ }
+ }
+
+ return pixel;
+ }
+
+ public static byte[] GetPixelDataDXT5(byte[] rawData, int width, int height)
+ {
+ byte[] pixel = new byte[width * height * 4];
+
+ Color[] colorTable = new Color[4];
+ int[] colorIdxTable = new int[16];
+ byte[] alphaTable = new byte[8];
+ int[] alphaIdxTable = new int[16];
+ for (int y = 0; y < height; y += 4)
+ {
+ for (int x = 0; x < width; x += 4)
+ {
+ int off = x * 4 + y * width;
+ ExpandAlphaTableDXT5(alphaTable, rawData[off + 0], rawData[off + 1]);
+ ExpandAlphaIndexTableDXT5(alphaIdxTable, rawData, off + 2);
+ ushort u0 = BitConverter.ToUInt16(rawData, off + 8);
+ ushort u1 = BitConverter.ToUInt16(rawData, off + 10);
+ ExpandColorTable(colorTable, u0, u1);
+ ExpandColorIndexTable(colorIdxTable, rawData, off + 12);
+
+ for (int j = 0; j < 4; j++)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ SetPixel(pixel,
+ x + i,
+ y + j,
+ width,
+ colorTable[colorIdxTable[j * 4 + i]],
+ alphaTable[alphaIdxTable[j * 4 + i]]);
+ }
+ }
+ }
+ }
+
+ return pixel;
+ }
+
+ public static unsafe byte[] GetPixelDataForm3(byte[] rawData, int width, int height)
+ {
+ byte[] pixel = new byte[width * height * 4];
+ fixed (byte* pArray = pixel)
+ {
+ int* argb2 = (int*)pArray;
+ int w = ((int)Math.Ceiling(width / 4.0));
+ int h = ((int)Math.Ceiling(height / 4.0));
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ var index = (x + y * w) * 2; //原像素索引
+ var index2 = x * 4 + y * width * 4; //目标像素索引
+ var p = (rawData[index] & 0x0F) | ((rawData[index] & 0x0F) << 4)
+ | ((rawData[index] & 0xF0) | ((rawData[index] & 0xF0) >> 4)) << 8
+ | ((rawData[index + 1] & 0x0F) | ((rawData[index + 1] & 0x0F) << 4)) << 16
+ | ((rawData[index + 1] & 0xF0) | ((rawData[index + 1] & 0xF0) >> 4)) << 24;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (x * 4 + i < width)
+ {
+ argb2[index2 + i] = p;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ //复制行
+ var srcIndex = y * width * 4 * 4;
+ var dstIndex = srcIndex + width * 4;
+ for (int j = 1; j < 4; j++)
+ {
+ if (y * 4 + j < height)
+ {
+ Array.Copy(pixel, srcIndex, pixel, dstIndex, width * 4);
+ dstIndex += width * 4;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ return pixel;
+ }
+
+ public static byte[] GetPixelDataForm517(byte[] rawData, int width, int height)
+ {
+ byte[] pixel = new byte[width * height * 2];
+ int lineIndex = 0;
+ for (int j0 = 0, j1 = height / 16; j0 < j1; j0++)
+ {
+ var dstIndex = lineIndex;
+ for (int i0 = 0, i1 = width / 16; i0 < i1; i0++)
+ {
+ int idx = (i0 + j0 * i1) * 2;
+ byte b0 = rawData[idx];
+ byte b1 = rawData[idx + 1];
+ for (int k = 0; k < 16; k++)
+ {
+ pixel[dstIndex++] = b0;
+ pixel[dstIndex++] = b1;
+ }
+ }
+
+ for (int k = 1; k < 16; k++)
+ {
+ Array.Copy(pixel, lineIndex, pixel, dstIndex, width * 2);
+ dstIndex += width * 2;
+ }
+
+ lineIndex += width * 32;
+ }
+ return pixel;
+ }
+
+ private static void SetPixel(byte[] pixelData, int x, int y, int width, Color color, byte alpha)
+ {
+ int offset = (y * width + x) * 4;
+ pixelData[offset + 0] = color.B;
+ pixelData[offset + 1] = color.G;
+ pixelData[offset + 2] = color.R;
+ pixelData[offset + 3] = alpha;
+ }
+
+ #region DXT1 Color
+ private static void ExpandColorTable(Color[] color, ushort c0, ushort c1)
+ {
+ color[0] = RGB565ToColor(c0);
+ color[1] = RGB565ToColor(c1);
+ if (c0 > c1)
+ {
+ color[2] = Color.FromArgb(0xff, (color[0].R * 2 + color[1].R + 1) / 3, (color[0].G * 2 + color[1].G + 1) / 3, (color[0].B * 2 + color[1].B + 1) / 3);
+ color[3] = Color.FromArgb(0xff, (color[0].R + color[1].R * 2 + 1) / 3, (color[0].G + color[1].G * 2 + 1) / 3, (color[0].B + color[1].B * 2 + 1) / 3);
+ }
+ else
+ {
+ color[2] = Color.FromArgb(0xff, (color[0].R + color[1].R) / 2, (color[0].G + color[1].G) / 2, (color[0].B + color[1].B) / 2);
+ color[3] = Color.FromArgb(0xff, Color.Black);
+ }
+ }
+
+ private static void ExpandColorIndexTable(int[] colorIndex, byte[] rawData, int offset)
+ {
+ for (int i = 0; i < 16; i += 4, offset++)
+ {
+ colorIndex[i + 0] = (rawData[offset] & 0x03);
+ colorIndex[i + 1] = (rawData[offset] & 0x0c) >> 2;
+ colorIndex[i + 2] = (rawData[offset] & 0x30) >> 4;
+ colorIndex[i + 3] = (rawData[offset] & 0xc0) >> 6;
+ }
+ }
+ #endregion
+
+ #region DXT3/DXT5 Alpha
+ private static void ExpandAlphaTableDXT3(byte[] alpha, byte[] rawData, int offset)
+ {
+ for (int i = 0; i < 16; i += 2, offset++)
+ {
+ alpha[i + 0] = (byte)(rawData[offset] & 0x0f);
+ alpha[i + 1] = (byte)((rawData[offset] & 0xf0) >> 4);
+ }
+ for (int i = 0; i < 16; i++)
+ {
+ alpha[i] = (byte)(alpha[i] | (alpha[i] << 4));
+ }
+ }
+
+ private static void ExpandAlphaTableDXT5(byte[] alpha, byte a0, byte a1)
+ {
+ alpha[0] = a0;
+ alpha[1] = a1;
+ if (a0 > a1)
+ {
+ for(int i = 2; i < 8; i++)
+ {
+ alpha[i] = (byte)(((8 - i) * a0 + (i - 1) * a1 + 3) / 7);
+ }
+ }
+ else
+ {
+ for(int i = 2; i < 6; i++)
+ {
+ alpha[i] = (byte)(((6 - i) * a0 + (i - 1) * a1 + 2) / 5);
+ }
+ alpha[6] = 0;
+ alpha[7] = 255;
+ }
+ }
+
+ private static void ExpandAlphaIndexTableDXT5(int[] alphaIndex, byte[] rawData, int offset)
+ {
+ for (int i = 0; i < 16; i += 8, offset += 3)
+ {
+ int flags = rawData[offset]
+ | (rawData[offset + 1] << 8)
+ | (rawData[offset + 2] << 16);
+ for(int j = 0; j < 8; j++)
+ {
+ int mask = 0x07 << (3 * j);
+ alphaIndex[i + j] = (flags & mask) >> (3 * j);
+ }
+ }
+ }
+ #endregion
+
+ public static Color RGB565ToColor(ushort val)
+ {
+ const int rgb565_mask_r = 0xf800;
+ const int rgb565_mask_g = 0x07e0;
+ const int rgb565_mask_b = 0x001f;
+ int r = (val & rgb565_mask_r) >> 11;
+ int g = (val & rgb565_mask_g) >> 5;
+ int b = (val & rgb565_mask_b);
+ var c = Color.FromArgb(
+ (r << 3) | (r >> 2),
+ (g << 2) | (g >> 4),
+ (b << 3) | (b >> 2));
+ return c;
+ }
+
+ public static void CopyBmpDataWithStride(byte[] source, int stride, BitmapData bmpData)
+ {
+ if (bmpData.Stride == stride)
+ {
+ Marshal.Copy(source, 0, bmpData.Scan0, source.Length);
+ }
+ else
+ {
+ for (int y = 0; y < bmpData.Height; y++)
+ {
+ Marshal.Copy(source, stride * y, bmpData.Scan0 + bmpData.Stride * y, stride);
+ }
+ }
+
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Sound.cs b/WzComparerR2.WzLib/Wz_Sound.cs
new file mode 100644
index 0000000..7a00405
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Sound.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Sound
+ {
+ public Wz_Sound(uint offset, int length, byte[] header, int ms, Wz_Image wz_i)
+ {
+ this.offset = offset;
+ this.dataLength = length;
+ this.header = header;
+ this.ms = ms;
+ this.wz_i = wz_i;
+ TryDecryptHeader();
+ }
+
+ private uint offset;
+ private byte[] header;
+ private int dataLength;
+ private int ms;
+
+ private Wz_Image wz_i;
+
+ ///
+ /// 获取或设置数据块对于文件的偏移。
+ ///
+ public uint Offset
+ {
+ get { return offset; }
+ set { offset = value; }
+ }
+
+ ///
+ /// 获取或设置头部字节段。
+ ///
+ public byte[] Header
+ {
+ get { return header; }
+ set { header = value; }
+ }
+
+ public int Frequency
+ {
+ get
+ {
+ if (header == null || header.Length < 0x3c)
+ {
+ return 0;
+ }
+ return BitConverter.ToInt32(header, 0x38);
+ }
+ }
+
+ ///
+ /// 获取或设置数据块的长度。
+ ///
+ public int DataLength
+ {
+ get { return dataLength; }
+ set { dataLength = value; }
+ }
+
+ ///
+ /// 获取或设置Mp3的声音毫秒数。
+ ///
+ public int Ms
+ {
+ get { return ms; }
+ set { ms = value; }
+ }
+
+ ///
+ /// 获取或设置图片所属的WzFile。
+ ///
+ public Wz_File WzFile
+ {
+ get { return wz_i.WzFile; }
+ set { wz_i.WzFile = value; }
+ }
+
+ ///
+ /// 获取或设置图片所属的WzImage。
+ ///
+ public Wz_Image WzImage
+ {
+ get { return wz_i; }
+ set { wz_i = value; }
+ }
+
+ public Wz_SoundType SoundType
+ {
+ get
+ {
+ Wz_SoundType soundType;
+ if (this.header == null)
+ {
+ soundType = Wz_SoundType.Mp3;
+ }
+ else
+ {
+ switch (this.header.Length)
+ {
+ default:
+ case 0x52:
+ soundType = Wz_SoundType.Mp3;
+ break;
+
+ case 0x46:
+ {
+ if (this.Frequency == this.dataLength)
+ {
+ soundType = Wz_SoundType.Binary;
+ }
+ else
+ {
+ soundType = Wz_SoundType.WavRaw;
+ }
+ }
+ break;
+ }
+ }
+
+ return soundType;
+ }
+ }
+
+ public byte[] ExtractSound()
+ {
+ switch (this.SoundType)
+ {
+ case Wz_SoundType.Mp3:
+ {
+ byte[] data = new byte[this.dataLength];
+ this.WzFile.FileStream.Seek(this.offset, System.IO.SeekOrigin.Begin);
+ this.WzFile.FileStream.Read(data, 0, this.dataLength);
+ return data;
+ }
+ case Wz_SoundType.WavRaw:
+ {
+ byte[] data = new byte[this.dataLength + 44];
+ this.WzFile.FileStream.Seek(this.offset, System.IO.SeekOrigin.Begin);
+ this.WzFile.FileStream.Read(data, 44, this.dataLength);
+ byte[] wavHeader = new byte[44]{
+ 0x52,0x49,0x46,0x46, //"RIFF"
+ 0,0,0,0, //ChunkSize
+ 0x57,0x41,0x56,0x45, //"WAVE"
+
+ 0x66,0x6d,0x74,0x20, //"fmt "
+ 0x10,0,0,0, //chunk1Size
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // copy16字节
+
+ 0x64,0x61,0x74,0x61, //"data"
+ 0,0,0,0 //chunk2Size
+ };
+ Array.Copy(BitConverter.GetBytes(this.dataLength + 36), 0, wavHeader, 4, 4);
+ Array.Copy(this.header, 0x34, wavHeader, 20, 16);
+ Array.Copy(BitConverter.GetBytes(this.dataLength), 0, wavHeader, 40, 4);
+ Array.Copy(wavHeader, data, wavHeader.Length);
+ return data;
+ }
+ }
+ return null;
+ }
+
+ private void TryDecryptHeader()
+ {
+ if (this.header == null)
+ {
+ return;
+ }
+ if (this.header.Length > 51)
+ {
+ byte waveFormatLen = this.header[51];
+ if (this.header.Length != 52 + waveFormatLen) //长度错误
+ {
+ return;
+ }
+ int cbSize = BitConverter.ToUInt16(this.header, 52 + 16);
+ if (cbSize + 18 != waveFormatLen)
+ {
+ byte[] tempHeader = new byte[waveFormatLen];
+ Buffer.BlockCopy(this.header, 52, tempHeader, 0, tempHeader.Length);
+ var enc = this.WzImage.Encryption;
+ enc.keys.Decrypt(tempHeader, 0, tempHeader.Length); //解密
+ cbSize = BitConverter.ToUInt16(tempHeader, 16); //重新验证
+ if (cbSize + 18 == waveFormatLen)
+ {
+ Buffer.BlockCopy(tempHeader, 0, this.header, 52, tempHeader.Length);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_SoundType.cs b/WzComparerR2.WzLib/Wz_SoundType.cs
new file mode 100644
index 0000000..6de7f7a
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_SoundType.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ public enum Wz_SoundType
+ {
+ Mp3 = 0,
+ WavRaw = 1,
+ Binary = 2,
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Structure.cs b/WzComparerR2.WzLib/Wz_Structure.cs
new file mode 100644
index 0000000..521296e
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Structure.cs
@@ -0,0 +1,269 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Linq;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Structure
+ {
+ public Wz_Structure()
+ {
+ this.wz_files = new List();
+ this.encryption = new Wz_Crypto();
+ this.img_number = 0;
+ this.has_basewz = false;
+ this.TextEncoding = Wz_Structure.DefaultEncoding;
+ this.AutoDetectExtFiles = Wz_Structure.DefaultAutoDetectExtFiles;
+ this.ImgCheckDisabled = Wz_Structure.DefaultImgCheckDisabled;
+ }
+
+ public List wz_files;
+ public Wz_Crypto encryption;
+ public Wz_Node WzNode;
+ public int img_number;
+ public bool has_basewz;
+ public bool sorted; //暂时弃用
+
+ public Encoding TextEncoding { get; set; }
+ public bool AutoDetectExtFiles { get; set; }
+ public bool ImgCheckDisabled { get; set; }
+
+ public void Clear()
+ {
+ foreach (Wz_File f in this.wz_files)
+ {
+ f.Close();
+ }
+ this.wz_files.Clear();
+ this.encryption.Reset();
+ this.img_number = 0;
+ this.has_basewz = false;
+ this.WzNode = null;
+ this.sorted = false;
+ }
+
+ public void calculate_img_count()
+ {
+ foreach (Wz_File f in this.wz_files)
+ {
+ this.img_number += f.ImageCount;
+ }
+ }
+
+ public void Load(string fileName, bool useBaseWz = false)
+ {
+ //现在我们已经不需要list了
+ this.WzNode = new Wz_Node(Path.GetFileName(fileName));
+ if (Path.GetFileName(fileName).ToLower() == "list.wz")
+ {
+ this.encryption.LoadListWz(Path.GetDirectoryName(fileName));
+ foreach (string list in this.encryption.List)
+ {
+ WzNode.Nodes.Add(list);
+ }
+ }
+ else
+ {
+ LoadFile(fileName, WzNode, useBaseWz);
+ }
+ calculate_img_count();
+ }
+
+ public Wz_File LoadFile(string fileName, Wz_Node node, bool useBaseWz = false, bool loadWzAsFolder = false)
+ {
+ Wz_File file = null;
+
+ try
+ {
+ file = new Wz_File(fileName, this);
+ if (!file.Loaded)
+ {
+ throw new Exception("The file is not a valid wz file.");
+ }
+ this.wz_files.Add(file);
+ file.TextEncoding = this.TextEncoding;
+ if (!this.encryption.encryption_detected)
+ {
+ this.encryption.DetectEncryption(file);
+ }
+ node.Value = file;
+ file.Node = node;
+ file.FileStream.Position = file.Header.DataStartPosition;
+ file.GetDirTree(node, useBaseWz, loadWzAsFolder);
+ file.DetectWzType();
+ file.DetectWzVersion();
+ return file;
+ }
+ catch
+ {
+ if (file != null)
+ {
+ file.Close();
+ this.wz_files.Remove(file);
+ }
+ throw;
+ }
+ }
+
+ public void LoadImg(string fileName)
+ {
+ this.WzNode = new Wz_Node(Path.GetFileName(fileName));
+ this.LoadImg(fileName, WzNode);
+ }
+
+ public void LoadImg(string fileName, Wz_Node node)
+ {
+ Wz_File file = null;
+
+ try
+ {
+ file = new Wz_File(fileName, this);
+ file.TextEncoding = this.TextEncoding;
+ var imgNode = new Wz_Node(node.Text);
+ //跳过checksum检测
+ var img = new Wz_Image(node.Text, (int)file.FileStream.Length, 0, 0, 0, file)
+ {
+ OwnerNode = imgNode,
+ Offset = 0,
+ IsChecksumChecked = true
+ };
+ imgNode.Value = img;
+ node.Nodes.Add(imgNode);
+ this.wz_files.Add(file);
+ }
+ catch
+ {
+ file?.Close();
+ throw;
+ }
+ }
+
+ public void LoadKMST1125DataWz(string fileName)
+ {
+ LoadWzFolder(Path.GetDirectoryName(fileName), ref this.WzNode, true);
+ calculate_img_count();
+ }
+
+ public bool IsKMST1125WzFormat(string fileName)
+ {
+ if (!string.Equals(Path.GetExtension(fileName), ".wz", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ string iniFile = Path.ChangeExtension(fileName, ".ini");
+ if (!File.Exists(iniFile))
+ {
+ return false;
+ }
+
+ // check if the file is an empty wzfile
+ using (var file = new Wz_File(fileName, this))
+ {
+ if (!file.Loaded)
+ {
+ return false;
+ }
+ var tempNode = new Wz_Node();
+ if (!this.encryption.encryption_detected)
+ {
+ this.encryption.DetectEncryption(file);
+ }
+ file.FileStream.Position = file.Header.DataStartPosition;
+ file.GetDirTree(tempNode);
+ return file.ImageCount == 0;
+ }
+ }
+
+ public void LoadWzFolder(string folder, ref Wz_Node node, bool useBaseWz = false)
+ {
+ string baseName = Path.Combine(folder, Path.GetFileName(folder));
+ string entryWzFileName = Path.ChangeExtension(baseName, ".wz");
+ string iniFileName = Path.ChangeExtension(baseName, ".ini");
+ Func extraWzFileName = _index => Path.ChangeExtension($"{baseName}_{_index:D3}", ".wz");
+
+ // load iniFile
+ int? lastWzIndex = null;
+ if (File.Exists(iniFileName))
+ {
+ var iniConf = File.ReadAllLines(iniFileName).Select(row =>
+ {
+ string[] columns = row.Split('|');
+ string key = columns.Length > 0 ? columns[0] : null;
+ string value = columns.Length > 1 ? columns[1] : null;
+ return new KeyValuePair(key, value);
+ });
+ if (int.TryParse(iniConf.FirstOrDefault(kv => kv.Key == "LastWzIndex").Value, out var indexFromIni))
+ {
+ lastWzIndex = indexFromIni;
+ }
+ }
+
+ // ini file missing or unexpected format
+ if (lastWzIndex == null)
+ {
+ for (int i = 0; ; i++)
+ {
+ string extraFile = extraWzFileName(i);
+ if (!File.Exists(extraFile))
+ {
+ break;
+ }
+ lastWzIndex = i;
+ }
+ }
+
+ // load entry file
+ if (node == null)
+ {
+ node = new Wz_Node(Path.GetFileName(entryWzFileName));
+ }
+ var entryWzf = this.LoadFile(entryWzFileName, node, useBaseWz, true);
+
+ // load extra file
+ if (lastWzIndex != null)
+ {
+ for (int i = 0, j = lastWzIndex.Value; i <= j; i++)
+ {
+ string extraFile = extraWzFileName(i);
+ var tempNode = new Wz_Node(Path.GetFileName(extraFile));
+ var extraWzf = this.LoadFile(extraFile, tempNode, false, true);
+
+ /*
+ * there is a little hack here, we'll move all img to the entry file, and each img still refers to the original wzfile.
+ * before:
+ * base.wz (Wz_File)
+ * |- a.img (Wz_Image)
+ * base_000.wz (Wz_File)
+ * |- b.img (Wz_Image) { wz_f = base_000.wz }
+ *
+ * after:
+ * base.wz (Wz_File) { mergedFiles = [base_000.wz] }
+ * |- a.img (Wz_Image) { wz_f = base.wz }
+ * |- b.img (Wz_Image) { wz_f = base_000.wz }
+ *
+ * this.wz_files references all opened files so they can be closed correctly.
+ */
+
+ entryWzf.MergeWzFile(extraWzf);
+ }
+ }
+ }
+
+ #region Global Settings
+ public static Encoding DefaultEncoding
+ {
+ get { return _defaultEncoding ?? Encoding.Default; }
+ set { _defaultEncoding = value; }
+ }
+
+ private static Encoding _defaultEncoding;
+
+ public static bool DefaultAutoDetectExtFiles { get; set; }
+
+ public static bool DefaultImgCheckDisabled { get; set; }
+ #endregion
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Type.cs b/WzComparerR2.WzLib/Wz_Type.cs
new file mode 100644
index 0000000..826190e
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Type.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ ///
+ /// 标识Wz_File的内部类型。
+ ///
+ public enum Wz_Type
+ {
+ Unknown = 0,
+ Base,
+ Character,
+ Effect,
+ Etc,
+ Item,
+ Map,
+ Mob,
+ Morph,
+ Npc,
+ Quest,
+ Reactor,
+ Skill,
+ Sound,
+ String,
+ TamingMob,
+ UI,
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Uol.cs b/WzComparerR2.WzLib/Wz_Uol.cs
new file mode 100644
index 0000000..2809799
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Uol.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Uol
+ {
+ public Wz_Uol(string uol)
+ {
+ this.uol = uol;
+ }
+
+ private string uol;
+
+ ///
+ /// 获取或设置连接路径字符串。
+ ///
+ public string Uol
+ {
+ get { return uol; }
+ set { uol = value; }
+ }
+
+ public Wz_Node HandleUol(Wz_Node currentNode)
+ {
+ if (currentNode == null || currentNode.ParentNode == null || string.IsNullOrEmpty(uol))
+ return null;
+ string[] dirs = this.uol.Split('/');
+ currentNode = currentNode.ParentNode;
+
+ bool outImg = false;
+
+ for (int i = 0; i < dirs.Length; i++)
+ {
+ string dir = dirs[i];
+ if (dir == "..")
+ {
+ if (currentNode.ParentNode == null)
+ {
+ Wz_Image img = currentNode.GetValueEx(null);
+ if (img != null)
+ {
+ currentNode = img.OwnerNode.ParentNode;
+ outImg = true;
+ }
+ else
+ {
+ currentNode = null;
+ }
+ }
+ else
+ {
+ currentNode = currentNode.ParentNode;
+ }
+ }
+ else
+ {
+ var dirNode = currentNode.FindNodeByPath(dir);
+
+ if (dirNode == null && outImg)
+ {
+ dirNode = currentNode.FindNodeByPath(true, dir + ".img");
+ if (dirNode.GetValueEx(null) != null)
+ {
+ outImg = false;
+ }
+ }
+
+ currentNode = dirNode;
+ }
+ if (currentNode == null)
+ return null;
+ }
+ return currentNode;
+ }
+ }
+}
diff --git a/WzComparerR2.WzLib/Wz_Vector.cs b/WzComparerR2.WzLib/Wz_Vector.cs
new file mode 100644
index 0000000..873eab1
--- /dev/null
+++ b/WzComparerR2.WzLib/Wz_Vector.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Drawing;
+
+namespace WzComparerR2.WzLib
+{
+ public class Wz_Vector
+ {
+ public Wz_Vector(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ private int x;
+ private int y;
+
+ ///
+ /// 获取或设置向量的X值。
+ ///
+ public int X
+ {
+ get { return x; }
+ set { x = value; }
+ }
+
+ ///
+ /// 获取或设置向量的Y值。
+ ///
+ public int Y
+ {
+ get { return y; }
+ set { y = value; }
+ }
+
+ public static implicit operator Point(Wz_Vector vector)
+ {
+ return vector == null ? new Point() : new Point(vector.x, vector.y);
+ }
+ }
+}