Commit Graph
15291 Commits
Author SHA1 Message Date
Henrik Rydgård 949f773220 WebServer: bail out of an upload whose peer disconnected
Same root cause as the websocket spin this branch fixes, different symptom, so it
belongs with the InputSink EOF change rather than apart from it.

ReadBinaryUntilTerminator() returns 0 both when nothing has arrived yet and when
the peer is gone, and the multipart loop only exits on finding its terminator - so
a cancelled browser upload left a handler thread spinning forever. That also hangs
web server shutdown, since it joins its handler threads.

Now that InputSink reports EOF, ask it.

(Moved here from misc-correctness-fixes, which will be rebased on top of this.)
2026-08-31 01:02:46 +02:00
Henrik Rydgård 76c7ddfb11 Merge pull request #22170 from hrydgard/upnp-portmanager-fixes
UPnP: fix the exit hang and the CPU spin, and only run the thread when enabled
2026-08-30 10:25:44 +02:00
Henrik Rydgård 480f42c2b8 Merge pull request #22171 from hrydgard/vulkan-surface-init-errors
Vulkan: Report surface init failures instead of asserting later
2026-08-30 10:25:29 +02:00
Henrik Rydgård 054cb1c485 Merge pull request #22168 from hrydgard/more-demangle-clauding
More symbol demangling
2026-08-30 10:25:21 +02:00
Henrik Rydgård f7c2c55e83 Vulkan: Report surface init failures instead of asserting later
VulkanGraphicsContext::InitSurface() threw away VulkanContext::InitSurface()'s
VkResult and carried on, so a failed surface init surfaced as
_dbg_assert_(GetAvailablePresentModes().size() > 0) in the VKContext
constructor rather than as a graphics error with the usual backend fallback.
The vkCreate*SurfaceKHR failure path in ReinitSurface() didn't log anything
either, so the assert was the only trace of it.

Now ReinitSurface() logs and sets init_error_ for all three ways it can bail
(surface creation, ChooseQueue, present mode enumeration), InitSurface()
checks the result, and MainThreadFunc() passes the message back out instead of
writing it to a local it then drops - Windows/main.cpp was reporting
"Failed to initialize main thread function." to the user.

Also deletes the Application on that failure path, which was leaked.
2026-08-30 01:18:37 +02:00
Henrik RydgårdandClaude Opus 5 2cb0be1bc4 UPnP: fix the exit hang and the CPU spin, and only run the thread when enabled
The two long-standing bug reports had a shared root: the service loop could
end up in a state it never left.

- Exit hang: UPNP_CMD_EXIT was queued alongside port requests and only acted
  on when it reached the front. A request that couldn't complete was never
  popped, so exit sat behind it forever and join() blocked indefinitely.
  Exit is a flag now, checked before anything else.
- CPU spin: wait_for() with a predicate returns immediately when the predicate
  already holds, so a stuck queue head meant a tight loop. sceNetInet's bind()
  queues UPnP_Add regardless of the setting, so this hit whenever UPnP was off
  and a game used sockets. The loop always blocks now, and requests are dropped
  while UPnP is off.
- Failed discovery was retried every 5s forever, each time a full 2s SSDP round
  plus an error toast. Now backs off 5s -> 300s and reports once.

Other things found while in here:

- Every failed Initialize() leaked a UPNPUrls + IGDdatas, so ~every 5 seconds
  for anyone with UPnP on and no router. The manual miniwget/parserootdesc/
  GetUPNPUrls block was also redundant - UPNP_GetValidIGD does all of it and
  memsets over the result, leaking the URLs and costing an extra HTTP round
  trip per attempt.
- UPNP_GetValidIGD's status was never checked, so we could go DONE with no
  usable IGD and hand a NULL controlURL to UPNP_GetConnectionTypeInfo.
- miniupnpc's strncpy into the port-mapping-entry buffers doesn't guarantee a
  terminator; an 80-char description ran std::string off the end of desc[80].
- Add() marked another app's port "taken" only after our own add succeeded, so
  a failed add left their mapping deleted and never restored.
- Clear() walked the router's entire table at exit, one HTTP round trip per
  index. It now deletes only what we know we mapped, and the exit cleanup has
  a time budget so an unreachable router can't stall shutdown.
- The in-flight request stayed in the queue during the router call, so a
  same-port request arriving concurrently could erase it and be dropped
  unexecuted.
- The queue is bounded, and last-write-wins per port collapses the churn from
  games that rebind in a loop.
- The mapping description is built when the request is queued rather than read
  off g_paramSFO from the UPnP thread later.

The thread now only exists while the setting is on - turning it off makes it
remove its mappings and exit, turning it on starts one. That means __UPnPInit()
has to run after the config is actually loaded; g_Config.Init() only builds a
lookup table. QueueRequest() reconciles too, so a per-game config or a libretro
core option enabling UPnP works without a notify at every call site.

The settings checkbox is disabled in-game, since sceNet latches related
settings at boot and a game that already mapped its ports wouldn't cope with
them disappearing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-08-30 00:37:05 +02:00
Henrik RydgårdandClaude Opus 5 bd505b7f1c Interpreter: implement the FPU divide-by-zero exception
div.s now maintains fcr31's Cause.Z and (when the trap is masked) sticky
Flag.Z bits, in the standard MIPS bit positions. Only a finite non-zero
dividend counts, so 0/0, inf/0 and NaN operands are excluded per IEEE 754.

When the guest has the trap unmasked, the new Core_FPUException() reports it
with the usual module suffix and MIPS call stack, and fd is left unwritten as
hardware would. That's gated behind a new developer setting, off by default:
PSP threads start with fcr31 = 0x00000e00, i.e. three of the traps already
enabled, and games divide by zero without meaning anything by it. The fcr31
bits are updated either way, so what the game reads back doesn't depend on
the setting.

Interpreter only - the JITs are unchanged, and none of this is reachable
under them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9fKXvYBrnqtp1QQGaGWVv
2026-08-29 23:40:48 +02:00
Henrik RydgårdandClaude Opus 5 dfc04f3578 Demangle: handle CodeWarrior templates, function pointers and @-symbols
Checked against two PSP binaries that shipped with intact symbol tables,
which turned up several constructs the format's usual description doesn't
mention:

- Template arguments are written literally inside the length-prefixed name
  ("39CList<Q38hlScreen5Brwsr13CContentsUnit>"), not with a "__PT" prefix,
  and they nest. Function templates put theirs in the base name instead,
  followed by the return type.
- A family of "@"-decorated symbols for things with no C++ name: thunks
  ("@12@__dt__3SonFv"), string literals, function-local statics and their
  guard variables. Plus __vt__/__RTTI__/__sinit_, printed in the same style
  as the Itanium special names.
- Types are now built as a split declarator, so a pointer to a function
  comes out as "int (*)(int)" rather than "int (int) *".

Also stop the lenient pass from turning plain C names with a "__" in them
into nonsense - "I3dClut__FlushCache" became "I3dClut(long, ...)". It now
requires a class qualifier, which costs nothing: over ~10000 symbols the
lenient pass rescued none and only produced those false positives.

Symbol map names go from 128 to 256 characters, since a demangled name
keeps its parameters and templates make short work of 128.

docs/CodeWarriorMangling.md describes the format, marking the parts that
are inferred from cfront rather than attested in a real binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF5eS5QDNexLksRDeDZvwY
2026-08-29 23:23:57 +02:00
Henrik Rydgård 4e3bda639e Merge pull request #22159 from hrydgard/debugger-dont-spin
Headless: Try to not spin so hard in the debugger when stepping
2026-08-29 10:00:13 +02:00
Henrik Rydgård 10edf68b00 Merge pull request #22150 from hrydgard/interpreter-review
Claude review of the interpreter
2026-08-29 00:20:17 +02:00
Henrik RydgårdandClaude Opus 5 6175fab373 Try to not spin so hard in the debugger when stepping
Core_ProcessStepping() returns immediately when the CPU is stopped with nothing
queued, so Core_RunLoopUntil() returns immediately, so whatever drives it comes
straight back. headless does that in a loop with no frame pacing at all, so a
paused emulator sat at 100% of a core: measured 6.02 CPU-seconds over 6 wall
seconds parked at startBreak. A debugger session is stopped most of the time, so
this also dominated any profile taken of one - showing up as synchronization
overhead around Core_RunOnCPUThread, which was just the hottest thing inside the
spin rather than a problem with the queue.

The CPU thread now blocks on a condition variable in that case. Anything that
gives it something to do wakes it - Core_RunOnCPUThread() on push (with the
queue mutex held, so it can't sleep on a task already queued),
Core_RequestCPUStep(), and Core_Resume() - so the 2ms timeout is only a backstop
for state changed without a wake, never how work is normally noticed.

The wait is deliberately short rather than indefinite: callers do real work after
Core_RunLoopUntil() returns, and in the app build that includes rendering the
ImGui debugger from this same thread, so this has to bound how long a paused
frame takes rather than replace the frame loop.

Now 0.05 CPU-seconds over the same 6 seconds. No measurable cost to anything
else: an identical scripted boot runs in 2514ms vs 2476ms before, and 20
consecutive cpu.stepInto still complete promptly. 55 unit tests pass, 314/314
pspautotests with --graphics=software.

Also: wsdbg's README claimed a raw JSON line gets a ticket auto-assigned when it
lacks one. It doesn't - the code deliberately sends raw lines exactly as written,
and omitting the ticket is how you say "not waiting for an answer". Corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-29 00:07:27 +02:00
a-blondel 4b324b278a Strip spaces of the disc ID 2026-08-28 22:36:59 +02:00
keycross 0c95caccea Avoid validating unused savedata name lists 2026-08-28 21:38:58 +09:00
Henrik Rydgård 3a1162475b Interpreter: Return 0 on all types of bad memory reads - probably best for IgnoreBadMemoryAccess
But ideally I want to get rid of this at some point.
2026-08-28 14:32:42 +02:00
Henrik Rydgård 04bf3e56ef Merge pull request #22149 from hrydgard/symbol-map-cache-fix
ImDebugger: fix stale symbol list after a game is reloaded
2026-08-27 20:19:03 +02:00
Henrik RydgårdandClaude Opus 5 991d43a713 Interpreter: reject misaligned lv.q/sv.q instead of carrying them out
These two raised the memory exception and then went ahead and did the access
anyway, unlike every other load/store here. A quadword access that isn't
16-byte aligned isn't valid, so there's nothing to carry out - and on 64-bit,
where GetPointerUnchecked is base + address with no masking, an address that
failed the validity check meant dereferencing whatever that landed on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 19:18:30 +02:00
Henrik RydgårdandClaude Opus 5 53f616faa3 Interpreter: vrot cleared the wrong lane's D prefix saturation
vrot clears the D prefix for the cosine lane, since the prefix doesn't apply
there, but shifted the saturation mask by cosineLane rather than cosineLane * 2.
That field is two bits per element - ApplyPrefixD reads it as (data >> (i * 2))
& 3, and every other site in the file shifts accordingly - so for lanes 1 and up
it cleared the wrong lane's saturation and left the cosine lane's in place. The
mask field next to it is one bit per element and was already right.

Only reachable through the interpreter, but that includes the JITs, which fall
back here for any prefixed vrot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 19:01:23 +02:00
Henrik RydgårdandClaude Opus 5 d79117146d Interpreter: fix the ins mask when the encoded msb is below pos
ins derived its width as (_SIZE + 1) - pos, which is zero or negative when the
encoded msb is below pos: the following shift is then 32 or more, undefined, and
on x86 produces an all-ones mask that writes bits the JITs don't touch. Build
the mask from msb and shift it down instead, which is what the JITs do and can't
shift out of range. Hardware calls that encoding unpredictable, so consistency
is all that's wanted here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 19:01:23 +02:00
Henrik RydgårdandClaude Opus 5 484898385f Interpreter: don't spin forever on a memory access that's set to be ignored
The alignment checks added in d8edeb7649 return out of the instruction handler
without advancing PC, so with IgnoreBadMemAccess - which is the default, and
which makes Core_MemoryException log and return - the run loop comes straight
back to the same instruction and never gets past it. cpu/crash/crash_read_u32
under -i logged the same SIGSEGV 585096 times in 30 seconds before being killed;
the JIT runs it to completion.

Continue instead, the way Memory::Read_U32 did before those checks existed and
the way the JIT's safe-memory path still does: loads produce zero, stores are
dropped, PC advances. When the exception is set to break rather than ignore,
Core_Break has already stopped the core by the time we get here, so nothing
changes for that case.

lv.q/sv.q are left alone - they already fall through and do the access.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 16:43:43 +02:00
Henrik RydgårdandClaude Opus 5 3fa67f22ed Interpreter: honor the guest's FPU rounding mode and flush-to-zero
Every JIT backend puts the host FPU into the mode fcr31 asks for (bits 0-1 and
24) before running emulated code, and takes it back out before calling any host
code. The plain interpreter did none of that, so all its float math rounded to
nearest with denormals intact no matter what the game had set - cpu/fpu/fpu
fails under -i and passes under the JIT on exactly this.

Move the helpers the IR interpreter already had for this out of IRInterpreter
and into MIPS.cpp as ApplyHostRoundingMode/RestoreHostRoundingMode, and use them
around the interpreter's run loop and single step, restoring around syscalls and
replacement functions, which are host code. ctc1 re-applies immediately, since
the interpreter has no block boundary to defer it to.

round.w.s changes with it: it was floorf(x + 0.5f), which is half-away-from-zero
rather than the half-to-even every JIT produces, and the add would now pick up
the guest's rounding mode on top of that. round_ieee_754 is both correct and
mode-independent, and is what cvt.w.s already used for the same rounding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 16:43:06 +02:00
Henrik RydgårdandClaude Opus 5 daa18fc25a ImDebugger: fix stale symbol list after a game is reloaded
The disasm window cached the flattened symbol list and only rebuilt it when one
of three menu items said so. Nothing marked it dirty when a game booted or
exited, and a new SymbolMap is allocated per boot, so the list kept showing the
previous game's functions.

Give SymbolMap a version counter that every mutator bumps, and let the window
compare against it instead. The counter is process-wide rather than per-map, so
a fresh map can't hand out a version a cached copy already holds.

Also re-find the selected symbol by address after a rebuild (the index means
something else afterwards), and drop the unused symbol cache members in
ImMemWindow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
2026-08-27 10:43:08 +02:00
Henrik RydgårdandClaude Opus 5 049bcd5483 Demangle C++ symbol names when loading ELF symbols
C++ homebrew has an unreadable symbol table -
everything is _ZN10PxRenderer7DrawImmE... - which makes the disassembly and
symbol list nearly useless. Add an Itanium C++ ABI demangler and run ELF
symbols through it on load, in both ElfReader::LoadSymbols (unstripped EXECs,
which is what a CMake pspdev EBOOT actually contains) and the companion-ELF
path.

The demangling standard is called Itanium for historical reasons - it
was defined for Itanium but ended up being almost universally
applicable.

Written from scratch rather than using __cxa_demangle, which doesn't exist on
MSVC/UWP, or vendoring LLVM's demangler, whose license doesn't fit. Anything
unrecognized (arbitrary constant expressions, decltype) aborts the parse and
the caller gets the original mangled name back, so a caller never sees a
half-parsed result. Recursion is depth-capped since the input comes from a
file we didn't write.

Checked against c++filt as an oracle: of 1089 mangled symbols in a real C++
homebrew EBOOT, one differs; of 55189 from libstdc++/libLLVM/cc1plus, 22
differ and 413 are declined. Fuzzed with 220k mutated and random inputs under
ASan/UBSan.

Also adds a right-click menu to the ImDebugger symbol list.

Note that SymbolMap stores names in char[128], so the longest STL names get
truncated in the UI. Still far more readable than the mangled form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3DbkJ8ShYiXU7q5Tv1LZu
2026-08-26 08:12:45 +02:00
Henrik Rydgård a810597ac0 Implement MMIO for the JIT (by falling back to the interpreter for load/stores from kernel addresses)
Fixes the VSH in JIT mode (but NOT ir)
2026-08-25 00:22:40 +02:00
Henrik Rydgård fb385e7627 Merge pull request #22138 from hrydgard/vsh-auto-install
Allow auto-installing firmware from EBOOT.PBP firmware updates
2026-08-24 18:01:48 +02:00
Henrik Rydgård 11b4509158 Apply proper names to some more HLE functions 2026-08-24 17:37:39 +02:00
Henrik RydgårdandClaude Opus 5 65592effc5 Block until game info is ready in LaunchFile
When launching a file from outside the main screen (file association, shortcut,
drag-and-drop), the info hasn't been computed yet, so the file type and ID checks
that decide what to do with the file were reading empty data. Add
GameInfo::WaitUntilReady() - a condition variable signalled from
MarkReadyNoLock(), which every exit path of the work item goes through - and use
it there.

Also demote a noisy PRX decryption log line to DEBUG.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMLTdwyyzU6Mze3w8VC2JL
2026-08-24 17:23:20 +02:00
Henrik RydgårdandClaude Opus 5 4b761f0715 Offer to install PSP firmware updaters instead of running them
Opening an official updater (a PSP/GAME/UPDATE EBOOT.PBP, identified by the
MSTKUPDATE disc ID) from the main screen now brings up a confirmation dialog
that unpacks the firmware into the NAND directory, where the emulated
flash0/flash1 live. Running the updater itself doesn't work, so there was
nothing useful to do with one before.

Unpacks the file list for the model we claim to be (iPSPModel), on a worker
thread, with a progress bar - for which PSARUnpackOptions gets an optional
progress callback.

AGENTS.md: translate UI strings last, in a separate commit

The English string is what all ~47 languages get derived from, so rewording it
after the sweep means redoing the sweep. Check the wording first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 17:23:14 +02:00
Henrik Rydgård 9205eb642a UWP buildfix, tweaks and de-clauding 2026-08-24 12:15:31 +02:00
Henrik Rydgård e9cdb2b20f UI work for VSH (Windows and ImGui) 2026-08-24 11:13:00 +02:00
Henrik RydgårdandClaude Opus 5 9de3eb1244 Answer the UMD region check, which the VSH opens with an error without
sceIoDevctl 0x01E18030 asks whether the disc's region matches the console's.
Unusually it answers through the return value rather than an output buffer -
1 matches, 0 doesn't - so leaving it unimplemented read as a mismatch, and the
VSH opened on "This disc cannot be started. The region code is not correct.",
asking 12 times. PPSSPP has no region-locked discs; anything it can load is
something it should run, so this always matches. The call now happens once.

Behind it is a second thing, not fixed here: the VSH believes a disc is inserted
at all because nothing in PPSSPP models an empty drive. sceUmd reports
PSP_UMD_PRESENT | PSP_UMD_READY unconditionally, and devctl 0x01F20001 always
answers "game disc". JPCSP answers "no disc" when no ISO is loaded, which is why
it never reaches the region question. Giving those two a notion of "no disc"
would be the real fix - low risk for games, which always have one, but it is on a
path every game uses, so it is written up in docs/VSHBootInvestigation.md rather
than done as a drive-by.

With this the shell reaches the interactive XMB: the error is dismissable with
circle and the menu behind it works. The per-frame display list stops settling
into one repeated frame and alternates between 45 and 48 stall points, which is
the headless-visible sign of a live menu rather than a static dialog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 11:13:00 +02:00
Henrik RydgårdandClaude Opus 5 708e7e1d9c Decrypt the VSH's XMB index: add PRX decryption type 9
pspDecryptPRX() tried types 0, 1, 2, 5 and 6. flash0:/vsh/etc/index_XXg.dat -
the index of what the XMB shows, fetched through sceResmgr_9DC14891 - needs
type 9, so it failed and the shell had no menu to build.

Type 9 is type 6 with three differences, all following from a type 9 file
carrying a real ECDSA signature at 0x104..0x12C where a type 6 file has nothing:

- The "must be empty" header check stops at 0x104 instead of 0x10C. The index's
  signature starts there, so 8 of its bytes were failing type 6's check - the
  original failure.
- The signature is left out of the hashed header rather than fed into it. JPCSP
  zeroes buf2[0x34..0x5C), which is that same range once its header
  rearrangement is undone, so PRXType9 just leaves the field zero.
- ecdsa_hash in the KIRK CMD1 header stays 0. Type 6/7 set it, but the branch
  type 9 takes writes only the mode word, and setting it made KIRK reject the
  block.

Tried last in the chain: its header check is a subset of type 6's, so a genuine
type 6 PRX would pass it and then fail on the hash, and trying it earlier would
shadow the real answer. False positives are not really possible either way - the
SHA1 check inside has to match before anything is decrypted.

Verified end to end: 496 bytes in, 159 out (the comp_size in the header),
starting "release:". sceResmgr checks that prefix and says so in its log line,
since a wrong-but-plausible decrypt would otherwise look like success here and
fail much later as an unreadable index.

The VSH now draws something different - the per-frame display list settles at 24
stall points rather than 38 - but what it shows is not visually confirmed;
framebuffer readback doesn't work under headless on either backend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 11:13:00 +02:00
Henrik RydgårdandClaude Opus 5 e7e8d362f0 Add sceResmgr, which is what the VSH uses to decrypt the XMB index
Found the cause of the red error screen the VSH ends on. Every resource load in
the boot succeeds - fonts, all the plugin RCOs, topmenu_icon.rco - and then:

  sceIoOpen(flash0:/vsh/etc/index_02g.dat) -> fd 8
  sceIoRead(8, 092a2d40, 496)
  sceIoClose(8)
  unresolved import sceResmgr/9dc14891, called from 'vsh_module'
  sceKernelExitDeleteThread(1)

index_02g.dat is the index of what the XMB displays, and it is encrypted (it
starts "PSPsysGP"). sceResmgr_9DC14891 decrypts it. There was no sceResmgr module
at all, so the call trapped, the index stayed encrypted, and the ScePafJob thread
building the top menu exited - a shell with everything loaded and nothing to show.

This adds the module and the three tags it needs (0x0B2B90F0/91F0/92F0, keys and
code 0x5C) to PrxDecrypter.

It is not the whole fix yet: pspDecryptPRX() tries decryption types 0, 1, 2, 5
and 6, and this needs type 9, which JPCSP passes explicitly. So the call is now
reached and fails cleanly with a logged error instead of trapping, but does not
yet decrypt. Type 9 is a variant of type 2 and is the next job; the notes in
docs/VSHBootInvestigation.md say where it is in JPCSP and how to check a port
(159 bytes out, starting "release:").

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 11:12:59 +02:00
Henrik RydgårdandClaude Opus 5 f449fa6ab2 Implement a batch of the VSH's unresolved imports (and document three we must not)
Mostly small stubs:

- sceImpose: GetParam/SetParam/Changes/SetStatus, plus the 6.60 alias of
  sceImposeGetBatteryIconStatus. Also fixes that function's first output - it
  is a plain "is it charging" boolean, not a BATTICON_ value. We wrote
  PSP_IMPOSE_BATTICON_NONE (0x80000000) there, which games ignore but which the
  VSH reads as "no battery" and draws the empty-battery indicator for. These
  are the bulk of the traffic: the VSH calls sceImposeChanges once a frame, so
  this alone removes ~10000 trapped calls from a boot.
- SysMemForKernel: sceKernelSetRebootKernel, sceKernelSetUmdCacheOn.
- scePower_driver: scePowerSetWakeupCondition.
- sceHprm_driver, sceUsb: one NID-named call each, as in JPCSP.

Three groups are deliberately left unresolved, with comments explaining why,
because resolving them lets real flash0 drivers walk into hardware we do not
emulate and the boot dies where it used to reach the shell:

- ThreadManForKernel mutex/fpl NIDs: the NAND and ID storage drivers use these
  to init, then poll the NAND controller at 0xbd101300 forever.
- InterruptManagerForKernel intr registration: 31 calls, then a stall in GE
  list execution with no plugin module ever started.

73 unresolved import hits over 37 distinct module/NID pairs remain in a VSH
boot, mostly sceSysEventForKernel, sceSuspendForKernel and the various
*_driver modules that need real hardware behind them.

The sceImpose savestate section goes to v2 for the two new state variables.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 10:58:35 +02:00
Henrik RydgårdandClaude Opus 5 4667f5e2da HLE: implement sceKernelLoadModuleVSH - the VSH now draws its UI
ModuleMgrForKernel/0xD5DDAB1F is how the VSH loads its own plugins. The XMB's
interface lives in flash0:/vsh/module/*_plugin.prx and vshmain pulls those in
through this kernel call rather than the user-mode sceKernelLoadModule, so the
existing note that vshmain never imports sceKernelLoadModule was true but
incomplete - it imports this instead, and it was unresolved.

The consequence was quiet: vshmain got no module id back and then called
sceKernelStartModule with id 0, which failed with UNKNOWN_MODULE. None of the
plugins that populate the XMB ever ran. The scene still had its containers,
which is why every frame set up render state per node and drew nothing inside
them - the "6x render-state-setup, 0 draws" symptom this investigation has been
chasing.

Also implements 0xD86DD11B sceKernelSearchModuleByName, the other unresolved
ModuleMgrForKernel import.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 10:58:35 +02:00
Henrik RydgårdandClaude Opus 5 23f219d276 docs: add VSHBootInvestigation.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 10:58:35 +02:00
Henrik Rydgård 75bd16d0f4 Apply two hacks to scePaf memory-arena hack
For some reason, a pointer used to allocate the heap for scePaf is not
initialized. This hacks aroung that.

Additionally zero out the specific 4-byte "category 1 alarm count" address in vsh_module.

This gets us much further.
2026-08-24 10:58:35 +02:00
Henrik Rydgård 6d14a10d83 HW: implement real Syscon serial protocol emulation (SysconSerialMMIO)
Extends the SysconSerialMMIO stub added for VSH boot into a real command/
response protocol matching uofw's Syscon_cmd() reference exactly (packet
framing, checksum, GPIO4 "response ready" handshake via a new GpioMMIO
cross-module hook), with handling for NOP/read-write clock/read-write
alarm commands.

Still hitting a SIGSEGV though.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
(cherry picked from commit 11887bf9e1fecd1eac56ec705c01b6fcfac09b2e)
2026-08-24 10:58:35 +02:00
Henrik Rydgård ed5c05fb28 VSH boot: load real kd/ kernel driver modules
Extends LoadAndStartVshKernelModules() to load the 11 real kd/*.prx
kernel drivers for --vsh (dmacman, systimer, memlmd_01g,
loadexec_01g, lowio, idstorage, syscon, rtc, wlan, wlanfirm_01g, utility),
ahead of the existing 4 VSH-specific modules.
Only active when g_runningVSH, no effect on normal game boot.

Improve implementations of sceKernelSm1ReferOperations and sceKernelIsIntrContext.

Add some more MMIO stubs (GPIO, SYSCON).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
(cherry picked from commit 7f3168b7df85e47438900016c9ee7d7ef01a0a28)
2026-08-24 10:58:35 +02:00
Henrik Rydgård 1d991557e8 Extend LoadExecForKernel with real VSH loadexec/exit syscalls
Adds SceKernelLoadExecVSHParam and fills in the rest of LoadExecForKernel's
NIDs: real implementations for sceKernelExitVSHVSH/Kernel
(mirrors sceKernelExitGame) and sceKernelLoadExecBufferVSHUsbWlan (loads
an exec from an in-RAM buffer instead of a file, plus UNIMPL stubs.

sceKernelLoadExecBufferVSHUsbWlan needed __KernelLoadExec split into a
file-reading front end and a shared __KernelLoadExecFromPtr back end
that both it and the new buffer-based path call into.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
(cherry picked from commit 4adbedec9e2e3221113c9e5ad00b248bba1153e1)
2026-08-24 10:58:34 +02:00
Henrik RydgårdandClaude Sonnet 5 04a8948ded Add a VSH-only allowlist for real-loading flash0 modules, sceVshCommonUtil stub and sceVshBridge
Load and start VSH's kernel modules before booting vshmain.prx

A few flash0 modules (vshbridge.prx, paf.prx, common_gui.prx,
common_util.prx) should run for real once we know we're
actually booting the VSH rather than a game, since our fakes are unlikely
to be good substitutes for the genuine thing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
2026-08-24 10:58:34 +02:00
Henrik Rydgård b3a2178a5c ImDebugger: Show per-thread current directory in threads view 2026-08-24 10:21:27 +02:00
Henrik Rydgård 78ef1eae82 Instead of flash0directory, have a NAND subdirectory under PSP, where you can keep flash0, flash1 etc.
Also make it configurable via command line.
2026-08-24 09:53:37 +02:00
Henrik Rydgård 786a1530de Correct host0: mount 2026-08-24 09:30:44 +02:00
Henrik Rydgård b437ba0fc6 MetaFileSystem: Add subdirectory mounts 2026-08-24 09:30:44 +02:00
Henrik Rydgård 632fa10b23 Use VFS directly to read HLE-sceFont fonts instead of relying on the flash0: mount 2026-08-24 09:30:42 +02:00
Henrik RydgårdandClaude Opus 5 396f9e802d GameInfoCache: report the firmware updater bundled on a game disc
Adds GameInfoFlags::BUNDLED_UPDATE_INFO, holding the version, title, size
and timestamp of the updater in PSP_GAME/SYSDIR/UPDATE. It comes from the
PARAM.SFO and the directory entry next to the archive, so it's a couple of
small reads on the ISOFileSystem the worker already has open - no
decryption, and DATA.BIN itself is only sniffed for its magic. Only
computed for ISOs; everything else is marked complete with an empty struct.

ISOFileSystem now parses the date out of the ISO9660 directory record,
stored as Unix UTC seconds and reported as the PSP's atime/ctime/mtime.
Those used to always read back as zero, so games calling sceIoGetstat on a
UMD file saw 1900 where hardware gives the mastering date.

Shown on GameScreen as e.g. "Firmware update on disc: 6.60 (2011-10-05),
25.6 MB".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149QcTVgZEXKXbgHyvXF4ZY
2026-08-23 16:44:38 +02:00
Henrik Rydgård a0ecf545a6 Implement or stub assorted functions the VSH is calling 2026-08-21 10:56:06 +02:00
Henrik Rydgård 4fb5d5965c MMIO: Simplest possible kernel-mode permission check, start work on supporting in JIT 2026-08-21 10:32:22 +02:00
Henrik Rydgård d94a40931e Log the debugger port at NOTICE, remove fprintf logging of the port 2026-08-21 10:32:21 +02:00
Henrik Rydgård c6061aebca Debugger: don't persist log.channel.set changes to the saved config
log.channel.set is meant for temporary, session-only diagnostic
tweaks (e.g. quieting a noisy channel while investigating something
over the WebSocket debugger). It was going through the same
SetLogLevel/SetEnabled calls the UI settings use, so a normal app
exit would persist whatever channels/levels the debugger last left
set, silently overwriting the user's real saved log preferences for
future runs - discovered when a later session's default logging
looked "off" for no apparent reason.

LogManager now tracks whether the debugger has touched channel
config this run and skips SaveConfig() entirely if so, leaving
whatever's already on disk untouched.
2026-08-21 10:32:21 +02:00