Add files via upload
This commit is contained in:
parent
8788da440e
commit
4c6b53aa26
|
@ -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>
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue