Add some kernel nids

sceReg:made the registry writable in-memory (sceRegSetKeyValue/sceRegCreateKey overlay + merged cached lookups), which is what fixed the Chinese font. Fix #14032
This commit is contained in:
sum2012 committed 2026-10-06 04:59:56 +08:00
1 parent 647819fb6f
commit 472cf26be4
7 files changed
+235 -17

No files matched your search

+1
View File
@@ -274,6 +274,7 @@ void RegisterAllModules() {
Register_sceImpose();
Register_sceSuspendForUser();
Register_sceGe_user();
Register_sceGe_driver();
Register_sceUmdUser();
Register_sceDmac();
Register_sceUtility();
+3
View File
@@ -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>, "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>, "sceCtrlReadBufferPositive", 'i', "xx"},
};
void Register_sceCtrl()
+6
View File
@@ -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);
}
+1
View File
@@ -46,6 +46,7 @@ struct PspGeListArgs
};
void Register_sceGe_user();
void Register_sceGe_driver();
void __GeInit();
void __GeDoState(PointerWrap &p);
+10
View File
@@ -976,6 +976,16 @@ const HLEFunction ThreadManForKernel[] =
{0xC11BA8C4, &WrapI_II<sceKernelNotifyCallback>, "sceKernelNotifyCallback", 'i', "ii", HLE_KERNEL_SYSCALL },
{0xF6427665, &WrapI_V<sceKernelGetUserLevel>, "sceKernelGetUserLevel", 'i', "", HLE_KERNEL_SYSCALL },
{0x85A2A5BF, &WrapI_V<sceKernelIsUserModeThread>, "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>, "sceKernelReferThreadStatus", 'i', "xp", HLE_KERNEL_SYSCALL },
{0x369EEB6B, &WrapI_IU<sceKernelReferThreadEventHandlerStatus>, "sceKernelReferThreadEventHandlerStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
{0x730ED8BC, &WrapI_IU<sceKernelReferCallbackStatus>, "sceKernelReferCallbackStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
{0xBC6FEBC5, &WrapI_IU<sceKernelReferSemaStatus>, "sceKernelReferSemaStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
{0xA66B0120, &WrapU_IU<sceKernelReferEventFlagStatus>, "sceKernelReferEventFlagStatus", 'x', "ix", HLE_KERNEL_SYSCALL },
{0x33BE4024, &WrapI_IU<sceKernelReferMsgPipeStatus>, "sceKernelReferMsgPipeStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
{0xD8199E4C, &WrapI_IU<sceKernelReferFplStatus>, "sceKernelReferFplStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
{0x5F32BEAA, &WrapU_IU<sceKernelReferVTimerStatus>, "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
+7
View File
@@ -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>, "sctrlHENSetStartModuleHandler", 'x', "x", HLE_KERNEL_SYSCALL},
{0x78E46415, &WrapU_C<sctrlHENFindDriver>, "sctrlHENFindDriver", 'x', "s", HLE_KERNEL_SYSCALL},
};
void Register_SystemCtrlForKernel() {
+207 -17
View File
@@ -1,4 +1,5 @@
#include <map>
#include <deque>
#include <algorithm>
#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<std::string, std::map<std::string, OverlayValue>> 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<KeyValue> keyvals;
std::deque<std::string> strStorage;
};
static std::map<std::string, MergedCategory> 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<std::string_view> 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<std::string_view> 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) {