From 831e367078999b40726339c5a9d843bbad2448d2 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 21 Jan 2022 03:31:42 -0500 Subject: [PATCH] Add ability to read LBP textures --- .gitignore | 1 + ProjectLighthouse.sln.DotSettings | 4 + .../Controllers/ResourcesController.cs | 21 ++++- ProjectLighthouse/Helpers/BinaryHelper.cs | 2 + .../Extensions/BinaryReaderExtensions.cs | 37 ++++++++ ProjectLighthouse/Helpers/FileHelper.cs | 4 +- ProjectLighthouse/Helpers/ImageHelper.cs | 86 +++++++++++++++++++ ProjectLighthouse/ProjectLighthouse.csproj | 2 + ProjectLighthouse/Types/Files/LbpFile.cs | 10 ++- 9 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 ProjectLighthouse/Helpers/Extensions/BinaryReaderExtensions.cs create mode 100644 ProjectLighthouse/Helpers/ImageHelper.cs diff --git a/.gitignore b/.gitignore index 55a09e5f..1e2fd1a7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ launchSettings.json # Lighthouse junk riderModule.iml r/ +png/ /ProjectLighthouse/r/* /ProjectLighthouse/logs/* lighthouse.config.json diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index a3b1a293..6439f6b2 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -72,12 +72,15 @@ UseExplicitType UseExplicitType UseExplicitType + BE + DDS DLC IP LBP MM NAT NP + PNG PS PSP RPCS @@ -98,6 +101,7 @@ True True True + True True True True diff --git a/ProjectLighthouse/Controllers/ResourcesController.cs b/ProjectLighthouse/Controllers/ResourcesController.cs index 980d985f..f9ff9346 100644 --- a/ProjectLighthouse/Controllers/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/ResourcesController.cs @@ -1,3 +1,4 @@ +#nullable enable using System.IO; using System.Linq; using System.Threading.Tasks; @@ -51,11 +52,23 @@ public class ResourcesController : ControllerBase [ResponseCache(Duration = 86400)] [HttpGet("/gameAssets/{hash}")] - public IActionResult GetWebResource(string hash) + public IActionResult GetGameImage(string hash) { - string path = FileHelper.GetResourcePath(hash); + string path = $"png/{hash}.png"; - if (FileHelper.ResourceExists(hash) && LbpFile.FromHash(hash)?.FileType == LbpFileType.Jpeg) return this.File(IOFile.OpenRead(path), "image/jpeg"); + if (IOFile.Exists(path)) + { + return this.File(IOFile.OpenRead(path), "image/png"); + } + + LbpFile? file = LbpFile.FromHash(hash); + if (file != null) + { + if (ImageHelper.LbpFileToPNG(file)) + { + return this.File(IOFile.OpenRead(path), "image/png"); + } + } return this.NotFound(); } @@ -79,7 +92,7 @@ public class ResourcesController : ControllerBase return this.UnprocessableEntity(); } - string calculatedHash = HashHelper.Sha1Hash(file.Data).ToLower(); + string calculatedHash = file.Hash; if (calculatedHash != hash) { Logger.Log diff --git a/ProjectLighthouse/Helpers/BinaryHelper.cs b/ProjectLighthouse/Helpers/BinaryHelper.cs index 8e44c4ff..7d242e60 100644 --- a/ProjectLighthouse/Helpers/BinaryHelper.cs +++ b/ProjectLighthouse/Helpers/BinaryHelper.cs @@ -28,6 +28,8 @@ public static class BinaryHelper while (readByte != byteToReadTo); } + public static byte[] ReadToEnd(BinaryReader reader) => reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); + public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true) { long oldPosition = reader.BaseStream.Position; diff --git a/ProjectLighthouse/Helpers/Extensions/BinaryReaderExtensions.cs b/ProjectLighthouse/Helpers/Extensions/BinaryReaderExtensions.cs new file mode 100644 index 00000000..feacec19 --- /dev/null +++ b/ProjectLighthouse/Helpers/Extensions/BinaryReaderExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; + +namespace LBPUnion.ProjectLighthouse.Helpers.Extensions; + +public static class BinaryReaderExtensions +{ + + #region Big Endian reading + + // Yoinked from https://stackoverflow.com/questions/8620885/c-sharp-binary-reader-in-big-endian + public static byte[] Reverse(this byte[] b) + { + Array.Reverse(b); + return b; + } + + public static ushort ReadUInt16BE(this BinaryReader binRdr) => BitConverter.ToUInt16(binRdr.ReadBytesRequired(sizeof(ushort)).Reverse(), 0); + + public static short ReadInt16BE(this BinaryReader binRdr) => BitConverter.ToInt16(binRdr.ReadBytesRequired(sizeof(short)).Reverse(), 0); + + public static uint ReadUInt32BE(this BinaryReader binRdr) => BitConverter.ToUInt32(binRdr.ReadBytesRequired(sizeof(uint)).Reverse(), 0); + + public static int ReadInt32BE(this BinaryReader binRdr) => BitConverter.ToInt32(binRdr.ReadBytesRequired(sizeof(int)).Reverse(), 0); + + public static byte[] ReadBytesRequired(this BinaryReader binRdr, int byteCount) + { + byte[] result = binRdr.ReadBytes(byteCount); + + if (result.Length != byteCount) throw new EndOfStreamException($"{byteCount} bytes required from stream, but only {result.Length} returned."); + + return result; + } + + #endregion + +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index 08c4caf3..d67384ef 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -17,7 +17,7 @@ public static class FileHelper { if (!ServerSettings.Instance.CheckForUnsafeFiles) return true; - if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); + if (file.FileType == LbpFileType.Unknown) return false; return file.FileType switch { @@ -32,7 +32,7 @@ public static class FileHelper LbpFileType.Jpeg => true, LbpFileType.Png => true, #if DEBUG - _ => throw new ArgumentOutOfRangeException(nameof(file), $"Unhandled file type ({file.FileType}) in FileHelper.IsFileSafe()"), + _ => throw new ArgumentOutOfRangeException(nameof(file), $"Unhandled file type ({file.FileType}) in FileHelper.IsFileSafe()"), #else _ => false, #endif diff --git a/ProjectLighthouse/Helpers/ImageHelper.cs b/ProjectLighthouse/Helpers/ImageHelper.cs new file mode 100644 index 00000000..691dc426 --- /dev/null +++ b/ProjectLighthouse/Helpers/ImageHelper.cs @@ -0,0 +1,86 @@ +#nullable enable +using System.IO; +using DDSReader; +using ICSharpCode.SharpZipLib.Zip.Compression; +using LBPUnion.ProjectLighthouse.Helpers.Extensions; +using LBPUnion.ProjectLighthouse.Types.Files; + +namespace LBPUnion.ProjectLighthouse.Helpers; + +public static class ImageHelper +{ + public static bool LbpFileToPNG(LbpFile file) => LbpFileToPNG(file.Data, file.Hash, file.FileType); + + public static bool LbpFileToPNG(byte[] data, string hash, LbpFileType type) + { + if (type != LbpFileType.Jpeg && type != LbpFileType.Png && type != LbpFileType.Texture) return false; + + using MemoryStream ms = new(data); + using BinaryReader reader = new(ms); + + if (type == LbpFileType.Texture) return TextureToPNG(hash, reader); + + return false; + } + + private static bool TextureToPNG(string hash, BinaryReader reader) + { + // Skip the magic (3 bytes), we already know its a texture + for(int i = 0; i < 3; i++) reader.ReadByte(); + + // This below is shamelessly stolen from ennuo's Toolkit: https://github.com/ennuo/toolkit/blob/d996ee4134740db0ee94e2cbf1e4edbd1b5ec798/src/main/java/ennuo/craftworld/utilities/Compressor.java#L40 + + // This byte determines the method of reading. We can only read a texture (' ') so if it's not ' ' it must be invalid. + if ((char)reader.ReadByte() != ' ') return false; + + reader.ReadInt16(); // ? + short chunks = reader.ReadInt16BE(); + + int[] compressed = new int[chunks]; + int[] decompressed = new int[chunks]; + + int decompressedSize = 0; + int compressedSize = 0; + + for(int i = 0; i < chunks; ++i) + { + compressed[i] = reader.ReadUInt16BE(); + decompressed[i] = reader.ReadUInt16BE(); + + decompressedSize += decompressed[i]; + compressedSize += compressed[i]; + } + + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms); + for(int i = 0; i < chunks; ++i) + { + byte[] deflatedData = reader.ReadBytes(compressed[i]); + if (compressed[i] == decompressed[i]) + { + writer.Write(deflatedData); + } + + Inflater inflater = new(); + inflater.SetInput(deflatedData); + byte[] inflatedData = new byte[decompressed[i]]; + inflater.Inflate(inflatedData); + + writer.Write(inflatedData); + } + + return DDSToPNG(hash, ms.ToArray()); + } + + private static bool DDSToPNG(string hash, byte[] data) + { + using MemoryStream stream = new(); + DDSImage image = new(data); + + image.SaveAsPng(stream); + + Directory.CreateDirectory("png"); + File.WriteAllBytes($"png/{hash}.png", stream.ToArray()); + return true; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 212aa906..4cc4e34e 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -9,6 +9,7 @@ + @@ -21,6 +22,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/ProjectLighthouse/Types/Files/LbpFile.cs b/ProjectLighthouse/Types/Files/LbpFile.cs index 0cd4a4d1..b3cad5e3 100644 --- a/ProjectLighthouse/Types/Files/LbpFile.cs +++ b/ProjectLighthouse/Types/Files/LbpFile.cs @@ -15,12 +15,16 @@ public class LbpFile /// /// The type of file. /// - public LbpFileType FileType; + public readonly LbpFileType FileType; - public LbpFile(byte[] data) + public readonly string Hash; + + public LbpFile(byte[] data, string? hash = null) { this.Data = data; + this.FileType = FileHelper.DetermineFileType(this.Data); + this.Hash = HashHelper.Sha1Hash(this.Data).ToLower(); } public static LbpFile? FromHash(string hash) @@ -30,6 +34,6 @@ public class LbpFile byte[] data = File.ReadAllBytes(path); - return new LbpFile(data); + return new LbpFile(data, hash); } } \ No newline at end of file