Add files via upload

This commit is contained in:
Elem8100 2022-01-14 20:35:16 +08:00 committed by GitHub
parent 8788da440e
commit 4c6b53aa26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 3706 additions and 0 deletions

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WzComparerR2.WzLib</RootNamespace>
<AssemblyName>WzComparerR2.WzLib</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.XML" />
</ItemGroup>
<ItemGroup Condition="Exists('..\Build\CommonAssemblyInfo.cs')">
<Compile Include="..\Build\CommonAssemblyInfo.cs">
<Link>Properties\CommonAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Wz_Directory.cs" />
<Compile Include="Wz_SoundType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Wz_Crypto.cs" />
<Compile Include="Wz_File.cs" />
<Compile Include="Wz_Header.cs" />
<Compile Include="Wz_Image.cs" />
<Compile Include="Wz_Sound.cs" />
<Compile Include="Wz_Node.cs" />
<Compile Include="Wz_Png.cs" />
<Compile Include="Wz_Structure.cs" />
<Compile Include="Wz_Type.cs" />
<Compile Include="Wz_Uol.cs" />
<Compile Include="Wz_Vector.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -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 };
}
}
}

View File

@ -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; }
}
}

View File

@ -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<long, string>();
this.directories = new List<Wz_Directory>();
}
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<Wz_File> mergedWzFiles;
private Wz_File ownerWzFile;
private readonly List<Wz_Directory> directories;
public Encoding TextEncoding { get; set; }
public readonly object ReadLock = new object();
internal Dictionary<long, string> 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<Wz_File> MergedWzFiles
{
get { return this.mergedWzFiles ?? Enumerable.Empty<Wz_File>(); }
}
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;
}
}
/// <summary>
/// 为字符串解密提供缓冲区。
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
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<string> dirs = new List<string>();
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<Wz_File>();
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<string> path = new List<string>(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<Wz_Type>(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<Wz_Image> 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<Wz_File>();
}
this.mergedWzFiles.Add(wz_File);
wz_File.ownerWzFile = this;
}
private IEnumerable<Wz_Image> 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;
}
}
}
}
}
}

View File

@ -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<int>();
this.hasVersionTest = new List<uint>();
this.startVersion = -1;
}
public int EncryptedVersion { get; private set; }
private int startVersion;
private List<int> versionTest;
private List<uint> 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;
}
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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<Wz_Node>
{
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<string> path = new Stack<string>();
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<string> path = new Stack<string>();
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<Wz_Image>();
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<Wz_Image>()) != 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<Wz_Image>();
if (img != null && img.TryExtract()) //判断是否是img
{
node = img.Node;
}
}
}
return node;
}
public T GetValue<T>(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<T>(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<T>()
{
return GetValue<T>(default(T));
}
//innerClass
public class WzNodeCollection : IEnumerable<Wz_Node>
{
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<T>(Func<Wz_Node, T> getKeyFunc) where T : IComparable<T>
{
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<Wz_Node> GetEnumerator()
{
return this.innerCollection?.GetEnumerator() ?? System.Linq.Enumerable.Empty<Wz_Node>().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<string, Wz_Node>
{
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<Wz_Node>)?.Sort();
}
public void Sort<T>(Func<Wz_Node, T> getKeyFunc) where T : IComparable<T>
{
ListSorter.Sort(base.Items as List<Wz_Node>, getKeyFunc);
}
public void Trim()
{
(base.Items as List<Wz_Node>)?.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<Wz_Node> list = this.Items as List<Wz_Node>;
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<T, TKey>(List<T> list, Func<T, TKey> 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<Wz_Node>)this).CompareTo(obj as Wz_Node);
}
int IComparable<Wz_Node>.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<T>(this Wz_Node node, T defaultValue)
{
if (node == null)
return defaultValue;
return node.GetValue<T>(defaultValue);
}
public static T? GetValueEx<T>(this Wz_Node node) where T : struct
{
if (node == null)
return null;
return node.GetValue<T>();
}
public static Wz_Node ResolveUol(this Wz_Node node)
{
if (node == null)
return null;
Wz_Uol uol;
while ((uol = node?.GetValueEx<Wz_Uol>(null)) != null)
{
node = uol.HandleUol(node);
}
return node;
}
/// <summary>
/// 搜索node所属的wz_file若搜索不到则返回null。
/// </summary>
/// <param Name="node">要搜索的wznode。</param>
/// <returns></returns>
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<SortKey>
{
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<Type, Delegate> cache = new Dictionary<Type, Delegate>();
private delegate bool TryParseFunc<T>(string s, out T value);
public static bool TryParse<T>(string s, out T value, out bool hasTryParse)
{
var typeT = typeof(T);
TryParseFunc<T> 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<T>)Delegate.CreateDelegate(typeof(TryParseFunc<T>), proxyInstance, proxyParseFunc);
}
else
{
cache[typeT] = tryParseFunc = (TryParseFunc<T>)Delegate.CreateDelegate(typeof(TryParseFunc<T>), methodInfo);
}
}
else
{
cache[typeT] = null;
}
}
else
{
tryParseFunc = dele as TryParseFunc<T>;
}
if (tryParseFunc != null)
{
hasTryParse = true;
return tryParseFunc(s, out value);
}
else
{
hasTryParse = false;
value = default(T);
return false;
}
}
private class NullableTryParse<T> where T : struct
{
public NullableTryParse(TryParseFunc<T> func)
{
this.func = func;
}
private readonly TryParseFunc<T> 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;
}
}
}
}
}

View File

@ -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;
/// <summary>
/// 获取或设置图片的宽度。
/// </summary>
public int Width
{
get { return w; }
set { w = value; }
}
/// <summary>
/// 获取或设置图片的高度。
/// </summary>
public int Height
{
get { return h; }
set { h = value; }
}
/// <summary>
/// 获取或设置数据块的长度。
/// </summary>
public int DataLength
{
get { return data_length; }
set { data_length = value; }
}
/// <summary>
/// 获取或设置数据块对于文件的偏移。
/// </summary>
public uint Offset
{
get { return offs; }
set { offs = value; }
}
/// <summary>
/// 获取或设置图片的数据压缩方式。
/// </summary>
public int Form
{
get { return form; }
set { form = value; }
}
/// <summary>
/// 获取或设置图片所属的WzFile
/// </summary>
public Wz_File WzFile
{
get { return wz_i.WzFile; }
set { wz_i.WzFile = value; }
}
/// <summary>
/// 获取或设置图片所属的WzImage
/// </summary>
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);
}
}
}
}
}

View File

@ -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;
/// <summary>
/// 获取或设置数据块对于文件的偏移。
/// </summary>
public uint Offset
{
get { return offset; }
set { offset = value; }
}
/// <summary>
/// 获取或设置头部字节段。
/// </summary>
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);
}
}
/// <summary>
/// 获取或设置数据块的长度。
/// </summary>
public int DataLength
{
get { return dataLength; }
set { dataLength = value; }
}
/// <summary>
/// 获取或设置Mp3的声音毫秒数。
/// </summary>
public int Ms
{
get { return ms; }
set { ms = value; }
}
/// <summary>
/// 获取或设置图片所属的WzFile。
/// </summary>
public Wz_File WzFile
{
get { return wz_i.WzFile; }
set { wz_i.WzFile = value; }
}
/// <summary>
/// 获取或设置图片所属的WzImage。
/// </summary>
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);
}
}
}
}
}
}

View File

@ -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,
}
}

View File

@ -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<Wz_File>();
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_File> 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<int, string> 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<string, string>(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
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace WzComparerR2.WzLib
{
/// <summary>
/// 标识Wz_File的内部类型。
/// </summary>
public enum Wz_Type
{
Unknown = 0,
Base,
Character,
Effect,
Etc,
Item,
Map,
Mob,
Morph,
Npc,
Quest,
Reactor,
Skill,
Sound,
String,
TamingMob,
UI,
}
}

View File

@ -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;
/// <summary>
/// 获取或设置连接路径字符串。
/// </summary>
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<Wz_Image>(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<Wz_Image>(null) != null)
{
outImg = false;
}
}
currentNode = dirNode;
}
if (currentNode == null)
return null;
}
return currentNode;
}
}
}

View File

@ -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;
/// <summary>
/// 获取或设置向量的X值。
/// </summary>
public int X
{
get { return x; }
set { x = value; }
}
/// <summary>
/// 获取或设置向量的Y值。
/// </summary>
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);
}
}
}