mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-23 19:29:05 +02:00
A PS1 EBOOT now boots the installed firmware's POPS manager (flash0:/kd/popsman.prx), which loads its PS1 emulator (pops_0Xg.prx) for the game. Load_PSP_POPS stands in for the kernel's reboot into its POPS configuration: it loads the manager and that configuration's user modules, and a helper thread makes the kernel init's scePopsManLoadModule(path, 0) call. InitForKernel reports a POPS boot from the memory stick. The emulator plays its sound on the Media Engine, so a PS1 boot always gets the emulated ME, whatever the homebrew setting says, and is refused on platforms that can't emulate it. The manager boots the ME with its own code (POPS's reboot has no me_wrapper, so Sony's ME firmware isn't booted for it), and its ME loop runs there unchanged, playing through the audio block. Everything in POPS that reaches hardware registers is kernel code, so a POPS boot leaves the ME's kernel mode stopgap (g_kernelModeAnywhere) off: 7% fewer host instructions in Crash Bandicoot. The manager also drives the GE directly: its stall register and FINISH status are an MMIO device that only answers during a POPS boot, and stall writes extend the HLE GPU's current list. The 6.60 and 6.61 popsman.prx and pops_01g/02g.prx have the same code. The manager's is checked when it's loaded, and any other one fails the boot with a message. Savestates include the boot's threads. Reading a game's disc image needs the next two changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
348 lines
13 KiB
C++
348 lines
13 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/.
|
|
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <cstdarg>
|
|
#include <type_traits>
|
|
#include <string_view>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/ConfigValues.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning (error: 4834) // discarding return value of function with 'nodiscard' attribute
|
|
#endif
|
|
|
|
class PointerWrap;
|
|
class PSPAction;
|
|
typedef void (* HLEFunc)(MIPSState *mips);
|
|
|
|
enum {
|
|
// The low 8 bits are a value, indicating special jit handling.
|
|
// Currently there are none.
|
|
|
|
// The remaining 24 bits are flags.
|
|
// Don't allow the call within an interrupt. Not yet implemented.
|
|
HLE_NOT_IN_INTERRUPT = 1 << 8,
|
|
// Don't allow the call if dispatch or interrupts are disabled.
|
|
HLE_NOT_DISPATCH_SUSPENDED = 1 << 9,
|
|
// Indicates the call should write zeros to the stack (stackBytesToClear in the table.)
|
|
HLE_CLEAR_STACK_BYTES = 1 << 10,
|
|
// Indicates that this call operates in kernel mode.
|
|
HLE_KERNEL_SYSCALL = 1 << 11,
|
|
};
|
|
|
|
struct HLEFunction {
|
|
// This is the id, or nid, of the function (which is how it's linked.)
|
|
// Generally, the truncated least significant 32 bits of a SHA-1 hash.
|
|
u32 ID;
|
|
// A pointer to the C++ handler; see FunctionWrappers.h for helpers.
|
|
HLEFunc func;
|
|
// Name of the function. Often the same as func, this should match the PSP func's name and hash.
|
|
const char *name;
|
|
// The return type, see argmask for the possible values.
|
|
// An additional value is possible - 'v', which means void (no return type.)
|
|
char retmask;
|
|
// The argument types, in order. Use the following characters:
|
|
// - x: for u32 (shown as hex in log)
|
|
// - i: for int/s32
|
|
// - f: for float
|
|
// - X: uppercase, for u64
|
|
// - I: uppercase, for s64
|
|
// - F: uppercase, for double
|
|
// - s: a string pointer (const char *, utf-8)
|
|
// - p: a pointer (e.g. u32 *, shown with value in log)
|
|
const char *argmask;
|
|
// Flags (see above, e.g. HLE_NOT_IN_INTERRUPT.)
|
|
u32 flags;
|
|
// See HLE_CLEAR_STACK_BYTES.
|
|
u32 stackBytesToClear;
|
|
};
|
|
|
|
struct HLEModule {
|
|
std::string_view name;
|
|
int numFunctions;
|
|
const HLEFunction *funcTable;
|
|
};
|
|
|
|
typedef char SyscallModuleName[32];
|
|
|
|
struct Syscall {
|
|
SyscallModuleName moduleName;
|
|
u32 symAddr;
|
|
u32 nid;
|
|
};
|
|
|
|
// These work on the MIPSState named mips, which HLE functions get passed (see HLEFunc and FunctionWrappers.h).
|
|
#define PARAM(n) mips->r[MIPS_REG_A0 + n]
|
|
#define PARAM64(n) (mips->r[MIPS_REG_A0 + n] | ((u64)mips->r[MIPS_REG_A0 + n + 1] << 32))
|
|
#define PARAMF(n) mips->f[12 + n]
|
|
#define RETURN(n) mips->r[MIPS_REG_V0] = n;
|
|
#define RETURN64(n) {u64 RETURN64_tmp = n; mips->r[MIPS_REG_V0] = RETURN64_tmp & 0xFFFFFFFF; mips->r[MIPS_REG_V1] = RETURN64_tmp >> 32;}
|
|
#define RETURNF(fl) mips->f[0] = fl
|
|
|
|
struct HLEModuleMeta {
|
|
// This is the modname (name from the PRX header). Probably, we should really blacklist on the module names of the exported symbol metadata.
|
|
const char *modname;
|
|
const char *importName; // Technically a module can export functions with different module names, but doesn't seem to happen.
|
|
DisableHLEFlags disableFlag;
|
|
};
|
|
|
|
const HLEModuleMeta *GetHLEModuleMetaByFlag(DisableHLEFlags flag);
|
|
const HLEModuleMeta *GetHLEModuleMeta(std::string_view modname);
|
|
bool ShouldHLEModule(std::string_view modname, bool *wasDisabledManually = nullptr);
|
|
// When set, ShouldHLEModule always says no, so every module genuinely loads and runs Sony's
|
|
// code. Needed by the headless reverse-engineering dump (headless/ReverseEngineer.cpp), which
|
|
// exists precisely to look at the real thing - including modules like sceAudiocodec_Driver that
|
|
// have no DisableHLEFlags bit of their own and so can't be turned off the normal way.
|
|
// Only affects loading; imports still resolve through the HLE tables, which is what gives
|
|
// imported functions their names.
|
|
void SetForceRealModuleLoads(bool force);
|
|
bool ShouldHLEModuleByImportName(std::string_view importModuleName);
|
|
|
|
// May return nullptr
|
|
const char *GetHLEFuncName(std::string_view module, u32 nib);
|
|
// May return nullptr if indices out of range.
|
|
const char *GetHLEFuncName(int module, int func);
|
|
|
|
const HLEModule *GetHLEModuleByName(std::string_view name);
|
|
const HLEFunction *GetHLEFunc(std::string_view module, u32 nib);
|
|
int GetHLEFuncIndexByNib(int moduleIndex, u32 nib);
|
|
int GetHLEModuleIndex(std::string_view modulename);
|
|
u32 GetNibByName(std::string_view module, std::string_view function);
|
|
|
|
void RegisterHLEModule(std::string_view name, int numFunctions, const HLEFunction *funcTable);
|
|
int GetNumRegisteredHLEModules();
|
|
const HLEModule *GetHLEModuleByIndex(int index);
|
|
DisableHLEFlags AlwaysDisableHLEFlags();
|
|
// The flags actually in effect for this boot - latched at the first module load and restored from
|
|
// savestates, so it can differ from what g_Config says if the setting changed since.
|
|
DisableHLEFlags GetEffectiveDisableHLEFlags();
|
|
|
|
// Run the current thread's callbacks after the syscall finishes.
|
|
void hleCheckCurrentCallbacks();
|
|
// Reschedule after the syscall finishes.
|
|
void hleReSchedule(const char *reason);
|
|
// Reschedule and go into a callback processing state after the syscall finishes.
|
|
void hleReSchedule(bool callbacks, const char *reason);
|
|
// Run interrupts after the syscall finishes.
|
|
void hleRunInterrupts();
|
|
// Pause emulation after the syscall finishes.
|
|
void hleDebugBreak();
|
|
// Don't set temp regs to 0xDEADBEEF.
|
|
void hleSkipDeadbeef();
|
|
// Set time spent in realtime sync.
|
|
void hleSetFlipTime(double t);
|
|
// Check if the current syscall context is kernel.
|
|
bool hleIsKernelMode();
|
|
// Enqueue a MIPS function to be called after this HLE call finishes.
|
|
void hleEnqueueCall(u32 func, int argc, const u32 *argv, PSPAction *afterAction = nullptr);
|
|
|
|
// Delays the result for usec microseconds, allowing other threads to run during this time.
|
|
u32 hleDelayResult(u32 result, const char *reason, int usec);
|
|
u64 hleDelayResult(u64 result, const char *reason, int usec);
|
|
bool hleWakeDelayedThread(int threadID);
|
|
void hleEatCycles(int cycles);
|
|
void hleEatMicro(int usec);
|
|
|
|
// Don't manually call this, it's called by the various syscall return macros.
|
|
// This should only be called once per syscall!
|
|
void hleLeave();
|
|
|
|
void hleCoreTimingForceCheck();
|
|
|
|
// Causes the syscall to not fully execute immediately, instead give the Ge a chance to
|
|
// execute display lists.
|
|
void hleSplitSyscallOverGe();
|
|
|
|
// Called after a split syscall from System.cpp
|
|
void hleFinishSyscallAfterGe(MIPSState *mips);
|
|
|
|
[[nodiscard]]
|
|
inline int hleDelayResult(int result, const char *reason, int usec) {
|
|
return hleDelayResult((u32) result, reason, usec);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
inline s64 hleDelayResult(s64 result, const char *reason, int usec) {
|
|
return hleDelayResult((u64) result, reason, usec);
|
|
}
|
|
|
|
void HLEInit();
|
|
void HLECheckModuleAvailability();
|
|
// The flags that were asked for but could not be honoured, because the firmware module they need
|
|
// is neither installed nor on the disc - those libraries ran our HLE instead. HLECheckModuleAvailability()
|
|
// fills this in during boot, so it only means anything after that.
|
|
DisableHLEFlags HLEGetUnavailableDisableFlags();
|
|
void HLEDoState(PointerWrap &p);
|
|
void HLEShutdown();
|
|
const HLEFunction *HLEGetFunctionBeingCalled();
|
|
size_t HLEFormatLogArgs(const MIPSState *mips, char *message, size_t sz, const char *argmask);
|
|
// Runs a printf-style format string from the emulated program, pulling the varargs out of the PSP's
|
|
// registers and stack. firstVarArg is the index of the first vararg, counting a0 as 0 - so 1 for a
|
|
// printf(fmt, ...), 2 for an sprintf(dst, fmt, ...). Returns false, having logged why, if the format
|
|
// string or an argument couldn't be read; the caller shouldn't use the result then.
|
|
bool HLEFormatPrintf(const MIPSState *mips, u32 fmtAddr, int firstVarArg, std::string *result);
|
|
u32 GetSyscallOp(std::string_view module, u32 nib);
|
|
bool WriteHLESyscall(std::string_view module, u32 nib, u32 address);
|
|
void CallSyscall(MIPSState *mips, MIPSOpcode op);
|
|
void CallSyscallWithPC(MIPSState *mips, MIPSOpcode op, u32 pc); // better diagnostics
|
|
void CallSyscallUnresolvedAtPC(MIPSState *mips, u32 pc);
|
|
void WriteFuncStub(u32 stubAddr, u32 symAddr);
|
|
void WriteFuncMissingStub(u32 stubAddr, u32 nid);
|
|
|
|
void HLEReturnFromMipsCall(MIPSState *mips);
|
|
|
|
const HLEFunction *GetSyscallFunctionData(MIPSOpcode op, u32 pcForDiagnostics);
|
|
// For jit, the returned function takes the args: MIPSState *, const HLEFunction *
|
|
void *GetQuickSyscallFunc(const HLEFunction *info, MIPSOpcode op);
|
|
|
|
void hleDoLogInternal(Log t, LogLevel level, u64 res, const char *file, int line, const char *reportTag, const char *reason, const char *formatted_reason);
|
|
|
|
template <bool leave, bool convert_code, typename T>
|
|
[[nodiscard]]
|
|
NO_INLINE
|
|
ATTR_FORMAT_PRINTF(7, 8) T hleDoLog(Log t, LogLevel level, T res, const char *file, int line, const char *reportTag, MSVC_FORMAT_PRINTF const char *reasonFmt, ...) {
|
|
if (!GenericLogEnabled(t, level)) {
|
|
if (leave) {
|
|
hleLeave();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
if (convert_code && (int)res >= 0) {
|
|
level = LogLevel::LDEBUG;
|
|
}
|
|
|
|
char formatted_reason[4096] = {0};
|
|
if (reasonFmt != nullptr) {
|
|
va_list args;
|
|
va_start(args, reasonFmt);
|
|
formatted_reason[0] = ':';
|
|
formatted_reason[1] = ' ';
|
|
vsnprintf(formatted_reason + 2, sizeof(formatted_reason) - 3, reasonFmt, args);
|
|
formatted_reason[sizeof(formatted_reason) - 1] = '\0';
|
|
va_end(args);
|
|
}
|
|
|
|
u64 fmtRes = res;
|
|
if (std::is_floating_point<T>::value) {
|
|
// We reinterpret as the bits for now, so we can have a common helper.
|
|
fmtRes = *(const u32 *)&res;
|
|
} else if (std::is_signed<T>::value) {
|
|
fmtRes = (s64)res;
|
|
}
|
|
hleDoLogInternal(t, level, fmtRes, file, line, reportTag, reasonFmt, formatted_reason);
|
|
if (leave) {
|
|
hleLeave();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
template <bool leave, bool convert_code, typename T>
|
|
[[nodiscard]]
|
|
NO_INLINE
|
|
T hleDoLog(Log t, LogLevel level, T res, const char *file, int line, const char *reportTag) {
|
|
if (convert_code && (int)res >= 0) {
|
|
level = LogLevel::LDEBUG;
|
|
}
|
|
|
|
if (((int)level > MAX_LOGLEVEL || !GenericLogEnabled(t, level)) && !reportTag) {
|
|
if (leave) {
|
|
hleLeave();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
u64 fmtRes = res;
|
|
if (std::is_floating_point<T>::value) {
|
|
// We reinterpret as the bits for now, so we can have a common helper.
|
|
fmtRes = *(const u32 *)&res;
|
|
} else if (std::is_signed<T>::value) {
|
|
fmtRes = (s64)res;
|
|
}
|
|
hleDoLogInternal(t, level, fmtRes, file, line, reportTag, nullptr, "");
|
|
if (leave) {
|
|
hleLeave();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// These unwind the log stack.
|
|
template <typename T>
|
|
[[nodiscard]]
|
|
inline T hleNoLog(T t) {
|
|
hleLeave();
|
|
return t;
|
|
}
|
|
inline void hleNoLogVoid() {
|
|
hleLeave();
|
|
}
|
|
|
|
// TODO: See if we can search the tables with constexpr tricks!
|
|
void hlePushFuncDesc(std::string_view module, std::string_view funcName);
|
|
|
|
// Calls a syscall from another syscall, managing the logging stack.
|
|
template<class R, class F, typename... Args>
|
|
inline R hleCallImpl(std::string_view module, std::string_view funcName, F func, Args... args) {
|
|
hlePushFuncDesc(module, funcName);
|
|
return func(args...);
|
|
}
|
|
|
|
// Note: ## is to eat the last comma if it's not needed (no arguments).
|
|
#define hleCall(module, retType, funcName, ...) hleCallImpl<retType>(#module, #funcName, funcName, ## __VA_ARGS__)
|
|
|
|
// This is just a quick way to force logging to be more visible for one file.
|
|
#ifdef HLE_LOG_FORCE
|
|
#define HLE_LOG_LDEBUG LNOTICE
|
|
#define HLE_LOG_LVERBOSE LDEBUG
|
|
|
|
#undef DEBUG_LOG
|
|
#define DEBUG_LOG NOTICE_LOG
|
|
#undef VERBOSE_LOG
|
|
#define VERBOSE_LOG DEBUG_LOG
|
|
#undef DEBUG_LEVEL
|
|
#define DEBUG_LEVEL NOTICE_LEVEL
|
|
#undef VERBOSE_LEVEL
|
|
#define VERBOSE_LEVEL NOTICE_LEVEL
|
|
#else
|
|
#define HLE_LOG_LDEBUG LogLevel::LDEBUG
|
|
#define HLE_LOG_LVERBOSE LogLevel::LVERBOSE
|
|
#endif
|
|
|
|
// IMPORTANT: These *must* only be used directly in HLE functions. They cannot be used by utility functions
|
|
// called by them. Use regular ERROR_LOG etc for those.
|
|
|
|
#define hleLogReturnHelper(convert, t, level, res, ...) \
|
|
(((int)level <= MAX_LOGLEVEL) ? hleDoLog<true, convert>(t, level, (res), __FILE__, __LINE__, nullptr, ##__VA_ARGS__) : hleNoLog(res))
|
|
|
|
#define hleLogError(t, res, ...) hleLogReturnHelper(false, t, LogLevel::LERROR, res, ##__VA_ARGS__)
|
|
#define hleLogWarning(t, res, ...) hleLogReturnHelper(false, t, LogLevel::LWARNING, res, ##__VA_ARGS__)
|
|
#define hleLogDebug(t, res, ...) hleLogReturnHelper(false, t, HLE_LOG_LDEBUG, res, ##__VA_ARGS__)
|
|
#define hleLogInfo(t, res, ...) hleLogReturnHelper(false, t, LogLevel::LINFO, res, ##__VA_ARGS__)
|
|
#define hleLogVerbose(t, res, ...) hleLogReturnHelper(false, t, HLE_LOG_LVERBOSE, res, ##__VA_ARGS__)
|
|
|
|
#define hleLogDebugOrWarn(t, res, ...) hleLogReturnHelper(true, t, LogLevel::LWARNING, res, ##__VA_ARGS__)
|
|
#define hleLogDebugOrError(t, res, ...) hleLogReturnHelper(true, t, LogLevel::LERROR, res, ##__VA_ARGS__)
|
|
|
|
#define hleReportError(t, res, ...) hleDoLog<true, false>(t, LogLevel::LERROR, res, __FILE__, __LINE__, "", ##__VA_ARGS__)
|
|
#define hleReportWarning(t, res, ...) hleDoLog<true, false>(t, LogLevel::LWARNING, res, __FILE__, __LINE__, "", ##__VA_ARGS__)
|