mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-07-28 16:08:38 +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
|
# Lighthouse junk
|
||||||
riderModule.iml
|
riderModule.iml
|
||||||
r/
|
r/
|
||||||
|
png/
|
||||||
/ProjectLighthouse/r/*
|
/ProjectLighthouse/r/*
|
||||||
/ProjectLighthouse/logs/*
|
/ProjectLighthouse/logs/*
|
||||||
lighthouse.config.json
|
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/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@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/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/=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/=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/=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/=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/=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/=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/=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/=PSP/@EntryIndexedValue">PSP</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPCS/@EntryIndexedValue">RPCS</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/=BCJS/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Braaains/@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/=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/=dpadrate/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ezoiar/@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>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=farc/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -51,11 +52,23 @@ public class ResourcesController : ControllerBase
|
||||||
|
|
||||||
[ResponseCache(Duration = 86400)]
|
[ResponseCache(Duration = 86400)]
|
||||||
[HttpGet("/gameAssets/{hash}")]
|
[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();
|
return this.NotFound();
|
||||||
}
|
}
|
||||||
|
@ -79,7 +92,7 @@ public class ResourcesController : ControllerBase
|
||||||
return this.UnprocessableEntity();
|
return this.UnprocessableEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
string calculatedHash = HashHelper.Sha1Hash(file.Data).ToLower();
|
string calculatedHash = file.Hash;
|
||||||
if (calculatedHash != hash)
|
if (calculatedHash != hash)
|
||||||
{
|
{
|
||||||
Logger.Log
|
Logger.Log
|
||||||
|
|
|
@ -28,6 +28,8 @@ public static class BinaryHelper
|
||||||
while (readByte != byteToReadTo);
|
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)
|
public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true)
|
||||||
{
|
{
|
||||||
long oldPosition = reader.BaseStream.Position;
|
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 (!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
|
return file.FileType switch
|
||||||
{
|
{
|
||||||
|
|
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>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2"/>
|
<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="Discord.Net.Webhook" Version="3.2.0"/>
|
||||||
<PackageReference Include="InfluxDB.Client" Version="3.2.0"/>
|
<PackageReference Include="InfluxDB.Client" Version="3.2.0"/>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0"/>
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.0"/>
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.3.3"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -15,12 +15,16 @@ public class LbpFile
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of file.
|
/// The type of file.
|
||||||
/// </summary>
|
/// </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.Data = data;
|
||||||
|
|
||||||
this.FileType = FileHelper.DetermineFileType(this.Data);
|
this.FileType = FileHelper.DetermineFileType(this.Data);
|
||||||
|
this.Hash = HashHelper.Sha1Hash(this.Data).ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LbpFile? FromHash(string hash)
|
public static LbpFile? FromHash(string hash)
|
||||||
|
@ -30,6 +34,6 @@ public class LbpFile
|
||||||
|
|
||||||
byte[] data = File.ReadAllBytes(path);
|
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