Maple.StringPool 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Maple.StringPool --version 0.1.0
                    
NuGet\Install-Package Maple.StringPool -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Maple.StringPool" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Maple.StringPool" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Maple.StringPool" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Maple.StringPool --version 0.1.0
                    
#r "nuget: Maple.StringPool, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Maple.StringPool@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Maple.StringPool&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Maple.StringPool&version=0.1.0
                    
Install as a Cake Tool

Maple.StringPool

.NET C# Build Status codecov Nuget License

Zero-allocation decoder for the MapleStory GMS v95 StringPool singleton. Cross-platform, trimmable and AOT/NativeAOT compatible.

⭐ Please star this project if you like it. ⭐

Example | Example Catalogue | CLI Tool (sp) | Public API Reference

Example

// Demonstration — requires a real MapleStory.exe; skip if not present.
const string exePath = "MapleStory.exe";
if (!File.Exists(exePath))
    return;

using var pool = StringPoolDecoder.Open(exePath);

// Single slot by index (decimal or hex)
string hero = pool.GetString(25); // "Hero"
Console.WriteLine(hero);

// Enumerate all entries
foreach (StringPoolEntry e in pool.GetAll())
    Console.WriteLine(e); // SP[0x19] (25): Hero

// Case-insensitive substring search
foreach (StringPoolEntry e in pool.Find("warrior"))
    Console.WriteLine(e);

// Slice [start, end)
foreach (StringPoolEntry e in pool.GetRange(0, 100))
    Console.WriteLine(e);

For more examples see Example Catalogue.

Benchmarks

Benchmarks.

Detailed Benchmarks

Comparison Benchmarks
TestBench Benchmark Results

Example Catalogue

The following examples are available in ReadMeTest.cs.

Example - Empty

// Demonstration — requires a real MapleStory.exe; skip if not present.
const string exePath = "MapleStory.exe";
if (!File.Exists(exePath))
    return;

using var pool = StringPoolDecoder.Open(exePath);

// Single slot by index (decimal or hex)
string hero = pool.GetString(25); // "Hero"
Console.WriteLine(hero);

// Enumerate all entries
foreach (StringPoolEntry e in pool.GetAll())
    Console.WriteLine(e); // SP[0x19] (25): Hero

// Case-insensitive substring search
foreach (StringPoolEntry e in pool.Find("warrior"))
    Console.WriteLine(e);

// Slice [start, end)
foreach (StringPoolEntry e in pool.GetRange(0, 100))
    Console.WriteLine(e);

CLI Tool (sp)

The sp command-line tool wraps StringPoolDecoder for interactive use by humans and agents.

Installation

Download the latest sp.exe from GitHub Releases or build from source:

dotnet publish src/Maple.StringPool.Cli/Maple.StringPool.Cli.csproj -c Release -r win-x64

Usage

sp <MapleStory.exe> <command> [options]

Commands:
  get   <index>              Decode SP[index] — accepts decimal or 0x hex
  range <start> <end>        Decode slots [start, end)
  find  <term>               Search all slots for <term> (case-insensitive)
  dump                       Dump every slot
  info                       Print metadata: count, key-size, master-key hex

Options (range, find, dump):
  --format text|json|csv     Output format (default: text)
  --filter <term>            Only emit entries containing <term> (range, dump)
  --out <file>               Write output to <file> instead of stdout

Examples

sp MapleStory.exe get 8
sp MapleStory.exe get 0x19
sp MapleStory.exe range 0 100
sp MapleStory.exe range 0 100 --format json --out slice.json
sp MapleStory.exe find Warrior
sp MapleStory.exe find Warrior --format csv
sp MapleStory.exe dump --format csv --out strings.csv
sp MapleStory.exe dump --filter UI/Login
sp MapleStory.exe info

Public API Reference

[assembly: System.Reflection.AssemblyMetadata("IsAotCompatible", "True")]
[assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")]
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Bia10/Maple.StringPool/")]
[assembly: System.Resources.NeutralResourcesLanguage("en")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Maple.StringPool.Benchmarks")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Maple.StringPool.ComparisonBenchmarks")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Maple.StringPool.Test")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Maple.StringPool.XyzTest")]
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")]
namespace Maple.StringPool
{
    public static class KnownLayouts
    {
        public static readonly Maple.StringPool.StringPoolAddresses GmsV95;
    }
    public readonly struct StringPoolAddresses : System.IEquatable<Maple.StringPool.StringPoolAddresses>
    {
        public required uint ImageBase { get; init; }
        public required uint MsAKey { get; init; }
        public required uint MsAString { get; init; }
        public required uint MsNKeySize { get; init; }
        public required uint MsNSize { get; init; }
    }
    public sealed class StringPoolDecoder : System.IDisposable
    {
        public StringPoolDecoder(Maple.StringPool.Source.IPeImageReader reader, Maple.StringPool.StringPoolAddresses? addresses = default) { }
        public int Count { get; }
        public int KeySize { get; }
        public System.ReadOnlySpan<byte> MasterKey { get; }
        public void Dispose() { }
        public System.Collections.Generic.IEnumerable<Maple.StringPool.StringPoolEntry> Find(string term, System.StringComparison comparison = 5) { }
        public System.Collections.Generic.IEnumerable<Maple.StringPool.StringPoolEntry> GetAll() { }
        public string GetBSTR(uint index) { }
        public System.Collections.Generic.IEnumerable<Maple.StringPool.StringPoolEntry> GetRange(uint start, uint end) { }
        public string GetString(uint index) { }
        public string GetStringW(uint index) { }
        public static Maple.StringPool.StringPoolDecoder FromBytes(byte[] peImage, Maple.StringPool.StringPoolAddresses? addresses = default) { }
        public static Maple.StringPool.StringPoolDecoder Open(string exePath, Maple.StringPool.StringPoolAddresses? addresses = default) { }
    }
    public readonly struct StringPoolEntry : System.IEquatable<Maple.StringPool.StringPoolEntry>
    {
        public StringPoolEntry(uint Index, string Value) { }
        public uint Index { get; init; }
        public string Value { get; init; }
        public override string ToString() { }
    }
}
namespace Maple.StringPool.NativeTypes
{
    public readonly ref struct EncodedEntryLayout
    {
        public const int BodyOffset = 1;
        public const int SeedBytes = 1;
        public const int SeedOffset = 0;
    }
    public readonly ref struct StringPoolKeyLayout
    {
        public const int KeyArrayOffset = 0;
        public const int TotalBytes = 4;
    }
    public readonly ref struct StringPoolLayout
    {
        public const int LockOffset = 8;
        public const int NarrowCacheOffset = 0;
        public const int TotalBytes = 16;
        public const int WideCacheOffset = 4;
    }
    public static class TypeSizes
    {
        public const int Int32 = 4;
        public const int Pointer = 4;
    }
    public static class ZArray
    {
        public static byte[] ReadByteElements(System.ReadOnlySpan<byte> image, int payloadFileOffset, int count) { }
        public static int ReadCount(System.ReadOnlySpan<byte> image, int payloadFileOffset) { }
        public static uint[] ReadPointerElements(System.ReadOnlySpan<byte> image, int payloadFileOffset, int count) { }
    }
    public readonly ref struct ZArrayLayout
    {
        public const int CountOffset = 0;
        public const int HeaderBytes = 4;
        public const int PayloadOffset = 4;
        public ZArrayLayout(int elementCount) { }
        public int ElementCount { get; }
        public int TotalBytes(int elementSize) { }
    }
    public readonly struct ZFatalSection
    {
        public ZFatalSection(uint tibPointer, int refCount) { }
        public int RefCount { get; }
        public uint TibPointer { get; }
        public static Maple.StringPool.NativeTypes.ZFatalSection Unlocked { get; }
    }
    public readonly ref struct ZFatalSectionLayout
    {
        public const int RefCountOffset = 4;
        public const int TibPointerOffset = 0;
        public const int TotalBytes = 8;
    }
    public readonly struct ZXString
    {
        public ZXString(string value, int refCount = 1, int capacity = 0, int byteLength = 0) { }
        public int ByteLength { get; }
        public int Capacity { get; }
        public int RefCount { get; }
        public string Value { get; }
        public override string ToString() { }
        public static Maple.StringPool.NativeTypes.ZXString ReadFrom(System.ReadOnlySpan<byte> image, int payloadFileOffset) { }
        public static string op_Implicit(Maple.StringPool.NativeTypes.ZXString s) { }
    }
    public readonly ref struct ZXStringDataLayout
    {
        public const int ByteLengthOffset = 8;
        public const int CapacityOffset = 4;
        public const int HeaderBytes = 12;
        public const int NullTerminatorBytes = 1;
        public const int PayloadOffset = 12;
        public const int RefCountOffset = 0;
        public ZXStringDataLayout(int payloadBytes) { }
        public int TotalBytes { get; }
    }
}
namespace Maple.StringPool.Source
{
    public interface IPeImageReader : System.IDisposable
    {
        System.ReadOnlyMemory<byte> Image { get; }
    }
    public sealed class MemoryPeImageReader : Maple.StringPool.Source.IPeImageReader, System.IDisposable
    {
        public System.ReadOnlyMemory<byte> Image { get; }
        public void Dispose() { }
        public static Maple.StringPool.Source.MemoryPeImageReader FromBytes(byte[] image) { }
        public static Maple.StringPool.Source.MemoryPeImageReader FromFile(string path) { }
    }
}

Design Notes

  • Zero heap allocations per decodeRotatedKey lives on the stack via [InlineArray(256)]; the XOR body is decrypted into a stackalloc buffer. The only allocation is the final string.
  • Thread safetyGetString uses Interlocked.CompareExchange to publish decoded strings; concurrent callers may decode redundantly but only one result is stored.
  • Cipher — circular left-rotation of the 16-byte master key by the per-entry seed byte, then XOR with zero-collision rule.

Verified Addresses (GMS v95 PDB)

Field Address Value
ms_aString 0xC5A878 const char*[6883] pointer table
ms_aKey 0xB98830 16-byte master XOR key
ms_nKeySize 0xB98840 16
ms_nSize 0xB98844 6883 (0x1AE3)
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 90 4/2/2026
0.1.0 90 3/23/2026