using Ludarium.Application; using Ludarium.Domain; namespace Ludarium.UnitTests; public sealed class BrowserPlayPolicyTests { [Fact] public void DeterministicNesFixtureIsAllowlistedOnlyWhenSourceIsReadOnly() { var fixture = Convert.FromBase64String(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "fixtures", "minimal-ines.base64")).Trim()); Assert.Equal([0x4e, 0x45, 0x53, 0x1a], fixture[..4]); var candidate = Candidate("nes", "roms/nes/synthetic.nes", fixture.Length, readOnly: true); var result = BrowserPlayPolicy.Evaluate(candidate, configured: true, emulatorHealthy: true, DateTimeOffset.UnixEpoch); Assert.True(result.Available); Assert.Equal(BrowserPlayState.Available, result.State); Assert.Equal("fceumm", result.Core); Assert.True(result.AutomaticRestore); } [Theory] [InlineData("pc", "PC/setup.exe", BrowserPlayState.UnsupportedPlatform)] [InlineData("nes", "roms/nes/setup.exe", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "roms/nes/launch.bat", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "roms/nes/launch.ps1", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "roms/nes/archive.zip", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "roms/nes/../../private/game.nes", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "C:\\private\\game.nes", BrowserPlayState.UnsupportedFormat)] [InlineData("nes", "/private/game.nes", BrowserPlayState.UnsupportedFormat)] public void NativeExecutableScriptAndNonAllowlistedContentFailClosed(string platform, string path, BrowserPlayState state) { var result = BrowserPlayPolicy.Evaluate(Candidate(platform, path, 64, true), true, true, DateTimeOffset.UnixEpoch); Assert.False(result.Available); Assert.Equal(state, result.State); } [Fact] public void WritableMissingAndOversizedSourcesFailClosed() { Assert.Equal(BrowserPlayState.Disabled, BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", 16, false), true, true, DateTimeOffset.UnixEpoch).State); Assert.Equal("fceumm", BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", 16, true), false, false, DateTimeOffset.UnixEpoch).Core); Assert.Equal(BrowserPlayState.MissingRom, BrowserPlayPolicy.Evaluate(null, true, true, DateTimeOffset.UnixEpoch).State); Assert.Equal(BrowserPlayState.UnsupportedFormat, BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", BrowserPlayPolicy.MaximumRomBytes + 1, true), true, true, DateTimeOffset.UnixEpoch).State); } [Fact] public void SafeNameCannotExposeAStoredPath() { Assert.Equal("game.nes", BrowserPlayPolicy.SafeFileName("roms/nes/../../private/game.nes")); Assert.Equal("game.nes", BrowserPlayPolicy.SafeFileName("roms\\nes\\game.nes")); } [Fact] public void FixtureQualifiedCoreClaimsVerifiedAutomaticRestore() { var result = BrowserPlayPolicy.Evaluate(Candidate("gba", "roms/gba/synthetic.gba", 64, true), true, true, DateTimeOffset.UnixEpoch); Assert.True(result.Available); Assert.Equal(BrowserPlayState.Available, result.State); Assert.Equal("mgba", result.Core); Assert.True(result.AutomaticRestore); } [Fact] public void N64ZipContainerProceedsOnlyToTheServiceLevelContentValidation() { var result = BrowserPlayPolicy.Evaluate(Candidate("n64", "roms/n64/game.zip", 1024, true), true, true, DateTimeOffset.UnixEpoch); Assert.True(result.Available); Assert.Equal("n64wasm", result.Core); } [Fact] public void Ps2UsesItsFixtureProvenDiscLimitWithoutClaimingStateRestore() { var belowLimit = BrowserPlayPolicy.Evaluate( Candidate("ps2", "roms/ps2/personal.iso", BrowserPlayPolicy.MaximumRomBytes + 1, true), true, true, DateTimeOffset.UnixEpoch); var aboveLimit = BrowserPlayPolicy.Evaluate( Candidate("ps2", "roms/ps2/personal.iso", 8L * 1024 * 1024 * 1024 + 1, true), true, true, DateTimeOffset.UnixEpoch); Assert.True(belowLimit.Available); Assert.Equal("playjs", belowLimit.Core); Assert.Equal("Ludarium Play!.js", belowLimit.Emulator); Assert.False(belowLimit.AutomaticRestore); Assert.Equal(BrowserPlayState.UnsupportedFormat, aboveLimit.State); } [Fact] public void EveryAdvertisedCoreHasAReleaseFixture() { var qualified = BrowserPlayPolicy.DescribePlatforms().Where(platform => platform.ReleaseQualified).ToArray(); Assert.Equal(13, qualified.Length); Assert.Contains(qualified, platform => platform.Platform == "nes" && platform.Core == "fceumm"); Assert.Contains(qualified, platform => platform.Platform == "snes" && platform.Core == "snes9x"); Assert.Contains(qualified, platform => platform.Platform == "gb" && platform.Core == "gambatte"); Assert.Contains(qualified, platform => platform.Platform == "gbc" && platform.Core == "gambatte"); Assert.Contains(qualified, platform => platform.Platform == "gba" && platform.Core == "mgba"); Assert.Contains(qualified, platform => platform.Platform == "nds" && platform.Core == "melonds"); Assert.Contains(qualified, platform => platform.Platform == "psp" && platform.Core == "ppsspp"); Assert.Contains(qualified, platform => platform.Platform == "psx" && platform.Core == "pcsx_rearmed"); Assert.Contains(qualified, platform => platform.Platform == "ps2" && platform.Core == "playjs" && platform.MaximumBytes == 8L * 1024 * 1024 * 1024); Assert.Contains(qualified, platform => platform.Platform == "n64" && platform.Core == "n64wasm" && platform.MaximumBytes == BrowserPlayPolicy.MaximumRomBytes); foreach (var platform in new[] { "genesis", "mastersystem", "gamegear" }) Assert.Contains(qualified, entry => entry.Platform == platform && entry.Core == "genesis_plus_gx"); } private static BrowserPlayCandidate Candidate(string platform, string path, long size, bool readOnly) => new(Guid.NewGuid(), Guid.NewGuid(), "Synthetic", platform, path, size, null, readOnly); [Fact] public void ACueSheetWinsOverTheTracksItDescribes() { var gameId = Guid.NewGuid(); var sheet = Candidate(gameId, "roms/psx/Game/Game.cue", 512); var track = Candidate(gameId, "roms/psx/Game/Game (Track 1).bin", 700_000_000); var audio = Candidate(gameId, "roms/psx/Game/Game (Track 2).bin", 40_000_000); // Without this the tracks make every multi-track disc look ambiguous and unplayable. Assert.Same(sheet, BrowserPlayPolicy.SelectCandidate([track, sheet, audio])); Assert.Same(track, BrowserPlayPolicy.SelectCandidate([track])); } [Fact] public void APlaylistWinsOverTheDiscsAndTracksItNames() { var gameId = Guid.NewGuid(); var playlist = Candidate(gameId, "roms/psx/Game/Game.m3u", 64); var firstDisc = Candidate(gameId, "roms/psx/Game/Game (Disc 1).cue", 512); var secondDisc = Candidate(gameId, "roms/psx/Game/Game (Disc 2).cue", 512); var track = Candidate(gameId, "roms/psx/Game/Game (Disc 1) (Track 1).bin", 700_000_000); // Descriptors outrank what they describe, most general first. Assert.Same(playlist, BrowserPlayPolicy.SelectCandidate([firstDisc, track, playlist, secondDisc])); Assert.Null(BrowserPlayPolicy.SelectCandidate([firstDisc, secondDisc, track])); } [Fact] public void AMultiDiscGameIsDeliverableButNotYetQualified() { var psx = BrowserPlayPolicy.GetPlatform("psx")!; Assert.True(psx.Accepts(".m3u")); Assert.False(psx.IsQualified(".m3u")); Assert.False(BrowserPlayPolicy.GetPlatform("nes")!.Accepts(".m3u")); } [Fact] public void TwoSheetsOrTwoDiscsStayUnresolvable() { var gameId = Guid.NewGuid(); Assert.Null(BrowserPlayPolicy.SelectCandidate([ Candidate(gameId, "roms/psx/Game/Disc 1.cue", 512), Candidate(gameId, "roms/psx/Game/Disc 2.cue", 512)])); Assert.Null(BrowserPlayPolicy.SelectCandidate([ Candidate(gameId, "roms/psx/Game/Disc 1.chd", 500_000_000), Candidate(gameId, "roms/psx/Game/Disc 2.chd", 500_000_000)])); Assert.Null(BrowserPlayPolicy.SelectCandidate([])); } [Fact] public void SegaPlatformsAreReleaseQualifiedByTheirDeterministicFixtures() { foreach (var platform in new[] { "genesis", "mastersystem", "gamegear" }) { var mapping = BrowserPlayPolicy.GetPlatform(platform); Assert.NotNull(mapping); Assert.Equal("genesis_plus_gx", mapping.Core); // Rule 10: each of these completed the live core gate on its own pinned fixture, which // booted genesis_plus_gx, captured a savestate and restored it into a relaunched session. Assert.True(mapping.ReleaseQualified); Assert.Contains("genesis_plus_gx", BrowserPlayPolicy.RequiredCores); } // The savestate half of that evidence is what lets a session restore without being asked. Assert.True(BrowserGameDataPolicy.SupportsAutomaticRestore("genesis_plus_gx")); } [Fact] public void EveryMappedCoreIsRequiredExceptLudariumsOwnPlayers() { Assert.DoesNotContain("playjs", BrowserPlayPolicy.RequiredCores); Assert.DoesNotContain("n64wasm", BrowserPlayPolicy.RequiredCores); foreach (var platform in BrowserPlayPolicy.DescribePlatforms()) if (platform.Core is not "playjs" and not "n64wasm") Assert.Contains(platform.Core, BrowserPlayPolicy.RequiredCores); } [Fact] public void ACueSetIsDeliverableForPlayStationButNotForPlayJs() { var psx = BrowserPlayPolicy.GetPlatform("psx")!; var ps2 = BrowserPlayPolicy.GetPlatform("ps2")!; // EmulatorJS mounts the streamed archive; Play!.js boots a raw disc image and cannot. Assert.True(psx.Accepts(".cue")); Assert.False(ps2.Accepts(".cue")); Assert.False(BrowserPlayPolicy.GetPlatform("nes")!.Accepts(".cue")); } [Fact] public void ADeliverableButUnqualifiedFormatFailsClosedWithAnExactReason() { var gameId = Guid.NewGuid(); var qualified = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.chd", 500_000_000), true, true, DateTimeOffset.UnixEpoch); var candidate = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.cue", 512), true, true, DateTimeOffset.UnixEpoch); var unknown = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.rar", 512), true, true, DateTimeOffset.UnixEpoch); Assert.True(qualified.Available); // Rule 10: an implemented delivery path is not a support claim until a fixture proves it. Assert.False(candidate.Available); Assert.Equal(BrowserPlayState.UnsupportedFormat, candidate.State); Assert.Contains("not release-qualified", candidate.Message, StringComparison.Ordinal); Assert.False(unknown.Available); Assert.Contains("not allowlisted", unknown.Message, StringComparison.Ordinal); } [Fact] public void OnlyTheFixtureProvenPlayStation2ContainerIsClaimedPlayable() { var ps2 = BrowserPlayPolicy.GetPlatform("ps2")!; // Play!.js range-reads a raw block device and boots it as an ISO. Only the ISO fixture has // completed the live gate, so a compressed container is deliverable, never claimed. Assert.True(ps2.IsQualified(".iso")); foreach (var candidate in new[] { ".cso", ".chd", ".isz", ".bin" }) { Assert.True(ps2.Accepts(candidate), candidate); Assert.False(ps2.IsQualified(candidate), candidate); Assert.False(BrowserPlayPolicy.Evaluate( Candidate(Guid.NewGuid(), $"roms/ps2/Game/Game{candidate}", 4_000_000_000), true, true, DateTimeOffset.UnixEpoch).Available, candidate); } Assert.True(BrowserPlayPolicy.Evaluate(Candidate(Guid.NewGuid(), "roms/ps2/Game/Game.iso", 4_000_000_000), true, true, DateTimeOffset.UnixEpoch).Available); } [Theory] [InlineData("snes", ".sfc", ".smc")] [InlineData("psx", ".chd", ".pbp")] [InlineData("psp", ".pbp", ".iso")] [InlineData("psp", ".pbp", ".cso")] [InlineData("genesis", ".md", ".gen")] [InlineData("genesis", ".md", ".smd")] [InlineData("genesis", ".md", ".bin")] public void ContainerClaimsStayBoundToTheExactLiveFixture(string platform, string qualifiedExtension, string candidateExtension) { var mapping = BrowserPlayPolicy.GetPlatform(platform)!; Assert.True(mapping.IsQualified(qualifiedExtension)); Assert.True(mapping.Accepts(candidateExtension)); Assert.False(mapping.IsQualified(candidateExtension)); var result = BrowserPlayPolicy.Evaluate( Candidate(Guid.NewGuid(), $"roms/{platform}/Fixture/Game{candidateExtension}", 64 * 1024), true, true, DateTimeOffset.UnixEpoch); Assert.False(result.Available); Assert.Equal(BrowserPlayState.UnsupportedFormat, result.State); Assert.Contains("not release-qualified", result.Message, StringComparison.Ordinal); } private static BrowserPlayCandidate Candidate(Guid gameId, string path, long size) => new(gameId, Guid.NewGuid(), "Fixture", path.Split('/')[1], path, size, null, true); }