* Added possibility to install multi-Discs GameCube games

* Added auto-detection and loading multi-Disc games with both Devolution
  and DIOS MIOS (Lite).
  The Disc 2 must be named disc2.iso and placed in the same folder
  than game.iso
* DML: Prevent temporary ocarina .gct file copy if the game is
  launched from disc

Note: 
If you use "GameCube compress" option (Extracted game format):
- If one of the disc is already extracted, it will not allows
  you to dump another disc (as it's now using the same folder,
  it would overwrite files from the other disc)
- If Disc 1 doesn't exist, it will tell you that disc 2 needs 
  to be in ISO format, but will asks if you really want to install
  the game in extracted format.
This commit is contained in:
Cyan 2012-12-09 20:31:55 +00:00
parent 420c290d7b
commit 77f7daf9dc
8 changed files with 119 additions and 18 deletions

View File

@ -2,8 +2,8 @@
<app version="1"> <app version="1">
<name> USB Loader GX</name> <name> USB Loader GX</name>
<coder>USB Loader GX Team</coder> <coder>USB Loader GX Team</coder>
<version>3.0 r1205</version> <version>3.0 r1206</version>
<release_date>20121209184858</release_date> <release_date>20121209202238</release_date>
<!-- // remove this line to enable arguments <!-- // remove this line to enable arguments
<arguments> <arguments>
<arg>--ios=250</arg> <arg>--ios=250</arg>

View File

@ -39,7 +39,7 @@ enum DMLConfig
DML_CFG_FORCE_WIDE = (1<<9), // DM v2.1+, Config v02 DML_CFG_FORCE_WIDE = (1<<9), // DM v2.1+, Config v02
DML_CFG_BOOT_DISC = (1<<10), DML_CFG_BOOT_DISC = (1<<10),
// DML_CFG_BOOT_DOL = (1<<11), // unused since DML v1.0, removed in v2.1 // DML_CFG_BOOT_DOL = (1<<11), // unused since DML v1.0, removed in v2.1
DML_CFG_BOOT_DISC2 = (1<<11), // DM v2.1+, Config v02 DML_CFG_BOOT_DISC2 = (1<<11), // added in DM v2.1+, Config v02, used in v2.6+
DML_CFG_NODISC2 = (1<<12), // added back in DM v2.2 update2 (r20) and removed in v2.3 DML_CFG_NODISC2 = (1<<12), // added back in DM v2.2 update2 (r20) and removed in v2.3
DML_CFG_SCREENSHOT = (1<<13), // added in v2.5 DML_CFG_SCREENSHOT = (1<<13), // added in v2.5
}; };

View File

@ -263,11 +263,13 @@ s32 GCDumper::InstallGame(const char *installpath, u32 game)
Wbfs_Fat::CleanTitleCharacters(gametitle); Wbfs_Fat::CleanTitleCharacters(gametitle);
char gamepath[512]; char gamepath[512];
snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]%s/", installpath, gametitle, gcheader.id, Disc ? "2" : ""); // snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]%s/", installpath, gametitle, gcheader.id, Disc ? "2" : ""); // Disc2 currently needs to be on the same folder.
snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]/", installpath, gametitle, gcheader.id);
CreateSubfolder(gamepath); CreateSubfolder(gamepath);
snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]%s/game.iso", installpath, gametitle, gcheader.id, Disc ? "2" : ""); // snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]%s/game.iso", installpath, gametitle, gcheader.id, Disc ? "2" : ""); // Disc2 currently needs to be on the same folder.
snprintf(gamepath, sizeof(gamepath), "%s%s [%.6s]/%s.iso", installpath, gametitle, gcheader.id, Disc ? "disc2" : "game");
FILE *f = fopen(gamepath, "wb"); FILE *f = fopen(gamepath, "wb");
if(!f) if(!f)

View File

@ -61,6 +61,7 @@ void GCGames::LoadGameList(const string &path, vector<struct discHdr> &headerLis
struct discHdr tmpHdr; struct discHdr tmpHdr;
struct stat st; struct stat st;
u8 id[8]; u8 id[8];
u8 disc_number = 0;
char fpath[1024]; char fpath[1024];
char fname_title[64]; char fname_title[64];
DIR *dir_iter; DIR *dir_iter;
@ -123,6 +124,22 @@ void GCGames::LoadGameList(const string &path, vector<struct discHdr> &headerLis
break; break;
} }
// Check if only disc2.iso is present
if(!found)
{
for(int i = 0; i < 2; i++)
{
char name[50];
snprintf(name, sizeof(name), "disc2.%s", (i % 2) == 0 ? "gcm" : "iso"); // allow gcm, though DM(L) require "disc2.iso" filename
snprintf(fpath, sizeof(fpath), "%s%s/%s", path.c_str(), dirname, name);
if((found = (stat(fpath, &st) == 0)) == true)
{
disc_number = 1;
break;
}
}
}
if(!found) if(!found)
{ {
snprintf(fpath, sizeof(fpath), "%s%s/sys/boot.bin", path.c_str(), dirname); snprintf(fpath, sizeof(fpath), "%s%s/sys/boot.bin", path.c_str(), dirname);
@ -166,6 +183,7 @@ void GCGames::LoadGameList(const string &path, vector<struct discHdr> &headerLis
strncpy(tmpHdr.title, title, sizeof(tmpHdr.title)-1); strncpy(tmpHdr.title, title, sizeof(tmpHdr.title)-1);
tmpHdr.magic = GCGames::MAGIC; tmpHdr.magic = GCGames::MAGIC;
tmpHdr.type = extracted ? TYPE_GAME_GC_EXTRACTED : TYPE_GAME_GC_IMG; tmpHdr.type = extracted ? TYPE_GAME_GC_EXTRACTED : TYPE_GAME_GC_IMG;
tmpHdr.disc_no = disc_number;
headerList.push_back(tmpHdr); headerList.push_back(tmpHdr);
pathList.push_back(gamePath); pathList.push_back(gamePath);
continue; continue;
@ -362,12 +380,31 @@ float GCGames::GetGameSize(const char *gameID)
return ((float) st.st_size / GB_SIZE); return ((float) st.st_size / GB_SIZE);
} }
bool GCGames::IsInstalled(const char *gameID) const bool GCGames::IsInstalled(const char *gameID, u8 disc_number) const
{ {
for(u32 n = 0; n < HeaderList.size(); n++) for(u32 n = 0; n < HeaderList.size(); n++)
{ {
if(memcmp(HeaderList[n].id, gameID, 6) == 0) if(memcmp(HeaderList[n].id, gameID, 6) == 0)
return true; {
if(HeaderList[n].type == TYPE_GAME_GC_EXTRACTED)
return true; // Multi-disc games in extracted form are currently unsupported by DML, no need to check further.
if(HeaderList[n].disc_no == disc_number) // Disc number already in headerList. If Disc2 is loaded in headerList, then Disc1 is not installed yet
{
return true;
}
else if(disc_number == 1) // Check if the second Game Disc exists in the same folder than Disc1.
{
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s", GetPath(gameID));
char *pathPtr = strrchr(filepath, '/');
if(pathPtr) *pathPtr = 0;
snprintf(filepath, sizeof(filepath), "%s/disc2.iso", filepath);
if(CheckFile(filepath))
return true;
}
}
} }
return false; return false;
} }

View File

@ -58,7 +58,7 @@ public:
} }
bool CopyUSB2SD(const struct discHdr *header); bool CopyUSB2SD(const struct discHdr *header);
bool IsInstalled(const char *gameID) const; bool IsInstalled(const char *gameID, u8 disc_number) const;
private: private:
static GCGames *instance; static GCGames *instance;

View File

@ -70,7 +70,7 @@ int MenuGCInstall()
gcDumper.SetCompressed(Settings.GCInstallCompressed); gcDumper.SetCompressed(Settings.GCInstallCompressed);
gcDumper.SetCompressed(Settings.GCInstallAligned); gcDumper.SetCompressed(Settings.GCInstallAligned);
//! If a different main path then the SD path is selected ask where to install //! If a different main path than the SD path is selected ask where to install
int destination = 1; int destination = 1;
if(strcmp(Settings.GameCubePath, Settings.GameCubeSDPath) != 0) if(strcmp(Settings.GameCubePath, Settings.GameCubeSDPath) != 0)
destination = WindowPrompt(tr("Where should the game be installed to?"), 0, tr("Main Path"), tr("SD Path"), tr("Cancel")); destination = WindowPrompt(tr("Where should the game be installed to?"), 0, tr("Main Path"), tr("SD Path"), tr("Cancel"));
@ -95,7 +95,7 @@ int MenuGCInstall()
for(u32 i = 0; i < installGames.size(); ++i) for(u32 i = 0; i < installGames.size(); ++i)
{ {
//! check if the game is already installed on SD/USB //! check if the game is already installed on SD/USB
if(GCGames::Instance()->IsInstalled((char *)gcDumper.GetDiscHeaders().at(installGames[i]).id)) if(GCGames::Instance()->IsInstalled((char *)gcDumper.GetDiscHeaders().at(installGames[i]).id, gcDumper.GetDiscHeaders().at(installGames[i]).disc_no))
{ {
WindowPrompt(tr("Game is already installed:"), gcDumper.GetDiscHeaders().at(installGames[i]).title, tr("OK")); WindowPrompt(tr("Game is already installed:"), gcDumper.GetDiscHeaders().at(installGames[i]).title, tr("OK"));
if(i+1 < installGames.size()) { if(i+1 < installGames.size()) {
@ -108,6 +108,23 @@ int MenuGCInstall()
} }
} }
// Check Disc2 installation format (DML 2.6+ auto-swap feature doesn't work with extracted game format)
if(Settings.GCInstallCompressed && gcDumper.GetDiscHeaders().at(installGames[i]).disc_no == 1)
{
int choice = WindowPrompt(tr(gcDumper.GetDiscHeaders().at(installGames[i]).title), tr("Disc2 needs to be installed in uncompressed format to work with DM(L) v2.6+, are you sure you want to install in compressed format?"), tr("Yes"), tr("Cancel"));
if(choice == 0)
{
if(i+1 < installGames.size()) {
continue;
}
else if(i == 0)
{
result = MENU_DISCLIST;
break;
}
}
}
// game is not yet installed so let's install it // game is not yet installed so let's install it
int ret = gcDumper.InstallGame(InstallPath, installGames[i]); int ret = gcDumper.InstallGame(InstallPath, installGames[i]);
if(ret >= 0) { if(ret >= 0) {

View File

@ -474,7 +474,7 @@ int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
} }
// Check Ocarina and cheat file location. the .gct file need to be located on the same partition than the game. // 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) if(gameHdr->type != TYPE_GAME_GC_DISC && ocarinaChoice && strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) != 0)
{ {
char path[255], destPath[255]; char path[255], destPath[255];
int res = -1; int res = -1;
@ -491,6 +491,28 @@ int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
} }
} }
// Check if game has multi Discs
bool bootDisc2 = false;
if(gameHdr->type != TYPE_GAME_GC_DISC && gameHdr->disc_no == 0 && currentMIOS != QUADFORCE)
{
if(IosLoader::GetDMLVersion() >= DML_VERSION_DM_2_6_0)
{
char disc2Path[255];
snprintf(disc2Path, sizeof(disc2Path), "%s", RealPath);
char *pathPtr = strrchr(disc2Path, '/');
if(pathPtr) *pathPtr = 0;
snprintf(disc2Path, sizeof(disc2Path), "%s/disc2.iso", disc2Path);
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 0;
if(choice == 2)
bootDisc2 = true;
}
}
}
const char *gcPath = strchr(RealPath, '/'); const char *gcPath = strchr(RealPath, '/');
if(!gcPath) gcPath = ""; if(!gcPath) gcPath = "";
@ -498,6 +520,13 @@ int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
char gamePath[255]; char gamePath[255];
snprintf(gamePath, sizeof(gamePath), "%s", gcPath); snprintf(gamePath, sizeof(gamePath), "%s", gcPath);
if(bootDisc2)
{
char *pathPtr = strrchr(gamePath, '/');
if(pathPtr) *pathPtr = 0;
snprintf(gamePath, sizeof(gamePath), "%s/disc2.iso", gamePath);
}
ExitApp(); ExitApp();
// Game ID // Game ID
@ -532,14 +561,14 @@ int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
// setup cheat and path // setup cheat and path
if(ocarinaChoice) if(ocarinaChoice)
{ {
// Check is the .gct folder is on the same partition than the game, if not load the temporary .gct file. // 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) if(strcmp(DeviceHandler::GetDevicePrefix(RealPath), DeviceHandler::GetDevicePrefix(Settings.Cheatcodespath)) == 0)
{ {
const char *CheatPath = strchr(Settings.Cheatcodespath, '/'); const char *CheatPath = strchr(Settings.Cheatcodespath, '/');
if(!CheatPath) CheatPath = ""; if(!CheatPath) CheatPath = "";
snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "%s%.6s.gct", CheatPath, (char *)gameHdr->id); snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "%s%.6s.gct", CheatPath, (char *)gameHdr->id);
} }
else else if(gameHdr->type != TYPE_GAME_GC_DISC)
{ {
snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "DMLTemp.gct"); snprintf(dml_config->CheatPath, sizeof(dml_config->CheatPath), "DMLTemp.gct");
} }
@ -561,6 +590,8 @@ int GameBooter::BootDIOSMIOS(struct discHdr *gameHdr)
dml_config->Config |= DML_CFG_FORCE_WIDE; dml_config->Config |= DML_CFG_FORCE_WIDE;
if(dmlScreenshotChoice) if(dmlScreenshotChoice)
dml_config->Config |= DML_CFG_SCREENSHOT; dml_config->Config |= DML_CFG_SCREENSHOT;
if(bootDisc2)
dml_config->Config |= DML_CFG_BOOT_DISC2;
// Setup Video Mode // Setup Video Mode
@ -694,9 +725,18 @@ int GameBooter::BootDevolution(struct discHdr *gameHdr)
const char *RealPath = GCGames::Instance()->GetPath((const char *) gameHdr->id); const char *RealPath = GCGames::Instance()->GetPath((const char *) gameHdr->id);
char disc1[100]; char disc1[100];
//char disc2[100]; char disc2[100];
bool multiDisc = false;
char DEVO_memCard[100]; char DEVO_memCard[100];
snprintf(disc1, sizeof(disc1), "%s", RealPath); snprintf(disc1, sizeof(disc1), "%s", RealPath);
snprintf(disc2, sizeof(disc2), "%s", RealPath);
char *pathPtr = strrchr(disc2, '/');
if(pathPtr) *pathPtr = 0;
snprintf(disc2, sizeof(disc2), "%s/disc2.iso", disc2);
if(CheckFile(disc2))
multiDisc = true;
snprintf(DEVO_memCard, sizeof(DEVO_memCard), "%s", RealPath); // Set memory card folder to Disc1 folder snprintf(DEVO_memCard, sizeof(DEVO_memCard), "%s", RealPath); // Set memory card folder to Disc1 folder
char *ptr = strrchr(DEVO_memCard, '/'); char *ptr = strrchr(DEVO_memCard, '/');
if(ptr) *ptr = 0; if(ptr) *ptr = 0;
@ -711,8 +751,9 @@ int GameBooter::BootDevolution(struct discHdr *gameHdr)
stat(disc1, &st1); stat(disc1, &st1);
// Get the starting cluster for the ISO file 2 // Get the starting cluster for the ISO file 2
//struct stat st2; struct stat st2;
//stat(disc2, &st2); if(multiDisc)
stat(disc2, &st2);
// setup Devolution // setup Devolution
memset(devo_config, 0, sizeof(*devo_config)); memset(devo_config, 0, sizeof(*devo_config));
@ -722,7 +763,8 @@ int GameBooter::BootDevolution(struct discHdr *gameHdr)
// Only last two letters are returned by DevkitPro, so we set them manually to Devolution config. // Only last two letters are returned by DevkitPro, so we set them manually to Devolution config.
devo_config->device_signature = st1.st_dev == 'SD' ? 'SD' : 'SB'; // Set device type. devo_config->device_signature = st1.st_dev == 'SD' ? 'SD' : 'SB'; // Set device type.
devo_config->disc1_cluster = st1.st_ino; // set starting cluster for first disc ISO file devo_config->disc1_cluster = st1.st_ino; // set starting cluster for first disc ISO file
//devo_config->disc2_cluster = st2.st_ino; // set starting cluster for second disc ISO file if(multiDisc)
devo_config->disc2_cluster = st2.st_ino; // set starting cluster for second disc ISO file
// Devolution configs // Devolution configs
// use wifi logging if USB gecko is not found in slot B // use wifi logging if USB gecko is not found in slot B

View File

@ -14,8 +14,11 @@ extern "C"
/* Game ID */ /* Game ID */
u8 id[6]; u8 id[6];
/* Game Disc number */
u8 disc_no;
/* Game version */ /* Game version */
u16 version; u8 disc_ver;
/* Audio streaming */ /* Audio streaming */
u8 streaming; u8 streaming;