Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions Ultima/AnimationsUopLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ private static void BuildHashTable(FileStream fs, int fileIdx)
continue;
}

int dataSize = flag == 1 ? compressedLength : decompressedLength;
// compressedLength is the byte count on disk; decompressedLength only matches it while
// the entry is stored.
int dataSize = compressedLength;

_hashTable[hash] = new UopEntry
{
Expand Down Expand Up @@ -201,7 +203,9 @@ private static void LoadAnimationSequence()
continue;
}

int dataSize = flag == 1 ? compressedLength : decompressedLength;
// compressedLength is the byte count on disk; decompressedLength only matches it while
// the entry is stored.
int dataSize = compressedLength;
seqEntries[hash] = new UopEntry
{
FileIndex = -1,
Expand Down Expand Up @@ -465,13 +469,33 @@ private static byte[] ReadEntryData(UopEntry entry)
_ = fileStream.Read(buffer, 0, buffer.Length);
}

if (entry.CompressionFlag >= 1)
if (entry.CompressionFlag == 0)
{
var (ok, data) = UopUtils.Decompress(buffer);
return ok ? data : null;
return buffer;
}

return buffer;
var (ok, data) = UopUtils.Decompress(buffer);
if (!ok)
{
return null;
}

if (entry.CompressionFlag != (int)CompressionFlag.Mythic)
{
return data;
}

// Flag 3 is zlib wrapped around a Mythic stream, the same layering gumpart uses. No shipped
// AnimationFrame*.uop uses it, but ignoring it hands Mythic bytes to the frame parser as pixels.
uint mythicLength = MythicDecompress.PeekDecompressedLength(data);
if (mythicLength == 0 || mythicLength > int.MaxValue)
{
return null;
}

var mythic = new byte[(int)mythicLength];

return MythicDecompress.TryDecompress(data, mythic, out _) ? mythic : null;
}

private static AnimationFrame[] ParseUopFrames(byte[] data, int direction, bool flip)
Expand Down
79 changes: 64 additions & 15 deletions Ultima/FileIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Ultima.Helpers;

namespace Ultima
Expand All @@ -15,7 +16,41 @@ public sealed class FileIndex : IDisposable
public IEntry this[int index]
{
get => FileAccessor[index];
set => FileAccessor[index] = (Entry6D)value;
// Let the accessor cast: it knows whether it stores Entry3D or Entry6D.
set => FileAccessor[index] = value;
}

private readonly Lock _entryWriteLock = new();

/// <summary>
/// Persists dimensions discovered by actually decoding an entry back into the index, so a
/// later lookup does not have to decode it again.
/// </summary>
/// <remarks>
/// <see cref="Seek(int, ref IEntry, out bool)"/> and the indexer hand out a <b>boxed copy</b> of
/// the entry, so assigning to <c>entry.Extra1</c> on that copy is discarded - write-back has to go
/// through here. Callers only ever pass values read out of the payload, so the lock is only there
/// to stop two threads tearing the struct mid-write.
/// </remarks>
public void CacheDimensions(int index, int width, int height)
{
if (FileAccessor == null || index < 0 || index >= FileAccessor.IndexLength)
{
return;
}

lock (_entryWriteLock)
{
IEntry entry = FileAccessor[index];
if (entry == null)
{
return;
}

entry.Extra1 = width;
entry.Extra2 = height;
FileAccessor[index] = entry;
}
}

private readonly string _mulPath;
Expand Down Expand Up @@ -499,25 +534,34 @@ public struct Entry6D : IEntry

public int Length { get; set; }

private int extra1;
private int extra2;
public int DecompressedLength { get; set; }

/// <summary>
/// High half of <see cref="Extra"/>. For gumps this is the width, matching the
/// (width &lt;&lt; 16 | height) packing in gumpidx.mul.
/// </summary>
public int Extra1 { get; set; }

/// <summary>
/// Low half of <see cref="Extra"/>. For gumps this is the height.
/// </summary>
public int Extra2 { get; set; }

/// <summary>
/// Packed (Extra1 &lt;&lt; 16 | Extra2) view over the two halves, mirroring <see cref="Entry3D"/>.
/// Getter and setter must agree on the order: they once did not, so every UOP gump reported its
/// width and height swapped.
/// </summary>
public int Extra
{
get => extra1 << 16 | extra2;
get => (Extra1 << 16) | (Extra2 & 0xFFFF);
set
{
extra1 = value & 0x0000FFFF;
extra2 = (int)((value & 0xFFFF0000) >> 16);
Extra1 = (value >> 16) & 0xFFFF;
Extra2 = value & 0xFFFF;
}
}

public int DecompressedLength { get; set; }

public int Extra1 { get; set; }

public int Extra2 { get; set; }

public CompressionFlag Flag { get; set; }
}

Expand Down Expand Up @@ -672,7 +716,8 @@ public UopFileAccessor(string path, string uopEntryExtension, int length, int id
{
Index[i].Lookup = -1;
Index[i].Length = -1;
Index[i].Extra = -1;
Index[i].Extra1 = -1;
Index[i].Extra2 = -1;
}

do
Expand Down Expand Up @@ -700,14 +745,18 @@ public UopFileAccessor(string path, string uopEntryExtension, int length, int id
continue;
}

if (idx < 0 || idx > Index.Length)
if (idx < 0 || idx >= Index.Length)
{
throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!");
}

offset += headerLength;

if (hasextra && flag != 3)
// The width/height prefix can only be read straight off the stream when the payload
// is stored. For anything compressed those first eight bytes belong to the zlib (or
// zlib+Mythic) stream and the dimensions come out of the decompressed payload instead
// - see Gumps.GetRawGump.
if (hasextra && (CompressionFlag)flag == CompressionFlag.None)
{
long curPos = br.BaseStream.Position;

Expand Down
Loading