/*
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 java.io.File;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import jpcsp.HLE.Modules;
import jpcsp.graphics.VideoEngine;
import jpcsp.hardware.Screen;
import jpcsp.memory.DebuggerMemory;
import jpcsp.memory.DirectBufferMemory;
import jpcsp.memory.FastMemory;
import jpcsp.memory.NativeMemory;
import jpcsp.memory.SafeDirectBufferMemory;
import jpcsp.memory.SafeFastMemory;
import jpcsp.memory.SafeNativeMemory;
import jpcsp.memory.SafeSparseNativeMemory;
import jpcsp.memory.SparseNativeMemory;
import jpcsp.memory.StandardMemory;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import org.apache.log4j.Logger;
public abstract class Memory {
public static Logger log = Logger.getLogger("memory");
private static Memory instance = null;
public static boolean useNativeMemory = false;
public static boolean useDirectBufferMemory = false;
public static boolean useSafeMemory = true;
public static final int addressMask = 0x3FFFFFFF;
private boolean ignoreInvalidMemoryAccess = false;
protected static final int MEMORY_PAGE_SHIFT = 12;
protected static boolean[] validMemoryPage = new boolean[0x00100000];
// Assume that a video check during a memcpy is only necessary
// when copying at least one screen row (at 2 bytes per pixel).
private static final int MINIMUM_LENGTH_FOR_VIDEO_CHECK = Screen.width * 2;
public static Memory getInstance() {
if (instance == null) {
//
// The following memory implementations are available:
// - StandardMemory : low memory requirements, performs address checking, slow
// - SafeFastMemory : high memory requirements, performs address checking, fast
// - FastMemory : high memory requirements, no address checking, very fast
// - SafeDirectBufferMemory: high memory requirements, performs address checking, moderate
// - DirectBufferMemory : high memory requirements, no address checking, fast
//
// Best choices are currently
// 1) SafeFastMemory (address check is useful when debugging programs)
// 2) StandardMemory when available memory is not sufficient for 1st choice
//
boolean useDebuggerMemory = false;
if (Settings.getInstance().readBool("emu.useDebuggerMemory") || new File(DebuggerMemory.mBrkFilePath).exists()) {
useDebuggerMemory = true;
// Always use the safe memory when using the debugger memory
useSafeMemory = true;
}
// Disable address checking when the option
// "ignoring invalid memory access" is selected.
if (Settings.getInstance().readBool("emu.ignoreInvalidMemoryAccess") && !useDebuggerMemory) {
useSafeMemory = false;
}
if (useNativeMemory) {
try {
System.loadLibrary("memory");
} catch (UnsatisfiedLinkError e) {
log.error("Cannot load memory library", e);
useNativeMemory = false;
}
}
if (useNativeMemory) {
if (useSafeMemory) {
instance = new SafeNativeMemory();
} else {
instance = new NativeMemory();
}
} else if (useDirectBufferMemory) {
if (useSafeMemory) {
instance = new SafeDirectBufferMemory();
} else {
instance = new DirectBufferMemory();
}
} else {
if (useSafeMemory) {
instance = new SafeFastMemory();
} else {
instance = new FastMemory();
}
}
if (instance != null) {
if (!instance.allocate()) {
instance = null;
// Second chance for a native memory...
if (useNativeMemory) {
if (useSafeMemory) {
instance = new SafeSparseNativeMemory();
} else {
instance = new SparseNativeMemory();
}
if (!instance.allocate()) {
log.warn(String.format("Cannot allocate native memory"));
instance = null;
}
}
}
}
if (instance == null) {
instance = new StandardMemory();
if (!instance.allocate()) {
instance = null;
}
}
if (instance == null) {
throw new OutOfMemoryError("Cannot allocate memory");
}
if (useDebuggerMemory) {
DebuggerMemory.install();
}
if (log.isDebugEnabled()) {
log.debug(String.format("Using %s", instance.getClass().getName()));
}
}
return instance;
}
private class IgnoreInvalidMemoryAccessSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setIgnoreInvalidMemoryAccess(value);
}
}
protected Memory() {
Settings.getInstance().registerSettingsListener("Memory", "emu.ignoreInvalidMemoryAccess", new IgnoreInvalidMemoryAccessSettingsListerner());
}
public static void setInstance(Memory mem) {
instance = mem;
}
public void invalidMemoryAddress(int address, String prefix, int status) {
String message = String.format("%s - Invalid memory address: 0x%08X PC=0x%08X", prefix, address, Emulator.getProcessor().cpu.pc);
if (ignoreInvalidMemoryAccess) {
log.warn("IGNORED: " + message);
} else {
log.error(message);
Emulator.PauseEmuWithStatus(status);
}
}
public void invalidMemoryAddress(int address, int length, String prefix, int status) {
String message = String.format("%s - Invalid memory address: 0x%08X-0x%08X(length=0x%X) PC=0x%08X", prefix, address, address + length, length, Emulator.getProcessor().cpu.pc);
if (ignoreInvalidMemoryAccess) {
log.warn("IGNORED: " + message);
} else {
log.error(message);
Emulator.PauseEmuWithStatus(status);
}
}
public boolean read32AllowedInvalidAddress(int address) {
//
// Ugly hack for programs using pspsdk :-(
//
// The function pspSdkInstallNoPlainModuleCheckPatch()
// is trying to patch 2 psp modules and is expecting to have
// the module stub implemented as a Jump instruction,
// something like:
// [08XXXXXX]: j YYYYYYYY // YYYYYYYY = XXXXXX << 2
// [00000000]: nop
//
// Jpcsp is however based on the following code sequence, e.g.:
// [03E00008]: jr $ra
// [00081B4C]: syscall 0x0206D
//
// The function pspSdkInstallNoPlainModuleCheckPatch()
// is retrieving the address of the Jump instruction and reading
// from it in kernel mode.
// On jpcsp, it is thus trying to read at the following address
// 0x8f800020 = (0x03E00008 << 2) || 0x80000000
// up to 0x8f8001ac
//
// The hack here is to allow these memory reads and returns 0.
//
// Here is the C code from pspsdk:
//
// int pspSdkInstallNoPlainModuleCheckPatch(void)
// {
// u32 *addr;
// int i;
//
// addr = (u32*) (0x80000000 | ((sceKernelProbeExecutableObject & 0x03FFFFFF) << 2));
// //printf("sceKernelProbeExecutableObject %p\n", addr);
// for(i = 0; i < 100; i++)
// {
// if((addr[i] & 0xFFE0FFFF) == LOAD_EXEC_PLAIN_CHECK)
// {
// //printf("Found instruction %p\n", &addr[i]);
// addr[i] = (LOAD_EXEC_PLAIN_PATCH | (addr[i] & ~0xFFE0FFFF));
// }
// }
//
// addr = (u32*) (0x80000000 | ((sceKernelCheckPspConfig & 0x03FFFFFF) << 2));
// //printf("sceCheckPspConfig %p\n", addr);
// for(i = 0; i < 100; i++)
// {
// if((addr[i] & 0xFFE0FFFF) == LOAD_EXEC_PLAIN_CHECK)
// {
// //printf("Found instruction %p\n", &addr[i]);
// addr[i] = (LOAD_EXEC_PLAIN_PATCH | (addr[i] & ~0xFFE0FFFF));
// }
// }
//
// sceKernelDcacheWritebackAll();
//
// return 0;
// }
//
if ((address >= 0x8f800020 && address <= 0x8f8001ac)
|| (address >= 0x0f800020 && address <= 0x0f8001ac)) { // Accept also masked address
log.debug("read32 - ignoring pspSdkInstallNoPlainModuleCheckPatch");
return true;
}
return false;
}
public abstract void Initialise();
public abstract int read8(int address);
public abstract int read16(int address);
public abstract int read32(int address);
public abstract void write8(int address, byte data);
public abstract void write16(int address, short data);
public abstract void write32(int address, int data);
public abstract void memset(int address, byte data, int length);
public abstract Buffer getMainMemoryByteBuffer();
public abstract Buffer getBuffer(int address, int length);
public abstract void copyToMemory(int address, ByteBuffer source, int length);
protected abstract void memcpy(int destination, int source, int length, boolean checkOverlap);
public static boolean isAddressGood(int address) {
return validMemoryPage[address >>> MEMORY_PAGE_SHIFT];
}
public static boolean isAddressAlignedTo(int address, int alignment) {
return (address % alignment) == 0;
}
public static boolean isRawAddressGood(int rawAddress) {
return validMemoryPage[rawAddress >> MEMORY_PAGE_SHIFT];
}
public boolean allocate() {
for (int i = 0; i < validMemoryPage.length; i++) {
int address = normalizeAddress(i << MEMORY_PAGE_SHIFT);
boolean isValid = false;
if (address >= MemoryMap.START_RAM && address <= MemoryMap.END_RAM) {
isValid = true;
} else if (address >= MemoryMap.START_VRAM && address <= MemoryMap.END_VRAM) {
isValid = true;
} else if (address >= MemoryMap.START_SCRATCHPAD && address <= MemoryMap.END_SCRATCHPAD) {
isValid = true;
}
validMemoryPage[i] = isValid;
}
return true;
}
public long read64(int address) {
long low = read32(address);
long high = read32(address + 4);
return (low & 0xFFFFFFFFL) | (high << 32);
}
public void write64(int address, long data) {
write32(address, (int) data);
write32(address + 4, (int) (data >> 32));
}
// memcpy does not check overlapping source and destination areas
public void memcpy(int destination, int source, int length) {
memcpy(destination, source, length, false);
}
/**
* Same as memcpy but checking if the source/destination are not used as video textures.
*
* @param destination destination address
* @param source source address
* @param length length in bytes to be copied
*/
public void memcpyWithVideoCheck(int destination, int source, int length) {
// As an optimization, do not perform the video check if we are copying only a small memory area.
if (length >= MINIMUM_LENGTH_FOR_VIDEO_CHECK) {
// If copying to the VRAM or the frame buffer, do not cache the texture
if (isVRAM(destination) || Modules.sceDisplayModule.isFbAddress(destination)) {
// If the display is rendering to the destination address, wait for its completion
// before performing the memcpy.
Modules.sceDisplayModule.waitForRenderingCompletion(destination);
VideoEngine.getInstance().addVideoTexture(destination, source, length);
}
// If copying from the VRAM, force the saving of the GE to memory
if (isVRAM(source) && Modules.sceDisplayModule.getSaveGEToTexture()) {
VideoEngine.getInstance().addVideoTexture(source, source + length);
}
} else if (isVRAM(destination)) {
Modules.sceDisplayModule.waitForRenderingCompletion(destination);
}
memcpy(destination, source, length);
}
/**
* Same as memset but checking if the destination is not used as video texture.
*
* @param address destination address
* @param data byte to be set in memory
* @param length length in bytes to be set
*/
public void memsetWithVideoCheck(int address, byte data, int length) {
// As an optimization, do not perform the video check if we are setting only a small memory area.
if (length >= MINIMUM_LENGTH_FOR_VIDEO_CHECK) {
// If changing the VRAM or the frame buffer, do not cache the texture
if (isVRAM(address) || Modules.sceDisplayModule.isFbAddress(address)) {
// If the display is rendering to the destination address, wait for its completion
// before performing the memcpy.
Modules.sceDisplayModule.waitForRenderingCompletion(address);
VideoEngine.getInstance().addVideoTexture(address, address + length);
}
} else if (isVRAM(address)) {
Modules.sceDisplayModule.waitForRenderingCompletion(address);
}
memset(address, data, length);
}
// memmove reproduces the bytes correctly at destination even if the two areas overlap
public void memmove(int destination, int source, int length) {
memcpy(destination, source, length, true);
}
public static int normalizeAddress(int address) {
address &= addressMask;
// Test on a PSP: 0x4200000 is equivalent to 0x4000000
if ((address & 0xFF000000) == MemoryMap.START_VRAM) {
address &= 0xFF1FFFFF;
}
return address;
}
protected boolean areOverlapping(int destination, int source, int length) {
if (source + length <= destination || destination + length <= source) {
return false;
}
return true;
}
public boolean isIgnoreInvalidMemoryAccess() {
return ignoreInvalidMemoryAccess;
}
public void setIgnoreInvalidMemoryAccess(boolean ignoreInvalidMemoryAccess) {
this.ignoreInvalidMemoryAccess = ignoreInvalidMemoryAccess;
log.info(String.format("Ignore invalid memory access: %b", ignoreInvalidMemoryAccess));
}
public static boolean isRAM(int address) {
address &= addressMask;
return address >= MemoryMap.START_RAM && address <= MemoryMap.END_RAM;
}
public static boolean isVRAM(int address) {
address &= addressMask;
// Test first against END_VRAM as it is most likely to fail first (because RAM is above VRAM)
return address <= MemoryMap.END_VRAM && address >= MemoryMap.START_VRAM;
}
}