4.1 KiB
Adding HLE modules
How the HLE module tables work, the savestate-compatibility rules for adding to them, and the list of build files a new source file has to be added to.
HLE module implementations live in Core/HLE/sce<ModuleName>.cpp / .h (e.g. sceOpenPSID.cpp, scePauth.cpp are good
small examples to copy from). A module is a const HLEFunction <name>[] table of
{nid, &WrapX_YYY<func>, "funcName", retChar, argString} entries, registered via
RegisterHLEModule("<name>", ARRAY_SIZE(table), table) inside a Register_<name>() function declared in the header.
-
FunctionWrappers.hhas genericWrapX_YYY<func>templates for common signatures (return type X, args YYY) - add new wrappers there if you need a new signature. -
Format string legend for the
retmask/argmask chars:x= u32 (shown as hex),i= int/s32,f= float,X= u64,I= s64,v= void. -
For functions of genuinely unknown purpose (only known by NID), name them
<moduleName>_<NID>and stub them withreturn hleLogError(Log::HLE, 0, "UNIMPL");- an established pattern (seescePauth.cpp,sceOpenPSID.cpp). -
New modules must be registered at the very end of the registration function in
Core/HLE/HLETables.cpp(look for the// add new modules here.comment near the end of that function) - not inserted alphabetically/logically among the existingRegister_*()calls. Module registration order affects numeric IDs used in savestates, so inserting a new module earlier in that list would break save-state compatibility for saves made with older builds. -
The same rule applies one level down: new entries in an existing module's
const HLEFunction <name>[]table must go at the very end of that array too, never inserted alphabetically or next to a "related" existing entry. A resolved import gets written directly into guest RAM as a syscall opcode that encodes the entry's array position (modulenum<<18 | funcindex<<6, seeGetSyscallOp()inCore/HLE/HLE.cpp), and a savestate captures that opcode verbatim - inserting a new entry before an existing one shifts every later entry's index, so loading an older savestate after such a change resolves those later entries to the wrong function. Adding an all-new table (a new module, including a new alias-module..._driver[]variant of an existing one) is unaffected, since no old savestate could reference indices into a table that didn't exist yet - only existing tables need this care. -
Remember to add any new
.cpp/.cfile to seven places:Core/CMakeLists.txt,Core/Core.vcxproj,Core/Core.vcxproj.filters,UWP/CoreUWP/CoreUWP.vcxproj,UWP/CoreUWP/CoreUWP.vcxproj.filters,android/jni/Android.mk, andlibretro/Makefile.common. New.hfiles need the first five (everything exceptAndroid.mk/Makefile.common, which are plain compiled-source lists so headers don't go in them). Double check each by hand against how an existing neighboring file (e.g.sceVaudio.cpp) is listed. Forgetting the UWP entries is easy to miss - the CMake and MSBuild (Core.vcxproj) builds both succeed silently, and it only surfaces as a UWP-only build failure (this has happened for real:Core/MIPS/InterpreterDispatch.cpplanded without its UWP entries, and the omission wasn't caught until someone actually built the UWP project). Note: New files in the unittest project have to be updated in the unittest part in android/jni/Android.mk.Both the CMakeLists.txt change (via a Linux/Mac build) and the
Core.vcxproj/UWP changes (via MSBuild on Windows) can actually be build-tested, not just eyeballed - see Building and testing for the main Windows solution, and for UWP specifically:$installPath = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath $msbuild = "$installPath\MSBuild\Current\Bin\MSBuild.exe" & $msbuild "UWP\PPSSPP_UWP.sln" /t:CoreUWP /p:Configuration=Debug /p:Platform=x64 /m(only
android/jni/Android.mkandlibretro/Makefile.commongenuinely can't be build-tested here - see Building and testing for what verification is possible for those.)