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
Module Lifecycle: module_start / module_stop

Every PSP kernel module (PRX) needs two exported entry points, declared via PSP_MODULE_INFO/PSP_MAIN_THREAD_ATTR macros and listed in exports.exp. TempAR's are in main.c:

module_start(argc, argv) — called once when the plugin loader loads the PRX. TempAR does the minimum here and defers everything else to a worker thread:

  1. Parses argv[0] (its own load path) to extract the plugin's directory and drive by hand-scanning for / characters — no path-splitting helper in the kernel libc.
  2. Records the boot game's path via sceKernelInitFileName().
  3. Snapshots the current thread list into thread_buf_start — this baseline is what gamePause()/gameResume() later diff against to know which threads belong to the game vs. threads that existed before TempAR loaded.
  4. Creates and starts "TempAR_thread" running main_thread(), then returns.
Why defer to a thread?
module_start runs very early in boot, before other system modules the plugin depends on (sceKernelLibrary in particular) have necessarily finished loading. main_thread()'s first act is a polling wait:
while(!sceKernelFindModuleByName("sceKernelLibrary")) {
    sceKernelDelayThread(1000000);
}
Returning from module_start quickly and doing real init later, on a separate thread, is the standard PSP plugin pattern for this reason.

module_stop(argc, argv) — called on unload. Sets running = 0 and menu.visible = 0, waits up to 100ms for the worker thread to exit cleanly, and force-terminates it if it doesn't. Then frees cheat memory, language memory, the text-viewer buffer, and disconnects USB. If you add a new subsystem with its own allocated memory or open handle, add its teardown here — there's no automatic resource tracking.

The Main Loop
while(running) {
    if(menu.visible)            menu_show();       // blocking, returns when user closes menu
    else if(cfg.cheat_hz != 0)  cheat_apply(0);     // apply all enabled cheats once
    // screenshot handling...
    sceKernelDelayThread(cfg.cheat_hz ? cfg.cheat_hz : 15000);
}

cfg.cheat_hz is a user-configurable delay (in microseconds) between cheat-apply passes — lower means cheats reapply more often at the cost of more CPU time stolen from the game.

Support Modules: What Each One Wraps
ModuleWraps / solves
kmalloc.csceKernelAllocPartitionMemory wrapped with alignment support and automatic partition selection (checks partitions 1 and 6 for enough free space). Stores its own SceUID just before the returned pointer so kfree() can look it up without the caller tracking it.
syslibc.cFills gaps in the kernel-mode libc (USE_KERNEL_LIBC=1 pulls in a smaller libc). Implements vsnprintf/snprintf on top of PSPSDK's internal formatter, plus strcasecmp.
log.cA minimal file-based debug logger, entirely compiled out unless _DEBUG_ is defined (not set in any current makefile — opt-in only).
usb.cToggles USB mass-storage mode so a PC can read the memory stick without rebooting. Gated by _USB_, full build only.
screenshot.cCaptures the framebuffer to a file on the screenshot hotkey. Gated by _SCREENSHOT_.
float.cFloat/fixed-point conversion helpers used by PSPAR-extended code types that operate on floating-point values.
sdk.cThe public API surface exposed to other PRX plugins via exports.exp's cwcheat group.
psid.cDeliberately corrupts the OpenPSID for network anti-cheat evasion, gated by _PSID_, non-POPS titles only.
exports.exp and imports.S

exports.exp declares what this PRX exposes to the outside world. Two export groups:

PSP_EXPORT_START(syslib, 0, 0x8000)
    PSP_EXPORT_FUNC_HASH(module_start)
    PSP_EXPORT_FUNC_HASH(module_stop)
    PSP_EXPORT_VAR_HASH(module_info)
PSP_EXPORT_END

PSP_EXPORT_START(cwcheat, 0, 0x0001)
    PSP_EXPORT_FUNC_HASH(add_codeline_pspcheat_prx)
    PSP_EXPORT_FUNC_HASH(add_cheat_pspcheat_prx)
    PSP_EXPORT_FUNC_HASH(read_config)
    PSP_EXPORT_FUNC_HASH(config_saver)
    PSP_EXPORT_FUNC_HASH(setcodes)
    PSP_EXPORT_FUNC_HASH(readdb)
PSP_EXPORT_END
  • syslib (mandatory for every PRX): module_start, module_stop, module_info.
  • cwcheat: TempAR's own small public API, all implemented in sdk.c. Exists so a separate PRX (a game-specific trainer plugin) can add cheats or trigger a cheat-apply pass against TempAR's already-running instance. Treat this list as a public API, not an internal one — renaming or changing a signature breaks binary compatibility for any third-party plugin built against it.

imports.S is hand-written MIPS assembly declaring stub imports — functions TempAR calls that live in system modules (SysMemForKernel, sceImpose_driver, sceUsb, sceUsbstorBoot, sceRtc, sceUtility, sceOpenPSID_driver) but aren't exposed through PSPSDK's normal header+import-library pairing, usually because they're kernel-only, undocumented, or from an SDK version too old/new to have a matching stub already generated. If a future PSPSDK version starts providing a proper stub for one of these, the hand-written one becomes a duplicate symbol and must be removed.

Docker as the Toolchain

TempAR doesn't assume a locally-installed PSPSDK. Every build runs inside a container image (pspdev/pspsdk or pspdev/pspdev) that bundles the full MIPS cross-compiler + PSPSDK pre-built. The toolchain version is whatever the Docker image tag currently resolves to — no version pin in this repo. When the image updates upstream, the build can break without any change to this repository's own code. Every compat-fix commit on the Gotchas page was triggered exactly this way.