v1.70.4 is out now - crash recovery added - fixed a memory allocator bug that crashed GTA: Liberty City Stories - config self-heal - CWCheat + PSPAR in one plugin - works on CFW only, not OFW - full changelog inside
A Blocking State Machine Over a Text Console

There's no frame-timed render loop and no custom graphics engine. TempAR's UI draws directly onto PSPSDK's built-in debug-screen text console, aliased via macros in menu.h:

#define printf(...) pspDebugScreenKprintf(__VA_ARGS__)
#define puts pspDebugScreenPuts

Every screen is a while loop that blocks on controller input, redraws synchronously, and returns control up the call stack once the user backs out (CIRCLE) or switches tabs (L/R trigger). There's no dirty-rectangle tracking — screens simply reprint.

menu_show() is the single entry point into the whole UI, called from button_callback() once menu.visible is set. It performs PSP-specific setup/teardown:

  1. Hide the system home popup, delay ~150ms.
  2. Mask controller input from reaching the game.
  3. Snapshot resume_count (detects a suspend/resume cycle while the menu is open).
  4. Resolve a VRAM pointer via get_vram() and re-init the debug screen against it.
  5. Optionally pause the game's other threads.
  6. Call layout_tab() — the actual UI — which blocks until closed.
  7. Reverse every step above, in reverse order.
Order-dependent, no RAII
This setup/teardown must be mirrored exactly. There's no cleanup-on-error here — it's manual. A layout_* function that adds a new early-return path deep in the call tree without going through the normal return chain would leave the game's input permanently masked, or the home-button config in the wrong state. Always return, never bypass the call stack.

get_vram() has a fallback path (hardcoded 0x44000000) when the Impose plugin's framebuffer can't be resolved — this is the homebrew-without-imposeplugin compatibility path, and it also force-sets menu.options.force_pause = 1 as a side effect. Intentional, not a bug, but a non-obvious behavior change from an unrelated-looking function.

Tab System

layout_tab() is the top-level UI loop: a switch on the global char tab_selected (0-4: Cheater, Searcher, PRX/Options, Browser/Decoder, Credits/GameID), dispatching to one of five layout_*() functions, each owning its own inner input loop and returning a u32 control mask when it exits.

FunctionTab / role
layout_heading()Draws the tab bar + cheat-engine on/off indicator (shared by every tab)
layout_cheats()Cheater tab — cheat list browsing/toggling
layout_cheatmenu(cheat)TRIANGLE popup submenu (edit/rename/copy/delete/favorite)
layout_cheatedit(cheat)Hex/code editor for a cheat's raw lines
layout_searcher()Searcher tab
layout_options()PRX/Options tab — settings, hotkey rebinding, config save
layout_browser()Memory Browser/Disassembler tab
layout_copymenu(...)Address/value copy-paste dialog shared by browser & disassembler

Shared drawing helpers: line_print(), line_clear(), line_cursor(), get_print_start_end() (computes the visible scroll-window by centering the current selection — this was the site of the display-order rendering regression, see Cheat Engine), percentage_to_color(), show_error().

Input Handling (ctrl.c)

ctrl_read() is the single point where the raw SceCtrlData is polled and turned into TempAR's augmented button mask:

  • A HOME press, or a resume_count mismatch, forces menu.visible = 0 — how the menu auto-closes across suspend/resume.
  • Analog stick tilt is synthesized into dpad bits (threshold <50/>200 on the 0-255 axis).
  • When the menu isn't visible, PSP_CTRL_CIRCLE is force-set in the returned mask.
  • cfg.swap_xo (CROSS/CIRCLE swap) is applied here transparently.

Blocking wait helpers (ctrl_waitany, ctrl_waitkey, ctrl_waitmask, ctrl_waitrelease) implement key-repeat via ctrl_delay(): while held, repeat_count increments (capped at 12) to accelerate repeat speed, using 145ms/9ms by default or a 17ms/1ms "quick" variant for fast-scroll contexts.

Global hotkeys are registered via sceCtrlRegisterButtonCallback at boot and re-registered three more times inside the options screen whenever the user rebinds a key. button_callback() checks curr_but against cfg.menu_key, cfg.screen_key, and cfg.trigger_key.

Config Persistence (config.c)

Config is a single __attribute__((packed)) struct — ver byte, settings fields, trailing checksum. The struct layout is the file format: config_load() reads the version byte, and if it matches CONFIG_VER (currently 0x08), reads the rest of the struct as one raw memory blob straight from the file. Field order, packing, and alignment must exactly match between whatever wrote the file and whatever reads it.

No migration path
A version mismatch, failed checksum, or out-of-range field caught by config_validate() causes a silent full reset to defaults, not a partial upgrade. Any time you add, remove, or reorder a Config field, bump CONFIG_VER — otherwise old config files will be misinterpreted rather than rejected.
On-Screen Keyboard (pspdebugkb.c)

A PSPSDK-provided utility (not TempAR-authored) for editing a string in place with the D-pad instead of the system OSK dialog. Draws a fixed 13-column x 4-row character grid plus a 5-item command row. pspDebugKbInit(str, len) is the blocking entry point, called from exactly two places: renaming a cheat, and entering search text.