From af589b404e879c28937aee3bf6704c9b423f44e7 Mon Sep 17 00:00:00 2001 From: "overjoy.psm" Date: Mon, 20 Feb 2012 08:26:50 +0000 Subject: [PATCH] * Updated DML Ex Gamecube disc dumper: - Added retry on read errors - Added skipping on read errors - Added pos to compress data - Added pos to align all files by 32k - Dumper will now only dump game.iso on default - Now using drivebay led to show disc reading activity - Now checking if game already exists before dumping it Configuration of the dumper: Set the following vars in wiiflow.ini (domain: DML) to config the dumper: - skip_on_error=yes/no to enable/disable error skipping (default = no) - compressed_dump=yes/no to enable/disable compressed dumping (default = no) - write_ex_files=yes/no write ex files yes/no (default = no) - align_files=yes/no to align all by 32k yes/no (default = no) - num_retries=# were # = the amount of read retries the dumper should perform before it marks the block as bad on read errors --- source/loader/gc_disc.cpp | 377 ++++++++++++++++++++++++++++++++++---- source/loader/gc_disc.hpp | 86 ++++++++- source/loader/utils.h | 2 +- source/loader/wdvd.c | 50 +++-- source/loader/wdvd.h | 1 + source/menu/menu.hpp | 2 + source/menu/menu_wbfs.cpp | 36 +++- 7 files changed, 486 insertions(+), 68 deletions(-) diff --git a/source/loader/gc_disc.cpp b/source/loader/gc_disc.cpp index a3b90e35..955564d7 100644 --- a/source/loader/gc_disc.cpp +++ b/source/loader/gc_disc.cpp @@ -1,9 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2012 + * by OverjoY for Wiiflow + * + * 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. + * + * gc_disc.cpp + * + ***************************************************************************/ + #include -#include #include #include -#include -#include +#include #include "gc_disc.hpp" #include "DeviceHandler.hpp" @@ -11,68 +36,342 @@ #include "utils.h" #include "wdvd.h" #include "text.hpp" +#include "gecko.h" -s32 __DiscReadRaw(void *outbuf, u32 offset, u32 length) -{ - return WDVD_UnencryptedRead(outbuf, length, offset); +static u8 *FSTable ALIGNED(32); + +s32 GCDump::__DiscReadRaw(void *outbuf, u32 offset, u32 length) +{ + while(1) + { + *(u32*)0xCD0000C0 |= 0x20; + gc_error = 0; + s32 ret = WDVD_UnencryptedRead(outbuf, length, offset); + if( ret != 0 ) + { + WDVD_LowRequestError(&gc_error); + if(gc_error == 0x30200 || gc_error == 0x30201 || gc_error == 0x31100) + { + if(gc_retry >= gc_nbrretry) + { + if(!skiponerror) + { + *(u32*)0xCD0000C0 &= ~0x20; + return gc_error; + } + else + { + gc_retry = 0; + gc_skipped++; + gprintf("Read error (%x) at offset: 0x%08x. Skipping %d bytes\n", gc_error, offset, length); + *(u32*)0xCD0000C0 &= ~0x20; + return 1; + } + } + gc_retry++; + } + else + { + gprintf("Read error(%x) at offset: 0x%08x.\n", gc_error, offset); + *(u32*)0xCD0000C0 &= ~0x20; + return 0; + } + } + else + { + *(u32*)0xCD0000C0 &= ~0x20; + gc_retry = 0; + return ret; + } + } + return -1; } -s32 GC_GameDumper(progress_callback_t spinner, void *spinner_data) +s32 GCDump::__DiscWrite(char * path, u32 offset, u32 length, progress_callback_t spinner, void *spinner_data) +{ + gprintf("__DiscWrite(%s, 0x%08x, %x)\n", path, offset, length); + u8 *ReadBuffer = (u8 *)memalign(32, gc_readsize); + u32 toread = 0; + u32 wrote = 0; + FILE *f = fopen(path, "ab"); + + while(length) + { + toread = gc_readsize; + if(toread > length) + toread = length; + + s32 ret = __DiscReadRaw(ReadBuffer, offset, (toread+31)&(~31)); + if( ret == 1 ) + memset(ReadBuffer, 0, gc_readsize); + + else if( ret > 1 ) + return 0; + + fwrite(ReadBuffer, 1, toread, f); + + wrote += toread; + offset += toread; + length -= toread; + if(spinner) + spinner(wrote, DiscSize, spinner_data); + } + + fclose(f); + free(ReadBuffer); + return wrote; +} + +s32 GCDump::__DiscWriteAligned(char * path, u32 offset, u32 length) +{ + u8 *ReadBuffer = (u8 *)memalign(32, gc_readsize+4); + u32 toread = 0; + u32 wrote = 0; + FILE *f = fopen(path, "ab"); + + while(length) + { + toread = gc_readsize; + if(toread > length) + toread = length; + + s32 ret = __DiscReadRaw(ReadBuffer, offset, (toread+31)&(~31)); + if( ret == 1 ) + memset(ReadBuffer, 0, gc_readsize); + + + if( ret > 1 ) + return -1; + + if(aligned) + { + fwrite(ReadBuffer, 1, (toread+31)&(~31), f); + wrote += (toread+31)&(~31); + } + else + { + fwrite(ReadBuffer, 1, (toread+3)&(~3), f); + wrote += (toread+3)&(~3); + } + offset += toread; + length -= toread; + } + + fclose(f); + free(ReadBuffer); + return wrote; +} + +s32 GCDump::DumpGame(progress_callback_t spinner, void *spinner_data) { static gc_discHdr gcheader ATTRIBUTE_ALIGN(32); + + u8 *ReadBuffer = (u8 *)memalign(32, 0x40); + u8 *FSTBuffer; + u32 wrote = 0; - FILE *f; - u8 *ReadBuffer = (u8 *)memalign(32, READSIZE); - u32 DiscSec = 0; u32 ApploaderSize = 0; + u32 DOLOffset = 0; + u32 DOLSize = 0; + u32 FSTOffset = 0; + u32 FSTSize = 0; + u32 FSTEnt = 0; + u32 GamePartOffset = 0; + u32 DataSize = 0; + char *FSTNameOff = (char *)NULL; + char folder[MAX_FAT_PATH]; bzero(folder, MAX_FAT_PATH); char gamepath[MAX_FAT_PATH]; bzero(gamepath, MAX_FAT_PATH); - Disc_ReadGCHeader(&gcheader); + s32 ret = Disc_ReadGCHeader(&gcheader); Asciify2(gcheader.title); snprintf(folder, sizeof(folder), "%s:/games/%s [%s]", DeviceName[SD], gcheader.title, (char *)gcheader.id); makedir((char *)folder); - snprintf(gamepath, sizeof(gamepath), "%s/game.iso", folder); - f = fopen(gamepath, "wb"); - while( DiscSec < 0xAE0B ) + if(writeexfiles) { - __DiscReadRaw(ReadBuffer, DiscSec*READSIZE, READSIZE); - fwrite(ReadBuffer, 1, READSIZE, f); - spinner(DiscSec, 0xAE0B, spinner_data); - DiscSec++; + snprintf(folder, sizeof(folder), "%s:/games/%s [%s]/sys", DeviceName[SD], gcheader.title, (char *)gcheader.id); + makedir((char *)folder); } - fclose(f); - snprintf(folder, sizeof(folder), "%s:/games/%s [%s]/sys", DeviceName[SD], gcheader.title, (char *)gcheader.id); - makedir((char *)folder); - snprintf(gamepath, sizeof(gamepath), "%s/boot.bin", folder); - __DiscReadRaw(ReadBuffer, 0, 0x440); + ret = __DiscReadRaw(ReadBuffer, 0x400, 0x40); + if(ret > 0) + return 0x31100; - f = fopen(gamepath, "wb"); - fwrite(ReadBuffer, 1, 0x440, f); - fclose(f); + ApploaderSize = *(vu32*)(ReadBuffer); + DOLOffset = *(vu32*)(ReadBuffer+0x20); + FSTOffset = *(vu32*)(ReadBuffer+0x24); + FSTSize = *(vu32*)(ReadBuffer+0x28); + GamePartOffset = *(vu32*)(ReadBuffer+0x34); + DataSize = *(vu32*)(ReadBuffer+0x38); + + free(ReadBuffer); + + DOLSize = FSTOffset - DOLOffset; + DiscSize = DataSize + GamePartOffset; + + FSTBuffer = (u8 *)memalign(32, (FSTSize+31)&(~31)); - ApploaderSize = *(vu32*)(ReadBuffer+0x400); + ret = __DiscReadRaw(FSTBuffer, FSTOffset, (FSTSize+31)&(~31)); + if(ret > 0) + return 0x31100; + + FSTable = (u8*)FSTBuffer; + + FSTEnt = *(u32*)(FSTable+0x08); - snprintf(gamepath, sizeof(gamepath), "%s/bi2.bin", folder); - __DiscReadRaw(ReadBuffer, 0x440, 0x2000); + FSTNameOff = (char*)(FSTable + FSTEnt * 0x0C); + FST *fst = (FST *)(FSTable); - f = fopen(gamepath, "wb"); - fwrite(ReadBuffer, 1, 0x2000, f); - fclose(f); + gprintf("Dumping: %s %s\n", gcheader.title, compressed ? "compressed" : "full"); - snprintf(gamepath, sizeof(gamepath), "%s/apploader.img", folder); - __DiscReadRaw(ReadBuffer, 0x2440, ApploaderSize); + 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("Disc size : %d\n", DiscSize); - f = fopen(gamepath, "wb"); - fwrite(ReadBuffer, 1, ApploaderSize, f); - fclose(f); + if(writeexfiles) + { + gprintf("Writing %s/boot.bin\n", folder); + snprintf(gamepath, sizeof(gamepath), "%s/boot.bin", folder); + __DiscWrite(gamepath, 0, 0x440, spinner, spinner_data); + + gprintf("Writing %s/bi2.bin\n", folder); + snprintf(gamepath, sizeof(gamepath), "%s/bi2.bin", folder); + __DiscWrite(gamepath, 0x440, 0x2000, spinner, spinner_data); - free(ReadBuffer); + gprintf("Writing %s/apploader.img\n", folder); + snprintf(gamepath, sizeof(gamepath), "%s/apploader.img", folder); + __DiscWrite(gamepath, 0x2440, ApploaderSize, spinner, spinner_data); + } - return 0; + snprintf(gamepath, sizeof(gamepath), "%s:/games/%s [%s]/game.iso", DeviceName[SD], gcheader.title, (char *)gcheader.id); + + gprintf("Writing %s\n", gamepath); + if(compressed) + { + ret = __DiscWriteAligned(gamepath, 0, GamePartOffset); + wrote += ret; + + u32 i; + + for( i=1; i < FSTEnt; ++i ) + { + if( fst[i].Type ) + { + continue; + } + else + { + ret = __DiscWriteAligned(gamepath, fst[i].FileOffset, fst[i].FileLength); + gprintf("Writing: %d/%d: %s from 0x%08x to 0x%08x\n", i, FSTEnt, FSTNameOff + fst[i].NameOffset, fst[i].FileOffset, wrote); + if( ret >= 0 ) + { + fst[i].FileOffset = wrote; + wrote += ret; + spinner(i, FSTEnt, spinner_data); + } + else + { + spinner(FSTEnt, FSTEnt, spinner_data); + free(FSTBuffer); + return gc_error; + } + } + } + + gprintf("Updating FST\n"); + + FILE *f = fopen(gamepath, "r+"); + fseek(f, FSTOffset, SEEK_SET); + fwrite(fst, 1, FSTSize, f); + fclose(f); + + gprintf("Done!! Disc old size: %d, disc new size: %d, saved: %d\n", DiscSize, wrote, DiscSize - wrote); + } + else + { + ret = __DiscWrite(gamepath, 0, DiscSize, spinner, spinner_data); + if( ret < 0 ) + { + free(FSTBuffer); + return gc_error; + } + gprintf("Done!! Disc size: %d\n", DiscSize); + } + + free(FSTBuffer); + + return gc_skipped; +} + +s32 GCDump::CheckSpace(u32 *needed, bool comp) +{ + u8 *ReadBuffer = (u8 *)memalign(32, 0x40); + + s32 ret = __DiscReadRaw(ReadBuffer, 0x400, 0x40); + if(ret > 0) + return 1; + + u32 ApploaderSize = *(vu32*)(ReadBuffer); + u32 FSTOffset = *(vu32*)(ReadBuffer+0x24); + u32 FSTSize = *(vu32*)(ReadBuffer+0x28); + u32 GamePartOffset = *(vu32*)(ReadBuffer+0x34); + u32 DataSize = *(vu32*)(ReadBuffer+0x38); + + u32 DiscSize = DataSize + GamePartOffset; + + free(ReadBuffer); + + u32 size = 0; + + if(writeexfiles) + { + size += 0xa440; + size += ApploaderSize; + } + + if(!comp) + { + size += DiscSize; + } + else + { + u8 *FSTBuffer = (u8 *)memalign(32, (FSTSize+31)&(~31)); + + ret = __DiscReadRaw(FSTBuffer, FSTOffset, (FSTSize+31)&(~31)); + if(ret > 0) + { + free(FSTBuffer); + return 1; + } + + FSTable = (u8*)FSTBuffer; + u32 FSTEnt = *(u32*)(FSTable+0x08); + FST *fst = (FST *)(FSTable); + + u32 i; + + for( i=1; i < FSTEnt; ++i ) + { + if( fst[i].Type ) + { + continue; + } + else + { + size += (fst[i].FileLength+31)&(~31); + } + } + free(FSTBuffer); + } + *needed = size/0x8000; + gprintf("Free space needed on SD: %d bytes (%x blocks)\n", size, size/0x8000); + return 0; } \ No newline at end of file diff --git a/source/loader/gc_disc.hpp b/source/loader/gc_disc.hpp index bf3771a9..ee71a98c 100644 --- a/source/loader/gc_disc.hpp +++ b/source/loader/gc_disc.hpp @@ -1,9 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2012 + * by OverjoY for Wiiflow + * + * 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. + * + * gc_disc.hpp + * + ***************************************************************************/ + #ifndef GC_DISC_H_ #define GC_DISC_H_ typedef void (*progress_callback_t)(int status,int total,void *user_data); -s32 GC_GameDumper(progress_callback_t spinner, void *spinner_data); -s32 GC_DiskSpace(u64 *free); - +class GCDump +{ +public: + void Init(bool skip, bool comp, bool wexf, bool align, u32 nretry, u32 rsize) + { + skiponerror = skip; + compressed = comp; + writeexfiles = wexf; + aligned = align; + gc_nbrretry = nretry; + gc_readsize = rsize; + gc_skipped = 0; + } + s32 DumpGame(progress_callback_t spinner, void *spinner_data); + s32 CheckSpace(u32 *needed, bool comp); +private: + bool aligned; + bool skiponerror; + bool compressed; + bool writeexfiles; + u32 gc_nbrretry; + u32 gc_error; + u32 gc_retry; + u32 gc_skipped; + u32 gc_readsize; + u32 DiscSize; + typedef struct + { + union + { + struct + { + u32 Type :8; + u32 NameOffset :24; + }; + u32 TypeName; + }; + union + { + struct + { + u32 FileOffset; + u32 FileLength; + }; + struct + { + u32 ParentOffset; + u32 NextOffset; + }; + u32 entry[2]; + }; + } FST; + s32 __DiscReadRaw(void *outbuf, u32 offset, u32 length); + s32 __DiscWrite(char * path, u32 offset, u32 length, progress_callback_t spinner , void *spinner_data); + s32 __DiscWriteAligned(char * path, u32 offset, u32 length); +}; #endif \ No newline at end of file diff --git a/source/loader/utils.h b/source/loader/utils.h index 3267a939..75475367 100644 --- a/source/loader/utils.h +++ b/source/loader/utils.h @@ -7,7 +7,6 @@ #define MB_SIZE 1048576.0 #define GB_SIZE 1073741824.0 -#define READSIZE (32*1024) #define MAX_FAT_PATH 1024 /* Macros */ @@ -15,6 +14,7 @@ #define ALIGN(x) (((x) + 3) & ~3) #define ALIGN32(x) (((x) + 31) & ~31) +#define ALIGNED(x) __attribute__((aligned(x))) #define SMART_FREE(P) {if(!!P)P.release();} #define SAFE_FREE(P) {if(P != NULL){free(P);P = NULL;}} diff --git a/source/loader/wdvd.c b/source/loader/wdvd.c index 6c662da8..71b96bca 100644 --- a/source/loader/wdvd.c +++ b/source/loader/wdvd.c @@ -5,25 +5,26 @@ #include "gecko.h" /* Constants */ -#define IOCTL_DI_READID 0x70 -#define IOCTL_DI_READ 0x71 -#define IOCTL_DI_WAITCVRCLOSE 0x79 -#define IOCTL_DI_GETCOVER 0x88 -#define IOCTL_DI_RESET 0x8A -#define IOCTL_DI_OPENPART 0x8B -#define IOCTL_DI_CLOSEPART 0x8C -#define IOCTL_DI_UNENCREAD 0x8D -#define IOCTL_DI_SEEK 0xAB -#define IOCTL_DI_STOPLASER 0xD2 -#define IOCTL_DI_OFFSET 0xD9 -#define IOCTL_DI_DISC_BCA 0xDA -#define IOCTL_DI_STOPMOTOR 0xE3 -#define IOCTL_DI_SETWBFSMODE 0xF4 +#define IOCTL_DI_READID 0x70 +#define IOCTL_DI_READ 0x71 +#define IOCTL_DI_WAITCVRCLOSE 0x79 +#define IOCTL_DI_GETCOVER 0x88 +#define IOCTL_DI_RESET 0x8A +#define IOCTL_DI_OPENPART 0x8B +#define IOCTL_DI_CLOSEPART 0x8C +#define IOCTL_DI_UNENCREAD 0x8D +#define IOCTL_DI_SEEK 0xAB +#define IOCTL_DI_STOPLASER 0xD2 +#define IOCTL_DI_OFFSET 0xD9 +#define IOCTL_DI_DISC_BCA 0xDA +#define IOCTL_DI_REQUESTERROR 0xE0 +#define IOCTL_DI_STOPMOTOR 0xE3 +#define IOCTL_DI_SETWBFSMODE 0xF4 #define IOCTL_DI_DVDLowAudioBufferConfig 0xE4 -#define IOCTL_DI_SETFRAG 0xF9 -#define IOCTL_DI_GETMODE 0xFA -#define IOCTL_DI_HELLO 0xFB +#define IOCTL_DI_SETFRAG 0xF9 +#define IOCTL_DI_GETMODE 0xFA +#define IOCTL_DI_HELLO 0xFB /* Variables */ static u32 inbuf[8] ATTRIBUTE_ALIGN(32); @@ -271,6 +272,21 @@ s32 WDVD_Read(void *buf, u32 len, u64 offset) return (ret == 1) ? 0 : -ret; } +s32 WDVD_LowRequestError(u32 *error) +{ + memset(inbuf, 0, sizeof(inbuf)); + + inbuf[0] = IOCTL_DI_REQUESTERROR << 24; + + s32 ret = IOS_Ioctl(di_fd, IOCTL_DI_REQUESTERROR, inbuf, sizeof(inbuf), outbuf, sizeof(outbuf)); + if (ret < 0) return ret; + + if (ret == 1) + memcpy(error, outbuf, sizeof(u32)); + + return (ret == 1) ? 0 : -ret; +} + s32 WDVD_WaitForDisc(void) { memset(inbuf, 0, sizeof(inbuf)); diff --git a/source/loader/wdvd.h b/source/loader/wdvd.h index 5ed3615a..9a57da73 100644 --- a/source/loader/wdvd.h +++ b/source/loader/wdvd.h @@ -19,6 +19,7 @@ s32 WDVD_OpenPartition(u64 offset, void* Ticket, void* Certificate, unsigned int s32 WDVD_ClosePartition(void); s32 WDVD_UnencryptedRead(void *, u32, u64); s32 WDVD_Read(void *, u32, u64); +s32 WDVD_LowRequestError(u32 *error); s32 WDVD_WaitForDisc(void); s32 WDVD_GetCoverStatus(u32 *); s32 WDVD_SetUSBMode(u32, const u8 *, s32); diff --git a/source/menu/menu.hpp b/source/menu/menu.hpp index fadc606f..f35fca1e 100644 --- a/source/menu/menu.hpp +++ b/source/menu/menu.hpp @@ -21,6 +21,7 @@ #include "gct.h" #include "DeviceHandler.hpp" #include "musicplayer.h" +#include "loader/gc_disc.hpp" //Also in wbfs.h #define PART_FS_WBFS 0 @@ -66,6 +67,7 @@ private: Config m_titles; Config m_version; Channels m_channels; + GCDump m_gcdump; safe_vector m_homebrewArgs; SmartBuf m_base_font; u32 m_base_font_size; diff --git a/source/menu/menu_wbfs.cpp b/source/menu/menu_wbfs.cpp index e3b516e6..8463c127 100644 --- a/source/menu/menu_wbfs.cpp +++ b/source/menu/menu_wbfs.cpp @@ -110,6 +110,15 @@ int CMenu::_GCgameInstaller(void *obj) { CMenu &m = *(CMenu *)obj; + bool skip = m.m_cfg.getBool("DML", "skip_on_error", false); + bool comp = m.m_cfg.getBool("DML", "compressed_dump", false); + bool wexf = m.m_cfg.getBool("DML", "write_ex_files", false); + bool alig = m.m_cfg.getBool("DML", "align_files", false); + u32 nretry = m.m_cfg.getUInt("DML", "num_retries", 5); + u32 rsize = 32768; + + m.m_gcdump.Init(skip, comp, wexf, alig, nretry, rsize); + int ret; if (!DeviceHandler::Instance()->IsInserted(SD)) @@ -123,29 +132,33 @@ int CMenu::_GCgameInstaller(void *obj) statvfs("sd:/" , &stats); u64 free = (u64)stats.f_frsize * (u64)stats.f_bfree; + u32 needed = 0; - int blockfree = free/0x8000; + m.m_gcdump.CheckSpace(&needed, comp); - if (blockfree <= 44556) + u32 blockfree = free/0x8000; + + if (blockfree <= needed) { LWP_MutexLock(m.m_mutex); - m._setThrdMsg(wfmt(m._fmt("wbfsop11", L"Not enough space : 44557 blocks needed, %d available"), blockfree), 0.f); + m._setThrdMsg(wfmt(m._fmt("wbfsop11", L"Not enough space : %d blocks needed, %d available"), needed, blockfree), 0.f); LWP_MutexUnlock(m.m_mutex); ret = -1; } else { - LWP_MutexLock(m.m_mutex); m._setThrdMsg(L"", 0); LWP_MutexUnlock(m.m_mutex); - ret=0; - - ret = GC_GameDumper(CMenu::_addDiscProgress, obj); + ret = m.m_gcdump.DumpGame(CMenu::_addDiscProgress, obj); LWP_MutexLock(m.m_mutex); - if (ret == 0) + if(ret == 0) m._setThrdMsg(m._t("wbfsop8", L"Game installed"), 1.f); + else if( ret >= 0x30200) + m._setThrdMsg(wfmt(m._fmt("wbfsop12", L"DVDError(%d)"), ret), 0.f); + else if( ret > 0) + m._setThrdMsg(wfmt(m._fmt("wbfsop13", L"Game installed, but disc contains errors (%d)"), ret), 0.f); else m._setThrdMsg(m._t("wbfsop9", L"An error has occurred"), 1.f); LWP_MutexUnlock(m.m_mutex); @@ -243,6 +256,13 @@ bool CMenu::_wbfsOp(CMenu::WBFS_OP op) else if(Disc_IsGC() == 0) { Disc_ReadGCHeader(&gcheader); + + if (_searchGamesByID((const char *) gcheader.id).size() != 0) + { + error(_t("wbfsoperr4", L"Game already installed")); + out = true; + break; + } cfPos = string((char *) gcheader.id); m_btnMgr.setText(m_wbfsLblDialog, wfmt(_fmt("wbfsop6", L"Installing [%s] %s..."), string((const char *)gcheader.id, sizeof gcheader.id).c_str(), string((const char *)gcheader.title, sizeof gcheader.title).c_str())); done = true;