mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
dffe037ac7
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
452 lines
18 KiB
C++
452 lines
18 KiB
C++
/* test_pgxp_session.cpp -- does the psx.enhancement.pgxp mod arm PGXP, and
|
|
* only for the session whose plan holds it? (docs/ENHANCEMENTS.md G1.11/G1.12)
|
|
*
|
|
* The mod's activation runs at session start; main.cpp's renderer setup runs
|
|
* after it and applies the [video] baseline. Before G1.11 the activation
|
|
* armed the corrections directly and the baseline switched them straight back
|
|
* off, so the mod did nothing. Now the activation records a request and the
|
|
* session arming (src/pgxp_session.cpp, the code main.cpp calls) takes it.
|
|
*
|
|
* This test links the real mod runtime and package manager, the real builtin
|
|
* plugin (src/mod_builtin_pgxp.c, registered by its constructor) and the real
|
|
* arming, stages the framework's own builtin manifest (and a title override
|
|
* of it with default_enabled = true, as a title that ships PGXP on by default
|
|
* does), and drives sessions in main.cpp's order:
|
|
*
|
|
* commit (offline) or mod_runtime_clear_for_netplay (netplay)
|
|
* reset_mod_owned_presentation() -> psx_pgxp_session_reset()
|
|
* mod_runtime_activate_plugins() -> builtin_pgxp_activate (if planned)
|
|
* renderer setup -> psx_pgxp_session_arm(...)
|
|
*
|
|
* The engine setters the arming calls are stubbed to record what it armed.
|
|
*/
|
|
#include "mod_runtime.h"
|
|
#include "mod_packages.h"
|
|
#include "mod_plugins.h"
|
|
#include "pgxp.h"
|
|
#include "pgxp_session.h"
|
|
#include "psx_lobby_client.h"
|
|
|
|
#include "cpu_state.h"
|
|
#include "gpu.h"
|
|
#include "gpu_hd_textures.h"
|
|
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/* POSIX setenv/unsetenv are missing on MinGW; an empty _putenv_s value unsets. */
|
|
static void test_setenv(const char *name, const char *value) {
|
|
#ifdef _WIN32
|
|
_putenv_s(name, value);
|
|
#else
|
|
setenv(name, value, 1);
|
|
#endif
|
|
}
|
|
static void test_unsetenv(const char *name) {
|
|
#ifdef _WIN32
|
|
_putenv_s(name, "");
|
|
#else
|
|
unsetenv(name);
|
|
#endif
|
|
}
|
|
|
|
#ifndef PSX_BUILTIN_PGXP_MANIFEST
|
|
#error "PSX_BUILTIN_PGXP_MANIFEST must name mods/builtin/.../manifest.toml"
|
|
#endif
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
static int g_failures = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \
|
|
#cond); \
|
|
g_failures++; \
|
|
} \
|
|
} while (0)
|
|
|
|
/* ---- what the arming armed (gte.cpp / gpu.c setters, stubbed) ------------ */
|
|
|
|
static int g_geometry = -1;
|
|
static int g_texture = -1;
|
|
extern "C" int psx_netplay_active(void) { return 0; }
|
|
/* No external HD pack is configured by the PGXP session fixture. */
|
|
extern "C" void gpu_hd_textures_shutdown(void) {}
|
|
extern "C" int gpu_hd_textures_configure(const char*, int, int, char*, size_t) { return 0; }
|
|
extern "C" int gpu_hd_textures_reload(char*, size_t) { return 0; }
|
|
extern "C" void gpu_hd_textures_set_dump_enabled(int) {}
|
|
extern "C" void gpu_hd_textures_get_diag(GpuHdTextureDiag* out) { *out = GpuHdTextureDiag{}; }
|
|
extern "C" void gte_geometry_correction_set(int enabled) { g_geometry = enabled; }
|
|
extern "C" void gpu_texture_correction_set(int enabled) { g_texture = enabled; }
|
|
/* pgxp.cpp's position-cache tier lives in gte.cpp. */
|
|
static bool g_cached_hud_vertex = false;
|
|
extern "C" int gte_geometry_correction_lookup(uint32_t, int32_t* x, int32_t* y) {
|
|
if (!g_cached_hud_vertex) return 0;
|
|
*x = (160 << 16) + 0x4000;
|
|
*y = (80 << 16) + 0x4000;
|
|
return 1;
|
|
}
|
|
|
|
/* The session harness has no GTE backend; pre-draw probes must link as a no-op. */
|
|
extern "C" int gte_geometry_correction_lookup_probe(uint32_t, int32_t*, int32_t*) {
|
|
return 0;
|
|
}
|
|
|
|
/* ---- the runtime surface mod_runtime.cpp links against ------------------- */
|
|
|
|
static std::array<uint8_t, 2 * 1024 * 1024> ram;
|
|
extern "C" uint8_t psx_read_byte(uint32_t a) { return ram[a & 0x1fffffu]; }
|
|
extern "C" void psx_write_byte(uint32_t a, uint8_t v) { ram[a & 0x1fffffu] = v; }
|
|
extern "C" uint16_t psx_read_half(uint32_t a) {
|
|
const uint32_t o = a & 0x1fffffu;
|
|
return (uint16_t)(ram[o] | ((uint16_t)ram[o + 1] << 8));
|
|
}
|
|
extern "C" void psx_write_half(uint32_t a, uint16_t v) {
|
|
const uint32_t o = a & 0x1fffffu;
|
|
ram[o] = (uint8_t)v;
|
|
ram[o + 1] = (uint8_t)(v >> 8);
|
|
}
|
|
/* Side-effect-free peek (memory.c); host-output observation reads. */
|
|
extern "C" int psx_peek_word(uint32_t, uint32_t* out) { *out = 0; return 0; }
|
|
extern "C" uint32_t psx_read_word(uint32_t a) {
|
|
const uint32_t o = a & 0x1fffffu;
|
|
return (uint32_t)ram[o] | ((uint32_t)ram[o + 1] << 8) |
|
|
((uint32_t)ram[o + 2] << 16) | ((uint32_t)ram[o + 3] << 24);
|
|
}
|
|
extern "C" void psx_write_word(uint32_t a, uint32_t v) {
|
|
const uint32_t o = a & 0x1fffffu;
|
|
for (int i = 0; i < 4; i++) ram[o + i] = (uint8_t)(v >> (8 * i));
|
|
}
|
|
extern "C" void psx_host_write_byte(uint32_t a, uint8_t v) { psx_write_byte(a, v); }
|
|
extern "C" void psx_host_write_half(uint32_t a, uint16_t v) { psx_write_half(a, v); }
|
|
extern "C" void psx_host_write_word(uint32_t a, uint32_t v) { psx_write_word(a, v); }
|
|
extern "C" uint32_t psx_mod_memory_alloc(uint32_t, uint32_t) { return 0; }
|
|
extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) { return 0; }
|
|
extern "C" int psx_ws_x_margin(void) { return 0; }
|
|
extern "C" void gpu_get_display_info(GpuDisplayInfo* out) { *out = GpuDisplayInfo{}; }
|
|
extern "C" void dirty_ram_mark_executable_range(uint32_t, uint32_t) {}
|
|
extern "C" int fntrace_is_game_started(void) { return 1; }
|
|
extern "C" void gpu_ws_tag_hud_primitive(uint32_t, int) {}
|
|
extern "C" void gpu_ws_tag_hud_prim_projected(uint32_t, int) {}
|
|
extern "C" void gpu_ws_set_gte_sxy_tracking(int) {}
|
|
extern "C" int gpu_ws_gte_sxy_recent(int32_t, int32_t) { return 0; }
|
|
extern "C" void gpu_ws_tag_world_primitive(uint32_t, int) {}
|
|
extern "C" void gpu_ws_set_adaptive_backdrop_preload(int) {}
|
|
extern "C" void psx_projection_reset_session(void) {}
|
|
extern "C" void gpu_ws_set_native_scene_predicate(int (*)(void)) {}
|
|
extern "C" int gpu_ws_configured_x_reveal(void) { return 0; }
|
|
extern "C" void gpu_ws_tag_hud_prim(uint32_t, int) {}
|
|
extern "C" void gpu_ws_tag_screen_mask_quad(uint32_t) {}
|
|
extern "C" void gpu_ws_tag_radial_screen_mask_quad(uint32_t, float) {}
|
|
extern "C" void gpu_ws_tag_feedback_rect(uint32_t) {}
|
|
/* mod_runtime.cpp's lobby netplay commit reads the negotiated match caps.
|
|
* This test drives netplay through mod_runtime_clear_for_netplay, so no
|
|
* lobby match is ever negotiated. */
|
|
static PsxLobbyMatchCaps no_match_caps;
|
|
extern "C" const PsxLobbyMatchCaps* psx_lobby_match_caps(void) { return &no_match_caps; }
|
|
|
|
/* ---- staging ------------------------------------------------------------- */
|
|
|
|
static void write_text(const fs::path& path, const std::string& text) {
|
|
fs::create_directories(path.parent_path());
|
|
std::ofstream out(path, std::ios::binary);
|
|
out << text;
|
|
}
|
|
|
|
static std::string read_text(const fs::path& path) {
|
|
std::ifstream in(path, std::ios::binary);
|
|
std::ostringstream ss;
|
|
ss << in.rdbuf();
|
|
return ss.str();
|
|
}
|
|
|
|
static std::string replace_once(std::string s, const std::string& from,
|
|
const std::string& to) {
|
|
const size_t at = s.find(from);
|
|
CHECK(at != std::string::npos);
|
|
if (at != std::string::npos) s.replace(at, from.size(), to);
|
|
return s;
|
|
}
|
|
|
|
/* A mods root holding the builtin manifest as `manifest` (verbatim, or a
|
|
* title's default-on override of it) and an optional state.toml. */
|
|
static fs::path stage(const char* name, const std::string& manifest,
|
|
const std::string& state) {
|
|
const fs::path root = fs::temp_directory_path() /
|
|
(std::string("psxrecomp-pgxp-session-") + name);
|
|
std::error_code ec;
|
|
fs::remove_all(root, ec);
|
|
write_text(root / "packages/psx.enhancement.pgxp/1.0.0/manifest.toml",
|
|
manifest);
|
|
if (!state.empty()) write_text(root / "state.toml", state);
|
|
std::vector<char> zeros(8 * 2352, 0);
|
|
write_text(root / "disc.bin", std::string(zeros.begin(), zeros.end()));
|
|
write_text(root / "disc.cue",
|
|
"FILE \"disc.bin\" BINARY\n"
|
|
" TRACK 01 MODE2/2352\n"
|
|
" INDEX 01 00:00:00\n");
|
|
return root;
|
|
}
|
|
|
|
static bool commit(const fs::path& root) {
|
|
std::string error;
|
|
if (!PSXRecompV4::mod_runtime_initialize(root, "SLUS-PGXPT", 0x80010000u,
|
|
{}, &error)) {
|
|
std::fprintf(stderr, "initialize: %s\n", error.c_str());
|
|
return false;
|
|
}
|
|
if (!PSXRecompV4::mod_runtime_commit(root / "disc.cue", &error)) {
|
|
std::fprintf(stderr, "commit: %s\n", error.c_str());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* ---- one session start, as main.cpp sequences it ------------------------- */
|
|
|
|
static PSXPgxpSessionConfig g_cfg;
|
|
|
|
static PSXPgxpSessionArm session(bool do_reset = true) {
|
|
if (do_reset) psx_pgxp_session_reset(); /* reset_mod_owned_presentation */
|
|
mod_runtime_activate_plugins();
|
|
g_geometry = g_texture = -1;
|
|
return psx_pgxp_session_arm(&g_cfg, nullptr);
|
|
}
|
|
|
|
static bool armed(const PSXPgxpSessionArm& a, int g, int t, int c, int cull) {
|
|
return a.geometry == g && a.texture == t && a.cpu_mode == c &&
|
|
a.culling == cull && g_geometry == g && g_texture == t &&
|
|
pgxp_cpu_mode() == c && pgxp_culling() == cull;
|
|
}
|
|
|
|
static void netplay_clear(void) {
|
|
std::string error;
|
|
CHECK(PSXRecompV4::mod_runtime_clear_for_netplay(&error));
|
|
}
|
|
|
|
int main(void) {
|
|
for (const char* v : {"PSX_GEOMETRY_CORRECTION", "PSX_PERSPECTIVE_TEXTURING",
|
|
"PSX_PGXP_CPU_MODE", "PSX_PGXP_CULLING"})
|
|
test_unsetenv(v);
|
|
g_cfg = PSXPgxpSessionConfig{};
|
|
/* No title tuning: session arming must choose the build's defaults,
|
|
* including resetting values left by a previous session/debug toggle. */
|
|
pgxp_set_tolerance(2.0f);
|
|
pgxp_set_position_fallback(0);
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
CHECK(pgxp_tolerance() < 0.0f && pgxp_position_fallback() == 0);
|
|
#else
|
|
CHECK(pgxp_tolerance() == 0.5f && pgxp_position_fallback() == 1);
|
|
#endif
|
|
CHECK(pgxp_preserve_projection() == 0);
|
|
/* Run those defaults through the real engine: a proven world vertex
|
|
* retains its 0.75px fraction, while a HUD coordinate sharing its integer
|
|
* position cannot borrow a different projection from the cache. */
|
|
pgxp_set_enabled(1);
|
|
const uint32_t packed = (80u << 16) | 160u;
|
|
const uint32_t addr = 0x80100000u;
|
|
const int32_t precise_x = (160 << 16) + 0xc000;
|
|
pgxp_gte_push_sxy(precise_x, 80 << 16, 100, packed);
|
|
psx_pgxp_cop2(nullptr, (0x3au << 26) | (14u << 16), packed, addr);
|
|
int32_t x, y;
|
|
uint16_t z;
|
|
const int world = pgxp_get_precise_vertex(addr, packed, 160, 80, &x, &y, &z);
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
CHECK(world == PGXP_SRC_DATAFLOW && x == precise_x && z == 100);
|
|
#else
|
|
CHECK(world == PGXP_SRC_NATIVE && x == (160 << 16) && z == 100);
|
|
#endif
|
|
g_cached_hud_vertex = true;
|
|
const int hud = pgxp_get_precise_vertex(0xffffffffu, packed, 160, 80, &x, &y, &z);
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
CHECK(hud == PGXP_SRC_NATIVE && x == (160 << 16) && z == 0);
|
|
#else
|
|
CHECK(hud == PGXP_SRC_FALLBACK && x == (160 << 16) + 0x4000 && z == 0);
|
|
#endif
|
|
g_cached_hud_vertex = false;
|
|
pgxp_set_enabled(0);
|
|
/* Each explicit compatibility override is independent of the other. */
|
|
g_cfg.tolerance = 0.25f;
|
|
g_cfg.tolerance_set = 1;
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
CHECK(pgxp_tolerance() == 0.25f);
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
CHECK(pgxp_position_fallback() == 0);
|
|
#else
|
|
CHECK(pgxp_position_fallback() == 1);
|
|
#endif
|
|
g_cfg.tolerance_set = 0;
|
|
g_cfg.position_fallback = 1;
|
|
g_cfg.position_fallback_set = 1;
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
CHECK(pgxp_tolerance() < 0.0f);
|
|
#else
|
|
CHECK(pgxp_tolerance() == 0.5f);
|
|
#endif
|
|
CHECK(pgxp_position_fallback() == 1);
|
|
/* Explicit compatibility tuning wins even in the hook flavor. */
|
|
g_cfg.tolerance = 0.5f;
|
|
g_cfg.tolerance_set = 1;
|
|
g_cfg.position_fallback = 1;
|
|
g_cfg.position_fallback_set = 1;
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
CHECK(pgxp_tolerance() == 0.5f && pgxp_position_fallback() == 1);
|
|
g_cfg.tolerance = -1.0f;
|
|
g_cfg.position_fallback = 0;
|
|
g_cfg.preserve_projection = 1;
|
|
|
|
const std::string builtin = read_text(PSX_BUILTIN_PGXP_MANIFEST);
|
|
CHECK(builtin.find("id = \"psx.enhancement.pgxp\"") != std::string::npos);
|
|
CHECK(builtin.find("default_enabled = false") != std::string::npos);
|
|
CHECK(builtin.find("id = \"culling\"") != std::string::npos);
|
|
/* A title that ships PGXP on by default overrides the builtin at the
|
|
* same id and version: the feature on, and here precise culling too. */
|
|
std::string title = replace_once(builtin, "default_enabled = false",
|
|
"default_enabled = true");
|
|
{
|
|
const size_t opt = title.find("id = \"culling\"");
|
|
const size_t def = title.find("default = \"false\"", opt);
|
|
CHECK(opt != std::string::npos && def != std::string::npos);
|
|
if (def != std::string::npos)
|
|
title.replace(def, std::strlen("default = \"false\""),
|
|
"default = \"true\"");
|
|
}
|
|
|
|
/* The builtin's default: nothing planned, the faithful floor, and the
|
|
* tuning keys are applied regardless (they are inert while off). */
|
|
CHECK(commit(stage("builtin", builtin, "")));
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
CHECK(pgxp_tolerance() < 0.0f && pgxp_position_fallback() == 0 &&
|
|
pgxp_preserve_projection() == 1);
|
|
|
|
/* A title's default-on override: the mod arms geometry, texture and its
|
|
* culling option at the first boot (the G1.11 bug left it all off). */
|
|
const fs::path title_root = stage("title", title, "");
|
|
CHECK(commit(title_root));
|
|
CHECK(armed(session(), 1, 1, 0, 1));
|
|
|
|
/* Netplay rematch: the plan is cleared, nothing activates, PGXP is off
|
|
* -- default-on packages included. */
|
|
netplay_clear();
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
/* ...even if the session-start reset were skipped: the offline
|
|
* session's arming took its request, so nothing is left to inherit. */
|
|
CHECK(commit(title_root));
|
|
CHECK(armed(session(), 1, 1, 0, 1));
|
|
netplay_clear();
|
|
CHECK(armed(session(false), 0, 0, 0, 0));
|
|
|
|
/* Offline rematch after netplay: on again. */
|
|
CHECK(commit(title_root));
|
|
CHECK(armed(session(), 1, 1, 0, 1));
|
|
|
|
/* A netplay session whose published plan carries the mod (content
|
|
* negotiation, mod_runtime_commit_for_netplay): geometry and texture as
|
|
* planned, precise culling never -- not even from the env override --
|
|
* because its guest-visible NCLIP reads host-only shadows that a rollback
|
|
* load drops on one peer only. */
|
|
g_cfg.netplay = 1;
|
|
CHECK(armed(session(), 1, 1, 0, 0));
|
|
test_setenv("PSX_PGXP_CULLING", "1");
|
|
CHECK(armed(session(), 1, 1, 0, 0));
|
|
test_unsetenv("PSX_PGXP_CULLING");
|
|
g_cfg.netplay = 0;
|
|
CHECK(armed(session(), 1, 1, 0, 1));
|
|
|
|
/* The player's off switch on the Mods page. */
|
|
const std::string disabled =
|
|
"format_version = 2\n"
|
|
"[[package]]\n"
|
|
"id = \"psx.enhancement.pgxp\"\n"
|
|
"version = \"1.0.0\"\n"
|
|
"[[feature]]\n"
|
|
"package_id = \"psx.enhancement.pgxp\"\n"
|
|
"id = \"pgxp\"\n"
|
|
"enabled = false\n";
|
|
CHECK(commit(stage("title-off", title, disabled)));
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
|
|
/* Options: culling off, CPU mode on. */
|
|
const std::string options =
|
|
"format_version = 2\n"
|
|
"[[package]]\n"
|
|
"id = \"psx.enhancement.pgxp\"\n"
|
|
"version = \"1.0.0\"\n"
|
|
"[[feature]]\n"
|
|
"package_id = \"psx.enhancement.pgxp\"\n"
|
|
"id = \"pgxp\"\n"
|
|
"enabled = true\n"
|
|
"[feature.values]\n"
|
|
"culling = false\n"
|
|
"cpu_mode = true\n";
|
|
CHECK(commit(stage("title-options", title, options)));
|
|
CHECK(armed(session(), 1, 1, 1, 0));
|
|
|
|
/* [video] baseline: a player's keys work without the mod and add to it;
|
|
* culling has no [video] key. */
|
|
CHECK(commit(stage("title-off2", title, disabled)));
|
|
g_cfg.video_texture = 1;
|
|
CHECK(armed(session(), 0, 1, 0, 0));
|
|
g_cfg.video_geometry = 1;
|
|
CHECK(armed(session(), 1, 1, 0, 0));
|
|
/* ...unless the title made the mod the one switch (pgxp_mod_only): then
|
|
* a stale settings.toml value cannot keep half of PGXP on. */
|
|
g_cfg.mod_only = 1;
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
CHECK(commit(title_root));
|
|
CHECK(armed(session(), 1, 1, 0, 1));
|
|
netplay_clear();
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
g_cfg.mod_only = 0;
|
|
g_cfg.video_texture = 0;
|
|
g_cfg.video_geometry = 0;
|
|
|
|
/* Validation env overrides win over both. Culling needs geometry. */
|
|
CHECK(commit(title_root));
|
|
test_setenv("PSX_GEOMETRY_CORRECTION", "0");
|
|
test_setenv("PSX_PERSPECTIVE_TEXTURING", "0");
|
|
CHECK(armed(session(), 0, 0, 0, 0));
|
|
test_setenv("PSX_PERSPECTIVE_TEXTURING", "1");
|
|
CHECK(armed(session(), 0, 1, 0, 0));
|
|
test_unsetenv("PSX_GEOMETRY_CORRECTION");
|
|
test_unsetenv("PSX_PERSPECTIVE_TEXTURING");
|
|
test_setenv("PSX_PGXP_CULLING", "0");
|
|
CHECK(armed(session(), 1, 1, 0, 0));
|
|
test_unsetenv("PSX_PGXP_CULLING");
|
|
CHECK(commit(stage("builtin2", builtin, "")));
|
|
test_setenv("PSX_GEOMETRY_CORRECTION", "1");
|
|
test_setenv("PSX_PGXP_CULLING", "1");
|
|
CHECK(armed(session(), 1, 0, 0, 1));
|
|
test_setenv("PSX_PERSPECTIVE_TEXTURING", ""); /* set but empty = off (Windows: unset, same result) */
|
|
CHECK(armed(session(), 1, 0, 0, 1));
|
|
test_unsetenv("PSX_GEOMETRY_CORRECTION");
|
|
test_unsetenv("PSX_PGXP_CULLING");
|
|
test_unsetenv("PSX_PERSPECTIVE_TEXTURING");
|
|
|
|
CHECK(psx_pgxp_session_env_flag(nullptr) == -1);
|
|
CHECK(psx_pgxp_session_env_flag("0") == 0);
|
|
CHECK(psx_pgxp_session_env_flag("") == 0);
|
|
CHECK(psx_pgxp_session_env_flag("1") == 1);
|
|
CHECK(psx_pgxp_session_env_flag("yes") == 1);
|
|
|
|
if (g_failures) {
|
|
std::fprintf(stderr, "test_pgxp_session: %d FAILURES\n", g_failures);
|
|
return 1;
|
|
}
|
|
std::printf("test_pgxp_session: all checks passed\n");
|
|
return 0;
|
|
}
|
|
|
|
/* render_pass.c: no sandboxed local view runs in this test. */
|
|
extern "C" int psx_mod_local_view_scope(void) { return 0; }
|