From e7a1bda2af049be2f910ed2bfe214b6641171e80 Mon Sep 17 00:00:00 2001
From: Quick <577652+markdwags@users.noreply.github.com>
Date: Fri, 1 Nov 2024 11:44:32 -0500
Subject: [PATCH] Read updated cliloc format
---
Razor/Razor.csproj | 19 +
Razor/UltimaSDK/BwtDecompress.cs | 253 +++++++++++++
Razor/UltimaSDK/StackDataReader.cs | 495 ++++++++++++++++++++++++++
Razor/UltimaSDK/StringHelper.cs | 379 ++++++++++++++++++++
Razor/UltimaSDK/StringList.cs | 56 +++
Razor/UltimaSDK/ValueStringBuilder.cs | 378 ++++++++++++++++++++
Razor/packages.config | 7 +
7 files changed, 1587 insertions(+)
create mode 100644 Razor/UltimaSDK/BwtDecompress.cs
create mode 100644 Razor/UltimaSDK/StackDataReader.cs
create mode 100644 Razor/UltimaSDK/StringHelper.cs
create mode 100644 Razor/UltimaSDK/ValueStringBuilder.cs
create mode 100644 Razor/packages.config
diff --git a/Razor/Razor.csproj b/Razor/Razor.csproj
index 1ca6fc77..12f762b4 100644
--- a/Razor/Razor.csproj
+++ b/Razor/Razor.csproj
@@ -156,9 +156,13 @@
.\cuoapi.dll
+
System
+
+ ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+
System.Data
@@ -166,6 +170,16 @@
System.Drawing
+
+ ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll
+
+
+
+ ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
System.Windows.Forms
@@ -493,6 +507,7 @@
+
@@ -512,7 +527,9 @@
+
+
@@ -520,6 +537,7 @@
+
@@ -627,6 +645,7 @@
+
diff --git a/Razor/UltimaSDK/BwtDecompress.cs b/Razor/UltimaSDK/BwtDecompress.cs
new file mode 100644
index 00000000..57ebdd6b
--- /dev/null
+++ b/Razor/UltimaSDK/BwtDecompress.cs
@@ -0,0 +1,253 @@
+#region license
+// Razor: An Ultima Online Assistant
+// Copyright (c) 2024 Razor Development Community on GitHub
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+#endregion
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ultima
+{
+ internal static class BwtDecompress
+ {
+ public static byte[] Decompress(byte[] buffer)
+ {
+ byte[] output = null;
+
+ using (var reader = new BinaryReader(new MemoryStream(buffer)))
+ {
+ var header = reader.ReadUInt32();
+ var len = 0u;
+
+ var firstChar = reader.ReadByte();
+
+ Span table = new ushort[256 * 256];
+ table = BuildTable(table, firstChar);
+
+ var list = new byte[reader.BaseStream.Length - 4];
+ var i = 0;
+ while (reader.BaseStream.Position < reader.BaseStream.Length)
+ {
+ var currentValue = firstChar;
+ var value = table[currentValue];
+ if (currentValue > 0)
+ {
+ do
+ {
+ table[currentValue] = table[currentValue - 1];
+ } while (--currentValue > 0);
+ }
+
+ table[0] = value;
+
+ list[i++] = (byte) value;
+ firstChar = reader.ReadByte();
+ }
+
+ output = InternalDecompress(list, len);
+ }
+
+ return output;
+ }
+
+ ///
+ static void MergeSort(Span span)
+ {
+ if (span.Length <= 1)
+ return;
+
+ var list = span.ToArray().ToList();
+ list.Sort();
+
+ int mid = span.Length / 2;
+ var left = span.Slice(0, mid);
+ var right = span.Slice(mid);
+
+ MergeSort(left);
+ MergeSort(right);
+
+ Merge(span, left, right);
+ }
+
+ static void Merge(Span destination, Span left, Span right)
+ {
+ int i = 0, j = 0, k = 0;
+
+ while (i < left.Length && j < right.Length)
+ {
+ if (left[i] <= right[j])
+ {
+ destination[k++] = left[i++];
+ }
+ else
+ {
+ destination[k++] = right[j++];
+ }
+ }
+
+ while (i < left.Length)
+ {
+ destination[k++] = left[i++];
+ }
+
+ while (j < right.Length)
+ {
+ destination[k++] = right[j++];
+ }
+ }
+
+ ///
+
+ static Span BuildTable(Span table, byte startValue)
+ {
+ int index = 0;
+ byte firstByte = startValue;
+ byte secondByte = 0;
+ for (int i = 0; i < 256 * 256; i++)
+ {
+ var val = (ushort) (firstByte + (secondByte << 8));
+ table[index++] = val;
+
+ firstByte++;
+ if (firstByte == 0)
+ {
+ secondByte++;
+ }
+ }
+
+ var list = table.ToArray().ToList();
+ list.Sort();
+ return list.ToArray();
+ }
+
+ static byte[] InternalDecompress(Span input, uint len)
+ {
+ Span symbolTable = stackalloc char[256];
+ Span frequency = stackalloc char[256];
+ Span partialInput = stackalloc int[256 * 3];
+ partialInput.Clear();
+
+ for (var i = 0; i < 256; i++)
+ symbolTable[i] = (char) i;
+
+ input.Slice(0, 1024).CopyTo(MemoryMarshal.AsBytes(partialInput));
+
+ var sum = 0;
+ for (var i = 0; i < 256; i++)
+ sum += partialInput[i];
+
+ if (len == 0)
+ {
+ len = (uint) sum;
+ }
+
+ if (sum != len)
+ return Array.Empty();
+
+ var output = new byte[len];
+
+ var count = 0;
+ var nonZeroCount = 0;
+
+ for (var i = 0; i < 256; i++)
+ {
+ if (partialInput[i] != 0)
+ nonZeroCount++;
+ }
+
+ Frequency(partialInput, frequency);
+
+ for (int i = 0, m = 0; i < nonZeroCount; ++i)
+ {
+ var freq = (byte) frequency[i];
+ symbolTable[input[m + 1024]] = (char) freq;
+ partialInput[freq + 256] = m + 1;
+ m += partialInput[freq];
+ partialInput[freq + 512] = m;
+ }
+
+ var val = (byte) symbolTable[0];
+
+ if (len != 0)
+ {
+ do
+ {
+ ref var firstValRef = ref partialInput[val + 256];
+ output[count] = val;
+
+ if (firstValRef >= partialInput[val + 512])
+ {
+ if (nonZeroCount-- > 0)
+ {
+ ShiftLeft(symbolTable, nonZeroCount);
+ val = (byte) symbolTable[0];
+ }
+ }
+ else
+ {
+ var idx = (char) input[firstValRef + 1024];
+ firstValRef++;
+
+ if (idx != 0)
+ {
+ ShiftLeft(symbolTable, idx);
+ symbolTable[(byte) idx] = (char) val;
+ val = (byte) symbolTable[0];
+ }
+ }
+
+ count++;
+ } while (count < len);
+ }
+
+ return output;
+ }
+
+ static void Frequency(Span input, Span output)
+ {
+ Span tmp = stackalloc int[256];
+ input.Slice(0, tmp.Length).CopyTo(tmp);
+
+ for (var i = 0; i < 256; i++)
+ {
+ uint value = 0;
+ byte index = 0;
+
+ for (var j = 0; j < 256; j++)
+ {
+ if (tmp[j] > value)
+ {
+ index = (byte) j;
+ value = (uint) tmp[j];
+ }
+ }
+
+ if (value == 0)
+ break;
+
+ output[i] = (char) index;
+ tmp[index] = 0;
+ }
+ }
+
+ static void ShiftLeft(Span input, int max)
+ {
+ for (var i = 0; i < max; ++i)
+ input[i] = input[i + 1];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Razor/UltimaSDK/StackDataReader.cs b/Razor/UltimaSDK/StackDataReader.cs
new file mode 100644
index 00000000..d04dcf7b
--- /dev/null
+++ b/Razor/UltimaSDK/StackDataReader.cs
@@ -0,0 +1,495 @@
+#region license
+
+// Copyright (c) 2024, andreakarasho
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All advertising materials mentioning features or use of this software
+// must display the following acknowledgement:
+// This product includes software developed by andreakarasho - https://github.com/andreakarasho
+// 4. Neither the name of the copyright holder nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+using System;
+using System.Buffers.Binary;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ultima
+{
+ unsafe ref struct StackDataReader
+ {
+ private readonly ReadOnlySpan _data;
+ public StackDataReader(ReadOnlySpan data)
+ {
+ _data = data;
+ Length = data.Length;
+ Position = 0;
+ }
+
+ public int Position { get; private set; }
+ public long Length { get; }
+ public int Remaining => (int)(Length - Position);
+
+ public IntPtr StartAddress => (IntPtr)Unsafe.AsPointer(ref GetPinnableReference());
+ public IntPtr PositionAddress
+ {
+ get
+ {
+ return (IntPtr)((byte*)Unsafe.AsPointer(ref GetPinnableReference()) + Position);
+ }
+ }
+
+ public byte this[int index] => _data[0];
+
+ public ReadOnlySpan Buffer => _data;
+
+
+ public ref byte GetPinnableReference()
+ {
+ return ref MemoryMarshal.GetReference(_data);
+ }
+
+ public void Release()
+ {
+ // do nothing right now.
+ }
+
+ public void Seek(long p)
+ {
+ Position = (int)p;
+ }
+
+ public void Skip(int count)
+ {
+ Position += count;
+ }
+
+ public byte ReadUInt8()
+ {
+ if (Position + 1 > Length)
+ {
+ return 0;
+ }
+
+ return _data[Position++];
+ }
+
+ public sbyte ReadInt8()
+ {
+ if (Position + 1 > Length)
+ {
+ return 0;
+ }
+
+ return (sbyte)_data[Position++];
+ }
+
+ public bool ReadBool() => ReadUInt8() != 0;
+
+ public ushort ReadUInt16LE()
+ {
+ if (Position + 2 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt16LittleEndian(_data.Slice(Position), out ushort v);
+
+ Skip(2);
+
+ return v;
+ }
+
+ public short ReadInt16LE()
+ {
+ if (Position + 2 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadInt16LittleEndian(_data.Slice(Position), out short v);
+
+ Skip(2);
+
+ return v;
+ }
+
+ public uint ReadUInt32LE()
+ {
+ if (Position + 4 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt32LittleEndian(_data.Slice(Position), out uint v);
+
+ Skip(4);
+
+ return v;
+ }
+
+ public int ReadInt32LE()
+ {
+ if (Position + 4 > Length)
+ {
+ return 0;
+ }
+
+ int v = BinaryPrimitives.ReadInt32LittleEndian(_data.Slice(Position));
+
+ Skip(4);
+
+ return v;
+ }
+
+ public ulong ReadUInt64LE()
+ {
+ if (Position + 8 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt64LittleEndian(_data.Slice(Position), out ulong v);
+
+ Skip(8);
+
+ return v;
+ }
+
+ public long ReadInt64LE()
+ {
+ if (Position + 8 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadInt64LittleEndian(_data.Slice(Position), out long v);
+
+ Skip(8);
+
+ return v;
+ }
+
+
+
+
+
+ public ushort ReadUInt16BE()
+ {
+ if (Position + 2 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt16BigEndian(_data.Slice(Position), out ushort v);
+
+ Skip(2);
+
+ return v;
+ }
+
+ public short ReadInt16BE()
+ {
+ if (Position + 2 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadInt16BigEndian(_data.Slice(Position), out short v);
+
+ Skip(2);
+
+ return v;
+ }
+
+ public uint ReadUInt32BE()
+ {
+ if (Position + 4 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt32BigEndian(_data.Slice(Position), out uint v);
+
+ Skip(4);
+
+ return v;
+ }
+
+ public int ReadInt32BE()
+ {
+ if (Position + 4 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadInt32BigEndian(_data.Slice(Position), out int v);
+
+ Skip(4);
+
+ return v;
+ }
+
+ public ulong ReadUInt64BE()
+ {
+ if (Position + 8 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadUInt64BigEndian(_data.Slice(Position), out ulong v);
+
+ Skip(8);
+
+ return v;
+ }
+
+ public long ReadInt64BE()
+ {
+ if (Position + 8 > Length)
+ {
+ return 0;
+ }
+
+ BinaryPrimitives.TryReadInt64BigEndian(_data.Slice(Position), out long v);
+
+ Skip(8);
+
+ return v;
+ }
+
+ private string ReadRawString(int length, int sizeT, bool safe)
+ {
+ if (length == 0 || Position + sizeT > Length)
+ {
+ return string.Empty;
+ }
+
+ bool fixedLength = length > 0;
+ int remaining = Remaining;
+ int size;
+
+ if (fixedLength)
+ {
+ size = length * sizeT;
+
+ if (size > remaining)
+ {
+ size = remaining;
+ }
+ }
+ else
+ {
+ size = remaining - (remaining & (sizeT - 1));
+ }
+
+ ReadOnlySpan slice = _data.Slice(Position, size);
+
+ int index = GetIndexOfZero(slice, sizeT);
+ size = index < 0 ? size : index;
+
+ string result;
+
+ if (size <= 0)
+ {
+ result = String.Empty;
+ }
+ else
+ {
+ result = StringHelper.Cp1252ToString(slice.Slice(0, size));
+
+ if (safe)
+ {
+ Span buff = stackalloc char[256];
+ ReadOnlySpan chars = result.AsSpan();
+
+ ValueStringBuilder sb = new ValueStringBuilder(buff);
+
+ bool hasDoneAnyReplacements = false;
+ int last = 0;
+ for (int i = 0; i < chars.Length; i++)
+ {
+ if (!StringHelper.IsSafeChar(chars[i]))
+ {
+ hasDoneAnyReplacements = true;
+ sb.Append(chars.Slice(last, i - last));
+ last = i + 1; // Skip the unsafe char
+ }
+ }
+
+ if (hasDoneAnyReplacements)
+ {
+ // append the rest of the string
+ if (last < chars.Length)
+ {
+ sb.Append(chars.Slice(last, chars.Length - last));
+ }
+
+ result = sb.ToString();
+ }
+
+ sb.Dispose();
+ }
+ }
+
+ Position += Math.Max(size + (!fixedLength && index >= 0 ? sizeT : 0), length * sizeT);
+
+ return result;
+ }
+
+ public string ReadASCII(bool safe = false)
+ {
+ return ReadRawString(-1, 1, safe);
+ //return ReadString(StringHelper.Cp1252Encoding, -1, 1, safe);
+ }
+
+ public string ReadASCII(int length, bool safe = false)
+ {
+ return ReadRawString(length, 1, safe);
+
+ //return ReadString(StringHelper.Cp1252Encoding, length, 1, safe);
+ }
+
+ public string ReadUnicodeBE(bool safe = false)
+ {
+ return ReadString(Encoding.BigEndianUnicode, -1, 2, safe);
+ }
+
+ public string ReadUnicodeBE(int length, bool safe = false)
+ {
+ return ReadString(Encoding.BigEndianUnicode, length, 2, safe);
+ }
+
+ public string ReadUnicodeLE(bool safe = false)
+ {
+ return ReadString(Encoding.Unicode, -1, 2, safe);
+ }
+
+ public string ReadUnicodeLE(int length, bool safe = false)
+ {
+ return ReadString(Encoding.Unicode, length, 2, safe);
+ }
+
+ public string ReadUTF8(bool safe = false)
+ {
+ return ReadString(Encoding.UTF8, -1, 1, safe);
+ }
+
+ public string ReadUTF8(int length, bool safe = false)
+ {
+ return ReadString(Encoding.UTF8, length, 1, safe);
+ }
+
+ public void Read(Span data, int offset, int count)
+ {
+ _data.Slice(Position + offset, count).CopyTo(data);
+ }
+
+ // from modernuo <3
+ private string ReadString(Encoding encoding, int length, int sizeT, bool safe)
+ {
+ if (length == 0 || Position + sizeT > Length)
+ {
+ return string.Empty;
+ }
+
+ bool fixedLength = length > 0;
+ int remaining = Remaining;
+ int size;
+
+ if (fixedLength)
+ {
+ size = length * sizeT;
+
+ if (size > remaining)
+ {
+ size = remaining;
+ }
+ }
+ else
+ {
+ size = remaining - (remaining & (sizeT - 1));
+ }
+
+ ReadOnlySpan slice = _data.Slice(Position, size);
+
+ int index = GetIndexOfZero(slice, sizeT);
+ size = index < 0 ? size : index;
+
+ string result;
+
+ fixed (byte* ptr = slice)
+ {
+ result = encoding.GetString(ptr, size);
+ }
+
+ if (safe)
+ {
+ Span buff = stackalloc char[256];
+ ReadOnlySpan chars = result.AsSpan();
+
+ ValueStringBuilder sb = new ValueStringBuilder(buff);
+
+ bool hasDoneAnyReplacements = false;
+ int last = 0;
+ for (int i = 0; i < chars.Length; i++)
+ {
+ if (!StringHelper.IsSafeChar(chars[i]))
+ {
+ hasDoneAnyReplacements = true;
+ sb.Append(chars.Slice(last, i - last));
+ last = i + 1; // Skip the unsafe char
+ }
+ }
+
+ if (hasDoneAnyReplacements)
+ {
+ // append the rest of the string
+ if (last < chars.Length)
+ {
+ sb.Append(chars.Slice(last, chars.Length - last));
+ }
+
+ result = sb.ToString();
+ }
+
+ sb.Dispose();
+ }
+
+ Position += Math.Max(size + (!fixedLength && index >= 0 ? sizeT : 0), length * sizeT);
+
+ return result;
+ }
+
+ private static int GetIndexOfZero(ReadOnlySpan span, int sizeT)
+ {
+ switch (sizeT)
+ {
+ case 2: return MemoryMarshal.Cast(span).IndexOf('\0') * 2;
+ case 4: return MemoryMarshal.Cast(span).IndexOf((uint)0) * 4;
+ default: return span.IndexOf((byte)0);
+ }
+ }
+ }
+}
diff --git a/Razor/UltimaSDK/StringHelper.cs b/Razor/UltimaSDK/StringHelper.cs
new file mode 100644
index 00000000..b0787c81
--- /dev/null
+++ b/Razor/UltimaSDK/StringHelper.cs
@@ -0,0 +1,379 @@
+#region license
+
+// Copyright (c) 2021, andreakarasho
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. All advertising materials mentioning features or use of this software
+// must display the following acknowledgement:
+// This product includes software developed by andreakarasho - https://github.com/andreakarasho
+// 4. Neither the name of the copyright holder nor the
+// names of its contributors may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Ultima
+{
+ internal static class StringHelper
+ {
+ private static readonly char[] _dots = { '.', ',', ';', '!' };
+
+ public static IEnumerable StringToCp1252Bytes(string s, int length = -1)
+ {
+ length = length > 0 ? Math.Min(length, s.Length) : s.Length;
+
+ for (int i = 0; i < length; i += char.IsSurrogatePair(s, i) ? 2 : 1)
+ {
+ yield return UnicodeToCp1252(char.ConvertToUtf32(s, i));
+ }
+ }
+
+ public static string Cp1252ToString(ReadOnlySpan strCp1252)
+ {
+ var sb = new ValueStringBuilder(strCp1252.Length);
+
+ for (int i = 0; i < strCp1252.Length; ++i)
+ {
+ sb.Append(char.ConvertFromUtf32(Cp1252ToUnicode(strCp1252[i])));
+ }
+
+ var str = sb.ToString();
+
+ sb.Dispose();
+
+ return str;
+ }
+
+ ///
+ /// Converts a unicode code point into a cp1252 code point
+ ///
+ private static byte UnicodeToCp1252(int codepoint)
+ {
+ if (codepoint >= 0x80 && codepoint <= 0x9f)
+ return (byte)'?';
+ else if (codepoint <= 0xff)
+ return (byte)codepoint;
+ else
+ {
+ switch (codepoint)
+ {
+ case 0x20AC: return 128; //€
+ case 0x201A: return 130; //‚
+ case 0x0192: return 131; //ƒ
+ case 0x201E: return 132; //„
+ case 0x2026: return 133; //…
+ case 0x2020: return 134; //†
+ case 0x2021: return 135; //‡
+ case 0x02C6: return 136; //ˆ
+ case 0x2030: return 137; //‰
+ case 0x0160: return 138; //Š
+ case 0x2039: return 139; //‹
+ case 0x0152: return 140; //Œ
+ case 0x017D: return 142; //Ž
+ case 0x2018: return 145; //‘
+ case 0x2019: return 146; //’
+ case 0x201C: return 147; //“
+ case 0x201D: return 148; //”
+ case 0x2022: return 149; //•
+ case 0x2013: return 150; //–
+ case 0x2014: return 151; //—
+ case 0x02DC: return 152; //˜
+ case 0x2122: return 153; //™
+ case 0x0161: return 154; //š
+ case 0x203A: return 155; //›
+ case 0x0153: return 156; //œ
+ case 0x017E: return 158; //ž
+ case 0x0178: return 159; //Ÿ
+ default: return (byte)'?';
+ }
+ }
+ }
+
+ ///
+ /// Converts a cp1252 code point into a unicode code point
+ ///
+ private static int Cp1252ToUnicode(byte codepoint)
+ {
+ switch (codepoint)
+ {
+ case 128: return 0x20AC; //€
+ case 130: return 0x201A; //‚
+ case 131: return 0x0192; //ƒ
+ case 132: return 0x201E; //„
+ case 133: return 0x2026; //…
+ case 134: return 0x2020; //†
+ case 135: return 0x2021; //‡
+ case 136: return 0x02C6; //ˆ
+ case 137: return 0x2030; //‰
+ case 138: return 0x0160; //Š
+ case 139: return 0x2039; //‹
+ case 140: return 0x0152; //Œ
+ case 142: return 0x017D; //Ž
+ case 145: return 0x2018; //‘
+ case 146: return 0x2019; //’
+ case 147: return 0x201C; //“
+ case 148: return 0x201D; //”
+ case 149: return 0x2022; //•
+ case 150: return 0x2013; //–
+ case 151: return 0x2014; //—
+ case 152: return 0x02DC; //˜
+ case 153: return 0x2122; //™
+ case 154: return 0x0161; //š
+ case 155: return 0x203A; //›
+ case 156: return 0x0153; //œ
+ case 158: return 0x017E; //ž
+ case 159: return 0x0178; //Ÿ
+ default: return codepoint;
+ }
+ }
+
+ public static string CapitalizeFirstCharacter(string str)
+ {
+ if (string.IsNullOrEmpty(str))
+ {
+ return string.Empty;
+ }
+
+ if (str.Length == 1)
+ {
+ return char.ToUpper(str[0]).ToString();
+ }
+
+ return char.ToUpper(str[0]) + str.Substring(1);
+ }
+
+
+ public static string CapitalizeAllWords(string str)
+ {
+ if (string.IsNullOrEmpty(str))
+ {
+ return string.Empty;
+ }
+
+ Span span = stackalloc char[str.Length];
+ ValueStringBuilder sb = new ValueStringBuilder(span);
+ bool capitalizeNext = true;
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ sb.Append(capitalizeNext ? char.ToUpper(str[i]) : str[i]);
+
+ if (!char.IsWhiteSpace(str[i]))
+ {
+ capitalizeNext = i + 1 < str.Length && char.IsWhiteSpace(str[i + 1]);
+ }
+ }
+
+ string ss = sb.ToString();
+
+ sb.Dispose();
+
+ return ss;
+ }
+
+ public static string CapitalizeWordsByLimitator(string str)
+ {
+ if (string.IsNullOrEmpty(str))
+ {
+ return string.Empty;
+ }
+
+ Span span = stackalloc char[str.Length];
+ ValueStringBuilder sb = new ValueStringBuilder(span);
+
+ bool capitalizeNext = true;
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ sb.Append(capitalizeNext ? char.ToUpper(str[i]) : str[i]);
+ capitalizeNext = false;
+
+ for (int j = 0; j < _dots.Length; j++)
+ {
+ if (str[i] == _dots[j])
+ {
+ capitalizeNext = true;
+
+ break;
+ }
+ }
+ }
+
+ string ss = sb.ToString();
+
+ sb.Dispose();
+
+ return ss;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsSafeChar(int c)
+ {
+ return c >= 0x20 && c < 0xFFFE;
+ }
+
+ public static void AddSpaceBeforeCapital(string[] str, bool checkAcronyms = true)
+ {
+ for (int i = 0; i < str.Length; i++)
+ {
+ str[i] = AddSpaceBeforeCapital(str[i], checkAcronyms);
+ }
+ }
+
+ public static string AddSpaceBeforeCapital(string str, bool checkAcronyms = true)
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ {
+ return "";
+ }
+
+ ValueStringBuilder sb = new ValueStringBuilder(str.Length * 2);
+ sb.Append(str[0]);
+
+ for (int i = 1, len = str.Length - 1; i <= len; i++)
+ {
+ if (char.IsUpper(str[i]))
+ {
+ if (str[i - 1] != ' ' && !char.IsUpper(str[i - 1]) || checkAcronyms && char.IsUpper(str[i - 1]) && i < len && !char.IsUpper(str[i + 1]))
+ {
+ sb.Append(' ');
+ }
+ }
+
+ sb.Append(str[i]);
+ }
+
+ string s = sb.ToString();
+
+ sb.Dispose();
+
+ return s;
+ }
+
+ public static string RemoveUpperLowerChars(string str, bool removelower = true)
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ {
+ return "";
+ }
+
+ Span span = stackalloc char[str.Length];
+ ValueStringBuilder sb = new ValueStringBuilder(span);
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (char.IsUpper(str[i]) == removelower || str[i] == ' ')
+ {
+ sb.Append(str[i]);
+ }
+ }
+
+ string ss = sb.ToString();
+
+ sb.Dispose();
+
+ return ss;
+ }
+
+ public static string IntToAbbreviatedString(int num)
+ {
+ if (num > 999999)
+ {
+ return string.Format("{0}M+", num / 1000000);
+ }
+
+ if (num > 999)
+ {
+ return string.Format("{0}K+", num / 1000);
+ }
+
+ return num.ToString();
+ }
+
+
+ public static string GetPluralAdjustedString(string str, bool plural = false)
+ {
+ if (str.Contains("%"))
+ {
+ string[] parts = str.Split(new[] { '%' }, System.StringSplitOptions.RemoveEmptyEntries);
+
+ if (parts.Length < 2)
+ {
+ return str;
+ }
+
+ Span span = stackalloc char[str.Length];
+ ValueStringBuilder sb = new ValueStringBuilder(span);
+
+ sb.Append(parts[0]);
+
+ if (parts[1].Contains("/"))
+ {
+ string[] pluralparts = parts[1].Split('/');
+
+ if (plural)
+ {
+ sb.Append(pluralparts[0]);
+ }
+ else if (pluralparts.Length > 1)
+ {
+ sb.Append(pluralparts[1]);
+ }
+ }
+ else if (plural)
+ {
+ sb.Append(parts[1]);
+ }
+
+ if (parts.Length == 3)
+ {
+ sb.Append(parts[2]);
+ }
+
+ string ss = sb.ToString();
+
+ sb.Dispose();
+
+ return ss;
+ }
+
+ return str;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool UnsafeCompare(char* buffer, string str, int length)
+ {
+ for (int i = 0; i < length && i < str.Length; ++i)
+ {
+ if (buffer[i] != str[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Razor/UltimaSDK/StringList.cs b/Razor/UltimaSDK/StringList.cs
index db2dfd3d..198cc267 100644
--- a/Razor/UltimaSDK/StringList.cs
+++ b/Razor/UltimaSDK/StringList.cs
@@ -20,6 +20,7 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
+using Assistant;
namespace Ultima
{
@@ -54,10 +55,23 @@ public StringList(string language)
public StringList(string language, string path)
{
Language = language;
+
LoadEntry(path);
}
private void LoadEntry(string path)
+ {
+ if (Engine.ClientVersion.Major >= 7 && Engine.ClientVersion.Build >= 105)
+ {
+ LoadNewEntryFormat(path);
+ }
+ else
+ {
+ LoadOldEntryFormat(path);
+ }
+ }
+
+ private void LoadOldEntryFormat(string path)
{
if (path == null)
{
@@ -95,6 +109,48 @@ private void LoadEntry(string path)
}
}
}
+
+ // Based on the implementation from Karasho @ ClassicUO
+ private void LoadNewEntryFormat(string path)
+ {
+ if (path == null)
+ {
+ Entries = new List(0);
+ return;
+ }
+
+ Entries = new List();
+ m_StringTable = new Dictionary();
+ m_EntryTable = new Dictionary();
+
+ using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
+ {
+ int bytesRead;
+ var totalRead = 0;
+ var buf = new byte[fileStream.Length];
+ while ((bytesRead = fileStream.Read(buf, totalRead, Math.Min(4096, buf.Length - totalRead))) > 0)
+ totalRead += bytesRead;
+
+ var output = buf[3] == 0x8E ? BwtDecompress.Decompress(buf) : buf;
+
+ var reader = new StackDataReader(output);
+ m_Header1 = reader.ReadInt32LE();
+ m_Header2 = reader.ReadInt16LE();
+
+ while (reader.Remaining > 0)
+ {
+ var number = reader.ReadInt32LE();
+ var flag = reader.ReadUInt8();
+ var length = reader.ReadInt16LE();
+ var text = string.Intern(reader.ReadUTF8(length));
+
+ m_StringTable[number] = text;
+ StringEntry se = new StringEntry(number, text, flag);
+ Entries.Add(se);
+ m_EntryTable[number] = se;
+ }
+ }
+ }
///
/// Saves to FileName
diff --git a/Razor/UltimaSDK/ValueStringBuilder.cs b/Razor/UltimaSDK/ValueStringBuilder.cs
new file mode 100644
index 00000000..606c9489
--- /dev/null
+++ b/Razor/UltimaSDK/ValueStringBuilder.cs
@@ -0,0 +1,378 @@
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ultima
+{
+ // https://github.com/Thealexbarney/LibHac/blob/master/src/LibHac/FsSystem/ValueStringBuilder.cs
+ internal ref struct ValueStringBuilder
+ {
+ private char[] _arrayToReturnToPool;
+ private Span _chars;
+ private int _pos;
+
+
+ // If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
+ public ValueStringBuilder(ReadOnlySpan initialString) : this(initialString.Length)
+ {
+ Append(initialString);
+ }
+
+ public ValueStringBuilder(ReadOnlySpan initialString, Span initialBuffer) : this(initialBuffer)
+ {
+ Append(initialString);
+ }
+
+ public ValueStringBuilder(Span initialBuffer)
+ {
+ _arrayToReturnToPool = null;
+ _chars = initialBuffer;
+ _pos = 0;
+ }
+
+ public ValueStringBuilder(int initialCapacity)
+ {
+ _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
+ _chars = _arrayToReturnToPool;
+ _pos = 0;
+ }
+
+ public int Length
+ {
+ get => _pos;
+ set
+ {
+ Debug.Assert(value >= 0);
+ Debug.Assert(value <= _chars.Length);
+ _pos = value;
+ }
+ }
+
+ public int Capacity => _chars.Length;
+
+ public void EnsureCapacity(int capacity)
+ {
+ if (capacity > _chars.Length)
+ Grow(capacity - _chars.Length);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ /// Does not ensure there is a null char after
+ /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
+ /// the explicit method call, and write eg "fixed (char* c = builder)"
+ ///
+ public ref char GetPinnableReference()
+ {
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ ///
+ /// Ensures that the builder has a null char after
+ public ref char GetPinnableReference(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ public ref char this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _chars[index];
+ }
+ }
+
+ public override string ToString()
+ {
+ string s = _chars.Slice(0, _pos).ToString();
+ Dispose();
+ return s;
+ }
+
+ /// Returns the underlying storage of the builder.
+ public Span RawChars => _chars;
+
+ ///
+ /// Returns a span around the contents of the builder.
+ ///
+ /// Ensures that the builder has a null char after
+ public ReadOnlySpan AsSpan(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return _chars.Slice(0, _pos);
+ }
+
+ public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos);
+ public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start);
+ public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length);
+
+ public bool TryCopyTo(Span destination, out int charsWritten)
+ {
+ if (_chars.Slice(0, _pos).TryCopyTo(destination))
+ {
+ charsWritten = _pos;
+ Dispose();
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ Dispose();
+ return false;
+ }
+ }
+
+ public void Insert(int index, char value, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ _chars.Slice(index, count).Fill(value);
+ _pos += count;
+ }
+
+ public void Insert(int index, ReadOnlySpan s)
+ {
+ int count = s.Length;
+
+ if (_pos > (_chars.Length - count))
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ s.CopyTo(_chars.Slice(index));
+ _pos += count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(char c)
+ {
+ int pos = _pos;
+ if ((uint)pos < (uint)_chars.Length)
+ {
+ _chars[pos] = c;
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowAndAppend(c);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(string s)
+ {
+ int pos = _pos;
+ if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
+ {
+ _chars[pos] = s[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ AppendSlow(s);
+ }
+ }
+
+ private void AppendSlow(string s)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - s.Length)
+ {
+ Grow(s.Length);
+ }
+
+ s.AsSpan().CopyTo(_chars.Slice(pos));
+ _pos += s.Length;
+ }
+
+ public void Append(char c, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ Span dst = _chars.Slice(_pos, count);
+ for (int i = 0; i < dst.Length; i++)
+ {
+ dst[i] = c;
+ }
+ _pos += count;
+ }
+
+ public void Append(ReadOnlySpan value)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - value.Length)
+ {
+ Grow(value.Length);
+ }
+
+ value.CopyTo(_chars.Slice(_pos));
+ _pos += value.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AppendSpan(int length)
+ {
+ int origPos = _pos;
+ if (origPos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ _pos = origPos + length;
+ return _chars.Slice(origPos, length);
+ }
+
+ public void Replace(ReadOnlySpan oldChars, ReadOnlySpan newChars)
+ {
+ Replace(oldChars, newChars, 0, _pos);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Replace(ReadOnlySpan oldChars, ReadOnlySpan newChars, int startIndex, int count)
+ {
+ var slice = _chars.Slice(startIndex, count);
+
+ var indexOf = slice.IndexOf(oldChars);
+
+ if (indexOf == -1)
+ {
+ return;
+ }
+
+ if (newChars.Length > oldChars.Length)
+ {
+ int i = 0;
+
+ for (; i < oldChars.Length; ++i)
+ {
+ slice[indexOf + i] = newChars[i];
+ }
+
+ Insert(indexOf + i, newChars.Slice(i));
+ }
+ else if (newChars.Length < oldChars.Length)
+ {
+ int i = 0;
+
+ for (; i < newChars.Length; ++i)
+ {
+ slice[indexOf + i] = newChars[i];
+ }
+
+ Remove(indexOf + i, oldChars.Length - i);
+ }
+ else
+ {
+ newChars.CopyTo(slice.Slice(0, oldChars.Length));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Replace(char oldChar, char newChar)
+ {
+ var slice = _chars;
+
+ var indexOf = slice.IndexOf(oldChar);
+ if (indexOf == -1)
+ {
+ return;
+ }
+
+ slice[indexOf] = newChar;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Remove(int startIndex, int length)
+ {
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ if (startIndex < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+ }
+
+ if (length > _pos - startIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ if (startIndex == 0)
+ {
+ _chars = _chars.Slice(length);
+ }
+ else if (startIndex + length == _pos)
+ {
+ _chars = _chars.Slice(0, startIndex);
+ }
+ else
+ {
+ // Somewhere in the middle, this will be slow
+ _chars.Slice(startIndex + length).CopyTo(_chars.Slice(startIndex));
+ }
+
+ _pos -= length;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndAppend(char c)
+ {
+ Grow(1);
+ Append(c);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Grow(int requiredAdditionalCapacity)
+ {
+ Debug.Assert(requiredAdditionalCapacity > 0);
+
+ char[] poolArray = ArrayPool.Shared.Rent(Math.Max(_pos + requiredAdditionalCapacity, _chars.Length * 2));
+
+ _chars.CopyTo(poolArray);
+
+ char[] toReturn = _arrayToReturnToPool;
+ _chars = _arrayToReturnToPool = poolArray;
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ char[] toReturn = _arrayToReturnToPool;
+ this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Razor/packages.config b/Razor/packages.config
new file mode 100644
index 00000000..1d538525
--- /dev/null
+++ b/Razor/packages.config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file