Files
Ludarium release export df869819ce
Public source validation / source (push) Successful in 2m16s
Publish Ludarium source
2026-09-03 02:08:58 +02:00

206 lines
9.1 KiB
C#

using System.Buffers.Binary;
using System.Text;
using Ludarium.Application;
using Ludarium.Domain;
namespace Ludarium.UnitTests;
/// <summary>
/// Platform identification for the container formats the players actually accept.
/// </summary>
/// <remarks>
/// A disc or cartridge that classifies as Unknown never becomes a launch candidate, so a format the
/// Dolphin policy or the browser matrix allows must also be identifiable here. These fixtures are
/// synthesized from the documented on-media headers, not from copyrighted dumps.
/// </remarks>
public sealed class PlatformIdentificationTests
{
[Fact]
public void GameCubeDiscsAreIdentifiedByTheirMagicWordNotTheirDirectory()
{
var classification = ArtifactAnalysis.Classify("library/unsorted/Disc.iso", GameCubeDisc());
Assert.Equal(MediaType.DiscImage, classification.MediaType);
Assert.Equal("gamecube", classification.Platform);
Assert.Equal(Confidence.Deterministic, classification.Confidence);
Assert.Contains(classification.Evidence, item => item.Kind == "signature" && item.Value == "GameCube disc");
Assert.Contains(classification.Evidence, item => item.Kind == "rom.gameCode" && item.Value == "GLZE");
Assert.Contains(classification.Evidence, item => item.Kind == "rom.region" && item.Value == "usa");
Assert.Contains(classification.Evidence, item => item.Kind == "rom.title" && item.Value == "LUDARIUM GAMECUBE FIXTURE");
}
[Fact]
public void WiiDiscsAreIdentifiedByTheirMagicWordNotTheirDirectory()
{
var classification = ArtifactAnalysis.Classify("library/unsorted/Disc.iso", WiiDisc());
Assert.Equal(MediaType.DiscImage, classification.MediaType);
Assert.Equal("wii", classification.Platform);
Assert.Equal(Confidence.Deterministic, classification.Confidence);
Assert.Contains(classification.Evidence, item => item.Kind == "signature" && item.Value == "Wii disc");
}
[Fact]
public void ADiscMagicOverridesAMisleadingPlatformDirectory()
{
var classification = ArtifactAnalysis.Classify("roms/gamecube/Disc.iso", WiiDisc(), "gamecube");
Assert.Equal("wii", classification.Platform);
Assert.Contains(classification.Evidence, item => item.Kind == "platform.conflict" &&
item.Value == "directory:gamecube;signature:wii");
}
[Theory]
[InlineData("roms/gamecube/Game.gcm", "gamecube")]
[InlineData("roms/gamecube/Game.gcz", "gamecube")]
[InlineData("roms/wii/Game.wia", "wii")]
[InlineData("roms/wii/Game.rvz", "wii")]
[InlineData("roms/wii/Game.wbfs", "wii")]
public void EveryDolphinContainerFormatClassifiesAsAPlayableDiscImage(string path, string hint)
{
var classification = ArtifactAnalysis.Classify(path, [], hint);
Assert.Equal(MediaType.DiscImage, classification.MediaType);
Assert.Equal(hint, classification.Platform);
Assert.True(classification.Supported);
}
[Fact]
public void EveryFormatTheDolphinPolicyAcceptsIsIdentifiable()
{
foreach (var extension in NativeRemotePlayerRegistry.Dolphin.Extensions)
{
var classification = ArtifactAnalysis.Classify($"roms/wii/Game{extension}", [], "wii");
Assert.True(classification.Supported,
$"{extension} is launchable in Dolphin but is not identified by the scanner.");
}
}
[Fact]
public void MegaDriveCartridgesAreIdentifiedAndSeparatedFromTheirVariants()
{
var genesis = ArtifactAnalysis.Classify("roms/unsorted/Game.md", SegaCartridge("SEGA MEGA DRIVE "));
var thirtyTwoX = ArtifactAnalysis.Classify("roms/unsorted/Game.32x", SegaCartridge("SEGA 32X "));
var segaCd = ArtifactAnalysis.Classify("roms/unsorted/Game.bin", SegaCartridge("SEGA SEGACD "));
Assert.Equal("genesis", genesis.Platform);
Assert.Equal(MediaType.Rom, genesis.MediaType);
Assert.Equal("sega32x", thirtyTwoX.Platform);
Assert.Equal("segacd", segaCd.Platform);
Assert.Contains(genesis.Evidence, item => item.Kind == "rom.internationalTitle" &&
item.Value == "LUDARIUM GENESIS FIXTURE");
}
[Theory]
[InlineData(0x40, "mastersystem")]
[InlineData(0x30, "mastersystem")]
[InlineData(0x50, "gamegear")]
[InlineData(0x60, "gamegear")]
[InlineData(0x70, "gamegear")]
public void TheEightBitSegaRegionNibbleSeparatesMasterSystemFromGameGear(int region, string expected)
{
var classification = ArtifactAnalysis.Classify("roms/unsorted/Game.sms", SegaEightBit(region));
Assert.Equal(expected, classification.Platform);
Assert.Equal(MediaType.Rom, classification.MediaType);
Assert.Equal(Confidence.Deterministic, classification.Confidence);
Assert.Contains(classification.Evidence, item => item.Kind == "rom.productCode" && item.Value == "50123");
Assert.Contains(classification.Evidence, item => item.Kind == "rom.revision" && item.Value == "1");
Assert.Contains(classification.Evidence, item => item.Kind == "rom.checksum" && item.Value == "1234");
}
[Theory]
[InlineData("megadrive", "genesis")]
[InlineData("mega-drive", "genesis")]
[InlineData("MD", "genesis")]
[InlineData("sms", "mastersystem")]
[InlineData("game-gear", "gamegear")]
public void HardwareNamedDirectoriesResolveToTheCanonicalPlatform(string directory, string expected)
{
var hint = ArtifactAnalysis.InferPlatformHint("library", "/mnt/games", $"roms/{directory}/Game.md");
Assert.Equal(expected, hint);
Assert.Equal(expected, ArtifactAnalysis.Classify($"roms/{directory}/Game.md", [], hint).Platform);
}
[Fact]
public void PlatformValuesWrittenByEarlierVersionsStillResolve()
{
Assert.Equal("genesis", ArtifactAnalysis.CanonicalPlatform("sega-genesis"));
Assert.Equal("mastersystem", ArtifactAnalysis.CanonicalPlatform("sms"));
Assert.Equal("nes", ArtifactAnalysis.CanonicalPlatform("nes"));
Assert.Null(ArtifactAnalysis.CanonicalPlatform(null));
}
[Fact]
public void ARawTrackIsOnlyADiscImageInsideADiscLibrary()
{
var disc = ArtifactAnalysis.Classify("roms/psx/Game/Game (Track 1).bin", [], "psx");
var installer = ArtifactAnalysis.Classify("PC/Some Game/data1.bin", [], "windows");
Assert.Equal(MediaType.DiscImage, disc.MediaType);
Assert.Equal("psx", disc.Platform);
// An installer payload must not be promoted to a disc image on the strength of its extension.
Assert.Equal(MediaType.Unknown, installer.MediaType);
}
[Theory]
[InlineData("roms/3ds/Game.3ds", "3ds")]
[InlineData("roms/3ds/Game.cia", "3ds")]
[InlineData("roms/wiiu/Game.wud", "wiiu")]
[InlineData("roms/psvita/Game.vpk", "psvita")]
public void FormatsForResearchedPlatformsAreIdentifiedEvenWithoutAPlayer(string path, string platform)
{
var classification = ArtifactAnalysis.Classify(path, []);
Assert.Equal(platform, classification.Platform);
Assert.True(classification.Supported);
}
private static byte[] GameCubeDisc()
{
var header = new byte[0x500];
Encoding.ASCII.GetBytes("GLZE").CopyTo(header, 0);
Encoding.ASCII.GetBytes("01").CopyTo(header, 4);
header[6] = 0;
header[7] = 1;
BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(0x1C), 0xC2339F3D);
Encoding.ASCII.GetBytes("LUDARIUM GAMECUBE FIXTURE").CopyTo(header, 0x20);
return header;
}
private static byte[] WiiDisc()
{
var header = new byte[0x500];
Encoding.ASCII.GetBytes("RLZP").CopyTo(header, 0);
Encoding.ASCII.GetBytes("01").CopyTo(header, 4);
BinaryPrimitives.WriteUInt32BigEndian(header.AsSpan(0x18), 0x5D1C9EA3);
Encoding.ASCII.GetBytes("LUDARIUM WII FIXTURE").CopyTo(header, 0x20);
return header;
}
private static byte[] SegaCartridge(string console)
{
var rom = new byte[0x200];
Encoding.ASCII.GetBytes(console.PadRight(16)[..16]).CopyTo(rom, 0x100);
Encoding.ASCII.GetBytes("(C)LUDARIUM 2026".PadRight(16)).CopyTo(rom, 0x110);
Encoding.ASCII.GetBytes("LUDARIUM GENESIS FIXTURE".PadRight(48)).CopyTo(rom, 0x120);
Encoding.ASCII.GetBytes("LUDARIUM GENESIS FIXTURE".PadRight(48)).CopyTo(rom, 0x150);
Encoding.ASCII.GetBytes("GM 00000000-00".PadRight(14)).CopyTo(rom, 0x180);
Encoding.ASCII.GetBytes("JUE").CopyTo(rom, 0x1F0);
return rom;
}
private static byte[] SegaEightBit(int region)
{
var rom = new byte[0x8000];
Encoding.ASCII.GetBytes("TMR SEGA").CopyTo(rom, 0x7FF0);
rom[0x7FFA] = 0x34; // checksum, low byte
rom[0x7FFB] = 0x12; // checksum, high byte
rom[0x7FFC] = 0x23; // product code, low BCD pair
rom[0x7FFD] = 0x01; // product code, high BCD pair
rom[0x7FFE] = 0x51; // top product digit and revision 1
rom[0x7FFF] = (byte)(region | 0x0C); // region nibble plus a 32 KiB size nibble
return rom;
}
}