mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
fe6bd20a0e
Adds a general, opt-in language selector to the RmlUi launcher, driven entirely
by game.toml [localization]:
[localization]
default = "en"
languages = [ {code="en", label="English"}, {code="off", label="Default"} ]
- config_loader: parse [localization].default + languages[] into RuntimeConfig;
add UserSettings.language with settings.toml [localization] parse + write.
- launcher: a "LOCALIZATION" panel in the Settings view (cycle toggle, matching
the Renderer/Screen-model idiom) shown only when the game declares languages.
The chosen code persists to settings.toml and rides UserSettings.
- text_xlate: new text_xlate_set_language() re-applies the language after the
launcher (init runs before it); "off"/"jp"/"" disable APPLY (native game).
- main: seed/read-back the language through the launcher and re-apply on every
boot path (so a settings.toml choice sticks even when the launcher is skipped).
- Settings view laid out in two columns (VIDEO | LOCALIZATION+AUDIO+CONTROLLER)
with SYSTEM full-width below, so it uses the horizontal space and fits without
scrolling; #root gains overflow-y:auto as a safety net.
Only games that declare [localization] get the menu (Tsumu today); byte-identical
for every other title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
5.4 KiB
C
97 lines
5.4 KiB
C
/* text_xlate.h — on-the-fly string translation / localization (framework).
|
|
*
|
|
* A reusable psxrecomp feature: capture the game's source strings from guest
|
|
* memory and substitute translated bytes on the fly so a JP-only title can be
|
|
* played in the target language. See docs/STRING_TRANSLATION.md for the design.
|
|
*
|
|
* Mechanism (all always-on, hung off the psx_dispatch chokepoint):
|
|
* - CAPTURE: on every dispatch, scan the arg registers for pointers to source
|
|
* text records (encoding-profile validated). Every distinct record is hashed
|
|
* (FNV-1a64) and recorded into an in-memory inventory ring queryable over the
|
|
* TCP debug server (the "enumerate every string" authoring source). Never
|
|
* arm-then-run — the ring records from boot; the debug command queries it.
|
|
* - APPLY: when an arg points at a record present in the loaded translation
|
|
* table, transcode the target-language string to the game's glyph codes,
|
|
* write it into transient guest-stack scratch (below $sp), and repoint the
|
|
* arg register — the ORIGINAL draw routine then renders the replacement.
|
|
* KV-gated, so a stray pointer that isn't in the table is left untouched.
|
|
* - GLYPH-LABEL PATCH: UI text drawn glyph-by-glyph from a fixed-stride,
|
|
* space-padded EXE table (level/stage names) has no string pointer and no
|
|
* terminator, so the arg-scan hook can never see it. Instead each confirmed
|
|
* slot is patched in guest RAM once the EXE region is resident: verify the
|
|
* expected JP bytes are present, then overwrite exactly the slot width with
|
|
* target-language fullwidth Shift-JIS re-padded to that width. The game's own
|
|
* per-glyph routine then draws the translation. Fed by [[glyph_label]] TOML
|
|
* entries; gated on language; never spills past the confirmed slot bounds.
|
|
* - VRAM-STRIP PATCH: some UI text is PRE-RENDERED PIXELS in a texture asset
|
|
* (e.g. Tsumu's HUD labels live in the glyph-sheet image the game uploads to
|
|
* VRAM and samples with quads) — there are no glyph codes anywhere, so
|
|
* neither hook above can ever see it. Instead the patch rides the game's own
|
|
* upload: on completion of every CPU→VRAM image transfer (GP0 0xA0) any
|
|
* configured rect contained in the upload is verified against its expected
|
|
* JP pixels and, only on an exact match, rewritten with the target-language
|
|
* pixels through the same renderer write path (software/GL/supersample
|
|
* coherent). Re-applies automatically whenever the game re-uploads the
|
|
* asset — this is a source-path patch, not a one-shot VRAM poke. Fed by
|
|
* [[vram_patch]] TOML entries; gated on language; verify-before-patch.
|
|
*
|
|
* Encoding is abstracted behind an EncodingProfile (validator + terminator rules
|
|
* + UTF-8 -> glyph transcoder) selected per title; Tsumu uses the shift_jis
|
|
* profile. No generated-code edits, no regen: the hook is called from
|
|
* fntrace_record (runtime source), so it works in Release.
|
|
*/
|
|
#ifndef PSXRECOMP_TEXT_XLATE_H
|
|
#define PSXRECOMP_TEXT_XLATE_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct CPUState;
|
|
|
|
/* Called at the top of every psx_dispatch iteration (from fntrace_record),
|
|
* BEFORE the target function runs, with cpu->gpr[4..7] holding this call's
|
|
* args. Cheap no-op when the module is uninitialised or disarmed. */
|
|
void text_xlate_on_dispatch(struct CPUState* cpu, uint32_t target);
|
|
|
|
/* One-time init. Loads every translations/ *.toml under project_root and
|
|
* selects the active `language` column. language "jp"/"off"/"" (or no tables)
|
|
* disables APPLY; CAPTURE still runs (always-on inventory). The PSX_LANG env
|
|
* var overrides `language` when set. Safe to call once at startup; copies what
|
|
* it needs. */
|
|
void text_xlate_init(const char* project_root, const char* language);
|
|
|
|
/* Switch the active language and reload the translation tables. For the launcher:
|
|
* text_xlate_init runs at config-load (before the launcher), so the launcher's
|
|
* chosen language is re-applied through this after it returns. "jp"/"off"/""
|
|
* disables APPLY (untranslated native game). PSX_LANG env still wins if set.
|
|
* No-op if `language` matches the already-active one. Thread-safe. */
|
|
void text_xlate_set_language(const char* language);
|
|
|
|
/* Called by the GPU on completion of every CPU→VRAM image upload (GP0 0xA0)
|
|
* with the upload rect in VRAM halfword coords. Applies any [[vram_patch]]
|
|
* whose rect is contained in the upload (verify-then-patch). Cheap no-op when
|
|
* no patches are loaded or the language is off. */
|
|
void text_xlate_vram_upload(int x, int y, int w, int h);
|
|
|
|
/* Debug-server surface (observability; no log files — Rule 3). subcmd:
|
|
* "stats" -> counters (calls, hits, distinct captured, table size, lang)
|
|
* "dump" -> JSON array of every captured record {hash, addr, pc, len,
|
|
* count, translated, sjis_hex} — the authoring inventory
|
|
* "todo" -> JSON array of captured records with NO table entry yet
|
|
* "glyph" -> JSON array of per-glyph label RAM-patch slots {addr, width,
|
|
* patched, en} — status of the glyph-label source-patch layer
|
|
* "vpatch" -> force-apply every [[vram_patch]] against current VRAM (verify
|
|
* still gates each) + per-patch status {x,y,w,h,applied}
|
|
* "reload" -> re-read the translation table from disk
|
|
* Returns bytes written into out (<= cap). */
|
|
int text_xlate_debug_json(const char* subcmd, char* out, int cap);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PSXRECOMP_TEXT_XLATE_H */
|