From a9e1c25968ae42e79bbe3d6a6da58c0d76f3ab25 Mon Sep 17 00:00:00 2001 From: Berk <12572227+par274@users.noreply.github.com> Date: Mon, 28 Sep 2026 04:43:05 +0300 Subject: [PATCH] [Ampr] Fix PakDirectoryTracker to handle multiple directories with the same name (#925) --- src/SharpEmu.Libs/Ampr/PakDirectoryTracker.cs | 17 ++++++++++++++++- .../Ampr/PakDirectoryTrackerTests.cs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Ampr/PakDirectoryTracker.cs b/src/SharpEmu.Libs/Ampr/PakDirectoryTracker.cs index 6b82a42..e5233f0 100644 --- a/src/SharpEmu.Libs/Ampr/PakDirectoryTracker.cs +++ b/src/SharpEmu.Libs/Ampr/PakDirectoryTracker.cs @@ -60,10 +60,19 @@ internal static class PakDirectoryTracker // related assets, and the guest streams them with locality, so disambiguate collisions // by choosing the unconsumed match nearest the running read cursor. DirectoryEntry? best = null; + DirectoryEntry? uniqueMatch = null; + var matchCount = 0; var bestDistance = ulong.MaxValue; foreach (var entry in entries) { - if (entry.Consumed || entry.FileLen != requestedSize) + if (entry.FileLen != requestedSize) + { + continue; + } + + uniqueMatch = entry; + matchCount++; + if (entry.Consumed) { continue; } @@ -90,6 +99,12 @@ internal static class PakDirectoryTracker return best.FilePos; } + + // A unique archive member may be opened again after its first read. + if (matchCount == 1) + { + return uniqueMatch!.FilePos; + } } return state.NextOffset; diff --git a/tests/SharpEmu.Libs.Tests/Ampr/PakDirectoryTrackerTests.cs b/tests/SharpEmu.Libs.Tests/Ampr/PakDirectoryTrackerTests.cs index d07f42a..3b79e30 100644 --- a/tests/SharpEmu.Libs.Tests/Ampr/PakDirectoryTrackerTests.cs +++ b/tests/SharpEmu.Libs.Tests/Ampr/PakDirectoryTrackerTests.cs @@ -72,6 +72,23 @@ public sealed class PakDirectoryTrackerTests Assert.Equal(0x1000UL + (runLen * 2), third); } + [Fact] + public void ResolveSequentialOffset_UniqueEntryCanBeReadAgain() + { + const uint fileId = 0x5AA5_0003; + const uint size = 15903; + var memory = new FakeCpuMemory(0x1_0000_0000, 0x1000); + var ctx = new CpuContext(memory, Generation.Gen5); + LoadDirectory(ctx, fileId, memory, 0x1_0000_0000, dirFileOffset: 0x400, new[] + { + new PakEntry("bots/characters.txt", FilePos: 0xF81CF, FileLen: size), + }); + + Assert.Equal(0xF81CFUL, PakDirectoryTracker.ResolveSequentialOffset(fileId, size)); + PakDirectoryTracker.OnReadCompleted(ctx, fileId, destination: 0x1_0000_0000, fileOffset: 0xF81CF, bytesRead: size); + Assert.Equal(0xF81CFUL, PakDirectoryTracker.ResolveSequentialOffset(fileId, size)); + } + // Feeds the tracker a synthetic PACK header + directory table exactly as the AMPR read path does: // first the 12-byte header (which arms directory parsing), then the directory records themselves. private static void LoadDirectory(