[HELP needed] Any possibility to adjust the brightness of game? menu

User Tag List

Results 1 to 11 of 11
  1. #1
    serlev's Avatar Member
    Reputation
    3
    Join Date
    Apr 2012
    Posts
    97
    Thanks G/R
    10/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    [HELP needed] Any possibility to adjust the brightness of game?

    I am one of the oldest player in game and I hardly see around with existing dark pool of game.
    ExMap tool has a choice to make the game fully bright (on or off). I use it atm since I couldnt find any other tool to adjust the brightness.
    In old days we used to have CheatEngine scripts released by the people who knows how to search for new offsets after each patch.

    If anyone knows how to adjust the brightness of game please kindly inform here.

    [HELP needed] Any possibility to adjust the brightness of game?
  2. #2
    Sychotix's Avatar Moderator Authenticator enabled
    Reputation
    1421
    Join Date
    Apr 2006
    Posts
    3,942
    Thanks G/R
    285/572
    Trade Feedback
    1 (100%)
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    You're probably best off just using exmap as that has some anti-detection functionality in it.

  3. #3
    serlev's Avatar Member
    Reputation
    3
    Join Date
    Apr 2012
    Posts
    97
    Thanks G/R
    10/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Although I gave break time to time I like the game and play for many years. I just hate the darkness of game. It really hurts my old eyes.
    Fortunately there was always a tool for brightness. This time we have ExMap; if I couldnt find it I wouldnt play PoE. The problem with ExMap is the brightness is "off" or "on".
    I am thankfull to its author, at least I can play the game but I still look for a proper tool to adjust brightness between on and off.

  4. #4
    pushedx's Avatar Contributor
    Reputation
    257
    Join Date
    Nov 2009
    Posts
    137
    Thanks G/R
    8/135
    Trade Feedback
    0 (0%)
    Mentioned
    12 Post(s)
    Tagged
    0 Thread(s)
    I recently reversed a large chunk of the client's environment system. I had been playing with changing the environment at runtime for fun: Screen capture - 4dba893a4afbff00a85ce1249779cedf - Gyazo

    That got me thinking about how it should be possible to instead just play with each individual setting.

    I wasn't originally intending to post this, simply because you can argue QoL all you want, but it's against the game's ToS, so is it worth risking getting banned for changing client settings? Probably not for most, but if anyone feels like it's worth it, and they're willing to do some work, then I don't mind sharing this code.

    Here's the complete, minimized code required to accomplish what you're asking about. It was quite a bit of work, but if you're looking for a complete solution, this is what's required.

    Make a new C# .Net Framework 4.8 console project. Make sure to change the project settings to support Unsafe code. You'll need to use nuget to add the "System.Runtime.CompilerServices.Unsafe" package to the project. That library helps solve some issues with unmanaged memory operations we used to have to use .net/cli for instead. I should also just mention, this code isn't fully updated to new .net stuff yet, but it works, as I've copied parts of it from another work in progress project of mine.

    Program.cs - This is the user facing logic, where you'd update offsets and implement your settings changes. The normal client/steam is supported, but x64 only.
    Code:
    using EnvMgr;
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;
    
    class Program
    {
    	static void Main(string[] args)
    	{
    		// TODO: Update offsets as they change
    
    		bool useSteam = false;
    
    		bool doCodeGen = false; // When you want to regenerate the two main structs from memory after layout changes
    		bool doDumpEnv = true; // When you want to dump the current area's settings
    		bool doChangeEnv = false; // When you want to change the current area's settings
    
    		Process[] procs;
    		if (useSteam)
    		{
    			procs = Process.GetProcessesByName("PathOfExile_x64Steam");
    		}
    		else
    		{
    			procs = Process.GetProcessesByName("PathOfExile_x64");
    		}
    
    		if (procs.Length != 1)
    		{
    			Console.WriteLine($"Error: {procs.Length} processes were found!");
    			return;
    		}
    
    		using (var process = procs[0])
    		{
    			Api.Process = process;
    
    			ulong GlobalStatesPtrOffset;
    			if (useSteam)
    			{
    				// 3.10.1.5-Steam
    				// 4C 89 30 4C 89 70 08 4C 89 70 10 4C 89 70 18 4C 89 70 20 4C 89 70 28 4C 89 70 30 4C 89 70 38 4C 89 70 40 4C 89 70 48 4C 89 70 50 4C 89 70 58 4C 89 70 60 4C 89 70 68 4C 89 70 70 4C 89 70 78 4C 89 B0 80 00 00 00 4C 89 B0 88 00 00 00 4C 89 B0 90 00 00 00 4C 89 B0 98 00 00 00 4C 89 B0 A0 00 00 00 4C 89 B0 A8 00 00 00 4C 89 B0 B0 00 00 00 4C 89 B0 B8 00 00 00 EB 03
    				GlobalStatesPtrOffset = (0x7FF68EF8D718 - 0x7FF68CF40000);  // 3.10.1.5-Steam: GlobalStatesPtr - MainModule.BaseAddress
    			}
    			else
    			{
    				// 3.10.1.5 global states sig
    				// 48 89 38 48 89 78 08 48 89 78 10 48 89 78 18 48 89 78 20 48 89 78 28 48 89 78 30 48 89 78 38 48 89 78 40 48 89 78 48 48 89 78 50 48 89 78 58 48 89 78 60 48 89 78 68 48 89 78 70 48 89 78 78 48 89 B8 80 00 00 00 48 89 B8 88 00 00 00 48 89 B8 90 00 00 00 48 89 B8 98 00 00 00 48 89 B8 A0 00 00 00 48 89 B8 A8 00 00 00 48 89 B8 B0 00 00 00 48 89 B8 B8 00 00 00 48 89 B8 C0 00 00 00 48 89 B8 C8 00 00 00 EB
    				GlobalStatesPtrOffset = (0x7FF7391AD808 - 0x7FF737160000);  // 3.10.1.5: GlobalStatesPtr - MainModule.BaseAddress
    			}
    
    			// Look down a little...
    			//		00007FF7377BE825 | EB 03 | JMP 0x7FF7377BE82A |
    			//		00007FF7377BE827 | 48:8BC7 | MOV RAX, RDI |
    			//		00007FF7377BE82A | 48:8B1D D7EF9E01             | MOV RBX, QWORD PTR DS:[0x7FF7391AD808] |
    			//		00007FF7377BE831 | 48:8905 D0EF9E01 | MOV QWORD PTR DS:[0x7FF7391AD808], RAX | GlobalStatesPtr
    			//		00007FF7377BE838 | 48:85DB | TEST RBX, RBX |
    			//		00007FF7377BE83B | 74 21 | JE 0x7FF7377BE85E |
    
    			UIntPtr GlobalStatesPtr = Api.Read<UIntPtr>(new UIntPtr((ulong)process.MainModule.BaseAddress.ToInt64() + GlobalStatesPtrOffset));
    
    			const int InGameStatePtrOffset = 0x10; // 3.10.1.5 (this will be the same for both versions)
    			UIntPtr InGameStatePtr = Api.Read<UIntPtr>(GlobalStatesPtr + InGameStatePtrOffset);
    
    			int LocalDataOffset;
    			if (useSteam)
    			{
    				LocalDataOffset = 0x3A0; // 3.10.1.5-Steam
    			}
    			else
    			{
    				LocalDataOffset = 0x378; // 3.10.1.5
    			}
    			UIntPtr LocalDataPtr = Api.Read<UIntPtr>(InGameStatePtr + LocalDataOffset);
    
    			// LocalData is only set when actually in-game
    			if (LocalDataPtr == UIntPtr.Zero)
    			{
    				Console.WriteLine("LocalData is not valid yet!");
    				return;
    			}
    
    			const int WorldAreaOffset = 0x60; // 3.10.1.5 (this will be the same for both versions)
    			UIntPtr WorldAreaPtr = Api.Read<UIntPtr>(LocalDataPtr + WorldAreaOffset);
    			var areaId = Api.ReadStringW(Api.Read<UIntPtr>(WorldAreaPtr));
    			var areaName = Api.ReadStringW(Api.Read<UIntPtr>(WorldAreaPtr + 8));
    			Console.WriteLine($"in [{areaId}] {areaName}");
    
    			const int EnvironmentSettingsOffset = 0x8C0; // 3.10.1.5 (this will be the same for both versions)
    			UIntPtr EnvironmentSettingsPtr = Api.Read<UIntPtr>(LocalDataPtr + EnvironmentSettingsOffset);
    
    			if (doCodeGen)
    			{
    				Api.CodeGen(EnvironmentSettingsPtr);
    
    				Console.WriteLine("CodeGen completed!");
    			}
    
    			if (doDumpEnv)
    			{
    				var sb1 = new StringBuilder();
    				sb1.AppendLine($"[{areaId}] {areaName}");
    				sb1.AppendLine();
    				Api.IterateEnvironmentSettings(EnvironmentSettingsPtr, (idx, addr, entry) =>
    				{
    					// Dump the env data to file to make it easier to read the base data
    					sb1.AppendLine(Api.ToString($"[EnvEntries[{idx}] - 0x{addr:X}] 0x{Api.SizeOf<NativeEnvEntryData>():X} bytes", entry.GetType(), entry, "\t"));
    					sb1.AppendLine();
    				});
    				File.WriteAllText("EnvironmentSettings.txt", sb1.ToString());
    
    				Console.WriteLine("The EnvironmentSettings have been saved to disk!");
    			}
    
    			if (doChangeEnv)
    			{
    				Api.IterateEnvironmentSettings(EnvironmentSettingsPtr, (idx, addr, entry) =>
    				{
    					var env = Api.StdStringW(entry.Name);
    					Console.WriteLine($"Now changing settings for {env}");
    
    					// TODO: Implement your logic to modify the env entries as you need.
    					// Lots of ways to go about handling this, but here's a hardcoded
    					// example that tries to set full brightness always (certain areas will
    					// look TOO bright as a result).
    
    					// NOTE: You'll have to trial and error with settings to figure out
    					// what works best for you. Each area reacts differently to settings
    					// changes, so there's no one size fits all set of values to have
    					// everything perfect.
    					// For example, The Ship Graveyard is too bright using settings that make 
    					// most other areas look great. This means per-area settings are ideal, 
    					// so a system where some base set of values is used, and then specific 
    					// area overrides are coded to make those areas more bearable would work 
    					// best.
    					// Make sure to dump the original area data first to tweak it, simply 
    					// leave and return to the area to reset the data, since these changes 
    					// are lost on area change.
    
    					// This clears up some stuff floating around in the air in certain areas
    					entry.area_global_particle_effect._40 = 0.0f;
    					entry.area_player_environment_ao._40 = 0.0f;
    					entry.post_transform_texture._40 = 0.0f;
    
    					// Make sure the area's light is white
    					entry.directional_light_colour.Value1 = 1.0f;
    					entry.directional_light_colour.Value2 = 1.0f;
    					entry.directional_light_colour.Value3 = 1.0f;
    					entry.directional_light_colour.Override = 1;
    
    					// The area's light shouldn't cast shadows
    					entry.directional_light_shadows_enabled.Override = 1;
    					entry.directional_light_shadows_enabled.Value = 0;
    
    					// We can strengthen or weaken how intense the area's light is
    					entry.directional_light_multiplier.Override = 1;
    					entry.directional_light_multiplier.Value = 0.75f;
    
    					// Make sure the player's light is white
    					entry.player_light_colour.Value1 = 1.0f;
    					entry.player_light_colour.Value2 = 1.0f;
    					entry.player_light_colour.Value3 = 1.0f;
    					entry.player_light_colour.Override = 1;
    
    					// We can strengthen or weaken how intense the player's light is
    					entry.player_light_intensity.Override = 1;
    					entry.player_light_intensity.Value = 0.75f;
    
    					// The player light shouldn't cast shadows
    					entry.player_light_shadows_enabled.Override = 1;
    					entry.player_light_shadows_enabled.Value = 0;
    
    					// Have to play with changing this brightness as well
    					entry.environment_mapping_env_brightness.Override = 1;
    					entry.environment_mapping_env_brightness.Value = 1.0f;
    
    					// Disable linear fog (not related to delirium fog)
    					entry.fog_linear_fog_is_enabled.Override = 1;
    					entry.fog_linear_fog_is_enabled.Value = 0;
    
    					// Disable exponential fog (not related to delirium fog)
    					entry.fog_exp_fog_is_enabled.Override = 1;
    					entry.fog_exp_fog_is_enabled.Value = 0;
    
    					entry.area_lightning.Override = 1;
    					entry.area_lightning.Value = 0;
    
    					entry.global_illumination_use_forced_screenspace_shadows.Override = 1;
    					entry.global_illumination_use_forced_screenspace_shadows.Value = 0;
    
    					// Read this and then come up with better values as this setting will cause some 
    					// artifacts and isn't perfect, but at least shows the effects from the changes.
    					// https://docs.microsoft.com/en-us/windows/win32/direct3d9/light-types#spotlight
    					entry.directional_light_phi.Override = 1;
    					entry.directional_light_phi.Value = 3.00f;
    
    					entry.directional_light_theta.Override = 1;
    					entry.directional_light_theta.Value = 0.1f;
    
    					var newBytes = Api.FromStruct(entry).ToArray();
    
    					if (!Api.WriteProcessMemory(Api.Process.SafeHandle, (UIntPtr)addr, newBytes, (ulong)newBytes.Length, out var wrote))
    					{
    						Console.WriteLine("\tWriteProcessMemory failed!");
    					}
    				});
    
    				Console.WriteLine("The EnvironmentSettings have been modified!");
    			}
    		}
    	}
    }
    Native.cs - Native struct layouts required
    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;
    	}
    }
    NativeEnvSettings.cs - This is code generated, but requires some manual reversing to fill in some parts that can't automatically be detected.
    Code:
    using System.Runtime.InteropServices;
    
    namespace EnvMgr
    {
    	[StructLayout(LayoutKind.Sequential, Pack = 1)]
    	internal struct NativeEnvSettings
    	{
    		public Vector EnvData; // 0x00
    
    		public Map SettingsMap0; // 0x18
    
    		public NativeType1SettingsDefault directional_light_phi_default; // 0x28
    		public NativeType1SettingsDefault directional_light_theta_default; // 0x70
    		public NativeType1SettingsDefault directional_light_multiplier_default; // 0xB8
    		public NativeType1SettingsDefault directional_light_desaturation_default; // 0x100
    		public NativeType1SettingsDefault directional_light_cam_far_percent_default; // 0x148
    		public NativeType1SettingsDefault directional_light_cam_near_percent_default; // 0x190
    		public NativeType1SettingsDefault directional_light_cam_extra_z_default; // 0x1D8
    		public NativeType1SettingsDefault directional_light_cam_min_offset_default; // 0x220
    		public NativeType1SettingsDefault directional_light_cam_max_offset_default; // 0x268
    		public NativeType1SettingsDefault player_light_intensity_default; // 0x2B0
    		public NativeType1SettingsDefault player_light_penumbra_default; // 0x2F8
    		public NativeType1SettingsDefault player_light_bias_default; // 0x340
    		public NativeType1SettingsDefault post_processing_bloom_cutoff_default; // 0x388
    		public NativeType1SettingsDefault post_processing_bloom_intensity_default; // 0x3D0
    		public NativeType1SettingsDefault post_processing_original_intensity_default; // 0x418
    		public NativeType1SettingsDefault environment_mapping_direct_light_env_ratio_default; // 0x460
    		public NativeType1SettingsDefault environment_mapping_env_brightness_default; // 0x4A8
    		public NativeType1SettingsDefault environment_mapping_gi_additional_env_light_default; // 0x4F0
    		public NativeType1SettingsDefault environment_mapping_hor_angle_default; // 0x538
    		public NativeType1SettingsDefault environment_mapping_vert_angle_default; // 0x580
    		public NativeType1SettingsDefault environment_mapping_solid_specular_attenuation_default; // 0x5C8
    		public NativeType1SettingsDefault environment_mapping_water_specular_attenuation_default; // 0x610
    		public NativeType1SettingsDefault fog_linear_fog_height_default; // 0x658
    		public NativeType1SettingsDefault fog_linear_fog_length_default; // 0x6A0
    		public NativeType1SettingsDefault fog_linear_fog_post_cam_default; // 0x6E8
    		public NativeType1SettingsDefault fog_exp_fog_height_default; // 0x730
    		public NativeType1SettingsDefault fog_exp_fog_length_default; // 0x778
    		public NativeType1SettingsDefault fog_exp_fog_post_cam_default; // 0x7C0
    		public NativeType1SettingsDefault fog_exp_fog_exponent_default; // 0x808
    		public NativeType1SettingsDefault area_ground_scale_default; // 0x850
    		public NativeType1SettingsDefault area_ground_scroll_speedX_default; // 0x898
    		public NativeType1SettingsDefault area_ground_scroll_speedY_default; // 0x8E0
    		public NativeType1SettingsDefault rain_rain_dist_default; // 0x928
    		public NativeType1SettingsDefault rain_rain_amount_default; // 0x970
    		public NativeType1SettingsDefault rain_rain_intensity_default; // 0x9B8
    		public NativeType1SettingsDefault rain_rain_hor_angle_default; // 0xA00
    		public NativeType1SettingsDefault rain_rain_vert_angle_default; // 0xA48
    		public NativeType1SettingsDefault rain_rain_turbulence_default; // 0xA90
    		public NativeType1SettingsDefault water_caustics_mult_default; // 0xAD8
    		public NativeType1SettingsDefault water_directionness_default; // 0xB20
    		public NativeType1SettingsDefault water_flow_foam_default; // 0xB68
    		public NativeType1SettingsDefault water_flow_intensity_default; // 0xBB0
    		public NativeType1SettingsDefault water_flow_turbulence_default; // 0xBF8
    		public NativeType1SettingsDefault water_open_water_murkiness_default; // 0xC40
    		public NativeType1SettingsDefault water_reflectiveness_default; // 0xC88
    		public NativeType1SettingsDefault water_refraction_index_default; // 0xCD0
    		public NativeType1SettingsDefault water_subsurface_scattering_default; // 0xD18
    		public NativeType1SettingsDefault water_terrain_water_murkiness_default; // 0xD60
    		public NativeType1SettingsDefault water_wind_direction_angle_default; // 0xDA8
    		public NativeType1SettingsDefault water_wind_intensity_default; // 0xDF0
    		public NativeType1SettingsDefault water_wind_speed_default; // 0xE38
    		public NativeType1SettingsDefault water_swell_angle_default; // 0xE80
    		public NativeType1SettingsDefault water_swell_height_default; // 0xEC8
    		public NativeType1SettingsDefault water_swell_intensity_default; // 0xF10
    		public NativeType1SettingsDefault water_swell_period_default; // 0xF58
    		public NativeType1SettingsDefault camera_far_plane_default; // 0xFA0
    		public NativeType1SettingsDefault camera_near_plane_default; // 0xFE8
    		public NativeType1SettingsDefault camera_exposure_default; // 0x1030
    		public NativeType1SettingsDefault effect_spawner_spawn_rate_default; // 0x1078
    		public NativeType1SettingsDefault audio_version_default; // 0x10C0
    		public NativeType1SettingsDefault audio_listener_height_default; // 0x1108
    		public NativeType1SettingsDefault audio_listener_offset_default; // 0x1150
    		public NativeType1SettingsDefault global_illumination_ambient_occlusion_power_default; // 0x1198
    		public NativeType1SettingsDefault global_illumination_indirect_light_area_default; // 0x11E0
    		public NativeType1SettingsDefault global_illumination_indirect_light_rampup_default; // 0x1228
    
    		public Map SettingsMap1; // 0x1270
    
    		public NativeType2SettingsDefault directional_light_colour_default; // 0x1280
    		public NativeType2SettingsDefault player_light_colour_default; // 0x12D0
    		public NativeType2SettingsDefault fog_linear_fog_colour_default; // 0x1320
    		public NativeType2SettingsDefault fog_exp_fog_colour_default; // 0x1370
    		public NativeType2SettingsDefault water_open_water_color_default; // 0x13C0
    		public NativeType2SettingsDefault water_terrain_water_color_default; // 0x1410
    
    		public Map SettingsMap2; // 0x1460
    
    		public NativeType3SettingsDefault directional_light_shadows_enabled_default; // 0x1470
    		public NativeType3SettingsDefault directional_light_cinematic_mode_default; // 0x14B8
    		public NativeType3SettingsDefault player_light_shadows_enabled_default; // 0x1500
    		public NativeType3SettingsDefault unused_default; // 0x1548
    		public NativeType3SettingsDefault fog_linear_fog_is_enabled_default; // 0x1590
    		public NativeType3SettingsDefault fog_exp_fog_is_enabled_default; // 0x15D8
    		public NativeType3SettingsDefault area_lightning_default; // 0x1620
    		public NativeType3SettingsDefault global_illumination_use_forced_screenspace_shadows_default; // 0x1668
    		public NativeType3SettingsDefault water_flow_wait_init_default; // 0x16B0
    
    		public Map SettingsMap3; // 0x16F8
    
    		public NativeType4SettingsDefault environment_mapping_diffuse_cube_default; // 0x1708
    		public NativeType4SettingsDefault environment_mapping_specular_cube_default; // 0x1768
    		public NativeType4SettingsDefault area_global_particle_effect_default; // 0x17C8
    		public NativeType4SettingsDefault area_player_environment_ao_default; // 0x1828
    		public NativeType4SettingsDefault post_transform_texture_default; // 0x1888
    		public NativeType4SettingsDefault audio_env_bank_name_default; // 0x18E8
    		public NativeType4SettingsDefault audio_env_event_name_default; // 0x1948
    		public NativeType4SettingsDefault audio_music_default; // 0x19A8
    		public NativeType4SettingsDefault audio_ambient_sound_default; // 0x1A08
    		public NativeType4SettingsDefault audio_ambient_sound2_default; // 0x1A68
    		public NativeType4SettingsDefault audio_ambient_bank_default; // 0x1AC8
    
    		public Map SettingsMap4; // 0x1B28
    
    		public NativeEnvSettingsEntryMap Entry5_0; // 0x1B38
    		public NativeEnvSettingsEntryMap Entry5_1; // 0x1B88
    		public NativeEnvSettingsEntryMap Entry5_2; // 0x1BD8
    		public NativeEnvSettingsEntryMap Entry5_3; // 0x1C28
    
    		public Vector _1C78; // 0x1C78
    
    		public StringW _1C90; // 0x1C90
    		public StringW _1CB0; // 0x1CB0
    
    		public ulong _1CD0; // 0x1CD0
    		public byte _1CD8; // 0x1CD8
    		private byte pad_1CD9; // 0x1CD9
    		private byte pad_1CDA; // 0x1CDA
    		private byte pad_1CDB; // 0x1CDB
    		private uint pad_1CDC; // 0x1CDC
    
    		public byte _1CE0; // 0x1CE0
    		private byte pad_1CE1; // 0x1CE1
    		private byte pad_1CE2; // 0x1CE2
    		private byte pad_1CE3; // 0x1CE3
    		public float _1CE4; // 0x1CE4
    
    		public Vector EnvEntries; // 0x1CE8, => NativeEnvEntryData
    		public Vector _1D00; // 0x1D00
    
    		public Map LoadedEnvs; // 0x1D18
    
    		public NativeType1Settings directional_light_phi; // 0x1D28
    		public NativeType1Settings directional_light_theta; // 0x1D30
    		public NativeType1Settings directional_light_multiplier; // 0x1D38
    		public NativeType1Settings directional_light_desaturation; // 0x1D40
    		public NativeType1Settings directional_light_cam_far_percent; // 0x1D48
    		public NativeType1Settings directional_light_cam_near_percent; // 0x1D50
    		public NativeType1Settings directional_light_cam_extra_z; // 0x1D58
    		public NativeType1Settings directional_light_cam_min_offset; // 0x1D60
    		public NativeType1Settings directional_light_cam_max_offset; // 0x1D68
    		public NativeType1Settings player_light_intensity; // 0x1D70
    		public NativeType1Settings player_light_penumbra; // 0x1D78
    		public NativeType1Settings player_light_bias; // 0x1D80
    		public NativeType1Settings post_processing_bloom_cutoff; // 0x1D88
    		public NativeType1Settings post_processing_bloom_intensity; // 0x1D90
    		public NativeType1Settings post_processing_original_intensity; // 0x1D98
    		public NativeType1Settings environment_mapping_direct_light_env_ratio; // 0x1DA0
    		public NativeType1Settings environment_mapping_env_brightness; // 0x1DA8
    		public NativeType1Settings environment_mapping_gi_additional_env_light; // 0x1DB0
    		public NativeType1Settings environment_mapping_hor_angle; // 0x1DB8
    		public NativeType1Settings environment_mapping_vert_angle; // 0x1DC0
    		public NativeType1Settings environment_mapping_solid_specular_attenuation; // 0x1DC8
    		public NativeType1Settings environment_mapping_water_specular_attenuation; // 0x1DD0
    		public NativeType1Settings fog_linear_fog_height; // 0x1DD8
    		public NativeType1Settings fog_linear_fog_length; // 0x1DE0
    		public NativeType1Settings fog_linear_fog_post_cam; // 0x1DE8
    		public NativeType1Settings fog_exp_fog_height; // 0x1DF0
    		public NativeType1Settings fog_exp_fog_length; // 0x1DF8
    		public NativeType1Settings fog_exp_fog_post_cam; // 0x1E00
    		public NativeType1Settings fog_exp_fog_exponent; // 0x1E08
    		public NativeType1Settings area_ground_scale; // 0x1E10
    		public NativeType1Settings area_ground_scroll_speedX; // 0x1E18
    		public NativeType1Settings area_ground_scroll_speedY; // 0x1E20
    		public NativeType1Settings rain_rain_dist; // 0x1E28
    		public NativeType1Settings rain_rain_amount; // 0x1E30
    		public NativeType1Settings rain_rain_intensity; // 0x1E38
    		public NativeType1Settings rain_rain_hor_angle; // 0x1E40
    		public NativeType1Settings rain_rain_vert_angle; // 0x1E48
    		public NativeType1Settings rain_rain_turbulence; // 0x1E50
    		public NativeType1Settings water_caustics_mult; // 0x1E58
    		public NativeType1Settings water_directionness; // 0x1E60
    		public NativeType1Settings water_flow_foam; // 0x1E68
    		public NativeType1Settings water_flow_intensity; // 0x1E70
    		public NativeType1Settings water_flow_turbulence; // 0x1E78
    		public NativeType1Settings water_open_water_murkiness; // 0x1E80
    		public NativeType1Settings water_reflectiveness; // 0x1E88
    		public NativeType1Settings water_refraction_index; // 0x1E90
    		public NativeType1Settings water_subsurface_scattering; // 0x1E98
    		public NativeType1Settings water_terrain_water_murkiness; // 0x1EA0
    		public NativeType1Settings water_wind_direction_angle; // 0x1EA8
    		public NativeType1Settings water_wind_intensity; // 0x1EB0
    		public NativeType1Settings water_wind_speed; // 0x1EB8
    		public NativeType1Settings water_swell_angle; // 0x1EC0
    		public NativeType1Settings water_swell_height; // 0x1EC8
    		public NativeType1Settings water_swell_intensity; // 0x1ED0
    		public NativeType1Settings water_swell_period; // 0x1ED8
    		public NativeType1Settings camera_far_plane; // 0x1EE0
    		public NativeType1Settings camera_near_plane; // 0x1EE8
    		public NativeType1Settings camera_exposure; // 0x1EF0
    		public NativeType1Settings effect_spawner_spawn_rate; // 0x1EF8
    		public NativeType1Settings audio_version; // 0x1F00
    		public NativeType1Settings audio_listener_height; // 0x1F08
    		public NativeType1Settings audio_listener_offset; // 0x1F10
    		public NativeType1Settings global_illumination_ambient_occlusion_power; // 0x1F18
    		public NativeType1Settings global_illumination_indirect_light_area; // 0x1F20
    		public NativeType1Settings global_illumination_indirect_light_rampup; // 0x1F28
    
    		public NativeType2Settings directional_light_colour; // 0x1F30
    		public NativeType2Settings player_light_colour; // 0x1F40
    		public NativeType2Settings fog_linear_fog_colour; // 0x1F50
    		public NativeType2Settings fog_exp_fog_colour; // 0x1F60
    		public NativeType2Settings water_open_water_color; // 0x1F70
    		public NativeType2Settings water_terrain_water_color; // 0x1F80
    
    		public NativeType3Settings directional_light_shadows_enabled; // 0x1F90
    		public NativeType3Settings directional_light_cinematic_mode; // 0x1F92
    		public NativeType3Settings player_light_shadows_enabled; // 0x1F94
    		public NativeType3Settings unused; // 0x1F96
    		public NativeType3Settings fog_linear_fog_is_enabled; // 0x1F98
    		public NativeType3Settings fog_exp_fog_is_enabled; // 0x1F9A
    		public NativeType3Settings area_lightning; // 0x1F9C
    		public NativeType3Settings global_illumination_use_forced_screenspace_shadows; // 0x1F9E
    		public NativeType3Settings water_flow_wait_init; // 0x1FA0
    		public NativeType3Settings pad_1FA2; // 0x1FA2
    		public NativeType3Settings pad_1FA4; // 0x1FA4
    		public NativeType3Settings pad_1FA6; // 0x1FA6
    
    		public NativeType4Settings environment_mapping_diffuse_cube; // 0x1FA8
    		public NativeType4Settings environment_mapping_specular_cube; // 0x1FF8
    		public NativeType4Settings area_global_particle_effect; // 0x2048
    		public NativeType4Settings area_player_environment_ao; // 0x2098
    		public NativeType4Settings post_transform_texture; // 0x20E8
    		public NativeType4Settings audio_env_bank_name; // 0x2138
    		public NativeType4Settings audio_env_event_name; // 0x2188
    		public NativeType4Settings audio_music; // 0x21D8
    		public NativeType4Settings audio_ambient_sound; // 0x2228
    		public NativeType4Settings audio_ambient_sound2; // 0x2278
    		public NativeType4Settings audio_ambient_bank; // 0x22C8
    
    		public NativeEnvSettingsEntryUnknown _2318; // 0x2318
    		public NativeEnvSettingsEntryUnknown _2338; // 0x2338
    		public NativeEnvSettingsEntryUnknown _2358; // 0x2358
    
    		public byte _2378; // 0x2378
    		private byte pad_2379; // 0x2379
    		private byte pad_237A; // 0x237A
    		private byte pad_237B; // 0x237B
    		private uint pad_237C; // 0x237C
    
    		// 0x2380
    	}
    }
    NativeEnvEntryData.cs - This is code generated from client data, but is the env data you'll change
    Code:
    using System.Runtime.InteropServices;
    
    namespace EnvMgr
    {
    	[StructLayout(LayoutKind.Sequential, Pack = 1)]
    	internal struct NativeEnvEntryData
    	{
    		public StringW Name; // 0x0
    
    		public NativeType1Settings directional_light_phi; // 0x20
    		public NativeType1Settings directional_light_theta; // 0x28
    		public NativeType1Settings directional_light_multiplier; // 0x30
    		public NativeType1Settings directional_light_desaturation; // 0x38
    		public NativeType1Settings directional_light_cam_far_percent; // 0x40
    		public NativeType1Settings directional_light_cam_near_percent; // 0x48
    		public NativeType1Settings directional_light_cam_extra_z; // 0x50
    		public NativeType1Settings directional_light_cam_min_offset; // 0x58
    		public NativeType1Settings directional_light_cam_max_offset; // 0x60
    		public NativeType1Settings player_light_intensity; // 0x68
    		public NativeType1Settings player_light_penumbra; // 0x70
    		public NativeType1Settings player_light_bias; // 0x78
    		public NativeType1Settings post_processing_bloom_cutoff; // 0x80
    		public NativeType1Settings post_processing_bloom_intensity; // 0x88
    		public NativeType1Settings post_processing_original_intensity; // 0x90
    		public NativeType1Settings environment_mapping_direct_light_env_ratio; // 0x98
    		public NativeType1Settings environment_mapping_env_brightness; // 0xA0
    		public NativeType1Settings environment_mapping_gi_additional_env_light; // 0xA8
    		public NativeType1Settings environment_mapping_hor_angle; // 0xB0
    		public NativeType1Settings environment_mapping_vert_angle; // 0xB8
    		public NativeType1Settings environment_mapping_solid_specular_attenuation; // 0xC0
    		public NativeType1Settings environment_mapping_water_specular_attenuation; // 0xC8
    		public NativeType1Settings fog_linear_fog_height; // 0xD0
    		public NativeType1Settings fog_linear_fog_length; // 0xD8
    		public NativeType1Settings fog_linear_fog_post_cam; // 0xE0
    		public NativeType1Settings fog_exp_fog_height; // 0xE8
    		public NativeType1Settings fog_exp_fog_length; // 0xF0
    		public NativeType1Settings fog_exp_fog_post_cam; // 0xF8
    		public NativeType1Settings fog_exp_fog_exponent; // 0x100
    		public NativeType1Settings area_ground_scale; // 0x108
    		public NativeType1Settings area_ground_scroll_speedX; // 0x110
    		public NativeType1Settings area_ground_scroll_speedY; // 0x118
    		public NativeType1Settings rain_rain_dist; // 0x120
    		public NativeType1Settings rain_rain_amount; // 0x128
    		public NativeType1Settings rain_rain_intensity; // 0x130
    		public NativeType1Settings rain_rain_hor_angle; // 0x138
    		public NativeType1Settings rain_rain_vert_angle; // 0x140
    		public NativeType1Settings rain_rain_turbulence; // 0x148
    		public NativeType1Settings water_caustics_mult; // 0x150
    		public NativeType1Settings water_directionness; // 0x158
    		public NativeType1Settings water_flow_foam; // 0x160
    		public NativeType1Settings water_flow_intensity; // 0x168
    		public NativeType1Settings water_flow_turbulence; // 0x170
    		public NativeType1Settings water_open_water_murkiness; // 0x178
    		public NativeType1Settings water_reflectiveness; // 0x180
    		public NativeType1Settings water_refraction_index; // 0x188
    		public NativeType1Settings water_subsurface_scattering; // 0x190
    		public NativeType1Settings water_terrain_water_murkiness; // 0x198
    		public NativeType1Settings water_wind_direction_angle; // 0x1A0
    		public NativeType1Settings water_wind_intensity; // 0x1A8
    		public NativeType1Settings water_wind_speed; // 0x1B0
    		public NativeType1Settings water_swell_angle; // 0x1B8
    		public NativeType1Settings water_swell_height; // 0x1C0
    		public NativeType1Settings water_swell_intensity; // 0x1C8
    		public NativeType1Settings water_swell_period; // 0x1D0
    		public NativeType1Settings camera_far_plane; // 0x1D8
    		public NativeType1Settings camera_near_plane; // 0x1E0
    		public NativeType1Settings camera_exposure; // 0x1E8
    		public NativeType1Settings effect_spawner_spawn_rate; // 0x1F0
    		public NativeType1Settings audio_version; // 0x1F8
    		public NativeType1Settings audio_listener_height; // 0x200
    		public NativeType1Settings audio_listener_offset; // 0x208
    		public NativeType1Settings global_illumination_ambient_occlusion_power; // 0x210
    		public NativeType1Settings global_illumination_indirect_light_area; // 0x218
    		public NativeType1Settings global_illumination_indirect_light_rampup; // 0x220
    
    		public NativeType2Settings directional_light_colour; // 0x228
    		public NativeType2Settings player_light_colour; // 0x238
    		public NativeType2Settings fog_linear_fog_colour; // 0x248
    		public NativeType2Settings fog_exp_fog_colour; // 0x258
    		public NativeType2Settings water_open_water_color; // 0x268
    		public NativeType2Settings water_terrain_water_color; // 0x278
    
    		public NativeType3Settings directional_light_shadows_enabled; // 0x288
    		public NativeType3Settings directional_light_cinematic_mode; // 0x28A
    		public NativeType3Settings player_light_shadows_enabled; // 0x28C
    		public NativeType3Settings unused; // 0x28E
    		public NativeType3Settings fog_linear_fog_is_enabled; // 0x290
    		public NativeType3Settings fog_exp_fog_is_enabled; // 0x292
    		public NativeType3Settings area_lightning; // 0x294
    		public NativeType3Settings global_illumination_use_forced_screenspace_shadows; // 0x296
    		public NativeType3Settings water_flow_wait_init; // 0x298
    		public NativeType3Settings pad_29A; // 0x29A
    		public NativeType3Settings pad_29C; // 0x29C
    		public NativeType3Settings pad_29E; // 0x29E
    
    		public NativeType4Settings environment_mapping_diffuse_cube; // 0x2A0
    		public NativeType4Settings environment_mapping_specular_cube; // 0x2F0
    		public NativeType4Settings area_global_particle_effect; // 0x340
    		public NativeType4Settings area_player_environment_ao; // 0x390
    		public NativeType4Settings post_transform_texture; // 0x3E0
    		public NativeType4Settings audio_env_bank_name; // 0x430
    		public NativeType4Settings audio_env_event_name; // 0x480
    		public NativeType4Settings audio_music; // 0x4D0
    		public NativeType4Settings audio_ambient_sound; // 0x520
    		public NativeType4Settings audio_ambient_sound2; // 0x570
    		public NativeType4Settings audio_ambient_bank; // 0x5C0
    
    		public NativeEnvSettingsEntryUnknown _610; // 0x610
    		public NativeEnvSettingsEntryUnknown _630; // 0x630
    		public NativeEnvSettingsEntryUnknown _650; // 0x650
    
    		// 0x670
    	}
    }
    Api.cs - Low level api required to read memory and process data.
    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 CodeGen function in Api.cs to see how I generate the structs. That logic will need updating on major client updates if the code breaks.

    There's a few other aspects to the system. Basically, environments can be "blended" together I believe, so that's why I'm modifying the core env data for the area where I am, as opposed to the final "mixed" main area. That main area's values are updated each frame, so if you were to modify the final result externally, it'd just get overwritten the next frame and it'd look like nothing was changing.

    In addition, envs can be swapped between at runtime, which is why there's code to iterate over multiple environments. Areas with sub-areas, such as boss rooms, and even the new delerium stuff, can add more envs, so the code by default will modify all of their settings.

    Changing string values is possible, but not implemented, as it adds extra complications not worth posting. The project is setup to where you need to run it each area change. If you had your own hook installed in the game, you could do it each frame automatically, but that's not the type of code I'd share for obvious reasons since it'd be easier to detect. The data is changed per-area, so if you leave the area and come back, you'll be back on stock settings as you play around with changes to see what you like best. This external approach makes dev a lot easier.

    You could probably make a CE table from this code, but you'd need to support the variable sized vector of env data, which means the workflow for changing stuff per-area is going to be a lot more painful, especially since you can't use one set of settings that works nicely across all areas. Be sure to read the code's comments about that, so just using a program like this instead is your best bet.

    Lastly, if you dump files from the GGPK, and check the "Metadata/EnvironmentSettings" folder, you'll be able to see all the base data if you don't want to visit each area in game individually.

    The only other thing to mention is that since area has its own specific env settings, if you were to blanket use the settings from another area, you might run into issues with the camera, break water, trigger game bugs, etc... That is why this code is designed around processing each env setting separately, as opposed to simply switching the env itself out like my gif showed above.

    I think that's about it, I double checked everything I'm posting so it should work as-is for the current client versions and I don't think I missed any other project files. Here's a little gif of changing the area's brightness in a loop in increments (on top of disabling shadows and changing the color of the lights): Screen capture - 593a5a31972b07ab7d7627cbd5d2a570 - Gyazo

  5. Thanks Sychotix, GameHelper, TehCheat, MrM000Cow (4 members gave Thanks to pushedx for this useful post)
  6. #5
    serlev's Avatar Member
    Reputation
    3
    Join Date
    Apr 2012
    Posts
    97
    Thanks G/R
    10/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Dear Pushedx,
    It is really incredible what you have done . You got the point and spent a lot of your time to help me and the other people having same/similar problem with ingame brightness.
    I cant find any words to describe my appreciate. The gifs linked by you show that it is exactly what I need.
    Unfortunately I am quite old and my programming knowledge almost stopped with Fortran V. I tried to understand what I am supposed to do with all these codes you have specified but no way.
    All are totally beyond of my programming knowledge. I can only use the ready tools like ExMap or can use CE scripts (only if someone announces the new offsets after each patch ).
    Hope someone jumps in your codes and make a tool (if possible) for everyone to adjust only brightness of the game and updates it for the new game releases.

    Ban is meaningless for my case since I cant play game as it is. I am fully a solo player and nothing to do with the game competions at all. That is playing with game brightness doesnt give me any advantage against other players.
    If I get ban, there is nothing to do I open another account and keep playing. Fortunately I didnt get any ban for the last 5 years which I used many different tools for only brightness.

    Thanks again Pushedx...

  7. #6
    Sychotix's Avatar Moderator Authenticator enabled
    Reputation
    1421
    Join Date
    Apr 2006
    Posts
    3,942
    Thanks G/R
    285/572
    Trade Feedback
    1 (100%)
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Just from a quick look, it seems like he's setup the code fairly neatly so the main (or even only?) section you'd need to change is wrapped in
    if (doChangeEnv)

    Just take the IterateEnvironmentSettings code and instead set it to your choice of environment.

  8. #7
    serlev's Avatar Member
    Reputation
    3
    Join Date
    Apr 2012
    Posts
    97
    Thanks G/R
    10/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by Sychotix View Post

    Just take the IterateEnvironmentSettings code and instead set it to your choice of environment.
    If you can describe step by step what to do I try to follow your instructions:
    such as
    i) Copy paste "program.cs code" at NotePad; save as program.cs
    ii) put this file in the same directory your PoE is
    etc... etc...
    else I have no clue what to do

  9. #8
    Sychotix's Avatar Moderator Authenticator enabled
    Reputation
    1421
    Join Date
    Apr 2006
    Posts
    3,942
    Thanks G/R
    285/572
    Trade Feedback
    1 (100%)
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by serlev View Post
    If you can describe step by step what to do I try to follow your instructions:
    such as
    i) Copy paste "program.cs code" at NotePad; save as program.cs
    ii) put this file in the same directory your PoE is
    etc... etc...
    else I have no clue what to do
    Pushedx posted the majority of the source code, but as I believe it was his intention to make at least someone work for it (you?)... I won't do the work for you

    Doing this task requires basic knowledge of visual studio (or your C# compiler of choice), the ability to understand and slightly modify C#, and the willingness to learn.

    To point you in the right direction, you will need to download Visual Studio 2019 Community Edition, Create a new solution, place the files in the solution, follow his instruction for enabling unsafe code, and then either modify the IterateEnvironmentSettings method to not loop through everything, but only change it for the one you want... or better yet write your own method ripping out the bits from IterateEnvironmentSettings that you need, and call that in the doChangeEnv if block.

    Then once you're done and have no compilation errors... run it (make sure it is run as admin as it is modifying memory) and enjoy

    Also, great work pushedx! Love the contributions you've been making to the public.
    Last edited by Sychotix; 05-02-2020 at 01:22 PM.

  10. #9
    GameHelper's Avatar ★ Elder ★ CoreCoins Purchaser
    Reputation
    2455
    Join Date
    Jun 2015
    Posts
    3,048
    Thanks G/R
    455/2200
    Trade Feedback
    0 (0%)
    Mentioned
    65 Post(s)
    Tagged
    1 Thread(s)
    Originally Posted by pushedx View Post
    I recently reversed a large chunk of the client's environment system.
    Amazing work pushedx, especially since you have pointed out (i.e. InGameSate->AreaData -> etc etc) how to get this data again if the offset changes


    Originally Posted by pushedx View Post
    Check out the CodeGen function in Api.cs to see how I generate the structs. That logic will need updating on major client updates if the code breaks.
    I read through the code, and I got a Q for you. what's the benefit of CodeGen code? As far as I understand it will break when they add/delete/move an element (i.e. major client update).
    So what's the difference between manually updating a structure OR manually updating the code (i.e. CodeGen) which writes that structure.

  11. #10
    pushedx's Avatar Contributor
    Reputation
    257
    Join Date
    Nov 2009
    Posts
    137
    Thanks G/R
    8/135
    Trade Feedback
    0 (0%)
    Mentioned
    12 Post(s)
    Tagged
    0 Thread(s)
    I read through the code, and I got a Q for you. what's the benefit of CodeGen code? As far as I understand it will break when they add/delete/move an element (i.e. major client update).
    So what's the difference between manually updating a structure OR manually updating the code (i.e. CodeGen) which writes that structure.
    The main purpose of the codegen is to show how the most important parts of the struct were labeled. Without them, there was no sane way to know what each setting controlled or even what it was for. For example, imagine trying to figure out that "phi"/"theta" were exactly what they do with no previous knowledge of the system and somehow ending up with the names "phi"/"theta" in your struct. It'd be almost impossible unless you debugged each value and figure out how it was being used elsewhere in the client.

    The second purpose is to have code to run after each update to detect changes in the system. If the codegen function throws an exception, you know something changed and can start to look into it before running the logic and causing some unintended side effect in the client. If you diff the generated structs against your main versions and the order has changed or something, you'll at least be able to have an updated version of the current layout without having to manually check over the entire struct field by field each time after a patch in fear of a sneaky change that goes undetected.

    I basically took advantage of the client system to generate something usable to work from for the future, as the alternative would have been a bunch of unnamed fields that people would have had to fill in over time with a lot of tedious trial and error. As a result of how the client system works, that means until it changes, you don't have to manually figure out where in the struct each setting will end up if new settings were added or the order of settings was changed. More on this later.

    My reversing process went something like this:
    - Started with a pointer in LocalData that I didn't quite know what it was (EnvironmentSettings at +0x8C0)
    - Looking for common data patterns (map, vector, etc..) I started noticing a more global pattern of StdMap followed by a fixed number of custom structs
    - After doing the initial struct layout (which nothing was labeled) I then reversed the maps and saw the correlation of data that preceded it, and made sense of what they were doing
    - While it results in a circular dependency, which tends to happen a lot with codegen, I realized that I could just use the data in memory to generate the struct I'm trying to reverse
    - What results is very little manual work (mostly the stuff between 0x1C78 -> 0x1D28 ) that actually has to be updated when things change

    Now as for the circular dependency that results and how to handle updates.

    Let's assume they added more type1 settings, which would cause SettingsMap1 and everything after to be misaligned.
    - The memory layout for StdMap is easy to visually recognize, and since you know the size of the NativeType1SettingsDefault type, figuring out how many new entries were added is not a problem.
    - You manually add X more NativeType1SettingsDefault to the main struct and rerun the codegen. This results in SettingsMap1 now being correct, but what's more important is that you now know what all the new NativeType1SettingsDefault fields are, as opposed to just being unlabeled entries. You'd copy those changes from the generated struct (you'd have to comment out the broken stuff ofc to generate the working stuff) into the main file (which is why the codegen doesn't automatically replace the main file, since you do have to do some manual work first).
    - Had I not included the codegen, you'd essentially have to manually do the same thing each change on top of handling any reordering of stuff, which is just not practical. Imagine for lulz ggg re-ordered all of the type1 settings, the codegen will handle this no problems and you'll be good to go and know when they're messing with stuff. With only the struct, you'd only know something broke because the results in game aren't what you'd expect, and that's the best case.
    - You then repeat this process for each main type of settings, working your way down to having the updated struct. The unknown, unused fields you don't have to even fix as you can just switch to a fixed byte array of the required size to make sure the EnvEntries StdVector is aligned. Once again, the memory layout for StdVector is pretty easy to distinguish, so it doesn't matter what happens in between, you'll be able to figure out what the new offset is and adjust accordingly.

    Essentially, what you're seeing here is the behind the scenes stuff that I'd do to work with this game. The process of reversing and the intermediate work done to arrive at the results is almost always lost in the final results. Changes are frequent and often random, so when it comes to managing an API for the game like we did with Exilebuddy, you need to be able to detect changes with minimal work, as well as have the tooling to reduce the amount of work required to do updates. In this case, this client system is very rare, as there's not too many other systems that allow you to figure out struct layouts from stuff in memory.

    Hopefully that makes more sense!

  12. Thanks Sychotix, serlev, MrM000Cow (3 members gave Thanks to pushedx for this useful post)
  13. #11
    urgent2009's Avatar Member
    Reputation
    2
    Join Date
    Jan 2015
    Posts
    10
    Thanks G/R
    0/1
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @pushedx

    After spending some time on some FPS game, I say to myself "should I give PoE another go?" because I gave up on my first time since this was all too hard.

    I come here and what do I see? The legend himself is active! I don't care what you say but I'm taking this as a sign and I'll give my 100% this time, no giving up!

    I've expressed my admiration to you on Skype lots of times but I wanted to do it once again in public

    I've got to ask, though. Are you spending time again on PoE for fun/hobby or is there a project in the horizon?

Similar Threads

  1. Possible to change the Color of some Spells?
    By Noyze in forum WoW ME Questions and Requests
    Replies: 1
    Last Post: 03-28-2011, 04:43 AM
  2. Is it still possible to change the models of pets?
    By Aradroth in forum WoW ME Questions and Requests
    Replies: 3
    Last Post: 01-06-2011, 04:06 PM
  3. [Hmm] Is it possible to change the ranges of ranged weapons
    By Snailz in forum WoW EMU Questions & Requests
    Replies: 5
    Last Post: 08-15-2008, 08:28 PM
  4. [Request] I need a program to edit the maps of wow 2.4.3
    By jejuasji in forum WoW ME Questions and Requests
    Replies: 2
    Last Post: 07-28-2008, 07:48 AM
  5. Is it possible to change the color of a bobber?
    By =sinister= in forum WoW ME Questions and Requests
    Replies: 2
    Last Post: 01-05-2007, 03:13 PM
All times are GMT -5. The time now is 05:58 PM. Powered by vBulletin® Version 4.2.3
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2024 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search