usbloadergx/source/GameCube/GCDumper.cpp
Cyan 5adbf57bf5 * Fixed GameCube multiDisc installation.
* Fixed progress window not hiding if Wiiload didn't send the file.
* Simplify HBC loading logic.
* Added a warning when trying to launch a Wii game with a non-d2x
  cIOS if EmuNAND is enabled.
* Fixed individual game settings to allow modification of the EmuNAND
  settings based on the game's IOS instead of global IOS.
* Added back cIOS rev17-21 support for EmuNAND Channel. issue 2152
  (Thanks to Garfunkiel for the patch and to Themanuel
  for all the tests with game compatibility and partition detection)
  This hidden feature is unlocked only if all the user's setup
  is compatible with rev17-21:
  EmuNAND Channel path must be on the root of the first FAT32
  partition on a 512 bytes/sector device.
* DML: Changed the GameCube Multi-discs prompt logic to display
  on older DIOS MIOS (Lite) versions too.
* DML: Added a partition type verification to warn the user
  if the HDD is not correctly setup.
2013-01-06 13:41:22 +00:00

404 lines
9.9 KiB
C++

/***************************************************************************
* Copyright (C) 2012
* by OverjoY and FIX94 for Wiiflow
*
* Adjustments for USB Loader GX by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
***************************************************************************/
#include <algorithm>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <sys/statvfs.h>
#include "GCDumper.hpp"
#include "FileOperations/fileops.h"
#include "language/gettext.h"
#include "prompts/ProgressWindow.h"
#include "usbloader/disc.h"
#include "usbloader/wdvd.h"
#include "usbloader/wbfs/wbfs_fat.h"
#include "usbloader/wbfs/wbfs_rw.h"
#include "utils/ShowError.h"
#include "utils/tools.h"
#include "gecko.h"
static const u32 BUF_SIZE = (64*1024);
GCDumper::GCDumper()
: force_align32(false)
, compressed(false)
, ReadBuffer((u8 *) memalign(32, ALIGN32(BUF_SIZE)))
{
}
GCDumper::~GCDumper()
{
if(ReadBuffer)
free(ReadBuffer);
}
s32 GCDumper::CopyDiscData(FILE *f, u64 offset, u32 length, u8 *buffer)
{
u32 toread = 0;
u32 wrote = 0;
while(length)
{
if(ProgressCanceled())
return PROGRESS_CANCELED;
ShowProgress(discWrote, discTotal);
toread = std::min(length, BUF_SIZE);
s32 ret = __ReadDVDPlain(buffer, toread, offset);
if (ret < 0)
return ret;
fwrite(buffer, 1, toread, f);
wrote += toread;
offset += toread;
length -= toread;
discWrote += toread;
}
return wrote;
}
s32 GCDumper::ReadDiscHeader(void)
{
if(!ReadBuffer)
return -1;
s32 result = 0;
struct discHdr *gcheader = (struct discHdr *) memalign(32, ALIGN32(sizeof(struct discHdr)));
if(!gcheader)
return -1;
s32 ret = Disc_ReadHeader(gcheader);
if(ret < 0) {
free(gcheader);
return ret;
}
if(memcmp(gcheader->id, "GCOPDV", 6) == 0)
{
while(result == 0)
{
__ReadDVDPlain(ReadBuffer, 0x10, 0x40+(gameOffsets.size()*4));
u64 MultiGameOffset = ((u64)(*(u32*)ReadBuffer)) << 2ULL;
if(!MultiGameOffset)
break;
ret = __ReadDVDPlain(gcheader, sizeof(struct discHdr), MultiGameOffset);
if(ret < 0)
result = -3;
if(ReadDiscInfo(MultiGameOffset) < 0)
result = -4;
discHeaders.push_back(*gcheader);
gameOffsets.push_back(MultiGameOffset);
}
}
else
{
discHeaders.push_back(*gcheader);
gameOffsets.push_back(0);
if(ReadDiscInfo(0) < 0)
result = -5;
}
free(gcheader);
return result;
}
int GCDumper::ReadDiscInfo(const u64 &game_offset)
{
if(!ReadBuffer)
return -1;
s32 ret = __ReadDVDPlain(ReadBuffer, 0x440, game_offset);
if(ret < 0)
return -2;
u32 FSTOffset = *(u32*)(ReadBuffer+0x424);
u32 FSTSize = *(u32*)(ReadBuffer+0x428);
u32 GamePartOffset = *(u32*)(ReadBuffer+0x434);
u32 DataSize = *(u32*)(ReadBuffer+0x438);
u32 DiscSize = DataSize + GamePartOffset;
u32 installSize = 0;
if(!compressed)
{
installSize += DiscSize;
}
else
{
u8 *FSTBuffer = (u8 *)memalign(32, ALIGN32(FSTSize));
ret = __ReadDVDPlain(FSTBuffer, ALIGN32(FSTSize), game_offset+FSTOffset);
if(ret < 0)
{
free(FSTBuffer);
return -3;
}
u8 *FSTable = (u8*)FSTBuffer;
u32 FSTEnt = *(u32*)(FSTable+0x08);
FST *fst = (FST *)(FSTable);
installSize += (FSTOffset + FSTSize);
u32 i;
u32 correction;
u32 align;
for( i=1; i < FSTEnt; ++i )
{
if( fst[i].Type ) {
continue;
}
else
{
for(align = 0x8000; align > 2; align/=2)
{
if((fst[i].FileOffset & (align-1)) == 0 || force_align32)
{
correction = 0;
while(((installSize+correction) & (align-1)) != 0)
correction++;
installSize += correction;
break;
}
}
installSize += fst[i].FileLength;
}
}
free(FSTBuffer);
}
gameSizes.push_back(installSize);
return 0;
}
s32 GCDumper::InstallGame(const char *installpath, u32 game, const char *installedGamePath)
{
if(!ReadBuffer || game >= discHeaders.size() || game >= gameOffsets.size() || game >= gameSizes.size())
return -1;
const u64 &game_offset = gameOffsets[game];
const struct discHdr &gcheader = discHeaders[game];
discWrote = 0;
discTotal = gameSizes[game];
//! check for enough free space
{
struct statvfs sd_vfs;
if(statvfs(installpath, &sd_vfs) != 0)
{
ShowError(tr("Could not get free device space for game."));
return -102;
}
if(((u64)sd_vfs.f_frsize * (u64)sd_vfs.f_bfree) < discTotal)
{
ShowError(tr("Not enough free space on device."));
return -103;
}
}
s32 ret = __ReadDVDPlain(ReadBuffer, 0x440, game_offset);
if(ret < 0) {
ShowError(tr("Disc read error."));
return -2;
}
u32 Disc = *(u8*)(ReadBuffer+0x06);
u32 ApploaderSize = *(u32*)(ReadBuffer+0x400);
u32 DOLOffset = *(u32*)(ReadBuffer+0x420);
u32 FSTOffset = *(u32*)(ReadBuffer+0x424);
u32 FSTSize = *(u32*)(ReadBuffer+0x428);
u32 GamePartOffset = *(u32*)(ReadBuffer+0x434);
u32 DataSize = *(u32*)(ReadBuffer+0x438);
u32 DOLSize = FSTOffset - DOLOffset;
u32 DiscSize = DataSize + GamePartOffset;
u8 *FSTBuffer = (u8 *)memalign(32, ALIGN32(FSTSize));
if(!FSTBuffer) {
ShowError(tr("Not enough memory for FST."));
return -3;
}
ret = __ReadDVDPlain(FSTBuffer, ALIGN32(FSTSize), game_offset+FSTOffset);
if(ret < 0)
{
free(FSTBuffer);
ShowError(tr("Disc read error."));
return -3;
}
char gametitle[65];
snprintf(gametitle, sizeof(gametitle), "%s", gcheader.title);
Wbfs_Fat::CleanTitleCharacters(gametitle);
char gamepath[512];
// 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);
// If another Disc from the same gameID already exists, let's use that path
if(strlen((char *)installedGamePath) != 0)
snprintf(gamepath, sizeof(gamepath), "%s/", installedGamePath);
CreateSubfolder(gamepath);
// 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.iso", gamepath, Disc ? "disc2" : "game");
FILE *f = fopen(gamepath, "wb");
if(!f)
{
free(FSTBuffer);
ShowError(tr("Can't open file for write: %s"), gamepath);
return -4;
}
u8 *FSTable = (u8*)FSTBuffer;
u32 FSTEnt = *(u32*)(FSTable+0x08);
FST *fst = (FST *)(FSTable);
gprintf("Dumping: %s %s\n", gcheader.title, compressed ? "compressed" : "full");
gprintf("Apploader size : %d\n", ApploaderSize);
gprintf("DOL offset : 0x%08x\n", DOLOffset);
gprintf("DOL size : %d\n", DOLSize);
gprintf("FST offset : 0x%08x\n", FSTOffset);
gprintf("FST size : %d\n", FSTSize);
gprintf("Num FST entries: %d\n", FSTEnt);
gprintf("Data Offset : 0x%08x\n", FSTOffset+FSTSize);
gprintf("Disc size : %d\n", DiscSize);
if(compressed)
gprintf("Compressed size: %d\n", discTotal);
gprintf("Writing %s\n", gamepath);
s32 result = 0;
ProgressCancelEnable(true);
StartProgress(tr("Installing Game Cube Game..."), gcheader.title, 0, true, true);
if(compressed)
{
u32 align;
u32 correction;
u32 toread;
u32 wrote = 0;
ret = CopyDiscData(f, game_offset, (FSTOffset + FSTSize), ReadBuffer);
if(ret < 0)
result = -3;
wrote += (FSTOffset + FSTSize);
for(u32 i = 1; (result == 0) && (i < FSTEnt); ++i)
{
if(ProgressCanceled()) {
result = PROGRESS_CANCELED;
break;
}
if( fst[i].Type ) {
continue;
}
else
{
for(align = 0x8000; align > 2; align/=2)
{
if((fst[i].FileOffset & (align-1)) == 0 || force_align32)
{
correction = 0;
while(((wrote+correction) & (align-1)) != 0)
correction++;
wrote += correction;
while(correction)
{
toread = std::min(correction, BUF_SIZE);
memset(ReadBuffer, 0, toread);
fwrite(ReadBuffer, 1, toread, f);
correction -= toread;
}
break;
}
}
ret = CopyDiscData(f, game_offset+fst[i].FileOffset, fst[i].FileLength, ReadBuffer);
if(ret < 0) {
result = -2;
break;
}
fst[i].FileOffset = wrote;
wrote += ret;
}
}
fseek(f, FSTOffset, SEEK_SET);
fwrite(fst, 1, FSTSize, f);
gprintf("Done!! Disc old size: %d, disc new size: %d, saved: %d\n", DiscSize, wrote, DiscSize - wrote);
}
else
{
ret = CopyDiscData(f, game_offset, discTotal, ReadBuffer);
if( ret < 0 )
result = -2;
else
gprintf("Done!! Disc size: %d\n", DiscSize);
}
// Stop progress
ProgressStop();
ProgressCancelEnable(false);
free(FSTBuffer);
fclose(f);
if(result < 0)
{
RemoveFile(gamepath);
if(strlen((char *)installedGamePath) == 0) // If no other disc is installed in that folder, delete it.
{
char *pathPtr = strrchr(gamepath, '/');
if(pathPtr) *pathPtr = 0;
RemoveFile(gamepath);
}
if(result != PROGRESS_CANCELED)
ShowError(tr("Disc read error."));
}
return result;
}