From 298e392ccb1967036b60abc151ec3093304ab00e Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Thu, 30 Jul 2026 10:51:51 -0400 Subject: Improve LUT feature, add HDR params Signed-off-by: Andrew Opalach --- LUT.cs | 243 ++++++++++++++++++++++++--------------------- Plugin.cs | 123 +++++------------------ Shaders/alpha_as_luma.hlsl | 4 +- 3 files changed, 160 insertions(+), 210 deletions(-) diff --git a/LUT.cs b/LUT.cs index 1181216..f38010c 100644 --- a/LUT.cs +++ b/LUT.cs @@ -2,57 +2,23 @@ using System.Text; using System.Numerics; -using SharpPluginLoader.Core; +using System.Runtime.InteropServices; + +using ImGuiNET; using SharpPluginLoader.Core.Memory; -using SharpPluginLoader.Core.Rendering; +using SharpPluginLoader.Core.Resources; +using SharpPluginLoader.Core.IO; namespace WorldTuningTool { - public class WorldLUT + public class WorldLUTs { - private const string lutsPath = "nativePC/plugins/CSharp/LUT"; - - public WorldLUT() { } - - private string[] lutList = []; - - public string[] GetLUTs() - { - return lutList; - } + private const string addPath = "nativePC/plugins/CSharp/LUT"; - public void ReloadLUTs() - { - if (Directory.Exists(lutsPath)) - { - lutList = Directory.GetFiles(lutsPath); - } - } - - public static nint ProbeD3DResource(nint lightingObject) - { - nint lutTexture = MemoryUtil.Read(lightingObject + 0x1C0); - nint d3dResource = 0x0; - if (lutTexture != 0x0) - { - if (Renderer.IsDirectX12) - { - d3dResource = MemoryUtil.Read(lutTexture + 0x10); - } - else - { - d3dResource = MemoryUtil.Read(lutTexture + 0x60); - if (d3dResource != 0x0) - { - d3dResource = MemoryUtil.Read(d3dResource + 0x18); - } - } - } - return d3dResource; - } + public WorldLUTs() { } // https://github.com/AsteriskAmpersand/CrappyLUTStudio--CLUTS-/blob/5af10daaa5f295b106cc5db09530a6fe5a4320d4/LUT.py#L35 - public byte[]? parseWorldLUT(BinaryReader reader) + private byte[]? parseWorldLUT(BinaryReader reader) { byte[] signature = reader.ReadBytes(4); if (!(signature[0] == 0x54 && signature[1] == 0x45 && @@ -87,12 +53,11 @@ namespace WorldTuningTool // https://drive.google.com/file/d/143Eh08ZYncCAMwJ1q4gWxVOqR_OSWYvs/view // When does DOMAIN_MIN/MAX apply? - public byte[]? parseCubeLUT(StreamReader reader) + private byte[]? parseCubeLUT(StreamReader reader) { string? line; - Vector3[,,]? parsed = null; - int dim = 0, last = 0; - int x = 0, y = 0, z = 0; + Vector3[]? parsed = null; + int dim = 0, idx = 0; while ((line = reader.ReadLine()) != null) { if (line.StartsWith("LUT_3D_SIZE")) @@ -113,8 +78,7 @@ namespace WorldTuningTool { return null; } - parsed = new Vector3[dim,dim,dim]; - last = dim - 1; + parsed = new Vector3[dim * dim * dim]; while ((line = reader.ReadLine()) != null) { if (line == "") continue; @@ -132,7 +96,7 @@ namespace WorldTuningTool { try { - parsed[x,y,z] = Plugin.StringToVector3(line, ' '); + parsed[idx] = Plugin.StringToVector3(line, ' '); } catch (FormatException) { @@ -142,23 +106,7 @@ namespace WorldTuningTool { return null; } - if (x == last) - { - if (y == last) - { - z++; - y = 0; - } - else - { - y++; - } - x = 0; - } - else - { - x++; - } + idx++; } } if (parsed == null) @@ -166,71 +114,144 @@ namespace WorldTuningTool return null; } const int lutDataSize = 32 * 32 * 32 * 8; - byte[] data = new byte[lutDataSize], bytes; + byte[] data = new byte[lutDataSize]; float scale = MathF.Pow(2, 14) - 1; - int idx = 0; - for (z = 0; z < 32; z++) + for (idx = 0; idx < lutDataSize; idx+=8) { - for (y = 0; y < 32; y++) + Vector3 v = parsed[idx/8]; + ushort normX = (ushort)Math.Round(v.X * scale); + ushort normY = (ushort)Math.Round(v.Y * scale); + ushort normZ = (ushort)Math.Round(v.Z * scale); + byte[] bytes = BitConverter.GetBytes(normX); + data[idx] = bytes[0]; + data[idx + 1] = bytes[1]; + bytes = BitConverter.GetBytes(normY); + data[idx + 2] = bytes[0]; + data[idx + 3] = bytes[1]; + bytes = BitConverter.GetBytes(normZ); + data[idx + 4] = bytes[0]; + data[idx + 5] = bytes[1]; + bytes = BitConverter.GetBytes(0x2B88); // Alpha constant. + data[idx + 6] = bytes[0]; + data[idx + 7] = bytes[1]; + } + return data; + } + + private unsafe bool overwriteFromStream(nint texture, Stream stream, bool cube = false) + { + byte[]? data = null; + if (cube) + { + using (StreamReader reader = new StreamReader(stream)) { - for (x = 0; x < 32; x++) - { - Vector3 v = parsed[x,y,z]; - ushort normX = (ushort)Math.Round(v.X * scale); - ushort normY = (ushort)Math.Round(v.Y * scale); - ushort normZ = (ushort)Math.Round(v.Z * scale); - bytes = BitConverter.GetBytes(normX); - data[idx] = bytes[0]; - data[idx + 1] = bytes[1]; - bytes = BitConverter.GetBytes(normY); - data[idx + 2] = bytes[0]; - data[idx + 3] = bytes[1]; - bytes = BitConverter.GetBytes(normZ); - data[idx + 4] = bytes[0]; - data[idx + 5] = bytes[1]; - bytes = BitConverter.GetBytes(0x2B88); // Alpha constant. - data[idx + 6] = bytes[0]; - data[idx + 7] = bytes[1]; - idx += 8; - } + data = parseCubeLUT(reader); } } - return data; + else + { + using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, false)) + { + data = parseWorldLUT(reader); + } + } + if (data == null) + { + return false; + } +#if TEXTURE_FEATURE + fixed (byte *dataPointer = (byte[])data) + { + // DXGI_FORMAT_R16G16B16A16_FLOAT = 10 + return new Texture(texture).Replace(dataPointer, 10, 32, 32, 32); + } +#else + return true; +#endif } - public unsafe bool OverwriteFromFile(nint resource, string path) + private unsafe bool overwriteFromFile(nint texture, string path) { + string ext = path.Split('.').Last().ToLower(); using (FileStream stream = File.Open(path, FileMode.Open)) { - byte[]? data = null; - string ext = path.Split('.').Last().ToLower(); - if (ext == "tex") + return overwriteFromStream(texture, stream, ext == "cube"); + } + } + + private void reloadBlendLUT(nint lightingObject) + { + MemoryUtil.GetRef(lightingObject + 0x1D4) = MemoryUtil.GetRef(lightingObject + 0x1D0) + 1.0f; + } + + private string[] fileList = []; + private static string[] selectedFile = [ "", "" ]; + private static bool[] selectionStarted = [ false, false ]; + private static bool[] selectedInvalid = [ false, false ]; + + public void DrawLUT(nint texture, nint lightingObject, int i, float width) + { + ImGui.PushID(i); + string lutPath = Marshal.PtrToStringAnsi(texture + 0xC)!; + ImGui.Text($"LUT {i}: {lutPath}"); +#if TEXTURE_FEATURE + if (ImGui.Button("Reset")) + { + MtFileStream? file = MtFileStream.FromPath($"native:\\{lutPath}.tex", OpenMode.Read, false); + if (file != null) { - using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, false)) + if (overwriteFromStream(texture, new MemoryStream(file.Read(file.Length)))) { - data = parseWorldLUT(reader); + reloadBlendLUT(lightingObject); } + file.Close(); } - else if (ext == "cube") + selectedFile[i] = ""; + selectedInvalid[i] = false; + } + ImGui.SameLine(); + ImGui.SetNextItemWidth(width * 0.6f); + bool prevWasInvalid = selectedInvalid[i]; + if (prevWasInvalid) + { + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF0000FF); + } + if (ImGui.BeginCombo("##LUT Files", selectedFile[i], ImGuiComboFlags.HeightLarge)) + { + if (prevWasInvalid) { - using (StreamReader reader = new StreamReader(stream)) - { - data = parseCubeLUT(reader); - } + ImGui.PopStyleColor(); } - if (data == null) + if (!selectionStarted[i]) { - return false; + fileList = Directory.Exists(addPath) ? Directory.GetFiles(addPath) : []; + selectionStarted[i] = true; } - fixed (byte *dataPointer = (byte[])data) + foreach (string filePath in fileList) { -#if TEXTURE_FEATURE - // DXGI_FORMAT_R16G16B16A16_FLOAT = 10 - Renderer.ReplaceTexture(resource, dataPointer, 10, 32, 32, 32); -#endif + string fileName = Path.GetFileName(filePath); + bool isSelected = selectedFile[i] == fileName; + if (ImGui.Selectable(fileName, isSelected)) + { + selectedFile[i] = fileName; + if (!(selectedInvalid[i] = !overwriteFromFile(texture, filePath))) + { + reloadBlendLUT(lightingObject); + } + } } - return true; + ImGui.EndCombo(); } + else + { + if (prevWasInvalid) + { + ImGui.PopStyleColor(); + } + selectionStarted[i] = false; + } +#endif + ImGui.PopID(); } } } diff --git a/Plugin.cs b/Plugin.cs index 820b79f..6547a0a 100755 --- a/Plugin.cs +++ b/Plugin.cs @@ -6,7 +6,6 @@ using System.Numerics; using System.Globalization; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; #if ENABLE_ASSERTS using System.Diagnostics; #endif @@ -24,11 +23,10 @@ using SharpPluginLoader.Core.Resources; #endif // @TODO: -// - Cleanup SSR patches. // - Configurable Broad Area Shadow Resolution. // - Hook sMhScene constructor to set hqMode. // - Optimized update logic. -// - Only update non-override values when ui is shown. +// - Only update non-override values when UI is shown. // - Map sky params. // - Lights refactor. // - Descriptions. @@ -50,6 +48,7 @@ using SharpPluginLoader.Core.Resources; // - The Rotten Vale jump out of Southeast Camp. Wall on the opposite side slightly to the left. // - Special Arena. // - Elder's Recess Lavasioth area. +// - Hoarfrost pit area with wedge beetles. // // Known issues to think about: // - Hair clipping while looking down. @@ -828,8 +827,7 @@ namespace WorldTuningTool if (NoOverride) { - // Dependent on overall ImGui style. - ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(40.0f, 0.0f)); + ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.X, 0.0f)); } else if (!assumeOverride) { @@ -1592,16 +1590,10 @@ namespace WorldTuningTool private Hook? createLightingObject; private Hook? destroyLightingObject; private nint lightingObject = 0x0; - private delegate void SetLightingParameters(nint lightingObjectInternal); - private Hook? setLightingParams; - private NativeAction setLightingParamsFunc; private delegate void UpdateLightingParameters(nint stackOffset, nint lightingObjectInternal); private Hook? updateLightingParams; private Hook? updateLutBlend; - private static WorldLUT lut = new WorldLUT(); - private static string selectedLut = ""; - private static bool lutSelectionStarted = false; - private static bool selectedLutInvalid = false; + private static WorldLUTs luts = new WorldLUTs(); private static Parameter[] lightingParameters = { newParameter("Light Tone Map Type", 0x16C, ParameterType.INT, pMinMaxStep(1, 6, 1)), newParameter("Light Compute Luminance", 0x170, ParameterType.BOOL), @@ -1624,7 +1616,12 @@ namespace WorldTuningTool }; private static Parameter[] brightnessParameters = { newParameter("Gamma Correct", 0x1C8, ParameterType.FLOAT, pStep(0.001f)), - newParameter("Gamma Correct EX", 0x1CC, ParameterType.FLOAT, pStep(0.001f)) + newParameter("Gamma Correct EX", 0x1CC, ParameterType.FLOAT, pStep(0.001f)), + newParameterView("Hdr Output", 0x21A, ParameterType.BOOL), + newParameter("Hdr Output White Level", 0x21C, ParameterType.FLOAT, pStep(0.05f)), + newParameter("Hdr Output Gamut Mapping Ratio", 0x220, ParameterType.FLOAT, pStep(0.001f)), + newParameter("Hdr Output Gamma", 0x224, ParameterType.FLOAT, pStep(0.001f)), + newParameter("Hdr Is Gui Hdr Gamma", 0x228, ParameterType.BOOL) }; private static MtObject? sLightProbes = null; private static Parameter[] lightProbesParameters = { @@ -1735,7 +1732,7 @@ namespace WorldTuningTool private static Parameter[] allParameters = new Parameter[]{ hqMode } .Concat(lodParameters) .Concat(snowParameters) - .Concat(new Parameter[] { shadowResParameter }) + .Append(shadowResParameter) .Concat(shadowParameters2) .Concat(shadowCascadeParameters) .Concat(broadAreaShadowParameters) @@ -1745,6 +1742,7 @@ namespace WorldTuningTool .Concat(sslrParameters) .Concat(passthroughParameters) .Concat(lightingParameters) + .Concat(brightnessParameters) .Concat(bloomParameters) .Concat(dofParameters) .Concat(motionBlurParameters) @@ -2143,7 +2141,7 @@ namespace WorldTuningTool onAreaChange = Hook.Create(0x141AC27D0, OnAreaChangeHook); // nint - // Needed for at least "Broad Area Shadow Dir". Otherwise it will flicker during an update on center change. + // Needed for at least "Broad Area Shadow Dir". Otherwise it will flicker on center change. evalSceneParams = Hook.Create(0x1423C3790, EvalSceneParamsHook); // nint, nint, int, nint // Light Dir XZY is updated seperately at 0x1420397F0, we assume this function always happens later, though. @@ -2156,8 +2154,6 @@ namespace WorldTuningTool createLightingObject = Hook.Create(0x1424CDE20, CreateLightingObjectHook); destroyLightingObject = Hook.Create(0x1424CE380, DestroyLightingObjectHook); // nint, int - setLightingParams = Hook.Create(0x1424CF1C0, SetLightingParametersHook); // nint - setLightingParamsFunc = new NativeAction(0x1424CF1C0); updateLutBlend = Hook.Create(0x1416D8DF0, UpdateLutBlendHook); // nint, nint createBloomObject = Hook.Create(0x1424CB3C0, CreateBloomObjectHook); @@ -2326,7 +2322,7 @@ namespace WorldTuningTool ssrShr3 = new Patch(new IntPtr(0x14269E07A), [0x90, 0x90]); ssrShr4 = new Patch(new IntPtr(0x14269E080), [0x90, 0x90]); // Approximate depth mip resolution. - ssrShr5 = new Patch(new IntPtr(0x14269DED3) + 0x2, [0x3]); + ssrShr5 = new Patch(new IntPtr(0x14269DED3) + 0x2, [0x3]); // 4 -> 3 ssrShr6 = new Patch(new IntPtr(0x14269DEDA) + 0x3, [0x3]); // Depth mips pass 1 dispatch dimensions. ssrSars1 = new Patch(new IntPtr(0x142282FD8) + 0x3, [0x3]); // 4 -> 3 @@ -2913,15 +2909,6 @@ namespace WorldTuningTool return destroyLightingObject!.Original(lightingObjectInternal, unknownInt); } - private void SetLightingParametersHook(nint lightingObjectInternal) - { - setLightingParams!.Original(lightingObjectInternal); - if (lightingObject == lightingObjectInternal) - { - selectedLut = ""; - } - } - private void UpdateLutBlendHook(nint stackOffset, nint lightingObjectInternal) { updateLutBlend!.Original(stackOffset, lightingObjectInternal); @@ -3862,91 +3849,33 @@ namespace WorldTuningTool } ImGui.PopID(); - /* - ImGui.PushID("Snow"); - if (ImGui.CollapsingHeader("Snow")) - { - ImGui.Text($"Address: 0x{sMhScene!.Instance:X}"); - foreach (Parameter param in snowParameters) - { - param.Draw(width); - } - } - ImGui.PopID(); - */ - ImGui.PushID("Lighting"); if (ImGui.CollapsingHeader("Lighting")) { + ImGui.Text($"Address: 0x{lightingObject:X}"); if (lightingObject != 0x0) { - ImGui.Text($"Address: 0x{lightingObject:X}"); - if (ImGui.CollapsingHeader("LUT")) + if (ImGui.CollapsingHeader("LUTs")) { - nint d3dResource = WorldLUT.ProbeD3DResource(lightingObject); - if (d3dResource != 0x0) - { - if (ImGui.Button("Reset")) - { - // Attempt to force check for reloading LUT to pass. - MemoryUtil.GetRef(lightingObject + 0x1D4) = MemoryUtil.GetRef(lightingObject + 0x1D0) + 1.0f; - } - ImGui.SameLine(); - ImGui.SetNextItemWidth(width * 0.625f); - bool prevLutWasInvalid = selectedLutInvalid; - if (prevLutWasInvalid) - { - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF0000FF); - } - if (ImGui.BeginCombo("##LUT Files", selectedLut, ImGuiComboFlags.HeightLarge)) - { - if (prevLutWasInvalid) - { - ImGui.PopStyleColor(); - } - if (!lutSelectionStarted) - { - lut.ReloadLUTs(); - lutSelectionStarted = true; - } - foreach (string lutPath in lut.GetLUTs()) - { - string lutFile = Path.GetFileName(lutPath); - bool isSelected = selectedLut == lutFile; - if (ImGui.Selectable(lutFile, isSelected)) - { - selectedLutInvalid = !lut.OverwriteFromFile(d3dResource, lutPath); - selectedLut = lutFile; - } - } - ImGui.EndCombo(); - } - else - { - if (prevLutWasInvalid) - { - ImGui.PopStyleColor(); - } - lutSelectionStarted = false; - } - } + nint lutBlendTexture = MemoryUtil.Read(lightingObject + 0x1C0); + ImGui.Text($"LUT Blend: 0x{lutBlendTexture:X}"); nint lutMap0 = MemoryUtil.Read(lightingObject + 0x1A8); if (lutMap0 != 0x0) { - ImGui.Text($"LUT Map 0: {Marshal.PtrToStringAnsi(lutMap0 + 0xC)!}"); + luts.DrawLUT(lutMap0, lightingObject, 0, width); } nint lutMap1 = MemoryUtil.Read(lightingObject + 0x1B0); if (lutMap1 != 0x0) { - ImGui.Text($"LUT Map 1: {Marshal.PtrToStringAnsi(lutMap1 + 0xC)!}"); + luts.DrawLUT(lutMap1, lightingObject, 1, width); } } foreach (Parameter param in lightingParameters) { param.Draw(width); } - ImGui.Separator(); } + ImGui.Separator(); ImGui.Text($"Address: 0x{sMhRender!.Instance:X}"); foreach (Parameter param in brightnessParameters) { @@ -3964,9 +3893,9 @@ namespace WorldTuningTool ImGui.PushID("Bloom"); if (ImGui.CollapsingHeader("Bloom")) { + ImGui.Text($"Address: 0x{bloomObject:X}"); if (bloomObject != 0x0) { - ImGui.Text($"Address: 0x{bloomObject:X}"); foreach (Parameter param in bloomParameters) { param.Draw(width); @@ -3978,9 +3907,9 @@ namespace WorldTuningTool ImGui.PushID("DepthOfField"); if (ImGui.CollapsingHeader("Depth of Field")) { + ImGui.Text($"Address: 0x{dofObject:X}"); if (dofObject != 0x0) { - ImGui.Text($"Address: 0x{dofObject:X}"); foreach (Parameter param in dofParameters) { param.Draw(width); @@ -3992,9 +3921,9 @@ namespace WorldTuningTool ImGui.PushID("MotionBlur"); if (ImGui.CollapsingHeader("Motion Blur")) { + ImGui.Text($"Address: 0x{motionBlurObject:X}"); if (motionBlurObject != 0x0) { - ImGui.Text($"Address: 0x{motionBlurObject:X}"); foreach (Parameter param in motionBlurParameters) { param.Draw(width); @@ -4006,9 +3935,9 @@ namespace WorldTuningTool ImGui.PushID("FXAA"); if (ImGui.CollapsingHeader("FXAA")) { + ImGui.Text($"Address: 0x{fxaaObject:X}"); if (fxaaObject != 0x0) { - ImGui.Text($"Address: 0x{fxaaObject:X}"); foreach (Parameter param in fxaaParameters) { param.Draw(width); @@ -4020,9 +3949,9 @@ namespace WorldTuningTool ImGui.PushID("TAA"); if (ImGui.CollapsingHeader("TAA")) { + ImGui.Text($"Address: 0x{taaObject:X}"); if (taaObject != 0x0) { - ImGui.Text($"Address: 0x{taaObject:X}"); foreach (Parameter param in taaParameters) { param.Draw(width); diff --git a/Shaders/alpha_as_luma.hlsl b/Shaders/alpha_as_luma.hlsl index 525f3c6..dff76c6 100644 --- a/Shaders/alpha_as_luma.hlsl +++ b/Shaders/alpha_as_luma.hlsl @@ -1,14 +1,14 @@ // https://stackoverflow.com/a/37624009 float inverseGamma(float t) { - return ((t <= 0.0404482362771076) ? (t/12.92) : pow((t + 0.055)/1.055, 2.4)); + return (t <= 0.0404482362771076) ? (t/12.92) : pow((t + 0.055)/1.055, 2.4); } // CIE L*a*b* f function (used to convert XYZ to L*a*b*) // http://en.wikipedia.org/wiki/Lab_color_space float LABF(float t) { - return ((t >= 8.85645167903563082e-3) ? pow(t, (1.0/3.0)) : (841.0/108.0)*t + (4.0/29.0)); + return (t >= 8.85645167903563082e-3) ? pow(t, (1.0/3.0)) : (841.0/108.0)*t + (4.0/29.0); } float rgbToCIEL(float3 p) -- cgit v1.2.3-101-g0448