Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ docs/PLAN.md the Jedi Knight plan and history
# (components/mgs/mgs_reversing/port/extract_disc.py)
tpm/ # The Phantom Menace, USA Rev 1: the .bin of a bin/cue rip
Star Wars - Episode I - The Phantom Menace (USA) (Rev 1).bin
saves/ # save states and their screenshots
saves/ # save states and their screenshots; the PlayStation
# games' memory cards (tpm_card1.mcd, mgs_card1.mcd:
# raw 128 KB images, as PC emulators use)
```

Example `metadata.csv` (one line per game: rom path, boxart, name; the
Expand Down
6 changes: 5 additions & 1 deletion components/mgs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ mgs/STAGE.DIR,boxart/mgs.jpg,Metal Gear Solid
every thread's stack and context), keyed on the memory layout so a
rebuild that leaves the game code in place keeps old states loadable.
- Pause, reset, relaunch and quit in place.
- Not done: memory-card game saves (use states), full-motion video (MDEC,
- Memory card: the game's own saves (Mei Ling's codec frequency, 140.85;
the title's load) go to `/sdcard/saves/mgs_card1.mcd`, a raw 128 KB card
image as PC emulators use, made the first time the game looks. Unlike
save states they survive any rebuild.
- Not done: full-motion video (MDEC,
movies are skipped). 56 of the 96 stages have decompiled actors upstream.

## Measuring
Expand Down
4 changes: 4 additions & 0 deletions components/mgs/include/mgs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ struct Config {
/// Directory holding the disc files: STAGE.DIR, RADIO.DAT, FACE.DAT, ...
/// (what port/extract_disc.py produces), e.g. "/sdcard/mgs".
std::string data_dir;
/// The memory card in port 1, a 128 KB card image file (the format PC
/// emulators use): the game's own saves go there. Made, formatted, the
/// first time the game looks. Empty: no card.
std::string memory_card;
};

/// Start the game. Returns false if the data is missing or the engine could
Expand Down
2 changes: 1 addition & 1 deletion components/mgs/mgs_reversing
2 changes: 1 addition & 1 deletion components/mgs/psyz
2 changes: 2 additions & 0 deletions components/mgs/src/mgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <vector>

#include "../mgs_reversing/port/mgs_snapshot.h"
#include <psyz/mcd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

Expand Down Expand Up @@ -323,6 +324,7 @@ bool init(const Config &config) {
// the directory must be called MGS (FAT is case-insensitive about it).
const auto root = std::filesystem::path(config.data_dir).parent_path().string();
Mgs_SetDataRoot(root.c_str());
Mcd_SetPath(0, config.memory_card.c_str()); // the "bu00:" device (psyz/mcd.h)
mgs_platform_init(config.data_dir.c_str());
// the frame buffers the scanout converts VRAM into (the S3 port's app_main
// does this; here it is the cart's job)
Expand Down
3 changes: 2 additions & 1 deletion components/tpm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ if(TPM_ENGINE_READY)
set(rt ${TPM_DECOMP}/recomp/runtime)
file(GLOB rt_srcs ${rt}/*.c)
file(GLOB gen_srcs ${TPM_GEN}/recomp_*.c)
set(psyz_srcs ${TPM_PSYZ}/psyz/src/psyz/psyz_spu.c) # (the GTE is the runtime's own: rt_gte.c)
# (the GTE is the runtime's own: rt_gte.c); psyz_mcd.c: the memory card's image file
set(psyz_srcs ${TPM_PSYZ}/psyz/src/psyz/psyz_spu.c ${TPM_PSYZ}/psyz/src/psyz/psyz_mcd.c)
add_library(tpm_rt STATIC ${gen_srcs} ${rt_srcs} ${psyz_srcs})
idf_build_get_property(config_dir CONFIG_DIR)
target_include_directories(tpm_rt PRIVATE
Expand Down
13 changes: 9 additions & 4 deletions components/tpm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,20 @@ menu (3.6 MB each; save 1.5 s, load 0.6 s).
- Pads: the HAL's merged gamepad, face buttons by position (the HAL's B is
south): B = cross, A = circle, Y = square, X = triangle, L/R = L1/R1.
- Movies play at full speed (15 fps, the game thread ~80% busy) with their
XA audio when the overlay was recompiled (`CONFIG_TPM_DISC_PATH`). There is
no memory card (the game says so and carries on).
XA audio when the overlay was recompiled (`CONFIG_TPM_DISC_PATH`).
- Memory card: the game's own saves (pause menu: Save Game; title: Load
Game) go to `/sdcard/saves/tpm_card1.mcd`, a raw 128 KB card image as PC
emulators use (DuckStation, ePSXe, ...), made the first time the game
looks. Unlike save states they load into any firmware build.
- Frameskip (`CONFIG_TPM_FRAMESKIP`, default 2): when the game is ahead of
the GPU, the worker leaves up to that many frames in a row undrawn, so
the game keeps its pace instead of running in slow motion.
- The pause menu pauses the game at its next loop back-edge. Save states
hold guest RAM, VRAM, the SPU, the CPU / GTE / hardware state and the
runtime's live statics; without enough free PSRAM for the game's arena
(after another big cart) the game still runs, without save states.
runtime's live statics, and load only into the firmware build that wrote
them (the memory card is for keeping progress across updates); without
enough free PSRAM for the game's arena (after another big cart) the game
still runs, without save states.

Where the time goes (in the level): both the game thread and the GPU worker
are ~95% busy. The game thread is the recompiled code (with g_ram pinned in
Expand Down
4 changes: 4 additions & 0 deletions components/tpm/include/tpm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ struct Config {
/// Directory holding the disc's files: SLUS_008.84 and FILE.000 (FILE.001
/// .. FILE.003 later, for streams and movies), e.g. "/sdcard/tpm".
std::string data_dir;
/// The memory card in port 1, a 128 KB card image file (the format PC
/// emulators use): the game's own saves go there. Made, formatted, the
/// first time the game looks. Empty: no card.
std::string memory_card;
};

/// Open the data. Returns false if it is missing or not the USA Rev 1 disc.
Expand Down
2 changes: 1 addition & 1 deletion components/tpm/src/tpm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ bool show(size_t index) {
bool init(const Config &config) {
config_ = config;
#if defined(TPM_HAVE_ENGINE)
if (engine::init(config.data_dir)) {
if (engine::init(config.data_dir, config.memory_card)) {
engine_ = true;
running_ = true;
return true;
Expand Down
10 changes: 9 additions & 1 deletion components/tpm/src/tpm_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ constexpr uint32_t AUDIO_RATE = 44100;

FILE *g_disc{nullptr};
uint32_t g_disc_sectors{0};
std::string g_memory_card; // the card image in port 1 (plat_memcard_path)
std::vector<uint8_t> g_exe;

TaskHandle_t g_game_task{nullptr};
Expand Down Expand Up @@ -810,7 +811,8 @@ std::string find_disc(const std::string &dir) {

} // namespace

bool init(const std::string &data_dir) {
bool init(const std::string &data_dir, const std::string &memory_card) {
g_memory_card = memory_card;
const auto disc = find_disc(data_dir);
if (disc.empty()) {
logger.error("no disc image (.bin, raw 2352-byte sectors) in {}", data_dir);
Expand Down Expand Up @@ -1482,6 +1484,12 @@ static void __attribute__((noinline)) snap_saved_wait(void) {
}
}

// The game's own saves (libmcrd, tpm-decomp's rt_hle.c): a card image file.
const char *plat_memcard_path(int port) {
using namespace tpm::engine;
return port == 0 && !g_memory_card.empty() ? g_memory_card.c_str() : nullptr;
}

void plat_pause_wait(void) {
using namespace tpm::engine;
const int req = g_snap_req.load();
Expand Down
6 changes: 4 additions & 2 deletions components/tpm/src/tpm_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
namespace tpm::engine {

/// Open the disc image in data_dir (a .bin of raw 2352-byte sectors) and
/// start the game. Returns false if there is no usable image.
bool init(const std::string &data_dir);
/// start the game. Returns false if there is no usable image. memory_card:
/// the image file of the card in port 1 (made the first time the game looks;
/// empty: no card).
bool init(const std::string &data_dir, const std::string &memory_card);
void deinit();
bool running();
void pause();
Expand Down
10 changes: 10 additions & 0 deletions main/cart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ class Cart {
return "";
}

/// The image file of a PlayStation game's memory card (the raw 128 KB
/// format PC emulators read), named after the game's data directory
/// (saves/tpm_card1.mcd): the game's own saves, which unlike save states
/// do not depend on the firmware build.
std::string get_memory_card_path(int card = 1) const {
namespace fs = std::filesystem;
const auto game = fs::path(get_rom_filename()).parent_path().filename().string();
return savedir_ + "/" + game + fmt::format("_card{}.mcd", card);
}

std::string get_paused_image_path() const { return savedir_ + "/paused" + get_screenshot_extension(); }

std::string get_screenshot_path(bool bypass_exist_check = false) const {
Expand Down
2 changes: 1 addition & 1 deletion main/mgs_cart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class MgsCart : public Cart {
handle_video_setting();
#if defined(ENABLE_MGS)
const auto data_dir = std::filesystem::path(get_rom_filename()).parent_path().string();
if (!mgs::init({.data_dir = data_dir})) {
if (!mgs::init({.data_dir = data_dir, .memory_card = get_memory_card_path()})) {
logger_.error("could not start Metal Gear Solid");
running_ = false;
}
Expand Down
2 changes: 1 addition & 1 deletion main/tpm_cart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TpmCart : public Cart {
handle_video_setting();
#if defined(ENABLE_TPM)
const auto data_dir = std::filesystem::path(get_rom_filename()).parent_path().string();
if (!tpm::init({.data_dir = data_dir})) {
if (!tpm::init({.data_dir = data_dir, .memory_card = get_memory_card_path()})) {
logger_.error("could not start The Phantom Menace");
running_ = false;
}
Expand Down
Loading