/* 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.ThreadManForUser.NOP; import static jpcsp.format.Elf32SectionHeader.SHF_ALLOCATE; import static jpcsp.format.Elf32SectionHeader.SHF_EXECUTE; import static jpcsp.format.Elf32SectionHeader.SHF_NONE; import static jpcsp.format.Elf32SectionHeader.SHF_WRITE; import static jpcsp.util.Utilities.readUnaligned32; import static jpcsp.util.Utilities.writeUnaligned32; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import jpcsp.Allegrex.Common; import jpcsp.Allegrex.Opcodes; import jpcsp.Debugger.ElfHeaderInfo; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.Managers; import jpcsp.HLE.kernel.types.SceModule; import jpcsp.HLE.modules.SysMemUserForUser; import jpcsp.HLE.modules.ThreadManForUser; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.format.DeferredStub; import jpcsp.format.DeferredVStub32; import jpcsp.format.DeferredVStubHI16; import jpcsp.format.DeferredVstubLO16; import jpcsp.format.Elf32; import jpcsp.format.Elf32EntHeader; import jpcsp.format.Elf32ProgramHeader; import jpcsp.format.Elf32Relocate; import jpcsp.format.Elf32SectionHeader; import jpcsp.format.Elf32StubHeader; import jpcsp.format.PBP; import jpcsp.format.PSF; import jpcsp.format.PSP; import jpcsp.format.PSPModuleInfo; import jpcsp.memory.IMemoryReader; import jpcsp.memory.MemoryReader; import jpcsp.memory.MemorySection; import jpcsp.memory.MemorySections; import jpcsp.settings.Settings; import jpcsp.util.Utilities; public class Loader { private static Loader instance; private static Logger log = Logger.getLogger("loader"); public final static int SCE_MAGIC = 0x4543537E; public final static int PSP_MAGIC = 0x50535000; public final static int EDAT_MAGIC = 0x54414445; public final static int FIRMWAREVERSION_HOMEBREW = 999; // Simulate version 9.99 instead of 1.50 // Format bits public final static int FORMAT_UNKNOWN = 0x00; public final static int FORMAT_ELF = 0x01; public final static int FORMAT_PRX = 0x02; public final static int FORMAT_PBP = 0x04; public final static int FORMAT_SCE = 0x08; public final static int FORMAT_PSP = 0x10; public static Loader getInstance() { if (instance == null) instance = new Loader(); return instance; } private Loader() { } /** * @param pspfilename Example: * ms0:/PSP/GAME/xxx/EBOOT.PBP * disc0:/PSP_GAME/SYSDIR/BOOT.BIN * disc0:/PSP_GAME/SYSDIR/EBOOT.BIN * xxx:/yyy/zzz.prx * @param f the module file contents * @param baseAddress should be at least 64-byte aligned, * or how ever much is the default alignment in pspsysmem. * @param analyzeOnly true, if the module is not really loaded, but only * the SceModule object is returned; * false, if the module is really loaded in memory. * @return Always a SceModule object, you should check the * fileFormat member against the FORMAT_* bits. * Example: (fileFormat & FORMAT_ELF) == FORMAT_ELF **/ public SceModule LoadModule(String pspfilename, ByteBuffer f, int baseAddress, int mpidText, int mpidData, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { SceModule module = new SceModule(false); int currentOffset = f.position(); module.fileFormat = FORMAT_UNKNOWN; module.pspfilename = pspfilename; module.mpidtext = mpidText; module.mpiddata = mpidData; // The PSP startup code requires a ":" into the argument passed to the root thread. // On Linux, there is no ":" in the file name when loading a .pbp file; // on Windows, there is luckily one ":" in "C:/...". // Simulate a ":" by prefixing by "ms0:", as this is not really used by an application. if (!module.pspfilename.contains(":")) { module.pspfilename = "ms0:" + module.pspfilename; } if (f.capacity() - f.position() == 0) { log.error("LoadModule: no data."); return module; } // chain loaders do { f.position(currentOffset); if (LoadPBP(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall)) { currentOffset = f.position(); // probably kxploit stub if (currentOffset == f.limit()) break; } else if (!fromSyscall) { loadPSF(module, analyzeOnly, allocMem, fromSyscall); } if (module.psf != null) { log.info(String.format("PBP meta data:%s%s", System.lineSeparator(), module.psf)); if (!fromSyscall) { // Set firmware version from PSF embedded in PBP if (module.psf.isLikelyHomebrew()) { Emulator.getInstance().setFirmwareVersion(FIRMWAREVERSION_HOMEBREW); } else { Emulator.getInstance().setFirmwareVersion(module.psf.getString("PSP_SYSTEM_VER")); } Modules.SysMemUserForUserModule.setMemory64MB(module.psf.getNumeric("MEMSIZE") == 1); } } f.position(currentOffset); if (LoadSPRX(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall)) break; f.position(currentOffset); if (LoadSCE(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall)) break; f.position(currentOffset); if (LoadPSP(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall)) break; f.position(currentOffset); if (LoadELF(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall)) break; f.position(currentOffset); LoadUNK(f, module, baseAddress, analyzeOnly, allocMem, fromSyscall); } while(false); patchModule(module); return module; } private void loadPSF(SceModule module, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) { if (module.psf != null) return; String filetoload = module.pspfilename; if (filetoload.startsWith("ms0:")) filetoload = filetoload.replace("ms0:", "ms0"); // PBP doesn't have a PSF included. Check for one in kxploit directories File metapbp = null; File pbpfile = new File(filetoload); if (pbpfile.getParentFile() == null || pbpfile.getParentFile().getParentFile() == null) { // probably dynamically loading a prx return; } // %__SCE__kxploit File metadir = new File(pbpfile.getParentFile().getParentFile().getPath() + File.separatorChar + "%" + pbpfile.getParentFile().getName()); if (metadir.exists()) { File[] eboot = metadir.listFiles(new FileFilter() { @Override public boolean accept(File arg0) { return arg0.getName().equalsIgnoreCase("eboot.pbp"); } }); if (eboot.length > 0) metapbp = eboot[0]; } // kxploit% metadir = new File(pbpfile.getParentFile().getParentFile().getPath() + File.separatorChar + pbpfile.getParentFile().getName() + "%"); if (metadir.exists()) { File[] eboot = metadir.listFiles(new FileFilter() { @Override public boolean accept(File arg0) { return arg0.getName().equalsIgnoreCase("eboot.pbp"); } }); if (eboot.length > 0) metapbp = eboot[0]; } if (metapbp != null) { // Load PSF embedded in PBP FileChannel roChannel; try { RandomAccessFile raf = new RandomAccessFile(metapbp, "r"); roChannel = raf.getChannel(); ByteBuffer readbuffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int)roChannel.size()); PBP meta = new PBP(readbuffer); module.psf = meta.readPSF(readbuffer); raf.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } else { // Load unpacked PSF in the same directory File[] psffile = pbpfile.getParentFile().listFiles(new FileFilter() { @Override public boolean accept(File arg0) { return arg0.getName().equalsIgnoreCase("param.sfo"); } }); if (psffile != null && psffile.length > 0) { try { RandomAccessFile raf = new RandomAccessFile(psffile[0], "r"); FileChannel roChannel = raf.getChannel(); ByteBuffer readbuffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int)roChannel.size()); module.psf = new PSF(); module.psf.read(readbuffer); raf.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** @return true on success */ private boolean LoadPBP(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { PBP pbp = new PBP(f); if (pbp.isValid()) { module.fileFormat |= FORMAT_PBP; // Dump PSF info if (pbp.getOffsetParam() > 0) { module.psf = pbp.readPSF(f); } // Dump unpacked PBP if (Settings.getInstance().readBool("emu.pbpunpack")) { PBP.unpackPBP(f); } // Save PBP info for debugger ElfHeaderInfo.PbpInfo = pbp.toString(); // Setup position for chaining loaders f.position(pbp.getOffsetPspData()); return true; } // Not a valid PBP return false; } /** @return true on success */ private boolean LoadSPRX(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { int magicPSP = Utilities.readWord(f); int magicEDAT = Utilities.readWord(f); if ((magicPSP == PSP_MAGIC) && (magicEDAT == EDAT_MAGIC)) { log.warn("Encrypted file detected! (.PSPEDAT)"); // Skip the EDAT header and load as a regular ~PSP prx. f.position(0x90); LoadPSP(f.slice(), module, baseAddress, analyzeOnly, allocMem, fromSyscall); return true; } // Not a valid SPRX return false; } /** @return true on success */ private boolean LoadSCE(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { int magic = Utilities.readWord(f); if (magic == SCE_MAGIC) { module.fileFormat |= FORMAT_SCE; log.warn("Encrypted file not supported! (~SCE)"); return true; } // Not a valid PSP return false; } /** @return true on success */ private boolean LoadPSP(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { PSP psp = new PSP(f); if (!psp.isValid()) { // Not a valid PSP return false; } module.fileFormat |= FORMAT_PSP; long start = System.currentTimeMillis(); ByteBuffer decryptedPrx = psp.decrypt(f); long end = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug(String.format("Called crypto engine for PRX (duration=%d ms)", end - start)); } return LoadELF(decryptedPrx, module, baseAddress, analyzeOnly, allocMem, fromSyscall); } /** @return true on success */ private boolean LoadELF(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { int elfOffset = f.position(); Elf32 elf = new Elf32(f); if (elf.getHeader().isValid()) { module.fileFormat |= FORMAT_ELF; if (!elf.getHeader().isMIPSExecutable()) { log.error("Loader NOT a MIPS executable"); return false; } if (elf.isKernelMode()) { module.mpidtext = SysMemUserForUser.KERNEL_PARTITION_ID; module.mpiddata = SysMemUserForUser.KERNEL_PARTITION_ID; if (!analyzeOnly && baseAddress == MemoryMap.START_USERSPACE + 0x4000) { baseAddress = MemoryMap.START_RAM + Utilities.alignUp(ThreadManForUser.INTERNAL_THREAD_ADDRESS_SIZE, SysMemUserForUser.defaultSizeAlignment - 1); } } if (elf.getHeader().isPRXDetected()) { log.debug("Loader: Relocation required (PRX)"); module.fileFormat |= FORMAT_PRX; } else if (elf.getHeader().requiresRelocation()) { // Seen in .elf's generated by pspsdk with BUILD_PRX=1 before conversion to .prx log.info("Loader: Relocation required (ELF)"); } else { // After the user chooses a game to run and we load it, then // we can't load another PBP at the same time. We can only load // relocatable modules (PRX's) after the user loaded app. if (baseAddress > 0x08900000) { log.warn("Loader: Probably trying to load PBP ELF while another PBP ELF is already loaded"); } baseAddress = 0; } module.baseAddress = baseAddress; if (elf.getHeader().getE_entry() == -1) { module.entry_addr = -1; } else { module.entry_addr = baseAddress + elf.getHeader().getE_entry(); } // Note: baseAddress is 0 unless we are loading a PRX // Set loadAddressLow to the highest possible address, it will be updated // by LoadELFProgram(). module.loadAddressLow = (baseAddress != 0) ? baseAddress : MemoryMap.END_USERSPACE; module.loadAddressHigh = baseAddress; // Load into mem LoadELFProgram(f, module, baseAddress, elf, elfOffset, analyzeOnly); LoadELFSections(f, module, baseAddress, elf, elfOffset, analyzeOnly); if (module.loadAddressLow > module.loadAddressHigh) { log.error(String.format("Incorrect ELF module address: loadAddressLow=0x%08X, loadAddressHigh=0x%08X", module.loadAddressLow, module.loadAddressHigh)); module.loadAddressHigh = module.loadAddressLow; } if (!analyzeOnly) { // Relocate PRX if (elf.getHeader().requiresRelocation()) { relocateFromHeaders(f, module, baseAddress, elf, elfOffset); } // The following can only be done after relocation // Load .rodata.sceModuleInfo LoadELFModuleInfo(f, module, baseAddress, elf, elfOffset); if (allocMem) { // After LoadELFModuleInfo so the we can name the memory allocation after the module name LoadELFReserveMemory(module); } // Save imports LoadELFImports(module); // Save exports LoadELFExports(module); // Try to fixup imports for ALL modules Managers.modules.addModule(module); ProcessUnresolvedImports(module, fromSyscall); // Debug LoadELFDebuggerInfo(f, module, baseAddress, elf, elfOffset, fromSyscall); // If no text_addr is available up to now, use the lowest program header address if (module.text_addr == 0) { for (Elf32ProgramHeader phdr : elf.getProgramHeaderList()) { if (module.text_addr == 0 || phdr.getP_vaddr() < module.text_addr) { module.text_addr = phdr.getP_vaddr(); // Align the text_addr if an alignment has been specified if (phdr.getP_align() > 0) { module.text_addr = Utilities.alignDown(module.text_addr, phdr.getP_align() - 1); } } } } // Flush module struct to psp mem module.write(Memory.getInstance(), module.address); } return true; } // Not a valid ELF log.debug("Loader: Not a ELF"); return false; } /** Dummy loader for unrecognized file formats, put at the end of a loader chain. * @return true on success */ private boolean LoadUNK(ByteBuffer f, SceModule module, int baseAddress, boolean analyzeOnly, boolean allocMem, boolean fromSyscall) throws IOException { byte m0 = f.get(); byte m1 = f.get(); byte m2 = f.get(); byte m3 = f.get(); // Catch common user errors if (m0 == 0x43 && m1 == 0x49 && m2 == 0x53 && m3 == 0x4F) { // CSO log.info("This is not an executable file!"); log.info("Try using the Load UMD menu item"); } else if ((m0 == 0 && m1 == 0x50 && m2 == 0x53 && m3 == 0x46)) { // PSF log.info("This is not an executable file!"); } else { boolean handled = false; // check for ISO if (f.limit() >= 16 * 2048 + 6) { f.position(16 * 2048); byte[] id = new byte[6]; f.get(id); if((((char)id[1])=='C')&& (((char)id[2])=='D')&& (((char)id[3])=='0')&& (((char)id[4])=='0')&& (((char)id[5])=='1')) { log.info("This is not an executable file!"); log.info("Try using the Load UMD menu item"); handled = true; } } if (!handled) { log.info("Unrecognized file format"); log.info(String.format("File magic %02X %02X %02X %02X", m0, m1, m2, m3)); if (log.isDebugEnabled()) { byte[] buffer = new byte[256]; buffer[0] = m0; buffer[1] = m1; buffer[2] = m2; buffer[3] = m3; f.get(buffer, 4, buffer.length - 4); log.debug(String.format("File header: %s", Utilities.getMemoryDump(buffer, 0, buffer.length))); } } } return false; } // ELF Loader /** Load some programs into memory */ private void LoadELFProgram(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int elfOffset, boolean analyzeOnly) throws IOException { List<Elf32ProgramHeader> programHeaderList = elf.getProgramHeaderList(); Memory mem = Memory.getInstance(); int i = 0; for (Elf32ProgramHeader phdr : programHeaderList) { if (log.isTraceEnabled()) { log.trace(String.format("ELF Program Header: %s", phdr.toString())); } if (phdr.getP_type() == 0x00000001L) { int fileOffset = (int)phdr.getP_offset(); int memOffset = baseAddress + (int)phdr.getP_vaddr(); if (!Memory.isAddressGood(memOffset)) { memOffset = (int)phdr.getP_vaddr(); if (!Memory.isAddressGood(memOffset)) { log.warn(String.format("Program header has invalid memory offset 0x%08X!", memOffset)); } } int fileLen = (int)phdr.getP_filesz(); int memLen = (int)phdr.getP_memsz(); if (log.isDebugEnabled()) { log.debug(String.format("PH#%d: loading program %08X - file %08X - mem %08X", i, memOffset, memOffset + fileLen, memOffset + memLen)); log.debug(String.format("PH#%d:\n%s", i, phdr)); } f.position(elfOffset + fileOffset); if (f.position() + fileLen > f.limit()) { int newLen = f.limit() - f.position(); log.warn(String.format("PH#%d: program overflow clamping len %08X to %08X", i, fileLen, newLen)); fileLen = newLen; } if (!analyzeOnly) { if (memLen > fileLen) { // Clear the memory part not loaded from the file mem.memset(memOffset + fileLen, (byte) 0, memLen - fileLen); } mem.copyToMemory(memOffset, f, fileLen); } // Update memory area consumed by the module if (memOffset < module.loadAddressLow) { module.loadAddressLow = memOffset; if (log.isDebugEnabled()) { log.debug(String.format("PH#%d: new loadAddressLow %08X", i, module.loadAddressLow)); } } if (memOffset + memLen > module.loadAddressHigh) { module.loadAddressHigh = memOffset + memLen; if (log.isTraceEnabled()) { log.trace(String.format("PH#%d: new loadAddressHigh %08X", i, module.loadAddressHigh)); } } module.segmentaddr[module.nsegment] = memOffset; module.segmentsize[module.nsegment] = memLen; module.nsegment++; } i++; } if (log.isDebugEnabled()) { log.debug(String.format("PH alloc consumption %08X (mem %08X)", (module.loadAddressHigh - module.loadAddressLow), module.bss_size)); } } /** Load some sections into memory */ private void LoadELFSections(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int elfOffset, boolean analyzeOnly) throws IOException { List<Elf32SectionHeader> sectionHeaderList = elf.getSectionHeaderList(); Memory mem = Memory.getInstance(); module.text_addr = baseAddress; module.text_size = 0; module.data_size = 0; module.bss_size = 0; for (Elf32SectionHeader shdr : sectionHeaderList) { if (log.isTraceEnabled()) { log.trace(String.format("ELF Section Header: %s", shdr.toString())); } int memOffset = shdr.getSh_addr(baseAddress); int len = shdr.getSh_size(); int flags = shdr.getSh_flags(); if (flags != SHF_NONE && Memory.isAddressGood(memOffset)) { boolean read = (flags & SHF_ALLOCATE) != 0; boolean write = (flags & SHF_WRITE) != 0; boolean execute = (flags & SHF_EXECUTE) != 0; MemorySection memorySection = new MemorySection(memOffset, len, read, write, execute); MemorySections.getInstance().addMemorySection(memorySection); } if ((flags & SHF_ALLOCATE) != 0) { switch (shdr.getSh_type()) { case Elf32SectionHeader.SHT_PROGBITS: { // 1 // Load this section into memory // now loaded using program header type 1 if (len == 0) { if (log.isDebugEnabled()) { log.debug(String.format("%s: ignoring zero-length type 1 section %08X", shdr.getSh_namez(), memOffset)); } } else if (!Memory.isAddressGood(memOffset)) { log.error(String.format("Section header (type 1) has invalid memory offset 0x%08X!", memOffset)); } else { // Update memory area consumed by the module if (memOffset < module.loadAddressLow) { log.warn(String.format("%s: section allocates more than program %08X - %08X", shdr.getSh_namez(), memOffset, (memOffset + len))); module.loadAddressLow = memOffset; } if (memOffset + len > module.loadAddressHigh) { log.warn(String.format("%s: section allocates more than program %08X - %08X", shdr.getSh_namez(), memOffset, (memOffset + len))); module.loadAddressHigh = memOffset + len; } if ((flags & SHF_WRITE) != 0) { module.data_size += len; if (log.isTraceEnabled()) { log.trace(String.format("Section Header as data, len=0x%08X, data_size=0x%08X", len, module.data_size)); } } else { module.text_size += len; if (log.isTraceEnabled()) { log.trace(String.format("Section Header as text, len=0x%08X, text_size=0x%08X", len, module.text_size)); } } } break; } case Elf32SectionHeader.SHT_NOBITS: { // 8 // Zero out this portion of memory if (len == 0) { if (log.isDebugEnabled()) { log.debug(String.format("%s: ignoring zero-length type 8 section %08X", shdr.getSh_namez(), memOffset)); } } else if (!Memory.isAddressGood(memOffset)) { log.error(String.format("Section header (type 8) has invalid memory offset 0x%08X!", memOffset)); } else { if (log.isDebugEnabled()) { log.debug(String.format("%s: clearing section %08X - %08X (len %08X)", shdr.getSh_namez(), memOffset, (memOffset + len), len)); } if (!analyzeOnly) { mem.memset(memOffset, (byte) 0x0, len); } // Update memory area consumed by the module if (memOffset < module.loadAddressLow) { module.loadAddressLow = memOffset; if (log.isDebugEnabled()) { log.debug(String.format("%s: new loadAddressLow %08X (+%08X)", shdr.getSh_namez(), module.loadAddressLow, len)); } } if (memOffset + len > module.loadAddressHigh) { module.loadAddressHigh = memOffset + len; if (log.isDebugEnabled()) { log.debug(String.format("%s: new loadAddressHigh %08X (+%08X)", shdr.getSh_namez(), module.loadAddressHigh, len)); } } module.bss_size += len; } break; } } } } if (log.isTraceEnabled()) { log.trace(String.format("Storing module info: text addr 0x%08X, text_size 0x%08X, data_size 0x%08X, bss_size 0x%08X", module.text_addr, module.text_size, module.data_size, module.bss_size)); } } private void LoadELFReserveMemory(SceModule module) { // Mark the area of memory the module loaded into as used if (log.isDebugEnabled()) { log.debug(String.format("Reserving 0x%X bytes at 0x%08X for module '%s'", module.loadAddressHigh - module.loadAddressLow, module.loadAddressLow, module.pspfilename)); } int address = module.loadAddressLow & ~(SysMemUserForUser.defaultSizeAlignment - 1); // Round down to match sysmem allocations int size = module.loadAddressHigh - address; int partition = module.mpidtext > 0 ? module.mpidtext : SysMemUserForUser.USER_PARTITION_ID; SysMemInfo info = Modules.SysMemUserForUserModule.malloc(partition, module.modname, SysMemUserForUser.PSP_SMEM_Addr, size, address); if (info == null || info.addr != address) { log.warn(String.format("Failed to properly reserve memory consumed by module %s at address 0x%08X, size 0x%X: allocated %s", module.modname, address, size, info)); } module.addAllocatedMemory(info); } /** Loads from memory */ private void LoadELFModuleInfo(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int elfOffset) throws IOException { Elf32ProgramHeader phdr = elf.getProgramHeader(0); Elf32SectionHeader shdr = elf.getSectionHeader(".rodata.sceModuleInfo"); int moduleInfoAddr; if (!elf.getHeader().isPRXDetected() && shdr == null) { log.warn("ELF is not PRX, but has no section headers!"); moduleInfoAddr = (int)(phdr.getP_vaddr() + (phdr.getP_paddr() & 0x7FFFFFFFL) - phdr.getP_offset()); log.warn("Manually locating ModuleInfo at address: 0x" + Integer.toHexString(moduleInfoAddr)); PSPModuleInfo moduleInfo = new PSPModuleInfo(); moduleInfo.read(Memory.getInstance(), moduleInfoAddr); module.copy(moduleInfo); } else if (elf.getHeader().isPRXDetected()) { moduleInfoAddr = (int)(baseAddress + (phdr.getP_paddr() & 0x7FFFFFFFL) - phdr.getP_offset()); PSPModuleInfo moduleInfo = new PSPModuleInfo(); moduleInfo.read(Memory.getInstance(), moduleInfoAddr); module.copy(moduleInfo); } else if (shdr != null) { moduleInfoAddr = shdr.getSh_addr(baseAddress);; PSPModuleInfo moduleInfo = new PSPModuleInfo(); moduleInfo.read(Memory.getInstance(), moduleInfoAddr); module.copy(moduleInfo); } else { log.error("ModuleInfo not found!"); return; } if (log.isInfoEnabled()) { log.info(String.format("Found ModuleInfo at 0x%08X, name:'%s', version: %02X%02X, attr: 0x%08X, gp: 0x%08X", moduleInfoAddr, module.modname, module.version[1], module.version[0], module.attribute, module.gp_value)); } if ((module.attribute & SceModule.PSP_MODULE_KERNEL) != 0) { log.warn("Kernel mode module detected"); } if ((module.attribute & SceModule.PSP_MODULE_VSH) != 0) { log.warn("VSH mode module detected"); } } /** * @param f The position of this buffer must be at the start of a * list of Elf32Rel structs. * @param RelCount The number of Elf32Rel structs to read and process. * @param pspRelocationFormat true if the relocation are in the PSP specific format, * false if the relocation is in standard ELF format. */ private void relocateFromBuffer(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int RelCount, boolean pspRelocationFormat) throws IOException { Elf32Relocate rel = new Elf32Relocate(); int AHL = 0; // (AHI << 16) | (ALO & 0xFFFF) List<Integer> deferredHi16 = new LinkedList<Integer>(); // We'll use this to relocate R_MIPS_HI16 when we get a R_MIPS_LO16 Memory mem = Memory.getInstance(); for (int i = 0; i < RelCount; i++) { rel.read(f); int phOffset; int phBaseOffset; int R_OFFSET = rel.getR_offset(); int R_TYPE = rel.getR_info() & 0xFF; if (pspRelocationFormat) { int OFS_BASE = (rel.getR_info() >> 8) & 0xFF; int ADDR_BASE = (rel.getR_info() >> 16) & 0xFF; if (log.isTraceEnabled()) { log.trace(String.format("Relocation #%d type=%d, Offset PH#%d, Base Offset PH#%d, Offset 0x%08X", i, R_TYPE, OFS_BASE, ADDR_BASE, R_OFFSET)); } phOffset = elf.getProgramHeader(OFS_BASE).getP_vaddr(); phBaseOffset = elf.getProgramHeader(ADDR_BASE).getP_vaddr(); } else { phOffset = 0; phBaseOffset = 0; if (log.isTraceEnabled()) { log.trace(String.format("Relocation #%d type=%d, Symbol 0x%06X, Offset 0x%08X", i, R_TYPE, rel.getR_info() >> 8, R_OFFSET)); } } // Address of data to relocate int data_addr = baseAddress + R_OFFSET + phOffset; // Value of data to relocate int data = readUnaligned32(mem, data_addr); long result = 0; // Used to hold the result of relocation, OR this back into data int word32 = data & 0xFFFFFFFF; // <=> data; int targ26 = data & 0x03FFFFFF; int hi16 = data & 0x0000FFFF; int lo16 = data & 0x0000FFFF; int A = 0; // addend int S = baseAddress + phBaseOffset; switch (R_TYPE) { case 0: //R_MIPS_NONE // Tested on PSP: R_MIPS_NONE is ignored if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_NONE addr=0x%08X", data_addr)); } break; case 1: // R_MIPS_16 data = (data & 0xFFFF0000) | ((data + S) & 0x0000FFFF); if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_16 addr=0x%08X before=0x%08X after=0x%08X", data_addr, word32, data)); } break; case 2: //R_MIPS_32 data += S; if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_32 addr=0x%08X before=0x%08X after=0x%08X", data_addr, word32, data)); } break; case 4: //R_MIPS_26 A = targ26; result = ((A << 2) + S) >> 2; data &= ~0x03FFFFFF; data |= (int) (result & 0x03FFFFFF); // truncate if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_26 addr=0x%08X before=0x%08X after=0x%08X", data_addr, word32, data)); } break; case 5: //R_MIPS_HI16 A = hi16; AHL = A << 16; deferredHi16.add(data_addr); if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_HI16 addr=0x%08X", data_addr)); } break; case 6: //R_MIPS_LO16 A = lo16; AHL &= ~0x0000FFFF; // delete lower bits, since many R_MIPS_LO16 can follow one R_MIPS_HI16 AHL |= A & 0x0000FFFF; result = AHL + S; data &= ~0x0000FFFF; data |= result & 0x0000FFFF; // truncate // Process deferred R_MIPS_HI16 for (Iterator<Integer> it = deferredHi16.iterator(); it.hasNext();) { int data_addr2 = it.next(); int data2 = readUnaligned32(mem, data_addr2); result = ((data2 & 0x0000FFFF) << 16) + A + S; // The low order 16 bits are always treated as a signed // value. Therefore, a negative value in the low order bits // requires an adjustment in the high order bits. We need // to make this adjustment in two ways: once for the bits we // took from the data, and once for the bits we are putting // back in to the data. if ((A & 0x8000) != 0) { result -= 0x10000; } if ((result & 0x8000) != 0) { result += 0x10000; } data2 &= ~0x0000FFFF; data2 |= (result >> 16) & 0x0000FFFF; // truncate if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_HILO16 addr=0x%08X before=0x%08X after=0x%08X", data_addr2, readUnaligned32(mem, data_addr2), data2)); } writeUnaligned32(mem, data_addr2, data2); it.remove(); } if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_LO16 addr=0x%08X before=0x%08X after=0x%08X", data_addr, word32, data)); } break; case 7: // R_MIPS_GPREL16 // This relocation type is ignored by the PSP if (log.isTraceEnabled()) { log.trace(String.format("R_MIPS_GPREL16 addr=0x%08X before=0x%08X after=0x%08X", data_addr, word32, data)); } break; default: log.warn(String.format("Unhandled relocation type %d at 0x%08X", R_TYPE, data_addr)); break; } writeUnaligned32(mem, data_addr, data); } } private static String getRTypeName(int R_TYPE) { String[] names = { "R_MIPS_NONE", "R_MIPS_16", "R_MIPS_32", "R_MIPS_26", "R_MIPS_HI16", "R_MIPS_LO16", "R_MIPS_J26", "R_MIPS_JAL26" }; if (R_TYPE < 0 || R_TYPE >= names.length) { return String.format("%d", R_TYPE); } return names[R_TYPE]; } private void relocateFromBufferA1(ByteBuffer f, Elf32 elf, int baseAddress, int programHeaderNumber, int size) throws IOException { Memory mem = Memory.getInstance(); // Relocation variables. int R_OFFSET = 0; int R_BASE = 0; int OFS_BASE = 0; // Data variables. int data_addr; int data; int lo16 = 0; int hi16; int phBaseOffset; int r = 0; // Buffer position variable. int pos = f.position(); int end = pos + size; // Locate and read the flag, type and segment bits. f.position(pos + 2); int fbits = f.get(); int flagShift = 0; int flagMask = (1 << fbits) - 1; int sbits = programHeaderNumber < 3 ? 1 : 2; int segmentShift = fbits; int segmentMask = (1 << sbits) - 1; int tbits = f.get(); int typeShift = fbits + sbits; int typeMask = (1 << tbits) - 1; // Locate the flag table. int flags[] = new int[f.get() & 0xFF]; flags[0] = flags.length; for (int j = 1; j < flags.length; j++) { flags[j] = f.get() & 0xFF; } // Locate the type table. int types[] = new int[f.get() & 0xFF]; types[0] = types.length; for (int j = 1; j < types.length; j++) { types[j] = f.get() & 0xFF; } // Save the current position. pos = f.position(); int R_TYPE_OLD = types.length; while (pos < end) { // Read the CMD byte. int R_CMD = (f.get() & 0xFF) | ((f.get() & 0xFF) << 8); pos += 2; // Process the relocation flag. int flagIndex = (R_CMD >> flagShift) & flagMask; int R_FLAG = flags[flagIndex]; // Set the segment offset. int S = (R_CMD >> segmentShift) & segmentMask; // Process the relocation type. int typeIndex = (R_CMD >> typeShift) & typeMask; int R_TYPE = types[typeIndex]; // Operate on segment offset based on the relocation flag. if ((R_FLAG & 0x01) == 0) { if (log.isTraceEnabled()) { log.trace(String.format("Relocation 0x%04X, R_FLAG=0x%02X(%d), S=%d, rest=0x%X", R_CMD, R_FLAG, flagIndex, S, R_CMD >> (fbits + sbits))); } OFS_BASE = S; if ((R_FLAG & 0x06) == 0) { R_BASE = (R_CMD >> (fbits + sbits)); } else if ((R_FLAG & 0x06) == 4) { R_BASE = (f.get() & 0xFF); R_BASE |= ((f.get() & 0xFF) << 8); R_BASE |= ((f.get() & 0xFF) << 16); R_BASE |= ((f.get() & 0xFF) << 24); pos += 4; } else { log.warn("PH Relocation type 0x700000A1: Invalid size flag!"); R_BASE = 0; } } else { // Operate on segment address based on the relocation flag. if (log.isTraceEnabled()) { log.trace(String.format("Relocation 0x%04X, R_FLAG=0x%02X(%d), S=%d, %s(%d), rest=0x%X", R_CMD, R_FLAG, flagIndex, S, getRTypeName(R_TYPE), typeIndex, R_CMD >> (fbits + tbits + sbits))); } int ADDR_BASE = S; phBaseOffset = baseAddress + elf.getProgramHeader(ADDR_BASE).getP_vaddr(); if ((R_FLAG & 0x06) == 0x00) { R_OFFSET = (int) (short) R_CMD; // sign-extend 16 to 32 bits R_OFFSET >>= (fbits + tbits + sbits); R_BASE += R_OFFSET; } else if ((R_FLAG & 0x06) == 0x02) { R_OFFSET = (R_CMD << 16) >> (fbits + tbits + sbits); R_OFFSET &= 0xFFFF0000; R_OFFSET |= (f.get() & 0xFF); R_OFFSET |= ((f.get() & 0xFF) << 8); pos += 2; R_BASE += R_OFFSET; } else if ((R_FLAG & 0x06) == 0x04) { R_BASE = (f.get() & 0xFF); R_BASE |= ((f.get() & 0xFF) << 8); R_BASE |= ((f.get() & 0xFF) << 16); R_BASE |= ((f.get() & 0xFF) << 24); pos += 4; } else { log.warn("PH Relocation type 0x700000A1: Invalid relocation size flag!"); } // Process lo16. if ((R_FLAG & 0x38) == 0x00) { lo16 = 0; } else if ((R_FLAG & 0x38) == 0x08) { if ((R_TYPE_OLD ^ 0x04) != 0x00) { lo16 = 0; } } else if ((R_FLAG & 0x38) == 0x10) { lo16 = (f.get() & 0xFF); lo16 |= ((f.get() & 0xFF) << 8); lo16 = (int) (short) lo16; // sign-extend 16 to 32 bits pos += 2; } else if ((R_FLAG & 0x38) == 0x18) { log.warn("PH Relocation type 0x700000A1: Invalid lo16 setup!"); } else { log.warn("PH Relocation type 0x700000A1: Invalid lo16 setup!"); } // Read the data. data_addr = R_BASE + baseAddress + elf.getProgramHeader(OFS_BASE).getP_vaddr(); data = readUnaligned32(mem, data_addr); if (false) { // TODO for example sysmem.prx, the R_TYPE need to be modified to really do something meaningful. To be further investigated. // 0, 1, 2, 3, 4, 5, 6, 7 int map[] = new int[] { 0, 0, 3, 3, 0, 0, 4, 5 }; R_TYPE = map[R_TYPE]; } if (log.isDebugEnabled()) { log.debug(String.format("Relocation #%d type=%d, Offset PH#%d, Base Offset PH#%d, Offset 0x%08X", r, R_TYPE, OFS_BASE, ADDR_BASE, R_OFFSET)); } int previousData = data; // Apply the changes as requested by the relocation type. switch (R_TYPE) { case 0: // R_MIPS_NONE break; case 2: // R_MIPS_32 data += phBaseOffset; break; case 3: // R_MIPS_26 data = (data & 0xFC000000) | (((data & 0x03FFFFFF) + (phBaseOffset >> 2)) & 0x03FFFFFFF); break; case 6: // R_MIPS_J26 data = (Opcodes.J << 26) | (((data & 0x03FFFFFF) + (phBaseOffset >> 2)) & 0x03FFFFFFF); break; case 7: // R_MIPS_JAL26 data = (Opcodes.JAL << 26) | (((data & 0x03FFFFFF) + (phBaseOffset >> 2)) & 0x03FFFFFFF); break; case 4: // R_MIPS_HI16 hi16 = ((data << 16) + lo16) + phBaseOffset; if ((hi16 & 0x8000) == 0x8000) { hi16 += 0x00010000; } data = (data & 0xffff0000) | (hi16 >> 16); break; case 1: // R_MIPS_16 case 5: // R_MIPS_LO16 data = (data & 0xffff0000) | ((((int) (short) data) + phBaseOffset) & 0xffff); break; default: break; } if (previousData != data) { // Write the data. writeUnaligned32(mem, data_addr, data); if (log.isTraceEnabled()) { log.trace(String.format("Relocation at 0x%08X: 0x%08X -> 0x%08X", data_addr, previousData, data)); } } r++; } R_TYPE_OLD = R_TYPE; } } private boolean mustRelocate(Elf32 elf, Elf32SectionHeader shdr) { if (shdr.getSh_type() == Elf32SectionHeader.SHT_PRXREL) { // PSP PRX relocation section return true; } if (shdr.getSh_type() == Elf32SectionHeader.SHT_REL) { // Standard ELF relocation section Elf32SectionHeader relatedShdr = elf.getSectionHeader(shdr.getSh_info()); // No relocation required for a debug section (sh_flags==SHF_NONE) if (relatedShdr != null && relatedShdr.getSh_flags() != Elf32SectionHeader.SHF_NONE) { return true; } } return false; } /** Uses info from the elf program headers and elf section headers to * relocate a PRX. */ private void relocateFromHeaders(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int elfOffset) throws IOException { // Relocate from program headers int i = 0; for (Elf32ProgramHeader phdr : elf.getProgramHeaderList()) { if (phdr.getP_type() == 0x700000A0L) { int RelCount = phdr.getP_filesz() / Elf32Relocate.sizeof(); if (log.isDebugEnabled()) { log.debug(String.format("PH#%d: relocating %d entries", i, RelCount)); } f.position(elfOffset + phdr.getP_offset()); relocateFromBuffer(f, module, baseAddress, elf, RelCount, true); return; } else if (phdr.getP_type() == 0x700000A1L) { if (log.isDebugEnabled()) { log.debug(String.format("Type 0x700000A1 PH#%d: relocating A1 entries, size=0x%X", i, phdr.getP_filesz())); } f.position(elfOffset + phdr.getP_offset()); relocateFromBufferA1(f, elf, baseAddress, i, phdr.getP_filesz()); return; } i++; } // Relocate from section headers for (Elf32SectionHeader shdr : elf.getSectionHeaderList()) { if (mustRelocate(elf, shdr)) { int RelCount = shdr.getSh_size() / Elf32Relocate.sizeof(); if (log.isDebugEnabled()) { log.debug(shdr.getSh_namez() + ": relocating " + RelCount + " entries"); } f.position(elfOffset + shdr.getSh_offset()); relocateFromBuffer(f, module, baseAddress, elf, RelCount, shdr.getSh_type() != Elf32SectionHeader.SHT_REL); } } } private void ProcessUnresolvedImports(SceModule sourceModule, boolean fromSyscall) { Memory mem = Memory.getInstance(); NIDMapper nidMapper = NIDMapper.getInstance(); int numberoffailedNIDS = 0; int numberofmappedNIDS = 0; for (SceModule module : Managers.modules.values()) { module.importFixupAttempts++; for (Iterator<DeferredStub> it = module.unresolvedImports.iterator(); it.hasNext(); ) { DeferredStub deferredStub = it.next(); String moduleName = deferredStub.getModuleName(); int nid = deferredStub.getNid(); int importAddress = deferredStub.getImportAddress(); // Attempt to fixup stub to point to an already loaded module export int exportAddress = nidMapper.getAddressByNid(nid, moduleName); if (exportAddress != 0) { deferredStub.resolve(mem, exportAddress); it.remove(); sourceModule.resolvedImports.add(deferredStub); numberofmappedNIDS++; if (log.isDebugEnabled()) { log.debug(String.format("Mapped import at 0x%08X to export at 0x%08X [0x%08X] (attempt %d)", importAddress, exportAddress, nid, module.importFixupAttempts)); } } else if (nid == 0) { // Ignore patched nids log.warn(String.format("Ignoring import at 0x%08X [0x%08X] (attempt %d)", importAddress, nid, module.importFixupAttempts)); it.remove(); // This is an import to be ignored, implement it with the following // code sequence: // jr $ra (already written to memory) // li $v0, 0 // Rem.: "BUST A MOVE GHOST" is testing the return value $v0, // so it has to be set explicitly to 0. mem.write32(importAddress + 4, AllegrexOpcodes.ADDU | (2 << 11) | (0 << 16) | (0 << 21)); // addu $v0, $zr, $zr <=> li $v0, 0 } else { // Attempt to fixup stub to known syscalls int code = nidMapper.getSyscallByNid(nid, moduleName); if (code >= 0) { // Fixup stub, replacing nop with syscall int returnInstruction = // jr $ra (AllegrexOpcodes.SPECIAL << 26) | AllegrexOpcodes.JR | ((Common._ra) << 21); int syscallInstruction = // syscall <code> (AllegrexOpcodes.SPECIAL << 26) | AllegrexOpcodes.SYSCALL | ((code & 0x000fffff) << 6); // Some homebrews do not have a "jr $ra" set before the syscall if (mem.read32(importAddress) == 0) { mem.write32(importAddress, returnInstruction); } mem.write32(importAddress + 4, syscallInstruction); it.remove(); numberofmappedNIDS++; if (fromSyscall && log.isDebugEnabled()) { log.debug(String.format("Mapped import at 0x%08X to syscall 0x%05X [0x%08X] (attempt %d)", importAddress, code, nid, module.importFixupAttempts)); } } else { log.warn(String.format("Failed to map import at 0x%08X [0x%08X] Module '%s'(attempt %d)", importAddress, nid, moduleName, module.importFixupAttempts)); numberoffailedNIDS++; } } } } log.info(numberofmappedNIDS + " NIDS mapped"); if (numberoffailedNIDS > 0) { log.info(numberoffailedNIDS + " remaining unmapped NIDS"); } } /* Loads from memory */ private void LoadELFImports(SceModule module) throws IOException { Memory mem = Memory.getInstance(); int stubHeadersAddress = module.stub_top; int stubHeadersEndAddress = module.stub_top + module.stub_size; // n modules to import, 1 stub header per module to import. String moduleName; for (int i = 0; stubHeadersAddress < stubHeadersEndAddress; i++) { Elf32StubHeader stubHeader = new Elf32StubHeader(mem, stubHeadersAddress); // Skip 0 sized entries. if (stubHeader.getSize() <= 0) { log.warn("Skipping dummy entry with size " + stubHeader.getSize()); stubHeadersAddress += Elf32StubHeader.sizeof() / 2; } else { if (Memory.isAddressGood((int)stubHeader.getOffsetModuleName())) { moduleName = Utilities.readStringNZ((int) stubHeader.getOffsetModuleName(), 64); } else { // Generate a module name. moduleName = module.modname; } stubHeader.setModuleNamez(moduleName); if (log.isDebugEnabled()) { log.debug(String.format("Processing Import #%d: %s", i, stubHeader.toString())); } if (stubHeader.hasVStub()) { if (log.isDebugEnabled()) { log.debug(String.format("'%s': Header with VStub has size %d: %s", stubHeader.getModuleNamez(), stubHeader.getSize(), Utilities.getMemoryDump(stubHeadersAddress, stubHeader.getSize() * 4, 4, 16))); } int vStub = (int) stubHeader.getVStub(); if (vStub != 0) { int vStubSize = stubHeader.getVStubSize(); if (log.isDebugEnabled()) { log.debug(String.format("VStub has size %d: %s", vStubSize, Utilities.getMemoryDump(vStub, vStubSize * 8, 4, 16))); } IMemoryReader vstubReader = MemoryReader.getMemoryReader(vStub, vStubSize * 8, 4); for (int j = 0; j < vStubSize; j++) { int relocAddr = vstubReader.readNext(); int nid = vstubReader.readNext(); // relocAddr points to a list of relocation terminated by a 0 IMemoryReader relocReader = MemoryReader.getMemoryReader(relocAddr, 4); while (true) { int reloc = relocReader.readNext(); if (reloc == 0) { // End of relocation list break; } int opcode = reloc >>> 26; int address = (reloc & 0x03FFFFFF) << 2; DeferredStub deferredStub = null; switch (opcode) { case AllegrexOpcodes.BNE: deferredStub = new DeferredVStubHI16(module, stubHeader.getModuleNamez(), address, nid); break; case AllegrexOpcodes.BLEZ: deferredStub = new DeferredVstubLO16(module, stubHeader.getModuleNamez(), address, nid); break; case AllegrexOpcodes.J: deferredStub = new DeferredVStub32(module, stubHeader.getModuleNamez(), address, nid); break; default: log.warn(String.format("Unknown Vstub relocation nid 0x%08X, reloc=0x%08X", nid, reloc)); break; } if (deferredStub != null) { if (log.isDebugEnabled()) { log.debug(String.format("Vstub reloc %s", deferredStub)); } module.unresolvedImports.add(deferredStub); } } } } } stubHeadersAddress += stubHeader.getSize() * 4; if (!Memory.isAddressGood((int) stubHeader.getOffsetNid()) || !Memory.isAddressGood((int) stubHeader.getOffsetText())) { log.warn(String.format("Incorrect s_nid or s_text address in StubHeader #%d: %s", i, stubHeader.toString())); } else { // n stubs per module to import IMemoryReader nidReader = MemoryReader.getMemoryReader((int) stubHeader.getOffsetNid(), stubHeader.getImports() * 4, 4); for (int j = 0; j < stubHeader.getImports(); j++) { int nid = nidReader.readNext(); int importAddress = (int) (stubHeader.getOffsetText() + j * 8); DeferredStub deferredStub = new DeferredStub(module, stubHeader.getModuleNamez(), importAddress, nid); // Add a 0xfffff syscall so we can detect if an unresolved import is called deferredStub.unresolve(mem); } } } } if (module.unresolvedImports.size() > 0) { if (log.isInfoEnabled()) { log.info(String.format("Found %d unresolved imports", module.unresolvedImports.size())); } } } /* Loads from memory */ private void LoadELFExports(SceModule module) throws IOException { NIDMapper nidMapper = NIDMapper.getInstance(); Memory mem = Memory.getInstance(); int entHeadersAddress = module.ent_top; int entHeadersEndAddress = module.ent_top + module.ent_size; int entCount = 0; // n modules to export, 1 ent header per module to export. String moduleName; for (int i = 0; entHeadersAddress < entHeadersEndAddress; i++) { Elf32EntHeader entHeader = new Elf32EntHeader(mem, entHeadersAddress); if ((entHeader.getSize() <= 0)) { // Skip 0 sized entries. log.warn("Skipping dummy entry with size " + entHeader.getSize()); entHeadersAddress += Elf32EntHeader.sizeof() / 2; } else { if (Memory.isAddressGood((int)entHeader.getOffsetModuleName())) { moduleName = Utilities.readStringNZ((int) entHeader.getOffsetModuleName(), 64); } else { // Generate a module name. moduleName = module.modname; } entHeader.setModuleNamez(moduleName); if (log.isDebugEnabled()) { log.debug(String.format("Processing header #%d at 0x%08X: %s", i, entHeadersAddress, entHeader.toString())); } if (entHeader.getSize() > 4) { if (log.isDebugEnabled()) { log.debug(String.format("'%s': Header has size %d: %s", entHeader.getModuleNamez(), entHeader.getSize(), Utilities.getMemoryDump(entHeadersAddress, entHeader.getSize() * 4, 4, 16))); } entHeadersAddress += entHeader.getSize() * 4; } else { entHeadersAddress += Elf32EntHeader.sizeof(); } // The export section is organized as as sequence of: // - 32-bit NID * functionCount // - 32-bit NID * variableCount // - 32-bit export address * functionCount // - 32-bit variable address * variableCount // (each variable address references another structure, depending on its NID) // int functionCount = entHeader.getFunctionCount(); int variableCount = entHeader.getVariableCount(); int nidAddr = (int) entHeader.getOffsetResident(); IMemoryReader nidReader = MemoryReader.getMemoryReader(nidAddr, 4); int exportAddr = nidAddr + (functionCount + variableCount) * 4; IMemoryReader exportReader = MemoryReader.getMemoryReader(exportAddr, 4); if ((entHeader.getAttr() & 0x8000) == 0) { for (int j = 0; j < functionCount; j++) { int nid = nidReader.readNext(); int exportAddress = exportReader.readNext(); // Only accept exports with valid export addresses and // from custom modules (attr != 0x4000) unless // the module is a homebrew (loaded from MemoryStick) or // this is the EBOOT module. if (Memory.isAddressGood(exportAddress) && ((entHeader.getAttr() & 0x4000) != 0x4000) || module.pspfilename.startsWith("ms0:") || module.pspfilename.startsWith("disc0:/PSP_GAME/SYSDIR/EBOOT.") || module.pspfilename.startsWith("flash0:")) { nidMapper.addModuleNid(module, moduleName, nid, exportAddress); entCount++; if (log.isDebugEnabled()) { log.debug(String.format("Export found at 0x%08X [0x%08X]", exportAddress, nid)); } } } } else { for (int j = 0; j < functionCount; j++) { int nid = nidReader.readNext(); int exportAddress = exportReader.readNext(); switch (nid) { case 0xD632ACDB: // module_start module.module_start_func = exportAddress; if (log.isDebugEnabled()) { log.debug(String.format("module_start found: nid=0x%08X, function=0x%08X", nid, exportAddress)); } break; case 0xCEE8593C: // module_stop module.module_stop_func = exportAddress; if (log.isDebugEnabled()) { log.debug(String.format("module_stop found: nid=0x%08X, function=0x%08X", nid, exportAddress)); } break; case 0x2F064FA6: // module_reboot_before module.module_reboot_before_func = exportAddress; if (log.isDebugEnabled()) { log.debug(String.format("module_reboot_before found: nid=0x%08X, function=0x%08X", nid, exportAddress)); } break; case 0xADF12745: // module_reboot_phase module.module_reboot_phase_func = exportAddress; if (log.isDebugEnabled()) { log.debug(String.format("module_reboot_phase found: nid=0x%08X, function=0x%08X", nid, exportAddress)); } break; case 0xD3744BE0: // module_bootstart module.module_bootstart_func = exportAddress; if (log.isDebugEnabled()) { log.debug(String.format("module_bootstart found: nid=0x%08X, function=0x%08X", nid, exportAddress)); } break; default: // Only accept exports from custom modules (attr != 0x4000) and with valid export addresses. if (Memory.isAddressGood(exportAddress) && ((entHeader.getAttr() & 0x4000) != 0x4000)) { nidMapper.addModuleNid(module, moduleName, nid, exportAddress); entCount++; if (log.isDebugEnabled()) { log.debug(String.format("Export found at 0x%08X [0x%08X]", exportAddress, nid)); } } break; } } } int variableTableAddr = exportAddr + functionCount * 4; IMemoryReader variableReader = MemoryReader.getMemoryReader(variableTableAddr, 4); for (int j = 0; j < variableCount; j++) { int nid = nidReader.readNext(); int variableAddr = variableReader.readNext(); switch (nid) { case 0xF01D73A7: // module_info // Seems to be ignored by the PSP if (log.isDebugEnabled()) { log.debug(String.format("module_info found: nid=0x%08X, addr=0x%08X", nid, variableAddr)); } break; case 0x0F7C276C: // module_start_thread_parameter module.module_start_thread_priority = mem.read32(variableAddr + 4); module.module_start_thread_stacksize = mem.read32(variableAddr + 8); module.module_start_thread_attr = mem.read32(variableAddr + 12); if (log.isDebugEnabled()) { log.debug(String.format("module_start_thread_parameter found: nid=0x%08X, priority=%d, stacksize=%d, attr=0x%08X", nid, module.module_start_thread_priority, module.module_start_thread_stacksize, module.module_start_thread_attr)); } break; case 0xCF0CC697: // module_stop_thread_parameter module.module_stop_thread_priority = mem.read32(variableAddr + 4); module.module_stop_thread_stacksize = mem.read32(variableAddr + 8); module.module_stop_thread_attr = mem.read32(variableAddr + 12); if (log.isDebugEnabled()) { log.debug(String.format("module_stop_thread_parameter found: nid=0x%08X, priority=%d, stacksize=%d, attr=0x%08X", nid, module.module_stop_thread_priority, module.module_stop_thread_stacksize, module.module_stop_thread_attr)); } break; case 0xF4F4299D: // module_reboot_before_thread_parameter module.module_reboot_before_thread_priority = mem.read32(variableAddr + 4); module.module_reboot_before_thread_stacksize = mem.read32(variableAddr + 8); module.module_reboot_before_thread_attr = mem.read32(variableAddr + 12); if (log.isDebugEnabled()) { log.debug(String.format("module_reboot_before_thread_parameter found: nid=0x%08X, priority=%d, stacksize=%d, attr=0x%08X", nid, module.module_reboot_before_thread_priority, module.module_reboot_before_thread_stacksize, module.module_reboot_before_thread_attr)); } break; case 0x11B97506: // module_sdk_version // Currently ignored int sdk_version = mem.read32(variableAddr); if (log.isDebugEnabled()) { log.warn(String.format("module_sdk_version found: nid=0x%08X, sdk_version=0x%08X", nid, sdk_version)); } break; default: // Only accept exports from custom modules (attr != 0x4000) and with valid export addresses. if (Memory.isAddressGood(variableAddr) && ((entHeader.getAttr() & 0x4000) != 0x4000)) { nidMapper.addModuleNid(module, moduleName, nid, variableAddr); entCount++; if (log.isDebugEnabled()) { log.debug(String.format("Export found at 0x%08X [0x%08X]", variableAddr, nid)); } } else { log.warn(String.format("Unknown variable entry found: nid=0x%08X, addr=0x%08X", nid, variableAddr)); } break; } } } } if (entCount > 0) { if (log.isInfoEnabled()) { log.info(String.format("Found %d exports", entCount)); } } } private void LoadELFDebuggerInfo(ByteBuffer f, SceModule module, int baseAddress, Elf32 elf, int elfOffset, boolean fromSyscall) throws IOException { // Save executable section address/size for the debugger/instruction counter Elf32SectionHeader shdr; shdr = elf.getSectionHeader(".init"); if (shdr != null) { module.initsection[0] = shdr.getSh_addr(baseAddress); module.initsection[1] = shdr.getSh_size(); } shdr = elf.getSectionHeader(".fini"); if (shdr != null) { module.finisection[0] = shdr.getSh_addr(baseAddress); module.finisection[1] = shdr.getSh_size(); } shdr = elf.getSectionHeader(".sceStub.text"); if (shdr != null) { module.stubtextsection[0] = shdr.getSh_addr(baseAddress); module.stubtextsection[1] = shdr.getSh_size(); } if (!fromSyscall) { ElfHeaderInfo.ElfInfo = elf.getElfInfo(); ElfHeaderInfo.ProgInfo = elf.getProgInfo(); ElfHeaderInfo.SectInfo = elf.getSectInfo(); } } /** * Apply patches to some VSH and Kernel modules * * @param module */ private void patchModule(SceModule module) { Memory mem = Emulator.getMemory(); // Same patches as ProCFW if ("vsh_module".equals(module.modname)) { patch(mem, module, 0x000122B0, 0x506000E0, NOP()); patch(mem, module, 0x00012058, 0x1440003B, NOP()); patch(mem, module, 0x00012060, 0x14400039, NOP()); } // Patches to replace "https" with "http" so that the URL calls // can be proxied through the internal HTTP server. if ("sceNpCommerce2".equals(module.modname)) { patch(mem, module, 0x0000A598, 0x00000073, 0x00000000); // replace "https" with "http" patch(mem, module, 0x00003A60, 0x240701BB, 0x24070050); // replace port 443 with 80 } if ("sceNpCore".equals(module.modname)) { patchRemoveStringChar(mem, module, 0x00000D50, 's'); // replace "https" with "http" in "https://auth.%s.ac.playstation.net/nav/auth" } if ("sceNpService".equals(module.modname)) { patch(mem, module, 0x0001075C, 0x00000073, 0x00000000); // replace "https" with "http" for "https://getprof.%s.np.community.playstation.net/basic_view/sec/get_self_profile" } if ("sceVshNpInstaller_Module".equals(module.modname)) { patchRemoveStringChar(mem, module, 0x00016F90, 's'); // replace "https" with "http" in "https://commerce.%s.ac.playstation.net/cap.m" patchRemoveStringChar(mem, module, 0x00016FC0, 's'); // replace "https" with "http" in "https://commerce.%s.ac.playstation.net/cdp.m" patchRemoveStringChar(mem, module, 0x00016FF0, 's'); // replace "https" with "http" in "https://commerce.%s.ac.playstation.net/kdp.m" patchRemoveStringChar(mem, module, 0x00017020, 's'); // replace "https" with "http" in "https://account.%s.ac.playstation.net/ecomm/ingame/startDownloadDRM" patchRemoveStringChar(mem, module, 0x00017064, 's'); // replace "https" with "http" in "https://account.%s.ac.playstation.net/ecomm/ingame/finishDownloadDRM" } if ("marlindownloader".equals(module.modname)) { patchRemoveStringChar(mem, module, 0x000046C8, 's'); // replace "https" with "http" in "https://mds.%s.ac.playstation.net/" } if ("sceVshStoreBrowser_Module".equals(module.modname)) { patchRemoveStringChar(mem, module, 0x0005A244, 's'); // replace "https" with "http" in "https://nsx-e.sec.%s.dl.playstation.net/nsx/sec/..." patchRemoveStringChar(mem, module, 0x0005A2D8, 's'); // replace "https" with "http" in "https://nsx.sec.%s.dl.playstation.net/nsx/sec/..." } if ("sceGameUpdate_Library".equals(module.modname)) { patchRemoveStringChar(mem, module, 0x000030C4, 's'); // replace "https" with "http" in "https://a0.ww.%s.dl.playstation.net/tpl/..." } } private void patch(Memory mem, SceModule module, int offset, int oldValue, int newValue) { int checkValue = mem.read32(module.baseAddress + offset); if (checkValue != oldValue) { log.error(String.format("Patching of module '%s' failed at offset 0x%X, 0x%08X found instead of 0x%08X", module.modname, offset, checkValue, oldValue)); } else { mem.write32(module.baseAddress + offset, newValue); } } private void patchRemoveStringChar(Memory mem, SceModule module, int offset, int oldChar) { int address = module.baseAddress + offset; int checkChar = mem.read8(address); if (checkChar != oldChar) { log.error(String.format("Patching of module '%s' failed at offset 0x%X, 0x%02X found instead of 0x%02X", module.modname, offset, checkChar, oldChar)); } else { String s = Utilities.readStringZ(address); s = s.substring(1); Utilities.writeStringZ(mem, address, s); } } }