mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-24 14:11:29 +00:00
Add ability to read LBP textures
This commit is contained in:
parent
b7a86e7b98
commit
831e367078
9 changed files with 158 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,6 +19,7 @@ launchSettings.json
|
|||
# Lighthouse junk
|
||||
riderModule.iml
|
||||
r/
|
||||
png/
|
||||
/ProjectLighthouse/r/*
|
||||
/ProjectLighthouse/logs/*
|
||||
lighthouse.config.json
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
86
ProjectLighthouse/Helpers/ImageHelper.cs
Normal file
86
ProjectLighthouse/Helpers/ImageHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue