diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index b4d6529963..90c2232059 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -274,6 +274,7 @@ void RegisterAllModules() { Register_sceImpose(); Register_sceSuspendForUser(); Register_sceGe_user(); + Register_sceGe_driver(); Register_sceUmdUser(); Register_sceDmac(); Register_sceUtility(); diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index fd098ccb52..660792aeea 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -649,6 +649,9 @@ static const HLEFunction sceCtrl[] = // sceCtrl_driver's NID for sceCtrlPeekBufferPositive in 3.71. Imported by NJEMU's SystemButtons.prx // kernel plugin, which picks it when sceKernelDevkitVersion() is 3.71 or newer (#8925). {0XC4AAD55F, &WrapI_UU, "sceCtrlPeekBufferPositive", 'i', "xx"}, + // sceCtrl_driver NID for sceCtrlReadBufferPositive (firmware 3.71+, per JPCSP). + // Imported by translation-patch loaders like CLANNAD's chfont.prx. + {0X454455AC, &WrapI_UU, "sceCtrlReadBufferPositive", 'i', "xx"}, }; void Register_sceCtrl() diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 67d8dca6f7..57804f8035 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -670,3 +670,9 @@ const HLEFunction sceGe_user[] = { void Register_sceGe_user() { RegisterHLEModule("sceGe_user", ARRAY_SIZE(sceGe_user), sceGe_user); } + +void Register_sceGe_driver() { + // Same function set as sceGe_user; imported by kernel modules like chfont.prx + // using the driver NIDs (e.g. 0xE47E40E4 = sceGeEdramGetAddr). + RegisterHLEModule("sceGe_driver", ARRAY_SIZE(sceGe_user), sceGe_user); +} diff --git a/Core/HLE/sceGe.h b/Core/HLE/sceGe.h index 359e2ee054..a6372abc12 100644 --- a/Core/HLE/sceGe.h +++ b/Core/HLE/sceGe.h @@ -46,6 +46,7 @@ struct PspGeListArgs }; void Register_sceGe_user(); +void Register_sceGe_driver(); void __GeInit(); void __GeDoState(PointerWrap &p); diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index c9b80bee98..27db7a4645 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -976,6 +976,16 @@ const HLEFunction ThreadManForKernel[] = {0xC11BA8C4, &WrapI_II, "sceKernelNotifyCallback", 'i', "ii", HLE_KERNEL_SYSCALL }, {0xF6427665, &WrapI_V, "sceKernelGetUserLevel", 'i', "", HLE_KERNEL_SYSCALL }, {0x85A2A5BF, &WrapI_V, "sceKernelIsUserModeThread", 'i', "", HLE_KERNEL_SYSCALL }, + // Refer*Status functions that ThreadManForUser also exports, imported from kernel context by + // translation-patch loaders like CLANNAD's HookIO.prx. NIDs per JPCSP's ThreadManForUser. + {0x17C1684E, &WrapU_UU, "sceKernelReferThreadStatus", 'i', "xp", HLE_KERNEL_SYSCALL }, + {0x369EEB6B, &WrapI_IU, "sceKernelReferThreadEventHandlerStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, + {0x730ED8BC, &WrapI_IU, "sceKernelReferCallbackStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, + {0xBC6FEBC5, &WrapI_IU, "sceKernelReferSemaStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, + {0xA66B0120, &WrapU_IU, "sceKernelReferEventFlagStatus", 'x', "ix", HLE_KERNEL_SYSCALL }, + {0x33BE4024, &WrapI_IU, "sceKernelReferMsgPipeStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, + {0xD8199E4C, &WrapI_IU, "sceKernelReferFplStatus", 'i', "ip", HLE_KERNEL_SYSCALL }, + {0x5F32BEAA, &WrapU_IU, "sceKernelReferVTimerStatus", 'x', "ix", HLE_KERNEL_SYSCALL }, // NOT added on purpose, even though we implement all four for user mode already: // sceKernelCreateMutex (0xB7D098C6), sceKernelLockMutex (0xB011B11F), sceKernelUnlockMutex // (0x6B30100F) and sceKernelAllocateFpl (0xD979E9BF). They are what the real flash0 NAND and diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 25ecff5425..914758d964 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2643,6 +2643,12 @@ static u32 sctrlHENSetStartModuleHandler(u32 handlerAddr) { return hleLogInfo(Log::sceModule, previous, "handler %08x", handlerAddr); } +// NID 0x78E46415, per JPCSP's SystemCtrlForKernel. Imported by CLANNAD's chfont.prx to look up +// a driver by name; since no real driver patching is done, always report it as found. +static u32 sctrlHENFindDriver(const char *drvname) { + return hleLogInfo(Log::sceModule, 0, "driver %s", drvname ? drvname : "(null)"); +} + int __KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait) { if (needsWait) { *needsWait = false; @@ -3377,6 +3383,7 @@ void Register_ModuleMgrForKernel() { static const HLEFunction SystemCtrlForKernel[] = { {0x1C90BECB, &WrapU_U, "sctrlHENSetStartModuleHandler", 'x', "x", HLE_KERNEL_SYSCALL}, + {0x78E46415, &WrapU_C, "sctrlHENFindDriver", 'x', "s", HLE_KERNEL_SYSCALL}, }; void Register_SystemCtrlForKernel() { diff --git a/Core/HLE/sceReg.cpp b/Core/HLE/sceReg.cpp index 54b0357fa1..b8d75f1d75 100644 --- a/Core/HLE/sceReg.cpp +++ b/Core/HLE/sceReg.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "Core/HLE/sceReg.h" #include "Core/HLE/HLE.h" @@ -59,6 +60,31 @@ struct KeyValue { const KeyValue *dirContents; // intValue is the count. }; +// Guest-written registry values (sceRegCreateKey / sceRegSetKeyValue), keyed by category path +// (no leading slash, e.g. "DATA/FONT") then key name. The static dump above is read-only, but +// plugins like CLANNAD's chfont.prx register their font by creating /DATA/FONT/PROPERTY/INFO18 +// and rewriting /DATA/FONT's path_name and num_fonts at game boot - the game's font library only +// sees the font if these writes are visible to later reads. +struct OverlayValue { + ValueType type = ValueType::INT; + std::string str; // STR/BIN payload + int intValue = 0; + bool isDir = false; +}; +static std::map> g_overlay; + +// Merged view of a category: static dump entries overridden by g_overlay entries. Cached per +// path; invalidated whenever the overlay changes. strValue pointers point into strStorage. +struct MergedCategory { + std::vector keyvals; + std::deque strStorage; +}; +static std::map g_mergedCache; + +static void InvalidateMergedCache() { + g_mergedCache.clear(); +} + // TODO: /DATA/FONT/PROPERTY could just be generated from our fontRegistry in sceFont.cpp. // Partial dump of the PSP registry using tests/misc/reg.prx in pspautotests @@ -967,11 +993,94 @@ void __RegInit() { g_openRegistryCount = 0; g_handleGen = 1337; g_openCategories.clear(); + g_overlay.clear(); + InvalidateMergedCache(); } void __RegShutdown() { g_openCategories.clear(); g_openRegistryCount = 0; + g_overlay.clear(); + InvalidateMergedCache(); +} + +// Walks the static registry dump only (no guest writes applied). +static const KeyValue *LookupStaticCategory(const std::string &path, int *count) { + const KeyValue *curDir = ROOT; + int curCount = ARRAY_SIZE(ROOT); + + if (!path.empty()) { + std::vector parts; + SplitString(path, '/', parts); + for (const auto part : parts) { + bool found = false; + for (int i = 0; i < curCount; i++) { + if (equals(curDir[i].name, part)) { + if (curDir[i].type == ValueType::DIR) { + curCount = curDir[i].intValue; + curDir = curDir[i].dirContents; + found = true; + break; + } else { + return nullptr; + } + } + } + if (!found) { + return nullptr; + } + } + } + + *count = curCount; + return curDir; +} + +// Merges the static dump entries for path with the overlay entries written by the guest. +static const MergedCategory &GetMergedCategory(const std::string &path) { + auto cached = g_mergedCache.find(path); + if (cached != g_mergedCache.end()) { + return cached->second; + } + + MergedCategory &merged = g_mergedCache[path]; + int staticCount = 0; + const KeyValue *staticKeyvals = LookupStaticCategory(path, &staticCount); + if (staticKeyvals) { + merged.keyvals.assign(staticKeyvals, staticKeyvals + staticCount); + } + + auto overlayIter = g_overlay.find(path); + if (overlayIter != g_overlay.end()) { + for (const auto &entry : overlayIter->second) { + const std::string &keyName = entry.first; + const OverlayValue &value = entry.second; + + KeyValue kv; + kv.name = keyName; + kv.dirContents = nullptr; + bool replaced = false; + for (auto &existing : merged.keyvals) { + if (equals(existing.name, keyName)) { + merged.strStorage.push_back(value.str); + existing.strValue = merged.strStorage.back().c_str(); + existing.type = value.type; + existing.intValue = value.intValue; + replaced = true; + break; + } + } + if (!replaced) { + merged.strStorage.push_back(value.str); + kv.strValue = merged.strStorage.back().c_str(); + kv.type = value.type; + kv.intValue = value.intValue; + merged.keyvals.push_back(kv); + } + } + } + + return merged; } static const KeyValue *LookupCategory(std::string_view path, int *count) { @@ -979,34 +1088,33 @@ static const KeyValue *LookupCategory(std::string_view path, int *count) { std::vector parts; SplitString(path, '/', parts); - const KeyValue *curDir = ROOT; - int curCount = ARRAY_SIZE(ROOT); - + const MergedCategory *cur = &GetMergedCategory(""); + std::string curPath; for (const auto part : parts) { bool found = false; - for (int i = 0; i < curCount; i++) { - if (equals(curDir[i].name, part)) { - // Found the subdir. - if (curDir[i].type == ValueType::DIR) { - // Must update curCount before curDir, of course (since that line accesses it). - curCount = curDir[i].intValue; - curDir = curDir[i].dirContents; - found = true; - break; - } else { + for (const auto &kv : cur->keyvals) { + if (equals(kv.name, part)) { + if (kv.type != ValueType::DIR) { ERROR_LOG(Log::sceReg, "Not a dir"); return nullptr; } + found = true; + break; } } if (!found) { WARN_LOG(Log::sceReg, "LookupCategory: Path not found: %.*s", (int)path.size(), path.data()); return nullptr; } + if (!curPath.empty()) { + curPath += '/'; + } + curPath += std::string(part); + cur = &GetMergedCategory(curPath); } - *count = curCount; - return curDir; + *count = (int)cur->keyvals.size(); + return cur->keyvals.data(); } void __RegDoState(PointerWrap &p) { @@ -1354,11 +1462,93 @@ int sceRegGetKeyValueByName(int catHandle, const char *name, u32 bufAddr, u32 si } int sceRegSetKeyValue(int catHandle, const char *name, u32 bufAddr, u32 size) { - return hleLogError(Log::sceReg, 0); + if (!name) { + return hleLogError(Log::sceReg, -1, "Invalid name pointer"); + } + + auto iter = g_openCategories.find(catHandle); + if (iter == g_openCategories.end()) { + return hleLogError(Log::sceReg, 0, "Not an open category"); + } + + // chfont.prx (and other callers) pass their whole 32-byte staging buffer as size even for + // INT keys, so the size alone doesn't tell the value type. These are all the INT keys of + // the font registry entries it writes; anything else falls back to the size heuristic. + static const char *const knownIntKeys[] = { + "h_size", "v_size", "h_resolution", "v_resolution", "extra_attributes", "weight", + "family_code", "style", "sub_style", "language_code", "region_code", "country_code", + "expire_date", "shadow_option", "num_fonts", + }; + bool isIntKey = size == sizeof(u32); + for (const char *key : knownIntKeys) { + if (equals(name, key)) { + isIntKey = true; + break; + } + } + + OverlayValue value; + if (isIntKey && Memory::IsValid4AlignedAddress(bufAddr)) { + value.type = ValueType::INT; + value.intValue = (int)Memory::ReadUnchecked_U32(bufAddr); + } else if (Memory::IsValidRange(bufAddr, size)) { + value.type = ValueType::STR; + const char *str = (const char *)Memory::GetPointerUnchecked(bufAddr); + value.str.assign(str, size); + // Match the static dump convention: STR lengths include the null terminator. + if (!value.str.empty() && value.str.back() != '\0') { + value.str.push_back('\0'); + } + value.intValue = (int)value.str.size(); + } else { + return hleLogError(Log::sceReg, -1, "bad input addr"); + } + + std::string path = iter->second.path; + if (!path.empty() && path.front() == '/') { + path.erase(path.begin()); + } + g_overlay[path][name] = value; + InvalidateMergedCache(); + return hleLogDebug(Log::sceReg, 0, "set %s/%s", path.c_str(), name); } int sceRegCreateKey(int catHandle, const char *name, int type, u32 size) { - return hleLogError(Log::sceReg, 0); + if (!name) { + return hleLogError(Log::sceReg, -1, "Invalid name pointer"); + } + + auto iter = g_openCategories.find(catHandle); + if (iter == g_openCategories.end()) { + return hleLogError(Log::sceReg, 0, "Not an open category"); + } + + OverlayValue value; + switch (type) { + case REG_TYPE_DIR: + value.isDir = true; + value.type = ValueType::DIR; + break; + case REG_TYPE_INT: + value.type = ValueType::INT; + break; + case REG_TYPE_STR: + value.type = ValueType::STR; + break; + case REG_TYPE_BIN: + value.type = ValueType::BIN; + break; + default: + return hleLogError(Log::sceReg, -1, "bad type %d", type); + } + + std::string path = iter->second.path; + if (!path.empty() && path.front() == '/') { + path.erase(path.begin()); + } + g_overlay[path][name] = value; + InvalidateMergedCache(); + return hleLogDebug(Log::sceReg, 0, "create %s/%s (type %d)", path.c_str(), name, type); } // Speculated signature int sceRegRemoveKey(int catHandle, int key) {