From f6fb7b87a6940e29e1edf35a0ebb1bcf735ae4bd Mon Sep 17 00:00:00 2001
From: genment <5207957+genment@users.noreply.github.com>
Date: Sat, 12 Feb 2022 17:15:31 -0500
Subject: [PATCH] [MapleLib] Support new WZ files (GMS v230)
---
MapleLib/WzLib/WzDirectory.cs | 15 +-----
MapleLib/WzLib/WzFile.cs | 93 ++++++++++++++++++++++++++---------
2 files changed, 73 insertions(+), 35 deletions(-)
diff --git a/MapleLib/WzLib/WzDirectory.cs b/MapleLib/WzLib/WzDirectory.cs
index 9454fca3..83f4b7ec 100644
--- a/MapleLib/WzLib/WzDirectory.cs
+++ b/MapleLib/WzLib/WzDirectory.cs
@@ -263,17 +263,6 @@ internal void ParseDirectory(bool lazyParse = false)
}
}
- // Offsets are calculated manually
- if (this.wzFile != null && this.wzFile.b64BitClient) // This only applies for 64-bit client based WZ files.
- {
- long startOffset = reader.BaseStream.Position;
- foreach (WzImage image in WzImages)
- {
- image.Offset = (uint)startOffset;
- startOffset += image.BlockSize; // TODO: Test saving the wz after it is changed. This may break
- }
- }
-
foreach (WzDirectory subdir in subDirs)
{
if (subdir.Checksum != 0)
@@ -289,7 +278,7 @@ internal void ParseDirectory(bool lazyParse = false)
///
///
///
- internal void SaveImages(BinaryWriter wzWriter, FileStream fs)
+ internal void SaveImages(BinaryWriter wzWriter, FileStream fs)
{
// List wzImageNameTracking = new List(); // Check for duplicate WZ image name that could cause errors later on.
@@ -335,7 +324,7 @@ internal void SaveImages(BinaryWriter wzWriter, FileStream fs)
/// Uses the default MapleStory UserKey or a custom key.
/// The previously opened file stream
///
- internal int GenerateDataFile(byte[] useIv, bool bIsWzUserKeyDefault, FileStream prevOpenedStream)
+ internal int GenerateDataFile(byte[] useIv, bool bIsWzUserKeyDefault, FileStream prevOpenedStream)
{
bool useCustomIv = useIv != null; // whole shit gonna be re-written if its a custom IV specified
diff --git a/MapleLib/WzLib/WzFile.cs b/MapleLib/WzLib/WzFile.cs
index 89d700c5..9d9d0419 100644
--- a/MapleLib/WzLib/WzFile.cs
+++ b/MapleLib/WzLib/WzFile.cs
@@ -40,7 +40,8 @@ public class WzFile : WzObject
internal WzDirectory wzDir;
internal WzHeader header;
internal string name = "";
- internal short wzVersionHeader = 0;
+ internal ushort wzVersionHeader = 0;
+ internal const ushort wzVersionHeader64bit = 777;
internal uint versionHash = 0;
internal short mapleStoryPatchVersion = 0;
internal WzMapleVersion maplepLocalVersion;
@@ -119,7 +120,7 @@ public override void Dispose()
///
///
///
- public WzFile(short gameVersion, WzMapleVersion version)
+ public WzFile(short gameVersion, WzMapleVersion version)
{
wzDir = new WzDirectory();
this.Header = WzHeader.GetDefault();
@@ -222,12 +223,20 @@ internal WzFileParseStatus ParseMainWzDirectory(bool lazyParse = false)
byte[] unk2 = reader.ReadBytes((int)(Header.FStart - (ulong)reader.BaseStream.Position));
reader.Header = this.Header;
- this.wzVersionHeader = 0;
- if (!this.b64BitClient)
- this.wzVersionHeader = reader.ReadInt16();
+ Check64BitClient(reader); // update b64BitClient flag
+
+ // the value of wzVersionHeader is less important. It is used for reading/writing from/to WzFile Header, and calculating the versionHash.
+ // it can be any number if the client is 64-bit. Assigning 777 is just for convenience when calculating the versionHash.
+ this.wzVersionHeader = b64BitClient ? wzVersionHeader64bit : reader.ReadUInt16();
if (mapleStoryPatchVersion == -1)
{
+ // for 64-bit client, return immediately if version 777 works correctly.
+ if (b64BitClient && TryDecodeWithWZVersionNumber(reader, wzVersionHeader, wzVersionHeader64bit, lazyParse))
+ {
+ return WzFileParseStatus.Success;
+ }
+
// Attempt to get version from MapleStory.exe first
short maplestoryVerDetectedFromClient = GetMapleStoryVerFromExe(this.path, out this.mapleLocaleVersion);
@@ -238,19 +247,16 @@ internal WzFileParseStatus ParseMainWzDirectory(bool lazyParse = false)
for (int j = maplestoryVerDetectedFromClient; j < MAX_PATCH_VERSION; j++)
{
if (TryDecodeWithWZVersionNumber(reader, wzVersionHeader, j, lazyParse))
- {
- return WzFileParseStatus.Success;
- }
+ {
+ return WzFileParseStatus.Success;
+ }
}
//parseErrorMessage = "Error with game version hash : The specified game version is incorrect and WzLib was unable to determine the version itself";
return WzFileParseStatus.Error_Game_Ver_Hash;
}
else
{
- if (!this.b64BitClient)
- {
- this.versionHash = CheckAndGetVersionHash(wzVersionHeader, mapleStoryPatchVersion);
- }
+ this.versionHash = CheckAndGetVersionHash(wzVersionHeader, mapleStoryPatchVersion);
reader.Hash = this.versionHash;
WzDirectory directory = new WzDirectory(reader, this.name, this.versionHash, this.WzIv, this);
directory.ParseDirectory();
@@ -258,18 +264,58 @@ internal WzFileParseStatus ParseMainWzDirectory(bool lazyParse = false)
}
return WzFileParseStatus.Success;
}
-
- private bool TryDecodeWithWZVersionNumber(WzBinaryReader reader, int useWzVersionHeader, int useMapleStoryPatchVersion, bool lazyParse)
- {
- this.mapleStoryPatchVersion = (short)useMapleStoryPatchVersion;
- if (!this.b64BitClient)
+ ///
+ /// encVer detecting:
+ /// Since KMST1132 (GMSv230, 2022/02/09), wz removed the 2-byte encVer at 0x3C, and use a fixed encVer 777.
+ /// Here we try to read the first 2 bytes from data part (0x3C) and guess if it looks like an encVer.
+ ///
+ /// Credit: WzComparerR2 project
+ ///
+ private void Check64BitClient(WzBinaryReader reader)
+ {
+ if (this.Header.FSize >= 2)
+ {
+ this.wzVersionHeader = reader.ReadUInt16();
+ if (this.wzVersionHeader > 0xff)
+ {
+ b64BitClient = true;
+ }
+ else if (this.wzVersionHeader == 0x80)
+ {
+ // there's an exceptional case that the first field of data part is a compressed int which determines the 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 WzFile won't greater than 65536 (0xFFFF).
+ if (this.Header.FSize >= 5)
+ {
+ reader.BaseStream.Position = this.header.FStart; // go back to 0x3C
+ int propCount = reader.ReadCompressedInt();
+ if (propCount > 0 && (propCount & 0xFF) == 0 && propCount <= 0xFFFF)
+ {
+ b64BitClient = true;
+ }
+ }
+ }
+ }
+ else
{
- this.versionHash = CheckAndGetVersionHash(useWzVersionHeader, mapleStoryPatchVersion);
- if (this.versionHash == 0) // ugly hack, but that's the only way if the version number isnt known (nexon stores this in the .exe)
- return false;
+ // Obviously, if data part have only 1 byte, encVer must be deleted.
+ b64BitClient = true;
}
+ // reset position
+ reader.BaseStream.Position = this.Header.FStart;
+ }
+
+ private bool TryDecodeWithWZVersionNumber(WzBinaryReader reader, int useWzVersionHeader, int useMapleStoryPatchVersion, bool lazyParse)
+ {
+ this.mapleStoryPatchVersion = (short)useMapleStoryPatchVersion;
+
+ this.versionHash = CheckAndGetVersionHash(useWzVersionHeader, mapleStoryPatchVersion);
+ if (this.versionHash == 0) // ugly hack, but that's the only way if the version number isnt known (nexon stores this in the .exe)
+ return false;
+
reader.Hash = this.versionHash;
long fallbackOffsetPosition = reader.BaseStream.Position; // save position to rollback to, if should parsing fail from here
WzDirectory testDirectory;
@@ -443,12 +489,15 @@ private static uint CheckAndGetVersionHash(int wzVersionHeader, int maplestoryPa
VersionHash = (32 * VersionHash) + (int)VersionNumberStr[i] + 1;
}
+ if (wzVersionHeader == wzVersionHeader64bit)
+ return (uint)VersionHash; // always 59192
+
int a = (VersionHash >> 24) & 0xFF;
int b = (VersionHash >> 16) & 0xFF;
int c = (VersionHash >> 8) & 0xFF;
int d = VersionHash & 0xFF;
int DecryptedVersionNumber = (0xff ^ a ^ b ^ c ^ d);
-
+
if (wzVersionHeader == DecryptedVersionNumber)
return (uint)VersionHash;
return 0; // invalid
@@ -507,7 +556,7 @@ public void SaveToDisk(string path, WzMapleVersion savingToPreferredWzVer = WzMa
{
wzWriter.Hash = versionHash;
- uint totalLen = wzDir.GetImgOffsets(wzDir.GetOffsets(Header.FStart + 2));
+ uint totalLen = wzDir.GetImgOffsets(wzDir.GetOffsets(Header.FStart + (b64BitClient ? 0 : 2u)));
Header.FSize = totalLen - Header.FStart;
for (int i = 0; i < 4; i++)
{