Commit Graph
30 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5 05f5668dfe Save symbols outside any module to a per-game file, and only save real names
Module symbols are keyed by module+crc so they're shared by every game that
loads that module. But symbols the user attaches to addresses that aren't in
any module - the heap, the stack, scratchpad, a hardware register, typically
after a memory.search - describe one game's own memory layout and are worthless
to any other game. Those now go to PSP/SYSTEM/SYMBOLS/<gameID>_syms.ppsym.

They're module index 0 ("absolute"), which already round-trips through the
existing per-module code: GetModuleRelativeAddr/GetModuleAbsoluteAddr are
identity for it, so the file format is unchanged, just with absolute addresses.
SaveModuleSymbols only needed to stop requiring a ModuleEntry. Auto-load/save
hang off CPU_Init/CPU_Shutdown rather than module load/unload, gated on the same
bAutoSaveLoadSymbols setting - and deliberately not on SYSPROP_HAS_DEBUGGER,
which only the Windows port reports true for, so LoadSymbolsIfSupported next to
it does nothing at all on headless. hle.game.saveSymbols/loadSymbols expose it.

Four things found while doing it:

- Symbols outside any module were being dropped on the floor. AddFunction/
  AddData/AddLabel take moduleIndex -1 as "work it out", pass it to
  GetModuleIndex(), and store whatever comes back - but that returns -1 when no
  module contains the address, and -1 is never an active module, so the symbol
  never reached the active maps: invisible to every lookup and to any save.
  hle.data.add had spotted this and normalized -1 to 0 locally; nothing else
  did, so e.g. hle.func.add outside a module silently did nothing. Fixed
  centrally in a new ResolveModuleIndex() the three of them share.
  (This only became reachable with the GetModuleIndex() fix in 29a38af37e -
  before that it returned a wrong-but-valid module index instead.)

- The saved files were almost entirely noise. Every function the analyzer finds
  is named z_un_<addr> and every import stub zz_<name>, both regenerated from
  scratch on each load. One real module wrote 13KB - 443 unnamed functions and
  64 stub names - for the four names a human had actually chosen. Worse, on the
  next run those were loaded back as authoritative and would beat the module's
  own symbols to the address. Now only names that aren't regenerated get saved,
  and a module with none writes no file at all (and removes a stale one, so
  deleting a symbol sticks). That module's file went 13020 -> 81 bytes.

- LoadModuleSymbols trusted the addresses in the file. It's meant to be
  hand-edited and can outlive the build it came from, so relative addresses past
  the end of the module are now skipped with a warning instead of landing at
  nonsense addresses.

- AddFunction and AddData both erased the map entry they were updating and then
  read back through the now-dangling iterator to refresh the active copy. Only
  latent: the refresh is guarded on the active copy's module matching the new
  one, which is false exactly when the erase happens. Re-point the iterator at
  the entry's new home instead, so it can't rot if that guard ever changes.
  AddLabel already did the equivalent correctly, via a local copy.

Filename sanitizing goes through SanitizeString with a new FileName restriction
rather than being open-coded; unlike the existing restrictions it substitutes
'_' instead of dropping, so two module names can't collapse onto one file.

Verified end to end on headless with cpu_alu.prx: named a function inside the
module and data/functions in scratchpad and the heap, let it exit, checked both
files, rebooted and confirmed all of it came back at the right addresses.
Unit tests 51/51, pspautotests 314/314.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 16:53:48 +02:00
Henrik RydgårdandClaude Sonnet 5 29a38af37e Per-module symbol save/load, module identity via crc, GetModuleIndex fix
SymbolMap:
- Fix GetModuleIndex(): it only checked the end of an active module's range
  (via activeModuleEnds.upper_bound), never the start, so an address sitting
  in the gap before a module was silently misattributed to it. Added
  GetModuleIndexByName() as a companion lookup.
- AddModule() gains an optional crc param, stored per ModuleEntry. Reactivating
  a module by name now also requires the crc to agree when both sides know it,
  so two unrelated binaries that happen to share a name no longer get merged
  into one symbol table (addresses the old TODO at the top of SymbolMap.h).
- AddLabel()/AddFunction() gain an updateName param (default false, preserving
  existing "first writer wins" behavior) so a trusted source - like a loaded
  symbol file - can be allowed to overwrite a name that a lower-confidence
  automatic pass already assigned.
- New SaveModuleSymbols()/LoadModuleSymbols()/GetModuleSymbolsPath(): save or
  restore one module's functions/data/labels to/from a small human-editable
  text file, addressed relative to the module (so the file stays valid however
  the module ends up positioned on a later run). Keyed by
  PSP/SYSTEM/SYMBOLS/<moduleName>_<crc>.ppsym - deliberately by module+crc
  rather than by game, so it's shared by every game/homebrew that loads the
  exact same module. A "# game <id> <title>" comment records who last saved
  it, informational only.

WebSocket debugger: hle.module.saveSymbols/loadSymbols expose the above.

sceKernelModule.cpp: auto-load a module's saved symbols right after it's
registered with the symbol map (both the real ELF-load path and the
savestate-load path), and auto-save on unload (before UnloadModule(), while
its symbols are still active) - gated behind the new bAutoSaveLoadSymbols
config setting (default off), with a matching Developer Tools checkbox and
a --auto-save-load-symbols command-line override for headless use.

Includes some in-progress cleanup already staged: DescribeAddress now calls
g_symbolMap->GetDescription() directly instead of through the now-removed
MIPSDebugInterface::getDescription() wrapper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 16:01:23 +02:00
Henrik Rydgård 9b9083d3e5 Fix for headless port problem by Claude 2026-08-16 23:09:12 +02:00
Henrik RydgårdandClaude Sonnet 5 65495c76c0 Add a codegen tool to generate a fast switch-tree interpreter dispatcher
MIPSTables.cpp has walked a tree of tables on every single interpreted
instruction since forever, with a standing TODO asking for exactly this:
"generate smart dispatcher functions from above tables instead of this
slow method." GenerateInterpreterDispatch() does that - it walks the
same tables MIPSGetInstruction() walks at runtime, but resolves the
walk into a nested switch tree once, at generation time, with each
leaf calling straight into the existing MIPSInt::Int_* handlers and
returning that instruction's fixed cycle count. Anything not covered
(invalid opcodes, and the handful of instructions with no interpreter
implemented at all, e.g. tge/tlt/teq) falls back to the existing
MIPSInterpret()/MIPSGetInstructionCycleEstimate() slow path, so the
result is total over all 32-bit inputs, same as the table-walking path.

Wired up via a new headless --generate-interpreter-dispatch flag,
which prints the generated Core/MIPS/InterpreterDispatch.cpp source to
stdout and exits.

Also widens CmdLine.cpp's --help column formatting, which silently
truncated any option name longer than 24 characters - the new option's
name was the first to hit it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SKhm9wKEQzRUsx9mTrrtQ
2026-08-12 10:24:01 +02:00
Henrik Rydgård e062c90bf7 Try to fix the test difference (basically by replicating an old misfeature of headless...) 2026-08-11 22:36:47 +02:00
Henrik Rydgård 2d0e54f422 Headless (and main): Improve crash reporting 2026-08-11 22:36:47 +02:00
Henrik Rydgård b0f070040b Don't auto-enable logging in debug mode in headless, it's annoying. 2026-08-09 13:41:39 +02:00
Henrik Rydgård 8c9bf04add Headless: force opaque PNG screenshots by default, add --screenshot-keep-alpha
Games often use the framebuffer alpha channel for non-visual purposes,
so saved PNGs could look fully transparent in image viewers. Force alpha
to 255 when writing PNG screenshots unless --screenshot-keep-alpha is
passed. The MSE comparison ignores alpha either way.
2026-08-09 13:41:39 +02:00
Henrik Rydgård 893ac3e3db Frametests: add framedump rendering test runner and PNG screenshot support
- Add frametests.py: walks a dump tree, renders each dump per config variant
  through PPSSPPHeadless, generates reference images when missing and compares
  MSE when present, and writes a self-contained HTML report. The JSON config
  (which lives with the test set, not in the repo) points at the data tree
  and defines variants as suffix -> CLI args, e.g. 'soft': '--graphics=software'.
- Headless: --screenshot-save saves PNG when the path ends in .png; new
  --screenshot-diff always writes a visual comparison when comparing;
  screenshot comparison failures (mismatch or unloadable reference) now fail
  the test instead of passing silently.
- Read back framebuffers top-down, flipping only for BMP output/input
  (fixes upside-down PNG references). Sync libretro copy accordingly.
- Document the system in docs/frametest.md; add AGENTS.md reference.
2026-08-09 13:41:39 +02:00
Henrik RydgårdandClaude Sonnet 5 5bd1ff8066 Move remaining manual command line parsing into CmdLine.cpp
Headless.cpp, NativeApp.cpp, and SDLMain.cpp each still hand-parsed a few
argv flags directly (mount/log/state/ignore/loglevel in headless and the
app, xres/yres/dpi/scale in SDL), duplicating and in some cases conflicting
with the shared CommandLineOptions parser. Consolidate all of it into
CmdLine.cpp/.h so there's a single source of truth, and drop the now-dead
remain_argc/remain_argv filtering in SDLMain.cpp since NativeInit no longer
reads argv itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfFvWzpHxErWgRhKqqSewN
2026-08-05 16:02:49 +02:00
Henrik Rydgård 551e4cd0ab Headless: Make all GPU backends work correctly on Windows 2026-08-05 00:15:56 +02:00
Henrik Rydgård e0ddfc7109 Add --print-equal-lines option to headless, good for context when looking at output 2026-07-29 18:55:00 +02:00
Henrik Rydgård 34454f9d03 Claude's crazy exception handler hack 2026-07-27 23:41:13 +02:00
Henrik Rydgård e30b90e709 Quiet and improve some logs 2026-07-27 18:37:28 +02:00
Henrik Rydgård 5ef736f6a7 More command line improvements 2026-07-27 18:37:28 +02:00
Henrik Rydgård 3139d7ccfe Add command line parameters for controlling the behavior on memory/instruction exceptions 2026-07-27 18:37:26 +02:00
Henrik Rydgård 4bf36fc7f8 Add command line option --vsh to try to boot the VSH. Logspam reduction, improve printf logs. 2026-07-27 14:58:59 +02:00
Henrik RydgårdandClaude Opus 5 7e5dca0309 Unify --debugger=PORT for both the app and headless builds
The headless build's own --debugger=PORT never actually worked on
Windows: headless/Headless.cpp never called net::Init() (WSAStartup),
so socket binding silently failed ("Unable to listen on any port
(debugger - webserver)"). Fixed by calling net::Init()/net::Shutdown()
there, gated on --debugger being passed since headless has no other use
for networking.

With that confirmed working, move parsing into the shared
Core/CmdLine.cpp auto-param table as a single --debugger=PORT option
(0 = pick automatically) available on both CmdLineMode::Application and
CmdLineMode::Headless, replacing the previous app-only boolean
--debugger flag and headless's separate ad hoc argv scan for it.

Behavior differs deliberately by build, same as before:
- App: just starts the debugger, game boots and runs normally.
- Headless: also forces coreParameter.startBreak = true (break before
  anything runs), as it always has. Headless re-applies iRemoteISOPort
  after its own g_Config.RestoreDefaults() call, which runs after
  ApplyToConfig() and would otherwise wipe the requested port.

Verified live: headless now binds the requested port and responds to
cpu.status/game.status; the app build binds the requested port and
runs the game normally (not paused).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDNwPPuidmNxQGRJxBuRL6
2026-07-26 21:49:37 +02:00
Henrik RydgårdandClaude Opus 5 3ebc9872a5 Add --debugger command line flag to enable the WebSocket debugger
Application-mode only (CmdLineMode::Application) - the headless build has
its own separate --debugger=PORT mechanism in headless/Headless.cpp, which
doesn't currently work reliably, so this new flag intentionally does not
apply there. Sets bRemoteDebuggerOnStartup for the session without
persisting it to the config file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDNwPPuidmNxQGRJxBuRL6
2026-07-26 21:02:00 +02:00
Henrik Rydgård 00a566514e Buildfixes 2026-07-21 14:00:02 +02:00
Henrik Rydgård 8666a3cd57 Add resolution scale parameter to headless 2026-07-20 21:40:14 +02:00
Henrik Rydgård 4b24c187f2 Move more command line parameters to the new parser 2026-07-20 17:44:19 +02:00
Henrik Rydgård 650a59f964 Migrate more config parsing to the new parser 2026-07-20 16:50:34 +02:00
Henrik Rydgård bd25962cbe Convert more options to the new command line parser 2026-07-20 16:33:03 +02:00
Henrik Rydgård dca65d02ad Move the force_gl_version command line hack to the new parser
See #20687
2026-07-20 16:33:03 +02:00
Henrik Rydgård e6875fa339 More cmdline work. 2026-07-20 16:33:03 +02:00
Henrik Rydgård cef7da49b3 Add utility to parse parameters with params 2026-07-20 16:33:03 +02:00
Henrik Rydgård 717ffc9fae Buildfixes 2026-07-20 12:02:07 +02:00
Henrik Rydgård a59637975a Call the new command line parser from all backends 2026-07-20 12:02:07 +02:00
Henrik Rydgård 29836ea74e Move the new incomplete command line parser to Core 2026-07-20 12:02:07 +02:00