usbloadergx/source/GameCube/GCDumper.cpp
Cyan 77f7daf9dc * 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.
2012-12-09 20:31:55 +00:00

397 lines
9.6 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)
{
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);
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 [%.6s]/%s.iso", installpath, gametitle, gcheader.id, 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);
char *pathPtr = strrchr(gamepath, '/');
if(pathPtr) *pathPtr = 0;
RemoveFile(gamepath);
if(result != PROGRESS_CANCELED)
ShowError(tr("Disc read error."));
}
return result;
}