feat: Cleans up UOP file handling. Adds automatic bounds.bin generation (#1744)

### Summary
* Unifies reading UOP File indexes
* Adds ArtData file reader to get graphic bounds
* Automatically generates Bounds.bin from art file if missing
* Adds [GenBounds command to regenerate the bounds.bin file. Useful for when the art file changes.
This commit is contained in:
kaczy93 2024-05-02 05:02:06 +02:00 committed by GitHub
parent db0621b884
commit dc118d836f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 680 additions and 382 deletions

View file

@ -18,6 +18,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using Server.Buffers;
namespace Server;
@ -42,7 +43,7 @@ public static class MultiData
LoadMul(postHSMulFormat);
}
private static Dictionary<int, MultiComponentList> _components = new();
private static readonly Dictionary<int, MultiComponentList> _components = new();
public static MultiComponentList GetComponents(int multiID) =>
_components.TryGetValue(multiID & 0x3FFF, out var mcl) ? mcl : MultiComponentList.Empty;
@ -50,114 +51,78 @@ public static class MultiData
private static void LoadUOP(string path)
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var streamReader = new BinaryReader(stream);
// Head Information Start
if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP File
// TODO: Find out if housing.bin is needed and read that.
var uopEntries = UOPFiles.ReadUOPIndexes(stream, ".bin", 0x10000, 4, 6);
byte[] compressionBuffer = STArrayPool<byte>.Shared.Rent(0x10000);
var buffer = STArrayPool<byte>.Shared.Rent(0x10000);
foreach (var (i, entry) in uopEntries)
{
return;
}
stream.Seek(entry.Offset, SeekOrigin.Begin);
if (streamReader.ReadInt32() > 5) // Bad Version
{
return;
}
Span<byte> data;
UOPHash.BuildChunkIDs(out var chunkIds);
streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43
var startAddress = streamReader.ReadInt64();
stream.Seek(startAddress, SeekOrigin.Begin); // End of head block
long nextBlock;
do
{
var blockFileCount = streamReader.ReadInt32();
nextBlock = streamReader.ReadInt64();
var index = 0;
do
if (entry.Compressed)
{
var offset = streamReader.ReadInt64();
var headerSize = streamReader.ReadInt32(); // header length
var compressedSize = streamReader.ReadInt32(); // compressed size
var decompressedSize = streamReader.ReadInt32(); // decompressed size
var filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2)
streamReader.ReadUInt32();
var compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib)
index++;
if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin
if (stream.Read(buffer.AsSpan( 0, entry.CompressedSize)) != entry.CompressedSize)
{
continue;
throw new FileLoadException($"Error loading file {stream.Name}.");
}
chunkIds.TryGetValue(filehash, out var chunkID);
var position = stream.Position; // save current position
stream.Seek(offset + headerSize, SeekOrigin.Begin);
Span<byte> sourceData = GC.AllocateUninitializedArray<byte>(compressedSize);
if (stream.Read(sourceData) != compressedSize)
var decompressedSize = entry.Size;
if (Zlib.Unpack(compressionBuffer, ref decompressedSize, buffer, entry.CompressedSize) != ZlibError.Okay
|| decompressedSize != entry.Size)
{
continue;
throw new FileLoadException($"Error loading file {stream.Name}. Failed to unpack entry {i}.");
}
Span<byte> data;
data = compressionBuffer.AsSpan(0, decompressedSize);
}
else
{
data = buffer.AsSpan(0, entry.Size);
}
if (compressionMethod == 1)
var tileList = new List<MultiTileEntry>();
var reader = new SpanReader(data);
reader.Seek(4, SeekOrigin.Begin); // Skip the first 4 bytes
var count = reader.ReadUInt32LE();
for (uint t = 0; t < count; t++)
{
var itemId = reader.ReadUInt16LE();
var x = reader.ReadInt16LE();
var y = reader.ReadInt16LE();
var z = reader.ReadInt16LE();
var flagValue = reader.ReadUInt16LE();
var tileFlag = flagValue switch
{
data = GC.AllocateUninitializedArray<byte>(decompressedSize);
Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize);
}
else
{
data = sourceData;
}
1 => TileFlag.None,
257 => TileFlag.Generic,
_ => TileFlag.Background // 0
};
var tileList = new List<MultiTileEntry>();
var clilocsCount = reader.ReadUInt32LE();
var skip = (int)Math.Min(clilocsCount, int.MaxValue) * 4; // bypass binary block
reader.Seek(skip, SeekOrigin.Current);
var reader = new SpanReader(data);
reader.Seek(4, SeekOrigin.Begin);
var count = reader.ReadUInt32LE();
tileList.Add(new MultiTileEntry(itemId, x, y, z, tileFlag));
}
for (uint i = 0; i < count; i++)
{
var itemId = reader.ReadUInt16LE();
var x = reader.ReadInt16LE();
var y = reader.ReadInt16LE();
var z = reader.ReadInt16LE();
var flagValue = reader.ReadUInt16LE();
_components[i] = new MultiComponentList(tileList);
}
var tileFlag = flagValue switch
{
1 => TileFlag.None,
257 => TileFlag.Generic,
_ => TileFlag.Background // 0
};
STArrayPool<byte>.Shared.Return(buffer);
var clilocsCount = reader.ReadUInt32LE();
var skip = (int)Math.Min(clilocsCount, int.MaxValue) * 4; // bypass binary block
reader.Seek(skip, SeekOrigin.Current);
tileList.Add(new MultiTileEntry(itemId, x, y, z, tileFlag));
}
_components[chunkID] = new MultiComponentList(tileList);
stream.Seek(position, SeekOrigin.Begin); // back to position
} while (index < blockFileCount);
} while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0);
streamReader.Close();
if (compressionBuffer != null)
{
STArrayPool<byte>.Shared.Return(compressionBuffer);
}
}
private static void LoadMul(bool postHSMulFormat)
@ -769,150 +734,3 @@ public sealed class MultiComponentList
}
}
}
public static class UOPHash
{
public static void BuildChunkIDs(out Dictionary<ulong, int> chunkIds)
{
const int maxId = 0x10000;
chunkIds = new Dictionary<ulong, int>();
for (var i = 0; i < maxId; ++i)
{
chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i;
}
}
public static ulong HashLittle2(ReadOnlySpan<char> s)
{
var length = s.Length;
uint b, c;
var a = b = c = 0xDEADBEEF + (uint)length;
var k = 0;
while (length > 12)
{
a += s[k];
a += (uint)s[k + 1] << 8;
a += (uint)s[k + 2] << 16;
a += (uint)s[k + 3] << 24;
b += s[k + 4];
b += (uint)s[k + 5] << 8;
b += (uint)s[k + 6] << 16;
b += (uint)s[k + 7] << 24;
c += s[k + 8];
c += (uint)s[k + 9] << 8;
c += (uint)s[k + 10] << 16;
c += (uint)s[k + 11] << 24;
a -= c;
a ^= (c << 4) | (c >> 28);
c += b;
b -= a;
b ^= (a << 6) | (a >> 26);
a += c;
c -= b;
c ^= (b << 8) | (b >> 24);
b += a;
a -= c;
a ^= (c << 16) | (c >> 16);
c += b;
b -= a;
b ^= (a << 19) | (a >> 13);
a += c;
c -= b;
c ^= (b << 4) | (b >> 28);
b += a;
length -= 12;
k += 12;
}
if (length != 0)
{
switch (length)
{
case 12:
{
c += (uint)s[k + 11] << 24;
goto case 11;
}
case 11:
{
c += (uint)s[k + 10] << 16;
goto case 10;
}
case 10:
{
c += (uint)s[k + 9] << 8;
goto case 9;
}
case 9:
{
c += s[k + 8];
goto case 8;
}
case 8:
{
b += (uint)s[k + 7] << 24;
goto case 7;
}
case 7:
{
b += (uint)s[k + 6] << 16;
goto case 6;
}
case 6:
{
b += (uint)s[k + 5] << 8;
goto case 5;
}
case 5:
{
b += s[k + 4];
goto case 4;
}
case 4:
{
a += (uint)s[k + 3] << 24;
goto case 3;
}
case 3:
{
a += (uint)s[k + 2] << 16;
goto case 2;
}
case 2:
{
a += (uint)s[k + 1] << 8;
goto case 1;
}
case 1:
{
a += s[k];
break;
}
}
c ^= b;
c -= (b << 14) | (b >> 18);
a ^= c;
a -= (c << 11) | (c >> 21);
b ^= a;
b -= (a << 25) | (a >> 7);
c ^= b;
c -= (b << 16) | (b >> 16);
a ^= c;
a -= (c << 4) | (c >> 28);
b ^= a;
b -= (a << 14) | (a >> 18);
c ^= b;
c -= (b << 24) | (b >> 8);
}
return ((ulong)b << 32) | c;
}
}