summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LUT.cs207
-rwxr-xr-xPlugin.cs62
-rwxr-xr-xScripts/convert_luts.sh21
3 files changed, 284 insertions, 6 deletions
diff --git a/LUT.cs b/LUT.cs
new file mode 100644
index 0000000..cb8975c
--- /dev/null
+++ b/LUT.cs
@@ -0,0 +1,207 @@
+//#define TEXTURE_FEATURE
+
+using System.Text;
+using System.Numerics;
+using SharpPluginLoader.Core;
+using SharpPluginLoader.Core.Rendering;
+
+namespace WorldTuningTool
+{
+ public class WorldLUT
+ {
+ private const string lutsPath = "nativePC/plugins/CSharp/LUT";
+
+ public WorldLUT() { }
+
+ public string[] GetLUTs()
+ {
+ if (!Directory.Exists(lutsPath))
+ {
+ return [];
+ }
+ return Directory.GetFiles(lutsPath);
+ }
+
+ // https://github.com/AsteriskAmpersand/CrappyLUTStudio--CLUTS-/blob/5af10daaa5f295b106cc5db09530a6fe5a4320d4/LUT.py#L35
+ public byte[]? parseWorldLUT(BinaryReader reader)
+ {
+ byte[] signature = reader.ReadBytes(4);
+ if (!(signature[0] == 0x54 && signature[1] == 0x45 &&
+ (signature[2] == 0x58 && signature[3] == 0x0)))
+ {
+ return null;
+ }
+ ulong version = reader.ReadUInt64();
+ uint datablock = reader.ReadUInt32();
+ uint format = reader.ReadUInt32();
+ uint mipCount = reader.ReadUInt32();
+ uint width = reader.ReadUInt32();
+ uint height = reader.ReadUInt32();
+ uint mipListCount = reader.ReadUInt32();
+ uint typeData = reader.ReadUInt32();
+ uint depth = reader.ReadUInt32();
+ byte[] NULL0 = reader.ReadBytes(12);
+ int NEG0 = reader.ReadInt32();
+ byte[] NULL1 = reader.ReadBytes(8);
+ int Special = reader.ReadInt32();
+ byte[] NULL2 = reader.ReadBytes(16);
+ byte[] NEG1 = reader.ReadBytes(32);
+ byte[] flags = reader.ReadBytes(32);
+ byte[] NULLX = reader.ReadBytes(32);
+ reader.BaseStream.Position = (long)reader.ReadUInt64();
+ if (!(width == 32 && height == 32 && depth == 32))
+ {
+ return null;
+ }
+ return reader.ReadBytes((int)(width * height * depth * 8));
+ }
+
+ // https://drive.google.com/file/d/143Eh08ZYncCAMwJ1q4gWxVOqR_OSWYvs/view
+ // When does DOMAIN_MIN/MAX apply?
+ public byte[]? parseCubeLUT(StreamReader reader)
+ {
+ string? line;
+ Vector3[,,]? parsed = null;
+ int dim = 0, last = 0;
+ int x = 0, y = 0, z = 0;
+ while ((line = reader.ReadLine()) != null)
+ {
+ if (line.StartsWith("LUT_3D_SIZE"))
+ {
+ try
+ {
+ dim = Convert.ToInt32(line.Split(' ').Last());
+ }
+ catch (FormatException)
+ {
+ return null;
+ }
+ catch (OverflowException)
+ {
+ return null;
+ }
+ if (dim != 32)
+ {
+ return null;
+ }
+ parsed = new Vector3[dim,dim,dim];
+ last = dim - 1;
+ while ((line = reader.ReadLine()) != null)
+ {
+ if (line == "") continue;
+ if (line[0] == '0' || line[0] == '1')
+ {
+ break;
+ }
+ }
+ if (line == null)
+ {
+ break;
+ }
+ }
+ if (parsed != null)
+ {
+ try
+ {
+ parsed[x,y,z] = Plugin.StringToVector3(line, ' ');
+ }
+ catch (FormatException)
+ {
+ return null;
+ }
+ catch (OverflowException)
+ {
+ return null;
+ }
+ if (x == last)
+ {
+ if (y == last)
+ {
+ z++;
+ y = 0;
+ }
+ else
+ {
+ y++;
+ }
+ x = 0;
+ }
+ else
+ {
+ x++;
+ }
+ }
+ }
+ if (parsed == null)
+ {
+ return null;
+ }
+ const int lutDataSize = 32 * 32 * 32 * 8;
+ byte[] data = new byte[lutDataSize], bytes;
+ float scale = MathF.Pow(2, 14) - 1;
+ int idx = 0;
+ for (z = 0; z < 32; z++)
+ {
+ for (y = 0; y < 32; y++)
+ {
+ 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;
+ }
+ }
+ }
+ return data;
+ }
+
+ public unsafe bool OverwriteFromFile(nint resource, string path)
+ {
+ using (FileStream stream = File.Open(path, FileMode.Open))
+ {
+ byte[]? data = null;
+ string ext = path.Split('.').Last().ToLower();
+ if (ext == "tex")
+ {
+ using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, false))
+ {
+ data = parseWorldLUT(reader);
+ }
+ }
+ else if (ext == "cube")
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ data = parseCubeLUT(reader);
+ }
+ }
+ if (data == null)
+ {
+ return false;
+ }
+ fixed (byte *dataPointer = (byte[])data)
+ {
+#if TEXTURE_FEATURE
+ // DXGI_FORMAT_R16G16B16A16_FLOAT = 10
+ Renderer.ReplaceTexture(resource, dataPointer, 10, 32, 32, 32);
+#endif
+ }
+ return true;
+ }
+ }
+ }
+}
diff --git a/Plugin.cs b/Plugin.cs
index 56be4f3..e79ba74 100755
--- a/Plugin.cs
+++ b/Plugin.cs
@@ -104,13 +104,13 @@ namespace WorldTuningTool
private static string BooleanToString(bool b) { return b.ToString(CultureInfo.InvariantCulture); }
private static string Int32ToString(int i, string? format = null) { return i.ToString(format, CultureInfo.InvariantCulture); }
- private static Vector3 StringToVector3(string s)
+ public static Vector3 StringToVector3(string s, char delim = ',')
{
if (String.IsNullOrEmpty(s))
{
throw new FormatException();
}
- string[] vs = s.Split(',').Select(v => v.Trim()).ToArray();
+ string[] vs = s.Split(delim).Select(v => v.Trim()).ToArray();
if (vs.Length != 3)
{
throw new FormatException();
@@ -1645,9 +1645,14 @@ namespace WorldTuningTool
private Hook<CreateLightingObject>? createLightingObject;
private Hook<DestroyLightingObject>? destroyLightingObject;
private nint lightingObject = 0x0;
+ private delegate void SetLightingParameters(nint lightingObjectInternal);
+ private Hook<SetLightingParameters>? setLightingParams;
private delegate void UpdateLightingParameters(nint stackOffset, nint lightingObjectInternal);
private Hook<UpdateLightingParameters>? updateLightingParams;
private Hook<UpdateLightingParameters>? updateLutBlend;
+ private static WorldLUT lut = new WorldLUT();
+ private static string selectedLut = "";
+ private static bool selectedLutInavlid = false;
private static Parameter[] lightingParameters = {
newParameter<int>("Light Tone Map Type", 0x16C, ParameterType.INT, pMinMaxStep(1, 6, 1)),
newParameter<bool>("Light Compute Luminance", 0x170, ParameterType.BOOL),
@@ -2157,6 +2162,7 @@ namespace WorldTuningTool
createLightingObject = Hook.Create<CreateLightingObject>(0x1424CDE20, CreateLightingObjectHook);
destroyLightingObject = Hook.Create<DestroyLightingObject>(0x1424CE380, DestroyLightingObjectHook); // nint, int
+ setLightingParams = Hook.Create<SetLightingParameters>(0x1424CF1C0, SetLightingParametersHook); // nint
updateLutBlend = Hook.Create<UpdateLightingParameters>(0x1416D8DF0, UpdateLutBlendHook); // nint, nint
createBloomObject = Hook.Create<CreateBloomObject>(0x1424CB3C0, CreateBloomObjectHook);
@@ -2563,10 +2569,7 @@ namespace WorldTuningTool
{
foreach (Parameter param in lightingParameters)
{
- if (!param.PerFrame)
- {
- param.Update(lightingObject);
- }
+ param.Update(lightingObject);
}
}
foreach (Parameter param in lightProbesParameters)
@@ -2855,6 +2858,16 @@ namespace WorldTuningTool
return destroyLightingObject!.Original(lightingObjectInternal, unknownInt);
}
+ private void SetLightingParametersHook(nint lightingObjectInternal)
+ {
+ setLightingParams!.Original(lightingObjectInternal);
+ if (lightingObject == lightingObjectInternal)
+ {
+ // LUT texture should be set at this point.
+ selectedLut = "";
+ }
+ }
+
private void UpdateLutBlendHook(nint stackOffset, nint lightingObjectInternal)
{
updateLutBlend!.Original(stackOffset, lightingObjectInternal);
@@ -3789,6 +3802,43 @@ namespace WorldTuningTool
{
if (lightingObject != 0x0)
{
+ nint lutTexture = MemoryUtil.Read<nint>(lightingObject + 0x1C0);
+ nint d3d12Resource = 0x0;
+ if (lutTexture != 0x0)
+ {
+ d3d12Resource = MemoryUtil.Read<nint>(lutTexture + 0x10);
+ }
+ if (d3d12Resource != 0x0)
+ {
+ ImGui.SetNextItemWidth(width * 0.65f);
+ bool prevLutWasInvalid = selectedLutInavlid;
+ if (prevLutWasInvalid)
+ {
+ ImGui.PushStyleColor(ImGuiCol.Text, 0xFF0000FF);
+ }
+ if (ImGui.BeginCombo("LUT", selectedLut, ImGuiComboFlags.HeightLarge))
+ {
+ if (prevLutWasInvalid)
+ {
+ ImGui.PopStyleColor();
+ }
+ foreach (string lutPath in lut.GetLUTs())
+ {
+ string lutFile = Path.GetFileName(lutPath);
+ bool isSelected = selectedLut == lutFile;
+ if (ImGui.Selectable(lutFile, isSelected))
+ {
+ selectedLutInavlid = !lut.OverwriteFromFile(d3d12Resource, lutPath);
+ selectedLut = lutFile;
+ }
+ }
+ ImGui.EndCombo();
+ }
+ else if (prevLutWasInvalid)
+ {
+ ImGui.PopStyleColor();
+ }
+ }
ImGui.Text($"Address: 0x{lightingObject:X}");
foreach (Parameter param in lightingParameters)
{
diff --git a/Scripts/convert_luts.sh b/Scripts/convert_luts.sh
new file mode 100755
index 0000000..4778b02
--- /dev/null
+++ b/Scripts/convert_luts.sh
@@ -0,0 +1,21 @@
+#! /usr/bin/env sh
+# https://github.com/michelerenzullo/LUTify/blob/main/LUTify.py
+# -----
+# diff --git a/LUTify.py b/LUTify.py
+# index 71983ac..24020db 100644
+# --- a/LUTify.py
+# +++ b/LUTify.py
+# @@ -162,7 +162,8 @@ if args.input.lower().endswith((".cube",".png",".jpg",".jpeg",".tiff")) and args
+# title = re.search("[^\\\/]+(?=\.[\w]+$)",args.input)[0]
+# if args.input.lower().endswith(".cube"):
+# file = open(args.input,'r').read()
+# - o_array = np.array([i.lower().replace(',', '').split() for i in re.findall("\n[+-]?[0-9]*[.]?[0-9]+\s[+-]?[0-9]*[.]?[0-9]+\s[+-]?[0-9]*[.]?[0-9]+",file)],dtype=float).reshape(-1)
+# + mfloat = "[+-]?[0-9]*[.]?[0-9]*[eE]?[+-]?[0-9]+"
+# + o_array = np.array([i.lower().replace(',', '').split() for i in re.findall(f"\n{mfloat}\s{mfloat}\s{mfloat}",file)],dtype=float).reshape(-1)
+# lutSize = int(re.search("_SIZE.*?(\d+)",file).group(1))
+# input_title = re.search("TITLE.?[\"'](.*?)[\"']",file)
+# del file
+# -----
+for var in "$@"; do
+ python3 LUTify.py -i "$var" -o "$var.32.cube" -s 32 -m tetrahedral
+done