mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-03 16:39:51 +08:00
155 lines
6.1 KiB
C#
155 lines
6.1 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using SharpEmu.Libs.Ampr;
|
|
using Xunit;
|
|
|
|
namespace SharpEmu.Libs.Tests.Ampr;
|
|
|
|
// AmprFileRegistry is process-global static state, so the classes that index
|
|
// or clear it must not run concurrently with each other.
|
|
[Collection("AmprFileRegistry")]
|
|
public class AmprFileRegistryTests
|
|
{
|
|
[Fact]
|
|
public void ComputeFileId_matches_utf8_fnv1a()
|
|
{
|
|
const string relative = "CoreData/foo/bar.bin";
|
|
Assert.Equal(FnvUtf8("$/" + relative), AmprFileRegistry.ComputeFileId("$/" + relative));
|
|
Assert.Equal(FnvUtf8("/app0/" + relative), AmprFileRegistry.ComputeFileId("/app0/" + relative));
|
|
Assert.Equal(FnvUtf8("app0/" + relative), AmprFileRegistry.ComputeFileId("app0/" + relative));
|
|
Assert.Equal(FnvUtf8(relative), AmprFileRegistry.ComputeFileId(relative));
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterApp0Relative_publishes_same_ids_as_string_hashes()
|
|
{
|
|
AmprFileRegistry.ClearForTests();
|
|
const string relative = "misc/loadouts/test.txt";
|
|
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test", relative);
|
|
AmprFileRegistry.RegisterApp0RelativeForTests(relative, host);
|
|
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId(relative), out var d));
|
|
Assert.Equal(host, a);
|
|
Assert.Equal(host, b);
|
|
Assert.Equal(host, c);
|
|
Assert.Equal(host, d);
|
|
}
|
|
|
|
[Fact]
|
|
public void Register_publishes_all_app0_path_aliases()
|
|
{
|
|
AmprFileRegistry.ClearForTests();
|
|
const string relative = "scripts/cp11/cp11main.script";
|
|
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test2", relative);
|
|
AmprFileRegistry.Register("$/" + relative, host);
|
|
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
|
|
Assert.True(AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId(relative), out var d));
|
|
Assert.Equal(host, a);
|
|
Assert.Equal(host, b);
|
|
Assert.Equal(host, c);
|
|
Assert.Equal(host, d);
|
|
}
|
|
|
|
[Fact]
|
|
public void App0_index_cache_keeps_files_that_differ_only_by_case()
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-case-" + Guid.NewGuid().ToString("N"));
|
|
var cacheDir = Path.Combine(root, "..", "sharpemu-ampr-cache-" + Guid.NewGuid().ToString("N"));
|
|
var upper = Path.Combine(root, "data", "ASSET.bin");
|
|
var lower = Path.Combine(root, "data", "asset.bin");
|
|
var previousCacheDir = Environment.GetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE");
|
|
try
|
|
{
|
|
Directory.CreateDirectory(Path.Combine(root, "sce_sys"));
|
|
Directory.CreateDirectory(Path.Combine(root, "data"));
|
|
File.WriteAllText(Path.Combine(root, "sce_sys", "param.json"), "{}");
|
|
File.WriteAllBytes(upper, [1, 2, 3]);
|
|
if (File.Exists(lower))
|
|
{
|
|
// Case-insensitive host: the two names are one file, so there is
|
|
// nothing for an ignore-case index to lose.
|
|
return;
|
|
}
|
|
|
|
File.WriteAllBytes(lower, [4, 5, 6]);
|
|
Environment.SetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE", cacheDir);
|
|
|
|
var normalizedRoot = Path.GetFullPath(root);
|
|
var expectedUpper = Path.Combine(normalizedRoot, "data", "ASSET.bin");
|
|
var expectedLower = Path.Combine(normalizedRoot, "data", "asset.bin");
|
|
|
|
// Fresh tree walk, which also writes the on-disk index cache.
|
|
AmprFileRegistry.ClearForTests();
|
|
AmprFileRegistry.EnsureApp0Indexed(root);
|
|
AssertResolves(expectedUpper, expectedLower);
|
|
|
|
// Second boot: served from the cache the walk just wrote.
|
|
AmprFileRegistry.ClearForTests();
|
|
AmprFileRegistry.EnsureApp0Indexed(root);
|
|
AssertResolves(expectedUpper, expectedLower);
|
|
}
|
|
finally
|
|
{
|
|
Environment.SetEnvironmentVariable("SHARPEMU_AMPR_INDEX_CACHE", previousCacheDir);
|
|
AmprFileRegistry.ClearForTests();
|
|
TryDeleteDirectory(cacheDir);
|
|
TryDeleteDirectory(root);
|
|
}
|
|
|
|
static void AssertResolves(string expectedUpper, string expectedLower)
|
|
{
|
|
Assert.True(
|
|
AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("$/data/ASSET.bin"), out var actualUpper),
|
|
"data/ASSET.bin is missing from the app0 index.");
|
|
Assert.True(
|
|
AmprFileRegistry.TryGetHostPath(
|
|
AmprFileRegistry.ComputeFileId("$/data/asset.bin"), out var actualLower),
|
|
"data/asset.bin is missing from the app0 index.");
|
|
Assert.Equal(expectedUpper, actualUpper);
|
|
Assert.Equal(expectedLower, actualLower);
|
|
}
|
|
}
|
|
|
|
private static void TryDeleteDirectory(string path)
|
|
{
|
|
try
|
|
{
|
|
Directory.Delete(path, recursive: true);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Temp cleanup is best-effort.
|
|
}
|
|
}
|
|
|
|
private static uint FnvUtf8(string text)
|
|
{
|
|
const uint offset = 2166136261;
|
|
const uint prime = 16777619;
|
|
var hash = offset;
|
|
foreach (var b in System.Text.Encoding.UTF8.GetBytes(text))
|
|
{
|
|
hash ^= b;
|
|
hash *= prime;
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
}
|