/*
This file is part of jpcsp.
Jpcsp 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, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.HLE.modules;
import jpcsp.HLE.CanBeNull;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLELogging;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.PspString;
import jpcsp.HLE.SceKernelErrorException;
import jpcsp.HLE.TPointer32;
import java.io.IOException;
import java.nio.ByteBuffer;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_PROHIBIT_LOADEXEC_DEVICE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_LOADEXEC_FILENAME;
import jpcsp.Emulator;
import jpcsp.GeneralJpcspException;
import jpcsp.Loader;
import jpcsp.Memory;
import jpcsp.Processor;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.HLE.Modules;
import jpcsp.HLE.VFS.IVirtualFile;
import jpcsp.HLE.VFS.xmb.XmbIsoVirtualFile;
import jpcsp.HLE.kernel.types.SceKernelCallbackInfo;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.SceModule;
import jpcsp.filesystems.SeekableDataInput;
import jpcsp.filesystems.umdiso.UmdIsoReader;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.MemoryReader;
import jpcsp.util.Utilities;
import org.apache.log4j.Logger;
public class LoadExecForUser extends HLEModule {
public static Logger log = Modules.getLogger("LoadExecForUser");
protected int registeredExitCallbackUid;
protected static final String encryptedBootPath = "disc0:/PSP_GAME/SYSDIR/EBOOT.BIN";
protected static final String unencryptedBootPath = "disc0:/PSP_GAME/SYSDIR/BOOT.BIN";
public void triggerExitCallback() {
Modules.ThreadManForUserModule.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_EXIT, 0);
}
public int hleKernelLoadExec(PspString filename, int argSize, int argAddr) {
String name = filename.getString();
// The PSP is replacing a loadexec of disc0:/PSP_GAME/SYSDIR/BOOT.BIN with EBOOT.BIN
if (name.equals(unencryptedBootPath)) {
log.info(String.format("sceKernelLoadExec '%s' replaced by '%s'", name, encryptedBootPath));
name = encryptedBootPath;
}
// Flush system memory to mimic a real PSP reset.
Modules.SysMemUserForUserModule.reset();
byte[] arguments = null;
if (argSize > 0) {
// Save the memory content for the arguments because
// the memory would be overwritten by the loading of the new module.
arguments = new byte[argSize];
IMemoryReader memoryReader = MemoryReader.getMemoryReader(argAddr, argSize, 1);
for (int i = 0; i < argSize; i++) {
arguments[i] = (byte) memoryReader.readNext();
}
}
ByteBuffer moduleBuffer = null;
IVirtualFile vFile = Modules.IoFileMgrForUserModule.getVirtualFile(name, IoFileMgrForUser.PSP_O_RDONLY, 0);
UmdIsoReader iso = null;
if (vFile instanceof XmbIsoVirtualFile) {
try {
IVirtualFile vFileLoadExec = ((XmbIsoVirtualFile) vFile).ioReadForLoadExec();
if (vFileLoadExec != null) {
iso = ((XmbIsoVirtualFile) vFile).getIsoReader();
vFile.ioClose();
vFile = vFileLoadExec;
}
} catch (IOException e) {
log.debug("hleKernelLoadExec", e);
}
}
if (vFile != null) {
byte[] moduleBytes = Utilities.readCompleteFile(vFile);
vFile.ioClose();
if (moduleBytes != null) {
moduleBuffer = ByteBuffer.wrap(moduleBytes);
}
} else {
SeekableDataInput moduleInput = Modules.IoFileMgrForUserModule.getFile(name, IoFileMgrForUser.PSP_O_RDONLY);
if (moduleInput != null) {
try {
byte[] moduleBytes = new byte[(int) moduleInput.length()];
moduleInput.readFully(moduleBytes);
moduleInput.close();
moduleBuffer = ByteBuffer.wrap(moduleBytes);
} catch (IOException e) {
log.error(String.format("sceKernelLoadExec - Error while loading module '%s'", name), e);
return ERROR_KERNEL_PROHIBIT_LOADEXEC_DEVICE;
}
}
}
try {
if (moduleBuffer != null) {
SceModule module = Emulator.getInstance().load(name, moduleBuffer, true);
Emulator.getClock().resume();
// After a sceKernelLoadExec, host0: is relative to the directory where
// the loaded file (prx) was located.
// E.g.:
// after
// sceKernelLoadExec("disc0:/PSP_GAME/USRDIR/A.PRX")
// the following file access
// sceIoOpen("host0:B")
// is actually referencing the file
// disc0:/PSP_GAME/USRDIR/B
int pathIndex = name.lastIndexOf("/");
if (pathIndex >= 0) {
Modules.IoFileMgrForUserModule.setHost0Path(name.substring(0, pathIndex + 1));
}
if ((module.fileFormat & Loader.FORMAT_ELF) != Loader.FORMAT_ELF) {
log.warn("sceKernelLoadExec - failed, target is not an ELF");
throw new SceKernelErrorException(ERROR_KERNEL_ILLEGAL_LOADEXEC_FILENAME);
}
// Set the given arguments to the root thread.
// Do not pass the file name as first parameter (tested on PSP).
SceKernelThreadInfo rootThread = Modules.ThreadManForUserModule.getRootThread(module);
Modules.ThreadManForUserModule.hleKernelSetThreadArguments(rootThread, arguments, argSize);
// The memory model (32MB / 64MB) could have been changed, update the RuntimeContext
RuntimeContext.updateMemory();
if (iso != null) {
Modules.IoFileMgrForUserModule.setIsoReader(iso);
Modules.sceUmdUserModule.setIsoReader(iso);
}
}
} catch (GeneralJpcspException e) {
log.error("General Error", e);
Emulator.PauseEmu();
} catch (IOException e) {
log.error(String.format("sceKernelLoadExec - Error while loading module '%s'", name), e);
return ERROR_KERNEL_PROHIBIT_LOADEXEC_DEVICE;
}
return 0;
}
@HLELogging(level="info")
@HLEFunction(nid = 0xBD2F1094, version = 150, checkInsideInterrupt = true)
public int sceKernelLoadExec(PspString filename, @CanBeNull TPointer32 optionAddr) {
int argSize = 0;
int argAddr = 0;
if (optionAddr.isNotNull()) {
int optSize = optionAddr.getValue(0); // Size of the option struct.
if (optSize >= 16) {
argSize = optionAddr.getValue(4); // Size of memory required for arguments.
argAddr = optionAddr.getValue(8); // Arguments (memory area of size argSize).
int keyAddr = optionAddr.getValue(12); // Pointer to an encryption key (may not be used).
if (log.isDebugEnabled()) {
log.debug(String.format("sceKernelLoadExec params: optSize=%d, argSize=%d, argAddr=0x%08X, keyAddr=0x%08X: %s", optSize, argSize, argAddr, keyAddr, Utilities.getMemoryDump(argAddr, argSize)));
}
}
}
return hleKernelLoadExec(filename, argSize, argAddr);
}
@HLELogging(level="info")
@HLEFunction(nid = 0x2AC9954B, version = 150, checkInsideInterrupt = true)
public int sceKernelExitGameWithStatus(int status) {
Emulator.PauseEmuWithStatus(status);
RuntimeContext.reset();
Modules.ThreadManForUserModule.stop();
return 0;
}
@HLELogging(level="info")
@HLEFunction(nid = 0x05572A5F, version = 150, checkInsideInterrupt = true)
public int sceKernelExitGame() {
Emulator.PauseEmu();
RuntimeContext.reset();
Modules.ThreadManForUserModule.stop();
return 0;
}
@HLEFunction(nid = 0x4AC57943, version = 150, checkInsideInterrupt = true)
public int sceKernelRegisterExitCallback(int uid) {
if (Modules.ThreadManForUserModule.hleKernelRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_EXIT, uid)) {
registeredExitCallbackUid = uid;
}
return 0;
}
@HLELogging(level="info")
@HLEFunction(nid = 0x362A956B, version = 500)
public int LoadExecForUser_362A956B() {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B registeredExitCallbackUid=0x%X", registeredExitCallbackUid));
}
SceKernelCallbackInfo callbackInfo = Modules.ThreadManForUserModule.getCallbackInfo(registeredExitCallbackUid);
if (callbackInfo == null) {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B registeredExitCallbackUid=0x%x callback not found", registeredExitCallbackUid));
}
return SceKernelErrors.ERROR_KERNEL_NOT_FOUND_CALLBACK;
}
int callbackArgument = callbackInfo.getCallbackArgument();
if (!Memory.isAddressGood(callbackArgument)) {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B invalid address for callbackArgument=0x%08X", callbackArgument));
}
return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ADDR;
}
Memory mem = Processor.memory;
int unknown1 = mem.read32(callbackArgument - 8);
if (unknown1 < 0 || unknown1 >= 4) {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B invalid value unknown1=0x%08X", unknown1));
}
return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT;
}
int parameterArea = mem.read32(callbackArgument - 4);
if (!Memory.isAddressGood(parameterArea)) {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B invalid address for parameterArea=0x%08X", parameterArea));
}
return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ADDR;
}
int size = mem.read32(parameterArea);
if (size < 12) {
if (log.isDebugEnabled()) {
log.debug(String.format("LoadExecForUser_362A956B invalid parameter area size %d", size));
}
return SceKernelErrors.ERROR_KERNEL_ILLEGAL_SIZE;
}
mem.write32(parameterArea + 4, 0);
mem.write32(parameterArea + 8, -1);
return 0;
}
}