This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<datafile>
|
||||
<header><name>Ludarium Synthetic Catalog</name><version>1</version></header>
|
||||
<game name="Synthetic Example">
|
||||
<description>Synthetic Example</description>
|
||||
<rom name="synthetic.nes" size="16" crc="cbf43926" sha1="a9993e364706816aba3e25717850c26c9cd0d89d" sha256="ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" />
|
||||
</game>
|
||||
</datafile>
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": 1074,
|
||||
"name": "Sonic the Hedgehog",
|
||||
"summary": "A blue hedgehog races through six zones.",
|
||||
"first_release_date": 708393600,
|
||||
"genres": [{ "name": "Platform" }],
|
||||
"involved_companies": [
|
||||
{ "developer": true, "publisher": false, "company": { "name": "Sonic Team" } },
|
||||
{ "developer": false, "publisher": true, "company": { "name": "Sega" } }
|
||||
],
|
||||
"franchises": [{ "name": "Sonic the Hedgehog" }],
|
||||
"cover": { "image_id": "co1v7r" },
|
||||
"platforms": [{ "slug": "genesis-slash-megadrive", "name": "Sega Mega Drive/Genesis" }]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"games": [
|
||||
{
|
||||
"game_id": 6225,
|
||||
"title": "Sonic the Hedgehog",
|
||||
"description": "Sonic must stop Dr. Robotnik.",
|
||||
"genres": [{ "genre_name": "Action" }],
|
||||
"platforms": [{ "platform_name": "Genesis" }],
|
||||
"sample_cover": "https://cdn.mobygames.com/covers/sonic.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"ID": 1,
|
||||
"Title": "Sonic the Hedgehog",
|
||||
"ConsoleName": "Mega Drive",
|
||||
"ImageBoxArt": "/Images/051872.png",
|
||||
"Publisher": "Sega",
|
||||
"Developer": "Sonic Team",
|
||||
"Genre": "Platform",
|
||||
"Released": "1991-06-23",
|
||||
"Achievements": {
|
||||
"9": {
|
||||
"ID": 9,
|
||||
"Title": "That Was Easy",
|
||||
"Description": "Complete Green Hill Zone Act 1.",
|
||||
"Points": 3,
|
||||
"BadgeName": "250336",
|
||||
"DateEarnedHardcore": "2026-01-02 12:30:00"
|
||||
},
|
||||
"10": {
|
||||
"ID": 10,
|
||||
"Title": "Marble Master",
|
||||
"Description": "Complete Marble Zone.",
|
||||
"Points": 5,
|
||||
"BadgeName": "250337"
|
||||
}
|
||||
},
|
||||
"NumAwardedToUser": 1,
|
||||
"NumAchievements": 2
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Data><jeu id="1"><noms><nom region="us">Sonic the Hedgehog</nom><nom region="jp">Sonic the Hedgehog</nom></noms><synopsis><synopsis langue="en">Sonic crosses South Island.</synopsis></synopsis><genres><genre><noms><nom langue="en">Platform</nom></noms></genre></genres><developpeur>Sonic Team</developpeur><editeur>Sega</editeur><joueurs>2</joueurs><dates><date region="us">19910623</date></dates><medias><media type="box-2D" region="us">https://www.screenscraper.fr/media/sonic.png</media></medias></jeu></Data>
|
||||
@@ -0,0 +1,3 @@
|
||||
# Synthetic fixtures
|
||||
|
||||
All files in this tree are generated text or minimal deterministic headers and contain no copyrighted game data. The executable-looking fixture uses `.exe.fixture`, so it cannot be selected as an executable by normal platform behavior. Tests construct binary headers in memory and never launch fixture content.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Browser-play fixture
|
||||
|
||||
`minimal-ines.base64` decodes to a deterministic 16-byte iNES header with one declared PRG bank and no copyrighted program data. Tests use it only to prove the allowlisted `.nes` format boundary; it is intentionally not a playable commercial game.
|
||||
|
||||
`playable-nrom.nes.gz.base64` is a gzip-compressed, deterministic 24,592-byte
|
||||
iNES/NROM-128 cartridge created specifically for Ludarium. Its original 6502
|
||||
program initializes the PPU, selects a blue backdrop and loops forever; the CHR
|
||||
bank is empty. It contains no third-party game code, graphics, audio or data.
|
||||
The decompressed SHA-256 is
|
||||
`6b87142fb0a7e125b13c5fcd7afa8b13066a44bc97150d24ade8bd2d0e5fd5e7`.
|
||||
The live browser-play gate materializes it only below isolated candidate
|
||||
appdata and mounts that directory read-only.
|
||||
|
||||
Browser-play integration tests create the decoded bytes in an isolated temporary read-only fixture root. Executable, script, traversal, oversized and unsupported requests are generated separately and must fail closed.
|
||||
|
||||
`generate-fixtures.mjs` deterministically creates original, minimal fixtures for
|
||||
SNES, GB, GBC, GBA, N64, NDS and PSP. They contain only headers and an architecture-specific
|
||||
infinite loop authored for Ludarium; there is no third-party game program, artwork, audio,
|
||||
firmware or BIOS. A fixture is not sufficient to enable a core by itself: the candidate gate
|
||||
must still prove a local core canvas, scoped Range delivery and non-empty savestate capture.
|
||||
The generator refuses output if any pinned SHA-256 changes. The N64 fixture additionally embeds
|
||||
libdragon's open-source IPL3 compatibility bootcode from commit
|
||||
`35f85a0797324a5ed0c723203e33ab3c1da94fdd` under the Unlicense. It remains a negative fixture:
|
||||
the bundled Mupen core did not complete the live gate, so Ludarium continues to fail closed for N64.
|
||||
|
||||
The positive N64 release gate downloads Mupen64Plus' own GPL-licensed 2008 demo ROM from the
|
||||
historical `mupen64plus-core` commit `57d08111ae9e95938c43ec160ac9b5a50f9da089`.
|
||||
The compressed SHA-256 is `9dbe0a7b4d2473060a1745c0424abad5278d40f2ff99995a4142bb07286183af`;
|
||||
the 1,048,576-byte `.v64` SHA-256 is
|
||||
`b5fe9d650a67091c97838386f5102ad94c79232240f9c5bcc72334097d76224c`.
|
||||
It is materialized only below isolated candidate appdata and proves N64Wasm WebGL startup,
|
||||
capability-scoped delivery, state capture, cancellation and evidence-bound restore.
|
||||
|
||||
The PSP fixture is an original open homebrew program in `psp/main.c`, built as `EBOOT.PBP`
|
||||
with the official `pspdev/pspdev` container pinned to
|
||||
`sha256:c1dd948b190e9141242cf321316eb2b54562b274c9be78e24a66fe6d30a5dc24`.
|
||||
`SOURCE_DATE_EPOCH=0` and gzip `-n -9` make the checked-in payload reproducible. The PBP is
|
||||
130,008 bytes with SHA-256 `2ff952ec933c67ee6911049d77201da572aa7241e3f2ee7ff935ed61646964f4`;
|
||||
the compressed file SHA-256 is `6e405203a2bab4a7ab3d705e0b937d2ecc8350489f051dbfb58d194e577b02f4`.
|
||||
It contains no Sony firmware, keys, SDK data or commercial content.
|
||||
@@ -0,0 +1,170 @@
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
// libdragon open-source IPL3 compatibility bootcode, pinned to commit
|
||||
// 35f85a0797324a5ed0c723203e33ab3c1da94fdd (Unlicense), SHA-256
|
||||
// f522db2e31a701f82597f399e76d55c9487760d015aff9d90176b463ae39a068.
|
||||
const libdragonIpl3 = gunzipSync(Buffer.from("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", "base64"));
|
||||
|
||||
const output = process.argv[2];
|
||||
if (!output) throw new Error("output directory is required");
|
||||
mkdirSync(output, { recursive: true });
|
||||
|
||||
const expected = new Map([
|
||||
["Ludarium SNES Fixture.sfc", "c5458603faf6e04e550d73c4724bb948fb75c5b54bf24280a006501c2ace52ed"],
|
||||
["Ludarium GB Fixture.gb", "7ed74ff088ce50d18ecd37847e3c0b81774e9b54e254149fc6502ed8ec084870"],
|
||||
["Ludarium GBC Fixture.gbc", "59111888ffff8c92c3f5bbd6bbe0f9182a7a53c634ec6b00d2ce8d9bd49f9f5a"],
|
||||
["Ludarium GBA Fixture.gba", "5a393a7b3a5b9beeae7f9a5e0495f1669e1855dcf6379a73bd97e7a974787813"],
|
||||
["Ludarium N64 Fixture.z64", "4f6e7fcb1962f02e59b9f896591254e9f1b68273e2179fc7a0c26e681500c326"],
|
||||
["Ludarium NDS Fixture.nds", "b797bfaa0e5e20cf2091f8268975bfa3cb820dd88c2e06134f0c9ce2fb1180cf"],
|
||||
["Ludarium PSP Fixture.pbp", "2ff952ec933c67ee6911049d77201da572aa7241e3f2ee7ff935ed61646964f4"],
|
||||
["Ludarium PSX Fixture.chd", "58e9754dd2c868d091cbd2401182b5026e436a7b199437fb7dfab039fccb89e7"],
|
||||
["Ludarium Genesis Fixture.md", "b07f1e19de6d517399ee398295e08d4b94f3cad8f5b0470b46ce37bdae0d508e"],
|
||||
["Ludarium Master System Fixture.sms", "0302cdf0f3e67488cf2ad11b655ea73d0c66e60b9e35153e0fe2b626cf358a0e"],
|
||||
["Ludarium Game Gear Fixture.gg", "f5fb90060122ec533a2200103b4d78c254f6bce72deb04ca11c065cb1d61af17"]
|
||||
]);
|
||||
const write = (name, bytes) => {
|
||||
const digest = createHash("sha256").update(bytes).digest("hex");
|
||||
if (digest !== expected.get(name)) throw new Error(`${name} checksum changed: ${digest}`);
|
||||
writeFileSync(join(output, name), bytes, { mode: 0o444 });
|
||||
};
|
||||
const ascii = (buffer, offset, value, length) => buffer.write(value.slice(0, length), offset, "ascii");
|
||||
const u32le = (buffer, offset, value) => buffer.writeUInt32LE(value >>> 0, offset);
|
||||
const u32be = (buffer, offset, value) => buffer.writeUInt32BE(value >>> 0, offset);
|
||||
const crc16 = bytes => {
|
||||
let crc = 0xffff;
|
||||
for (const byte of bytes) {
|
||||
crc ^= byte;
|
||||
for (let bit = 0; bit < 8; bit++) crc = (crc & 1) ? (crc >>> 1) ^ 0xa001 : crc >>> 1;
|
||||
}
|
||||
return crc;
|
||||
};
|
||||
|
||||
const pspFixture = gunzipSync(readFileSync(join(import.meta.dirname, "psp", "ludarium-psp-fixture.pbp.gz")));
|
||||
const psxFixture = gunzipSync(readFileSync(join(import.meta.dirname, "psx", "ludarium-psx-fixture.chd.gz")));
|
||||
|
||||
function gameBoy(color) {
|
||||
const rom = Buffer.alloc(32 * 1024, 0);
|
||||
rom.set([0xc3, 0x50, 0x01], 0x100); // JP $0150; no boot ROM or native code is required.
|
||||
rom.set([0xce, 0xed, 0x66, 0x66], 0x104); // Deterministic cartridge signature used by the analyzer.
|
||||
ascii(rom, 0x134, color ? "LUDARIUM GBC" : "LUDARIUM GB", 15);
|
||||
rom[0x143] = color ? 0x80 : 0x00;
|
||||
rom[0x147] = 0x00; // ROM-only cartridge.
|
||||
rom[0x148] = 0x00; // 32 KiB.
|
||||
rom[0x149] = 0x00;
|
||||
rom.set([0xf3, 0x31, 0xfe, 0xff, 0x18, 0xfe], 0x150); // DI; LD SP,$FFFE; JR forever.
|
||||
let checksum = 0;
|
||||
for (let index = 0x134; index <= 0x14c; index++) checksum = (checksum - rom[index] - 1) & 0xff;
|
||||
rom[0x14d] = checksum;
|
||||
return rom;
|
||||
}
|
||||
|
||||
function snes() {
|
||||
const rom = Buffer.alloc(32 * 1024, 0);
|
||||
rom.set([0x78, 0x18, 0xfb, 0xc2, 0x30, 0x80, 0xfe], 0); // Native-mode loop at $008000.
|
||||
ascii(rom, 0x7fc0, "LUDARIUM SNES FIXTURE", 21);
|
||||
rom[0x7fd5] = 0x20; // LoROM, slow ROM.
|
||||
rom[0x7fd6] = 0x00;
|
||||
rom[0x7fd7] = 0x05;
|
||||
rom[0x7fd9] = 0x01;
|
||||
for (const vector of [0x7fea, 0x7fec, 0x7fee, 0x7ffa, 0x7ffc, 0x7ffe]) rom.writeUInt16LE(0x8000, vector);
|
||||
let checksum = 0;
|
||||
for (const byte of rom) checksum = (checksum + byte) & 0xffff;
|
||||
rom.writeUInt16LE(checksum ^ 0xffff, 0x7fdc);
|
||||
rom.writeUInt16LE(checksum, 0x7fde);
|
||||
return rom;
|
||||
}
|
||||
|
||||
function gba() {
|
||||
const rom = Buffer.alloc(32 * 1024, 0);
|
||||
u32le(rom, 0, 0xeafffffe); // ARM B .
|
||||
rom.set([0x24, 0xff, 0xae, 0x51], 0x04); // Deterministic cartridge signature used by the analyzer.
|
||||
ascii(rom, 0xa0, "LUDARIUMGBA", 12);
|
||||
ascii(rom, 0xac, "LUDE", 4);
|
||||
ascii(rom, 0xb0, "00", 2);
|
||||
rom[0xb2] = 0x96;
|
||||
let checksum = 0;
|
||||
for (let index = 0xa0; index <= 0xbc; index++) checksum = (checksum - rom[index]) & 0xff;
|
||||
rom[0xbd] = (checksum - 0x19) & 0xff;
|
||||
return rom;
|
||||
}
|
||||
|
||||
function n64() {
|
||||
const rom = Buffer.alloc(1024 * 1024, 0);
|
||||
libdragonIpl3.copy(rom, 0);
|
||||
u32be(rom, 8, 0x80000400);
|
||||
u32be(rom, 0x10, 8);
|
||||
ascii(rom, 0x20, "LUDARIUM N64 FIXTURE", 20);
|
||||
ascii(rom, 0x3b, "LU", 2);
|
||||
rom[0x3e] = 0x45;
|
||||
u32be(rom, 0x1000, 0x1000ffff); // MIPS BEQ $zero,$zero,-1.
|
||||
u32be(rom, 0x1004, 0x00000000);
|
||||
return rom;
|
||||
}
|
||||
|
||||
function nds() {
|
||||
const rom = Buffer.alloc(8 * 1024, 0);
|
||||
ascii(rom, 0x00, "LUDARIUM NDS", 12);
|
||||
ascii(rom, 0x0c, "LUDE", 4);
|
||||
ascii(rom, 0x10, "00", 2);
|
||||
u32le(rom, 0x20, 0x200); u32le(rom, 0x24, 0x02000000); u32le(rom, 0x28, 0x02000000); u32le(rom, 0x2c, 4);
|
||||
u32le(rom, 0x30, 0x400); u32le(rom, 0x34, 0x02380000); u32le(rom, 0x38, 0x02380000); u32le(rom, 0x3c, 4);
|
||||
u32le(rom, 0x80, rom.length); u32le(rom, 0x84, 0x200);
|
||||
rom.set([0x24, 0xff, 0xae, 0x51], 0xc0); // Deterministic cartridge signature used by the analyzer.
|
||||
u32le(rom, 0x200, 0xeafffffe); u32le(rom, 0x400, 0xeafffffe);
|
||||
rom.writeUInt16LE(crc16(rom.subarray(0, 0x15e)), 0x15e);
|
||||
return rom;
|
||||
}
|
||||
|
||||
// Sega hardware. A Mega Drive cartridge is identified by its console name at $100 and boots from the
|
||||
// 68000 vector table at $0; an 8-bit Sega ROM is identified by the "TMR SEGA" header near the end of
|
||||
// its first bank, whose region nibble separates Master System from Game Gear.
|
||||
function megaDrive() {
|
||||
const rom = Buffer.alloc(64 * 1024, 0);
|
||||
u32be(rom, 0x00, 0x00fffe00); // Initial stack pointer.
|
||||
u32be(rom, 0x04, 0x00000200); // Reset vector -> $200.
|
||||
for (let vector = 0x08; vector < 0x100; vector += 4) u32be(rom, vector, 0x00000200);
|
||||
ascii(rom, 0x100, "SEGA MEGA DRIVE ", 16);
|
||||
ascii(rom, 0x110, "(C)LUDARIUM 2026", 16);
|
||||
ascii(rom, 0x120, "LUDARIUM GENESIS FIXTURE".padEnd(48), 48);
|
||||
ascii(rom, 0x150, "LUDARIUM GENESIS FIXTURE".padEnd(48), 48);
|
||||
ascii(rom, 0x180, "GM 00000000-00", 14);
|
||||
u32be(rom, 0x1a0, 0x00000000);
|
||||
u32be(rom, 0x1a4, rom.length - 1);
|
||||
u32be(rom, 0x1a8, 0x00ff0000);
|
||||
u32be(rom, 0x1ac, 0x00ffffff);
|
||||
ascii(rom, 0x1f0, "JUE", 3);
|
||||
rom.writeUInt16BE(0x60fe, 0x200); // BRA.S * — a deterministic idle loop.
|
||||
let checksum = 0;
|
||||
for (let index = 0x200; index < rom.length; index += 2) checksum = (checksum + rom.readUInt16BE(index)) & 0xffff;
|
||||
rom.writeUInt16BE(checksum, 0x18e);
|
||||
return rom;
|
||||
}
|
||||
|
||||
function segaEightBit(gameGear) {
|
||||
const rom = Buffer.alloc(32 * 1024, 0);
|
||||
rom.set([0xf3, 0xed, 0x56, 0x18, 0xfe], 0x00); // DI; IM 1; JR * — a deterministic idle loop.
|
||||
ascii(rom, 0x7ff0, "TMR SEGA", 8);
|
||||
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] = (gameGear ? 0x60 : 0x40) | 0x0c; // Region nibble plus the 32 KiB size nibble.
|
||||
let checksum = 0;
|
||||
for (let index = 0; index < 0x7ff0; index++) checksum = (checksum + rom[index]) & 0xffff;
|
||||
rom.writeUInt16LE(checksum, 0x7ffa);
|
||||
return rom;
|
||||
}
|
||||
|
||||
write("Ludarium SNES Fixture.sfc", snes());
|
||||
write("Ludarium GB Fixture.gb", gameBoy(false));
|
||||
write("Ludarium GBC Fixture.gbc", gameBoy(true));
|
||||
write("Ludarium GBA Fixture.gba", gba());
|
||||
write("Ludarium N64 Fixture.z64", n64());
|
||||
write("Ludarium NDS Fixture.nds", nds());
|
||||
write("Ludarium Genesis Fixture.md", megaDrive());
|
||||
write("Ludarium Master System Fixture.sms", segaEightBit(false));
|
||||
write("Ludarium Game Gear Fixture.gg", segaEightBit(true));
|
||||
write("Ludarium PSP Fixture.pbp", pspFixture);
|
||||
write("Ludarium PSX Fixture.chd", psxFixture);
|
||||
@@ -0,0 +1 @@
|
||||
TkVTGgEAAAAAAAAAAAAAAA==
|
||||
@@ -0,0 +1 @@
|
||||
H4sIAAAAAAACCu3QoQ0CQRRF0T8IsOtIUINH4hmDIxiaoYmZApDrtospgxLoAQFZBBVgSM4RN0+/8/GySSm+yvU+lrYu4+v2aJFbym0ou0Uenp9Mh7rMU8zZ1tW8IteUT/seAAAAAAAAAAAAAAAAAAAAwA/01KO7AQAAAAAAAAD+3xuAOQilEGAAAA==
|
||||
@@ -0,0 +1,12 @@
|
||||
EE_BIN = ludarium.elf
|
||||
EE_OBJS = main.o
|
||||
EE_LIBS = -lpacket -ldma -lgraph -ldraw -lc
|
||||
|
||||
all: $(EE_BIN)
|
||||
$(EE_STRIP) --strip-all $(EE_BIN)
|
||||
|
||||
clean:
|
||||
rm -f $(EE_BIN) $(EE_OBJS)
|
||||
|
||||
include $(PS2SDK)/samples/Makefile.pref
|
||||
include $(PS2SDK)/samples/Makefile.eeglobal
|
||||
@@ -0,0 +1,3 @@
|
||||
BOOT2 = cdrom0:\LUDARIUM.ELF;1
|
||||
VER = 1.00
|
||||
VMODE = NTSC
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <kernel.h>
|
||||
#include <dma.h>
|
||||
#include <draw.h>
|
||||
#include <graph.h>
|
||||
#include <gs_psm.h>
|
||||
#include <packet.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
framebuffer_t frame = {
|
||||
.address = 0,
|
||||
.width = 640,
|
||||
.height = 448,
|
||||
.psm = GS_PSM_32,
|
||||
.mask = 0
|
||||
};
|
||||
zbuffer_t depth = { .enable = 0, .address = 0, .zsm = 0, .mask = 0 };
|
||||
packet_t *packet = packet_init(32, PACKET_NORMAL);
|
||||
|
||||
frame.address = graph_vram_allocate(frame.width, frame.height, frame.psm, GRAPH_ALIGN_PAGE);
|
||||
dma_channel_initialize(DMA_CHANNEL_GIF, NULL, 0);
|
||||
dma_channel_fast_waits(DMA_CHANNEL_GIF);
|
||||
graph_initialize(frame.address, frame.width, frame.height, frame.psm, 0, 0);
|
||||
|
||||
qword_t *command = draw_setup_environment(packet->data, 0, &frame, &depth);
|
||||
command = draw_clear(command, 0, 0, 0, frame.width, frame.height, 18, 42, 96);
|
||||
command = draw_finish(command);
|
||||
dma_channel_send_normal(DMA_CHANNEL_GIF, packet->data, command - packet->data, 0, 0);
|
||||
draw_wait_finish();
|
||||
|
||||
for (;;) graph_wait_vsync();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
TARGET = ludarium-fixture
|
||||
OBJS = main.o
|
||||
|
||||
CFLAGS = -O2 -G0 -Wall -Wextra
|
||||
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
|
||||
ASFLAGS = $(CFLAGS)
|
||||
LIBS = -lpspdebug
|
||||
|
||||
EXTRA_TARGETS = EBOOT.PBP
|
||||
PSP_EBOOT_TITLE = Ludarium PSP Fixture
|
||||
|
||||
PSPSDK = $(shell psp-config --pspsdk-path)
|
||||
include $(PSPSDK)/lib/build.mak
|
||||
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
#include <pspkernel.h>
|
||||
#include <pspdebug.h>
|
||||
|
||||
PSP_MODULE_INFO("LudariumFixture", PSP_MODULE_USER, 1, 0);
|
||||
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
pspDebugScreenInit();
|
||||
pspDebugScreenSetBackColor(0x00302010);
|
||||
pspDebugScreenSetTextColor(0x00ffffff);
|
||||
pspDebugScreenClear();
|
||||
pspDebugScreenSetXY(8, 10);
|
||||
pspDebugScreenPrintf("Ludarium PSP release fixture\n");
|
||||
pspDebugScreenSetXY(8, 12);
|
||||
pspDebugScreenPrintf("Original open homebrew - no firmware or game data");
|
||||
|
||||
for (;;)
|
||||
sceKernelDelayThread(1000000);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(LudariumPsxFixture LANGUAGES C ASM)
|
||||
|
||||
psn00bsdk_add_executable(ludarium GPREL main.c)
|
||||
psn00bsdk_add_cd_image(fixture fixture iso.xml DEPENDS ludarium system.cnf)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": { "major": 3, "minor": 21, "patch": 0 },
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "default",
|
||||
"generator": "Ninja",
|
||||
"toolchainFile": "$env{PSN00BSDK_LIBS}/cmake/sdk.cmake",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"PSN00BSDK_TC": "",
|
||||
"PSN00BSDK_TARGET": "mipsel-none-elf"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# Original PSX browser-play fixture
|
||||
|
||||
This fixture is original Ludarium test code. It initializes the PlayStation display to a
|
||||
solid blue background and waits for vertical blank forever. It contains no Sony BIOS,
|
||||
license sector, SDK binary, retail executable or game asset.
|
||||
|
||||
Build provenance:
|
||||
|
||||
- PSn00bSDK `v0.24`, commit `06e65bea3a778b2dae5af77a7935ae3868ddd4d3`;
|
||||
- official `PSn00bSDK-0.24-Linux.zip`, SHA-256
|
||||
`5ada7c9478d795b22bde96ee23d6869290c14a740bcde3d57c01c342a26ded35`;
|
||||
- official `gcc-mipsel-none-elf-12.3.0-linux.zip`, SHA-256
|
||||
`228f031a25cf2687d8845fd1421f625bafc211fa27da428e458e80d030a726f8`;
|
||||
- PSn00bSDK `mkpsxiso`, followed by MAME `chdman` 0.264;
|
||||
- final 15,411-byte CHD SHA-256
|
||||
`58e9754dd2c868d091cbd2401182b5026e436a7b199437fb7dfab039fccb89e7`;
|
||||
- checked-in gzip SHA-256
|
||||
`7b1af8fcfcaf052d40fac4696710b1f8b17bc3d6d32f36abd04e4c564d2d0f9f`.
|
||||
|
||||
`generate-fixtures.mjs` inflates the checked-in payload and refuses to emit it when the CHD
|
||||
checksum differs. The live gate then verifies the exact 15,411-byte artifact via scoped
|
||||
Range delivery before starting the actual pinned `pcsx_rearmed` core.
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<iso_project>
|
||||
<track type="data">
|
||||
<identifiers system="PLAYSTATION" volume="LUDARIUM_FIXTURE"
|
||||
volume_set="LUDARIUM_FIXTURE" publisher="LUDARIUM"
|
||||
data_preparer="PSN00BSDK" application="PLAYSTATION" copyright="README.TXT;1" />
|
||||
<directory_tree>
|
||||
<file name="SYSTEM.CNF" type="data" source="${PROJECT_SOURCE_DIR}/system.cnf" />
|
||||
<file name="LUDARIUM.EXE" type="data" source="ludarium.exe" />
|
||||
<dummy sectors="1024" />
|
||||
</directory_tree>
|
||||
</track>
|
||||
</iso_project>
|
||||
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
#include <psxgpu.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
DISPENV display;
|
||||
DRAWENV draw;
|
||||
|
||||
ResetGraph(0);
|
||||
SetDefDispEnv(&display, 0, 0, 320, 240);
|
||||
SetDefDrawEnv(&draw, 0, 0, 320, 240);
|
||||
setRGB0(&draw, 18, 42, 96);
|
||||
draw.isbg = 1;
|
||||
PutDispEnv(&display);
|
||||
PutDrawEnv(&draw);
|
||||
SetDispMask(1);
|
||||
|
||||
for (;;)
|
||||
VSync(0);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
BOOT=cdrom:\LUDARIUM.EXE;1
|
||||
TCB=4
|
||||
EVENT=10
|
||||
STACK=801FFFF0
|
||||
@@ -0,0 +1 @@
|
||||
LUDARIUM SYNTHETIC TRACK — NOT GAME CONTENT
|
||||
@@ -0,0 +1,3 @@
|
||||
FILE "game (Track 01).bin" BINARY
|
||||
TRACK 01 MODE1/2352
|
||||
INDEX 01 00:00:00
|
||||
@@ -0,0 +1,3 @@
|
||||
FILE "absent.bin" BINARY
|
||||
TRACK 01 MODE1/2352
|
||||
INDEX 01 00:00:00
|
||||
@@ -0,0 +1 @@
|
||||
FILE "../../outside.bin" BINARY
|
||||
@@ -0,0 +1 @@
|
||||
REM SYNTHETIC DISC 1
|
||||
@@ -0,0 +1 @@
|
||||
REM SYNTHETIC DISC 2
|
||||
@@ -0,0 +1,3 @@
|
||||
#EXTM3U
|
||||
Disc 1.cue
|
||||
Disc 2.cue
|
||||
@@ -0,0 +1 @@
|
||||
LUDARIUM SYNTHETIC PAYLOAD 1
|
||||
@@ -0,0 +1 @@
|
||||
LUDARIUM SYNTHETIC PAYLOAD 2
|
||||
@@ -0,0 +1 @@
|
||||
MZLUDARIUM SYNTHETIC INVALID PE FIXTURE — NEVER EXECUTE
|
||||
@@ -0,0 +1 @@
|
||||
MZ — deterministic inert text fixture; extension intentionally prevents execution.
|
||||
Reference in New Issue
Block a user