Skip to content

Commit c16eb6c

Browse files
committed
WzBinaryWriter: optimisation for .net 8.0 Span<T>
1 parent b2ae668 commit c16eb6c

File tree

1 file changed

+106
-109
lines changed

1 file changed

+106
-109
lines changed

MapleLib/WzLib/Util/WzBinaryWriter.cs

Lines changed: 106 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616

1717
using System;
1818
using System.Collections;
19+
using System.Collections.Generic;
1920
using System.IO;
2021
using System.Linq;
2122
using System.Runtime.CompilerServices;
@@ -33,7 +34,7 @@ public class WzBinaryWriter : BinaryWriter
3334
#region Properties
3435
public WzMutableKey WzKey { get; set; }
3536
public uint Hash { get; set; }
36-
public Hashtable StringCache { get; set; }
37+
public Dictionary<string, int> StringCache { get; set; }
3738
public WzHeader Header { get; set; }
3839
public bool LeaveOpen { get; internal set; }
3940
#endregion
@@ -54,8 +55,8 @@ public WzBinaryWriter(Stream output, byte[] WzIv, bool leaveOpen)
5455
: base(output)
5556
{
5657
WzKey = WzKeyGenerator.GenerateWzKey(WzIv);
57-
StringCache = new Hashtable();
58-
this.LeaveOpen = leaveOpen;
58+
StringCache = new Dictionary<string, int>();
59+
this.LeaveOpen = leaveOpen;
5960
}
6061
#endregion
6162

@@ -112,146 +113,142 @@ public bool WriteWzObjectValue(string stringObjectValue, WzDirectoryType type)
112113
int sOffset = (int)(this.BaseStream.Position - Header.FStart);
113114
Write((byte)type);
114115
Write(stringObjectValue);
115-
if (!StringCache.ContainsKey(storeName))
116-
{
117-
StringCache[storeName] = sOffset;
118-
}
119-
}
116+
StringCache[storeName] = sOffset;
117+
}
120118
return false;
121119
}
122120

123121
public override void Write(string value)
124122
{
125-
if (value.Length == 0)
123+
if (string.IsNullOrEmpty(value))
126124
{
127125
Write((byte)0);
126+
return;
128127
}
129-
else
130-
{
131-
bool unicode = value.Any(c => c > sbyte.MaxValue);
128+
bool unicode = value.Any(c => c > sbyte.MaxValue);
129+
130+
if (unicode)
131+
WriteUnicodeString(value);
132+
else // ASCII
133+
WriteAsciiString(value);
134+
}
132135

133-
if (unicode)
134-
{
135-
ushort mask = 0xAAAA;
136+
/// <summary>
137+
/// Encodes unicode string
138+
/// </summary>
139+
/// <param name="value"></param>
140+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
141+
private void WriteUnicodeString(string value)
142+
{
143+
ushort mask = 0xAAAA;
136144

137-
if (value.Length >= sbyte.MaxValue) // Bugfix - >= because if value.Length = MaxValue, MaxValue will be written and then treated as a long-length marker
138-
{
139-
Write(sbyte.MaxValue);
140-
Write(value.Length);
141-
}
142-
else
143-
{
144-
Write((sbyte)value.Length);
145-
}
145+
WriteStringLength(value.Length, false);
146146

147-
int i = 0;
148-
foreach (var character in value)
149-
{
150-
ushort encryptedChar = (ushort)character;
151-
encryptedChar ^= (ushort)((WzKey[i * 2 + 1] << 8) + WzKey[i * 2]);
152-
encryptedChar ^= mask;
153-
mask++;
154-
Write(encryptedChar);
147+
for (int i = 0; i < value.Length; i++)
148+
{
149+
ushort encryptedChar = (ushort)value[i];
150+
encryptedChar ^= (ushort)((WzKey[i * 2 + 1] << 8) + WzKey[i * 2]);
151+
encryptedChar ^= mask;
152+
mask++;
153+
Write(encryptedChar);
154+
}
155+
}
155156

156-
i++;
157-
}
158-
}
159-
else // ASCII
160-
{
161-
byte mask = 0xAA;
157+
/// <summary>
158+
/// Encodes Ascii string
159+
/// </summary>
160+
/// <param name="value"></param>
161+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
162+
private void WriteAsciiString(string value)
163+
{
164+
byte mask = 0xAA;
162165

163-
if (value.Length > sbyte.MaxValue) // Note - no need for >= here because of 2's complement (MinValue == -(MaxValue + 1))
164-
{
165-
Write(sbyte.MinValue);
166-
Write(value.Length);
167-
}
168-
else
169-
{
170-
Write((sbyte)(-value.Length));
171-
}
166+
WriteStringLength(value.Length, true);
172167

173-
int i = 0;
174-
foreach (char c in value)
175-
{
176-
byte encryptedChar = (byte)c;
177-
encryptedChar ^= WzKey[i];
178-
encryptedChar ^= mask;
179-
mask++;
180-
Write(encryptedChar);
168+
for (int i = 0; i < value.Length; i++)
169+
{
170+
byte encryptedChar = (byte)value[i];
171+
encryptedChar ^= WzKey[i];
172+
encryptedChar ^= mask;
173+
mask++;
174+
Write(encryptedChar);
175+
}
176+
}
181177

182-
i++;
183-
}
184-
}
185-
}
186-
}
178+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
179+
private void WriteStringLength(int length, bool isAscii)
180+
{
181+
// Bugfix - >= because if value.Length = MaxValue, MaxValue will be written and then treated as a long-length marker
182+
// Note - no need for >= here because of 2's complement (MinValue == -(MaxValue + 1))
183+
if (length > sbyte.MaxValue)
184+
{
185+
Write(sbyte.MinValue);
186+
Write(length);
187+
}
188+
else
189+
{
190+
if (isAscii)
191+
Write((sbyte)(-length));
192+
else
193+
Write((sbyte)length);
194+
}
195+
}
187196

188-
public void Write(string value, int length)
189-
{
190-
for (int i = 0; i < length; i++)
191-
{
192-
if (i < value.Length)
193-
{
194-
Write(value[i]);
195-
}
196-
else
197-
{
198-
Write((byte)0);
199-
}
200-
}
201-
}
197+
public void Write(string value, int length)
198+
{
199+
int writeLength = Math.Min(value.Length, length);
200+
Write(value.AsSpan(0, writeLength));
201+
for (int i = writeLength; i < length; i++)
202+
{
203+
Write((byte)0);
204+
}
205+
}
202206

203-
public char[] EncryptString(string stringToDecrypt)
207+
public char[] EncryptString(string stringToEncrypt)
204208
{
205-
char[] outputChars = new char[stringToDecrypt.Length];
206-
for (int i = 0; i < stringToDecrypt.Length; i++)
207-
outputChars[i] = (char)(stringToDecrypt[i] ^ ((char)((WzKey[i * 2 + 1] << 8) + WzKey[i * 2])));
208-
return outputChars;
209+
return stringToEncrypt.Select((c, i) => (char)(c ^ ((WzKey[i * 2 + 1] << 8) + WzKey[i * 2]))).ToArray();
209210
}
210211

211-
public char[] EncryptNonUnicodeString(string stringToDecrypt)
212+
public char[] EncryptNonUnicodeString(string stringToEncrypt)
212213
{
213-
char[] outputChars = new char[stringToDecrypt.Length];
214-
for (int i = 0; i < stringToDecrypt.Length; i++)
215-
outputChars[i] = (char)(stringToDecrypt[i] ^ WzKey[i]);
216-
return outputChars;
214+
return stringToEncrypt.Select((c, i) => (char)(c ^ WzKey[i])).ToArray();
217215
}
218216

219-
public void WriteNullTerminatedString(string value)
220-
{
221-
for (int i = 0; i < value.Length; i++)
222-
{
223-
Write((byte)value[i]);
224-
}
225-
Write((byte)0);
226-
}
217+
public void WriteNullTerminatedString(string value)
218+
{
219+
Write(value.AsSpan());
220+
Write((byte)0);
221+
}
227222

228-
public void WriteCompressedInt(int value)
229-
{
230-
if (value > sbyte.MaxValue || value <= sbyte.MinValue)
231-
{
232-
Write(sbyte.MinValue);
233-
Write(value);
234-
}
235-
else
236-
{
237-
Write((sbyte)value);
238-
}
239-
}
223+
public void WriteCompressedInt(int value) => WriteCompressed(value);
224+
225+
public void WriteCompressedLong(long value) => WriteCompressed(value);
240226

241-
public void WriteCompressedLong(long value)
227+
private void WriteCompressed<T>(T value) where T : IConvertible
242228
{
243-
if (value > sbyte.MaxValue || value <= sbyte.MinValue)
229+
long longValue = value.ToInt64(null);
230+
if (longValue > sbyte.MaxValue || longValue <= sbyte.MinValue)
244231
{
245232
Write(sbyte.MinValue);
246-
Write(value);
233+
switch (Type.GetTypeCode(typeof(T)))
234+
{
235+
case TypeCode.Int32:
236+
Write(value.ToInt32(null));
237+
break;
238+
case TypeCode.Int64:
239+
Write(value.ToInt64(null));
240+
break;
241+
default:
242+
throw new ArgumentException($"Unsupported type for compressed writing: {typeof(T)}");
243+
}
247244
}
248245
else
249246
{
250-
Write((sbyte)value);
247+
Write(value.ToSByte(null));
251248
}
252249
}
253250

254-
public void WriteOffset(long value)
251+
public void WriteOffset(long value)
255252
{
256253
uint encOffset = (uint)BaseStream.Position;
257254
encOffset = (encOffset - Header.FStart) ^ 0xFFFFFFFF;

0 commit comments

Comments
 (0)