ppsspp/Core/HLE/sceKernelModule.cpp

2668 lines
89 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <fstream>
#include <algorithm>
#include <set>
#include "zlib.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeSet.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/HLETables.h"
#include "Core/HLE/Plugins.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/Reporting.h"
#include "Core/Host.h"
#include "Core/Loaders.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/ELF/ElfReader.h"
#include "Core/ELF/PBPReader.h"
#include "Core/ELF/PrxDecrypter.h"
#include "Core/FileSystems/FileSystem.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/Util/BlockAllocator.h"
#include "Core/CoreTiming.h"
#include "Core/PSPLoaders.h"
#include "Core/System.h"
#include "Core/MemMapHelpers.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceMpeg.h"
#include "Core/HLE/scePsmf.h"
#include "Core/HLE/sceIo.h"
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/ELF/ParamSFO.h"
#include "GPU/Debugger/Playback.h"
#include "GPU/GPU.h"
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
enum {
PSP_THREAD_ATTR_KERNEL = 0x00001000,
PSP_THREAD_ATTR_USER = 0x80000000,
};
enum : u32 {
// Function exports.
NID_MODULE_START = 0xD632ACDB,
NID_MODULE_STOP = 0xCEE8593C,
NID_MODULE_REBOOT_BEFORE = 0x2F064FA6,
NID_MODULE_REBOOT_PHASE = 0xADF12745,
NID_MODULE_BOOTSTART = 0xD3744BE0,
// Variable exports.
NID_MODULE_INFO = 0xF01D73A7,
NID_MODULE_START_THREAD_PARAMETER = 0x0F7C276C,
NID_MODULE_STOP_THREAD_PARAMETER = 0xCF0CC697,
NID_MODULE_REBOOT_BEFORE_THREAD_PARAMETER = 0xF4F4299D,
NID_MODULE_SDK_VERSION = 0x11B97506,
};
// This is a workaround for misbehaving homebrew (like TBL's Suicide Barbie (Final)).
static const char * const lieAboutSuccessModules[] = {
"flash0:/kd/audiocodec.prx",
"flash0:/kd/audiocodec_260.prx",
"flash0:/kd/libatrac3plus.prx",
"disc0:/PSP_GAME/SYSDIR/UPDATE/EBOOT.BIN",
};
// Modules to not load. TODO: Look into loosening this a little (say sceFont).
static const char * const blacklistedModules[] = {
"sceATRAC3plus_Library",
"sceFont_Library",
"SceFont_Library",
"SceHttp_Library",
"sceMpeg_library",
"sceNetAdhocctl_Library",
"sceNetAdhocDownload_Library",
"sceNetAdhocMatching_Library",
"sceNetApDialogDummy_Library",
"sceNetAdhoc_Library",
"sceNetApctl_Library",
"sceNetInet_Library",
"sceNetResolver_Library",
"sceNet_Library",
"sceSsl_Module",
"sceDEFLATE_Library",
"sceMD5_Library",
};
struct VarSymbolImport {
char moduleName[KERNELOBJECT_MAX_NAME_LENGTH + 1];
u32 nid;
u32 stubAddr;
u8 type;
};
struct VarSymbolExport {
bool Matches(const VarSymbolImport &other) const {
return nid == other.nid && !strncmp(moduleName, other.moduleName, KERNELOBJECT_MAX_NAME_LENGTH);
}
char moduleName[KERNELOBJECT_MAX_NAME_LENGTH + 1];
u32 nid;
u32 symAddr;
};
struct FuncSymbolImport {
char moduleName[KERNELOBJECT_MAX_NAME_LENGTH + 1];
u32 stubAddr;
u32 nid;
};
struct FuncSymbolExport {
bool Matches(const FuncSymbolImport &other) const {
return nid == other.nid && !strncmp(moduleName, other.moduleName, KERNELOBJECT_MAX_NAME_LENGTH);
}
char moduleName[KERNELOBJECT_MAX_NAME_LENGTH + 1];
u32 symAddr;
u32 nid;
};
void ImportVarSymbol(const VarSymbolImport &var);
void ExportVarSymbol(const VarSymbolExport &var);
void UnexportVarSymbol(const VarSymbolExport &var);
void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting, const char *importingModule);
void ExportFuncSymbol(const FuncSymbolExport &func);
void UnexportFuncSymbol(const FuncSymbolExport &func);
class PSPModule;
static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr, bool reimporting = false);
struct NativeModule {
u32_le next;
u16_le attribute;
u8 version[2];
char name[28];
u32_le status;
u32_le unk1;
u32_le modid; // 0x2C
u32_le usermod_thid;
u32_le memid;
u32_le mpidtext;
u32_le mpiddata;
u32_le ent_top;
u32_le ent_size;
u32_le stub_top;
u32_le stub_size;
u32_le module_start_func;
u32_le module_stop_func;
u32_le module_bootstart_func;
u32_le module_reboot_before_func;
u32_le module_reboot_phase_func;
u32_le entry_addr;
u32_le gp_value;
u32_le text_addr;
u32_le text_size;
u32_le data_size;
u32_le bss_size;
u32_le nsegment;
u32_le segmentaddr[4];
u32_le segmentsize[4];
u32_le module_start_thread_priority;
u32_le module_start_thread_stacksize;
u32_le module_start_thread_attr;
u32_le module_stop_thread_priority;
u32_le module_stop_thread_stacksize;
u32_le module_stop_thread_attr;
u32_le module_reboot_before_thread_priority;
u32_le module_reboot_before_thread_stacksize;
u32_le module_reboot_before_thread_attr;
};
// by QueryModuleInfo
struct ModuleInfo {
SceSize_le size;
u32_le nsegment;
u32_le segmentaddr[4];
u32_le segmentsize[4];
u32_le entry_addr;
u32_le gp_value;
u32_le text_addr;
u32_le text_size;
u32_le data_size;
u32_le bss_size;
u16_le attribute;
u8 version[2];
char name[28];
};
struct ModuleWaitingThread {
SceUID threadID;
u32 statusPtr;
};
enum NativeModuleStatus {
MODULE_STATUS_STARTING = 4,
MODULE_STATUS_STARTED = 5,
MODULE_STATUS_STOPPING = 6,
MODULE_STATUS_STOPPED = 7,
MODULE_STATUS_UNLOADING = 8,
};
class PSPModule : public KernelObject {
public:
~PSPModule() {
if (memoryBlockAddr) {
// If it's either below user memory, or using a high kernel bit, it's in kernel.
if (memoryBlockAddr < PSP_GetUserMemoryBase() || memoryBlockAddr > PSP_GetUserMemoryEnd()) {
kernelMemory.Free(memoryBlockAddr);
} else {
userMemory.Free(memoryBlockAddr);
}
g_symbolMap->UnloadModule(memoryBlockAddr, memoryBlockSize);
}
if (modulePtr) {
//Only alloc at kernel memory.
kernelMemory.Free(modulePtr);
}
}
const char *GetName() override { return nm.name; }
const char *GetTypeName() override { return GetStaticTypeName(); }
static const char *GetStaticTypeName() { return "Module"; }
void GetQuickInfo(char *ptr, int size) override
{
// ignore size
sprintf(ptr, "%sname=%s gp=%08x entry=%08x",
isFake ? "faked " : "",
nm.name,
nm.gp_value,
nm.entry_addr);
}
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_MODULE; }
static int GetStaticIDType() { return PPSSPP_KERNEL_TMID_Module; }
int GetIDType() const override { return PPSSPP_KERNEL_TMID_Module; }
void DoState(PointerWrap &p) override
{
auto s = p.Section("Module", 1, 5);
if (!s)
return;
if (s >= 5) {
Do(p, nm);
} else {
char temp[192];
NativeModule *pnm = &nm;
char *ptemp = temp;
DoArray(p, ptemp, 0xC0);
memcpy(pnm, ptemp, 0x2C);
pnm->modid = GetUID();
memcpy(((uint8_t *)pnm) + 0x30, ((uint8_t *)ptemp) + 0x2C, 0xC0 - 0x2C);
}
Do(p, memoryBlockAddr);
Do(p, memoryBlockSize);
Do(p, isFake);
if (s < 2) {
bool isStarted = false;
Do(p, isStarted);
if (isStarted)
nm.status = MODULE_STATUS_STARTED;
else
nm.status = MODULE_STATUS_STOPPED;
}
if (s >= 3) {
Do(p, textStart);
Do(p, textEnd);
}
if (s >= 4) {
Do(p, libstub);
Do(p, libstubend);
}
if (s >= 5) {
Do(p, modulePtr);
}
ModuleWaitingThread mwt = {0};
Do(p, waitingThreads, mwt);
FuncSymbolExport fsx = {{0}};
Do(p, exportedFuncs, fsx);
FuncSymbolImport fsi = {{0}};
Do(p, importedFuncs, fsi);
VarSymbolExport vsx = {{0}};
Do(p, exportedVars, vsx);
VarSymbolImport vsi = {{0}};
Do(p, importedVars, vsi);
if (p.mode == p.MODE_READ) {
// On load state, we re-examine in case our syscall ids changed.
if (libstub != 0) {
importedFuncs.clear();
// Imports reloaded in KernelModuleDoState.
} else {
// Older save state. Let's still reload, but this may not pick up new flags, etc.
bool foundBroken = false;
auto importedFuncsState = importedFuncs;
importedFuncs.clear();
for (auto func : importedFuncsState) {
if (func.moduleName[KERNELOBJECT_MAX_NAME_LENGTH] != '\0' || !Memory::IsValidAddress(func.stubAddr)) {
foundBroken = true;
} else {
ImportFunc(func, true);
}
}
if (foundBroken) {
ERROR_LOG(LOADER, "Broken stub import data while loading state");
}
}
char moduleName[29] = {0};
truncate_cpy(moduleName, nm.name);
if (memoryBlockAddr != 0) {
g_symbolMap->AddModule(moduleName, memoryBlockAddr, memoryBlockSize);
}
}
HLEPlugins::DoState(p);
RebuildImpExpModuleNames();
}
// We don't do this in the destructor to avoid annoying messages on game shutdown.
void Cleanup();
void ImportFunc(const FuncSymbolImport &func, bool reimporting) {
if (!Memory::IsValidAddress(func.stubAddr)) {
WARN_LOG_REPORT(LOADER, "Invalid address for syscall stub %s %08x", func.moduleName, func.nid);
return;
}
DEBUG_LOG(LOADER, "Importing %s : %08x", GetFuncName(func.moduleName, func.nid), func.stubAddr);
// Add the symbol to the symbol map for debugging.
char temp[256];
sprintf(temp,"zz_%s", GetFuncName(func.moduleName, func.nid));
g_symbolMap->AddFunction(temp,func.stubAddr,8);
// Keep track and actually hook it up if possible.
importedFuncs.push_back(func);
impExpModuleNames.insert(func.moduleName);
ImportFuncSymbol(func, reimporting, GetName());
}
void ImportVar(const VarSymbolImport &var) {
// Keep track and actually hook it up if possible.
importedVars.push_back(var);
impExpModuleNames.insert(var.moduleName);
ImportVarSymbol(var);
}
void ExportFunc(const FuncSymbolExport &func) {
if (isFake) {
return;
}
exportedFuncs.push_back(func);
impExpModuleNames.insert(func.moduleName);
ExportFuncSymbol(func);
}
void ExportVar(const VarSymbolExport &var) {
if (isFake) {
return;
}
exportedVars.push_back(var);
impExpModuleNames.insert(var.moduleName);
ExportVarSymbol(var);
}
template <typename T>
void RebuildImpExpList(const std::vector<T> &list) {
for (size_t i = 0; i < list.size(); ++i) {
impExpModuleNames.insert(list[i].moduleName);
}
}
void RebuildImpExpModuleNames() {
impExpModuleNames.clear();
RebuildImpExpList(exportedFuncs);
RebuildImpExpList(importedFuncs);
RebuildImpExpList(exportedVars);
RebuildImpExpList(importedVars);
}
bool ImportsOrExportsModuleName(const std::string &moduleName) {
return impExpModuleNames.find(moduleName) != impExpModuleNames.end();
}
NativeModule nm{};
std::vector<ModuleWaitingThread> waitingThreads;
std::vector<FuncSymbolExport> exportedFuncs;
std::vector<FuncSymbolImport> importedFuncs;
std::vector<VarSymbolExport> exportedVars;
std::vector<VarSymbolImport> importedVars;
std::set<std::string> impExpModuleNames;
// Keep track of the code region so we can throw out analysis results
// when unloaded.
u32 textStart = 0;
u32 textEnd = 0;
// Keep track of the libstub pointers so we can recheck on load state.
u32 libstub = 0;
u32 libstubend = 0;
u32 memoryBlockAddr = 0;
u32 memoryBlockSize = 0;
u32 modulePtr = 0;
bool isFake = false;
};
KernelObject *__KernelModuleObject()
{
return new PSPModule;
}
class AfterModuleEntryCall : public PSPAction {
public:
AfterModuleEntryCall() {}
SceUID moduleID_;
u32 retValAddr;
void run(MipsCall &call) override;
void DoState(PointerWrap &p) override {
auto s = p.Section("AfterModuleEntryCall", 1);
if (!s)
return;
Do(p, moduleID_);
Do(p, retValAddr);
}
static PSPAction *Create() {
return new AfterModuleEntryCall;
}
};
void AfterModuleEntryCall::run(MipsCall &call) {
Memory::Write_U32(retValAddr, currentMIPS->r[MIPS_REG_V0]);
}
//////////////////////////////////////////////////////////////////////////
// MODULES
//////////////////////////////////////////////////////////////////////////
struct StartModuleInfo
{
u32_le size;
u32_le mpidtext;
u32_le mpiddata;
u32_le threadpriority;
u32_le threadattributes;
};
struct SceKernelLMOption {
SceSize_le size;
SceUID_le mpidtext;
SceUID_le mpiddata;
u32_le flags;
char position;
char access;
char creserved[2];
};
struct SceKernelSMOption {
SceSize_le size;
SceUID_le mpidstack;
SceSize_le stacksize;
s32_le priority;
u32_le attribute;
};
//////////////////////////////////////////////////////////////////////////
// STATE BEGIN
static int actionAfterModule;
static std::set<SceUID> loadedModules;
// STATE END
//////////////////////////////////////////////////////////////////////////
static void __KernelModuleInit()
{
actionAfterModule = __KernelRegisterActionType(AfterModuleEntryCall::Create);
}
void __KernelModuleDoState(PointerWrap &p)
{
auto s = p.Section("sceKernelModule", 1, 2);
if (!s)
return;
Do(p, actionAfterModule);
__KernelRestoreActionType(actionAfterModule, AfterModuleEntryCall::Create);
if (s >= 2) {
Do(p, loadedModules);
}
if (p.mode == p.MODE_READ) {
u32 error;
// We process these late, since they depend on loadedModules for interlinking.
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (module && module->libstub != 0) {
if (!KernelImportModuleFuncs(module, nullptr, true)) {
ERROR_LOG(LOADER, "Something went wrong loading imports on load state");
}
}
}
}
if (g_Config.bFuncReplacements) {
MIPSAnalyst::ReplaceFunctions();
}
}
void __KernelModuleShutdown()
{
loadedModules.clear();
MIPSAnalyst::Reset();
HLEPlugins::Unload();
}
// Sometimes there are multiple LO16's or HI16's per pair, even though the ABI says nothing of this.
// For multiple LO16's, we need the original (unrelocated) instruction data of the HI16.
// For multiple HI16's, we just need to set each one.
struct HI16RelocInfo
{
u32 addr;
u32 data;
};
static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool reverse = false)
{
// We have to post-process the HI16 part, since it might be +1 or not depending on the LO16 value.
static u32 lastHI16ExportAddress = 0;
static std::vector<HI16RelocInfo> lastHI16Relocs;
static bool lastHI16Processed = true;
u32 relocData = Memory::Read_Instruction(relocAddress, true).encoding;
switch (type)
{
case R_MIPS_NONE:
WARN_LOG_REPORT(LOADER, "Var relocation type NONE - %08x => %08x", exportAddress, relocAddress);
break;
case R_MIPS_32:
if (!reverse) {
relocData += exportAddress;
} else {
relocData -= exportAddress;
}
break;
// Not really tested, but should work...
/*
case R_MIPS_26:
if (exportAddress % 4 || (exportAddress >> 28) != ((relocAddress + 4) >> 28)) {
WARN_LOG_REPORT(LOADER, "Bad var relocation addresses for type 26 - %08x => %08x", exportAddress, relocAddress)
} else {
if (!reverse) {
relocData = (relocData & ~0x03ffffff) | ((relocData + (exportAddress >> 2)) & 0x03ffffff);
} else {
relocData = (relocData & ~0x03ffffff) | ((relocData - (exportAddress >> 2)) & 0x03ffffff);
}
}
break;
*/
case R_MIPS_HI16:
if (lastHI16ExportAddress != exportAddress) {
if (!lastHI16Processed && lastHI16Relocs.size() >= 1) {
WARN_LOG_REPORT(LOADER, "Unsafe unpaired HI16 variable relocation @ %08x / %08x", lastHI16Relocs[lastHI16Relocs.size() - 1].addr, relocAddress);
}
lastHI16ExportAddress = exportAddress;
lastHI16Relocs.clear();
}
// After this will be an R_MIPS_LO16. If that addition overflows, we need to account for it in HI16.
// The R_MIPS_LO16 and R_MIPS_HI16 will often be *different* relocAddress values.
HI16RelocInfo reloc;
reloc.addr = relocAddress;
reloc.data = Memory::Read_Instruction(relocAddress, true).encoding;
lastHI16Relocs.push_back(reloc);
lastHI16Processed = false;
break;
case R_MIPS_LO16:
{
// Sign extend the existing low value (e.g. from addiu.)
const u32 offsetLo = (s32)(s16)(u16)(relocData & 0xFFFF);
u32 full = exportAddress;
// This is only used in the error case (no hi/wrong hi.)
if (!reverse) {
full = offsetLo + exportAddress;
} else {
full = offsetLo - exportAddress;
}
// The ABI requires that these come in pairs, at least.
if (lastHI16Relocs.empty()) {
ERROR_LOG_REPORT(LOADER, "LO16 without any HI16 variable import at %08x for %08x", relocAddress, exportAddress);
// Try to process at least the low relocation...
} else if (lastHI16ExportAddress != exportAddress) {
ERROR_LOG_REPORT(LOADER, "HI16 and LO16 imports do not match at %08x for %08x (should be %08x)", relocAddress, lastHI16ExportAddress, exportAddress);
} else {
// Process each of the HI16. Usually there's only one.
for (auto it = lastHI16Relocs.begin(), end = lastHI16Relocs.end(); it != end; ++it)
{
if (!reverse) {
full = (it->data << 16) + offsetLo + exportAddress;
} else {
full = (it->data << 16) + offsetLo - exportAddress;
}
// The low instruction will be a signed add, which means (full & 0x8000) will subtract.
// We add 1 in that case so that it ends up the right value.
u16 high = (full >> 16) + ((full & 0x8000) ? 1 : 0);
Memory::Write_U32((it->data & ~0xFFFF) | high, it->addr);
currentMIPS->InvalidateICache(it->addr, 4);
}
lastHI16Processed = true;
}
// With full set above (hopefully), now we just need to correct the low instruction.
relocData = (relocData & ~0xFFFF) | (full & 0xFFFF);
}
break;
default:
WARN_LOG_REPORT(LOADER, "Unsupported var relocation type %d - %08x => %08x", type, exportAddress, relocAddress);
}
Memory::Write_U32(relocData, relocAddress);
currentMIPS->InvalidateICache(relocAddress, 4);
}
void ImportVarSymbol(const VarSymbolImport &var) {
if (var.nid == 0) {
// TODO: What's the right thing for this?
ERROR_LOG_REPORT(LOADER, "Var import with nid = 0, type = %d", var.type);
return;
}
if (!Memory::IsValidAddress(var.stubAddr)) {
ERROR_LOG_REPORT(LOADER, "Invalid address for var import nid = %08x, type = %d, addr = %08x", var.nid, var.type, var.stubAddr);
return;
}
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(var.moduleName)) {
continue;
}
// Look for exports currently loaded modules already have. Maybe it's available?
for (auto it = module->exportedVars.begin(), end = module->exportedVars.end(); it != end; ++it) {
if (it->Matches(var)) {
WriteVarSymbol(it->symAddr, var.stubAddr, var.type);
return;
}
}
}
// It hasn't been exported yet, but hopefully it will later.
INFO_LOG(LOADER, "Variable (%s,%08x) unresolved, storing for later resolving", var.moduleName, var.nid);
}
void ExportVarSymbol(const VarSymbolExport &var) {
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(var.moduleName)) {
continue;
}
// Look for imports currently loaded modules already have, hook it up right away.
for (auto it = module->importedVars.begin(), end = module->importedVars.end(); it != end; ++it) {
if (var.Matches(*it)) {
INFO_LOG(LOADER, "Resolving var %s/%08x", var.moduleName, var.nid);
WriteVarSymbol(var.symAddr, it->stubAddr, it->type);
}
}
}
}
void UnexportVarSymbol(const VarSymbolExport &var) {
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(var.moduleName)) {
continue;
}
// Look for imports modules that are *still* loaded have, and reverse them.
for (auto it = module->importedVars.begin(), end = module->importedVars.end(); it != end; ++it) {
if (var.Matches(*it)) {
INFO_LOG(LOADER, "Unresolving var %s/%08x", var.moduleName, var.nid);
WriteVarSymbol(var.symAddr, it->stubAddr, it->type, true);
}
}
}
}
void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting, const char *importingModule) {
// Prioritize HLE implementations.
// TODO: Or not?
if (FuncImportIsSyscall(func.moduleName, func.nid)) {
if (reimporting && Memory::Read_Instruction(func.stubAddr + 4) != GetSyscallOp(func.moduleName, func.nid)) {
WARN_LOG(LOADER, "Reimporting updated syscall %s", GetFuncName(func.moduleName, func.nid));
}
WriteSyscall(func.moduleName, func.nid, func.stubAddr);
currentMIPS->InvalidateICache(func.stubAddr, 8);
MIPSAnalyst::PrecompileFunction(func.stubAddr, 8);
return;
}
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(func.moduleName)) {
continue;
}
// Look for exports currently loaded modules already have. Maybe it's available?
for (auto it = module->exportedFuncs.begin(), end = module->exportedFuncs.end(); it != end; ++it) {
if (it->Matches(func)) {
if (reimporting && Memory::Read_Instruction(func.stubAddr) != MIPS_MAKE_J(it->symAddr)) {
WARN_LOG_REPORT(LOADER, "Reimporting: func import %s/%08x changed", func.moduleName, func.nid);
}
WriteFuncStub(func.stubAddr, it->symAddr);
currentMIPS->InvalidateICache(func.stubAddr, 8);
MIPSAnalyst::PrecompileFunction(func.stubAddr, 8);
return;
}
}
}
// It hasn't been exported yet, but hopefully it will later.
bool isKnownModule = GetModuleIndex(func.moduleName) != -1;
if (isKnownModule) {
// We used to report this, but I don't think it's very interesting anymore.
WARN_LOG(LOADER, "Unknown syscall from known module '%s': 0x%08x (import for '%s')", func.moduleName, func.nid, importingModule);
} else {
INFO_LOG(LOADER, "Function (%s,%08x) unresolved in '%s', storing for later resolving", func.moduleName, func.nid, importingModule);
}
if (isKnownModule || !reimporting) {
WriteFuncMissingStub(func.stubAddr, func.nid);
currentMIPS->InvalidateICache(func.stubAddr, 8);
}
}
void ExportFuncSymbol(const FuncSymbolExport &func) {
if (FuncImportIsSyscall(func.moduleName, func.nid)) {
// HLE covers this already - let's ignore the function.
WARN_LOG(LOADER, "Ignoring func export %s/%08x, already implemented in HLE.", func.moduleName, func.nid);
return;
}
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(func.moduleName)) {
continue;
}
// Look for imports currently loaded modules already have, hook it up right away.
for (auto it = module->importedFuncs.begin(), end = module->importedFuncs.end(); it != end; ++it) {
if (func.Matches(*it)) {
INFO_LOG(LOADER, "Resolving function %s/%08x", func.moduleName, func.nid);
WriteFuncStub(it->stubAddr, func.symAddr);
currentMIPS->InvalidateICache(it->stubAddr, 8);
MIPSAnalyst::PrecompileFunction(it->stubAddr, 8);
}
}
}
}
void UnexportFuncSymbol(const FuncSymbolExport &func) {
if (FuncImportIsSyscall(func.moduleName, func.nid)) {
// Oops, HLE covers this.
return;
}
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module || !module->ImportsOrExportsModuleName(func.moduleName)) {
continue;
}
// Look for imports modules that are *still* loaded have, and write back stubs.
for (auto it = module->importedFuncs.begin(), end = module->importedFuncs.end(); it != end; ++it) {
if (func.Matches(*it)) {
INFO_LOG(LOADER, "Unresolving function %s/%08x", func.moduleName, func.nid);
WriteFuncMissingStub(it->stubAddr, it->nid);
currentMIPS->InvalidateICache(it->stubAddr, 8);
}
}
}
}
void PSPModule::Cleanup() {
MIPSAnalyst::ForgetFunctions(textStart, textEnd);
loadedModules.erase(GetUID());
for (auto it = exportedVars.begin(), end = exportedVars.end(); it != end; ++it) {
UnexportVarSymbol(*it);
}
for (auto it = exportedFuncs.begin(), end = exportedFuncs.end(); it != end; ++it) {
UnexportFuncSymbol(*it);
}
if (memoryBlockAddr != 0 && nm.text_addr != 0 && memoryBlockSize >= nm.data_size + nm.bss_size + nm.text_size) {
DEBUG_LOG(LOADER, "Zeroing out module %s memory: %08x - %08x", nm.name, memoryBlockAddr, memoryBlockAddr + memoryBlockSize);
for (u32 i = 0; i < (u32)(nm.text_size + 3); i += 4) {
Memory::Write_U32(MIPS_MAKE_BREAK(1), nm.text_addr + i);
}
Memory::Memset(nm.text_addr + nm.text_size, -1, nm.data_size + nm.bss_size);
// Let's also invalidate, just to make sure it's cleared out for any future data.
currentMIPS->InvalidateICache(memoryBlockAddr, memoryBlockSize);
}
}
static void __SaveDecryptedEbootToStorageMedia(const u8 *decryptedEbootDataPtr, const u32 length) {
if (!decryptedEbootDataPtr) {
ERROR_LOG(SCEMODULE, "Error saving decrypted EBOOT.BIN: invalid pointer");
return;
}
if (length == 0) {
ERROR_LOG(SCEMODULE, "Error saving decrypted EBOOT.BIN: invalid length");
return;
}
const std::string filenameToDumpTo = g_paramSFO.GetValueString("DISC_ID") + ".BIN";
const std::string dumpDirectory = GetSysDirectory(DIRECTORY_DUMP);
const std::string fullPath = dumpDirectory + filenameToDumpTo;
// If the file already exists, don't dump it again.
if (File::Exists(fullPath)) {
INFO_LOG(SCEMODULE, "Decrypted EBOOT.BIN already exists for this game, skipping dump.");
return;
}
// Make sure the dump directory exists before continuing.
if (!File::Exists(dumpDirectory)) {
if (!File::CreateDir(dumpDirectory)) {
ERROR_LOG(SCEMODULE, "Unable to create directory for EBOOT dumping, aborting.");
return;
}
}
FILE *decryptedEbootFile = File::OpenCFile(fullPath, "wb");
if (!decryptedEbootFile) {
ERROR_LOG(SCEMODULE, "Unable to write decrypted EBOOT.");
return;
}
const size_t lengthToWrite = length;
fwrite(decryptedEbootDataPtr, sizeof(u8), lengthToWrite, decryptedEbootFile);
fclose(decryptedEbootFile);
INFO_LOG(SCEMODULE, "Successfully wrote decrypted EBOOT to %s", fullPath.c_str());
}
static bool IsHLEVersionedModule(const char *name) {
// TODO: Only some of these are currently known to be versioned.
// Potentially only sceMpeg_library matters.
// For now, we're just reporting version numbers.
for (size_t i = 0; i < ARRAY_SIZE(blacklistedModules); i++) {
if (!strncmp(name, blacklistedModules[i], 28)) {
return true;
}
}
static const char *otherModules[] = {
"sceAvcodec_driver",
"sceAudiocodec_Driver",
"sceAudiocodec",
"sceVideocodec_Driver",
"sceVideocodec",
"sceMpegbase_Driver",
"sceMpegbase",
"scePsmf_library",
"scePsmfP_library",
"scePsmfPlayer",
"sceSAScore",
"sceCcc_Library",
"SceParseHTTPheader_Library",
"SceParseURI_Library",
// Guessing.
"sceJpeg",
"sceJpeg_library",
"sceJpeg_Library",
};
for (size_t i = 0; i < ARRAY_SIZE(otherModules); i++) {
if (!strncmp(name, otherModules[i], 28)) {
return true;
}
}
return false;
}
static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr, bool reimporting) {
struct PspLibStubEntry {
u32_le name;
u16_le version;
u16_le flags;
u8 size;
u8 numVars;
u16_le numFuncs;
// each symbol has an associated nid; nidData is a pointer
// (in .rodata.sceNid section) to an array of longs, one
// for each function, which identifies the function whose
// address is to be inserted.
//
// The hash is the first 4 bytes of a SHA-1 hash of the function
// name. (Represented as a little-endian long, so the order
// of the bytes is reversed.)
u32_le nidData;
// the address of the function stubs where the function address jumps
// should be filled in
u32_le firstSymAddr;
// Optional, this is where var relocations are.
// They use the format: u32 addr, u32 nid, ...
// WARNING: May have garbage if size < 6.
u32_le varData;
// Not sure what this is yet, assume garbage for now.
// TODO: Tales of the World: Radiant Mythology 2 has something here?
u32_le extra;
};
// Can't run - we didn't keep track of the libstub entry.
if (module->libstub == 0) {
return false;
}
if (!Memory::IsValidRange(module->libstub, module->libstubend - module->libstub)) {
ERROR_LOG_REPORT(LOADER, "Garbage libstub address or end");
return false;
}
u32_le *entryPos = (u32_le *)Memory::GetPointerUnchecked(module->libstub);
u32_le *entryEnd = (u32_le *)Memory::GetPointerUnchecked(module->libstubend);
bool needReport = false;
while (entryPos < entryEnd) {
PspLibStubEntry *entry = (PspLibStubEntry *)entryPos;
entryPos += entry->size;
const char *modulename;
if (Memory::IsValidAddress(entry->name)) {
modulename = Memory::GetCharPointer(entry->name);
} else {
modulename = "(invalidname)";
needReport = true;
}
DEBUG_LOG(LOADER, "Importing Module %s, stubs at %08x", modulename, entry->firstSymAddr);
if (entry->size != 5 && entry->size != 6) {
if (entry->size != 7) {
WARN_LOG_REPORT(LOADER, "Unexpected module entry size %d", entry->size);
needReport = true;
} else if (entry->extra != 0) {
WARN_LOG_REPORT(LOADER, "Unexpected module entry with non-zero 7th value %08x", entry->extra);
needReport = true;
}
}
// If nidData is 0, only variables are being imported.
if (entry->nidData != 0) {
if (!Memory::IsValidAddress(entry->nidData)) {
ERROR_LOG_REPORT(LOADER, "Crazy nidData address %08x, skipping entire module", entry->nidData);
needReport = true;
continue;
}
FuncSymbolImport func;
strncpy(func.moduleName, modulename, KERNELOBJECT_MAX_NAME_LENGTH);
func.moduleName[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
u32_le *nidDataPtr = (u32_le *)Memory::GetPointer(entry->nidData);
for (int i = 0; i < entry->numFuncs; ++i) {
// This is the id of the import.
func.nid = nidDataPtr[i];
// This is the address to write the j and delay slot to.
func.stubAddr = entry->firstSymAddr + i * 8;
module->ImportFunc(func, reimporting);
}
if (firstImportStubAddr && (!*firstImportStubAddr || *firstImportStubAddr > (u32)entry->firstSymAddr))
*firstImportStubAddr = entry->firstSymAddr;
} else if (entry->numFuncs > 0) {
WARN_LOG_REPORT(LOADER, "Module entry with %d imports but no valid address", entry->numFuncs);
needReport = true;
}
// We skip vars when reimporting, since we might double-offset.
// We only reimport funcs, which can't be double-offset.
if (entry->varData != 0 && !reimporting) {
if (!Memory::IsValidAddress(entry->varData)) {
ERROR_LOG_REPORT(LOADER, "Crazy varData address %08x, skipping rest of module", entry->varData);
needReport = true;
continue;
}
VarSymbolImport var;
strncpy(var.moduleName, modulename, KERNELOBJECT_MAX_NAME_LENGTH);
var.moduleName[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
for (int i = 0; i < entry->numVars; ++i) {
u32 varRefsPtr = Memory::Read_U32(entry->varData + i * 8);
u32 nid = Memory::Read_U32(entry->varData + i * 8 + 4);
if (!Memory::IsValidAddress(varRefsPtr)) {
WARN_LOG_REPORT(LOADER, "Bad relocation list address for nid %08x in %s", nid, modulename);
continue;
}
u32_le *varRef = (u32_le *)Memory::GetPointer(varRefsPtr);
for (; *varRef != 0; ++varRef) {
var.nid = nid;
var.stubAddr = (*varRef & 0x03FFFFFF) << 2;
var.type = *varRef >> 26;
module->ImportVar(var);
}
}
} else if (entry->numVars > 0 && !reimporting) {
WARN_LOG_REPORT(LOADER, "Module entry with %d var imports but no valid address", entry->numVars);
needReport = true;
}
DEBUG_LOG(LOADER, "-------------------------------------------------------------");
}
if (needReport) {
std::string debugInfo;
entryPos = (u32_le *)Memory::GetPointer(module->libstub);
while (entryPos < entryEnd) {
PspLibStubEntry *entry = (PspLibStubEntry *)entryPos;
entryPos += entry->size;
char temp[512];
const char *modulename;
if (Memory::IsValidAddress(entry->name)) {
modulename = Memory::GetCharPointer(entry->name);
} else {
modulename = "(invalidname)";
}
snprintf(temp, sizeof(temp), "%s ver=%04x, flags=%04x, size=%d, numVars=%d, numFuncs=%d, nidData=%08x, firstSym=%08x, varData=%08x, extra=%08x\n",
modulename, entry->version, entry->flags, entry->size, entry->numVars, entry->numFuncs, entry->nidData, entry->firstSymAddr, entry->size >= 6 ? entry->varData : 0, entry->size >= 7 ? entry->extra : 0);
debugInfo += temp;
}
Reporting::ReportMessage("Module linking debug info:\n%s", debugInfo.c_str());
}
return true;
}
static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) {
int err;
z_stream stream;
u8 *outBufferPtr;
outBufferPtr = OutBuffer;
stream.next_in = InBuffer;
stream.avail_in = (uInt)OutBufferLength;
stream.next_out = outBufferPtr;
stream.avail_out = (uInt)OutBufferLength;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
err = inflateInit2(&stream, 16+MAX_WBITS);
if (err != Z_OK) {
return -1;
}
err = inflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
inflateEnd(&stream);
return -2;
}
inflateEnd(&stream);
return stream.total_out;
}
static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) {
PSPModule *module = new PSPModule();
kernelObjects.Create(module);
loadedModules.insert(module->GetUID());
memset(&module->nm, 0, sizeof(module->nm));
module->nm.modid = module->GetUID();
bool reportedModule = false;
u32 devkitVersion = 0;
u8 *newptr = 0;
u32_le *magicPtr = (u32_le *) ptr;
if (*magicPtr == 0x4543537e) { // "~SCE"
INFO_LOG(SCEMODULE, "~SCE module, skipping header");
u32 headerSize = *(u32_le*)(ptr + 4);
ptr += headerSize;
elfSize -= headerSize;
magicPtr = (u32_le *)ptr;
}
*magic = *magicPtr;
if (*magic == 0x5053507e) { // "~PSP"
DEBUG_LOG(SCEMODULE, "Decrypting ~PSP file");
PSP_Header *head = (PSP_Header*)ptr;
devkitVersion = head->devkitversion;
if (IsHLEVersionedModule(head->modname)) {
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", head->modname, ver, head->devkitversion);
reportedModule = true;
if (!strcmp(head->modname, "sceMpeg_library")) {
__MpegLoadModule(ver);
}
if (!strcmp(head->modname, "scePsmfP_library") || !strcmp(head->modname, "scePsmfPlayer")) {
__PsmfPlayerLoadModule(head->devkitversion);
}
}
const u8 *in = ptr;
const auto isGzip = head->comp_attribute & 1;
// Kind of odd.
u32 size = head->psp_size;
if (size > elfSize) {
*error_string = StringFromFormat("ELF/PRX truncated: %d > %d", (int)size, (int)elfSize);
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
return nullptr;
}
const auto maxElfSize = std::max(head->elf_size, head->psp_size);
newptr = new u8[maxElfSize];
ptr = newptr;
magicPtr = (u32_le *)ptr;
int ret = pspDecryptPRX(in, (u8*)ptr, head->psp_size);
if (reportedModule) {
// This should happen for all "kernel" modules.
*error_string = "Missing key";
delete [] newptr;
module->isFake = true;
strncpy(module->nm.name, head->modname, ARRAY_SIZE(module->nm.name));
module->nm.entry_addr = -1;
module->nm.gp_value = -1;
// Let's still try to allocate it. It may use user memory.
u32 totalSize = 0;
for (int i = 0; i < 4; ++i) {
if (head->seg_size[i]) {
const u32 align = head->seg_align[i] - 1;
totalSize = ((totalSize + align) & ~align) + head->seg_size[i];
}
}
bool kernelModule = (head->attribute & 0x1000) != 0;
BlockAllocator &memblock = kernelModule ? kernelMemory : userMemory;
size_t n = strnlen(head->modname, 28);
const std::string modName = "ELF/" + std::string(head->modname, n);
u32 addr = memblock.Alloc(totalSize, fromTop, modName.c_str());
if (addr == (u32)-1) {
error = SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
} else {
error = 0;
module->memoryBlockAddr = addr;
module->memoryBlockSize = totalSize;
}
return module;
} else if (ret <= 0) {
ERROR_LOG(SCEMODULE, "Failed decrypting PRX! That's not normal! ret = %i\n", ret);
Reporting::ReportMessage("Failed decrypting the PRX (ret = %i, size = %i, psp_size = %i)!", ret, head->elf_size, head->psp_size);
// Fall through to safe exit in the next check.
} else {
// TODO: Is this right?
module->nm.bss_size = head->bss_size;
// decompress if required
if (isGzip)
{
auto temp = new u8[ret];
memcpy(temp, ptr, ret);
gzipDecompress((u8 *)ptr, maxElfSize, temp);
delete[] temp;
}
// If we've made it this far, it should be safe to dump.
if (g_Config.bDumpDecryptedEboot) {
INFO_LOG(SCEMODULE, "Dumping decrypted EBOOT.BIN to file.");
const u32 dumpLength = ret;
__SaveDecryptedEbootToStorageMedia(ptr, dumpLength);
}
}
}
// DO NOT change to else if, see above.
if (*magicPtr != 0x464c457f) {
ERROR_LOG(SCEMODULE, "Wrong magic number %08x", *magicPtr);
*error_string = "File corrupt";
if (newptr)
delete [] newptr;
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
error = SCE_KERNEL_ERROR_UNSUPPORTED_PRX_TYPE;
return nullptr;
}
// Open ELF reader
ElfReader reader((void*)ptr, elfSize);
int result = reader.LoadInto(loadAddress, fromTop);
if (result != SCE_KERNEL_ERROR_OK) {
ERROR_LOG(SCEMODULE, "LoadInto failed with error %08x",result);
if (newptr)
delete [] newptr;
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
error = result;
return nullptr;
}
module->memoryBlockAddr = reader.GetVaddr();
module->memoryBlockSize = reader.GetTotalSize();
currentMIPS->InvalidateICache(module->memoryBlockAddr, module->memoryBlockSize);
SectionID sceModuleInfoSection = reader.GetSectionByName(".rodata.sceModuleInfo");
PspModuleInfo *modinfo;
if (sceModuleInfoSection != -1)
modinfo = (PspModuleInfo *)Memory::GetPointer(reader.GetSectionAddr(sceModuleInfoSection));
else
modinfo = (PspModuleInfo *)Memory::GetPointer(reader.GetSegmentVaddr(0) + (reader.GetSegmentPaddr(0) & 0x7FFFFFFF) - reader.GetSegmentOffset(0));
module->nm.nsegment = reader.GetNumSegments();
module->nm.attribute = modinfo->moduleAttrs;
module->nm.version[0] = modinfo->moduleVersion & 0xFF;
module->nm.version[1] = modinfo->moduleVersion >> 8;
module->nm.data_size = 0;
// TODO: Is summing them up correct? Must not be since the numbers aren't exactly right.
for (int i = 0; i < reader.GetNumSegments(); ++i) {
if (i < (int)ARRAY_SIZE(module->nm.segmentaddr)) {
module->nm.segmentsize[i] = reader.GetSegmentMemSize(i);
if (module->nm.segmentsize[i] != 0) {
module->nm.segmentaddr[i] = reader.GetSegmentVaddr(i);
} else {
module->nm.segmentaddr[i] = 0;
}
}
module->nm.data_size += reader.GetSegmentDataSize(i);
}
module->nm.gp_value = modinfo->gp;
strncpy(module->nm.name, modinfo->name, ARRAY_SIZE(module->nm.name));
// Let's also get a truncated version.
char moduleName[29] = {0};
strncpy(moduleName, modinfo->name, ARRAY_SIZE(module->nm.name));
// Check for module blacklist - we don't allow games to load these modules from disc
// as we have HLE implementations and the originals won't run in the emu because they
// directly access hardware or for other reasons.
for (u32 i = 0; i < ARRAY_SIZE(blacklistedModules); i++) {
if (strncmp(modinfo->name, blacklistedModules[i], ARRAY_SIZE(modinfo->name)) == 0) {
module->isFake = true;
}
}
if (!module->isFake && module->memoryBlockAddr != 0) {
g_symbolMap->AddModule(moduleName, module->memoryBlockAddr, module->memoryBlockSize);
}
SectionID textSection = reader.GetSectionByName(".text");
if (textSection != -1) {
module->textStart = reader.GetSectionAddr(textSection);
u32 textSize = reader.GetSectionSize(textSection);
module->textEnd = module->textStart + textSize - 4;
module->nm.text_addr = module->textStart;
module->nm.text_size = reader.GetTotalTextSize();
} else {
module->nm.text_addr = 0;
module->nm.text_size = 0;
}
module->nm.bss_size = reader.GetTotalSectionSizeByPrefix(".bss");
module->nm.data_size = reader.GetTotalDataSize() - module->nm.bss_size;
module->libstub = modinfo->libstub;
module->libstubend = modinfo->libstubend;
INFO_LOG(LOADER, "Module %s: %08x %08x %08x", modinfo->name, modinfo->gp, modinfo->libent, modinfo->libstub);
DEBUG_LOG(LOADER,"===================================================");
u32 firstImportStubAddr = 0;
KernelImportModuleFuncs(module, &firstImportStubAddr);
if (textSection == -1) {
module->textStart = reader.GetVaddr();
module->textEnd = firstImportStubAddr - 4;
// Reference Jpcsp.
if (reader.GetFirstSegmentAlign() > 0)
module->textStart &= ~(reader.GetFirstSegmentAlign() - 1);
// PSP set these values even if no section.
module->nm.text_addr = module->textStart;
module->nm.text_size = reader.GetTotalTextSizeFromSeg();
}
if (!module->isFake) {
bool scan = true;
#if defined(MOBILE_DEVICE)
scan = g_Config.bFuncReplacements;
#endif
// If the ELF has debug symbols, don't add entries to the symbol table.
bool insertSymbols = scan && !reader.LoadSymbols();
std::vector<SectionID> codeSections = reader.GetCodeSections();
for (SectionID id : codeSections) {
u32 start = reader.GetSectionAddr(id);
// Note: scan end is inclusive.
u32 end = start + reader.GetSectionSize(id) - 4;
u32 len = end + 4 - start;
if (!Memory::IsValidRange(start, len)) {
ERROR_LOG(LOADER, "Bad section %08x (len %08x) of section %d", start, len, id);
continue;
}
if (start < module->textStart)
module->textStart = start;
if (end > module->textEnd)
module->textEnd = end;
if (scan) {
insertSymbols = MIPSAnalyst::ScanForFunctions(start, end, insertSymbols);
}
}
// Some games don't have any sections at all.
if (scan && codeSections.empty()) {
u32 scanStart = module->textStart;
u32 scanEnd = module->textEnd;
if (Memory::IsValidRange(scanStart, scanEnd - scanStart)) {
// Skip the exports and imports sections, they're not code.
if (scanEnd >= std::min(modinfo->libent, modinfo->libstub)) {
insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::min(modinfo->libent, modinfo->libstub) - 4, insertSymbols);
scanStart = std::min(modinfo->libentend, modinfo->libstubend);
}
if (scanEnd >= std::max(modinfo->libent, modinfo->libstub)) {
insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::max(modinfo->libent, modinfo->libstub) - 4, insertSymbols);
scanStart = std::max(modinfo->libentend, modinfo->libstubend);
}
insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, scanEnd, insertSymbols);
} else {
ERROR_LOG(LOADER, "Bad text scan range %08x-%08x", scanStart, scanEnd);
}
}
if (scan) {
MIPSAnalyst::FinalizeScan(insertSymbols);
}
}
// Look at the exports, too.
struct PspLibEntEntry
{
u32_le name; /* ent's name (module name) address */
u16_le version;
u16_le flags;
u8 size;
u8 vcount;
u16_le fcount;
u32_le resident;
u16_le vcountNew;
u8 unknown1;
u8 unknown2;
};
const u32_le *entPos = (u32_le *)Memory::GetPointer(modinfo->libent);
const u32_le *entEnd = (u32_le *)Memory::GetPointer(modinfo->libentend);
for (int m = 0; entPos < entEnd; ++m) {
const PspLibEntEntry *ent = (const PspLibEntEntry *)entPos;
entPos += ent->size;
if (ent->size == 0) {
WARN_LOG_REPORT(LOADER, "Invalid export entry size %d", ent->size);
entPos += 4;
continue;
}
u32 variableCount = ent->size <= 4 ? ent->vcount : std::max((u32)ent->vcount , (u32)ent->vcountNew);
const char *name;
if (Memory::IsValidAddress(ent->name)) {
name = Memory::GetCharPointer(ent->name);
} else if (ent->name == 0) {
name = module->nm.name;
} else {
name = "invalid?";
}
INFO_LOG(LOADER, "Exporting ent %d named %s, %d funcs, %d vars, resident %08x", m, name, ent->fcount, ent->vcount, ent->resident);
if (!Memory::IsValidAddress(ent->resident)) {
if (ent->fcount + variableCount > 0) {
WARN_LOG_REPORT(LOADER, "Invalid export resident address %08x", ent->resident);
}
continue;
}
u32_le *residentPtr = (u32_le *)Memory::GetPointer(ent->resident);
u32_le *exportPtr = residentPtr + ent->fcount + variableCount;
if (ent->size != 4 && ent->unknown1 != 0 && ent->unknown2 != 0) {
WARN_LOG_REPORT(LOADER, "Unexpected export module entry size %d, vcountNew=%08x, unknown1=%08x, unknown2=%08x", ent->size, ent->vcountNew, ent->unknown1, ent->unknown2);
}
FuncSymbolExport func;
strncpy(func.moduleName, name, KERNELOBJECT_MAX_NAME_LENGTH);
func.moduleName[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
for (u32 j = 0; j < ent->fcount; j++) {
u32 nid = residentPtr[j];
u32 exportAddr = exportPtr[j];
switch (nid) {
case NID_MODULE_START:
module->nm.module_start_func = exportAddr;
break;
case NID_MODULE_STOP:
module->nm.module_stop_func = exportAddr;
break;
case NID_MODULE_REBOOT_BEFORE:
module->nm.module_reboot_before_func = exportAddr;
break;
case NID_MODULE_REBOOT_PHASE:
module->nm.module_reboot_phase_func = exportAddr;
break;
case NID_MODULE_BOOTSTART:
module->nm.module_bootstart_func = exportAddr;
break;
default:
func.nid = nid;
func.symAddr = exportAddr;
if (ent->name == 0) {
WARN_LOG_REPORT(HLE, "Exporting func from syslib export: %08x", nid);
}
module->ExportFunc(func);
}
}
VarSymbolExport var;
strncpy(var.moduleName, name, KERNELOBJECT_MAX_NAME_LENGTH);
var.moduleName[KERNELOBJECT_MAX_NAME_LENGTH] = '\0';
for (u32 j = 0; j < variableCount; j++) {
u32 nid = residentPtr[ent->fcount + j];
u32 exportAddr = exportPtr[ent->fcount + j];
int size;
switch (nid) {
case NID_MODULE_INFO:
// Points to a PspModuleInfo, often the exact one .rodata.sceModuleInfo points to.
break;
case NID_MODULE_START_THREAD_PARAMETER:
size = Memory::Read_U32(exportAddr);
if (size == 0)
break;
else if (size != 3)
WARN_LOG_REPORT(LOADER, "Strange value at module_start_thread_parameter export: %08x", Memory::Read_U32(exportAddr));
module->nm.module_start_thread_priority = Memory::Read_U32(exportAddr + 4);
module->nm.module_start_thread_stacksize = Memory::Read_U32(exportAddr + 8);
module->nm.module_start_thread_attr = Memory::Read_U32(exportAddr + 12);
break;
case NID_MODULE_STOP_THREAD_PARAMETER:
size = Memory::Read_U32(exportAddr);
if (size == 0)
break;
else if (size != 3)
WARN_LOG_REPORT(LOADER, "Strange value at module_stop_thread_parameter export: %08x", Memory::Read_U32(exportAddr));
module->nm.module_stop_thread_priority = Memory::Read_U32(exportAddr + 4);
module->nm.module_stop_thread_stacksize = Memory::Read_U32(exportAddr + 8);
module->nm.module_stop_thread_attr = Memory::Read_U32(exportAddr + 12);
break;
case NID_MODULE_REBOOT_BEFORE_THREAD_PARAMETER:
size = Memory::Read_U32(exportAddr);
if (size == 0)
break;
else if (size != 3)
WARN_LOG_REPORT(LOADER, "Strange value at module_reboot_before_thread_parameter export: %08x", Memory::Read_U32(exportAddr));
module->nm.module_reboot_before_thread_priority = Memory::Read_U32(exportAddr + 4);
module->nm.module_reboot_before_thread_stacksize = Memory::Read_U32(exportAddr + 8);
module->nm.module_reboot_before_thread_attr = Memory::Read_U32(exportAddr + 12);
break;
case NID_MODULE_SDK_VERSION:
DEBUG_LOG(LOADER, "Module SDK: %08x", Memory::Read_U32(exportAddr));
devkitVersion = Memory::Read_U32(exportAddr);
break;
default:
var.nid = nid;
var.symAddr = exportAddr;
if (ent->name == 0) {
WARN_LOG_REPORT(HLE, "Exporting var from syslib export: %08x", nid);
}
module->ExportVar(var);
break;
}
}
}
if (!module->isFake) {
module->nm.entry_addr = reader.GetEntryPoint();
// use module_start_func instead of entry_addr if entry_addr is 0
if (module->nm.entry_addr == 0)
module->nm.entry_addr = module->nm.module_start_func;
MIPSAnalyst::PrecompileFunctions();
} else {
module->nm.entry_addr = -1;
}
if (newptr)
delete [] newptr;
if (!reportedModule && IsHLEVersionedModule(modinfo->name)) {
INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
if (!strcmp(modinfo->name, "sceMpeg_library")) {
__MpegLoadModule(modinfo->moduleVersion);
}
if (!strcmp(modinfo->name, "scePsmfP_library") || !strcmp(modinfo->name, "scePsmfPlayer")) {
__PsmfPlayerLoadModule(devkitVersion);
}
}
u32 moduleSize = sizeof(module->nm);
char tag[32];
snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid);
module->modulePtr = kernelMemory.Alloc(moduleSize, true, tag);
// Fill the struct.
if (Memory::IsValidAddress(module->modulePtr))
Memory::WriteStruct(module->modulePtr, &module->nm);
error = 0;
return module;
}
SceUID KernelLoadModule(const std::string &filename, std::string *error_string) {
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
if (!info.exists)
return SCE_KERNEL_ERROR_NOFILE;
std::vector<uint8_t> buffer;
buffer.resize((size_t)info.size);
u32 handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);
pspFileSystem.ReadFile(handle, &buffer[0], info.size);
pspFileSystem.CloseFile(handle);
u32 error = SCE_KERNEL_ERROR_ILLEGAL_OBJECT;
u32 magic;
PSPModule *module = __KernelLoadELFFromPtr(&buffer[0], buffer.size(), 0, false, error_string, &magic, error);
if (module == nullptr)
return error;
return module->GetUID();
}
static PSPModule *__KernelLoadModule(u8 *fileptr, size_t fileSize, SceKernelLMOption *options, std::string *error_string) {
PSPModule *module = nullptr;
// Check for PBP
if (memcmp(fileptr, "\0PBP", 4) == 0) {
// PBP!
u32_le version;
memcpy(&version, fileptr + 4, 4);
u32_le offset0, offsets[16];
memcpy(&offset0, fileptr + 8, 4);
int numfiles = (offset0 - 8)/4;
offsets[0] = offset0;
for (int i = 1; i < numfiles; i++)
memcpy(&offsets[i], fileptr + 12 + 4*i, 4);
if (offsets[6] > fileSize) {
// File is too small to fully contain the ELF! Must have been truncated.
*error_string = "ELF file truncated - can't load";
return nullptr;
}
u32 magic = 0;
u8 *temp = nullptr;
size_t elfSize = offsets[6] - offsets[5];
if (offsets[5] & 3) {
// Our loader does NOT like to load from an unaligned address on ARM! Copy to a new block.
temp = new u8[elfSize];
memcpy(temp, fileptr + offsets[5], elfSize);
INFO_LOG(LOADER, "PBP: ELF unaligned (%d: %d), aligning!", offsets[5], offsets[5] & 3);
}
u32 error;
module = __KernelLoadELFFromPtr(temp ? temp : fileptr + offsets[5], elfSize, PSP_GetDefaultLoadAddress(), false, error_string, &magic, error);
if (temp) {
delete [] temp;
}
} else {
u32 error;
u32 magic = 0;
module = __KernelLoadELFFromPtr(fileptr, fileSize, PSP_GetDefaultLoadAddress(), false, error_string, &magic, error);
}
return module;
}
static void __KernelStartModule(PSPModule *m, int args, const char *argp, SceKernelSMOption *options) {
m->nm.status = MODULE_STATUS_STARTED;
if (m->nm.module_start_func != 0 && m->nm.module_start_func != (u32)-1)
{
if (m->nm.module_start_func != m->nm.entry_addr)
WARN_LOG_REPORT(LOADER, "Main module has start func (%08x) different from entry (%08x)?", m->nm.module_start_func, m->nm.entry_addr);
// TODO: Should we try to run both?
currentMIPS->pc = m->nm.module_start_func;
}
SceUID threadID = __KernelSetupRootThread(m->GetUID(), args, argp, options->priority, options->stacksize, options->attribute);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
if (HLEPlugins::Load()) {
KernelRotateThreadReadyQueue(0);
__KernelReSchedule("Started plugins");
}
}
u32 __KernelGetModuleGP(SceUID uid) {
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
if (module) {
return module->nm.gp_value;
} else {
return 0;
}
}
bool KernelModuleIsKernelMode(SceUID uid) {
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
if (module) {
return (module->nm.attribute & 0x1000) != 0;
} else {
return false;
}
}
void __KernelLoadReset() {
// Wipe kernel here, loadexec should reset the entire system
if (__KernelIsRunning()) {
u32 error;
while (!loadedModules.empty()) {
SceUID moduleID = *loadedModules.begin();
PSPModule *module = kernelObjects.Get<PSPModule>(moduleID, error);
if (module) {
module->Cleanup();
} else {
// An invalid module. We need to remove it or we'll loop forever.
WARN_LOG(LOADER, "Invalid module still marked as loaded on loadexec");
loadedModules.erase(moduleID);
}
}
Replacement_Shutdown();
__KernelShutdown();
// HLE needs to be reset here
HLEShutdown();
Replacement_Init();
HLEInit();
}
__KernelModuleInit();
__KernelInit();
}
bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string) {
SceKernelLoadExecParam param;
PSP_SetLoading("Loading exec...");
if (paramPtr)
Memory::ReadStruct(paramPtr, &param);
else
memset(&param, 0, sizeof(SceKernelLoadExecParam));
u8 *param_argp = 0;
u8 *param_key = 0;
if (param.args > 0) {
u32 argpAddr = param.argp;
param_argp = new u8[param.args];
Memory::Memcpy(param_argp, argpAddr, param.args);
}
if (param.keyp != 0) {
u32 keyAddr = param.keyp;
size_t keylen = strlen(Memory::GetCharPointer(keyAddr))+1;
param_key = new u8[keylen];
Memory::Memcpy(param_key, keyAddr, (u32)keylen);
}
__KernelLoadReset();
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
if (!info.exists) {
ERROR_LOG(LOADER, "Failed to load executable %s - file doesn't exist", filename);
*error_string = StringFromFormat("Could not find executable %s", filename);
if (paramPtr) {
if (param_argp) delete[] param_argp;
if (param_key) delete[] param_key;
}
__KernelShutdown();
return false;
}
u32 handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);
u8 *temp = new u8[(int)info.size + 0x01000000];
pspFileSystem.ReadFile(handle, temp, (size_t)info.size);
PSP_SetLoading("Loading modules...");
PSPModule *module = __KernelLoadModule(temp, (size_t)info.size, 0, error_string);
if (!module || module->isFake) {
if (module) {
module->Cleanup();
kernelObjects.Destroy<PSPModule>(module->GetUID());
}
ERROR_LOG(LOADER, "Failed to load module %s", filename);
*error_string = "Failed to load executable: " + *error_string;
delete [] temp;
if (paramPtr) {
if (param_argp) delete[] param_argp;
if (param_key) delete[] param_key;
}
return false;
}
host->NotifySymbolMapUpdated();
mipsr4k.pc = module->nm.entry_addr;
INFO_LOG(LOADER, "Module entry: %08x", mipsr4k.pc);
delete [] temp;
pspFileSystem.CloseFile(handle);
SceKernelSMOption option;
option.size = sizeof(SceKernelSMOption);
option.attribute = PSP_THREAD_ATTR_USER;
option.mpidstack = 2;
option.priority = 0x20;
option.stacksize = 0x40000; // crazy? but seems to be the truth
// Replace start options with module-specified values if they exist.
if (module->nm.module_start_thread_attr != 0)
option.attribute = module->nm.module_start_thread_attr;
if (module->nm.module_start_thread_priority != 0)
option.priority = module->nm.module_start_thread_priority;
if (module->nm.module_start_thread_stacksize != 0)
option.stacksize = module->nm.module_start_thread_stacksize;
PSP_SetLoading("Starting modules...");
if (paramPtr)
__KernelStartModule(module, param.args, (const char*)param_argp, &option);
else
__KernelStartModule(module, (u32)strlen(filename) + 1, filename, &option);
__KernelStartIdleThreads(module->GetUID());
if (param_argp) delete[] param_argp;
if (param_key) delete[] param_key;
hleSkipDeadbeef();
return true;
}
bool __KernelLoadGEDump(const std::string &base_filename, std::string *error_string) {
__KernelLoadReset();
PSP_SetLoading("Generating code...");
mipsr4k.pc = PSP_GetUserMemoryBase();
const static u32_le runDumpCode[] = {
// Save the filename.
MIPS_MAKE_ORI(MIPS_REG_S0, MIPS_REG_A0, 0),
MIPS_MAKE_ORI(MIPS_REG_S1, MIPS_REG_A1, 0),
// Call the actual render.
MIPS_MAKE_SYSCALL("FakeSysCalls", "__KernelGPUReplay"),
// Make sure we don't get out of sync.
MIPS_MAKE_LUI(MIPS_REG_A0, 0),
MIPS_MAKE_SYSCALL("sceGe_user", "sceGeDrawSync"),
// Set the return address after the entry which saved the filename.
MIPS_MAKE_LUI(MIPS_REG_RA, mipsr4k.pc >> 16),
MIPS_MAKE_ADDIU(MIPS_REG_RA, MIPS_REG_RA, 8),
// Wait for the next vblank to render again.
MIPS_MAKE_JR_RA(),
MIPS_MAKE_SYSCALL("sceDisplay", "sceDisplayWaitVblankStart"),
// This never gets reached, just here to be safe.
MIPS_MAKE_BREAK(0),
};
for (size_t i = 0; i < ARRAY_SIZE(runDumpCode); ++i) {
Memory::WriteUnchecked_U32(runDumpCode[i], mipsr4k.pc + (int)i * sizeof(u32_le));
}
PSPModule *module = new PSPModule();
kernelObjects.Create(module);
loadedModules.insert(module->GetUID());
memset(&module->nm, 0, sizeof(module->nm));
module->isFake = true;
module->nm.entry_addr = mipsr4k.pc;
module->nm.gp_value = -1;
SceUID threadID = __KernelSetupRootThread(module->GetUID(), (int)base_filename.size(), base_filename.data(), 0x20, 0x1000, 0);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
__KernelStartIdleThreads(module->GetUID());
return true;
}
void __KernelGPUReplay() {
// Special ABI: s0 and s1 are the "args". Not null terminated.
const char *filenamep = Memory::GetCharPointer(currentMIPS->r[MIPS_REG_S1]);
if (!filenamep) {
ERROR_LOG(G3D, "Failed to load dump filename");
Core_Stop();
return;
}
std::string filename(filenamep, currentMIPS->r[MIPS_REG_S0]);
if (!GPURecord::RunMountedReplay(filename)) {
Core_Stop();
}
}
int sceKernelLoadExec(const char *filename, u32 paramPtr)
{
std::string exec_filename = filename;
PSPFileInfo info = pspFileSystem.GetFileInfo(exec_filename);
// If there's an EBOOT.BIN, redirect to that instead.
if (info.exists && endsWith(exec_filename, "/BOOT.BIN")) {
std::string eboot_filename = exec_filename.substr(0, exec_filename.length() - strlen("BOOT.BIN")) + "EBOOT.BIN";
PSPFileInfo eboot_info = pspFileSystem.GetFileInfo(eboot_filename);
if (eboot_info.exists) {
exec_filename = eboot_filename;
info = eboot_info;
}
}
if (!info.exists) {
ERROR_LOG(LOADER, "sceKernelLoadExec(%s, ...): File does not exist", filename);
return SCE_KERNEL_ERROR_NOFILE;
}
s64 size = (s64)info.size;
if (!size) {
ERROR_LOG(LOADER, "sceKernelLoadExec(%s, ...): File is size 0", filename);
return SCE_KERNEL_ERROR_ILLEGAL_OBJECT;
}
DEBUG_LOG(SCEMODULE, "sceKernelLoadExec(name=%s,...): loading %s", filename, exec_filename.c_str());
std::string error_string;
if (!__KernelLoadExec(exec_filename.c_str(), paramPtr, &error_string)) {
ERROR_LOG(SCEMODULE, "sceKernelLoadExec failed: %s", error_string.c_str());
Core_UpdateState(CORE_RUNTIME_ERROR);
return -1;
}
if (gpu) {
gpu->Reinitialize();
}
return 0;
}
u32 sceKernelLoadModule(const char *name, u32 flags, u32 optionAddr) {
if (!name) {
return hleLogError(LOADER, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad filename");
}
for (size_t i = 0; i < ARRAY_SIZE(lieAboutSuccessModules); i++) {
if (!strcmp(name, lieAboutSuccessModules[i])) {
PSPModule *module = new PSPModule();
kernelObjects.Create(module);
loadedModules.insert(module->GetUID());
memset(&module->nm, 0, sizeof(module->nm));
module->isFake = true;
module->nm.entry_addr = -1;
module->nm.gp_value = -1;
u32 moduleSize = sizeof(module->nm);
char tag[32];
snprintf(tag, sizeof(tag), "SceModule-%d", module->nm.modid);
module->modulePtr = kernelMemory.Alloc(moduleSize, true, tag);
// Fill the struct.
if(Memory::IsValidAddress(module->modulePtr))
Memory::WriteStruct(module->modulePtr, &module->nm);
// TODO: It would be more ideal to allocate memory for this module.
return hleLogSuccessInfoI(LOADER, module->GetUID(), "created fake module");
}
}
PSPFileInfo info = pspFileSystem.GetFileInfo(name);
s64 size = (s64)info.size;
if (!info.exists) {
const u32 error = hleLogError(LOADER, SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND, "file does not exist");
return hleDelayResult(error, "module loaded", 500);
}
if (!size) {
const u32 error = hleLogError(LOADER, SCE_KERNEL_ERROR_FILEERR, "module file size is 0");
return hleDelayResult(error, "module loaded", 500);
}
// We log before hand because ELF loading logs a bunch.
DEBUG_LOG(LOADER, "sceKernelLoadModule(%s, %08x)", name, flags);
if (flags != 0) {
WARN_LOG_REPORT(LOADER, "sceKernelLoadModule: unsupported flags: %08x", flags);
}
SceKernelLMOption *lmoption = 0;
if (optionAddr) {
lmoption = (SceKernelLMOption *)Memory::GetPointer(optionAddr);
if (lmoption->position < PSP_SMEM_Low || lmoption->position > PSP_SMEM_HighAligned) {
ERROR_LOG_REPORT(LOADER, "sceKernelLoadModule(%s): invalid position (%i)", name, (int)lmoption->position);
return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE, "module loaded", 500);
}
if (lmoption->position == PSP_SMEM_LowAligned || lmoption->position == PSP_SMEM_HighAligned) {
ERROR_LOG_REPORT(LOADER, "sceKernelLoadModule(%s): invalid position (aligned)", name);
return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, "module loaded", 500);
}
if (lmoption->position == PSP_SMEM_Addr) {
ERROR_LOG_REPORT(LOADER, "sceKernelLoadModule(%s): invalid position (fixed)", name);
return hleDelayResult(SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED, "module loaded", 500);
}
WARN_LOG_REPORT(LOADER, "sceKernelLoadModule: unsupported options size=%08x, flags=%08x, pos=%d, access=%d, data=%d, text=%d", lmoption->size, lmoption->flags, lmoption->position, lmoption->access, lmoption->mpiddata, lmoption->mpidtext);
}
PSPModule *module = nullptr;
u8 *temp = new u8[(int)size];
u32 handle = pspFileSystem.OpenFile(name, FILEACCESS_READ);
pspFileSystem.ReadFile(handle, temp, (size_t)size);
u32 magic;
u32 error;
std::string error_string;
module = __KernelLoadELFFromPtr(temp, (size_t)size, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, error);
delete [] temp;
pspFileSystem.CloseFile(handle);
if (!module) {
if (magic == 0x46535000) {
ERROR_LOG(LOADER, "Game tried to load an SFO as a module. Go figure? Magic = %08x", magic);
// TODO: What's actually going on here?
error = -1;
return hleDelayResult(error, "module loaded", 500);
}
if (info.name == "BOOT.BIN") {
NOTICE_LOG_REPORT(LOADER, "Module %s is blacklisted or undecryptable - we try __KernelLoadExec", name);
// Name might get deleted.
const std::string safeName = name;
if (gpu) {
gpu->Reinitialize();
}
return __KernelLoadExec(safeName.c_str(), 0, &error_string);
} else {
hleLogError(LOADER, error, "failed to load");
return hleDelayResult(error, "module loaded", 500);
}
}
if (lmoption) {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModule(name=%s,flag=%08x,%08x,%08x,%08x,position = %08x)",
module->GetUID(),name,flags,
lmoption->size,lmoption->mpidtext,lmoption->mpiddata,lmoption->position);
} else {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModule(name=%s,flag=%08x,(...))", module->GetUID(), name, flags);
}
// TODO: This is not the right timing and probably not the right wait type, just an approximation.
return hleDelayResult(module->GetUID(), "module loaded", 500);
}
static u32 sceKernelLoadModuleNpDrm(const char *name, u32 flags, u32 optionAddr)
{
DEBUG_LOG(LOADER, "sceKernelLoadModuleNpDrm(%s, %08x)", name, flags);
return sceKernelLoadModule(name, flags, optionAddr);
}
int KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait) {
if (needsWait) {
*needsWait = false;
}
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module) {
return error;
}
u32 priority = 0x20;
u32 stacksize = 0x40000;
int attribute = module->nm.attribute;
u32 entryAddr = module->nm.entry_addr;
if (module->nm.module_start_func != 0 && module->nm.module_start_func != (u32)-1) {
entryAddr = module->nm.module_start_func;
if (module->nm.module_start_thread_attr != 0)
attribute = module->nm.module_start_thread_attr;
} else if (entryAddr == (u32)-1 || entryAddr == module->memoryBlockAddr - 1) {
if (smoption) {
// TODO: Does sceKernelStartModule() really give an error when no entry only if you pass options?
attribute = smoption->attribute;
} else {
// TODO: Why are we just returning the module ID in this case?
WARN_LOG(SCEMODULE, "sceKernelStartModule(): module has no start or entry func");
module->nm.status = MODULE_STATUS_STARTED;
return moduleId;
}
}
if (Memory::IsValidAddress(entryAddr)) {
if (smoption && smoption->priority > 0) {
priority = smoption->priority;
} else if (module->nm.module_start_thread_priority > 0) {
priority = module->nm.module_start_thread_priority;
}
if (smoption && smoption->stacksize > 0) {
stacksize = smoption->stacksize;
} else if (module->nm.module_start_thread_stacksize > 0) {
stacksize = module->nm.module_start_thread_stacksize;
}
SceUID threadID = __KernelCreateThread(module->nm.name, moduleId, entryAddr, priority, stacksize, attribute, 0, (module->nm.attribute & 0x1000) != 0);
__KernelStartThreadValidate(threadID, argsize, argAddr);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
if (needsWait) {
*needsWait = true;
}
} else if (entryAddr == 0) {
INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x): no entry address", moduleId, argsize, argAddr, returnValueAddr);
module->nm.status = MODULE_STATUS_STARTED;
} else {
ERROR_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x): invalid entry address", moduleId, argsize, argAddr, returnValueAddr);
return -1;
}
return moduleId;
}
static void sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, u32 optionAddr)
{
SceKernelSMOption smoption = {0};
if (optionAddr) {
Memory::ReadStruct(optionAddr, &smoption);
}
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module) {
INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x): error %08x", moduleId, argsize, argAddr, returnValueAddr, optionAddr, error);
RETURN(error);
return;
} else if (module->isFake) {
INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x): faked (undecryptable module)",
moduleId,argsize,argAddr,returnValueAddr,optionAddr);
if (returnValueAddr)
Memory::Write_U32(0, returnValueAddr);
RETURN(moduleId);
return;
} else if (module->nm.status == MODULE_STATUS_STARTED) {
ERROR_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x) : already started",
moduleId,argsize,argAddr,returnValueAddr,optionAddr);
// TODO: Maybe should be SCE_KERNEL_ERROR_ALREADY_STARTED, but I get SCE_KERNEL_ERROR_ERROR.
// But I also get crashes...
RETURN(SCE_KERNEL_ERROR_ERROR);
return;
} else {
INFO_LOG(SCEMODULE, "sceKernelStartModule(%d,asize=%08x,aptr=%08x,retptr=%08x,%08x)",
moduleId,argsize,argAddr,returnValueAddr,optionAddr);
bool needsWait;
int ret = KernelStartModule(moduleId, argsize, argAddr, returnValueAddr, optionAddr ? &smoption : nullptr, &needsWait);
if (needsWait) {
__KernelWaitCurThread(WAITTYPE_MODULE, moduleId, 1, 0, false, "started module");
const ModuleWaitingThread mwt = {__KernelGetCurThread(), returnValueAddr};
module->nm.status = MODULE_STATUS_STARTING;
module->waitingThreads.push_back(mwt);
}
RETURN(ret);
}
}
static u32 sceKernelStopModule(u32 moduleId, u32 argSize, u32 argAddr, u32 returnValueAddr, u32 optionAddr)
{
u32 priority = 0x20;
u32 stacksize = 0x40000;
u32 attr = 0;
// TODO: In a lot of cases (even for errors), this should resched. Needs testing.
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module)
{
ERROR_LOG(SCEMODULE, "sceKernelStopModule(%08x, %08x, %08x, %08x, %08x): invalid module id", moduleId, argSize, argAddr, returnValueAddr, optionAddr);
return error;
}
if (module->isFake)
{
INFO_LOG(SCEMODULE, "sceKernelStopModule(%08x, %08x, %08x, %08x, %08x) - faking", moduleId, argSize, argAddr, returnValueAddr, optionAddr);
if (returnValueAddr)
Memory::Write_U32(0, returnValueAddr);
return 0;
}
if (module->nm.status != MODULE_STATUS_STARTED)
{
ERROR_LOG(SCEMODULE, "sceKernelStopModule(%08x, %08x, %08x, %08x, %08x): already stopped", moduleId, argSize, argAddr, returnValueAddr, optionAddr);
return SCE_KERNEL_ERROR_ALREADY_STOPPED;
}
u32 stopFunc = module->nm.module_stop_func;
if (module->nm.module_stop_thread_priority != 0)
priority = module->nm.module_stop_thread_priority;
if (module->nm.module_stop_thread_stacksize != 0)
stacksize = module->nm.module_stop_thread_stacksize;
if (module->nm.module_stop_thread_attr != 0)
attr = module->nm.module_stop_thread_attr;
// TODO: Need to test how this really works. Let's assume it's an override.
if (Memory::IsValidAddress(optionAddr))
{
auto options = PSPPointer<SceKernelSMOption>::Create(optionAddr);
// TODO: Check how size handling actually works.
if (options->size != 0 && options->priority != 0)
priority = options->priority;
if (options->size != 0 && options->stacksize != 0)
stacksize = options->stacksize;
if (options->size != 0 && options->attribute != 0)
attr = options->attribute;
// TODO: Maybe based on size?
else if (attr != 0)
WARN_LOG_REPORT(SCEMODULE, "Stopping module with attr=%x, but options specify 0", attr);
}
if (Memory::IsValidAddress(stopFunc))
{
SceUID threadID = __KernelCreateThread(module->nm.name, moduleId, stopFunc, priority, stacksize, attr, 0, (module->nm.attribute & 0x1000) != 0);
__KernelStartThreadValidate(threadID, argSize, argAddr);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
__KernelWaitCurThread(WAITTYPE_MODULE, moduleId, 1, 0, false, "stopped module");
const ModuleWaitingThread mwt = {__KernelGetCurThread(), returnValueAddr};
module->nm.status = MODULE_STATUS_STOPPING;
module->waitingThreads.push_back(mwt);
}
else if (stopFunc == 0)
{
INFO_LOG(SCEMODULE, "sceKernelStopModule(%08x, %08x, %08x, %08x, %08x): no stop func, skipping", moduleId, argSize, argAddr, returnValueAddr, optionAddr);
module->nm.status = MODULE_STATUS_STOPPED;
}
else
{
ERROR_LOG_REPORT(SCEMODULE, "sceKernelStopModule(%08x, %08x, %08x, %08x, %08x): bad stop func address", moduleId, argSize, argAddr, returnValueAddr, optionAddr);
module->nm.status = MODULE_STATUS_STOPPED;
}
return 0;
}
static u32 sceKernelUnloadModule(u32 moduleId)
{
INFO_LOG(SCEMODULE,"sceKernelUnloadModule(%i)", moduleId);
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module)
return hleDelayResult(error, "module unloaded", 150);
module->Cleanup();
kernelObjects.Destroy<PSPModule>(moduleId);
return hleDelayResult(moduleId, "module unloaded", 500);
}
u32 hleKernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus) {
if (loadedModules.size() > 1) {
if (WithStatus)
ERROR_LOG_REPORT(SCEMODULE, "UNIMPL sceKernelStopUnloadSelfModuleWithStatus(%08x, %08x, %08x, %08x, %08x): game may have crashed", exitCode, argSize, argp, statusAddr, optionAddr);
else
ERROR_LOG_REPORT(SCEMODULE, "UNIMPL sceKernelSelfStopUnloadModule(%08x, %08x, %08x): game may have crashed", exitCode, argSize, argp);
SceUID moduleID = __KernelGetCurThreadModuleId();
u32 priority = 0x20;
u32 stacksize = 0x40000;
u32 attr = 0;
// TODO: In a lot of cases (even for errors), this should resched. Needs testing.
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(moduleID, error);
if (!module) {
if (WithStatus)
ERROR_LOG(SCEMODULE, "sceKernelStopUnloadSelfModuleWithStatus(%08x, %08x, %08x, %08x, %08x): invalid module id", exitCode, argSize, argp, statusAddr, optionAddr);
else
ERROR_LOG(SCEMODULE, "sceKernelSelfStopUnloadModule(%08x, %08x, %08x): invalid module id", exitCode, argSize, argp);
return error;
}
u32 stopFunc = module->nm.module_stop_func;
if (module->nm.module_stop_thread_priority != 0)
priority = module->nm.module_stop_thread_priority;
if (module->nm.module_stop_thread_stacksize != 0)
stacksize = module->nm.module_stop_thread_stacksize;
if (module->nm.module_stop_thread_attr != 0)
attr = module->nm.module_stop_thread_attr;
// TODO: Need to test how this really works. Let's assume it's an override.
if (Memory::IsValidAddress(optionAddr)) {
auto options = PSPPointer<SceKernelSMOption>::Create(optionAddr);
// TODO: Check how size handling actually works.
if (options->size != 0 && options->priority != 0)
priority = options->priority;
if (options->size != 0 && options->stacksize != 0)
stacksize = options->stacksize;
if (options->size != 0 && options->attribute != 0)
attr = options->attribute;
// TODO: Maybe based on size?
else if (attr != 0)
WARN_LOG_REPORT(SCEMODULE, "Stopping module with attr=%x, but options specify 0", attr);
}
if (Memory::IsValidAddress(stopFunc)) {
SceUID threadID = __KernelCreateThread(module->nm.name, moduleID, stopFunc, priority, stacksize, attr, 0, (module->nm.attribute & 0x1000) != 0);
__KernelStartThreadValidate(threadID, argSize, argp);
__KernelSetThreadRA(threadID, NID_MODULERETURN);
__KernelWaitCurThread(WAITTYPE_MODULE, moduleID, 1, 0, false, "unloadstopped module");
const ModuleWaitingThread mwt = {__KernelGetCurThread(), statusAddr};
module->nm.status = MODULE_STATUS_UNLOADING;
module->waitingThreads.push_back(mwt);
} else if (stopFunc == 0) {
if (WithStatus)
INFO_LOG(SCEMODULE, "sceKernelStopUnloadSelfModuleWithStatus(%08x, %08x, %08x, %08x, %08x): no stop func", exitCode, argSize, argp, statusAddr, optionAddr);
else
INFO_LOG(SCEMODULE, "sceKernelSelfStopUnloadModule(%08x, %08x, %08x): no stop func", exitCode, argSize, argp);
sceKernelExitDeleteThread(exitCode);
module->Cleanup();
kernelObjects.Destroy<PSPModule>(moduleID);
} else {
if (WithStatus)
ERROR_LOG_REPORT(SCEMODULE, "sceKernelStopUnloadSelfModuleWithStatus(%08x, %08x, %08x, %08x, %08x): bad stop func address", exitCode, argSize, argp, statusAddr, optionAddr);
else
ERROR_LOG_REPORT(SCEMODULE, "sceKernelSelfStopUnloadModule(%08x, %08x, %08x): bad stop func address", exitCode, argSize, argp);
sceKernelExitDeleteThread(exitCode);
module->Cleanup();
kernelObjects.Destroy<PSPModule>(moduleID);
}
} else {
if (WithStatus)
ERROR_LOG_REPORT(SCEMODULE, "UNIMPL sceKernelStopUnloadSelfModuleWithStatus(%08x, %08x, %08x, %08x, %08x): game has likely crashed", exitCode, argSize, argp, statusAddr, optionAddr);
else
ERROR_LOG_REPORT(SCEMODULE, "UNIMPL sceKernelSelfStopUnloadModule(%08x, %08x, %08x): game has likely crashed", exitCode, argSize, argp);
}
return 0;
}
static u32 sceKernelSelfStopUnloadModule(u32 exitCode, u32 argSize, u32 argp) {
// Used in Tom Clancy's Splinter Cell Essentials,Ghost in the Shell Stand Alone Complex
return hleKernelStopUnloadSelfModuleWithOrWithoutStatus(exitCode, argSize, argp, 0, 0, false);
}
static u32 sceKernelStopUnloadSelfModuleWithStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr) {
return hleKernelStopUnloadSelfModuleWithOrWithoutStatus(exitCode, argSize, argp, statusAddr, optionAddr, true);
}
void __KernelReturnFromModuleFunc()
{
// Return from the thread as normal.
hleSkipDeadbeef();
__KernelReturnFromThread();
SceUID leftModuleID = __KernelGetCurThreadModuleId();
SceUID leftThreadID = __KernelGetCurThread();
int exitStatus = __KernelGetThreadExitStatus(leftThreadID);
// Reschedule immediately (to leave the thread) and delete it and its stack.
__KernelReSchedule("returned from module");
sceKernelDeleteThread(leftThreadID);
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(leftModuleID, error);
if (!module) {
ERROR_LOG_REPORT(SCEMODULE, "Returned from deleted module start/stop func");
return;
}
// We can't be starting and stopping at the same time, so no need to differentiate.
if (module->nm.status == MODULE_STATUS_STARTING)
module->nm.status = MODULE_STATUS_STARTED;
if (module->nm.status == MODULE_STATUS_STOPPING)
module->nm.status = MODULE_STATUS_STOPPED;
for (auto it = module->waitingThreads.begin(), end = module->waitingThreads.end(); it < end; ++it) {
// Still waiting?
if (HLEKernel::VerifyWait(it->threadID, WAITTYPE_MODULE, leftModuleID))
{
if (module->nm.status == MODULE_STATUS_UNLOADING) {
// TODO: Maybe should maintain the exitCode?
sceKernelTerminateDeleteThread(it->threadID);
} else {
if (it->statusPtr != 0)
Memory::Write_U32(exitStatus, it->statusPtr);
__KernelResumeThreadFromWait(it->threadID, module->nm.status == MODULE_STATUS_STARTED ? leftModuleID : 0);
}
}
}
module->waitingThreads.clear();
if (module->nm.status == MODULE_STATUS_UNLOADING) {
// TODO: Delete the waiting thread?
module->Cleanup();
kernelObjects.Destroy<PSPModule>(leftModuleID);
}
}
struct GetModuleIdByAddressArg
{
u32 addr;
SceUID result;
};
static bool __GetModuleIdByAddressIterator(PSPModule *module, GetModuleIdByAddressArg *state) {
const u32 start = module->memoryBlockAddr, size = module->memoryBlockSize;
if (start != 0 && start <= state->addr && start + size > state->addr) {
state->result = module->GetUID();
return false;
}
return true;
}
static u32 sceKernelGetModuleIdByAddress(u32 moduleAddr)
{
GetModuleIdByAddressArg state;
state.addr = moduleAddr;
state.result = SCE_KERNEL_ERROR_UNKNOWN_MODULE;
kernelObjects.Iterate(&__GetModuleIdByAddressIterator, &state);
if (state.result == (SceUID)SCE_KERNEL_ERROR_UNKNOWN_MODULE)
ERROR_LOG(SCEMODULE, "sceKernelGetModuleIdByAddress(%08x): module not found", moduleAddr);
else
DEBUG_LOG(SCEMODULE, "%x=sceKernelGetModuleIdByAddress(%08x)", state.result, moduleAddr);
return state.result;
}
static u32 sceKernelGetModuleId()
{
INFO_LOG(SCEMODULE,"sceKernelGetModuleId()");
return __KernelGetCurThreadModuleId();
}
u32 sceKernelFindModuleByUID(u32 uid)
{
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
if (!module || module->isFake) {
ERROR_LOG(SCEMODULE, "0 = sceKernelFindModuleByUID(%d): Module Not Found or Fake", uid);
return 0;
}
INFO_LOG(SCEMODULE, "%d = sceKernelFindModuleByUID(%d)", module->modulePtr, uid);
return module->modulePtr;
}
u32 sceKernelFindModuleByName(const char *name)
{
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module)
continue;
if (!module->isFake && strcmp(name, module->nm.name) == 0) {
INFO_LOG(SCEMODULE, "%d = sceKernelFindModuleByName(%s)", module->modulePtr, name);
return module->modulePtr;
}
}
WARN_LOG(SCEMODULE, "0 = sceKernelFindModuleByName(%s): Module Not Found or Fake", name);
return 0;
}
static u32 sceKernelLoadModuleByID(u32 id, u32 flags, u32 lmoptionPtr)
{
u32 error;
u32 handle = __IoGetFileHandleFromId(id, error);
if (handle == (u32)-1) {
ERROR_LOG(SCEMODULE,"sceKernelLoadModuleByID(%08x, %08x, %08x): could not open file id",id,flags,lmoptionPtr);
return error;
}
if (flags != 0) {
WARN_LOG_REPORT(LOADER, "sceKernelLoadModuleByID: unsupported flags: %08x", flags);
}
SceKernelLMOption *lmoption = 0;
if (lmoptionPtr) {
lmoption = (SceKernelLMOption *)Memory::GetPointer(lmoptionPtr);
WARN_LOG_REPORT(LOADER, "sceKernelLoadModuleByID: unsupported options size=%08x, flags=%08x, pos=%d, access=%d, data=%d, text=%d", lmoption->size, lmoption->flags, lmoption->position, lmoption->access, lmoption->mpiddata, lmoption->mpidtext);
}
u32 pos = (u32) pspFileSystem.SeekFile(handle, 0, FILEMOVE_CURRENT);
size_t size = pspFileSystem.SeekFile(handle, 0, FILEMOVE_END);
std::string error_string;
pspFileSystem.SeekFile(handle, pos, FILEMOVE_BEGIN);
PSPModule *module = nullptr;
u8 *temp = new u8[size - pos];
pspFileSystem.ReadFile(handle, temp, size - pos);
u32 magic;
module = __KernelLoadELFFromPtr(temp, size - pos, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, error);
delete [] temp;
if (!module) {
// Some games try to load strange stuff as PARAM.SFO as modules and expect it to fail.
// This checks for the SFO magic number.
if (magic == 0x46535000) {
ERROR_LOG(LOADER, "Game tried to load an SFO as a module. Go figure? Magic = %08x", magic);
return error;
}
if ((int)error >= 0)
{
// Module was blacklisted or couldn't be decrypted, which means it's a kernel module we don't want to run..
// Let's just act as if it worked.
NOTICE_LOG(LOADER, "Module %d is blacklisted or undecryptable - we lie about success", id);
return 1;
}
else
{
NOTICE_LOG(LOADER, "Module %d failed to load: %08x", id, error);
return error;
}
}
if (lmoption) {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModuleByID(%d,flag=%08x,%08x,%08x,%08x,position = %08x)",
module->GetUID(),id,flags,
lmoption->size,lmoption->mpidtext,lmoption->mpiddata,lmoption->position);
} else {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModuleByID(%d,flag=%08x,(...))", module->GetUID(), id, flags);
}
return module->GetUID();
}
static u32 sceKernelLoadModuleDNAS(const char *name, u32 flags)
{
ERROR_LOG_REPORT(SCEMODULE, "UNIMPL 0=sceKernelLoadModuleDNAS()");
return 0;
}
// Pretty sure this is a badly brute-forced function name...
static SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr)
{
if (flags != 0) {
WARN_LOG_REPORT(LOADER, "sceKernelLoadModuleBufferUsbWlan: unsupported flags: %08x", flags);
}
SceKernelLMOption *lmoption = 0;
if (lmoptionPtr) {
lmoption = (SceKernelLMOption *)Memory::GetPointer(lmoptionPtr);
WARN_LOG_REPORT(LOADER, "sceKernelLoadModuleBufferUsbWlan: unsupported options size=%08x, flags=%08x, pos=%d, access=%d, data=%d, text=%d", lmoption->size, lmoption->flags, lmoption->position, lmoption->access, lmoption->mpiddata, lmoption->mpidtext);
}
std::string error_string;
PSPModule *module = nullptr;
u32 magic;
u32 error;
module = __KernelLoadELFFromPtr(Memory::GetPointer(bufPtr), size, 0, lmoption ? lmoption->position == PSP_SMEM_High : false, &error_string, &magic, error);
if (!module) {
// Some games try to load strange stuff as PARAM.SFO as modules and expect it to fail.
// This checks for the SFO magic number.
if (magic == 0x46535000) {
ERROR_LOG(LOADER, "Game tried to load an SFO as a module. Go figure? Magic = %08x", magic);
return error;
}
if ((int)error >= 0)
{
// Module was blacklisted or couldn't be decrypted, which means it's a kernel module we don't want to run..
// Let's just act as if it worked.
NOTICE_LOG(LOADER, "Module is blacklisted or undecryptable - we lie about success");
return 1;
}
else
{
NOTICE_LOG(LOADER, "Module failed to load: %08x", error);
return error;
}
}
if (lmoption) {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModuleBufferUsbWlan(%x,%08x,flag=%08x,%08x,%08x,%08x,position = %08x)",
module->GetUID(),size,bufPtr,flags,
lmoption->size,lmoption->mpidtext,lmoption->mpiddata,lmoption->position);
} else {
INFO_LOG(SCEMODULE,"%i=sceKernelLoadModuleBufferUsbWlan(%x,%08x,flag=%08x,(...))", module->GetUID(), size,bufPtr, flags);
}
return module->GetUID();
}
static u32 sceKernelQueryModuleInfo(u32 uid, u32 infoAddr)
{
INFO_LOG(SCEMODULE, "sceKernelQueryModuleInfo(%i, %08x)", uid, infoAddr);
u32 error;
PSPModule *module = kernelObjects.Get<PSPModule>(uid, error);
if (!module)
return error;
if (!Memory::IsValidAddress(infoAddr)) {
ERROR_LOG(SCEMODULE, "sceKernelQueryModuleInfo(%i, %08x) - bad infoAddr", uid, infoAddr);
return -1;
}
auto info = PSPPointer<ModuleInfo>::Create(infoAddr);
memcpy(info->segmentaddr, module->nm.segmentaddr, sizeof(info->segmentaddr));
memcpy(info->segmentsize, module->nm.segmentsize, sizeof(info->segmentsize));
info->nsegment = module->nm.nsegment;
info->entry_addr = module->nm.entry_addr;
info->gp_value = module->nm.gp_value;
info->text_addr = module->nm.text_addr;
info->text_size = module->nm.text_size;
info->data_size = module->nm.data_size;
info->bss_size = module->nm.bss_size;
// Even if it's bigger, if it's not exactly 96, skip this extra data.
// Even if it's 0, the above are all written though.
if (info->size == 96) {
info->attribute = module->nm.attribute;
info->version[0] = module->nm.version[0];
info->version[1] = module->nm.version[1];
memcpy(info->name, module->nm.name, 28);
}
return 0;
}
static u32 sceKernelGetModuleIdList(u32 resultBuffer, u32 resultBufferSize, u32 idCountAddr)
{
ERROR_LOG(SCEMODULE, "UNTESTED sceKernelGetModuleIdList(%08x, %i, %08x)", resultBuffer, resultBufferSize, idCountAddr);
int idCount = 0;
u32 resultBufferOffset = 0;
u32 error;
for (SceUID moduleId : loadedModules) {
PSPModule *module = kernelObjects.Get<PSPModule>(moduleId, error);
if (!module->isFake) {
if (resultBufferOffset < resultBufferSize) {
Memory::Write_U32(module->GetUID(), resultBuffer + resultBufferOffset);
resultBufferOffset += 4;
}
idCount++;
}
}
Memory::Write_U32(idCount, idCountAddr);
return 0;
}
//fix for tiger x dragon
static u32 sceKernelLoadModuleForLoadExecVSHDisc(const char *name, u32 flags, u32 optionAddr) {
return sceKernelLoadModule(name, flags, optionAddr);
}
const HLEFunction ModuleMgrForUser[] =
{
{0X977DE386, &WrapU_CUU<sceKernelLoadModule>, "sceKernelLoadModule", 'x', "sxx" },
{0XB7F46618, &WrapU_UUU<sceKernelLoadModuleByID>, "sceKernelLoadModuleByID", 'x', "xxx" },
{0X50F0C1EC, &WrapV_UUUUU<sceKernelStartModule>, "sceKernelStartModule", 'v', "xxxxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0XD675EBB8, &WrapU_UUU<sceKernelSelfStopUnloadModule>, "sceKernelSelfStopUnloadModule", 'x', "xxx" },
{0XD1FF982A, &WrapU_UUUUU<sceKernelStopModule>, "sceKernelStopModule", 'x', "xxxxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0X2E0911AA, &WrapU_U<sceKernelUnloadModule>, "sceKernelUnloadModule", 'x', "x" },
{0X710F61B5, nullptr, "sceKernelLoadModuleMs", '?', "" },
{0XF9275D98, &WrapI_UUUU<sceKernelLoadModuleBufferUsbWlan>, "sceKernelLoadModuleBufferUsbWlan", 'i', "xxxx" }, /// ??
{0XCC1D3699, nullptr, "sceKernelStopUnloadSelfModule", '?', "" },
{0X748CBED9, &WrapU_UU<sceKernelQueryModuleInfo>, "sceKernelQueryModuleInfo", 'x', "xx" },
{0XD8B73127, &WrapU_U<sceKernelGetModuleIdByAddress>, "sceKernelGetModuleIdByAddress", 'x', "x" },
{0XF0A26395, &WrapU_V<sceKernelGetModuleId>, "sceKernelGetModuleId", 'x', "" },
{0X8F2DF740, &WrapU_UUUUU<sceKernelStopUnloadSelfModuleWithStatus>, "sceKernelStopUnloadSelfModuleWithStatus", 'x', "xxxxx" },
{0XFEF27DC1, &WrapU_CU<sceKernelLoadModuleDNAS>, "sceKernelLoadModuleDNAS", 'x', "sx" },
{0X644395E2, &WrapU_UUU<sceKernelGetModuleIdList>, "sceKernelGetModuleIdList", 'x', "xxx" },
{0XF2D8D1B4, &WrapU_CUU<sceKernelLoadModuleNpDrm>, "sceKernelLoadModuleNpDrm", 'x', "sxx" },
{0XE4C4211C, nullptr, "ModuleMgrForUser_E4C4211C", '?', "" },
{0XFBE27467, nullptr, "ModuleMgrForUser_FBE27467", '?', "" },
};
const HLEFunction ModuleMgrForKernel[] =
{
{0x50F0C1EC, &WrapV_UUUUU<sceKernelStartModule>, "sceKernelStartModule", 'v', "xxxxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED | HLE_KERNEL_SYSCALL },
{0x977DE386, &WrapU_CUU<sceKernelLoadModule>, "sceKernelLoadModule", 'x', "sxx", HLE_KERNEL_SYSCALL },
{0xA1A78C58, &WrapU_CUU<sceKernelLoadModuleForLoadExecVSHDisc>, "sceKernelLoadModuleForLoadExecVSHDisc", 'x', "sxx", HLE_KERNEL_SYSCALL }, //fix for tiger x dragon
{0xCC1D3699, &WrapU_UUU<sceKernelSelfStopUnloadModule>, "sceKernelSelfStopUnloadModule", 'x', "xxx", HLE_KERNEL_SYSCALL }, // used in Dissidia final fantasy chinese patch
{0XD1FF982A, &WrapU_UUUUU<sceKernelStopModule>, "sceKernelStopModule", 'x', "xxxxx", HLE_KERNEL_SYSCALL | HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED }, // used in Dissidia final fantasy chinese patch
{0x748CBED9, &WrapU_UU<sceKernelQueryModuleInfo>, "sceKernelQueryModuleInfo", 'x', "xx", HLE_KERNEL_SYSCALL },
{0x644395E2, &WrapU_UUU<sceKernelGetModuleIdList>, "sceKernelGetModuleIdList", 'x', "xxx", HLE_KERNEL_SYSCALL },
{0X2E0911AA, &WrapU_U<sceKernelUnloadModule>, "sceKernelUnloadModule", 'x', "x" , HLE_KERNEL_SYSCALL },
};
void Register_ModuleMgrForUser()
{
RegisterModule("ModuleMgrForUser", ARRAY_SIZE(ModuleMgrForUser), ModuleMgrForUser);
}
void Register_ModuleMgrForKernel()
{
RegisterModule("ModuleMgrForKernel", ARRAY_SIZE(ModuleMgrForKernel), ModuleMgrForKernel);
};