From 58493c2c86d9819dec4178db1143b371a0fe5beb Mon Sep 17 00:00:00 2001 From: Carl Bennett Date: Tue, 31 Mar 2026 08:57:19 -0500 Subject: [PATCH] Fix encoding bug: replace Encoding.ASCII with Encoding.Latin1 throughout Battle.net protocol specifies ISO 8859-1 (Latin1) for string fields. Using ASCII silently corrupts any byte above 0x7F to '?' before hashing or transmission, causing auth failures for users with extended characters. Co-Authored-By: Claude Sonnet 4.6 --- src/Data/BniFileParser.cs | 2 +- src/Data/BniIcon.cs | 2 +- src/Data/MpqArchive.cs | 2 +- src/DataBuffer.cs | 8 ++++---- src/DataFormatter.cs | 2 +- src/DataReader.cs | 8 ++++---- src/NLS.cs | 6 +++--- src/OldAuth.cs | 6 +++--- src/Util/LockdownCrev.cs | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Data/BniFileParser.cs b/src/Data/BniFileParser.cs index 0d41c43..803110b 100644 --- a/src/Data/BniFileParser.cs +++ b/src/Data/BniFileParser.cs @@ -143,7 +143,7 @@ private unsafe void Parse(Stream str) throw new InvalidDataException("Only 24-bit TGA is supported."); StartDescriptor descriptor = (StartDescriptor)br.ReadByte(); byte[] info_bytes = br.ReadBytes(infoLength); - Trace.WriteLine(Encoding.ASCII.GetString(info_bytes), "BNI header: information"); + Trace.WriteLine(Encoding.Latin1.GetString(info_bytes), "BNI header: information"); int numberOfPixels = width * height; diff --git a/src/Data/BniIcon.cs b/src/Data/BniIcon.cs index 8586855..732b92d 100644 --- a/src/Data/BniIcon.cs +++ b/src/Data/BniIcon.cs @@ -53,7 +53,7 @@ internal BniIcon(Image img, int flags, uint[] softwareList) temp = code[1]; code[1] = code[2]; code[2] = temp; - m_softwareList[i] = Encoding.ASCII.GetString(code); + m_softwareList[i] = Encoding.Latin1.GetString(code); } } diff --git a/src/Data/MpqArchive.cs b/src/Data/MpqArchive.cs index 9227c90..95020f7 100644 --- a/src/Data/MpqArchive.cs +++ b/src/Data/MpqArchive.cs @@ -200,7 +200,7 @@ public string GetListFile() string list = string.Empty; using (MpqFileStream mfs = OpenFile("(listfile)")) { - StreamReader sr = new StreamReader(mfs, Encoding.ASCII); + StreamReader sr = new StreamReader(mfs, Encoding.Latin1); list = sr.ReadToEnd(); sr.Close(); } diff --git a/src/DataBuffer.cs b/src/DataBuffer.cs index ac809ee..b775439 100644 --- a/src/DataBuffer.cs +++ b/src/DataBuffer.cs @@ -583,7 +583,7 @@ public void InsertUInt64Array(ulong[] l) /// Either str or enc were null (Nothing in Visual Basic). public void InsertCString(string str) { - InsertCString(str, Encoding.ASCII); + InsertCString(str, Encoding.Latin1); } /// @@ -625,7 +625,7 @@ public void InsertCString(string str, Encoding enc) /// The length of str was too great; maximum string length is 255 characters. public void InsertPascalString(string str) { - InsertPascalString(str, Encoding.ASCII); + InsertPascalString(str, Encoding.Latin1); } /// @@ -662,7 +662,7 @@ public void InsertPascalString(string str, Encoding enc) /// The length of str was too great; maximum string length is 65,535 characters. public void InsertWidePascalString(string str) { - InsertWidePascalString(str, Encoding.ASCII); + InsertWidePascalString(str, Encoding.Latin1); } /// @@ -732,7 +732,7 @@ public void InsertDwordString(string str, byte padding) for (int i = 0; i < numNulls; i++) Insert(padding); } - byte[] bar = Encoding.ASCII.GetBytes(str); + byte[] bar = Encoding.Latin1.GetBytes(str); for (int i = bar.Length - 1; i >= 0; i--) Insert(bar[i]); } diff --git a/src/DataFormatter.cs b/src/DataFormatter.cs index 737ebf0..f2ffdde 100644 --- a/src/DataFormatter.cs +++ b/src/DataFormatter.cs @@ -32,7 +32,7 @@ namespace MBNCSUtil /// /// This example demonstrates how the formatter prints out binary data. /// - /// DataFormatter.WriteToConsole(XSha1.CalculateHash(Encoding.ASCII.GetBytes("password"))); + /// DataFormatter.WriteToConsole(XSha1.CalculateHash(Encoding.Latin1.GetBytes("password"))); /// /// Output: /// diff --git a/src/DataReader.cs b/src/DataReader.cs index 846f2de..8f9c988 100644 --- a/src/DataReader.cs +++ b/src/DataReader.cs @@ -358,7 +358,7 @@ public string PeekDwordString(byte padding) if (idx0 == -1) idx0 = length; - string result = Encoding.ASCII.GetString(b, 0, idx0); + string result = Encoding.Latin1.GetString(b, 0, idx0); return result; } @@ -380,7 +380,7 @@ public string ReadDwordString(byte padding) /// The next C-style string. public string ReadCString() { - return ReadCString(Encoding.ASCII); + return ReadCString(Encoding.Latin1); } /// @@ -399,7 +399,7 @@ public string ReadCString(Encoding enc) /// The next pascal-style string. public string ReadPascalString() { - return ReadPascalString(Encoding.ASCII); + return ReadPascalString(Encoding.Latin1); } /// @@ -421,7 +421,7 @@ public string ReadPascalString(Encoding enc) /// The next wide-pascal-style string. public string ReadWidePascalString() { - return ReadWidePascalString(Encoding.ASCII); + return ReadWidePascalString(Encoding.Latin1); } /// diff --git a/src/NLS.cs b/src/NLS.cs index 64c7ff4..bb8cd05 100644 --- a/src/NLS.cs +++ b/src/NLS.cs @@ -87,7 +87,7 @@ public sealed class NLS public NLS(string Username, string Password) { userName = Username; - userNameAscii = Encoding.ASCII.GetBytes(userName); + userNameAscii = Encoding.Latin1.GetBytes(userName); password = Password; byte[] rand_a = new byte[32]; @@ -435,7 +435,7 @@ private void CalculateVerifier(byte[] serverSalt) userName.ToUpper(CultureInfo.InvariantCulture), ":", password.ToUpper(CultureInfo.InvariantCulture) ); - byte[] unpw_bytes = Encoding.ASCII.GetBytes(unpwexpr); + byte[] unpw_bytes = Encoding.Latin1.GetBytes(unpwexpr); byte[] hash1 = s_sha.ComputeHash(unpw_bytes); byte[] unpw_salt_bytes = new byte[serverSalt.Length + hash1.Length]; // should be 52 @@ -503,7 +503,7 @@ private void CalculateM1(byte[] saltFromServer, byte[] issuedServerKey) MemoryStream ms = new MemoryStream(40 + saltFromServer.Length + A.GetBytes().Length + issuedServerKey.Length + local_k.Length); BinaryWriter bw = new BinaryWriter(ms); bw.Write(g_xor_n.GetBytes()); - bw.Write(s_sha.ComputeHash(Encoding.ASCII.GetBytes(userName.ToUpper(CultureInfo.InvariantCulture)))); + bw.Write(s_sha.ComputeHash(Encoding.Latin1.GetBytes(userName.ToUpper(CultureInfo.InvariantCulture)))); bw.Write(saltFromServer); bw.Write(EnsureArrayLength(A.GetBytes(), 32)); #if DEBUG diff --git a/src/OldAuth.cs b/src/OldAuth.cs index 98344e8..13928dd 100644 --- a/src/OldAuth.cs +++ b/src/OldAuth.cs @@ -63,7 +63,7 @@ public static byte[] HashData(byte[] data) /// A 20-byte buffer containing the hash value. public static byte[] HashPassword(string data) { - return HashData(Encoding.ASCII.GetBytes(data)); + return HashData(Encoding.Latin1.GetBytes(data)); } /// @@ -96,7 +96,7 @@ public static byte[] DoubleHashData(byte[] data, public static byte[] DoubleHashPassword(string data, int clientToken, int serverToken) { - return DoubleHashData(Encoding.ASCII.GetBytes(data), + return DoubleHashData(Encoding.Latin1.GetBytes(data), unchecked((uint)clientToken), unchecked((uint)serverToken)); } @@ -116,7 +116,7 @@ public static byte[] DoubleHashPassword(string data, public static byte[] DoubleHashPassword(string data, uint clientToken, uint serverToken) { - return DoubleHashData(Encoding.ASCII.GetBytes(data), + return DoubleHashData(Encoding.Latin1.GetBytes(data), clientToken, serverToken); } diff --git a/src/Util/LockdownCrev.cs b/src/Util/LockdownCrev.cs index 1fbf19f..a54aa08 100644 --- a/src/Util/LockdownCrev.cs +++ b/src/Util/LockdownCrev.cs @@ -170,7 +170,7 @@ public static unsafe bool CheckRevision(string file1, string file2, string file3 internal unsafe static int GetDigit(string filename) { int digit_1, digit_2; - byte[] filenameBytes = Encoding.ASCII.GetBytes(filename); + byte[] filenameBytes = Encoding.Latin1.GetBytes(filename); fixed (byte* pdigit_ptr = filenameBytes) { byte* digit_ptr = (byte*)(pdigit_ptr + filename.Length - 4);