Add ability to read LBP textures

This commit is contained in:
jvyden 2022-01-21 03:31:42 -05:00
parent b7a86e7b98
commit 831e367078
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
9 changed files with 158 additions and 9 deletions

1
.gitignore vendored
View file

@ -19,6 +19,7 @@ launchSettings.json
# Lighthouse junk
riderModule.iml
r/
png/
/ProjectLighthouse/r/*
/ProjectLighthouse/logs/*
lighthouse.config.json

View file

@ -72,12 +72,15 @@
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DDS/@EntryIndexedValue">DDS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DLC/@EntryIndexedValue">DLC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LBP/@EntryIndexedValue">LBP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MM/@EntryIndexedValue">MM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NAT/@EntryIndexedValue">NAT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NP/@EntryIndexedValue">NP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PNG/@EntryIndexedValue">PNG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PS/@EntryIndexedValue">PS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PSP/@EntryIndexedValue">PSP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPCS/@EntryIndexedValue">RPCS</s:String>
@ -98,6 +101,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCJS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Braaains/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=brun/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=deflater/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=dpadrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=farc/@EntryIndexedValue">True</s:Boolean>

View file

@ -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

View file

@ -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;

View file

@ -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
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2"/>
<PackageReference Include="DDSReader" Version="1.0.8-pre"/>
<PackageReference Include="Discord.Net.Webhook" Version="3.2.0"/>
<PackageReference Include="InfluxDB.Client" Version="3.2.0"/>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
@ -21,6 +22,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0"/>
<PackageReference Include="SharpZipLib" Version="1.3.3"/>
</ItemGroup>
<ItemGroup>

View file

@ -15,12 +15,16 @@ public class LbpFile
/// <summary>
/// The type of file.
/// </summary>
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);
}
}