//#define TEXTURE_FEATURE using System.Text; using System.Numerics; using SharpPluginLoader.Core; using SharpPluginLoader.Core.Memory; using SharpPluginLoader.Core.Rendering; namespace WorldTuningTool { public class WorldLUT { private const string lutsPath = "nativePC/plugins/CSharp/LUT"; public WorldLUT() { } private string[] lutList = []; public string[] GetLUTs() { return lutList; } 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; } // 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; } } } }