diff --git a/pcsx2/Interpreter.cpp b/pcsx2/Interpreter.cpp index 10b7fbdb2b..a2facc0b76 100644 --- a/pcsx2/Interpreter.cpp +++ b/pcsx2/Interpreter.cpp @@ -522,15 +522,35 @@ static void intExecute() case RESET: do execI(); - while (cpuRegs.pc != (eeloadMain ? eeloadMain : EELOAD_START)); + while (cpuRegs.pc != (g_eeloadMain ? g_eeloadMain : EELOAD_START)); if (cpuRegs.pc == EELOAD_START) { // The EELOAD _start function is the same across all BIOS versions afaik u32 mainjump = memRead32(EELOAD_START + 0x9c); if (mainjump >> 26 == 3) // JAL - eeloadMain = ((EELOAD_START + 0xa0) & 0xf0000000U) | (mainjump << 2 & 0x0fffffffU); - } else if (cpuRegs.pc == eeloadMain) + g_eeloadMain = ((EELOAD_START + 0xa0) & 0xf0000000U) | (mainjump << 2 & 0x0fffffffU); + } + else if (cpuRegs.pc == g_eeloadMain) + { eeloadHook(); + if (g_SkipBiosHack) + { + // See comments on this code in iR5900-32.cpp's recRecompile() + u32 typeAexecjump = memRead32(EELOAD_START + 0x470); + u32 typeBexecjump = memRead32(EELOAD_START + 0x5B0); + u32 typeCexecjump = memRead32(EELOAD_START + 0x618); + u32 typeDexecjump = memRead32(EELOAD_START + 0x600); + if ((typeBexecjump >> 26 == 3) || (typeCexecjump >> 26 == 3) || (typeDexecjump >> 26 == 3)) // JAL to 0x822B8 + g_eeloadExec = EELOAD_START + 0x2B8; + else if (typeAexecjump >> 26 == 3) // JAL to 0x82170 + g_eeloadExec = EELOAD_START + 0x170; + else + Console.WriteLn("intExecute: Could not enable launch arguments for fast boot mode; unidentified BIOS version! Please report this to the PCSX2 developers."); + } + } + else if (cpuRegs.pc == g_eeloadExec) + eeloadHook2(); + if (g_GameLoading) state = GAME_LOADING; else diff --git a/pcsx2/R5900.cpp b/pcsx2/R5900.cpp index 7177f040fc..0c49a1c10c 100644 --- a/pcsx2/R5900.cpp +++ b/pcsx2/R5900.cpp @@ -56,7 +56,16 @@ static const uint eeWaitCycles = 3072; bool eeEventTestIsActive = false; -u32 eeloadMain = 0; +u32 g_eeloadMain = 0, g_eeloadExec = 0, g_osdsys_str = 0; + +/* I don't know how much space for args there is in the memory block used for args in full boot mode, +but in fast boot mode, the block we use can fit at least 16 argv pointers (varies with BIOS version). +The second EELOAD call during full boot has three built-in arguments ("EELOAD rom0:PS2LOGO "), +meaning that only the first 13 game arguments supplied by the user can be added on and passed through. +In fast boot mode, 15 arguments can fit because the only call to EELOAD is " <>". */ +const int kMaxArgs = 16; +uptr g_argPtrs[kMaxArgs]; +#define DEBUG_LAUNCHARG 0 // show lots of helpful console messages as the launch arguments are passed to the game extern SysMainMemory& GetVmMemory(); @@ -110,7 +119,7 @@ void cpuReset() // is why I found out about this) --air LastELF = L""; - eeloadMain = 0; + g_eeloadMain = 0, g_eeloadExec = 0, g_osdsys_str = 0; } void cpuShutdown() @@ -540,6 +549,49 @@ void __fastcall eeGameStarting() } } +// Count arguments, save their starting locations, and replace the space separators with null terminators so they're separate strings +int ParseArgumentString(u32 arg_block) +{ + if (!arg_block) + return 0; + + int argc = 1; // one arg is guaranteed at least + g_argPtrs[0] = arg_block; // first arg is right here + bool wasSpace = false; // status of last char. scanned + int args_len = strlen((char *)PSM(arg_block)); + for (int i = 0; i < args_len; i++) + { + char curchar = *(char *)PSM(arg_block + i); + if (curchar == '\0') + break; // should never reach this + + bool isSpace = (curchar == ' '); + if (isSpace) + memset(PSM(arg_block + i), 0, 1); + else if (wasSpace) // then we're at a new arg + { + if (argc < kMaxArgs) + { + g_argPtrs[argc] = arg_block + i; + argc++; + } + else + { + Console.WriteLn("ParseArgumentString: Discarded additional arguments beyond the maximum of %d.", kMaxArgs); + break; + } + } + wasSpace = isSpace; + } +#if DEBUG_LAUNCHARG + // Check our args block + Console.WriteLn("ParseArgumentString: Saving these strings:"); + for (int a = 0; a < argc; a++) + Console.WriteLn("%p -> '%s'.", g_argPtrs[a], (char *)PSM(g_argPtrs[a])); +#endif + return argc; +} + // Called from recompilers; __fastcall define is mandatory. void __fastcall eeloadHook() { @@ -554,9 +606,71 @@ void __fastcall eeloadHook() int disctype = GetPS2ElfName(discelf); std::string elfname; - if (cpuRegs.GPR.n.a0.SD[0] >= 2) // argc >= 2 - elfname = (char*)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + 4)); // argv[1] + int argc = cpuRegs.GPR.n.a0.SD[0]; + if (argc) // calls to EELOAD *after* the first one during the startup process will come here + { +#if DEBUG_LAUNCHARG + Console.WriteLn("eeloadHook: EELOAD was called with %d arguments according to $a0 and %d according to vargs block:", + argc, memRead32(cpuRegs.GPR.n.a1.UD[0] - 4)); + for (int a = 0; a < argc; a++) + Console.WriteLn("argv[%d]: %p -> %p -> '%s'", a, cpuRegs.GPR.n.a1.UL[0] + (a * 4), + memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)), (char *)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)))); +#endif + if (argc > 1) + elfname = (char*)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + 4)); // argv[1] in OSDSYS's invocation "EELOAD " + // This code fires if the user chooses "full boot". First the Sony Computer Entertainment screen appears. This is the result + // of an EELOAD call that does not want to accept launch arguments (but we patch it to do so in eeloadHook2() in fast boot + // mode). Then EELOAD is called with the argument "rom0:PS2LOGO". At this point, we do not need any additional tricks + // because EELOAD is now ready to accept launch arguments. So in full-boot mode, we simply wait for PS2LOGO to be called, + // then we add the desired launch arguments. PS2LOGO passes those on to the game itself as it calls EELOAD a third time. + if (!g_Conf->CurrentGameArgs.empty() && !strcmp(elfname.c_str(), "rom0:PS2LOGO")) + { + const char *argString = g_Conf->CurrentGameArgs.c_str(); + Console.WriteLn("eeloadHook: Supplying launch argument(s) '%s' to module '%s'...", argString, elfname.c_str()); + + // Join all arguments by space characters so they can be processed as one string by ParseArgumentString(), then add the + // user's launch arguments onto the end + u32 arg_ptr = 0; + int arg_len = 0; + for (int a = 0; a < argc; a++) + { + arg_ptr = memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)); + arg_len = strlen((char *)PSM(arg_ptr)); + memset(PSM(arg_ptr + arg_len), 0x20, 1); + } + strcpy((char *)PSM(arg_ptr + arg_len + 1), g_Conf->CurrentGameArgs.c_str()); + u32 first_arg_ptr = memRead32(cpuRegs.GPR.n.a1.UD[0]); +#if DEBUG_LAUNCHARG + Console.WriteLn("eeloadHook: arg block is '%s'.", (char *)PSM(first_arg_ptr)); +#endif + argc = ParseArgumentString(first_arg_ptr); + + // Write pointer to next slot in $a1 + for (int a = 0; a < argc; a++) + memWrite32(cpuRegs.GPR.n.a1.UD[0] + (a * 4), g_argPtrs[a]); + cpuRegs.GPR.n.a0.SD[0] = argc; +#if DEBUG_LAUNCHARG + // Check our work + Console.WriteLn("eeloadHook: New arguments are:"); + for (int a = 0; a < argc; a++) + Console.WriteLn("argv[%d]: %p -> '%s'", a, memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)), + (char *)PSM(memRead32(cpuRegs.GPR.n.a1.UD[0] + (a * 4)))); +#endif + } + // else it's presumed that the invocation is "EELOAD <>", coming from PS2LOGO, and we needn't do + // anything more + } +#if DEBUG_LAUNCHARG + // This code fires in full/fast boot mode when EELOAD is called the first/only time. When EELOAD is not given any arguments, + // it calls rom0:OSDSYS by default, which displays the Sony Computer Entertainment screen. OSDSYS then calls "EELOAD + // rom0:PS2LOGO" and we end up above. + else + Console.WriteLn("eeloadHook: EELOAD was called with no arguments."); +#endif + + // If "fast boot" was chosen, then on EELOAD's first call we won't yet know what the game's ELF is. Find the name and write it + // into EELOAD's memory. if (g_SkipBiosHack && elfname.empty()) { std::string elftoload; @@ -571,37 +685,21 @@ void __fastcall eeloadHook() elftoload = discelf.ToUTF8(); } + // When fast-booting, we insert the game's ELF name into EELOAD so that the game is called instead of the default call of + // "rom0:OSDSYS"; any launch arguments supplied by the user will be inserted into EELOAD later by eeloadHook2() if (!elftoload.empty()) { -#if 0 - // FIXME: you'd think that changing argc and argv would work but no, need to work out why not - // This method would support adding command line arguments to homebrew (and is generally less hacky) - // It works if you hook on rom0:PS2LOGO loading, but not on the first call with no arguments - cpuRegs.GPR.n.a0.SD[0] = 2; // argc = 2 - // argv[0] = "EELOAD" - strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x40), "EELOAD"); - memWrite32(cpuRegs.GPR.n.a1.UD[0] + 0, cpuRegs.GPR.n.a1.UD[0] + 0x40); - // argv[1] = elftoload - strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x47), elftoload.c_str()); - memWrite32(cpuRegs.GPR.n.a1.UD[0] + 4, cpuRegs.GPR.n.a1.UD[0] + 0x47); - memWrite32(cpuRegs.GPR.n.a1.UD[0] + 8, 0); - g_GameLoading = true; - return; -#else - // The strings are all 64-bit aligned. Why? I don't know, but they are - for (u32 osdsys_str = EELOAD_START; osdsys_str < EELOAD_START + EELOAD_SIZE; osdsys_str += 8) { - if (!strcmp((char*)PSM(osdsys_str), "rom0:OSDSYS")) { - for (u32 osdsys_ptr = osdsys_str - 4; osdsys_ptr >= EELOAD_START; osdsys_ptr -= 4) { - if (memRead32(osdsys_ptr) == osdsys_str) { - strcpy((char*)PSM(cpuRegs.GPR.n.a1.UD[0] + 0x40), elftoload.c_str()); - memWrite32(osdsys_ptr, cpuRegs.GPR.n.a1.UD[0] + 0x40); - g_GameLoading = true; - return; - } - } + // Find and save location of default/fallback call "rom0:OSDSYS"; to be used later by eeloadHook2() + for (g_osdsys_str = EELOAD_START; g_osdsys_str < EELOAD_START + EELOAD_SIZE; g_osdsys_str += 8) // strings are 64-bit aligned + { + if (!strcmp((char*)PSM(g_osdsys_str), "rom0:OSDSYS")) + { + // Overwrite OSDSYS with game's ELF name + strcpy((char*)PSM(g_osdsys_str), elftoload.c_str()); + g_GameLoading = true; + return; } } -#endif } } @@ -609,6 +707,50 @@ void __fastcall eeloadHook() g_GameLoading = true; } +// Called from recompilers; __fastcall define is mandatory. +// Only called if g_SkipBiosHack is true +void __fastcall eeloadHook2() +{ + if (g_Conf->CurrentGameArgs.empty()) + return; + + if (!g_osdsys_str) + { + Console.WriteLn("eeloadHook2: Called before \"rom0:OSDSYS\" was found by eeloadHook()!"); + return; + } + + const char *argString = g_Conf->CurrentGameArgs.c_str(); + Console.WriteLn("eeloadHook2: Supplying launch argument(s) '%s' to ELF '%s'.", argString, (char *)PSM(g_osdsys_str)); + + // Add args string after game's ELF name that was written over "rom0:OSDSYS" by eeloadHook(). In between the ELF name and args + // string we insert a space character so that ParseArgumentString() has one continuous string to process. + int game_len = strlen((char *)PSM(g_osdsys_str)); + memset(PSM(g_osdsys_str + game_len), 0x20, 1); + strcpy((char *)PSM(g_osdsys_str + game_len + 1), g_Conf->CurrentGameArgs.c_str()); +#if DEBUG_LAUNCHARG + Console.WriteLn("eeloadHook2: arg block is '%s'.", (char *)PSM(g_osdsys_str)); +#endif + int argc = ParseArgumentString(g_osdsys_str); + + // Back up 4 bytes from start of args block for every arg + 4 bytes for start of argv pointer block, write pointers + uptr block_start = g_osdsys_str - (argc * 4); + for (int a = 0; a < argc; a++) + { +#if DEBUG_LAUNCHARG + Console.WriteLn("eeloadHook2: Writing address %p to location %p.", g_argPtrs[a], block_start + (a * 4)); +#endif + memWrite32(block_start + (a * 4), g_argPtrs[a]); + } + + // Save argc and argv as incoming arguments for EELOAD function which calls ExecPS2() +#if DEBUG_LAUNCHARG + Console.WriteLn("eeloadHook2: Saving %d and %p in $a0 and $a1.", argc, block_start); +#endif + cpuRegs.GPR.n.a0.SD[0] = argc; + cpuRegs.GPR.n.a1.UD[0] = block_start; +} + inline bool isBranchOrJump(u32 addr) { u32 op = memRead32(addr); diff --git a/pcsx2/R5900.h b/pcsx2/R5900.h index ffe6aa3fa6..b3ee05d406 100644 --- a/pcsx2/R5900.h +++ b/pcsx2/R5900.h @@ -273,10 +273,11 @@ const u32 EEKERNEL_START = 0; const u32 EENULL_START = 0x81FC0; const u32 EELOAD_START = 0x82000; const u32 EELOAD_SIZE = 0x20000; // overestimate for searching -extern u32 eeloadMain; +extern u32 g_eeloadMain, g_eeloadExec; extern void __fastcall eeGameStarting(); extern void __fastcall eeloadHook(); +extern void __fastcall eeloadHook2(); // -------------------------------------------------------------------------------------- // R5900cpu diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 9efd1d3c9a..debc166c31 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -288,6 +288,8 @@ public: wxString ElfFile; + wxString GameLaunchArgs; + // Specifies the CDVD source type to use when AutoRunning CDVD_SourceType CdvdSource; diff --git a/pcsx2/gui/AppConfig.h b/pcsx2/gui/AppConfig.h index 8dffa1653f..5b741a822e 100644 --- a/pcsx2/gui/AppConfig.h +++ b/pcsx2/gui/AppConfig.h @@ -315,6 +315,7 @@ public: wxString CurrentELF; wxString CurrentIRX; CDVD_SourceType CdvdSource; + wxString CurrentGameArgs; // Memorycard options - first 2 are default slots, last 6 are multitap 1 and 2 // slots (3 each) diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index 0ead3b072f..2a4b15716d 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -243,6 +243,7 @@ void Pcsx2App::OnInitCmdLine( wxCmdLineParser& parser ) parser.AddSwitch( wxEmptyString,L"nohacks", _("disables all speedhacks") ); parser.AddOption( wxEmptyString,L"gamefixes", _("use the specified comma or pipe-delimited list of gamefixes.") + fixlist, wxCMD_LINE_VAL_STRING ); parser.AddSwitch( wxEmptyString,L"fullboot", _("disables fast booting") ); + parser.AddOption( wxEmptyString,L"gameargs", _("passes the specified space-delimited string of launch arguments to the game"), wxCMD_LINE_VAL_STRING); parser.AddOption( wxEmptyString,L"cfgpath", _("changes the configuration file path"), wxCMD_LINE_VAL_STRING ); parser.AddOption( wxEmptyString,L"cfg", _("specifies the PCSX2 configuration file to use"), wxCMD_LINE_VAL_STRING ); @@ -365,6 +366,10 @@ bool Pcsx2App::OnCmdLineParsed( wxCmdLineParser& parser ) } } + wxString game_args; + if (parser.Found(L"gameargs", &game_args) && !game_args.IsEmpty()) + Startup.GameLaunchArgs = game_args; + if( parser.Found(L"usecd") ) { Startup.CdvdSource = CDVD_SourceType::Plugin; @@ -507,6 +512,7 @@ bool Pcsx2App::OnInit() if (Startup.CdvdSource == CDVD_SourceType::Iso) SysUpdateIsoSrcFile( Startup.IsoFile ); sApp.SysExecute( Startup.CdvdSource ); + g_Conf->CurrentGameArgs = Startup.GameLaunchArgs; } else if ( Startup.SysAutoRunElf ) { diff --git a/pcsx2/x86/ix86-32/iR5900-32.cpp b/pcsx2/x86/ix86-32/iR5900-32.cpp index 9d26933ecc..49d61c557f 100644 --- a/pcsx2/x86/ix86-32/iR5900-32.cpp +++ b/pcsx2/x86/ix86-32/iR5900-32.cpp @@ -1652,15 +1652,33 @@ static void __fastcall recRecompile( const u32 startpc ) pxAssert(s_pCurBlockEx); - if (HWADDR(startpc) == EELOAD_START) { - // The EELOAD _start function is the same across all BIOS versions afaik + if (HWADDR(startpc) == EELOAD_START) + { + // The EELOAD _start function is the same across all BIOS versions u32 mainjump = memRead32(EELOAD_START + 0x9c); if (mainjump >> 26 == 3) // JAL - eeloadMain = ((EELOAD_START + 0xa0) & 0xf0000000U) | (mainjump << 2 & 0x0fffffffU); + g_eeloadMain = ((EELOAD_START + 0xa0) & 0xf0000000U) | (mainjump << 2 & 0x0fffffffU); } - if (eeloadMain && HWADDR(startpc) == HWADDR(eeloadMain)) { + if (g_eeloadMain && HWADDR(startpc) == HWADDR(g_eeloadMain)) + { xFastCall((void*)eeloadHook); + if (g_SkipBiosHack) + { + // There are four known versions of EELOAD, identifiable by the location of the 'jal' to the EELOAD function which + // calls ExecPS2(). The function itself is at the same address in all BIOSs after v1.00-v1.10. + u32 typeAexecjump = memRead32(EELOAD_START + 0x470); // v1.00, v1.01?, v1.10? + u32 typeBexecjump = memRead32(EELOAD_START + 0x5B0); // v1.20, v1.50, v1.60 (3000x models) + u32 typeCexecjump = memRead32(EELOAD_START + 0x618); // v1.60 (3900x models) + u32 typeDexecjump = memRead32(EELOAD_START + 0x600); // v1.70, v1.90, v2.00, v2.20, v2.30 + if ((typeBexecjump >> 26 == 3) || (typeCexecjump >> 26 == 3) || (typeDexecjump >> 26 == 3)) // JAL to 0x822B8 + g_eeloadExec = EELOAD_START + 0x2B8; + else if (typeAexecjump >> 26 == 3) // JAL to 0x82170 + g_eeloadExec = EELOAD_START + 0x170; + else // There might be other types of EELOAD, because these models' BIOSs have not been examined: 18000, 3500x, 3700x, + // 5500x, and 7900x. However, all BIOS versions have been examined except for v1.01 and v1.10. + Console.WriteLn("recRecompile: Could not enable launch arguments for fast boot mode; unidentified BIOS version! Please report this to the PCSX2 developers."); + } // On fast/full boot this will have a crc of 0x0. But when the game/elf itself is // recompiled (below - ElfEntry && g_GameLoading), then the crc would be from the elf. @@ -1670,6 +1688,9 @@ static void __fastcall recRecompile( const u32 startpc ) doPlace0Patches(); g_patchesNeedRedo = 0; } + + if (g_eeloadExec && HWADDR(startpc) == HWADDR(g_eeloadExec)) + xFastCall((void*)eeloadHook2); // this is the only way patches get applied, doesn't depend on a hack if (g_GameLoading && HWADDR(startpc) == ElfEntry) {