diff --git a/README.md b/README.md index 70ce3bc..8746f82 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ using Cheat Device. ## Features * Easy to navigate menu system similar to CodeBreaker * Supports booting retail discs and loader ELFs +* HDD build: boot ELFs stored on PFS partitions with boot paths like + `hdd0:__common:pfs:/APPS/OPNPS2LD.ELF` (requires starting Cheat Device from the HDD) * Fast loading of large cheat lists * Save manager for backing up and restoring game saves to/from a flash drive * Powered by ps2rd's powerful cheat engine diff --git a/bootstrap/Makefile b/bootstrap/Makefile index 9b38d80..d4671c2 100644 --- a/bootstrap/Makefile +++ b/bootstrap/Makefile @@ -6,6 +6,8 @@ EE_OBJS = main.o #EE_LDFLAGS = -s -Ttext 0x000F3000 #EE_LIBS += -lkernel -lpatches -lerl +EE_LIBS += -lfileXio + EE_CFLAGS := -mips3 -ffreestanding -fno-builtin -G0 \ -fshort-double -mlong64 -mhard-float -mno-abicalls -O2 -EL -Wall \ $(EE_INCS) $(EE_CFLAGS) diff --git a/bootstrap/main.c b/bootstrap/main.c index 3cb5cd9..6dd2f9b 100644 --- a/bootstrap/main.c +++ b/bootstrap/main.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,102 @@ typedef struct { u32 align; } elf_pheader_t; +/* Loads an ELF from a PFS partition that Cheat Device left mounted on + * "pfs0:" before executing the bootstrap. SifLoadElf and fio can't see + * iomanX devices like pfs, so the ELF is loaded manually through the + * fileXio RPC, whose IOP module is still resident since the IOP was not + * reset. elfpath is in the "hdd0:partition:pfs:/path" form and is passed + * as-is to the loaded ELF as argv[0]. Only returns on failure, leaving + * the current stage's debug color on screen. */ +static void LoadElfFromPFS(char *elfpath) +{ + elf_header_t boot_header; + elf_pheader_t boot_pheader; + char pfspath[256]; + char *file; + char *args[1]; + int i, fd; + + GS_BGCOLOUR = red; /* RED: Opening elf */ + + /* Accept both the uLaunchELF-style ":pfs:" marker and the OPL/ + elf-loader-style ":pfs0:" one. */ + file = strstr(elfpath, ":pfs"); + if (file == NULL) + return; + file += 4; + while (*file >= '0' && *file <= '9') + file++; + if (*file != ':') + return; + file++; + + strcpy(pfspath, "pfs0:"); + strncat(pfspath, file, sizeof(pfspath) - sizeof("pfs0:")); + + SifInitRpc(0); + if (fileXioInit() < 0) + return; + + fd = fileXioOpen(pfspath, O_RDONLY, 0); + if (fd < 0) + return; + + GS_BGCOLOUR = blue; /* BLUE: Installing elf header */ + if (fileXioRead(fd, (void *)&boot_header, sizeof(elf_header_t)) != sizeof(elf_header_t)) { + fileXioClose(fd); + return; + } + + GS_BGCOLOUR = white; + /* check ELF magic */ + if ((*(u32*)boot_header.ident) != ELF_MAGIC) { + fileXioClose(fd); + return; + } + + GS_BGCOLOUR = green; /* GREEN: Reading elf */ + /* copy loadable program segments to RAM */ + for (i = 0; i < boot_header.phnum; i++) { + fileXioLseek(fd, boot_header.phoff + (i * sizeof(elf_pheader_t)), SEEK_SET); + fileXioRead(fd, (void *)&boot_pheader, sizeof(elf_pheader_t)); + + if (boot_pheader.type != ELF_PT_LOAD) + continue; + + fileXioLseek(fd, boot_pheader.offset, SEEK_SET); + fileXioRead(fd, boot_pheader.vaddr, boot_pheader.filesz); + + if (boot_pheader.memsz > boot_pheader.filesz) + memset(boot_pheader.vaddr + boot_pheader.filesz, 0, + boot_pheader.memsz - boot_pheader.filesz); + } + + fileXioClose(fd); + fileXioUmount("pfs0:"); + + GS_BGCOLOUR = yellow; /* YELLOW: ExecPS2 */ + + /* IOP reboot routine from ps2rd */ + while (!SifIopReset("rom0:UDNL rom0:EELOADCNF", 0)) + ; + while (!SifIopSync()) + ; + + /* exit services */ + fioExit(); + SifExitIopHeap(); + SifLoadFileExit(); + SifExitRpc(); + SifExitCmd(); + + FlushCache(0); + FlushCache(2); + + args[0] = elfpath; + ExecPS2((u32*)boot_header.entry, 0, 1, args); +} + /* From NetCheat */ void MyLoadElf(char *elfpath) { @@ -88,6 +185,14 @@ void MyLoadElf(char *elfpath) /* Clear scratchpad memory */ memset((void*)0x70000000, 0, 16 * 1024); + /* ELFs on the HDD live on PFS partitions, which SifLoadElf and fio + can't read. Load them through fileXio instead. */ + if (elfpath[0] == 'h' && elfpath[1] == 'd' && elfpath[2] == 'd') + { + LoadElfFromPFS(elfpath); /* only returns on failure */ + SleepThread(); + } + /* HACK do not reset IOP when launching ELF from mass */ if (!(elfpath[0] == 'm' && elfpath[1] == 'a' && elfpath[2] == 's' && elfpath[3] == 's')) diff --git a/src/main.c b/src/main.c index 15a8f1f..567a09b 100644 --- a/src/main.c +++ b/src/main.c @@ -36,15 +36,19 @@ int main(int argc, char *argv[]) #endif #ifdef HDD DPRINTF("Checking if booting from HDD\n"); - if (argc > 0) booting_from_hdd = (strstr(argv[0], "hdd0:")!=NULL)&&(strstr(argv[0], ":pfs:")!=NULL); + // uLaunchELF-style launchers pass "hdd0:partition:pfs:/path/file.elf" + // while OPL (through ps2sdk's elf-loader) passes + // "hdd0:partition:pfs0:path/file.elf", so accept both pfs markers. + if (argc > 0) booting_from_hdd = (strstr(argv[0], "hdd0:")!=NULL)&&(pfsPathGetFilePart(argv[0])!=NULL); DPRINTF("Booting from hdd:%d\n", booting_from_hdd); + if (booting_from_hdd) { char* BUF = NULL; BUF = strdup(argv[0]); //use strdup, otherwise, path will become `hdd0:` if (BUF==NULL) { DPRINTF("Could not strdup()\n"); } if (getMountInfo(BUF, NULL, MountPoint, pfspath)) { - + DPRINTF("MountPoint '%s'\npfspath '%s'\n", MountPoint, pfspath); char *pos = strrchr(pfspath, '/'); if (pos != NULL) { @@ -61,12 +65,13 @@ int main(int argc, char *argv[]) chdir(pfspath); } else displayError("Error processing HDD boot path (2)"); } else displayError("Error processing HDD boot path (1)"); + } #endif ret = loadModules(booting_from_hdd); if (ret != 0) displayError(error); #ifdef HDD - if (ret == 0) { + if (ret == 0 && booting_from_hdd) { int mtret=0; if ((mtret=fileXioMount("pfs0:", MountPoint, FIO_MT_RDWR)) < 0) { sprintf(error, "Error: failed to mount partition \"%s\"!\nerr:%d (0x%x)", MountPoint, mtret, mtret); diff --git a/src/startgame.c b/src/startgame.c index 0b302a0..31ebf0d 100644 --- a/src/startgame.c +++ b/src/startgame.c @@ -15,6 +15,15 @@ #include "util.h" #include "objectpool.h" +#ifdef HDD +#include + +extern int booting_from_hdd; +extern int HDD_USABLE; +extern char MountPoint[40]; +int getMountInfo(char *path, char *mountString, char *mountPoint, char *newCWD); +#endif + typedef struct { u8 ident[16]; // struct definition for ELF object header u16 type; @@ -86,10 +95,66 @@ static void* loadBootstrap() return (void *)eh->entry; } +#ifdef HDD +// Prepares booting an ELF from an "hdd0:partition:pfs:/path" style boot path: +// remounts pfs0: on the target partition and checks that the ELF exists, so +// failures show an error menu here instead of hanging in the bootstrap. +// On failure, pfs0: is restored to the boot partition and 0 is returned. +static int setupHddBootPath(const char *path) +{ + char msg[192]; + char pathCopy[100]; + char partition[64]; + char pfsPath[128]; + const char *file; + int ret; + + if(!booting_from_hdd || !HDD_USABLE) + { + displayError("HDD boot paths can only be used when\nCheat Device was started from the HDD."); + return 0; + } + + file = pfsPathGetFilePart(path); + strncpy(pathCopy, path, sizeof(pathCopy) - 1); + pathCopy[sizeof(pathCopy) - 1] = '\0'; + + if(!file || !getMountInfo(pathCopy, NULL, partition, NULL)) + { + displayError("Invalid HDD boot path!\nExpected format:\nhdd0:partition:pfs:/path/to/boot.elf"); + return 0; + } + + fileXioUmount("pfs0:"); + ret = fileXioMount("pfs0:", partition, FIO_MT_RDONLY); + if(ret < 0) + { + snprintf(msg, sizeof(msg), "Error: failed to mount partition\n\"%s\" (%d)", partition, ret); + fileXioMount("pfs0:", MountPoint, FIO_MT_RDWR); + displayError(msg); + return 0; + } + + snprintf(pfsPath, sizeof(pfsPath), "pfs0:%s", file); + ret = fileXioOpen(pfsPath, O_RDONLY, 0); + if(ret < 0) + { + snprintf(msg, sizeof(msg), "Error: couldn't open \"%s\"\non partition \"%s\" (%d)", file, partition, ret); + fileXioUmount("pfs0:"); + fileXioMount("pfs0:", MountPoint, FIO_MT_RDWR); + displayError(msg); + return 0; + } + fileXioClose(ret); + + return 1; +} +#endif + void startgameExecute(const char *path) { static char boot2[100]; - + if(strcmp(path, "==Disc==") == 0) { if(!discPrompt()) @@ -143,11 +208,26 @@ void startgameExecute(const char *path) } else { - strncpy(boot2, path, 100); + strncpy(boot2, path, sizeof(boot2) - 1); + boot2[sizeof(boot2) - 1] = '\0'; } + // Databases and settings must be saved before an HDD boot path gets a + // chance to remount pfs0: on another partition. cheatsSaveDatabase(); settingsSave(NULL, 0); + + if(strncmp(boot2, "hdd0:", 5) == 0) + { +#ifdef HDD + if(!setupHddBootPath(boot2)) + return; +#else + displayError("HDD boot paths are only supported by\nthe HDD build of Cheat Device."); + return; +#endif + } + cheatsInstallCodesForEngine(); killMenus(); killCheats(); diff --git a/src/util.c b/src/util.c index 535ee8c..dc33ddf 100644 --- a/src/util.c +++ b/src/util.c @@ -8,6 +8,7 @@ #include "cheats.h" #include "settings.h" #include +#include #include #include #include @@ -18,7 +19,7 @@ #include #include -extern char* error; +extern char error[255]; #define EXTERN_BIN2O(_name_) extern u8 _name_##_start[]; extern int _name_##_size; #define LOAD_IRX_BUF(_irx_, ARGC, ARGV, RET) SifExecModuleBuffer(_irx_##_start, _irx_##_size, ARGC, ARGV, RET) #define LOAD_IRX_BUF_NARG(_irx_, RET) LOAD_IRX_BUF(_irx_, 0, NULL, RET) @@ -105,9 +106,28 @@ void poweroffCallback(void *arg) } #endif +const char *pfsPathGetFilePart(const char *path) +{ + const char *p = strstr(path, ":pfs"); + if(!p) + return NULL; + + p += strlen(":pfs"); + while(*p >= '0' && *p <= '9') + p++; // Skip the mount point number, if any + + if(*p != ':') + return NULL; + + return p + 1; +} + int loadModules(int booting_from_hdd) { int ID, RET, HDDSTAT, filexio_loaded=0, dev9_loaded=0, mmceman_loaded=0; +#ifdef DEV9 + int dev9_id=0, dev9_ret=0; +#endif DPRINTF("\n ** Loading main modules **\n"); /* IOP reset routine taken from ps2rd */ @@ -151,6 +171,8 @@ int loadModules(int booting_from_hdd) if (booting_from_hdd) { ID = LOAD_IRX_BUF_NARG(_ps2dev9_irx, &RET); dev9_loaded = IRX_LOAD_SUCCESS(); + dev9_id = ID; + dev9_ret = RET; } #endif @@ -240,7 +262,11 @@ int loadModules(int booting_from_hdd) } } else { sprintf(error, "HDD Init Error:\nHDD Status: %d (%s)", HDDSTAT, HDDerr(HDDSTAT)); + return -6; } + } else { + sprintf(error, "HDD Init error\n%s: ID:%d, RET_%d!", "DEV9.IRX", dev9_id, dev9_ret); + return -7; } } #endif diff --git a/src/util.h b/src/util.h index 9a141cf..067842d 100644 --- a/src/util.h +++ b/src/util.h @@ -58,6 +58,12 @@ int getNumLines(const char *str); void replaceIllegalChars(const char *str, char* valid, char replacement); // Remove trailing whitespace from str. char* rtrim(char *str); +// Get the file path portion following the ":pfs:" or ":pfsN:" marker in an +// "hdd0:partition:pfs:path" style boot path, or NULL if there is no marker. +// uLaunchELF-style launchers use ":pfs:" while OPL (through ps2sdk's +// elf-loader) produces ":pfs0:". +const char *pfsPathGetFilePart(const char *path); + // Get file extension from filename. Returns null if filename doesn't have an extension. const char* getFileExtension(const char *filename); // Get file path without extension.