Code:
using System;
using System.Runtime.InteropServices;
namespace EnvMgr
{
[Flags]
public enum AllocationProtectEnum : uint
{
PAGE_EXECUTE = 0x00000010,
PAGE_EXECUTE_READ = 0x00000020,
PAGE_EXECUTE_READWRITE = 0x00000040,
PAGE_EXECUTE_WRITECOPY = 0x00000080,
PAGE_NOACCESS = 0x00000001,
PAGE_READONLY = 0x00000002,
PAGE_READWRITE = 0x00000004,
PAGE_WRITECOPY = 0x00000008,
PAGE_GUARD = 0x00000100,
PAGE_NOCACHE = 0x00000200,
PAGE_WRITECOMBINE = 0x00000400
}
[Flags]
public enum StateEnum : uint
{
MEM_COMMIT = 0x1000,
MEM_RESERVE = 0x2000,
MEM_DECOMMIT = 0x4000,
MEM_RELEASE = 0x8000,
MEM_FREE = 0x10000,
MEM_RESET = 0x80000,
MEM_RESET_UNDO = 0x1000000,
}
[Flags]
public enum TypeEnum : uint
{
MEM_IMAGE = 0x1000000,
MEM_MAPPED = 0x40000,
MEM_PRIVATE = 0x20000
}
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION_64
{
public ulong BaseAddress;
public ulong AllocationBase;
public AllocationProtectEnum AllocationProtect;
public ulong RegionSize;
public StateEnum State;
public AllocationProtectEnum Protect;
public TypeEnum Type;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct StringW
{
public fixed char Buf[8];
public ulong Size;
public ulong ReservedSize;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Vector
{
public UIntPtr First;
public UIntPtr Last;
public UIntPtr End;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct Map
{
public UIntPtr Head;
public ulong Size;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct MapNode<TKey, TValue>
where TKey : struct
where TValue : struct
{
public UIntPtr Left; // 0x00
public UIntPtr Parent; // 0x08
public UIntPtr Right; // 0x10
public byte Color; // 0x18
public byte IsNil; // 0x19
public byte pad_1A;
public byte pad_1B;
private uint pad_1C;
public MapNodeData<TKey, TValue> Data;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct MapNodeData<TKey, TValue>
where TKey : struct
where TValue : struct
{
public TKey Key;
public TValue Value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct AlignedInt
{
public int Value; // 0x00
internal uint pad_04;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x40 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct StringWStringW
{
public StringW Category;
public StringW Name;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x48 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType1SettingsDefault
{
public StringW Category; // 0x00
public StringW Name; // 0x20
public float Value; // 0x40
private uint pad_44;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x50 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType2SettingsDefault
{
public StringW Category;
public StringW Name;
public float X;
public float Y;
public float Z;
private uint pad_4C;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x48 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType3SettingsDefault
{
public StringW Category;
public StringW Name;
public byte Value;
private byte _41;
private byte _42;
private byte _43;
private uint pad_44;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x60 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType4SettingsDefault
{
public StringW Category;
public StringW Name;
public StringW Value;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x08 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType1Settings
{
public float Value;
public byte Override;
private byte pad_05;
private byte pad_06;
private byte pad_07;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x18 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType2Settings
{
public float Value1;
public float Value2;
public float Value3;
public byte Override;
private byte pad_0D;
private byte pad_0E;
private byte pad_0F;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x2 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType3Settings
{
public byte Value;
public byte Override;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x50 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeType4Settings
{
public StringW Category;
public StringW Name;
public float _40;
public float _44;
public byte Override;
private byte _49;
private byte _4A;
private byte _4B;
private uint pad_4C;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x50 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeEnvSettingsEntryMap
{
public StringW Category;
public StringW Name;
public Map Value;
}
/// <summary>
/// 3.10.1.5
/// x64: 0x20 bytes
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct NativeEnvSettingsEntryUnknown
{
public ulong _00;
public ulong _08;
public ulong _10;
public byte _18;
private byte pad_19;
private byte pad_1A;
private byte pad_1B;
private uint pad_1C;
}
}
Code:
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace EnvMgr
{
internal static class Api
{
static ulong DefaultReadStringCharacters => 512;
public static Process Process { get; set; }
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern ulong VirtualQueryEx(SafeProcessHandle hProcess, UIntPtr lpAddress, out MEMORY_BASIC_INFORMATION_64 lpBuffer, ulong dwLength);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool ReadProcessMemory(SafeProcessHandle hProcess, UIntPtr lpBaseAddress, [Out] byte[] lpBuffer, ulong dwSize, out ulong lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool WriteProcessMemory(SafeProcessHandle hProcess, UIntPtr lpBaseAddress, byte[] lpBuffer, ulong nSize, out ulong lpNumberOfBytesWritten);
static Api()
{
RegisterTypeFormatter(typeof(StringW), Format_StringW);
RegisterTypeFormatter(typeof(UIntPtr), Format_Pointer);
}
static void Format_Pointer(object instance, FieldInfo p, StringBuilder sb)
{
var native = (UIntPtr)p.GetValue(instance);
sb.AppendFormat("{0}: 0x{1:X}", p.Name, native.ToUInt64());
}
static void Format_StringW(object instance, FieldInfo p, StringBuilder sb)
{
var native = (StringW)p.GetValue(instance);
sb.AppendFormat("{0}: {1}", p.Name, StdStringW(native));
}
internal static T[] ToStructArray<T>(byte[] bytes) where T : struct
{
var typeSize = SizeOf<T>();
if (bytes.Length % typeSize != 0)
throw new ArgumentException("Length of bytes is not a multiple of structure size");
var values = new T[bytes.Length / typeSize];
if (bytes.Length > 0)
{
Unsafe.CopyBlockUnaligned(ref Unsafe.As<T, byte>(ref values[0]), ref bytes[0], (uint)bytes.Length);
}
return values;
}
internal static byte[] FromStruct<T>(T value) where T : struct
{
var typeSize = SizeOf<T>();
var bytes = new byte[typeSize];
if (bytes.Length > 0)
{
Unsafe.CopyBlockUnaligned(ref bytes[0], ref Unsafe.As<T, byte>(ref value), (uint)bytes.Length);
}
return bytes;
}
internal static T Read<T>(UIntPtr address) where T : struct
{
return ReadArray<T>(address, 1)[0];
}
internal static void ReadBytes(ulong address, ulong count, byte[] buffer)
{
if (!ReadProcessMemory(Process.SafeHandle, (UIntPtr)address, buffer, (uint)count, out var readCount))
{
throw new Exception($"ReadProcessMemory failed with error {Marshal.GetLastWin32Error()}.");
}
if (readCount != count)
{
throw new Exception($"ReadProcessMemory read {readCount} / {count} bytes.");
}
}
internal static byte[] ReadBytes(UIntPtr address, ulong count)
{
return ReadBytes(address.ToUInt64(), count);
}
internal static byte[] ReadBytes(ulong address, ulong count)
{
var buffer = new byte[count];
ReadBytes(address, count, buffer);
return buffer;
}
internal static T[] ReadArray<T>(UIntPtr address, ulong count) where T : struct
{
var buffer = ReadBytes(address, count * SizeOf<T>());
return ToStructArray<T>(buffer);
}
internal static string ReadStringW(UIntPtr address)
{
return ReadStringW(address.ToUInt64(), DefaultReadStringCharacters);
}
internal static string ReadStringW(UIntPtr address, ulong maxCharacters)
{
return ReadStringW(address.ToUInt64(), maxCharacters);
}
internal static unsafe string StdStringW(StringW nativeContainer)
{
ulong size = nativeContainer.Size;
if (size == 0)
{
return string.Empty;
}
if (nativeContainer.ReservedSize >= 8)
{
var pp = *(UIntPtr*)nativeContainer.Buf;
return ReadStringW(pp, size + 1);
}
var buffer = new char[size];
for (ulong i = 0; i < size; i++)
{
buffer[i] = nativeContainer.Buf[i];
}
return new string(buffer);
}
private static readonly Dictionary<Type, uint> TypeCache = new Dictionary<Type, uint>();
private static uint GetSizeOf(Type type)
{
if (TypeCache.TryGetValue(type, out var size))
return size;
try
{
size = (uint)Marshal.SizeOf(type);
}
catch (ArgumentException)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var field in fields)
{
var attrs = field.GetCustomAttributes(typeof(FixedBufferAttribute), false);
if (attrs.Length > 0)
{
var attr = (FixedBufferAttribute)attrs[0];
size += GetSizeOf(attr.ElementType) * (uint)attr.Length;
continue;
}
size += GetSizeOf(field.FieldType);
}
}
TypeCache.Add(type, size);
return size;
}
internal static uint SizeOf<T>()
where T : struct
{
return GetSizeOf(typeof(T));
}
internal static T[] StdVector<T>(Vector nativeContainer)
where T : struct
{
var typeSize = SizeOf<T>();
var length = nativeContainer.Last.ToUInt64() - nativeContainer.First.ToUInt64();
if (length == 0)
{
return new T[0];
}
if (length % typeSize != 0)
{
throw new ArgumentException($"The buffer is not aligned for '{typeof(T).Name}'");
}
return ReadArray<T>(nativeContainer.First, length / typeSize);
}
internal static Dictionary<TKey, TValue> StdMap<TKey, TValue>(Map nativeContainer)
where TKey : struct
where TValue : struct
{
var collection = new Dictionary<TKey, TValue>();
if (nativeContainer.Size == 0)
{
return collection;
}
var children = new Stack<MapNode<TKey, TValue>>();
var head = Read<MapNode<TKey, TValue>>(nativeContainer.Head);
var parent = Read<MapNode<TKey, TValue>>(head.Parent);
children.Push(parent);
while (children.Count != 0)
{
var cur = children.Pop();
if (cur.IsNil == 0)
{
collection.Add(cur.Data.Key, cur.Data.Value);
}
var left = Read<MapNode<TKey, TValue>>(cur.Left);
if (left.IsNil == 0)
{
children.Push(left);
}
var right = Read<MapNode<TKey, TValue>>(cur.Right);
if (right.IsNil == 0)
{
children.Push(right);
}
}
return collection;
}
public static string ReadStringW(ulong address, ulong maxCharacters)
{
if (address == 0)
return string.Empty;
var buffer = new byte[maxCharacters * 2];
ReadPartialSafe(address, maxCharacters * 2, buffer, out var read);
for (ulong idy = 0; idy < read; idy += 2)
{
if (buffer[idy] == 0 && buffer[idy + 1] == 0)
{
return Encoding.Unicode.GetString(buffer, 0, (int)idy);
}
}
return null;
}
public static void ReadPartialSafe(ulong address, ulong count, byte[] buffer, out ulong read)
{
MEMORY_BASIC_INFORMATION_64 mbi;
var mbiSize = SizeOf<MEMORY_BASIC_INFORMATION_64>();
var bytesRead = VirtualQueryEx(Process.SafeHandle, (UIntPtr)address, out mbi, mbiSize);
if (bytesRead != mbiSize)
{
throw new Exception(
$"VirtualQueryEx failed with error {Marshal.GetLastWin32Error()} for address 0x{address:X}. {bytesRead} / {mbiSize} bytes read.");
}
// Make sure we don't read past the memory region size.
var maxCount = mbi.RegionSize - (address - mbi.BaseAddress);
if (count > maxCount)
{
count = maxCount;
}
// This should no longer fail since we're sure we're only reading inside a valid region.
if (!ReadProcessMemory(Process.SafeHandle, (UIntPtr)address, buffer, (uint)count, out var tmpRead))
{
throw new Exception(
$"ReadProcessMemory failed with error {Marshal.GetLastWin32Error()} for address 0x{address:X}.");
}
read = tmpRead;
}
internal static void CodeGen(UIntPtr environmentSettingsPtr)
{
var envSettings = Read<NativeEnvSettings>(environmentSettingsPtr);
long offset = 0;
string indent = string.Empty;
string[] settings1;
string[] settings2;
string[] settings3;
string[] settings4;
#region NativeEnvSettings
{
var sb1 = new StringBuilder();
sb1.AppendLine($"{indent}using System.Runtime.InteropServices;");
sb1.AppendLine();
sb1.AppendLine($"{indent}namespace EnvMgr");
sb1.AppendLine($"{indent}{{");
indent = "\t";
sb1.AppendLine($"{indent}[StructLayout(LayoutKind.Sequential, Pack = 1)]");
sb1.AppendLine($"{indent}internal struct NativeEnvSettings");
sb1.AppendLine($"{indent}{{");
indent = "\t\t";
sb1.AppendLine($"{indent}public Vector EnvData; // 0x00");
offset += SizeOf<Vector>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map SettingsMap0; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
settings1 = new string[envSettings.SettingsMap1.Size];
var settings1Map = StdMap<StringWStringW, AlignedInt>(envSettings.SettingsMap1);
foreach (var kvp in settings1Map)
{
var category = StdStringW(kvp.Key.Category);
var name = StdStringW(kvp.Key.Name);
var index = kvp.Value.Value;
settings1[index] = $"{category}_{name}";
}
for (var idx = 0; idx < settings1.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType1SettingsDefault {settings1[idx]}_default; // 0x{offset:X}");
offset += SizeOf<NativeType1SettingsDefault>();
}
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map SettingsMap1; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
settings2 = new string[envSettings.SettingsMap2.Size];
var settings2Map = StdMap<StringWStringW, AlignedInt>(envSettings.SettingsMap2);
foreach (var kvp in settings2Map)
{
var category = StdStringW(kvp.Key.Category);
var name = StdStringW(kvp.Key.Name);
var index = kvp.Value.Value;
settings2[index] = $"{category}_{name}";
}
for (var idx = 0; idx < settings2.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType2SettingsDefault {settings2[idx]}_default; // 0x{offset:X}");
offset += SizeOf<NativeType2SettingsDefault>();
}
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map SettingsMap2; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
// TODO: ggg pls.. we need to add 1 extra entry manually
settings3 = new string[envSettings.SettingsMap3.Size + 1];
var settings3Map = StdMap<StringWStringW, AlignedInt>(envSettings.SettingsMap3);
foreach (var kvp in settings3Map)
{
var category = StdStringW(kvp.Key.Category);
var name = StdStringW(kvp.Key.Name);
var index = kvp.Value.Value;
settings3[index] = $"{category}_{name}";
}
for (var idx = 0; idx < settings3.Length; ++idx)
{
if (settings3[idx] == null)
settings3[idx] = "unused";
sb1.AppendLine($"{indent}public NativeType3SettingsDefault {settings3[idx]}_default; // 0x{offset:X}");
offset += SizeOf<NativeType3SettingsDefault>();
}
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map SettingsMap3; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
settings4 = new string[envSettings.SettingsMap4.Size];
var settings4Map = StdMap<StringWStringW, AlignedInt>(envSettings.SettingsMap4);
foreach (var kvp in settings4Map)
{
var category = StdStringW(kvp.Key.Category);
var name = StdStringW(kvp.Key.Name);
var index = kvp.Value.Value;
settings4[index] = $"{category}_{name}";
}
for (var idx = 0; idx < settings4.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType4SettingsDefault {settings4[idx]}_default; // 0x{offset:X}");
offset += SizeOf<NativeType4SettingsDefault>();
}
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map SettingsMap4; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
for (var idx = 0; idx < 4; ++idx)
{
sb1.AppendLine($"{indent}public NativeEnvSettingsEntryMap Entry5_{idx}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryMap>();
}
sb1.AppendLine();
//-----
sb1.AppendLine($"{indent}public Vector _{offset:X}; // 0x{offset:X}");
offset += SizeOf<Vector>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public StringW _{offset:X}; // 0x{offset:X}");
offset += SizeOf<StringW>();
sb1.AppendLine($"{indent}public StringW _{offset:X}; // 0x{offset:X}");
offset += SizeOf<StringW>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public ulong _{offset:X}; // 0x{offset:X}");
offset += SizeOf<ulong>();
sb1.AppendLine($"{indent}public byte _{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private uint pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<uint>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public byte _{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}public float _{offset:X}; // 0x{offset:X}");
offset += SizeOf<float>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public Vector EnvEntries; // 0x{offset:X}, => NativeEnvEntryData");
offset += SizeOf<Vector>();
sb1.AppendLine($"{indent}public Vector _{offset:X}; // 0x{offset:X}");
offset += SizeOf<Vector>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public Map LoadedEnvs; // 0x{offset:X}");
offset += SizeOf<Map>();
sb1.AppendLine();
//-----
for (var idx = 0; idx < settings1.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType1Settings {settings1[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType1Settings>();
}
sb1.AppendLine();
//-----
for (var idx = 0; idx < settings2.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType2Settings {settings2[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType2Settings>();
}
sb1.AppendLine();
//-----
for (var idx = 0; idx < settings3.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType3Settings {settings3[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType3Settings>();
}
// Handle 8 byte alignment to ensure padding is correct
var totalBytes = settings3.Length * SizeOf<NativeType3Settings>();
var extraPad = (totalBytes) % 8;
if (extraPad != 0)
{
var extraCount = ((8 * (1 + (totalBytes / 8))) - totalBytes) / 2;
for (var idx = 0; idx < extraCount; ++idx)
{
sb1.AppendLine($"{indent}public NativeType3Settings pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeType3Settings>();
}
}
sb1.AppendLine();
//-----
for (var idx = 0; idx < settings4.Length; ++idx)
{
sb1.AppendLine($"{indent}public NativeType4Settings {settings4[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType4Settings>();
}
sb1.AppendLine();
//-----
sb1.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb1.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb1.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb1.AppendLine();
sb1.AppendLine($"{indent}public byte _{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private byte pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<byte>();
sb1.AppendLine($"{indent}private uint pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<uint>();
sb1.AppendLine();
// TODO: Verify offset against the known value to detect bugs
// with wrong sized types from the API side. Since this code is a
// minimized version of pre-generated code, not going to add that in
// here myself.
sb1.AppendLine($"{indent}// 0x{offset:X}");
indent = "\t";
sb1.AppendLine($"{indent}}}");
indent = string.Empty;
sb1.AppendLine($"{indent}}}");
File.WriteAllText("NativeEnvSettings.cs", sb1.ToString());
}
#endregion
#region NativeEnvEntryData
{
var sb2 = new StringBuilder();
indent = "";
offset = 0;
sb2.AppendLine($"{indent}using System.Runtime.InteropServices;");
sb2.AppendLine();
sb2.AppendLine($"{indent}namespace EnvMgr");
sb2.AppendLine($"{indent}{{");
indent = "\t";
sb2.AppendLine($"{indent}[StructLayout(LayoutKind.Sequential, Pack = 1)]");
sb2.AppendLine($"{indent}internal struct NativeEnvEntryData");
sb2.AppendLine($"{indent}{{");
indent = "\t\t";
sb2.AppendLine($"{indent}public StringW Name; // 0x{offset:X}");
offset += SizeOf<StringW>();
sb2.AppendLine();
for (var idx = 0; idx < settings1.Length; ++idx)
{
sb2.AppendLine($"{indent}public NativeType1Settings {settings1[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType1Settings>();
}
sb2.AppendLine();
for (var idx = 0; idx < settings2.Length; ++idx)
{
sb2.AppendLine($"{indent}public NativeType2Settings {settings2[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType2Settings>();
}
sb2.AppendLine();
for (var idx = 0; idx < settings3.Length; ++idx)
{
sb2.AppendLine($"{indent}public NativeType3Settings {settings3[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType3Settings>();
}
// Handle 8 byte alignment to ensure padding is correct
var totalBytes = settings3.Length * SizeOf<NativeType3Settings>();
var extraPad = (totalBytes) % 8;
if (extraPad != 0)
{
var extraCount = ((8 * (1 + (totalBytes / 8))) - totalBytes) / 2;
for (var idx = 0; idx < extraCount; ++idx)
{
sb2.AppendLine($"{indent}public NativeType3Settings pad_{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeType3Settings>();
}
}
sb2.AppendLine();
for (var idx = 0; idx < settings4.Length; ++idx)
{
sb2.AppendLine($"{indent}public NativeType4Settings {settings4[idx]}; // 0x{offset:X}");
offset += SizeOf<NativeType4Settings>();
}
sb2.AppendLine();
//-----
sb2.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb2.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb2.AppendLine($"{indent}public NativeEnvSettingsEntryUnknown _{offset:X}; // 0x{offset:X}");
offset += SizeOf<NativeEnvSettingsEntryUnknown>();
sb2.AppendLine();
//-----
// TODO: Verify offset against the known value to detect bugs
// with wrong sized types from the API side. Since this code is a
// minimized version of pre-generated code, not going to add that in
// here myself.
sb2.AppendLine($"{indent}// 0x{offset:X}");
indent = "\t";
sb2.AppendLine($"{indent}}}");
indent = string.Empty;
sb2.AppendLine($"{indent}}}");
File.WriteAllText("NativeEnvEntryData.cs", sb2.ToString());
}
#endregion
}
internal static void IterateEnvironmentSettings(UIntPtr environmentSettingsPtr, Action<int, ulong, NativeEnvEntryData> action)
{
// Read the NativeEnvSettings struct from the pointer found in LocalData
var nativeEnv = Read<NativeEnvSettings>(environmentSettingsPtr);
// Use the client's memory manager to catch layout changes
var expectedSize = Read<uint>(environmentSettingsPtr - 0x8);
if (expectedSize != SizeOf<NativeEnvSettings>())
{
throw new Exception($"The memory layout for NativeEnvSettings has changed!");
}
// Read the vector of NativeentryData, which is the current set of env data loaded for the area
// If the memory layout of NativeEnvEntryData breaks, this should throw due to the buffer not being
// aligned anymore.
var envEntries = StdVector<NativeEnvEntryData>(nativeEnv.EnvEntries);
// Since vectors are contiguous, do a little pointer math to know where each entry starts.
var vecAddr = nativeEnv.EnvEntries.First.ToUInt64();
var vecSize = SizeOf<NativeEnvEntryData>();
// Now process everything
for (var idx = 0; idx < envEntries.Length; ++idx)
{
action(idx, vecAddr, envEntries[idx]);
vecAddr += vecSize;
}
}
static Dictionary<Type, Action<object, FieldInfo, StringBuilder>> typeFormatters =
new Dictionary<Type, Action<object, FieldInfo, StringBuilder>>();
internal static bool RegisterTypeFormatter(Type type, Action<object, FieldInfo, StringBuilder> func)
{
if (typeFormatters.ContainsKey(type))
return false;
typeFormatters.Add(type, func);
return true;
}
internal static unsafe void DumpObject(Type type, object instance, StringBuilder sb, string prefix)
{
// TODO: Clean this logic up a bit, but for now it works so whatever
foreach (var p in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
var recurse = false;
sb.Append(prefix);
if (p.FieldType == typeof(IntPtr))
{
sb.AppendFormat("{0}: 0x{1:X}", p.Name, ((IntPtr)p.GetValue(instance)).ToInt64());
}
else if (p.FieldType == typeof(UIntPtr))
{
sb.AppendFormat("{0}: 0x{1:X}", p.Name, ((UIntPtr)p.GetValue(instance)).ToUInt64());
}
else if (p.FieldType == typeof(byte))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((byte)p.GetValue(instance)));
}
else if (p.FieldType == typeof(sbyte))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((sbyte)p.GetValue(instance)));
}
else if (p.FieldType == typeof(ushort))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((ushort)p.GetValue(instance)));
}
else if (p.FieldType == typeof(short))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((short)p.GetValue(instance)));
}
else if (p.FieldType == typeof(int))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((int)p.GetValue(instance)));
}
else if (p.FieldType == typeof(uint))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((uint)p.GetValue(instance)));
}
else if (p.FieldType == typeof(long))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((long)p.GetValue(instance)));
}
else if (p.FieldType == typeof(ulong))
{
sb.AppendFormat("{0}: 0x{1:X} [{1}]", p.Name, ((ulong)p.GetValue(instance)));
}
else if (p.FieldType == typeof(float))
{
sb.AppendFormat("{0}: {1}", p.Name, ((float)p.GetValue(instance)));
}
else if (p.FieldType == typeof(double))
{
sb.AppendFormat("{0}: {1}", p.Name, ((double)p.GetValue(instance)));
}
else if (p.FieldType == typeof(Enum))
{
sb.AppendFormat("{0}: {1}", p.Name, p.GetValue(instance));
}
else if (typeFormatters.TryGetValue(p.FieldType, out var func))
{
func(instance, p, sb);
}
else
{
recurse = true;
}
if (recurse)
{
sb.AppendFormat("{0}:", p.Name);
sb.AppendLine();
if (p.CustomAttributes.Any(ca => ca.AttributeType == typeof(FixedBufferAttribute)))
{
var fixedBufferDescription = p.CustomAttributes
.Where(x => x.AttributeType == typeof(FixedBufferAttribute))
.Select(x => x.ConstructorArguments)
.Select(x => new KeyValuePair<Type, int>((Type)x[0].Value, (int)x[1].Value))
.First();
if (fixedBufferDescription.Key == typeof(byte))
{
var data = new List<byte>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
byte* raw = (byte*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X2} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(ushort))
{
var data = new List<ushort>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
ushort* raw = (ushort*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X4} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(short))
{
var data = new List<short>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
short* raw = (short*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X4} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(uint))
{
var data = new List<uint>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
uint* raw = (uint*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X8} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(int))
{
var data = new List<int>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
int* raw = (int*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X8} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(ulong))
{
var data = new List<ulong>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
ulong* raw = (ulong*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0:X16} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(float))
{
var data = new List<float>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
float* raw = (float*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else if (fixedBufferDescription.Key == typeof(double))
{
var data = new List<double>();
var value = p.GetValue(instance);
GCHandle hdl = GCHandle.Alloc(value, GCHandleType.Pinned);
double* raw = (double*)hdl.AddrOfPinnedObject();
for (var ix = 0; ix < fixedBufferDescription.Value; ++ix)
{
data.Add(*(raw + ix));
}
hdl.Free();
sb.Append(prefix);
for (var i = 0; i < data.Count; i++)
{
sb.AppendFormat("{0} ", data[i]);
if ((i + 1) % 16 == 0)
{
sb.AppendLine();
sb.Append(prefix);
}
}
sb.AppendLine();
}
else
{
sb.AppendLine($"<TODO | {fixedBufferDescription.Key.Name}>");
}
}
else
{
DumpObject(p.FieldType, p.GetValue(instance), sb, prefix + "\t");
}
}
else
{
sb.AppendLine();
}
}
}
public static string ToString(string header, Type type, object instance, string prefix = "\t")
{
var sb = new StringBuilder();
sb.AppendLine(header);
DumpObject(type, instance, sb, prefix);
return sb.ToString();
}
}
}
Check out the