Assembling started from an empty box, so replacing an instruction meant reading
it off the screen and retyping it, and tweaking one operand meant typing the
whole thing. It now opens with the current instruction already in it, selected,
so typing replaces it and editing is just editing.
Disassembled without symbol substitution for this, unlike what the view itself
shows: a branch displayed as a function name doesn't assemble back, and the
whole point of the prefill is that it's valid input.
Selection is only applied on this path. onChar() also seeds the box - with the
character the user just typed over an instruction - and there the text is the
start of what they're writing, not something to overwrite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
A game with symbols loaded puts a few thousand functions in that list, and the
only way to reach one was to scroll. Typing part of a name now narrows it,
case-insensitively.
The filter produces a list of indices into the symbol cache rather than a
filtered copy of it, so selectedSymbol_ keeps meaning the same thing whether or
not a filter is active - the Edit Symbol box above the list needs it to index
the cache. Rebuilt when the filter text changes or the symbol map reloads, not
per frame, and the list clipper works off the match list so a filter that
matches everything costs no more to draw than before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
Prototype of the frame-gated run-to-cursor idea. "Run to here" stops at the
first hit, which isn't what you want for an address hit many times per frame -
you end up stepping through the rest of the current frame to reach the state
you actually care about.
Built on machinery that was already there rather than a new stepping mode: the
one-shot breakpoint behind run-to-cursor already takes a condition (step-into
uses it to pin a step to one thread), and a hit that fails the condition leaves
it armed for the next one. So "the next frame" is just a condition that isn't
true yet - here "flipcount > <now>".
Counting presented frames rather than vblanks matters for a game that doesn't
render at the full refresh rate: at 30fps there are two vblanks per frame, so a
vblank-based condition would let you through halfway into the frame you were
trying to skip. The flip side is that the counter only advances when the
framebuffer actually changed, so if the game has stopped drawing - or is wedged
in the loop you're trying to debug - this never trips and the core keeps
running.
Both counters are exposed to the expression parser, next to
threadid/moduleid/usec/ticks, so they're usable in ordinary breakpoint
conditions and cpu.evaluate too, not just from this menu item: "flipcount" for
presented frames and "vcount" for the PSP's own vblank counter, which is what
sceDisplayGetVcount returns and is the one a game's own timing is written
against.
Verified with a headless session: across a second of emulated time flipcount
went 120 -> 172 and vcount 119 -> 172 (a game rendering every vblank, so they
track).
pspautotests 314/314, UnitTest 55/55.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
The disassembly view's assembler has been commented out since the ImGui
debugger was written, because it asked for the opcode with InputBox_GetString,
which is Win32-only. Replaced with a small popup built from ImGui, so it works
everywhere the ImGui debugger does.
Splitting it in two is what the popup costs: assembleOpcode() only records the
address and seed text and raises a flag, since ImGui popups have to be opened
and drawn inside the frame that owns them, and applyAssembly() does the work
when the input is submitted. The flag is consumed in PopupMenu(), next to the
existing rename-function popup, which had already established the pattern. The
new state lives in ImDisasmView.
Behaviour follows the Win32 version, including "register=expression" assigning
a register rather than assembling, and falling through to the assembler when
the left side isn't a register name. Two differences, both deliberate:
- Errors appear inside the popup instead of a modal message box, and the popup
stays open with the text still in it, so a typo can be corrected instead of
retyped.
- No Core_RunOnCPUThread() around the register write or the assemble. The Win32
debugger needs it because its dialogs are pumped by the WinMain message loop,
a genuinely different thread; UI/ImDebugger always runs on the same thread as
Core_RunLoopUntil(), so it can touch this state directly (see AGENTS.md).
Also updated for the current APIs while it was dead: MipsAssembleOpcode() takes
an out-parameter for the error now rather than MIPSAsm::GetAssembleError(), and
expression evaluation goes through initExpression()/parseExpression().
The core has to be stepping, checked both when the popup is requested and again
on submit - the popup is modeless, so the core can be resumed while it's open.
The keyboard shortcut (A) is re-enabled along with the context menu item.
onChar(), which seeds the popup with the character typed over an instruction,
already called this and needed no change - it still has no caller of its own,
which is a separate pre-existing gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
DisassemblyManager used to fuse lui+addiu/load/store into single pseudo-
instructions ("li", fused loads/stores) for display. This only applied to a
handful of opcodes, complicated DisassemblyManager, and was the root cause of
a stepping bug: Core_PerformCPUStep's Into/Over cases treated stepSize as a
byte count, while the WebSocket cpu.stepInto handler computed it as an
instruction count (needed to step over a whole fused macro in one go) - so a
plain, non-fused stepInto silently executed zero instructions.
Removed the fusion logic entirely (DisassemblyMacro, DISTYPE_MACRO) - every
disassembly line is now exactly one 4-byte instruction. With that,
"how many instructions does this line span" is always 1, so the
getInstructionSizeAt() byte-size queries in the legacy Windows and ImGui
debuggers are gone too; step requests just pass 1. Core_RequestCPUStep's
stepSize is now consistently in instructions everywhere.
Also fixes the PPSSPPHeadless build, broken since 0ed1f3e added
OpenWebDebugger() (which calls System_LaunchUrl) without a headless stub.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH