mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
As on a PSP: pressing HOME in a game shows "Do you want to quit the game?" over it. The game keeps running but gets no input while it's up (sceCtrl samples are blanked). Left/right picks Yes or No, the confirm button (our Confirmation button setting, or what compat forces) chooses, the other face button or HOME closes it, and Yes/No can be tapped. The volume keys change the game volume a step at a time, shown on the volume bar. Yes runs the game's registered exit callback first, like the PSP does, showing "Please wait..." meanwhile. Then, if the XMB launched the game, the XMB is booted again; otherwise it's back to the game browser, like Exit to menu. If the callback hasn't ended the game in 10 s, it's quit anyway. Not in the XMB itself, the PS1 emulator (its own HOME menu) or GE dumps. It lives in UI/ImposeHome.cpp; EmuScreen only feeds it input and frames and draws it over the PSP display. With a firmware dump installed, it's drawn with the firmware's own art from flash0:/kd/resource/impose.rsc, which is what impose.prx draws it from: prerendered text for each system language (the question, Yes/No normal and highlighted, Enter/Back, Please wait...), the button icons, the lines, the volume bar, the battery and the clock glyphs. The format is documented in ImposeHome.cpp. Without one, the same screen is drawn with our font and simple shapes at the same positions. Where impose.rsc doesn't say where things go, the layout follows impose_02g.prx (6.61), decrypted and disassembled: - The screen is filled with 0x9CE7 (#393939); the images are opaque. - Lines at (80, 96) and (80, 175); impose.prx grows them from the middle when it opens, which isn't done here. - Volume bar: the speaker at (28, 223), 30 steps from x 62, 12 apart. It shows our game volume in the PSP's 30 steps. - Button guide: the cross icon always left, the circle right, "Enter" and "Back" next to whichever confirms and cancels. - Status bar: the battery at (450, 5) (60% and up full, 30% two bars, 12% one, then empty), the clock ending next to it at y 5: "M/D H:MM", or "D/M" for the DD/MM/YYYY format, no leading zero on the month, day or hour, AM/PM for the 12-hour clock. The clock is the emulated PSP's local time (__RtcGetCurrentLocalTime, new), in the system's date and time formats. The battery is this device's, since the emulated PSP's is always full; a device without one shows it full. For that, Windows now reports -1 for SYSPROP_BATTERY_PERCENTAGE without a battery (was 100), as SDL already did; SDL polls the battery even when the status overlay doesn't show it, and the overlay leaves it out when there's none. Core side: __CtrlSetGameInputBlocked; __KernelCanExitToVSH and __KernelRequestExitToVSH to quit to the XMB from outside a syscall; and __KernelInvokeRegisteredExitCallback(toVSH), so a callback that returns without exiting ends in the XMB rather than powering down. New strings: "Do you want to quit the game?", "Please wait..." (Dialog). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NB244iFpvm14JH2FbwHXeQ
301 lines
11 KiB
C++
301 lines
11 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 <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <set>
|
|
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/MemMap.h"
|
|
|
|
class PointerWrap;
|
|
struct SceKernelSMOption;
|
|
|
|
// Known bits of PspModuleInfo::moduleAttrs.
|
|
enum {
|
|
PSP_MODULE_USER_MODE = 0x0000,
|
|
PSP_MODULE_VSH_MODE = 0x0800,
|
|
PSP_MODULE_KERNEL_MODE = 0x1000,
|
|
};
|
|
|
|
struct PspModuleInfo {
|
|
u16_le moduleAttrs; //0x0000 User Mode, 0x0800 VSH Mode, 0x1000 Kernel Mode
|
|
u16_le moduleVersion;
|
|
// 28 bytes of module name, packed with 0's.
|
|
char name[28];
|
|
u32_le gp; // ptr to MIPS GOT data (global offset table)
|
|
u32_le libent; // ptr to .lib.ent section
|
|
u32_le libentend; // ptr to end of .lib.ent section
|
|
u32_le libstub; // ptr to .lib.stub section
|
|
u32_le libstubend; // ptr to end of .lib.stub section
|
|
};
|
|
|
|
enum NativeModuleStatus {
|
|
MODULE_STATUS_STARTING = 4,
|
|
MODULE_STATUS_STARTED = 5,
|
|
MODULE_STATUS_STOPPING = 6,
|
|
MODULE_STATUS_STOPPED = 7,
|
|
MODULE_STATUS_UNLOADING = 8,
|
|
};
|
|
|
|
const char *NativeModuleStatusToString(NativeModuleStatus status);
|
|
|
|
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;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
struct WriteVarSymbolState;
|
|
|
|
struct ModuleWaitingThread {
|
|
SceUID threadID;
|
|
u32 statusPtr;
|
|
};
|
|
|
|
class PSPModule : public KernelObject {
|
|
public:
|
|
~PSPModule();
|
|
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;
|
|
void GetLongInfo(char *ptr, int bufSize) const override;
|
|
static u32 GetMissingErrorCode();
|
|
static int GetStaticIDType() { return PPSSPP_KERNEL_TMID_Module; }
|
|
int GetIDType() const override { return PPSSPP_KERNEL_TMID_Module; }
|
|
|
|
u32 GetDataAddr() const {
|
|
return nm.text_addr + nm.text_size;
|
|
}
|
|
u32 GetBSSAddr() const {
|
|
return nm.text_addr + nm.text_size + nm.data_size;
|
|
}
|
|
|
|
void DoState(PointerWrap &p) override;
|
|
|
|
// We don't do this in the destructor to avoid annoying messages on game shutdown.
|
|
void Cleanup();
|
|
|
|
void ImportFunc(const FuncSymbolImport &func, bool reimporting);
|
|
void ImportVar(WriteVarSymbolState &state, const VarSymbolImport &var);
|
|
void ExportFunc(const FuncSymbolExport &func);
|
|
void ExportVar(const VarSymbolExport &var);
|
|
|
|
template <typename T>
|
|
void RebuildExpList(const std::vector<T> &list) {
|
|
for (size_t i = 0; i < list.size(); ++i) {
|
|
expModuleNames.insert(list[i].moduleName);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void RebuildImpList(const std::vector<T> &list) {
|
|
for (size_t i = 0; i < list.size(); ++i) {
|
|
impModuleNames.insert(list[i].moduleName);
|
|
}
|
|
}
|
|
|
|
void RebuildImpExpModuleNames() {
|
|
impModuleNames.clear();
|
|
expModuleNames.clear();
|
|
RebuildExpList(exportedFuncs);
|
|
RebuildImpList(importedFuncs);
|
|
RebuildExpList(exportedVars);
|
|
RebuildImpList(importedVars);
|
|
}
|
|
|
|
bool ImportsOrExportsModuleName(const std::string &moduleName) {
|
|
return impModuleNames.find(moduleName) != impModuleNames.end() ||
|
|
expModuleNames.find(moduleName) != expModuleNames.end();
|
|
}
|
|
|
|
NativeModule nm{};
|
|
std::vector<ModuleWaitingThread> waitingThreads;
|
|
|
|
// From the plugin's perspective, this is the reference to the thread started by LoadExec
|
|
SceUID pluginWaitingThread = 0;
|
|
// Thread started by LoadExec is waiting for these plugins
|
|
std::vector<SceUID> startingPlugins;
|
|
|
|
// TODO: Should we store these grouped by moduleName instead? Seems more reasonable.
|
|
std::vector<FuncSymbolExport> exportedFuncs;
|
|
std::vector<FuncSymbolImport> importedFuncs;
|
|
std::vector<VarSymbolExport> exportedVars;
|
|
std::vector<VarSymbolImport> importedVars;
|
|
std::set<std::string> impModuleNames;
|
|
std::set<std::string> expModuleNames;
|
|
|
|
// 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 crc = 0;
|
|
PSPPointer<NativeModule> modulePtr{};
|
|
bool isFake = false;
|
|
};
|
|
|
|
KernelObject *__KernelModuleObject();
|
|
void __KernelModuleDoState(PointerWrap &p);
|
|
// The code of the loaded modules, as (address, size).
|
|
std::vector<std::pair<u32, u32>> __KernelModuleCodeRanges();
|
|
// For the next savestate load: code it leaves as it was, as (address, size). Its functions aren't
|
|
// hashed again.
|
|
void __KernelModuleSetUnchangedCodeOnLoad(std::vector<std::pair<u32, u32>> unchanged);
|
|
void __KernelModuleShutdown();
|
|
|
|
class MIPSState;
|
|
|
|
u32 __KernelGetModuleGP(SceUID module);
|
|
bool KernelModuleIsKernelMode(SceUID module);
|
|
bool __KernelLoadGEDump(MIPSState *mips, std::string_view base_filename, std::string *error_string);
|
|
// hostArgs, if set, are passed to the module instead of what paramPtr points to.
|
|
bool __KernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr, std::string *error_string, const std::vector<u8> *hostArgs = nullptr);
|
|
// The VSH (the XMB) is what's running, not a game it launched.
|
|
bool __KernelIsRunningVSH();
|
|
// What sceKernelExitGame does in a game the XMB launched, as on a PSP: back to the XMB, by booting it
|
|
// again. False if the XMB didn't launch this.
|
|
bool __KernelExitToVSH(MIPSState *mips);
|
|
// Whether the running game can quit back to the XMB, the way the HOME button's quit screen does it:
|
|
// the XMB launched it (and it's not the PS1 emulator, whose HOME menu is its own).
|
|
bool __KernelCanExitToVSH();
|
|
// Quits the running game back to the XMB from outside a syscall (the host's HOME screen, or after
|
|
// the game's exit callback returned): asks the frontend to boot the XMB, which stops the core.
|
|
// False if __KernelCanExitToVSH() is.
|
|
bool __KernelRequestExitToVSH();
|
|
// Exposed so sceVshBridge can reuse it directly for vshKernelLoadModuleBufferVSH, matching JPCSP.
|
|
SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr);
|
|
bool __KernelLoadExecFromBuffer(MIPSState *mips, const u8 *data, size_t size, u32 paramPtr, std::string *error_string);
|
|
// Exposed for HLE.cpp's "Unknown syscall" diagnostic - see the definition for details.
|
|
bool KernelFindImportByStubAddr(u32 stubAddr, std::string *importModuleName, u32 *nid, std::string *importingModuleName);
|
|
// Describes which loaded module (and section within it) an address falls in, e.g. "EBOOT.BIN.text+1234".
|
|
// Returns an empty string if the address isn't inside any currently loaded module.
|
|
bool DescribeModuleAddress(u32 address, char *buffer, size_t bufferSize);
|
|
int __KernelGPUReplay(MIPSState *mips);
|
|
void __KernelReturnFromModuleFunc(MIPSState *mips);
|
|
// fromTop puts the module at the top of the user partition instead of the bottom. Use it for
|
|
// firmware modules we inject before the game loads - taking the bottom pushes the game's own ELF
|
|
// up, which shifts every address in it and invalidates cheats and achievements, and outright
|
|
// fails for a game like Tekken 6 whose EBOOT must load at a fixed low address.
|
|
SceUID KernelLoadModule(const std::string &filename, std::string *error_string, bool fromTop = false);
|
|
|
|
// Whether a real (non-HLE-stub) module calling itself this is loaded. Lets a caller tell whether a
|
|
// library is already provided before bringing in another copy of it.
|
|
bool KernelModuleIsLoaded(std::string_view name);
|
|
|
|
// Take back out a module we brought in ourselves (the firmware swap in sceUtility). Frees the
|
|
// memory block, which is the point: a game that unloads its video libraries before a level load
|
|
// expects that space back, and one 33KB module left sitting in the middle of it is enough to
|
|
// break a contiguous allocation. Returns false if the id is not a live module.
|
|
bool KernelUnloadModuleByID(SceUID moduleId);
|
|
int __KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, SceKernelSMOption *smoption, bool *needsWait);
|
|
u32 __KernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus);
|
|
u32 sceKernelFindModuleByUID(u32 uid);
|
|
u32 sceKernelQueryLoadCoreCB();
|
|
|
|
void Register_ModuleMgrForUser();
|
|
void Register_ModuleMgrForKernel();
|
|
void Register_SystemCtrlForKernel();
|
|
void Register_SystemCtrlForUser();
|
|
|
|
// Expose for use by KUBridge.
|
|
u32 sceKernelLoadModule(MIPSState *mips, const char *name, u32 flags, u32 optionAddr);
|
|
u32 sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, u32 optionAddr);
|
|
|
|
// Address of a function a loaded module exports, or 0.
|
|
u32 KernelFindModuleExport(const char *libraryName, u32 nid);
|
|
// The stub through which a loaded module imports a function. 0 if it doesn't.
|
|
u32 KernelFindModuleImport(const char *moduleName, const char *libraryName, u32 nid);
|