mirror of
https://github.com/wiidev/usbloadergx.git
synced 2025-07-26 21:37:32 +02:00
2262 lines
79 KiB
C++
2262 lines
79 KiB
C++
/****************************************************************************
|
|
* Copyright (C) 2019-2025 blackb0x
|
|
* Copyright (C) 2012-2015 Cyan
|
|
* Copyright (C) 2011 Dimok
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
****************************************************************************/
|
|
#include <algorithm>
|
|
|
|
#include "menu/menus.h"
|
|
#include "menu/WDMMenu.hpp"
|
|
#include "mload/mload.h"
|
|
#include "mload/mload_modules.h"
|
|
#include "system/IosLoader.h"
|
|
#include "libs/libruntimeiospatch/runtimeiospatch.h"
|
|
#include "Controls/DeviceHandler.hpp"
|
|
#include "Channels/channels.h"
|
|
#include "usbloader/disc.h"
|
|
#include "usbloader/apploader.h"
|
|
#include "usbloader/usbstorage2.h"
|
|
#include "usbloader/wdvd.h"
|
|
#include "usbloader/GameList.h"
|
|
#include "settings/CGameSettings.h"
|
|
#include "settings/SettingsEnums.h"
|
|
#include "usbloader/frag.h"
|
|
#include "usbloader/wbfs.h"
|
|
#include "usbloader/playlog.h"
|
|
#include "usbloader/MountGamePartition.h"
|
|
#include "usbloader/AlternateDOLOffsets.h"
|
|
#include "usbloader/wbfs/wbfs_fat.h"
|
|
#include "GameCube/GCGames.h"
|
|
#include "settings/newtitles.h"
|
|
#include "network/Wiinnertag.h"
|
|
#include "patches/patchcode.h"
|
|
#include "patches/gamepatches.h"
|
|
#include "patches/wip.h"
|
|
#include "patches/bca.h"
|
|
#include "banner/OpeningBNR.hpp"
|
|
#include "wad/nandtitle.h"
|
|
#include "menu/menus.h"
|
|
#include "memory/memory.h"
|
|
#include "utils/StringTools.h"
|
|
#include "homebrewboot/BootHomebrew.h"
|
|
#include "GameBooter.hpp"
|
|
#include "NandEmu.h"
|
|
#include "SavePath.h"
|
|
#include "sys.h"
|
|
#include "FileOperations/fileops.h"
|
|
#include "prompts/ProgressWindow.h"
|
|
#include "neek.hpp"
|
|
#include "lstub.h"
|
|
#include "xml/GameTDB.hpp"
|
|
#include "wad/nandtitle.h"
|
|
#include "settings/GameTitles.h"
|
|
#include "SystemMenu/SystemMenuResources.h"
|
|
|
|
/* GCC 11 false positives */
|
|
#if __GNUC__ > 10
|
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
|
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
|
#endif
|
|
|
|
// appentrypoint has to be global because of asm
|
|
u32 AppEntrypoint = 0;
|
|
s8 ocarinaAnswer = -1;
|
|
|
|
extern bool isWiiVC; // in sys.cpp
|
|
extern u32 hdd_sector_size[2];
|
|
extern std::vector<struct d2x> d2x_list;
|
|
extern "C"
|
|
{
|
|
syssram *__SYS_LockSram();
|
|
u32 __SYS_UnlockSram(u32 write);
|
|
u32 __SYS_SyncSram(void);
|
|
extern void __exception_closeall();
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
u32 magic;
|
|
u32 version;
|
|
u32 count;
|
|
u32 reserved;
|
|
u32 entries[];
|
|
} aspectDB;
|
|
|
|
int GameBooter::BootGCMode(struct discHdr *gameHdr)
|
|
{
|
|
// check the settings
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHdr->id);
|
|
u8 GCMode = game_cfg->GameCubeMode == INHERIT ? Settings.GameCubeMode : game_cfg->GameCubeMode;
|
|
|
|
// Devolution
|
|
if (GCMode == GC_MODE_DEVOLUTION)
|
|
return BootDevolution(gameHdr);
|
|
|
|
// Nintendont
|
|
if (GCMode == GC_MODE_NINTENDONT)
|
|
return BootNintendont(gameHdr);
|
|
|
|
// DIOS MIOS (Lite) and QuadForce
|
|
int currentMIOS = IosLoader::GetMIOSInfo();
|
|
if (currentMIOS == DIOS_MIOS || currentMIOS == DIOS_MIOS_LITE || currentMIOS == QUADFORCE || currentMIOS == QUADFORCE_USB)
|
|
return BootDIOSMIOS(gameHdr);
|
|
|
|
// MIOS or Wiigator cMIOS
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC)
|
|
{
|
|
ExitApp();
|
|
gprintf("\nLoading BC for GameCube");
|
|
WII_Initialize();
|
|
return WII_LaunchTitle(0x0000000100000100ULL);
|
|
}
|
|
|
|
WindowPrompt(tr("Error:"), tr("You need to install an additional GameCube loader or select a different GameCube Mode to launch GameCube games from USB or SD card."), tr("OK"));
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 GameBooter::BootPartition(char *dolpath, u8 videoselected, u8 alternatedol, u32 alternatedoloffset, struct discHdr &gameHdr)
|
|
{
|
|
gprintf("Booting partition IOS %u r%u\n", IOS_GetVersion(), IOS_GetRevision());
|
|
entry_point p_entry;
|
|
s32 ret;
|
|
u64 offset;
|
|
|
|
/* Find game partition offset */
|
|
ret = Disc_FindPartition(&offset);
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
/* Open specified partition */
|
|
ret = WDVD_OpenPartition(offset, NULL);
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
/* Setup low memory */
|
|
Disc_SetLowMem(&gameHdr);
|
|
|
|
/* Setup video mode */
|
|
Disc_SelectVMode(videoselected, false, NULL, NULL);
|
|
|
|
/* Run apploader */
|
|
ret = Apploader_Run(&p_entry, dolpath, alternatedol, alternatedoloffset);
|
|
|
|
if (ret < 0)
|
|
return 0;
|
|
|
|
return (u32)p_entry;
|
|
}
|
|
|
|
void GameBooter::SetupAltDOL(u8 *gameID, u8 &alternatedol, u32 &alternatedoloffset)
|
|
{
|
|
if (alternatedol == ALT_DOL_ON_LAUNCH)
|
|
{
|
|
alternatedol = ALT_DOL_FROM_GAME;
|
|
alternatedoloffset = WDMMenu::GetAlternateDolOffset();
|
|
}
|
|
else if (alternatedol == ALT_DOL_DEFAULT)
|
|
{
|
|
alternatedol = ALT_DOL_FROM_GAME;
|
|
alternatedoloffset = defaultAltDol((char *)gameID);
|
|
}
|
|
|
|
if (alternatedol == ALT_DOL_FROM_GAME && alternatedoloffset == 0)
|
|
alternatedol = OFF;
|
|
}
|
|
|
|
void GameBooter::SetupNandEmu(u8 NandEmuMode, const char *NandEmuPath, struct discHdr &gameHeader)
|
|
{
|
|
if (NandEmuMode && strchr(NandEmuPath, '/'))
|
|
{
|
|
int partition = -1;
|
|
|
|
//! Create save game path and title.tmd for not existing saves
|
|
CreateSavePath(&gameHeader, NandEmuPath);
|
|
|
|
gprintf("Enabling %s NAND emulation on: %s\n", NandEmuMode == 2 ? "full" : "partial", NandEmuPath);
|
|
Set_FullMode(NandEmuMode == 2);
|
|
Set_Path(strchr(NandEmuPath, '/'));
|
|
|
|
//! Unmount devices to flush data before activating NAND Emu
|
|
if (strncmp(NandEmuPath, "usb", 3) == 0)
|
|
{
|
|
//! Set which partition to use (USB only)
|
|
partition = atoi(NandEmuPath + 3) - 1;
|
|
Set_Partition(DeviceHandler::PartitionToPortPartition(partition));
|
|
DeviceHandler::Instance()->UnMount(USB1 + partition);
|
|
}
|
|
else
|
|
{
|
|
//! Can't play from an SD card while saving to an SD card with bases other than 56 & 57
|
|
DeviceHandler::Instance()->UnMountSD();
|
|
}
|
|
|
|
Enable_Emu(strncmp(NandEmuPath, "usb", 3) == 0 ? EMU_USB : EMU_SD);
|
|
|
|
//! Remount the device
|
|
if (strncmp(NandEmuPath, "usb", 3) == 0)
|
|
DeviceHandler::Instance()->Mount(USB1 + partition);
|
|
else
|
|
DeviceHandler::Instance()->MountSD();
|
|
}
|
|
}
|
|
|
|
int GameBooter::SetupDisc(struct discHdr &gameHeader)
|
|
{
|
|
if (gameHeader.type == TYPE_GAME_WII_DISC)
|
|
{
|
|
gprintf("Loading DVD\n");
|
|
return Disc_Open(false);
|
|
}
|
|
|
|
int ret = -1;
|
|
|
|
if (IosLoader::IsWaninkokoIOS() && IOS_GetRevision() < 18)
|
|
{
|
|
gprintf("Disc_SetUSB...");
|
|
ret = Disc_SetUSB(gameHeader.id);
|
|
gprintf("%d\n", ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
gprintf("Loading fragment list...");
|
|
ret = get_frag_list(gameHeader.id);
|
|
gprintf("%d\n", ret);
|
|
if (ret < 0)
|
|
return ret;
|
|
DeviceHandler::Instance()->UnMountSD();
|
|
ret = set_frag_list(gameHeader.id, Settings.SDMode);
|
|
if (ret < 0)
|
|
return ret;
|
|
gprintf("%s set to game\n", Settings.SDMode ? "SD" : "USB");
|
|
DeviceHandler::Instance()->MountSD();
|
|
}
|
|
|
|
gprintf("Disc_Open()...");
|
|
ret = Disc_Open(false);
|
|
gprintf("%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GameBooter::ShutDownDevices(int gameUSBPort)
|
|
{
|
|
gprintf("Shutting down devices...\n");
|
|
//! Flush all caches and close up all devices
|
|
WBFS_CloseAll();
|
|
DeviceHandler::DestroyInstance();
|
|
|
|
//! Shadow mload - Only needed on some games with Hermes v5.1 (Check is inside the function)
|
|
shadow_mload();
|
|
|
|
//! Reset USB port because device handler changes it for cache flushing
|
|
if (Settings.USBPort == 2)
|
|
USBStorage2_SetPort(gameUSBPort);
|
|
USBStorage2_Deinit();
|
|
if (!Settings.SDMode)
|
|
USB_Deinitialize();
|
|
}
|
|
|
|
int GameBooter::BootGame(struct discHdr *gameHdr, const s8 useOcarina)
|
|
{
|
|
if (!gameHdr)
|
|
return -1;
|
|
|
|
ocarinaAnswer = useOcarina;
|
|
struct discHdr gameHeader;
|
|
memcpy(&gameHeader, gameHdr, sizeof(struct discHdr));
|
|
|
|
gprintf("Boot Game: %s (%.6s)\n", gameHeader.title, gameHeader.id);
|
|
|
|
// Load the HBC from NAND instead of from the homebrew browser
|
|
if (!isWiiVC && memcmp(gameHeader.id, "JODI", 4) == 0)
|
|
Sys_LoadHBC();
|
|
|
|
if (Settings.Wiinnertag)
|
|
Wiinnertag::TagGame((const char *)gameHeader.id);
|
|
|
|
if (gameHeader.type == TYPE_GAME_GC_IMG || gameHeader.type == TYPE_GAME_GC_DISC || gameHdr->type == TYPE_GAME_GC_EXTRACTED)
|
|
return BootGCMode(&gameHeader);
|
|
|
|
//! Setup game configuration from game settings. If no game settings exist use global/default.
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHeader.id);
|
|
u8 videoChoice = game_cfg->video == INHERIT ? Settings.videomode : game_cfg->video;
|
|
u8 videoPatchDolChoice = game_cfg->videoPatchDol == INHERIT ? Settings.videoPatchDol : game_cfg->videoPatchDol;
|
|
u8 patchFix480pChoice = game_cfg->patchFix480p == INHERIT ? Settings.patchFix480p : game_cfg->patchFix480p;
|
|
u8 aspectChoice = game_cfg->aspectratio == INHERIT ? Settings.GameAspectRatio : game_cfg->aspectratio;
|
|
u8 languageChoice = game_cfg->language == INHERIT ? Settings.language : game_cfg->language;
|
|
u8 ocarinaChoice = game_cfg->ocarina == INHERIT ? Settings.ocarina : game_cfg->ocarina;
|
|
if (ocarinaAnswer >= OCARINA_OFF)
|
|
ocarinaChoice = ocarinaAnswer;
|
|
u8 PrivServChoice = game_cfg->PrivateServer == INHERIT ? Settings.PrivateServer : game_cfg->PrivateServer;
|
|
const char *customAddress = game_cfg->CustomAddress.size() == 0 ? Settings.CustomAddress : game_cfg->CustomAddress.c_str();
|
|
u8 viChoice = game_cfg->vipatch == INHERIT ? Settings.videopatch : game_cfg->vipatch;
|
|
u8 deflicker = game_cfg->deflicker == INHERIT ? Settings.deflicker : game_cfg->deflicker;
|
|
u8 sneekChoice = game_cfg->sneekVideoPatch == INHERIT ? Settings.sneekVideoPatch : game_cfg->sneekVideoPatch;
|
|
s32 iosChoice = game_cfg->ios == INHERIT ? Settings.cios : game_cfg->ios;
|
|
u8 autoIOS = game_cfg->autoios == INHERIT ? Settings.AutoIOS : game_cfg->autoios;
|
|
u8 countrystrings = game_cfg->patchcountrystrings == INHERIT ? Settings.patchcountrystrings : game_cfg->patchcountrystrings;
|
|
u8 alternatedol = game_cfg->loadalternatedol;
|
|
u32 alternatedoloffset = game_cfg->alternatedolstart;
|
|
u8 reloadblock = game_cfg->iosreloadblock == INHERIT ? Settings.BlockIOSReload : game_cfg->iosreloadblock;
|
|
u8 Hooktype = game_cfg->Hooktype == INHERIT ? Settings.Hooktype : game_cfg->Hooktype;
|
|
u8 WiirdDebugger = game_cfg->WiirdDebugger == INHERIT ? Settings.WiirdDebugger : game_cfg->WiirdDebugger;
|
|
u8 disableMotor = game_cfg->wpadMotor == INHERIT ? Settings.wpadMotor : game_cfg->wpadMotor;
|
|
u8 disableSpeaker = game_cfg->wpadSpeaker == INHERIT ? Settings.wpadSpeaker : game_cfg->wpadSpeaker;
|
|
u8 ScreenMode = game_cfg->ScreenMode == INHERIT ? Settings.ScreenMode : game_cfg->ScreenMode;
|
|
u16 videoWidth = game_cfg->videoWidth == INHERIT ? Settings.videoWidth : game_cfg->videoWidth;
|
|
u64 returnToChoice = strlen(Settings.returnTo) > 0 ? (game_cfg->returnTo ? NandTitles.FindU32(Settings.returnTo) : 0) : 0;
|
|
u8 NandEmuMode = OFF;
|
|
const char *NandEmuPath = game_cfg->NandEmuPath.size() == 0 ? Settings.NandEmuPath : game_cfg->NandEmuPath.c_str();
|
|
if (gameHeader.type == TYPE_GAME_WII_IMG && !Settings.SDMode)
|
|
NandEmuMode = game_cfg->NandEmuMode == INHERIT ? Settings.NandEmuMode : game_cfg->NandEmuMode;
|
|
if (gameHeader.type == TYPE_GAME_EMUNANDCHAN)
|
|
{
|
|
NandEmuMode = game_cfg->NandEmuMode == INHERIT ? Settings.NandEmuChanMode : game_cfg->NandEmuMode;
|
|
NandEmuPath = game_cfg->NandEmuPath.size() == 0 ? Settings.NandEmuChanPath : game_cfg->NandEmuPath.c_str();
|
|
}
|
|
|
|
// boot neek for Wii games and EmuNAND channels only
|
|
if (NandEmuMode == EMUNAND_NEEK && (gameHeader.type == TYPE_GAME_WII_IMG || gameHeader.type == TYPE_GAME_EMUNANDCHAN))
|
|
return BootNeek(&gameHeader);
|
|
|
|
// Display games correctly on Wii U - https://wiibrew.org/wiki/43DB
|
|
if (isWiiU())
|
|
{
|
|
ExitGUIThreads();
|
|
if (aspectChoice == ASPECT_SYSTEM_DEFAULT && Settings.widescreen && ScreenMode == SCREEN_DEFAULT)
|
|
{
|
|
aspectDB *ardb = NULL;
|
|
char sys[2];
|
|
|
|
snprintf(sys, sizeof(sys), "%c", gameHeader.id[0]);
|
|
|
|
// Filter by type due to some custom games/channels using bad IDs
|
|
if (gameHeader.type <= TYPE_GAME_WII_DISC)
|
|
{
|
|
// Wii games
|
|
if (strstr("DRS", sys) != NULL)
|
|
ardb = (aspectDB *)SystemMenuResources::Instance()->Get43DBDisc();
|
|
}
|
|
else if (gameHeader.type >= TYPE_GAME_NANDCHAN)
|
|
{
|
|
// C64, NES, SNES, SMD/GEN, SMS, N64, TG16 & TGCD are always 4:3
|
|
if (strstr("CFJLMNPQ", sys) != NULL)
|
|
{
|
|
// Exclude Super Street Fighter II
|
|
if (memcmp(gameHeader.id, "MC3", 3) != 0)
|
|
write32(0xd8006a0, 0x30000002), mask32(0xd8006a8, 0, 2);
|
|
}
|
|
// Wii channels and WiiWare
|
|
else if (gameHeader.id[0] == 'H' || gameHeader.id[0] == 'W')
|
|
{
|
|
// Exclude Everybody Votes and Mii Contest channels
|
|
if (memcmp(gameHeader.id, "HAP", 3) != 0 && memcmp(gameHeader.id, "HAJ", 3) != 0)
|
|
ardb = (aspectDB *)SystemMenuResources::Instance()->Get43DBWiiWare();
|
|
}
|
|
// E5 and E6 are arcade games. EA, EB & EC are Neo Geo games
|
|
else if (gameHeader.id[0] == 'E')
|
|
{
|
|
if (gameHeader.id[1] == '5' || gameHeader.id[1] == '6')
|
|
ardb = (aspectDB *)SystemMenuResources::Instance()->Get43DBVC();
|
|
else if (gameHeader.id[1] == 'A' || gameHeader.id[1] == 'B' || gameHeader.id[1] == 'C')
|
|
write32(0xd8006a0, 0x30000002), mask32(0xd8006a8, 0, 2);
|
|
}
|
|
// XA are MSX games. The others are WiiWare demos
|
|
else if (gameHeader.id[0] == 'X')
|
|
{
|
|
if (gameHeader.id[1] == 'A')
|
|
write32(0xd8006a0, 0x30000002), mask32(0xd8006a8, 0, 2);
|
|
else
|
|
ardb = (aspectDB *)SystemMenuResources::Instance()->Get43DBWiiWare();
|
|
}
|
|
}
|
|
// Check the database and enable 4:3 if there's a match
|
|
if (ardb && ardb->magic == 0x34334442 && ardb->count)
|
|
{
|
|
for (u32 i = 0; i < ardb->count; i++)
|
|
{
|
|
if (memcmp(&ardb->entries[i], gameHeader.id, 3) == 0)
|
|
{
|
|
write32(0xd8006a0, 0x30000002), mask32(0xd8006a8, 0, 2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (aspectChoice == ASPECT_FORCE_4_3 && Settings.widescreen && ScreenMode == SCREEN_DEFAULT)
|
|
write32(0xd8006a0, 0x30000002), mask32(0xd8006a8, 0, 2);
|
|
else if (aspectChoice == ASPECT_FORCE_16_9)
|
|
write32(0xd8006a0, 0x30000004), mask32(0xd8006a8, 0, 2);
|
|
}
|
|
|
|
if (languageChoice == CONSOLE_DEFAULT)
|
|
{
|
|
std::string Filepath(Settings.titlestxt_path);
|
|
if (Filepath.back() != '/')
|
|
Filepath += '/';
|
|
Filepath += "wiitdb.xml";
|
|
|
|
GameTDB XML_DB;
|
|
if (XML_DB.OpenFile(Filepath.c_str()))
|
|
{
|
|
std::string gameLangs;
|
|
if (XML_DB.GetLanguages((char *)gameHeader.id, gameLangs))
|
|
{
|
|
// Check if the game supports the system language (CONF_GetLanguage returns 0-9)
|
|
std::string sysLangs[] = {"JA", "EN", "DE", "FR", "ES", "IT", "NL", "ZHCN", "ZHTW", "KO"};
|
|
if (gameLangs.find(sysLangs[CONF_GetLanguage()]) == std::string::npos)
|
|
{
|
|
// Use whatever is the first supported language
|
|
std::string lang = gameLangs.substr(0, gameLangs.find(","));
|
|
for (u32 i = 0; i < sizeof(sysLangs) / sizeof(sysLangs[0]); i++)
|
|
{
|
|
if (sysLangs[i] == lang)
|
|
{
|
|
gprintf("Changed language to %s\n", lang.c_str());
|
|
languageChoice = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
XML_DB.CloseFile();
|
|
}
|
|
}
|
|
|
|
if (autoIOS == GAME_IOS_AUTO && d2x_list.size() && !isWiiVC)
|
|
{
|
|
u8 requestedIOS = 0;
|
|
if (gameHeader.type == TYPE_GAME_NANDCHAN)
|
|
requestedIOS = Channels::GetRequestedIOS(gameHeader.tid, NULL);
|
|
else if (gameHeader.type == TYPE_GAME_EMUNANDCHAN)
|
|
requestedIOS = Channels::GetRequestedIOS(gameHeader.tid, NandEmuPath);
|
|
else if (gameHeader.type == TYPE_GAME_WII_IMG)
|
|
{
|
|
wbfs_disc_t *d = WBFS_OpenDisc(gameHeader.id);
|
|
if (d)
|
|
{
|
|
void *titleTMD = NULL;
|
|
int tmd_size = wbfs_extract_file(d, (char *)"TMD", &titleTMD);
|
|
if (titleTMD)
|
|
{
|
|
if (tmd_size > 0x18B)
|
|
requestedIOS = *((u8 *)titleTMD + 0x18B);
|
|
free(titleTMD);
|
|
}
|
|
WBFS_CloseDisc(d);
|
|
}
|
|
}
|
|
else if (gameHeader.type == TYPE_GAME_WII_DISC)
|
|
{
|
|
u64 offset;
|
|
if (Disc_FindPartition(&offset) >= 0)
|
|
{
|
|
if (WDVD_OpenPartition(offset, &requestedIOS) >= 0)
|
|
WDVD_ClosePartition();
|
|
}
|
|
}
|
|
|
|
if (requestedIOS)
|
|
{
|
|
// Remove cIOS duplicates
|
|
// This is done here so that IsD2X() always has the complete list
|
|
for (auto cios = d2x_list.begin(); cios != d2x_list.end();)
|
|
{
|
|
if (cios->duplicate)
|
|
{
|
|
gprintf("Duplicate IOS: %d in slot %d removed\n", cios->base, cios->slot);
|
|
cios = d2x_list.erase(cios);
|
|
}
|
|
else
|
|
++cios;
|
|
}
|
|
|
|
bool sdEmuNAND = false;
|
|
gprintf("Requested IOS: %d\n", requestedIOS);
|
|
// Workaround for SpongeBobs Boating Bash
|
|
if (memcmp(gameHeader.id, "SBV", 3) == 0)
|
|
{
|
|
// Check if we don't have a cIOS with base IOS 53
|
|
if (!IosLoader::GetD2XIOS(requestedIOS))
|
|
{
|
|
// Saves will go to NAND if the path is to the SD card while using base 38
|
|
requestedIOS = IosLoader::GetD2XIOS(58) ? 58 : 38;
|
|
gprintf("Applied SpongeBob workaround\n");
|
|
}
|
|
}
|
|
// Workaround for Wii System Transfer (vWii)
|
|
else if (memcmp(gameHeader.id, "HCT", 3) == 0)
|
|
{
|
|
requestedIOS = 56;
|
|
}
|
|
// SSBB mods like Infinite won't work with IOS 38
|
|
else if (memcmp(gameHeader.id, "RSB", 3) == 0)
|
|
{
|
|
requestedIOS = 56;
|
|
}
|
|
// The d2x cIOS can only save to SD cards with bases 56-60
|
|
else if ((strncmp(NandEmuPath, "sd", 2) == 0 && NandEmuMode > EMUNAND_OFF) || Settings.SDMode)
|
|
{
|
|
for (auto cios = d2x_list.begin(); cios != d2x_list.end();)
|
|
{
|
|
if (cios->base < 56 || cios->base > 60)
|
|
{
|
|
cios = d2x_list.erase(cios);
|
|
sdEmuNAND = true;
|
|
}
|
|
else
|
|
++cios;
|
|
}
|
|
}
|
|
|
|
// Check if there's any cIOS options remaining
|
|
if (d2x_list.size())
|
|
{
|
|
// Check for a d2x cIOS with the requested base IOS
|
|
int slot = IosLoader::GetD2XIOS(requestedIOS);
|
|
if (slot)
|
|
iosChoice = slot;
|
|
else
|
|
{
|
|
// Nothing found, so try the closest match
|
|
// e.g. if we've got 55, 57 & 58 and a game requests 56 we'll use 57
|
|
auto cios = std::lower_bound(d2x_list.begin(), d2x_list.end(), requestedIOS, [](const d2x &x, const int &y)
|
|
{ return x.base < y; });
|
|
// Check if the requested IOS is greater than what's available
|
|
if (cios == d2x_list.end())
|
|
{
|
|
requestedIOS = d2x_list.back().base;
|
|
iosChoice = d2x_list.back().slot;
|
|
}
|
|
else
|
|
{
|
|
requestedIOS = cios->base;
|
|
iosChoice = cios->slot;
|
|
}
|
|
gprintf("Next best IOS: %d%s\n", requestedIOS, sdEmuNAND ? " (restricted to 56-60)" : "");
|
|
}
|
|
gprintf("Boot with IOS: %d base %d\n", iosChoice, requestedIOS);
|
|
}
|
|
}
|
|
}
|
|
AppCleanUp();
|
|
|
|
gprintf("Settings.partition: %d\n", Settings.partition);
|
|
|
|
s32 ret = -1;
|
|
|
|
//! Remember game's USB port
|
|
int partition = gameList.GetPartitionNumber(gameHeader.id);
|
|
int usbport = DeviceHandler::PartitionToUSBPort(partition);
|
|
|
|
//! Prepare alternate dol settings
|
|
SetupAltDOL(gameHeader.id, alternatedol, alternatedoloffset);
|
|
|
|
//! Reload game settings cIOS for this game
|
|
if (iosChoice != IOS_GetVersion())
|
|
{
|
|
gprintf("Reloading into game cIOS: %i...\n", iosChoice);
|
|
IosLoader::LoadGameCios(iosChoice);
|
|
if (MountGamePartition(false) < 0)
|
|
return -1;
|
|
}
|
|
|
|
//! Modify Wii Message Board to display the game starting here (before NAND Emu)
|
|
if (Settings.PlaylogUpdate)
|
|
{
|
|
// enable isfs permission if using IOS+AHB or Hermes v4
|
|
if (IOS_GetVersion() < 200 || (IosLoader::IsHermesIOS() && IOS_GetRevision() == 4))
|
|
{
|
|
gprintf("Patching IOS%d...\n", IOS_GetVersion());
|
|
if (IosPatch_RUNTIME(true, false, false, false, false) == ERROR_PATCH)
|
|
gprintf("Patching %sIOS%d failed!\n", IOS_GetVersion() >= 200 ? "c" : "", IOS_GetVersion());
|
|
}
|
|
|
|
BNRInstance::Instance()->Load(&gameHeader);
|
|
Playlog_Update((char *)gameHeader.id, BNRInstance::Instance()->GetIMETTitle(CONF_GetLanguage()));
|
|
}
|
|
|
|
if (PrivServChoice == PRIVSERV_CUSTOM)
|
|
gprintf("Custom address: %s\n", customAddress);
|
|
|
|
//! Load wip codes
|
|
load_wip_code(gameHeader.id);
|
|
|
|
//! Load Ocarina codes
|
|
if (ocarinaChoice)
|
|
{
|
|
//! Force hooktype if not selected but Ocarina is enabled
|
|
if (ocarina_load_code(Settings.Cheatcodespath, gameHeader.id) > 0 && Hooktype == OFF)
|
|
Hooktype = 1;
|
|
}
|
|
|
|
//! Load gameconfig.txt even if ocarina disabled
|
|
if (Hooktype)
|
|
LoadGameConfig(Settings.Cheatcodespath);
|
|
|
|
//! Setup NAND emulation
|
|
if (!exclude_game(gameHeader.id, true))
|
|
SetupNandEmu(NandEmuMode, NandEmuPath, gameHeader);
|
|
|
|
//! Setup disc stuff if we load a game
|
|
if (gameHeader.tid == 0)
|
|
{
|
|
//! Setup disc in cIOS and open it
|
|
ret = SetupDisc(gameHeader);
|
|
if (ret < 0)
|
|
Sys_BackToLoader();
|
|
|
|
//! Load BCA data for the game
|
|
gprintf("Loading BCA data...");
|
|
ret = do_bca_code(Settings.BcaCodepath, gameHeader.id);
|
|
gprintf("%d\n", ret);
|
|
}
|
|
|
|
if (IosLoader::IsHermesIOS(iosChoice))
|
|
{
|
|
if (reloadblock == ON)
|
|
{
|
|
//! Setup IOS reload block
|
|
enable_ES_ioctlv_vector();
|
|
if (Settings.SDMode)
|
|
{
|
|
if (gameList.GetGameFSSD(gameHeader.id) == PART_FS_WBFS)
|
|
mload_close();
|
|
}
|
|
else
|
|
{
|
|
if (gameList.GetGameFS(gameHeader.id) == PART_FS_WBFS)
|
|
mload_close();
|
|
}
|
|
}
|
|
}
|
|
else if (IosLoader::IsD2X(iosChoice))
|
|
{
|
|
// Open ES file descriptor for the d2x patches
|
|
static char es_fs[] ATTRIBUTE_ALIGN(32) = "/dev/es";
|
|
int es_fd = IOS_Open(es_fs, 0);
|
|
if (es_fd >= 0)
|
|
{
|
|
// IOS Reload Block
|
|
if (reloadblock != OFF)
|
|
BlockIOSReload(es_fd, iosChoice);
|
|
// Check if new patch method for return to works otherwise old method will be used
|
|
if (PatchNewReturnTo(es_fd, returnToChoice) >= 0)
|
|
returnToChoice = 0; // Patch successful, no need for old method
|
|
|
|
// Close ES file descriptor
|
|
IOS_Close(es_fd);
|
|
}
|
|
}
|
|
|
|
//! Now we can free up the memory used by the game/channel lists
|
|
gameList.clear();
|
|
GameTitles.Clear();
|
|
GCGames::Instance()->clear();
|
|
Channels::Instance()->clear();
|
|
GCGames::DestroyInstance();
|
|
Channels::DestroyInstance();
|
|
|
|
//! Load main.dol or alternative dol into memory, start the game apploader and get game entrypoint
|
|
if (gameHeader.tid == 0)
|
|
{
|
|
gprintf("Game Boot\n");
|
|
AppEntrypoint = BootPartition(Settings.dolpath, videoChoice, alternatedol, alternatedoloffset, gameHeader);
|
|
// Reading of game is done we can close devices now
|
|
ShutDownDevices(usbport);
|
|
}
|
|
else
|
|
{
|
|
//! shutdown now and avoid later crashes with free if memory gets overwritten by channel
|
|
ShutDownDevices(DeviceHandler::PartitionToUSBPort(std::max(atoi(NandEmuPath + 3) - 1, 0)));
|
|
gprintf("Channel Boot\n");
|
|
// Load dol
|
|
AppEntrypoint = Channels::LoadChannel(gameHeader.tid);
|
|
/* Setup video mode */
|
|
Disc_SelectVMode(videoChoice, false, NULL, NULL);
|
|
}
|
|
|
|
//! No entrypoint found...back to HBC/SystemMenu
|
|
if (AppEntrypoint == 0)
|
|
{
|
|
gprintf("AppEntryPoint is 0, something went wrong\n");
|
|
WDVD_ClosePartition();
|
|
Sys_BackToLoader();
|
|
}
|
|
|
|
//! Do all the game patches
|
|
gprintf("Applying game patches...\n");
|
|
|
|
if (Settings.SDMode)
|
|
patch_sdcard(gameHeader.id);
|
|
|
|
//! Now this code block is responsible for the private server patch
|
|
//! and the gecko code handler loading
|
|
|
|
//! If a server other than Wiimmfi is selected, do the normal patching
|
|
//! If Wiimmfi is selected for other games than MKWii, do normal patching as well
|
|
//! If Wiimmfi is selected for MKWii, skip normal patching (PRIVSERV_OFF)
|
|
//! and let the new code in do_new_wiimmfi() handle the complete server patch
|
|
|
|
//! Also, the new Wiimmfi server patch should be loaded into memory after
|
|
//! the code handler and the cheat codes.
|
|
|
|
if (PrivServChoice != PRIVSERV_WIIMMFI || memcmp(gameHeader.id, "RMC", 3) != 0)
|
|
{
|
|
//! Either the server is not Wiimmfi, or, if it is Wiimmfi, the game isn't MKWii - patch the old way
|
|
gamepatches(videoChoice, videoPatchDolChoice, aspectChoice, languageChoice, countrystrings, viChoice,
|
|
deflicker, disableMotor, disableSpeaker,
|
|
sneekChoice, Hooktype, videoWidth, returnToChoice, PrivServChoice, customAddress);
|
|
}
|
|
else
|
|
{
|
|
//! Wiimmfi patch for Mario Kart Wii - patch with PRIVSERV_OFF and handle all the patching within do_new_wiimmfi()
|
|
gamepatches(videoChoice, videoPatchDolChoice, aspectChoice, languageChoice, countrystrings, viChoice,
|
|
deflicker, disableMotor, disableSpeaker,
|
|
sneekChoice, Hooktype, videoWidth, returnToChoice, PRIVSERV_OFF, customAddress);
|
|
}
|
|
|
|
//! Load Code handler if needed
|
|
load_handler(Hooktype, WiirdDebugger, Settings.WiirdDebuggerPause);
|
|
|
|
//! Apply the 480p fix (enabled by default).
|
|
//! This needs to be done after the call to gamepatches(), after loading any code handler.
|
|
//! Can (and should) be done before Wiimmfi patching, can't be done in gamepatches() itself.
|
|
//! Exclude Prince of Persia: The Forgotten Sands and a few games that use MetaFortress
|
|
if (patchFix480pChoice && !exclude_game(gameHeader.id, false))
|
|
PatchFix480p();
|
|
|
|
//! If we're NOT on Wiimmfi, patch the known RCE vulnerability in MKWii.
|
|
//! Wiimmfi will handle that on its own through the update payload.
|
|
//! This will also patch error 23400 for a couple games that still have official servers.
|
|
if (PrivServChoice != PRIVSERV_WIIMMFI)
|
|
patch_error_codes(gameHeader.id);
|
|
|
|
//! New Wiimmfi patch should be loaded last, after the codehandler, just before the call to the entry point
|
|
if (PrivServChoice == PRIVSERV_WIIMMFI && memcmp(gameHeader.id, "RMC", 3) == 0)
|
|
{
|
|
// all the cool new Wiimmfi stuff:
|
|
switch (do_new_wiimmfi())
|
|
{
|
|
case 0:
|
|
gprintf("Wiimmfi patch for Mario Kart Wii successful.\n");
|
|
break;
|
|
case -1:
|
|
gprintf("Could not determine game region for Wiimmfi patch - make sure the fourth char of the ID is one of [PEJK].\n");
|
|
break;
|
|
case -2:
|
|
gprintf("This image is already patched for Wiimmfi, no need to do so again.\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//! Jump to the entrypoint of the game - the last function of the USB Loader
|
|
gprintf("Jumping to game entrypoint: 0x%08x.\n", AppEntrypoint);
|
|
return Disc_JumpToEntrypoint(Hooktype, WDMMenu::GetDolParameter());
|
|
}
|
|
|
|
int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
|
|
{
|
|
const char *RealPath = GCGames::Instance()->GetPath((const char *)gameHdr->id);
|
|
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHdr->id);
|
|
s8 languageChoice = game_cfg->language == INHERIT ? Settings.language - 1 : game_cfg->language;
|
|
u8 ocarinaChoice = game_cfg->ocarina == INHERIT ? Settings.ocarina : game_cfg->ocarina;
|
|
if (ocarinaAnswer >= OCARINA_OFF)
|
|
ocarinaChoice = ocarinaAnswer;
|
|
u8 multiDiscChoice = Settings.MultiDiscPrompt;
|
|
u8 dmlVideoChoice = game_cfg->DMLVideo == INHERIT ? Settings.DMLVideo : game_cfg->DMLVideo;
|
|
u8 dmlProgressivePatch = game_cfg->DMLProgPatch == INHERIT ? Settings.DMLProgPatch : game_cfg->DMLProgPatch;
|
|
u8 dmlNMMChoice = game_cfg->DMLNMM == INHERIT ? Settings.DMLNMM : game_cfg->DMLNMM;
|
|
u8 dmlActivityLEDChoice = game_cfg->DMLActivityLED == INHERIT ? Settings.DMLActivityLED : game_cfg->DMLActivityLED;
|
|
u8 dmlPADHookChoice = game_cfg->DMLPADHOOK == INHERIT ? Settings.DMLPADHOOK : game_cfg->DMLPADHOOK;
|
|
u8 dmlNoDisc2Choice = game_cfg->DMLNoDisc2 == INHERIT ? Settings.DMLNoDisc2 : game_cfg->DMLNoDisc2;
|
|
u8 dmlWidescreenChoice = game_cfg->DMLWidescreen == INHERIT ? Settings.DMLWidescreen : game_cfg->DMLWidescreen;
|
|
u8 dmlScreenshotChoice = game_cfg->DMLScreenshot == INHERIT ? Settings.DMLScreenshot : game_cfg->DMLScreenshot;
|
|
u8 dmlJPNPatchChoice = game_cfg->DMLJPNPatch == INHERIT ? Settings.DMLJPNPatch : game_cfg->DMLJPNPatch;
|
|
u8 dmlDebugChoice = game_cfg->DMLDebug == INHERIT ? Settings.DMLDebug : game_cfg->DMLDebug;
|
|
|
|
int currentMIOS = IosLoader::GetMIOSInfo();
|
|
char LoaderName[15];
|
|
if (currentMIOS == DIOS_MIOS)
|
|
snprintf(LoaderName, sizeof(LoaderName), "DIOS MIOS");
|
|
else if (currentMIOS == DIOS_MIOS_LITE)
|
|
snprintf(LoaderName, sizeof(LoaderName), "DIOS MIOS Lite");
|
|
else if (currentMIOS == QUADFORCE)
|
|
snprintf(LoaderName, sizeof(LoaderName), "QuadForce");
|
|
else if (currentMIOS == QUADFORCE_USB)
|
|
snprintf(LoaderName, sizeof(LoaderName), "QuadForce_USB");
|
|
|
|
// DIOS MIOS
|
|
if (currentMIOS == DIOS_MIOS || currentMIOS == QUADFORCE_USB)
|
|
{
|
|
// Check Main GameCube Path location
|
|
if (strncmp(Settings.GameCubePath, "sd", 2) == 0 || strncmp(DeviceHandler::PathToFSName(Settings.GameCubePath), "FAT", 3) != 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' to an USB FAT32 partition."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check current game location
|
|
if (strncmp(RealPath, "sd", 2) == 0)
|
|
{
|
|
WindowPrompt(tr("The game is on SD Card."), fmt(tr("To run GameCube games with %s you need to place them on an USB FAT32 partition."), LoaderName), tr("OK"));
|
|
// Todo: Add here copySD2USB.
|
|
return -1;
|
|
}
|
|
|
|
// Check if the partition is the first primary partition on the drive
|
|
bool found = false;
|
|
int USB_partNum = DeviceHandler::PathToDriveType(Settings.GameCubePath) - USB1;
|
|
int USBport_partNum = DeviceHandler::PartitionToPortPartition(USB_partNum);
|
|
int usbport = DeviceHandler::PartitionToUSBPort(USB_partNum);
|
|
PartitionHandle *usbHandle = DeviceHandler::Instance()->GetUSBHandleFromPartition(USB_partNum);
|
|
for (int partition = 0; partition <= USBport_partNum; partition++)
|
|
{
|
|
if (usbHandle->GetPartitionTableType(partition) != MBR)
|
|
continue;
|
|
|
|
if (partition == USBport_partNum)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' on the first primary partition of the Hard Drive."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check HDD sector size. Only 512 bytes/sector is supported by DIOS MIOS
|
|
if (hdd_sector_size[usbport] != BYTES_PER_SECTOR)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to use a 512 bytes/sector Hard Drive."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
if (usbHandle->GetPartitionClusterSize(usbHandle->GetLBAStart(USBport_partNum)) > 32768)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to use a partition with 32k bytes/cluster or less."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// DIOS MIOS Lite
|
|
else if (currentMIOS == DIOS_MIOS_LITE || currentMIOS == QUADFORCE)
|
|
{
|
|
if (((gameHdr->type == TYPE_GAME_GC_IMG) || (gameHdr->type == TYPE_GAME_GC_EXTRACTED)) && strncmp(RealPath, "usb", 3) == 0)
|
|
{
|
|
if (!GCGames::Instance()->CopyUSB2SD(gameHdr))
|
|
return -1;
|
|
|
|
RealPath = GCGames::Instance()->GetPath((const char *)gameHdr->id);
|
|
}
|
|
}
|
|
|
|
// Check DIOS MIOS config for specific versions
|
|
if (currentMIOS != QUADFORCE && currentMIOS != QUADFORCE_USB)
|
|
{
|
|
if (IosLoader::GetDMLVersion() < DML_VERSION_DML_1_2)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("You need to install DIOS MIOS Lite v1.2 or a newer version."), tr("OK"));
|
|
return -1;
|
|
}
|
|
if (dmlWidescreenChoice && IosLoader::GetDMLVersion() < DML_VERSION_DM_2_1) // DML Force Widescreen setting : added in DM v2.1+, config v1.
|
|
{
|
|
if (Settings.DMLWidescreen) // Display the warning only if set as Global setting. Individual game setting is not displayed.
|
|
WindowPrompt(tr("Warning:"), tr("The Force Widescreen setting requires DIOS MIOS v2.1 or more. This setting will be ignored."), tr("OK"));
|
|
dmlWidescreenChoice = OFF;
|
|
}
|
|
if (dmlNoDisc2Choice && (IosLoader::GetDMLVersion() < DML_VERSION_DM_2_2_2 || IosLoader::GetDMLVersion() > DML_VERSION_DML_2_2_1)) // DML NoDisc+ setting : Added in DM 2.2 upate 2, config v2, removed in DM(L) v2.3
|
|
{
|
|
if (Settings.DMLNoDisc2) // Display the warning only if set as Global setting. Individual game setting is not displayed.
|
|
WindowPrompt(tr("Warning:"), tr("The No Disc+ setting requires DIOS MIOS 2.2 update2. This setting will be ignored."), tr("OK"));
|
|
dmlNoDisc2Choice = false;
|
|
}
|
|
}
|
|
|
|
// Check Ocarina and cheat file location. the .gct file need to be located on the same partition than the game.
|
|
if (gameHdr->type != TYPE_GAME_GC_DISC && ocarinaChoice && strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) != 0)
|
|
{
|
|
char path[255], destPath[255];
|
|
int res = -1;
|
|
snprintf(path, sizeof(path), "%s%.6s.gct", Settings.Cheatcodespath, (char *)gameHdr->id);
|
|
snprintf(destPath, sizeof(destPath), "%s:/DMLTemp.gct", DeviceHandler::GetDevicePrefix(RealPath));
|
|
|
|
gprintf("DML: Copying %s to %s \n", path, destPath);
|
|
res = CopyFile(path, destPath);
|
|
if (res < 0)
|
|
{
|
|
gprintf("DML: Couldn't copy the file. ret %d. Ocarina Disabled\n", res);
|
|
RemoveFile(destPath);
|
|
ocarinaChoice = false;
|
|
}
|
|
}
|
|
|
|
// Check if game has multi Discs
|
|
bool bootDisc2 = false;
|
|
if (multiDiscChoice && gameHdr->type != TYPE_GAME_GC_DISC && gameHdr->disc_no == 0 && currentMIOS != QUADFORCE)
|
|
{
|
|
char disc2Path[255];
|
|
snprintf(disc2Path, sizeof(disc2Path), "%s", RealPath);
|
|
char *pathPtr = strrchr(disc2Path, '/');
|
|
if (pathPtr)
|
|
*pathPtr = 0;
|
|
snprintf(disc2Path + strlen(disc2Path), sizeof(disc2Path) - strlen(disc2Path), "/disc2.iso");
|
|
if (CheckFile(disc2Path))
|
|
{
|
|
int choice = WindowPrompt(gameHdr->title, tr("This game has multiple discs. Please select the disc to launch."), tr("Disc 1"), tr("Disc 2"), tr("Cancel"));
|
|
if (choice == 0)
|
|
return -1;
|
|
else if (choice == 2)
|
|
bootDisc2 = true;
|
|
}
|
|
}
|
|
|
|
const char *gcPath = strchr(RealPath, '/');
|
|
if (!gcPath)
|
|
gcPath = "";
|
|
|
|
char gamePath[255];
|
|
snprintf(gamePath, sizeof(gamePath), "%s", gcPath);
|
|
|
|
if (bootDisc2)
|
|
{
|
|
char *pathPtr = strrchr(gamePath, '/');
|
|
if (pathPtr)
|
|
*pathPtr = 0;
|
|
snprintf(gamePath + strlen(gamePath), sizeof(gamePath) - strlen(gamePath), "/disc2.iso");
|
|
}
|
|
|
|
ExitApp();
|
|
|
|
// Game ID
|
|
memcpy((u8 *)Disc_ID, gameHdr->id, 6);
|
|
DCFlushRange((u8 *)Disc_ID, 6);
|
|
|
|
// *(vu32*)0xCC003024 |= 7; // DML 1.1- only?
|
|
|
|
DML_CFG *dml_config = (DML_CFG *)DML_CONFIG_ADDRESS;
|
|
memset(dml_config, 0, sizeof(DML_CFG));
|
|
|
|
// Magic and version for DML
|
|
dml_config->Magicbytes = DML_MAGIC;
|
|
dml_config->Version = IosLoader::GetDMLVersion() >= DML_VERSION_DM_2_2 ? 0x00000002 : 0x00000001;
|
|
|
|
// Select disc source
|
|
if ((gameHdr->type == TYPE_GAME_GC_IMG) || (gameHdr->type == TYPE_GAME_GC_EXTRACTED))
|
|
{
|
|
dml_config->Config |= DML_CFG_GAME_PATH;
|
|
strncpy(dml_config->GamePath, gamePath, sizeof(dml_config->GamePath));
|
|
// Extended NoDisc patch
|
|
if (dmlNoDisc2Choice && IosLoader::GetDMLVersion() >= DML_VERSION_DM_2_2_2 && IosLoader::GetDMLVersion() < DML_VERSION_DML_2_3m)
|
|
dml_config->Config |= DML_CFG_NODISC2; // used by v2.2 update2 as an Extended NoDisc patching
|
|
|
|
gprintf("DML: Loading game %s\n", dml_config->GamePath);
|
|
}
|
|
else
|
|
{
|
|
dml_config->Config |= DML_CFG_BOOT_DISC;
|
|
}
|
|
|
|
// setup cheat and path
|
|
if (ocarinaChoice)
|
|
{
|
|
// Check if the .gct folder is on the same partition than the game, if not load the temporary .gct file.
|
|
if (strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) == 0)
|
|
{
|
|
const char *CheatPath = strchr(Settings.Cheatcodespath, '/');
|
|
if (!CheatPath)
|
|
CheatPath = "";
|
|
snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "%s%.6s.gct", CheatPath, (char *)gameHdr->id);
|
|
}
|
|
else if (gameHdr->type != TYPE_GAME_GC_DISC)
|
|
{
|
|
snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "DMLTemp.gct");
|
|
}
|
|
|
|
dml_config->Config |= DML_CFG_CHEATS | DML_CFG_CHEAT_PATH;
|
|
gprintf("DML: Loading cheat %s\n", dml_config->CheatPath);
|
|
}
|
|
|
|
// other DML configs
|
|
if (dmlPADHookChoice)
|
|
dml_config->Config |= DML_CFG_PADHOOK;
|
|
if (dmlActivityLEDChoice)
|
|
dml_config->Config |= DML_CFG_ACTIVITY_LED;
|
|
if (dmlNMMChoice)
|
|
dml_config->Config |= dmlNMMChoice == ON ? DML_CFG_NMM : DML_CFG_NMM_DEBUG;
|
|
if (dmlDebugChoice)
|
|
dml_config->Config |= dmlDebugChoice == ON ? DML_CFG_DEBUGGER : DML_CFG_DEBUGGER | DML_CFG_DEBUGWAIT;
|
|
if (dmlWidescreenChoice)
|
|
dml_config->Config |= DML_CFG_FORCE_WIDE;
|
|
if (dmlScreenshotChoice)
|
|
{
|
|
dml_config->Config |= DML_CFG_SCREENSHOT;
|
|
dml_config->Config |= DML_CFG_PADHOOK;
|
|
}
|
|
if (bootDisc2 && IosLoader::GetDMLVersion() >= DML_VERSION_DM_2_6_0)
|
|
dml_config->Config |= DML_CFG_BOOT_DISC2;
|
|
|
|
// Setup Video Mode
|
|
if (dmlVideoChoice == DML_VIDEO_NONE) // No video mode
|
|
{
|
|
dml_config->VideoMode = DML_VID_NONE;
|
|
}
|
|
else
|
|
{
|
|
if (dmlVideoChoice == DML_VIDEO_AUTO) // Auto select video mode
|
|
{
|
|
dml_config->VideoMode = DML_VID_DML_AUTO;
|
|
Disc_SelectVMode(VIDEO_MODE_DISCDEFAULT, false, NULL, NULL);
|
|
}
|
|
else // Force user choice
|
|
{
|
|
Disc_SelectVMode(dmlVideoChoice - 1, false, &dml_config->VideoMode, NULL);
|
|
if (!(dml_config->VideoMode & DML_VID_DML_AUTO))
|
|
dml_config->VideoMode |= DML_VID_FORCE;
|
|
}
|
|
Disc_SetVMode();
|
|
}
|
|
|
|
if (dmlProgressivePatch)
|
|
dml_config->VideoMode |= DML_VID_PROG_PATCH;
|
|
|
|
DCFlushRange(dml_config, sizeof(DML_CFG));
|
|
memcpy((u8 *)DML_CONFIG_ADDRESS_V1_2, dml_config, sizeof(DML_CFG));
|
|
DCFlushRange((u8 *)DML_CONFIG_ADDRESS_V1_2, sizeof(DML_CFG));
|
|
|
|
// print the config set for DML
|
|
gprintf("DML: setup configuration 0x%X\n", dml_config->Config);
|
|
gprintf("DML: setup video mode 0x%X\n", dml_config->VideoMode);
|
|
|
|
// Set Sram flags
|
|
bool progressive = (dml_config->VideoMode & DML_VID_FORCE_PROG) || (dml_config->VideoMode & DML_VID_PROG_PATCH);
|
|
PatchSram(languageChoice, true, progressive);
|
|
|
|
/* NTSC-J Patch */ // Thanks to Fix94
|
|
u8 *diskid = (u8 *)Disc_ID;
|
|
if (dmlJPNPatchChoice && diskid[3] == 'J')
|
|
{
|
|
u32 region = *HW_VI1CFG;
|
|
*HW_VI1CFG = region | (1 << 17);
|
|
DCFlushRange((void *)HW_VI1CFG, 4);
|
|
}
|
|
|
|
gprintf("\nLoading BC for GameCube\n");
|
|
WII_Initialize();
|
|
return WII_LaunchTitle(0x0000000100000100ULL);
|
|
}
|
|
|
|
int GameBooter::BootDevolution(struct discHdr *gameHdr)
|
|
{
|
|
const char *RealPath = GCGames::Instance()->GetPath((const char *)gameHdr->id);
|
|
const char *LoaderName = "Devolution";
|
|
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHdr->id);
|
|
s8 languageChoice = game_cfg->language == INHERIT ? Settings.language - 1 : game_cfg->language;
|
|
u8 devoProgressivePatch = game_cfg->DMLProgPatch == INHERIT ? Settings.DMLProgPatch : game_cfg->DMLProgPatch;
|
|
u8 devoMCEmulation = game_cfg->DEVOMCEmulation == INHERIT ? Settings.DEVOMCEmulation : game_cfg->DEVOMCEmulation;
|
|
u8 devoActivityLEDChoice = game_cfg->DEVOActivityLED == INHERIT ? Settings.DEVOActivityLED : game_cfg->DEVOActivityLED;
|
|
u8 devoWidescreenChoice = game_cfg->DEVOWidescreen == INHERIT ? Settings.DEVOWidescreen : game_cfg->DEVOWidescreen;
|
|
u8 devoFZeroAXChoice = game_cfg->DEVOFZeroAX == INHERIT ? Settings.DEVOFZeroAX : game_cfg->DEVOFZeroAX;
|
|
u8 devoTimerFixChoice = game_cfg->DEVOTimerFix == INHERIT ? Settings.DEVOTimerFix : game_cfg->DEVOTimerFix;
|
|
u8 devoDButtonsChoice = game_cfg->DEVODButtons == INHERIT ? Settings.DEVODButtons : game_cfg->DEVODButtons;
|
|
u8 devoCropOverscanChoice = game_cfg->DEVOCropOverscan == INHERIT ? Settings.DEVOCropOverscan : game_cfg->DEVOCropOverscan;
|
|
u8 devoDiscDelayChoice = game_cfg->DEVODiscDelay == INHERIT ? Settings.DEVODiscDelay : game_cfg->DEVODiscDelay;
|
|
u64 returnToChoice = strlen(Settings.returnTo) > 0 ? (game_cfg->returnTo ? NandTitles.FindU32(Settings.returnTo) : 0) : 0;
|
|
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To run GameCube games from disc you need to set the GameCube mode to MIOS in the game settings."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
if (gameHdr->type == TYPE_GAME_GC_EXTRACTED)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("%s only accepts GameCube backups in ISO format."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
if (!CheckAHBPROT())
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("%s requires AHB access! Please launch USB Loader GX from HBC or from an updated channel or forwarder."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
if (strncmp(DeviceHandler::PathToFSName(RealPath), "FAT", 3) != 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' to an USB FAT32 partition."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check if Devolution is available
|
|
u8 *loader_bin = NULL;
|
|
int DEVO_version = 0;
|
|
char DEVO_loader_path[MAX_FAT_PATH];
|
|
snprintf(DEVO_loader_path, sizeof(DEVO_loader_path), "%sloader.bin", Settings.DEVOLoaderPath);
|
|
FILE *f = fopen(DEVO_loader_path, "rb");
|
|
if (f)
|
|
{
|
|
fseek(f, 0, SEEK_END);
|
|
u32 size = ftell(f);
|
|
rewind(f);
|
|
loader_bin = (u8 *)MEM2_alloc(size);
|
|
if (!loader_bin)
|
|
{
|
|
fclose(f);
|
|
WindowPrompt(tr("Error:"), tr("Devolution's loader.bin file can't be loaded."), tr("OK"));
|
|
return -1;
|
|
}
|
|
fread(loader_bin, 1, size, f);
|
|
|
|
// Read Devolution version
|
|
char version[5];
|
|
fseek(f, 23, SEEK_SET);
|
|
fread(version, 1, 4, f);
|
|
char *ptr = strchr(version, ' ');
|
|
if (ptr)
|
|
*ptr = 0;
|
|
else
|
|
version[4] = 0;
|
|
DEVO_version = atoi(version);
|
|
|
|
fclose(f);
|
|
}
|
|
else
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To run GameCube games with Devolution you need the loader.bin file in your Devolution Loader Path."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Devolution config
|
|
DEVO_CFG *devo_config = (DEVO_CFG *)0x80000020;
|
|
|
|
char disc1[MAX_FAT_PATH];
|
|
char disc2[MAX_FAT_PATH];
|
|
bool multiDisc = false;
|
|
char DEVO_memCard[MAX_FAT_PATH];
|
|
snprintf(disc1, sizeof(disc1), "%s", RealPath);
|
|
|
|
snprintf(disc2, sizeof(disc2), "%s", RealPath);
|
|
char *pathPtr = strrchr(disc2, '/');
|
|
if (pathPtr)
|
|
*pathPtr = 0;
|
|
snprintf(disc2 + strlen(disc2), sizeof(disc2) - strlen(disc2), "/disc2.iso");
|
|
if (CheckFile(disc2))
|
|
multiDisc = true;
|
|
|
|
snprintf(DEVO_memCard, sizeof(DEVO_memCard), "%s", RealPath); // Set memory card folder to Disc1 folder
|
|
char *ptr = strrchr(DEVO_memCard, '/');
|
|
if (ptr)
|
|
*ptr = 0;
|
|
|
|
// Make sure the directory exists
|
|
char devoPath[20];
|
|
snprintf(devoPath, sizeof(devoPath), "%s:/apps/gc_devo", DeviceHandler::GetDevicePrefix(RealPath));
|
|
CreateSubfolder(devoPath);
|
|
|
|
// Get the starting cluster (and device ID) for the ISO file 1
|
|
struct stat st1;
|
|
stat(disc1, &st1);
|
|
|
|
// Get the starting cluster for the ISO file 2
|
|
struct stat st2;
|
|
if (multiDisc)
|
|
stat(disc2, &st2);
|
|
|
|
// setup Devolution
|
|
memset(devo_config, 0, sizeof(*devo_config));
|
|
devo_config->signature = DEVO_SIG;
|
|
devo_config->version = DEVO_CONFIG_VERSION;
|
|
devo_config->device_signature = st1.st_dev == 'WISD' ? 'SD' : 'SB'; // Set device type.
|
|
devo_config->disc1_cluster = st1.st_ino; // set starting cluster for first disc ISO file
|
|
if (multiDisc)
|
|
devo_config->disc2_cluster = st2.st_ino; // set starting cluster for second disc ISO file
|
|
|
|
// Devolution configs
|
|
// use wifi logging if USB gecko is not found in slot B
|
|
// devo_config->options |= DEVO_CFG_WIFILOG; // removed on Tueidj request
|
|
if (devoWidescreenChoice && DEVO_version >= 188)
|
|
devo_config->options |= DEVO_CFG_WIDE;
|
|
if (!devoActivityLEDChoice && DEVO_version >= 142)
|
|
devo_config->options |= DEVO_CFG_NOLED; // ON by default
|
|
if (devoFZeroAXChoice && DEVO_version >= 196)
|
|
devo_config->options |= DEVO_CFG_FZERO_AX;
|
|
if (devoTimerFixChoice && DEVO_version >= 196)
|
|
devo_config->options |= DEVO_CFG_TIMER_FIX;
|
|
if (devoDButtonsChoice && DEVO_version >= 200)
|
|
devo_config->options |= DEVO_CFG_D_BUTTONS;
|
|
if (devoCropOverscanChoice && DEVO_version >= 234)
|
|
devo_config->options |= DEVO_CFG_CROP_OVERSCAN;
|
|
if (devoDiscDelayChoice && DEVO_version >= 234)
|
|
devo_config->options |= DEVO_CFG_DISC_DELAY;
|
|
// devo_config->options |= DEVO_CFG_PLAYLOG; // Playlog setting managed by USB Loader GX features menu
|
|
|
|
if (devoProgressivePatch && DEVO_version >= 266)
|
|
{
|
|
devo_config->options |= DEVO_CFG_FORCE_480P;
|
|
devo_config->options |= DEVO_CFG_FORCE_576P;
|
|
}
|
|
|
|
// check memory card
|
|
if (devoMCEmulation == DEVO_MC_OFF)
|
|
{
|
|
devo_config->memcard_cluster = 0;
|
|
snprintf(DEVO_memCard, sizeof(DEVO_memCard), "Original");
|
|
}
|
|
else
|
|
{
|
|
if (devoMCEmulation == DEVO_MC_INDIVIDUAL)
|
|
{
|
|
snprintf(DEVO_memCard + strlen(DEVO_memCard), sizeof(DEVO_memCard) - strlen(DEVO_memCard), "/memcard_%.6s.bin", (const char *)gameHdr->id);
|
|
}
|
|
else if (devoMCEmulation == DEVO_MC_REGIONAL)
|
|
{
|
|
snprintf(DEVO_memCard, sizeof(DEVO_memCard), "%s:/apps/gc_devo/memcard_%c.bin", DeviceHandler::GetDevicePrefix(RealPath), gameHdr->id[3]);
|
|
}
|
|
else // same for all games
|
|
{
|
|
snprintf(DEVO_memCard, sizeof(DEVO_memCard), "%s:/apps/gc_devo/memcard.bin", DeviceHandler::GetDevicePrefix(RealPath));
|
|
}
|
|
|
|
// check if file doesn't exist or is less than 512KB (59 Blocks)
|
|
struct stat st;
|
|
if (stat(DEVO_memCard, &st) == -1 || st.st_size < 1 << 19)
|
|
{
|
|
// need to enlarge or create it
|
|
FILE *f = fopen(DEVO_memCard, "wb");
|
|
if (f)
|
|
{
|
|
// make it 16MB
|
|
ShowProgress(tr("Please wait..."), 0, 0);
|
|
gprintf("Resizing memcard file...\n");
|
|
fseek(f, (16 << 20) - 1, SEEK_SET);
|
|
fputc(0, f);
|
|
fclose(f);
|
|
if (stat(DEVO_memCard, &st) == -1 || st.st_size < 1 << 19)
|
|
{
|
|
// it still isn't big enough. Give up.
|
|
st.st_ino = 0;
|
|
}
|
|
ProgressStop();
|
|
}
|
|
else
|
|
{
|
|
// couldn't open or create the memory card file
|
|
st.st_ino = 0;
|
|
}
|
|
}
|
|
devo_config->memcard_cluster = st.st_ino;
|
|
}
|
|
|
|
// read 32 bytes of disc 1 to the start of MEM1
|
|
FILE *iso_file = fopen(disc1, "rb");
|
|
if (!iso_file)
|
|
{
|
|
MEM2_free(loader_bin);
|
|
WindowPrompt(tr("Error:"), tr("File not found."), tr("OK"));
|
|
return -1;
|
|
}
|
|
u8 *lowmem = (u8 *)0x80000000;
|
|
fread(lowmem, 1, 32, iso_file);
|
|
fclose(iso_file);
|
|
|
|
// setup video mode
|
|
Disc_SelectVMode(0, true, NULL, NULL);
|
|
Disc_SetVMode();
|
|
|
|
// Set sram flags
|
|
PatchSram(languageChoice, false, false);
|
|
|
|
// flush disc ID and Devolution config out to memory
|
|
DCFlushRange(lowmem, 64);
|
|
|
|
ExitApp();
|
|
IosLoader::ReloadIosKeepingRights(58); // reload IOS 58 with AHBPROT rights
|
|
|
|
gprintf("DEVO: Loading game: %s\n", disc1);
|
|
gprintf("DEVO: Memory Card: %s\n\n", DEVO_memCard);
|
|
gprintf("%.72s", (const char *)loader_bin + 4);
|
|
|
|
if (returnToChoice)
|
|
{
|
|
loadStub();
|
|
Set_Stub(returnToChoice);
|
|
}
|
|
|
|
u32 cpu_isr;
|
|
SYS_ResetSystem(SYS_SHUTDOWN, 0, 0);
|
|
_CPU_ISR_Disable(cpu_isr);
|
|
__exception_closeall();
|
|
LAUNCH_DEVO();
|
|
_CPU_ISR_Restore(cpu_isr);
|
|
return 0;
|
|
}
|
|
|
|
int GameBooter::BootNintendont(struct discHdr *gameHdr)
|
|
{
|
|
char RealPath[MAX_FAT_PATH];
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC)
|
|
snprintf(RealPath, sizeof(RealPath), "di");
|
|
else
|
|
snprintf(RealPath, sizeof(RealPath), "%s", GCGames::Instance()->GetPath((const char *)gameHdr->id));
|
|
|
|
const char *LoaderName = "Nintendont";
|
|
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHdr->id);
|
|
s8 languageChoice = game_cfg->language == INHERIT ? Settings.language - 1 : game_cfg->language;
|
|
u8 ocarinaChoice = game_cfg->ocarina == INHERIT ? Settings.ocarina : game_cfg->ocarina;
|
|
if (ocarinaAnswer >= OCARINA_OFF)
|
|
ocarinaChoice = ocarinaAnswer;
|
|
u8 multiDiscChoice = Settings.MultiDiscPrompt;
|
|
u8 ninVideoChoice = game_cfg->DMLVideo == INHERIT ? Settings.DMLVideo : game_cfg->DMLVideo;
|
|
u8 ninProgressivePatch = game_cfg->DMLProgPatch == INHERIT ? Settings.DMLProgPatch : game_cfg->DMLProgPatch;
|
|
u8 ninDeflickerChoice = game_cfg->NINDeflicker == INHERIT ? Settings.NINDeflicker : game_cfg->NINDeflicker;
|
|
u8 ninWidescreenChoice = game_cfg->DMLWidescreen == INHERIT ? Settings.DMLWidescreen : game_cfg->DMLWidescreen;
|
|
u8 ninMCEmulationChoice = game_cfg->NINMCEmulation == INHERIT ? Settings.NINMCEmulation : game_cfg->NINMCEmulation;
|
|
u8 ninMCSizeChoice = game_cfg->NINMCSize == INHERIT ? Settings.NINMCSize : game_cfg->NINMCSize;
|
|
u8 ninAutobootChoice = Settings.NINAutoboot;
|
|
u8 ninSettingsChoice = Settings.NINSettings;
|
|
u8 ninUSBHIDChoice = game_cfg->NINUSBHID == INHERIT ? Settings.NINUSBHID : game_cfg->NINUSBHID;
|
|
u8 ninMaxPadsChoice = game_cfg->NINMaxPads == INHERIT ? Settings.NINMaxPads : game_cfg->NINMaxPads;
|
|
u8 ninNativeSIChoice = game_cfg->NINNativeSI == INHERIT ? Settings.NINNativeSI : game_cfg->NINNativeSI;
|
|
u8 ninWiiUWideChoice = game_cfg->NINWiiUWide == INHERIT ? Settings.NINWiiUWide : game_cfg->NINWiiUWide;
|
|
u8 ninLEDChoice = game_cfg->NINLED == INHERIT ? Settings.NINLED : game_cfg->NINLED;
|
|
u8 ninDebugChoice = game_cfg->DMLDebug == INHERIT ? Settings.DMLDebug : game_cfg->DMLDebug;
|
|
u8 ninOSReportChoice = game_cfg->NINOSReport == INHERIT ? Settings.NINOSReport : game_cfg->NINOSReport;
|
|
u8 ninLogChoice = game_cfg->NINLog == INHERIT ? Settings.NINLog : game_cfg->NINLog;
|
|
u8 ninVideoScale = game_cfg->NINVideoScale == INHERIT ? Settings.NINVideoScale : game_cfg->NINVideoScale;
|
|
u8 ninVideoOffset = game_cfg->NINVideoOffset == INHERIT - 20 ? Settings.NINVideoOffset : game_cfg->NINVideoOffset;
|
|
u8 ninPal50PatchChoice = game_cfg->NINPal50Patch == INHERIT ? Settings.NINPal50Patch : game_cfg->NINPal50Patch;
|
|
u8 ninRemlimitChoice = game_cfg->NINRemlimit == INHERIT ? Settings.NINRemlimit : game_cfg->NINRemlimit;
|
|
u8 ninArcadeModeChoice = game_cfg->NINArcadeMode == INHERIT ? Settings.NINArcadeMode : game_cfg->NINArcadeMode;
|
|
u8 ninCCRumbleChoice = game_cfg->NINCCRumble == INHERIT ? Settings.NINCCRumble : game_cfg->NINCCRumble;
|
|
u8 ninSkipIPLChoice = game_cfg->NINSkipIPL == INHERIT ? Settings.NINSkipIPL : game_cfg->NINSkipIPL;
|
|
u8 ninBBAChoice = game_cfg->NINBBA == INHERIT ? Settings.NINBBA : game_cfg->NINBBA;
|
|
u8 ninBBAProfileChoice = game_cfg->NINBBAProfile == INHERIT ? Settings.NINBBAProfile : game_cfg->NINBBAProfile;
|
|
u8 ninGamepadChoice = game_cfg->NINWiiUGamepadSlot == INHERIT ? Settings.NINWiiUGamepadSlot : game_cfg->NINWiiUGamepadSlot;
|
|
|
|
const char *ninLoaderPath = game_cfg->NINLoaderPath.size() == 0 ? Settings.NINLoaderPath : game_cfg->NINLoaderPath.c_str();
|
|
|
|
if (!CheckAHBPROT())
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("%s requires AHB access! Please launch USB Loader GX from HBC or from an updated channel or forwarder."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check if Nintendont boot.dol is available
|
|
char NIN_loader_path[MAX_FAT_PATH];
|
|
if (strncmp(RealPath, "usb", 3) == 0) // Nintendont r39 only
|
|
{
|
|
snprintf(NIN_loader_path, sizeof(NIN_loader_path), "%sloaderusb.dol", ninLoaderPath);
|
|
if (!CheckFile(NIN_loader_path))
|
|
snprintf(NIN_loader_path, sizeof(NIN_loader_path), "%sbootusb.dol", ninLoaderPath);
|
|
}
|
|
if (strncmp(RealPath, "sd", 2) == 0 || !CheckFile(NIN_loader_path))
|
|
{
|
|
snprintf(NIN_loader_path, sizeof(NIN_loader_path), "%sloader.dol", ninLoaderPath);
|
|
if (!CheckFile(NIN_loader_path))
|
|
snprintf(NIN_loader_path, sizeof(NIN_loader_path), "%sboot.dol", ninLoaderPath);
|
|
}
|
|
if (!CheckFile(NIN_loader_path))
|
|
{
|
|
// Nintendont boot.dol not found
|
|
WindowPrompt(tr("Error:"), tr("To run GameCube games with Nintendont you need the boot.dol file in your Nintendont Loader Path."), tr("OK"));
|
|
return -1;
|
|
}
|
|
gprintf("NIN: Loader path = %s \n", NIN_loader_path);
|
|
gprintf("NIN: Game path = %s \n", RealPath);
|
|
|
|
// Check Nintendont version
|
|
u32 NIN_cfg_version = NIN_CFG_VERSION;
|
|
char NINVersion[7] = "";
|
|
u32 NINRev = 0;
|
|
bool NINArgsboot = false;
|
|
NINRev = nintendontVersion(Settings.NINLoaderPath, NINVersion, sizeof(NINVersion));
|
|
if (NINRev > 0) // Version available since 3.324
|
|
{
|
|
gprintf("NIN: Nintendont revision = %d \n", NINRev);
|
|
NINArgsboot = true; // no need to check argsboot string, 3.324+ supports it.
|
|
}
|
|
else
|
|
{
|
|
char NINBuildDate[21] = "";
|
|
if (nintendontBuildDate(Settings.NINLoaderPath, NINBuildDate))
|
|
{
|
|
// Current build date
|
|
struct tm time;
|
|
strptime(NINBuildDate, "%b %d %Y %H:%M:%S", &time);
|
|
const time_t NINLoaderTime = mktime(&time);
|
|
|
|
// Alpha0.1
|
|
strptime("Sep 20 2013 15:27:01", "%b %d %Y %H:%M:%S", &time);
|
|
if (NINLoaderTime == mktime(&time))
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("USB Loader GX r1218 is required for Nintendont Alpha v0.1. Please update your Nintendont boot.dol version."), tr("Ok"));
|
|
return -1;
|
|
}
|
|
|
|
// r01 - r40
|
|
strptime("Mar 30 2014 12:33:44", "%b %d %Y %H:%M:%S", &time); // r42 - NIN_CFG_VERSION = 2
|
|
if (NINLoaderTime < mktime(&time))
|
|
{
|
|
gprintf("Nintendont r01 - r40 detected. Using CFG version 0x00000001\n");
|
|
NIN_cfg_version = 1;
|
|
|
|
strptime("Mar 29 2014 10:49:31", "%b %d %Y %H:%M:%S", &time); // r39
|
|
if (NINLoaderTime < mktime(&time) && strncmp(RealPath, "usb", 3) == 0)
|
|
{
|
|
if (WindowPrompt(tr("Warning:"), tr("This Nintendont version does not support games on USB."), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// v1.01 - v1.134
|
|
strptime("Aug 5 2014 22:38:21", "%b %d %Y %H:%M:%S", &time); // v1.135 - NIN_CFG_VERSION = 3
|
|
if (NINLoaderTime < mktime(&time) && NIN_cfg_version != 1)
|
|
{
|
|
gprintf("Nintendont v1.01 - v1.134 detected. Using CFG version 0x00000002\n");
|
|
NIN_cfg_version = 2;
|
|
// no need to fake NIN_CFG struct size, the size is checked in nintendont only since v1.143
|
|
}
|
|
else if (NINLoaderTime >= mktime(&time))
|
|
NINRev = 135;
|
|
|
|
// v2.200 to 2.207
|
|
strptime("Nov 6 2014.17:33:30", "%b %d %Y %H:%M:%S", &time); // v1.208
|
|
if (ninAutobootChoice && NINLoaderTime < mktime(&time))
|
|
{
|
|
strptime("Oct 31 2014 21:14:47", "%b %d %Y %H:%M:%S", &time); // v1.200
|
|
if (NINLoaderTime >= mktime(&time))
|
|
{
|
|
WindowPrompt(tr("Warning:"), tr("This Nintendont version is not correctly supported. Auto boot disabled."), tr("Ok"));
|
|
ninAutobootChoice = OFF;
|
|
}
|
|
}
|
|
|
|
// v2.259 - disc support
|
|
strptime("Dec 23 2014 17:28:56", "%b %d %Y %H:%M:%S", &time); // v1.259
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC && NINLoaderTime < mktime(&time))
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To run GameCube games from disc you need to set the GameCube mode to MIOS in the game settings."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// v3.304 - Controller.ini is now optional
|
|
strptime("Feb 23 2015 05:32:16", "%b %d %Y %H:%M:%S", &time); // v3.304
|
|
if (NINLoaderTime >= mktime(&time))
|
|
{
|
|
NINRev = 304;
|
|
}
|
|
|
|
// checks argsboot
|
|
if (ninAutobootChoice)
|
|
{
|
|
u8 *buffer = NULL;
|
|
u32 filesize = 0;
|
|
if (LoadFileToMem(NIN_loader_path, &buffer, &filesize))
|
|
{
|
|
for (u32 i = 0; i < filesize; i += 0x10)
|
|
{
|
|
if ((*(u32 *)(buffer + i)) == 'args' && (*(u32 *)(buffer + i + 4)) == 'boot')
|
|
{
|
|
gprintf("NIN: argsboot found at %08x, using arguments instead of Nincfg.bin\n", i);
|
|
NINArgsboot = true;
|
|
break;
|
|
}
|
|
}
|
|
free(buffer);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int choice = WindowPrompt(tr("Warning:"), tr("USB Loader GX couldn't verify Nintendont boot.dol file. Launch this boot.dol anyway?"), tr("Yes"), tr("Cancel"));
|
|
if (choice == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// needed since v3.354 CFG v4 to still work with old CFG version
|
|
if (NINRev >= 135 && NINRev < 354)
|
|
NIN_cfg_version = 3;
|
|
else if (NINRev >= 354 && NINRev < 358)
|
|
NIN_cfg_version = 4;
|
|
else if (NINRev >= 358 && NINRev < 368)
|
|
NIN_cfg_version = 5;
|
|
else if (NINRev >= 368 && NINRev < 424)
|
|
NIN_cfg_version = 6;
|
|
else if (NINRev >= 424 && NINRev < 431)
|
|
NIN_cfg_version = 7;
|
|
else if (NINRev >= 431 && NINRev < 487)
|
|
NIN_cfg_version = 8;
|
|
else if (NINRev >= 487 && NINRev < 493)
|
|
NIN_cfg_version = 9;
|
|
else if (NINRev >= 493)
|
|
NIN_cfg_version = 10;
|
|
|
|
// Check USB device
|
|
if (gameHdr->type != TYPE_GAME_GC_DISC && strncmp(RealPath, "usb", 3) == 0)
|
|
{
|
|
// Check Main GameCube Path location
|
|
if (strncmp(DeviceHandler::PathToFSName(Settings.GameCubePath), "FAT", 3) != 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' to an USB FAT32 partition."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check the partition type
|
|
int USB_partNum = DeviceHandler::PathToDriveType(Settings.GameCubePath) - USB1; // Get partition number across all mounted device
|
|
int USBport_partNum = DeviceHandler::PartitionToPortPartition(USB_partNum); // Get partition position from corresponding USB port
|
|
PartitionHandle *usbHandle = DeviceHandler::Instance()->GetUSBHandleFromPartition(USB_partNum); // Open a handle on used USB port
|
|
|
|
// GPT and EBR 0x0F support added on v3.400, primary type was required on old version.
|
|
if (NINRev < 400 && usbHandle->GetPartitionTableType(USBport_partNum) != MBR)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' on the first primary FAT32 partition."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Extended type EBR 0x05 was added in 4.406, only type 0x0F was working from 400 to 405
|
|
if (NINRev > 400 && NINRev < 406 && usbHandle->GetPartitionTableType(USBport_partNum) == EBR && usbHandle->GetPartitionType(USBport_partNum) != 0x0F)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("Your current GameCube partition is not compatible. Please update Nintendont."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// check if the partition is the first FAT32 of the drive. ExFAT was added to nintendont 4.x but USB Loader GX can't list games so no need to check that format.
|
|
bool found = false;
|
|
for (int partition = 0; partition <= USBport_partNum; partition++)
|
|
{
|
|
if (strncmp(usbHandle->GetFSName(partition), "FAT", 3) != 0)
|
|
continue;
|
|
|
|
if (partition == USBport_partNum)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
WindowPrompt(tr("Error:"), fmt(tr("To run GameCube games with %s you need to set your 'Main GameCube Path' on the first primary FAT32 partition."), LoaderName), tr("OK"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Set used device when launching game from disc
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC)
|
|
{
|
|
if (Settings.GameCubeSource >= GC_SOURCE_AUTO && strncmp(Settings.GameCubePath, "usb", 3) == 0)
|
|
{
|
|
if (WindowPrompt("", tr("Which device do you want to use for Nintendont files?"), tr("SD"), tr("USB")) == 1)
|
|
snprintf(RealPath, sizeof(RealPath), "%s:/", DeviceHandler::GetDevicePrefix(Settings.GameCubeSDPath));
|
|
else
|
|
snprintf(RealPath, sizeof(RealPath), "%s:/", DeviceHandler::GetDevicePrefix(Settings.GameCubePath));
|
|
}
|
|
else if (Settings.GameCubeSource == GC_SOURCE_MAIN)
|
|
{
|
|
snprintf(RealPath, sizeof(RealPath), "%s:/", DeviceHandler::GetDevicePrefix(Settings.GameCubePath));
|
|
}
|
|
else
|
|
snprintf(RealPath, sizeof(RealPath), "%s:/", DeviceHandler::GetDevicePrefix(Settings.GameCubeSDPath));
|
|
}
|
|
|
|
// Check Ocarina and cheat file location. the .gct file need to be located on the same partition than the game.
|
|
if (ocarinaChoice && strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) != 0)
|
|
{
|
|
char path[MAX_FAT_PATH], destPath[MAX_FAT_PATH];
|
|
int res = -1;
|
|
snprintf(path, sizeof(path), "%s%.6s.gct", Settings.Cheatcodespath, (char *)gameHdr->id);
|
|
snprintf(destPath, sizeof(destPath), "%s:/NINTemp.gct", DeviceHandler::GetDevicePrefix(RealPath));
|
|
|
|
gprintf("NIN: Copying %s to %s \n", path, destPath);
|
|
res = CopyFile(path, destPath);
|
|
if (res < 0)
|
|
{
|
|
gprintf("NIN: Couldn't copy the file. ret %d. Ocarina Disabled\n", res);
|
|
RemoveFile(destPath);
|
|
ocarinaChoice = false;
|
|
}
|
|
}
|
|
|
|
// Check kenobiwii.bin
|
|
if (NINRev < 336 && (ocarinaChoice || (ninDebugChoice && !isWiiU())))
|
|
{
|
|
char kenobiwii_path[30];
|
|
snprintf(kenobiwii_path, sizeof(kenobiwii_path), "%s:/sneek/kenobiwii.bin", DeviceHandler::GetDevicePrefix(RealPath));
|
|
if (!CheckFile(kenobiwii_path))
|
|
{
|
|
// try to copy kenobiwii from the other device
|
|
if (strcmp(Settings.GameCubePath, Settings.GameCubeSDPath) != 0)
|
|
{
|
|
char kenobiwii_srcpath[30];
|
|
|
|
snprintf(kenobiwii_srcpath, sizeof(kenobiwii_srcpath), "%s:/sneek/kenobiwii.bin", strncmp(RealPath, "usb", 3) == 0 ? "sd" : DeviceHandler::GetDevicePrefix(Settings.GameCubePath));
|
|
gprintf("kenobiwii source path = %s \n", kenobiwii_srcpath);
|
|
if (CheckFile(kenobiwii_srcpath))
|
|
{
|
|
if (CopyFile(kenobiwii_srcpath, kenobiwii_path) < 0)
|
|
{
|
|
gprintf("NIN: Couldn't copy %s to %s.\n", kenobiwii_srcpath, kenobiwii_path);
|
|
RemoveFile(kenobiwii_path);
|
|
if (WindowPrompt(tr("Warning:"), fmt(tr("To use Ocarina with %s you need the %s file."), LoaderName, kenobiwii_path), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gprintf("kenobiwii source path = %s Not found.\n", kenobiwii_srcpath);
|
|
if (WindowPrompt(tr("Warning:"), fmt(tr("To use Ocarina with %s you need the %s file."), LoaderName, kenobiwii_path), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gprintf("kenobiwii path = %s Not found.\n", kenobiwii_path);
|
|
if (WindowPrompt(tr("Warning:"), fmt(tr("To use Ocarina with %s you need the %s file."), LoaderName, kenobiwii_path), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check controller.ini
|
|
if (ninUSBHIDChoice)
|
|
{
|
|
// Check controller.ini file in priority, then controllers folder, for compatibility with older nintendont versions.
|
|
char controllerini_path[30];
|
|
snprintf(controllerini_path, sizeof(controllerini_path), "%s:/controller.ini", DeviceHandler::GetDevicePrefix(RealPath));
|
|
if (!CheckFile(controllerini_path) && strcmp(Settings.GameCubePath, Settings.GameCubeSDPath) != 0)
|
|
{
|
|
// try to copy controller.ini from the other device
|
|
char controllerini_srcpath[30];
|
|
snprintf(controllerini_srcpath, sizeof(controllerini_srcpath), "%s:/controller.ini", strncmp(RealPath, "usb", 3) == 0 ? "sd" : DeviceHandler::GetDevicePrefix(Settings.GameCubePath));
|
|
gprintf("Controller.ini source path = %s \n", controllerini_srcpath);
|
|
if (CheckFile(controllerini_srcpath))
|
|
{
|
|
if (CopyFile(controllerini_srcpath, controllerini_path) < 0)
|
|
{
|
|
gprintf("NIN: Couldn't copy %s to %s.\n", controllerini_srcpath, controllerini_path);
|
|
RemoveFile(controllerini_path);
|
|
if (NINRev < 304) // HID is always enabled and controller.ini optional since r304
|
|
{
|
|
if (WindowPrompt(tr("Warning:"), fmt(tr("To use HID with %s you need the %s file."), LoaderName, controllerini_path), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
else // check controllers folder if no controller.ini found on root.
|
|
{
|
|
|
|
// Check gamepath:/controllers/ folder
|
|
snprintf(controllerini_path, sizeof(controllerini_path), "%s:/controllers/", DeviceHandler::GetDevicePrefix(RealPath));
|
|
if (!CheckFile(controllerini_path) && strcmp(Settings.GameCubePath, Settings.GameCubeSDPath) != 0)
|
|
{
|
|
// try to copy controllers folder from the other device
|
|
char controllerini_srcpath[30];
|
|
snprintf(controllerini_srcpath, sizeof(controllerini_srcpath), "%s:/controllers/", strncmp(RealPath, "usb", 3) == 0 ? "sd" : DeviceHandler::GetDevicePrefix(Settings.GameCubePath));
|
|
gprintf("Controllers folder source path = %s \n", controllerini_srcpath);
|
|
if (CheckFile(controllerini_srcpath))
|
|
{
|
|
if (CopyDirectory(controllerini_srcpath, controllerini_path) < 0)
|
|
{
|
|
gprintf("NIN: Couldn't copy %s to %s.\n", controllerini_srcpath, controllerini_path);
|
|
RemoveDirectory(controllerini_path);
|
|
}
|
|
}
|
|
else if (NINRev < 304)
|
|
{
|
|
snprintf(controllerini_path, sizeof(controllerini_path), "%s:/controller.ini", DeviceHandler::GetDevicePrefix(RealPath));
|
|
if (WindowPrompt(tr("Warning:"), fmt(tr("To use HID with %s you need the %s file."), LoaderName, controllerini_path), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if game has multi Discs
|
|
bool bootDisc2 = false;
|
|
if (multiDiscChoice && gameHdr->type != TYPE_GAME_GC_DISC && gameHdr->disc_no == 0)
|
|
{
|
|
char disc2Path[MAX_FAT_PATH];
|
|
snprintf(disc2Path, sizeof(disc2Path), "%s", RealPath);
|
|
char *pathPtr = strrchr(disc2Path, '/');
|
|
if (pathPtr)
|
|
*pathPtr = 0;
|
|
snprintf(disc2Path + strlen(disc2Path), sizeof(disc2Path) - strlen(disc2Path), "/disc2.iso");
|
|
if (CheckFile(disc2Path))
|
|
{
|
|
int choice = WindowPrompt(gameHdr->title, tr("This game has multiple discs. Please select the disc to launch."), tr("Disc 1"), tr("Disc 2"), tr("Cancel"));
|
|
if (choice == 0)
|
|
return -1;
|
|
else if (choice == 2)
|
|
bootDisc2 = true;
|
|
}
|
|
}
|
|
const char *gcPath = strchr(RealPath, '/');
|
|
if (!gcPath)
|
|
gcPath = "";
|
|
|
|
char gamePath[MAX_FAT_PATH];
|
|
snprintf(gamePath, sizeof(gamePath), "%s", gcPath);
|
|
|
|
if (bootDisc2)
|
|
{
|
|
char *pathPtr = strrchr(gamePath, '/');
|
|
if (pathPtr)
|
|
*pathPtr = 0;
|
|
snprintf(gamePath + strlen(gamePath), sizeof(gamePath) - strlen(gamePath), "/disc2.iso");
|
|
}
|
|
|
|
if (gameHdr->type == TYPE_GAME_GC_DISC)
|
|
{
|
|
snprintf(gamePath, sizeof(gamePath), "di");
|
|
}
|
|
|
|
// Nintendont Config file settings
|
|
NIN_CFG *nin_config = NULL;
|
|
nin_config = (NIN_CFG *)MEM2_alloc(sizeof(NIN_CFG));
|
|
if (!nin_config)
|
|
{
|
|
gprintf("Not enough memory to create nincfg.bin file.\n");
|
|
WindowPrompt(tr("Error:"), tr("Could not write file."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
memset(nin_config, 0, sizeof(NIN_CFG));
|
|
|
|
// Magic and CFG_Version for Nintendont
|
|
nin_config->Magicbytes = NIN_MAGIC;
|
|
nin_config->Version = NIN_cfg_version;
|
|
|
|
// Game path
|
|
strncpy(nin_config->GamePath, gamePath, sizeof(nin_config->GamePath));
|
|
|
|
// setup cheat and path
|
|
if (ocarinaChoice)
|
|
{
|
|
// Check if the .gct folder is on the same partition than the game, if not load the temporary .gct file.
|
|
if (strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) == 0)
|
|
{
|
|
const char *CheatPath = strchr(Settings.Cheatcodespath, '/');
|
|
if (!CheatPath)
|
|
CheatPath = "";
|
|
snprintf(nin_config->CheatPath, sizeof(nin_config->CheatPath), "%s%.6s.gct", CheatPath, (char *)gameHdr->id);
|
|
}
|
|
else
|
|
{
|
|
snprintf(nin_config->CheatPath, sizeof(nin_config->CheatPath), "/NINTemp.gct");
|
|
}
|
|
|
|
nin_config->Config |= NIN_CFG_CHEATS | NIN_CFG_CHEAT_PATH;
|
|
gprintf("NIN: Loading cheat %s\n", nin_config->CheatPath);
|
|
}
|
|
|
|
// Set other settings
|
|
if (ninDebugChoice && !isWiiU()) // only on Wii
|
|
nin_config->Config |= ninDebugChoice == ON ? NIN_CFG_DEBUGGER : NIN_CFG_DEBUGGER | NIN_CFG_DEBUGWAIT;
|
|
if (ninMCEmulationChoice)
|
|
nin_config->Config |= NIN_CFG_MEMCARDEMU;
|
|
if (ninWidescreenChoice)
|
|
nin_config->Config |= NIN_CFG_FORCE_WIDE;
|
|
if (ninProgressivePatch)
|
|
nin_config->Config |= NIN_CFG_FORCE_PROG;
|
|
if (ninAutobootChoice)
|
|
nin_config->Config |= NIN_CFG_AUTO_BOOT;
|
|
if (ninUSBHIDChoice)
|
|
nin_config->Config |= NIN_CFG_HID; // auto enabled by nintendont v2.152 and less on vWii
|
|
if (ninOSReportChoice)
|
|
nin_config->Config |= NIN_CFG_OSREPORT;
|
|
if (strncmp(RealPath, "usb", 3) == 0)
|
|
nin_config->Config |= NIN_CFG_USB; // r40+
|
|
if (ninLEDChoice)
|
|
nin_config->Config |= NIN_CFG_LED; // r45+
|
|
if (ninLogChoice)
|
|
nin_config->Config |= NIN_CFG_LOG; // v1.109+
|
|
if (ninMCEmulationChoice == NIN_MC_MULTI)
|
|
nin_config->Config |= NIN_CFG_MC_MULTI; // v1.135+
|
|
if (ninNativeSIChoice)
|
|
nin_config->Config |= NIN_CFG_NATIVE_SI; // v2.189+
|
|
if (ninWiiUWideChoice)
|
|
nin_config->Config |= NIN_CFG_WIIU_WIDE; // v2.258+
|
|
if (ninArcadeModeChoice)
|
|
nin_config->Config |= NIN_CFG_ARCADE_MODE; // v4.424+ Triforce Arcade Mode
|
|
if (ninCCRumbleChoice)
|
|
nin_config->Config |= NIN_CFG_CC_RUMBLE; // v4.431+ Classic Controller Rumble
|
|
if (ninSkipIPLChoice)
|
|
nin_config->Config |= NIN_CFG_SKIP_IPL; // v4.435+ Skip Gamecube BIOS
|
|
|
|
// Max Pads
|
|
nin_config->MaxPads = ninMaxPadsChoice; // NIN_CFG_VERSION 2 r42
|
|
|
|
// GameID for MCEmu
|
|
memcpy(&nin_config->GameID, gameHdr->id, 4); // NIN_CFG_VERSION 2 r83
|
|
|
|
// GameID for Video mode DiscDefault
|
|
memcpy((u8 *)Disc_ID, gameHdr->id, 6);
|
|
DCFlushRange((u8 *)Disc_ID, 6);
|
|
|
|
// Memory Card Emulation Blocs size with NIN_CFG v3
|
|
if (NIN_cfg_version == 3)
|
|
nin_config->MemCardBlocks = ninMCSizeChoice; // NIN_CFG_VERSION 3 v1.135
|
|
// Memory Card Emulation Blocs size + Aspect ratio with NIN_CFG v4
|
|
if (NIN_cfg_version >= 4)
|
|
{
|
|
nin_config->MemCardBlocksV4 = ninMCSizeChoice; // NIN_CFG_VERSION 4 v3.354
|
|
nin_config->VideoScale = ninVideoScale; // v3.354+
|
|
nin_config->VideoOffset = ninVideoOffset; // v3.354+
|
|
}
|
|
|
|
// Remove data read speed limiter
|
|
if (NIN_cfg_version >= 5 && ninRemlimitChoice)
|
|
nin_config->Config |= NIN_CFG_REMLIMIT;
|
|
|
|
// BBA emulation
|
|
if (NIN_cfg_version >= 9 && ninBBAChoice)
|
|
nin_config->Config |= NIN_CFG_BBA_EMU; // v6.487+
|
|
|
|
// BBA network profile
|
|
if (NIN_cfg_version >= 9 && ninBBAChoice && !isWiiU())
|
|
nin_config->NetworkProfile = ninBBAProfileChoice; // v6.487+
|
|
|
|
// Wii U Gamepad Slot
|
|
if (NIN_cfg_version >= 10 && isWiiU())
|
|
nin_config->WiiUGamepadSlot = ninGamepadChoice; // v6.493+
|
|
|
|
// Setup Video Mode
|
|
if (ninVideoChoice == DML_VIDEO_NONE) // No video mode changes
|
|
{
|
|
nin_config->VideoMode = NIN_VID_NONE;
|
|
}
|
|
else
|
|
{
|
|
if (ninVideoChoice == DML_VIDEO_AUTO) // Auto select video mode
|
|
{
|
|
nin_config->VideoMode = NIN_VID_AUTO;
|
|
Disc_SelectVMode(VIDEO_MODE_DISCDEFAULT, false, NULL, &nin_config->VideoMode);
|
|
}
|
|
else // Force user choice
|
|
{
|
|
Disc_SelectVMode(ninVideoChoice - 1, false, NULL, &nin_config->VideoMode);
|
|
if (nin_config->VideoMode & NIN_VID_FORCE_MASK)
|
|
nin_config->VideoMode |= NIN_VID_FORCE;
|
|
|
|
if (ninDeflickerChoice)
|
|
nin_config->VideoMode |= NIN_VID_FORCE_DF; // v2.208+
|
|
|
|
if (ninPal50PatchChoice)
|
|
nin_config->VideoMode |= NIN_VID_PATCH_PAL50; // v3.368+
|
|
|
|
if (nin_config->VideoMode & NIN_VID_PROG)
|
|
nin_config->Config |= NIN_CFG_FORCE_PROG; // Set Force_PROG bit in Config
|
|
}
|
|
Disc_SetVMode();
|
|
}
|
|
|
|
if (ninProgressivePatch)
|
|
nin_config->VideoMode |= NIN_VID_PROG;
|
|
|
|
gprintf("NIN: Active device %s\n", nin_config->Config & NIN_CFG_USB ? "USB" : "SD");
|
|
gprintf("NIN: config 0x%08x\n", nin_config->Config);
|
|
gprintf("NIN: Video mode 0x%08x\n", nin_config->VideoMode);
|
|
|
|
// Set game language setting
|
|
if (languageChoice >= GC_ENGLISH && languageChoice <= GC_DUTCH)
|
|
{
|
|
nin_config->Language = languageChoice;
|
|
}
|
|
else // console default or other languages
|
|
{
|
|
nin_config->Language = NIN_LAN_AUTO; // Let Nintendont handle it
|
|
}
|
|
gprintf("NIN: Language 0x%08x \n", nin_config->Language);
|
|
|
|
// if WiiVC, force creation and use of nincfg.bin file to fix a nintendont bug if HID is connected before launching it.
|
|
if (isWiiVC)
|
|
{
|
|
ninSettingsChoice = ON;
|
|
NINArgsboot = OFF;
|
|
}
|
|
|
|
// Delete existing nincfg.bin files
|
|
if (ninSettingsChoice == OFF)
|
|
{
|
|
char NINCfgPath[17];
|
|
|
|
// Nintendont loader partition
|
|
snprintf(NINCfgPath, sizeof(NINCfgPath), "%s:/nincfg.bin", DeviceHandler::GetDevicePrefix(NIN_loader_path));
|
|
RemoveFile(NINCfgPath);
|
|
|
|
// game partition
|
|
if (strncmp(NINCfgPath, RealPath, 4) != 0)
|
|
{
|
|
snprintf(NINCfgPath, sizeof(NINCfgPath), "%s:/nincfg.bin", DeviceHandler::GetDevicePrefix(RealPath));
|
|
RemoveFile(NINCfgPath);
|
|
}
|
|
}
|
|
else if (ninSettingsChoice == ON || !NINArgsboot)
|
|
{
|
|
// Nintendont Config file path
|
|
char NINCfgPath[17];
|
|
snprintf(NINCfgPath, sizeof(NINCfgPath), "%s:/nincfg.bin", DeviceHandler::GetDevicePrefix(NIN_loader_path));
|
|
gprintf("NIN: Cfg path : %s \n", NINCfgPath);
|
|
|
|
// write config file to nintendont's partition root.
|
|
FILE *fp = fopen(NINCfgPath, "wb");
|
|
if (fp)
|
|
{
|
|
fwrite(nin_config, sizeof(char), sizeof(NIN_CFG), fp);
|
|
fclose(fp);
|
|
}
|
|
else
|
|
{
|
|
gprintf("Could not open NINCfgPath in write mode");
|
|
int choice = WindowPrompt(tr("Warning:"), tr("USB Loader GX couldn't write Nintendont config file. Launch Nintendont anyway?"), tr("Yes"), tr("Cancel"));
|
|
if (choice == 0)
|
|
{
|
|
MEM2_free(nin_config);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Copy Nintendont Config file to game path
|
|
if (strncmp(NINCfgPath, RealPath, 2) != 0)
|
|
{
|
|
char NINDestPath[17];
|
|
snprintf(NINDestPath, sizeof(NINDestPath), "%s:/nincfg.bin", DeviceHandler::GetDevicePrefix(RealPath));
|
|
gprintf("NIN: Copying %s to %s...", NINCfgPath, NINDestPath);
|
|
if (CopyFile(NINCfgPath, NINDestPath) < 0)
|
|
{
|
|
gprintf("\nError: Couldn't copy %s to %s.\n", NINCfgPath, NINDestPath);
|
|
RemoveFile(NINDestPath);
|
|
if (WindowPrompt(tr("Warning:"), tr("USB Loader GX couldn't write Nintendont config file. Launch Nintendont anyway?"), tr("Yes"), tr("Cancel")) == 0)
|
|
{
|
|
MEM2_free(nin_config);
|
|
return -1;
|
|
}
|
|
}
|
|
gprintf("done\n");
|
|
}
|
|
}
|
|
|
|
if (NINArgsboot)
|
|
{
|
|
// initialize homebrew and arguments
|
|
u8 *buffer = NULL;
|
|
u32 filesize = 0;
|
|
LoadFileToMem(NIN_loader_path, &buffer, &filesize);
|
|
if (!buffer)
|
|
{
|
|
MEM2_free(nin_config);
|
|
return -1;
|
|
}
|
|
FreeHomebrewBuffer();
|
|
CopyHomebrewMemory(buffer, 0, filesize);
|
|
|
|
AddBootArgument(NIN_loader_path);
|
|
AddBootArgument((char *)nin_config, sizeof(NIN_CFG));
|
|
|
|
MEM2_free(nin_config);
|
|
|
|
// Launch Nintendont
|
|
return !(BootHomebrewFromMem() < 0);
|
|
}
|
|
else
|
|
{
|
|
MEM2_free(nin_config);
|
|
// Launch Nintendont
|
|
return !(BootHomebrew(NIN_loader_path) < 0);
|
|
}
|
|
}
|
|
|
|
int GameBooter::BootNeek(struct discHdr *gameHdr)
|
|
{
|
|
struct discHdr gameHeader;
|
|
memcpy(&gameHeader, gameHdr, sizeof(struct discHdr));
|
|
|
|
GameCFG *game_cfg = GameSettings.GetGameCFG(gameHdr->id);
|
|
u8 ocarinaChoice = game_cfg->ocarina == INHERIT ? Settings.ocarina : game_cfg->ocarina;
|
|
if (ocarinaAnswer >= OCARINA_OFF)
|
|
ocarinaChoice = ocarinaAnswer;
|
|
u64 returnToChoice = game_cfg->returnTo;
|
|
const char *NandEmuPath = game_cfg->NandEmuPath.size() == 0 ? Settings.NandEmuChanPath : game_cfg->NandEmuPath.c_str();
|
|
bool autoboot = true;
|
|
bool NK2O_isInstalled = false;
|
|
char tempPath[100] = "";
|
|
int ret = -1;
|
|
|
|
// Check all settings first before loading kernel
|
|
|
|
// Check kernel.bin
|
|
int neekMode = neekIsNeek2o(NandEmuPath); // -1 = kernel.bin not found, 0 = neek, 1 = neek2o
|
|
if (neekMode == -1)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("Neek kernel file not found."), tr("OK"));
|
|
return -1;
|
|
}
|
|
if (neekMode == 0)
|
|
{
|
|
if (WindowPrompt(tr("Warning:"), tr("Current neek files are not neek2o. Game autoboot disabled."), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
autoboot = false;
|
|
}
|
|
|
|
// Set current EmuNAND path as default for neek2o.
|
|
if (neekMode == 1)
|
|
{
|
|
ret = neek2oSetNAND(NandEmuPath);
|
|
gprintf("NEEK: Setting EmuNAND in nandcfg.bin : %d \n", ret);
|
|
if (ret < 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("Neek NAND path selection failed."), tr("OK"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// check and prepare EmuNAND path for neek
|
|
char neekNandPath[256] = "";
|
|
neekPathFormat(neekNandPath, NandEmuPath, sizeof(neekNandPath));
|
|
|
|
// check if the NAND path is compatible with current neek mode.
|
|
if (neekMode == 0 && strlen(neekNandPath) > 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("You need neek2o to load EmuNAND from sub-folders."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check if EmuNAND path is on SD
|
|
if (neekMode == 1 && isWiiU() && strncmp(NandEmuPath, "sd", 2) == 0) // neek2o on SD is not supported with the vWii leaked version of neek2o. Users could use it on Wii too, but they should be using r96.
|
|
{
|
|
if (WindowPrompt(tr("Warning:"), tr("Neek2o does not support 'EmuNAND Channel Path' on SD! Please setup Uneek2o instead."), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
|
|
// check partition compatibility - TODO : confirm incompatibility with each check
|
|
|
|
// Check if EmuNAND partition is on USB devices
|
|
if (strncmp(NandEmuPath, "usb", 3) == 0)
|
|
{
|
|
// Todo: add uStealth'd HDD check here, might need neek version detection too.
|
|
|
|
// Check partition format // Assume SD is always FAT32
|
|
if (strncmp(DeviceHandler::PathToFSName(NandEmuPath), "FAT", 3) != 0)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To use neek you need to set your 'EmuNAND Channel Path' to a FAT32 partition."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check if the partition is the first primary partition on the drive - TODO : verify if it also needs to be the first partition of the drive.
|
|
bool found = false;
|
|
int USB_partNum = DeviceHandler::PathToDriveType(NandEmuPath) - USB1;
|
|
int USBport_partNum = DeviceHandler::PartitionToPortPartition(USB_partNum);
|
|
int usbport = DeviceHandler::PartitionToUSBPort(USB_partNum);
|
|
PartitionHandle *usbHandle = DeviceHandler::Instance()->GetUSBHandleFromPartition(USB_partNum);
|
|
for (int partition = 0; partition <= USBport_partNum; partition++)
|
|
{
|
|
if (usbHandle->GetPartitionTableType(partition) != MBR)
|
|
continue;
|
|
|
|
if (partition == USBport_partNum)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To use neek you need to set your 'EmuNAND Channel Path' on the first primary partition of the Hard Drive."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// Check HDD sector size. Only 512 bytes/sector is supported by neek?
|
|
if (neekMode == 0 && hdd_sector_size[usbport] != BYTES_PER_SECTOR) // neek2o supports 3TB+ HDD
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("To use neek you need to use a 512 bytes/sector Hard Drive."), tr("OK"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Set ocarina file.
|
|
if (ocarinaChoice)
|
|
{
|
|
if (WindowPrompt(tr("Warning:"), tr("Ocarina is not supported with neek2o yet. Launch game anyway?"), tr("Continue"), tr("Cancel")) == 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!returnToChoice)
|
|
{
|
|
// delete residual "return to" file if last shutdown was unclean.
|
|
snprintf(tempPath, sizeof(tempPath), "%s:/sneek/reload.sys", DeviceHandler::GetDevicePrefix(NandEmuPath));
|
|
if (CheckFile(tempPath))
|
|
RemoveFile(tempPath);
|
|
}
|
|
else
|
|
{
|
|
snprintf(tempPath, sizeof(tempPath), "%s/title/00010001/4e4b324f/content/title.tmd", NandEmuPath);
|
|
if (CheckFile(tempPath))
|
|
NK2O_isInstalled = true;
|
|
}
|
|
|
|
// Every checks passed successfully. Continue execution.
|
|
|
|
// Load neek kernel.bin
|
|
if (neekLoadKernel(NandEmuPath) == false)
|
|
{
|
|
WindowPrompt(tr("Error:"), tr("Neek kernel loading failed."), tr("OK"));
|
|
return -1;
|
|
}
|
|
|
|
// all is good so far, exit the loader, set the settings and boot neek.
|
|
ExitApp();
|
|
|
|
// Set Neek2o settings
|
|
NEEK_CFG *neek_config = (NEEK_CFG *)NEEK_CONFIG_ADDRESS;
|
|
memset(neek_config, 0, sizeof(NEEK_CFG));
|
|
|
|
// Magic and version for Neek2o
|
|
neek_config->magic = NEEK_MAGIC;
|
|
|
|
// Set NAND path
|
|
snprintf(neek_config->nandpath, sizeof(neek_config->nandpath), "%s", neekNandPath);
|
|
neek_config->config |= NCON_EXT_NAND_PATH; // specify a NAND path in case default NAND set in nandcfg.bin failed
|
|
// neek_config->config |= NCON_HIDE_EXT_PATH; // set NAND path as temporary (attention: "return to" loads channel from the default NAND path)
|
|
|
|
// Set TitleID to return to
|
|
if (autoboot && returnToChoice)
|
|
{
|
|
// Todo : allow user to select the channel to return to.
|
|
if (NK2O_isInstalled)
|
|
{
|
|
neek_config->returnto = TITLE_ID(0x00010001, 'NK2O'); // Currently forced to NK2O user channel
|
|
neek_config->config |= NCON_EXT_RETURN_TO; // enable "return to" patch
|
|
}
|
|
|
|
if (isWiiU())
|
|
{
|
|
neek_config->returnto = TITLE_ID(0x00010002, 'HCVA'); // Currently forced to "Return to WiiU" system channel
|
|
neek_config->config |= NCON_EXT_RETURN_TO; // enable "return to" patch
|
|
}
|
|
}
|
|
|
|
// Set GameID - Channels
|
|
if (autoboot && gameHeader.type == TYPE_GAME_EMUNANDCHAN)
|
|
neek_config->titleid = gameHeader.tid;
|
|
|
|
// Set GameID - Wii ISO
|
|
else if (autoboot && (gameHeader.type == TYPE_GAME_WII_IMG || gameHeader.type == TYPE_GAME_WII_DISC)) // This autoobot method doesn't work in neek2o r96
|
|
{
|
|
neek_config->gamemagic = 0x5d1c9ea3; // Wii game
|
|
neek_config->gameid = (u32)gameHeader.id; // wbfs GameID4 to autoboot
|
|
neek_config->config |= NCON_EXT_BOOT_GAME; // Boot di Game
|
|
}
|
|
|
|
// Set GameID - GameCube ISO
|
|
else if (autoboot && (gameHeader.type == TYPE_GAME_GC_IMG || gameHdr->type == TYPE_GAME_GC_EXTRACTED)) // not implemented yet
|
|
{
|
|
neek_config->gamemagic = 0xC2339F3D; // gamecube games
|
|
neek_config->gameid = (u32)gameHeader.id; // GameCube GameID4 to autoboot
|
|
neek_config->config |= NCON_EXT_BOOT_GAME; // Boot di Game
|
|
|
|
// set DML setttings in Neek config2
|
|
// see how to boot neek for DM/L games
|
|
}
|
|
|
|
// set a custom di folder
|
|
// snprintf(neek_config->dipath, sizeof(neek_config->dipath), "/sneek/vwii"); // Set path for di.bin and diconfig.bin
|
|
// neek_config->config |= NCON_EXT_DI_PATH; // Use custom di path
|
|
|
|
DCFlushRange(neek_config, sizeof(NEEK_CFG));
|
|
|
|
gprintf("NEEK: Settings:");
|
|
hexdump((u8 *)NEEK_CONFIG_ADDRESS, sizeof(NEEK_CFG));
|
|
|
|
if (neekBoot() == -1)
|
|
Sys_BackToLoader();
|
|
return 0;
|
|
}
|
|
|
|
void GameBooter::PatchSram(int language, bool patchVideoMode, bool progressive)
|
|
{
|
|
syssram *sram = __SYS_LockSram();
|
|
|
|
// Setup language flag
|
|
if (language >= GC_ENGLISH && language <= GC_DUTCH)
|
|
{
|
|
sram->lang = language;
|
|
}
|
|
else // console default
|
|
{
|
|
sram->lang = GC_ENGLISH;
|
|
if (CONF_GetLanguage() >= CONF_LANG_ENGLISH && CONF_GetLanguage() <= CONF_LANG_DUTCH)
|
|
{
|
|
sram->lang = CONF_GetLanguage() - 1;
|
|
}
|
|
}
|
|
gprintf("Sram: Language set to 0x%02x\n", sram->lang);
|
|
|
|
// Setup Video mode flags
|
|
if (patchVideoMode)
|
|
{
|
|
if (progressive)
|
|
sram->flags |= 0x80; // set progressive flag
|
|
else
|
|
sram->flags &= 0x7F; // clear progressive flag
|
|
|
|
if (*Video_Mode == VI_NTSC)
|
|
{
|
|
sram->flags &= ~1; // Clear bit 0 to set the video mode to NTSC
|
|
sram->ntd &= 0xBF; // clear pal60 flag
|
|
}
|
|
else
|
|
{
|
|
sram->flags |= 1; // Set bit 0 to set the video mode to PAL
|
|
sram->ntd |= 0x40; // set pal60 flag
|
|
}
|
|
|
|
gprintf("Sram: flags set to 0x%02x\n", sram->flags);
|
|
gprintf("Sram: ntd set to 0x%02x\n", sram->ntd);
|
|
}
|
|
|
|
__SYS_UnlockSram(1); // 1 -> write changes
|
|
while (!__SYS_SyncSram())
|
|
usleep(100);
|
|
|
|
// Log Sram's first 20 bytes
|
|
/*
|
|
char srambuff[64];
|
|
sram = __SYS_LockSram();
|
|
memcpy(srambuff, sram, 20);
|
|
__SYS_UnlockSram(0);
|
|
|
|
int i;
|
|
gprintf("SRAM Hex View\n\n");
|
|
gprintf(" \t\t 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
|
|
for (i=0; i < 20; i++)
|
|
{
|
|
if((i%16) == 0)
|
|
gprintf("\n0x%d0h\t\t", i/16);
|
|
|
|
gprintf("%02X ", srambuff[i]);
|
|
}
|
|
*/
|
|
}
|