/*
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;
import static jpcsp.HLE.modules.SysMemUserForUser.USER_PARTITION_ID;
import java.io.IOException;
import java.nio.ByteBuffer;
import jpcsp.Allegrex.compiler.Compiler;
import jpcsp.Allegrex.compiler.Profiler;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.Debugger.InstructionCounter;
import jpcsp.Debugger.StepLogger;
import jpcsp.GUI.IMainGUI;
import jpcsp.HLE.HLEModuleManager;
import jpcsp.HLE.HLEUidObjectMapping;
import jpcsp.HLE.Modules;
import jpcsp.HLE.kernel.Managers;
import jpcsp.HLE.kernel.managers.SceUidManager;
import jpcsp.HLE.kernel.types.SceModule;
import jpcsp.graphics.GEProfiler;
import jpcsp.graphics.VertexCache;
import jpcsp.graphics.VideoEngine;
import jpcsp.graphics.RE.externalge.ExternalGE;
import jpcsp.graphics.RE.software.BasePrimitiveRenderer;
import jpcsp.graphics.RE.software.BaseRenderer;
import jpcsp.graphics.RE.software.RendererExecutor;
import jpcsp.graphics.textures.TextureCache;
import jpcsp.hardware.Battery;
import jpcsp.hardware.Interrupts;
import jpcsp.hardware.Wlan;
import jpcsp.memory.MemorySections;
import jpcsp.network.proonline.ProOnlineNetworkAdapter;
import jpcsp.scheduler.Scheduler;
import jpcsp.sound.SoundChannel;
import jpcsp.util.DurationStatistics;
import jpcsp.util.JpcspDialogManager;
import org.apache.log4j.Logger;
/*
* TODO list:
* 1. Cleanup initialization in initNewPsp():
* - UMD: calls setFirmwareVersion before initNewPsp (PSF is read separate from BOOT.BIN).
* - PBP: calls initNewPsp before setFirmwareVersion (PSF is embedded in PBP).
* - ELF/PRX: only calls initNewPsp (doesn't have a PSF).
*/
public class Emulator implements Runnable {
private static Emulator instance;
private static Processor processor;
private static Clock clock;
private static Scheduler scheduler;
private boolean moduleLoaded;
private Thread mainThread;
public static boolean run = false;
public static boolean pause = false;
private static IMainGUI gui;
private InstructionCounter instructionCounter;
public static Logger log = Logger.getLogger("emu");
private SceModule module;
private int firmwareVersion = 999;
private String[] bootModuleBlackList = {"Prometheus Loader"};
public Emulator(IMainGUI gui) {
Emulator.gui = gui;
processor = new Processor();
clock = new Clock();
scheduler = Scheduler.getInstance();
moduleLoaded = false;
mainThread = new Thread(this, "Emu");
instance = this;
}
public Thread getMainThread() {
return mainThread;
}
public static void exit() {
if (DurationStatistics.collectStatistics) {
log.info(TextureCache.getInstance().statistics);
}
RendererExecutor.exit();
VertexCache.getInstance().exit();
Compiler.exit();
RuntimeContext.exit();
Profiler.exit();
GEProfiler.exit();
BaseRenderer.exit();
BasePrimitiveRenderer.exit();
ExternalGE.exit();
if (DurationStatistics.collectStatistics && Modules.ThreadManForUserModule.statistics != null && Modules.sceDisplayModule.statistics != null) {
long totalMillis = getClock().milliTime();
long displayMillis = Modules.sceDisplayModule.statistics.cumulatedTimeMillis;
long idleCpuMillis = RuntimeContext.idleDuration.getCpuDurationMillis();
long compilationCpuMillis = Compiler.compileDuration.getCpuDurationMillis();
long cpuMillis = Modules.ThreadManForUserModule.statistics.allCpuMillis - compilationCpuMillis - idleCpuMillis;
long cpuCycles = Modules.ThreadManForUserModule.statistics.allCycles;
double totalSecs = totalMillis / 1000.0;
double displaySecs = displayMillis / 1000.0;
double cpuSecs = cpuMillis / 1000.0;
if (totalSecs != 0) {
log.info("Total execution time: " + String.format("%.3f", totalSecs) + "s");
log.info(" PSP CPU time: " + String.format("%.3f", cpuSecs) + "s (" + String.format("%.1f", cpuSecs / totalSecs * 100) + "%)");
log.info(" Display time: " + String.format("%.3f", displaySecs) + "s (" + String.format("%.1f", displaySecs / totalSecs * 100) + "%)");
}
if (VideoEngine.getStatistics() != null) {
long videoCalls = VideoEngine.getStatistics().numberCalls;
if (videoCalls != 0) {
log.info("Elapsed time per frame: " + String.format("%.3f", totalSecs / videoCalls) + "s:");
log.info(" Display time: " + String.format("%.3f", displaySecs / videoCalls));
log.info(" PSP CPU time: " + String.format("%.3f", cpuSecs / videoCalls) + " (" + (cpuCycles / videoCalls) + " instr)");
}
if (totalSecs != 0) {
log.info("Display Speed: " + String.format("%.2f", videoCalls / totalSecs) + " FPS");
}
}
if (cpuSecs != 0) {
log.info("PSP CPU Speed: " + String.format("%.2f", cpuCycles / cpuSecs / 1000000.0) + "MHz (" + (long) (cpuCycles / cpuSecs) + " instructions per second)");
}
}
SoundChannel.exit();
}
private boolean isBootModuleBad(String name) {
for (String moduleName : bootModuleBlackList) {
if (name.equals(moduleName)) {
return true;
}
}
return false;
}
public SceModule load(String pspfilename, ByteBuffer f) throws IOException, GeneralJpcspException {
return load(pspfilename, f, false);
}
public SceModule load(String pspfilename, ByteBuffer f, boolean fromSyscall) throws IOException, GeneralJpcspException {
initNewPsp(fromSyscall);
HLEModuleManager.getInstance().loadAvailableFlash0Modules();
module = Loader.getInstance().LoadModule(pspfilename, f, MemoryMap.START_USERSPACE + 0x4000, USER_PARTITION_ID, USER_PARTITION_ID, false, true, fromSyscall);
if ((module.fileFormat & Loader.FORMAT_ELF) != Loader.FORMAT_ELF) {
throw new GeneralJpcspException("File format not supported!");
}
if (isBootModuleBad(module.modname)) {
JpcspDialogManager.showError(null, java.util.ResourceBundle.getBundle("jpcsp/languages/jpcsp").getString("Emulator.strPrometheusLoader.text"));
}
moduleLoaded = true;
initCpu(fromSyscall);
// Delete breakpoints and reset to PC
if (State.debugger != null) {
State.debugger.resetDebugger();
}
// Update instruction counter dialog with the new app
if (instructionCounter != null) {
instructionCounter.setModule(module);
}
return module;
}
private void initCpu(boolean fromSyscall) {
RuntimeContext.update();
int entryAddr = module.entry_addr;
if (Memory.isAddressGood(module.module_start_func)) {
if (module.module_start_func != entryAddr) {
log.warn(String.format("Using the module start function as module entry: 0x%08X instead of 0x%08X", module.module_start_func, entryAddr));
entryAddr = module.module_start_func;
}
}
HLEModuleManager.getInstance().startModules(fromSyscall);
Modules.ThreadManForUserModule.Initialise(module, entryAddr, module.attribute, module.pspfilename, module.modid, module.gp_value, fromSyscall);
if (State.memoryViewer != null) {
State.memoryViewer.RefreshMemory();
}
}
public void initNewPsp(boolean fromSyscall) {
moduleLoaded = false;
HLEModuleManager.getInstance().stopModules();
NIDMapper.getInstance().unloadAll();
RuntimeContext.reset();
if (!fromSyscall) {
// Do not reset the profiler if we have been called from sceKernelLoadExec
Profiler.reset();
GEProfiler.reset();
// Do not reset the clock if we have been called from sceKernelLoadExec
getClock().reset();
}
getProcessor().reset();
getScheduler().reset();
Memory mem = Memory.getInstance();
if (!fromSyscall) {
// Clear all memory, including VRAM.
mem.Initialise();
} else {
// Clear all memory excepted VRAM.
// E.g. screen is not cleared when executing syscall sceKernelLoadExec().
mem.memset(MemoryMap.START_SCRATCHPAD, (byte) 0, MemoryMap.SIZE_SCRATCHPAD);
mem.memset(MemoryMap.START_RAM, (byte) 0, MemoryMap.SIZE_RAM);
}
Battery.initialize();
Interrupts.initialize();
Wlan.initialize();
jpcsp.HLE.kernel.types.SceModule.ResetAllocator();
SceUidManager.reset();
HLEUidObjectMapping.reset();
ProOnlineNetworkAdapter.init();
if (State.fileLogger != null) {
State.fileLogger.resetLogging();
}
MemorySections.getInstance().reset();
HLEModuleManager.getInstance().init();
Managers.reset();
Modules.SysMemUserForUserModule.start();
Modules.SysMemUserForUserModule.setFirmwareVersion(firmwareVersion);
}
@Override
public void run() {
RuntimeContext.start();
GEProfiler.initialise();
clock.resume();
while (true) {
if (pause) {
clock.pause();
try {
synchronized (this) {
while (pause) {
wait();
}
}
} catch (InterruptedException e) {
// Ignore exception
}
clock.resume();
}
if (RuntimeContext.isCompilerEnabled()) {
RuntimeContext.run();
} else {
processor.step();
Modules.sceGe_userModule.step();
Modules.ThreadManForUserModule.step();
scheduler.step();
Modules.sceDisplayModule.step();
if (State.debugger != null) {
State.debugger.step();
}
}
}
}
public synchronized void RunEmu() {
if (!moduleLoaded) {
Emulator.log.debug("Nothing loaded, can't run...");
gui.RefreshButtons();
return;
}
if (pause) {
pause = false;
notifyAll();
} else if (!run) {
run = true;
mainThread.start();
}
Modules.sceDisplayModule.setGeDirty(true);
gui.RefreshButtons();
if (State.debugger != null) {
State.debugger.RefreshButtons();
}
}
private static void PauseEmu(boolean hasStatus, int status) {
if (run && !pause) {
pause = true;
if (hasStatus) {
StepLogger.setStatus(status);
}
gui.RefreshButtons();
if (State.debugger != null) {
State.debugger.RefreshButtons();
State.debugger.SafeRefreshDebugger(true);
}
if (State.memoryViewer != null) {
State.memoryViewer.SafeRefreshMemory();
}
if (State.imageViewer != null) {
State.imageViewer.SafeRefreshImage();
}
StepLogger.flush();
}
}
public static synchronized void PauseEmu() {
PauseEmu(false, 0);
}
public static final int EMU_STATUS_OK = 0x00;
public static final int EMU_STATUS_UNKNOWN = 0xFFFFFFFF;
public static final int EMU_STATUS_WDT_IDLE = 0x01;
public static final int EMU_STATUS_WDT_HOG = 0x02;
public static final int EMU_STATUS_WDT_ANY = EMU_STATUS_WDT_IDLE | EMU_STATUS_WDT_HOG;
public static final int EMU_STATUS_MEM_READ = 0x04;
public static final int EMU_STATUS_MEM_WRITE = 0x08;
public static final int EMU_STATUS_MEM_ANY = EMU_STATUS_MEM_READ | EMU_STATUS_MEM_WRITE;
public static final int EMU_STATUS_BREAKPOINT = 0x10;
public static final int EMU_STATUS_UNIMPLEMENTED = 0x20;
public static final int EMU_STATUS_PAUSE = 0x40;
public static final int EMU_STATUS_JUMPSELF = 0x80;
public static final int EMU_STATUS_BREAK = 0x100;
public static final int EMU_STATUS_HALT = 0x200;
public static synchronized void PauseEmuWithStatus(int status) {
PauseEmu(true, status);
}
public static void setFpsTitle(String fps) {
gui.setMainTitle(fps);
}
public static Processor getProcessor() {
return processor;
}
public static Memory getMemory() {
return Memory.getInstance();
}
public static Clock getClock() {
return clock;
}
private static void setClock(Clock clock) {
Emulator.clock = clock;
}
public static Scheduler getScheduler() {
return scheduler;
}
public static IMainGUI getMainGUI() {
return gui;
}
public static Emulator getInstance() {
return instance;
}
public void setInstructionCounter(InstructionCounter instructionCounter) {
this.instructionCounter = instructionCounter;
instructionCounter.setModule(module);
}
public int getFirmwareVersion() {
return firmwareVersion;
}
/**
* @param firmwareVersion : in this format: ABB, where A = major and B =
* minor, for example 271
*/
public void setFirmwareVersion(int firmwareVersion) {
this.firmwareVersion = firmwareVersion;
Modules.SysMemUserForUserModule.setFirmwareVersion(this.firmwareVersion);
RuntimeContext.setFirmwareVersion(firmwareVersion);
}
/**
* @param firmwareVersion : in this format: "A.BB", where A = major and B =
* minor, for example "2.71"
*/
public void setFirmwareVersion(String firmwareVersion) {
setFirmwareVersion(HLEModuleManager.psfFirmwareVersionToInt(firmwareVersion));
}
public static void setVariableSpeedClock(int numerator, int denominator) {
if (getClock() instanceof VariableSpeedClock) {
// Update the speed of the current variable speed clock
((VariableSpeedClock) getClock()).setSpeed(numerator, denominator);
} else if (numerator != 1 || denominator != 1) {
// Change the clock to a variable speed clock with the given speed
VariableSpeedClock variableSpeedClock = new VariableSpeedClock(clock, numerator, denominator);
setClock(variableSpeedClock);
}
}
}