diosmios/Patches.c

1188 lines
38 KiB
C
Raw Normal View History

#include "Patches.h"
#include "CardPatches.c"
#include "DVDPatches.c"
#include "FwritePatches.c"
#include "CheatCode.c"
extern u32 DOLSize;
u32 FrameBuffer = 0;
u32 FBOffset = 0;
u32 FBEnable = 0;
u32 FBSize = 0;
unsigned char VISetFB[] =
{
0x38, 0x7F, 0x00, 0xF0, // mr %r3, %r7
0x38, 0x9F, 0x01, 0x24,
0x38, 0xBF, 0x01, 0x28,
0x38, 0xDF, 0x01, 0x3C,
};
unsigned char OSReportDM[] =
{
0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x04, 0x90, 0xE1, 0x00, 0x08, 0x3C, 0xE0, 0xC0, 0x00,
0x90, 0x67, 0x18, 0x60, 0x90, 0x87, 0x18, 0x64, 0x90, 0xA7, 0x18, 0x68, 0x90, 0xC7, 0x18, 0x6C,
0x90, 0xE7, 0x18, 0x70, 0x91, 0x07, 0x18, 0x74, 0x80, 0x07, 0x18, 0x60, 0x7C, 0x00, 0x18, 0x00,
0x41, 0x82, 0xFF, 0xF8, 0x80, 0xE1, 0x00, 0x08, 0x80, 0x01, 0x00, 0x04, 0x7C, 0x08, 0x03, 0xA6,
0x4E, 0x80, 0x00, 0x20,
} ;
// Audio streaming replacement functions copied from Swiss r92
u32 __dvdLowAudioStatusNULL[17] = {
// execute function(1); passed in on r4
0x9421FFC0, // stwu sp, -0x0040 (sp)
0x7C0802A6, // mflr r0
0x90010000, // stw r0, 0 (sp)
0x7C8903A6, // mtctr r4
0x3C80CC00, // lis r4, 0xCC00
0x2E830000, // cmpwi cr5, r3, 0
0x4196000C, // beq- cr5, +0xC ?
0x38600001, // li r3, 1
0x48000008, // b +0x8 ?
0x38600000, // li r3, 0
0x90646020, // stw r3, 0x6020 (r4)
0x38600001, // li r3, 1
0x4E800421, // bctrl
0x80010000, // lwz r0, 0 (sp)
0x7C0803A6, // mtlr r0
0x38210040, // addi sp, sp, 64
0x4E800020 // blr
};
u32 __dvdLowAudioConfigNULL[10] = {
// execute callback(1); passed in on r5 without actually touching the drive!
0x9421FFC0, // stwu sp, -0x0040 (sp)
0x7C0802A6, // mflr r0
0x90010000, // stw r0, 0 (sp)
0x7CA903A6, // mtctr r5
0x38600001, // li r3, 1
0x4E800421, // bctrl
0x80010000, // lwz r0, 0 (sp)
0x7C0803A6, // mtlr r0
0x38210040, // addi sp, sp, 64
0x4E800020 // blr
};
u32 __dvdLowReadAudioNULL[] = {
// execute callback(1); passed in on r6 without actually touching the drive!
0x9421FFC0, // stwu sp, -0x0040 (sp)
0x7C0802A6, // mflr r0
0x90010000, // stw r0, 0 (sp)
0x7CC903A6, // mtctr r6
0x38600001, // li r3, 1
0x4E800421, // bctr;
0x80010000, // lwz r0, 0 (sp)
0x7C0803A6, // mtlr r0
0x38210040, // addi sp, sp, 64
0x4E800020
};
u32 __GXSetVAT_patch[31] = {
/*0x8122ce00,*/ 0x39400000, 0x896904f2, 0x7d284b78,
0x556007ff, 0x41820050, 0x38e00008, 0x3cc0cc01,
0x98e68000, 0x61400070, 0x61440080, 0x61430090,
0x98068000, 0x38000000, 0x80a8001c, 0x90a68000,
0x98e68000, 0x98868000, 0x8088003c, 0x90868000,
0x98e68000, 0x98668000, 0x8068005c, 0x90668000,
0x98068000, 0x556bf87f, 0x394a0001, 0x39080004,
0x4082ffa0, 0x38000000, 0x980904f2, 0x4e800020
};
u8 GXMObjects[][0x3C] =
{
{ // GXPal528IntDf
0x00, 0x00, 0x00, 0x04, 0x02, 0x80, 0x02, 0x10, 0x02, 0x10, 0x00, 0x28, 0x00, 0x17, 0x02, 0x80,
0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x08, 0x08, 0x0A, 0x0C, 0x0A, 0x08, 0x08, 0x00, 0x00, 0x00,
},
{ // GXEurgb60Hz480IntDf
0x00, 0x00, 0x00, 0x14, 0x02, 0x80, 0x01, 0xE0, 0x01, 0xE0, 0x00, 0x28, 0x00, 0x00, 0x02, 0x80,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x08, 0x08, 0x0A, 0x0C, 0x0A, 0x08, 0x08, 0x00, 0x00, 0x00,
},
{ // GXMpal480IntDf
0x00, 0x00, 0x00, 0x08, 0x02, 0x80, 0x01, 0xE0, 0x01, 0xE0, 0x00, 0x28, 0x00, 0x00, 0x02, 0x80,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x08, 0x08, 0x0A, 0x0C, 0x0A, 0x08, 0x08, 0x00, 0x00, 0x00,
},
{ // GXNtsc480IntDf
0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x01, 0xE0, 0x01, 0xE0, 0x00, 0x28, 0x00, 0x00, 0x02, 0x80,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x08, 0x08, 0x0A, 0x0C, 0x0A, 0x08, 0x08, 0x00, 0x00, 0x00,
},
{ // GXNtsc480Int
0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x01, 0xE0, 0x01, 0xE0, 0x00, 0x28, 0x00, 0x00, 0x02, 0x80,
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x00, 0x00, 0x15, 0x16, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00,
},
};
u32 DVDGetDriveStatus[] = {
0x38600000, // li r3, 0
0x4E800020
};
FuncPattern FPatterns[] =
{
{ 0xCC, 17, 10, 5, 3, 2, DVDInquiryAsync, sizeof(DVDInquiryAsync), "DVDInquiryAsync", 0, 0 },
{ 0xC8, 16, 9, 5, 3, 3, DVDSeekAbsAsyncPrio, sizeof(DVDSeekAbsAsyncPrio), "DVDSeekAbsAsyncPrio", 0, 0 },
{ 0xA8, 10, 4, 4, 6, 3, (u8*)DVDGetDriveStatus, sizeof(DVDGetDriveStatus), "DVDGetDriveStatus", 0, 0 },
{ 0xD4, 13, 8, 11, 2, 7, (u8*)NULL, 0xdead0004, "AIResetStreamSampleCount", 0, 0 },
{ 0x10C, 30, 18, 5, 2, 3, (u8*)NULL, 0xdead0002, "DVDLowRead A", 0, 0 },
{ 0xDC, 23, 18, 3, 2, 4, (u8*)NULL, 0xdead0002, "DVDLowRead B", 0, 0 },
{ 0x104, 29, 17, 5, 2, 3, (u8*)NULL, 0xdead0002, "DVDLowRead C", 0, 0 },
{ 0xCC, 3, 3, 1, 0, 3, (u8*)NULL, 0xdead000C, "C_MTXPerspective", 0, 0 },
{ 0xC8, 3, 3, 1, 0, 3, (u8*)NULL, 0xdead000D, "C_MTXLightPerspective", 0, 0 },
{ 0x94, 18, 10, 2, 0, 2, (u8*)__dvdLowReadAudioNULL, sizeof(__dvdLowReadAudioNULL), "DVDLowReadAudio", 0, 0 },
{ 0x88, 18, 8, 2, 0, 2, (u8*)__dvdLowAudioStatusNULL, sizeof(__dvdLowAudioStatusNULL), "DVDLowAudioStatus", 0, 0 },
{ 0x98, 19, 8, 2, 1, 3, (u8*)__dvdLowAudioConfigNULL, sizeof(__dvdLowAudioConfigNULL), "DVDLowAudioConfig", 0, 0 },
{ 0x308, 40, 18, 10, 23, 17, patch_fwrite_GC, sizeof(patch_fwrite_GC), "__fwrite A", 1, 0 },
{ 0x338, 48, 20, 10, 24, 16, patch_fwrite_GC, sizeof(patch_fwrite_GC), "__fwrite B", 1, 0 },
{ 0x2D8, 41, 17, 8, 21, 13, patch_fwrite_GC, sizeof(patch_fwrite_GC), "__fwrite C", 1, 0 },
{ 0x98, 8, 3, 0, 3, 5, (u8*)NULL, 0xdead0001, "__GXSetVAT", 0, 0 },
{ 0x3A8, 86, 13, 27, 17, 24, (u8*)NULL, 0xdead000B, "PADRead A", 2, 0 },
{ 0x2FC, 73, 8, 23, 16, 15, (u8*)NULL, 0xdead000B, "PADRead B", 2, 0 },
{ 0x3B0, 87, 13, 27, 17, 25, (u8*)NULL, 0xdead000B, "PADRead C", 2, 0 },
{ 0x334, 78, 7, 20, 17, 19, (u8*)NULL, 0xdead000B, "PADRead D", 2, 0 },
{ 0x2A8, 66, 4, 20, 17, 14, (u8*)NULL, 0xdead000B, "PADRead E", 2, 0 },
};
FuncPattern CPatterns[] =
{
{ 0x14C, 28, 12, 7, 12, 4, CARDFreeBlocks, sizeof(CARDFreeBlocks), "CARDFreeBlocks A", 1, 0 },
{ 0x11C, 24, 10, 7, 10, 4, CARDFreeBlocks, sizeof(CARDFreeBlocks), "CARDFreeBlocks B", 1, 0 },
{ 0xC0, 22, 5, 2, 5, 10, CARDGetSerialNo, sizeof(CARDGetSerialNo),"CARDGetSerialNo", 0, 0 },
{ 0x84, 12, 5, 3, 4, 2, CARDGetEncoding, sizeof(CARDGetEncoding),"CARDGetEncoding", 0, 0 },
{ 0x80, 11, 5, 3, 4, 2, CARDGetMemSize, sizeof(CARDGetMemSize), "CARDGetMemSize", 0, 0 },
{ 0x94, 11, 6, 3, 5, 4, __CARDSync, sizeof(__CARDSync), "__CARDSync", 0, 0 },
{ 0x50, 6, 3, 2, 2, 2, CARDCheck, sizeof(CARDCheck), "CARDCheck", 0, 0 },
//{ 0x24, 4, 2, 1, 0, 2, CARDCheckAsync, sizeof(CARDCheckAsync), "CARDCheckAsync", 0, 0 },
{ 0x58C, 82, 11, 18, 41, 57, CARDCheckEX, sizeof(CARDCheckEX), "CARDCheckExAsync", 0, 0 },
{ 0x34, 4, 2, 1, 2, 2, CARDProbe, sizeof(CARDProbe), "CARDProbe", 2, 0 },
//{ 0x1C, 2, 2, 1, 0, 2, CARDProbe, sizeof(CARDProbe), "CARDProbe B", 2, 0 }, //This is causing more trouble than a hack...
{ 0x178, 20, 6, 6, 20, 4, CARDProbeEX, sizeof(CARDProbeEX), "CARDProbeEx A", 3, 0 },
{ 0x198, 22, 6, 5, 19, 4, CARDProbeEX, sizeof(CARDProbeEX), "CARDProbeEx B", 3, 0 },
{ 0x160, 17, 6, 5, 18, 4, CARDProbeEX, sizeof(CARDProbeEX), "CARDProbeEx C", 3, 0 },
{ 0x19C, 32, 14, 11, 12, 3, CARDMountAsync, sizeof(CARDMountAsync), "CARDMountAsync A", 4, 0 },
{ 0x184, 30, 14, 11, 10, 3, CARDMountAsync, sizeof(CARDMountAsync), "CARDMountAsync B", 4, 0 },
{ 0xA8, 15, 8, 6, 3, 2, CARDCheck, sizeof(CARDCheck), "CARDUnMount", 0, 0 },
{ 0x174, 23, 6, 7, 14, 5, CARDOpen, sizeof(CARDOpen), "CARDOpen A", 5, 0 },
{ 0x118, 14, 6, 6, 11, 4, CARDOpen, sizeof(CARDOpen), "CARDOpen B", 5, 0 },
{ 0x170, 23, 6, 7, 14, 5, CARDOpen, sizeof(CARDOpen), "CARDOpen C", 5, 0 },
{ 0x15C, 27, 6, 5, 15, 6, CARDFastOpen, sizeof(CARDFastOpen), "CARDFastOpen A", 11, 0 },
{ 0x100, 20, 10, 4, 10, 4, CARDFastOpen, sizeof(CARDFastOpen), "CARDFastOpen B", 11, 0 },
{ 0x50, 8, 4, 2, 2, 3, CARDClose, sizeof(CARDClose), "CARDClose", 0, 0 },
{ 0x21C, 44, 6, 13, 19, 12, CARDCreate, sizeof(CARDCreate), "CARDCreateAsync A", 6, 0 },
{ 0x214, 42, 6, 13, 19, 12, CARDCreate, sizeof(CARDCreate), "CARDCreateAsync B", 6, 0 },
{ 0x10C, 25, 6, 9, 9, 5, CARDDelete, sizeof(CARDDelete), "CARDDeleteAsync A", 12, 0 },
{ 0x10C, 25, 6, 9, 9, 5, CARDDelete, sizeof(CARDDelete), "CARDDeleteAsync C", 12, 0 },
{ 0x128, 24, 7, 9, 12, 5, CARDFastDelete, sizeof(CARDFastDelete), "CARDFastDelete", 0, 0 },
{ 0x144, 27, 3, 8, 10, 9, CARDRead, sizeof(CARDRead), "CARDReadAsync A", 7, 0 },
{ 0x140, 30, 7, 7, 10, 10, CARDRead, sizeof(CARDRead), "CARDReadAsync B", 7, 0 },
{ 0x140, 27, 3, 8, 10, 9, CARDRead, sizeof(CARDRead), "CARDReadAsync C", 7, 0 },
{ 0x110, 24, 4, 8, 9, 6, CARDWrite, sizeof(CARDWrite), "CARDWriteAsync A", 8, 0 },
{ 0x10C, 23, 4, 8, 9, 6, CARDWrite, sizeof(CARDWrite), "CARDWriteAsync B", 8, 0 },
{ 0x1F8, 37, 3, 17, 18, 9, CARDRename, sizeof(CARDRename), "CARDRenameAsync", 0, 0 },
{ 0x128, 25, 9, 9, 6, 5, CARDGetStats, sizeof(CARDGetStats), "CARDGetStatus A", 9, 0 },
{ 0x110, 25, 9, 8, 6, 5, CARDGetStats, sizeof(CARDGetStats), "CARDGetStatus B", 9, 0 },
{ 0x124, 25, 9, 9, 6, 5, CARDGetStats, sizeof(CARDGetStats), "CARDGetStatus C", 9, 0 },
{ 0x170, 29, 9, 9, 12, 5, CARDSetStats, sizeof(CARDSetStats), "CARDSetStatusAsync A", 10, 0 },
{ 0x16C, 29, 9, 9, 12, 5, CARDSetStats, sizeof(CARDSetStats), "CARDSetStatusAsync B", 10, 0 },
};
u32 CardLowestOff = 0;
u32 FB[MAX_FB];
void SMenuAddFramebuffer( void )
{
u32 i,j,f;
if( *(vu32*)FBEnable != 1 )
return;
FrameBuffer = (*(vu32*)FBOffset) & 0x7FFFFFFF;
for( i=0; i < MAX_FB; i++)
{
if( FB[i] ) //add a new entry
continue;
//check if we already know this address
f=0;
for( j=0; j<i; ++j )
{
if( FrameBuffer == FB[j] ) // already known!
{
f=1;
return;
}
}
if( !f && FrameBuffer && FrameBuffer < 0x14000000 ) // add new entry
{
dbgprintf("ES:Added new FB[%d]:%08X\n", i, FrameBuffer );
FB[i] = FrameBuffer;
switch( *(vu32*)(FBEnable+0x20) )
{
case VI_NTSC:
FBSize = 304*480*4;
break;
case VI_PAL:
FBSize = 320*480*4;
break;
case VI_EUR60:
FBSize = 320*480*4;
break;
default:
dbgprintf("ES:SMenuFindOffsets():Invalid Video mode:%d\n", *(vu32*)(FBEnable+0x20) );
break;
}
}
}
}
void ScreenShot( void )
{
if( *(vu32*)FBEnable != 1 )
return;
set32( HW_GPIO_OUT, 1<<5 );
char *str = (char*)malloc( 64 );
u32 i=0,r,wrote;
FIL SCRFile;
sprintf( str, "/screenshots" );
f_mkdir( str );
do
{
sprintf( str, "/screenshots/scrn_%02X.raw", i++ );
r = f_open( &SCRFile, str, FA_CREATE_NEW|FA_WRITE );
if( r == FR_OK )
{
break;
} else {
if( r != FR_EXIST )
{
dbgprintf("Failed to create file:%u\n", r );
clear32( HW_GPIO_OUT, 1<<5 );
return;
}
}
} while(1);
free(str);
if( r == FR_OK )
{
f_write( &SCRFile, (void*)(FB[0]), FBSize, &wrote );
f_close( &SCRFile );
}
clear32( HW_GPIO_OUT, 1<<5 );
}
void PatchB( u32 dst, u32 src )
{
u32 newval = (dst - src);
newval&= 0x03FFFFFC;
newval|= 0x48000000;
write32( src, newval );
}
void PatchBL( u32 dst, u32 src )
{
u32 newval = (dst - src);
newval&= 0x03FFFFFC;
newval|= 0x48000001;
write32( src, newval );
}
void PatchFunc( char *ptr )
{
u32 i = 0;
u32 reg=-1;
while(1)
{
u32 op = read32( (u32)ptr + i );
if( op == 0x4E800020 ) // blr
break;
if( (op & 0xFC00FFFF) == 0x3C00CC00 ) // lis rX, 0xCC00
{
reg = (op & 0x3E00000) >> 21;
write32( (u32)ptr + i, (reg<<21) | 0x3C00C000 ); // Patch to: lis rX, 0xC000
dbgprintf("[%08X] %08X: lis r%u, 0xC000\n", (u32)ptr+i, read32( (u32)ptr+i), reg );
}
if( (op & 0xFC00FFFF) == 0x3C00A800 ) // lis rX, 0xA800
{
write32( (u32)ptr + i, (op & 0x3E00000) | 0x3C00A700 ); // Patch to: lis rX, 0xA700
dbgprintf("[%08X] %08X: lis rX, 0xA700\n", (u32)ptr+i, read32( (u32)ptr+i) );
}
if( (op & 0xFC00FFFF) == 0x38006000 ) // addi rX, rY, 0x6000
{
u32 src = (op >> 16) & 0x1F;
u32 dst = (op >> 21) & 0x1F;
if( src == reg )
{
write32( (u32)ptr + i, (dst<<21) | (src<<16) | 0x38002F00 ); // Patch to: addi rX, rY, 0x2F00
dbgprintf("[%08X] %08X: addi r%u, r%u, 0x2F00\n", (u32)ptr+i, read32( (u32)ptr+i), dst, src );
}
}
if( (op & 0xFC000000 ) == 0x90000000 )
{
u32 src = (op >> 16) & 0x1F;
u32 dst = (op >> 21) & 0x1F;
u32 val = op & 0xFFFF;
if( src == reg )
{
if( (val & 0xFF00) == 0x6000 ) // case with 0x60XY(rZ)
{
write32( (u32)ptr + i, (dst<<21) | (src<<16) | 0x2F00 | (val&0xFF) | 0x90000000 ); // Patch to: stw rX, 0x2FXY(rZ)
dbgprintf("[%08X] %08X: stw r%u, 0x%04X(r%u)\n", (u32)ptr+i, read32( (u32)ptr+i), dst, 0x2F00 | (val&0xFF), src );
}
}
}
i += 4;
}
}
void MPattern( u8 *Data, u32 Length, FuncPattern *FunctionPattern )
{
u32 i;
memset( FunctionPattern, 0, sizeof(FuncPattern) );
for( i = 0; i < Length; i+=4 )
{
u32 word = read32( (u32)Data + i );
if( (word & 0xFC000003) == 0x48000001 )
FunctionPattern->FCalls++;
if( (word & 0xFC000003) == 0x48000000 )
FunctionPattern->Branch++;
if( (word & 0xFFFF0000) == 0x40800000 )
FunctionPattern->Branch++;
if( (word & 0xFFFF0000) == 0x41800000 )
FunctionPattern->Branch++;
if( (word & 0xFFFF0000) == 0x40810000 )
FunctionPattern->Branch++;
if( (word & 0xFFFF0000) == 0x41820000 )
FunctionPattern->Branch++;
if( (word & 0xFC000000) == 0x80000000 )
FunctionPattern->Loads++;
if( (word & 0xFF000000) == 0x38000000 )
FunctionPattern->Loads++;
if( (word & 0xFF000000) == 0x3C000000 )
FunctionPattern->Loads++;
if( (word & 0xFC000000) == 0x90000000 )
FunctionPattern->Stores++;
if( (word & 0xFC000000) == 0x94000000 )
FunctionPattern->Stores++;
if( (word & 0xFF000000) == 0x7C000000 )
FunctionPattern->Moves++;
if( word == 0x4E800020 )
break;
}
FunctionPattern->Length = i;
}
bool CPattern( FuncPattern *FPatA, FuncPattern *FPatB )
{
if( memcmp( FPatA, FPatB, sizeof(u32) * 6 ) == 0 )
return true;
else
return false;
}
void DoCardPatches( char *ptr, u32 size, u32 SectionOffset )
{
u32 i,j,k,offset,fail,FoundCardFuncStart=0;
dbgprintf("DoCardPatches( 0x%p, %d, 0x%X)\n", ptr, size, SectionOffset );
for( i=0; i < size; i+=4 )
{
if( read32( (u32)ptr + i) == 0x7C630214 && read32( (u32)ptr + i + 4) == 0x806300B8 )
{
dbgprintf("Found [CARDGetXferredBytes] @ 0x%08X\n", (u32)ptr + i - 12 );
memcpy( ptr + i - 12, CARDGetXferredBytes, sizeof(CARDGetXferredBytes) );
}
if( read32( (u32)ptr + i ) != 0x7C0802A6 ) // MFLR
continue;
FuncPattern fp;
MPattern( (u8*)(ptr+i), size, &fp );
for( j=0; j < sizeof(CPatterns)/sizeof(FuncPattern); ++j )
{
if( CPatterns[j].PatchLength == 0 )
continue;
if( CPatterns[j].Found ) // Skip already found patches
continue;
if( CPattern( &fp, &(CPatterns[j]) ) )
{
if( CPatterns[j].Patch == CARDFreeBlocks )
{
if( CardLowestOff == 0 )
{
dbgprintf("CardLowestOff:0x%08X\n", i );
CardLowestOff = i;
}
//Check for CARDGetResultCode which is always (when used) above CARDFreeBlocks
if( read32( (u32)ptr + i - 0x30 ) == 0x2C030000 )
{
dbgprintf("Found [CARDGetResultCode] @ 0x%08X\n", (u32)ptr + i - 0x30 + SectionOffset );
memcpy( ptr + i - 0x30, CARDGetResultCode, sizeof(CARDGetResultCode) );
}
FoundCardFuncStart = 1;
}
if( FoundCardFuncStart == 0 )
continue;
dbgprintf("Found [%s] @ 0x%08X\n", CPatterns[j].Name, (u32)ptr + i + SectionOffset );
CPatterns[j].Found = (u32)ptr + i;
// If this is a patch group set all others of this group as found aswell
if( CPatterns[j].Group )
{
for( k=0; k < sizeof(CPatterns)/sizeof(FuncPattern); ++k )
{
if( CPatterns[k].Group == CPatterns[j].Group )
{
if( !CPatterns[k].Found ) //Don't overwrite the offset!
CPatterns[k].Found = -1; // Usually this holds the offset, to determinate it from a REALLY found pattern we set it -1 which still counts a logical TRUE
//dbgprintf("Setting [%s] to found!\n", CPatterns[k].Name );
}
}
}
//If by now no CARDProbe is found it won't be so set it to found to prevent CARDProbe B false hits
if( CPatterns[j].Patch == CARDRead )
{
for( k=0; k < sizeof(CPatterns)/sizeof(FuncPattern); ++k )
{
if( CPatterns[k].Patch == CARDProbe )
{
if( !CPatterns[k].Found ) //Don't overwrite the offset!
CPatterns[k].Found = -1;
}
}
}
if( strstr( CPatterns[j].Name, "Async" ) != NULL )
{
//dbgprintf("Async!\n");
//Most games only use the normal functions so we patch a branch over to async and clear the CB
//Find function call to our function
offset = (u32)ptr + i;
fail = 0;
while(fail < 3)
{
//dbgprintf("[%08X] %08X %08X(%08X)\n", offset, read32( offset ) & 0xFC000003,read32( offset ) & 0x03FFFFFC ,(read32( offset ) & 0x03FFFFFC ) + offset);
if( (read32( offset ) & 0xFC000003 ) == 0x48000001 )
{
if( (((read32( offset ) & 0x03FFFFFC ) + offset) & 0x03FFFFFC) == (u32)ptr+i )
break;
}
if( read32( offset ) == 0x4E800020 )
fail++;
offset+=4;
}
if( fail < 3 )
{
dbgprintf("Found function call to [%s] @ 0x%08X\n", CPatterns[j].Name, offset + SectionOffset );
//Now find function start
offset -= 4;
while(1)
{
if( read32( offset ) == 0x7C0802A6 )
break;
offset-=4;
}
dbgprintf("Found function start of [%s(Sync)] @ 0x%08X\n", CPatterns[j].Name, offset + SectionOffset );
//This patches a li rX, 0 before the Async function call for the Sync call
//Since this register of the cb is different per function we do this haxx
if( (read32( offset + 0x04 ) & 0x0000F000 ) == 0x00008000 ) // lis
{
write32( offset, read32( offset + 0x0C ) & 0xFBE00000 );
offset += 4;
if( CPatterns[j].Patch == CARDCheckEX )
{
write32( offset, 0x38800000 ); // lis r4,0
offset += 4;
}
//Forge a branch to the async function
u32 newval = ((u32)ptr + i) - offset;
newval&= 0x03FFFFFC;
newval|= 0x48000000;
write32( offset, newval );
} else {
dbgprintf("Unhandled Async cb case!\n");
}
} else {
dbgprintf("No sync function found!\n");
}
memcpy( ptr + i, CPatterns[j].Patch, CPatterns[j].PatchLength );
} else {
memcpy( ptr + i, CPatterns[j].Patch, CPatterns[j].PatchLength );
}
}
}
}
//if( CardLowestOff )
//{
// for( j=0; j < sizeof(CPatterns)/sizeof(FuncPattern); ++j )
// {
// if( CPatterns[j].Found == 0 )
// dbgprintf("Pattern %s not found!\n", CPatterns[j].Name );
// }
//}
return;
}
void DoPatches( char *ptr, u32 size, u32 SectionOffset )
{
u32 i=0,j=0,k=0,value;
u32 PatchCount = 0;
u32 r13 = 0;
FBOffset = 0;
FBEnable = 0;
CardLowestOff = 0;
dbgprintf("DoPatches( 0x%p, %d, 0x%X)\n", ptr, size, SectionOffset );
// HACK: PokemonXD and Pokemon Colosseum low memory clear patch
if(( (read32(0)>>8) == 0x475858 ) || ( (read32(0)>>8) == 0x474336 ))
{
// patch out initial memset(0x1800, 0, 0x1800)
if( (read32(0) & 0xFF) == 0x4A ) // JAP
write32( 0x560C, 0x60000000 );
else // EUR/USA
write32( 0x5614, 0x60000000 );
// patch memset to jump to test function
write32(0x00005498, 0x4BFFABF0);
// patch in test < 0x3000 function
write32(0x00000088, 0x3D008000);
write32(0x0000008C, 0x61083000);
write32(0x00000090, 0x7C044000);
write32(0x00000094, 0x4180542C);
write32(0x00000098, 0x90E40004);
write32(0x0000009C, 0x48005400);
// skips __start init of debugger mem
write32(0x00003194, 0x48000028);
}
// Reset Found
for( k=0; k < sizeof(FPatterns)/sizeof(FuncPattern); ++k )
FPatterns[k].Found = 0;
if( ConfigGetConfig(DML_CFG_NMM) )
DoCardPatches( ptr, size, SectionOffset );
//Note: ORing the values prevents an early break out when a single patterns has multiple hits
PatchCount=1;
for( i=0; i < size; i+=4 )
{
if( (PatchCount & 2) == 0 )
if( (read32( (u32)ptr + i ) & 0xFC00FFFF) == 0x5400077A &&
(read32( (u32)ptr + i + 4 ) & 0xFC00FFFF) == 0x28000000 &&
read32( (u32)ptr + i + 8 ) == 0x41820008 &&
(read32( (u32)ptr + i +12 ) & 0xFC00FFFF) == 0x64002000
)
{
dbgprintf("Patch:Found [__OSDispatchInterrupt]: 0x%08X 0x%08X\n", (u32)ptr + i + 0 + SectionOffset, (u32)ptr + i + 0x1A8 + SectionOffset );
write32( (u32)ptr + i + 0, (read32( (u32)ptr + i + 0 ) & 0xFFFF0000) | 0x0463 );
write32( (u32)ptr + i + 0x1A8, (read32( (u32)ptr + i + 0x1A8 ) & 0xFFFF0000) | 0x0463 );
PatchCount |= 2;
}
if( (PatchCount & 4) == 0 )
if( read32( (u32)ptr + i ) == 0x5480056A &&
read32( (u32)ptr + i + 4 ) == 0x28000000 &&
read32( (u32)ptr + i + 8 ) == 0x40820008 &&
(read32( (u32)ptr + i +12 )&0xFC00FFFF) == 0x60000004
)
{
dbgprintf("Patch:Found [SetInterruptMask]: 0x%08X\n", (u32)ptr + i + 12 + SectionOffset );
write32( (u32)ptr + i + 12, (read32( (u32)ptr + i + 12 ) & 0xFFFF0000) | 0x4000 );
PatchCount |= 4;
}
if( (PatchCount & 8) == 0 )
if( (read32( (u32)ptr + i + 0 ) & 0xFFFF) == 0x6000 &&
(read32( (u32)ptr + i + 4 ) & 0xFFFF) == 0x002A &&
(read32( (u32)ptr + i + 8 ) & 0xFFFF) == 0x0054
)
{
u32 Offset = (u32)ptr + i - 8;
dbgprintf("Patch:Found [__DVDIntrruptHandler]: 0x%08X\n", Offset + SectionOffset );
if( (read32(Offset+4) & 0xFFFF) == 0xCC00 ) // Loader
{
value = *(vu32*)(Offset+4);
value&= 0xFFFF0000;
value|= 0x0000C000;
*(vu32*)(Offset+4) = value;
} else {
value = *(vu32*)Offset;
value&= 0xFFFF0000;
value|= 0x0000C000;
*(vu32*)Offset = value;
}
Offset += 8;
value = *(vu32*)Offset;
value&= 0xFFFF0000;
value|= 0x00002F30;
*(vu32*)Offset = value;
Offset += 20;
dbgprintf("Patch:[__DVDInterruptHandler] 0x%08X\n", Offset + SectionOffset );
*(vu32*)Offset = 0x3D00CD00; Offset += 4;
*(vu32*)Offset = 0x38000034; Offset += 4;
*(vu32*)Offset = 0x90080004; Offset +=16;
dbgprintf("Patch:[__DVDInterruptHandler] 0x%08X\n", Offset + SectionOffset );
*(vu32*)Offset = 0x3D00CD00; Offset += 4;
*(vu32*)Offset = 0x3C004000; Offset += 4;
*(vu32*)Offset = 0x90080030; Offset +=32;
if( (read32(Offset-8) & 0xFFFF) == 0xCC00 ) // Loader
{
Offset -= 8;
}
dbgprintf("Patch:[__DVDInterruptHandler] 0x%08X\n", Offset + SectionOffset );
value = *(vu32*)Offset;
value&= 0xFFFF0000;
value|= 0x0000C000;
*(vu32*)Offset = value;
Offset += 4;
value = *(vu32*)Offset;
value&= 0xFFFF0000;
value|= 0x00002F08;
*(vu32*)Offset = value;
PatchCount |= 8;
}
if( (PatchCount & 16) == 0 )
if( ConfigGetConfig(DML_CFG_CHEATS) || ConfigGetConfig( DML_CFG_DEBUGGER ) )
{
// OSSleepThread(Pattern 1)
if( (PatchCount & 16) == 0 )
if( read32((u32)ptr + i + 0 ) == 0x3C808000 &&
( read32((u32)ptr + i + 4 ) == 0x38000004 || read32((u32)ptr + i + 4 ) == 0x808400E4 ) &&
( read32((u32)ptr + i + 8 ) == 0x38000004 || read32((u32)ptr + i + 8 ) == 0x808400E4 )
)
{
int j = 12;
while( read32((u32)ptr + i + j ) != 0x4E800020 )
j+=4;
dbgprintf("Patch:[Hook:OSSleepThread] at 0x%08X\n", ((u32)ptr + i + j) | 0x80000000 );
u32 DBGSize;
if( ConfigGetConfig( DML_CFG_DEBUGGER ) )
{
memcpy( (void*)0x1800, kenobigcDBG, sizeof(kenobigcDBG) );
DBGSize = sizeof(kenobigcDBG);
} else {
memcpy( (void*)0x1800, kenobigc, sizeof(kenobigc) );
DBGSize = sizeof(kenobigc);
}
if( ConfigGetConfig(DML_CFG_DEBUGWAIT) )
write32( P2C(read32(0x1808)), 1 );
else
write32( P2C(read32(0x1808)), 0 );
u32 newval = 0x18A8 - ((u32)ptr + i + j);
newval&= 0x03FFFFFC;
newval|= 0x48000000;
write32( (u32)ptr + i + j, newval );
memcpy( (void*)0x1800, (void*)0, 6 );
char *path = (char*)malloc( 128 );
if( ConfigGetConfig(DML_CFG_CHEAT_PATH) )
{
sprintf( path, "%s", ConfigGetCheatPath() );
} else {
sprintf( path, "/games/%.6s/%.6s.gct", (char*)0x1800, (char*)0x1800 );
}
FIL CodeFD;
u32 read;
if( f_open( &CodeFD, path, FA_OPEN_EXISTING|FA_READ ) == FR_OK )
{
if( CodeFD.fsize >= 0x2E60 - (0x1800+DBGSize-8) )
{
dbgprintf("Patch:Cheatfile is too large, it must not be large than %d bytes!\n",
0x2E60 - (0x1800+DBGSize-8));
} else {
if( f_read( &CodeFD, (void*)(0x1800+DBGSize-8), CodeFD.fsize, &read ) == FR_OK )
{
dbgprintf("Patch:Copied cheat file to memory\n");
write32( 0x1804, 1 );
} else
dbgprintf("Patch:Failed to read cheat file:\"%s\"\n", path );
}
f_close( &CodeFD );
} else {
dbgprintf("Patch:Failed to open/find cheat file:\"%s\"\n", path );
}
free(path);
PatchCount |= 16;
}
}
if( (PatchCount & 32) == 0 )
{
if( (read32( (u32)ptr + i + 0 ) & 0xFFFF) == 0xCC00 && // Game
(read32( (u32)ptr + i + 4 ) & 0xFFFF) == 0x6000 &&
(read32( (u32)ptr + i +12 ) & 0xFFFF) == 0x001C
)
{
u32 Offset = (u32)ptr + i;
dbgprintf("Patch:[cbForStateBusy] 0x%08X\n", Offset + SectionOffset );
write32( Offset, 0x3C80C000 );
write32( Offset+4, 0x38842F30 );
PatchCount |= 32;
} else if( (read32( (u32)ptr + i + 0 ) & 0xFFFF) == 0xCC00 && // Loader
(read32( (u32)ptr + i + 4 ) & 0xFFFF) == 0x6018 &&
(read32( (u32)ptr + i +12 ) & 0xFFFF) == 0x001C
)
{
u32 Offset = (u32)ptr + i;
dbgprintf("Patch:[cbForStateBusy] 0x%08X\n", Offset + SectionOffset );
write32( Offset, 0x3C60C000 );
write32( Offset+4, 0x80832F48 );
PatchCount |= 32;
}
}
if( (PatchCount & 64) == 0 )
{
if( read32( (u32)ptr + i + 0 ) == 0x3C608000 )
{
if( ((read32( (u32)ptr + i + 4 ) & 0xFC1FFFFF ) == 0x800300CC) && ((read32( (u32)ptr + i + 8 ) >> 24) == 0x54 ) )
{
dbgprintf( "Patch:[VIConfgiure] 0x%08X\n", (u32)(ptr+i) );
write32( *(vu32*)(ptr+i+4), 0x5400F0BE | ((read32( (u32)ptr + i + 4 ) & 0x3E00000) >> 5 ) );
PatchCount |= 64;
}
}
}
if( (PatchCount & 128) == 0 )
{
if( ConfigGetConfig(DML_CFG_SCREENSHOT) )
{
if( *(u32*)(ptr+i) >> 16 == 0x3DA0 && r13 == 0 )
{
r13 = ((*(u32*)(ptr+i)) & 0xFFFF) << 16;
r13|= (*(u32*)(ptr+i+4)) & 0xFFFF;
dbgprintf("Patch:r13:%08X\n", r13 );
}
if( memcmp( ptr+i, VISetFB, sizeof(VISetFB) ) == 0 && FBEnable == 0 )
{
dbgprintf("Patch:[VISetFB]%08X\n", (u32)ptr+i );
FBEnable = ( *(u32*)(ptr+i-4) );
if( FBEnable & 0x8000 )
{
FBEnable = ((~FBEnable) & 0xFFFF) + 1;
FBEnable = (r13 - FBEnable) & 0x7FFFFFF;
} else {
FBEnable = FBEnable & 0xFFFF;
FBEnable = (r13 + FBEnable) & 0x7FFFFFF;
}
FBOffset = FBEnable - 0x08;
// dbgprintf("FBOffset:%08X\n", FBOffset );
// dbgprintf("FBEnable:%08X\n", FBEnable );
for( j=0; j < MAX_FB; ++j )
FB[j] = 0;
PatchCount |= 128;
}
} else {
PatchCount |= 128;
}
}
if( (PatchCount & 256) == 0 ) //DVDLowStopMotor
{
if( read32( (u32)ptr + i ) == 0x3C00E300 )
{
u32 Offset = (u32)ptr + i;
dbgprintf("Patch:[DVDLowStopMotor] 0x%08X\n", Offset + SectionOffset );
value = *(vu32*)(Offset-12);
value&= 0xFFFF0000;
value|= 0x0000C000;
*(vu32*)(Offset-12) = value;
value = *(vu32*)(Offset-8);
value&= 0xFFFF0000;
value|= 0x00002F00;
*(vu32*)(Offset-8) = value;
value = *(vu32*)(Offset+4);
value&= 0xFFFF0000;
value|= 0x00002F08;
*(vu32*)(Offset+4) = value;
PatchCount |= 256;
}
}
if( (PatchCount & 512) == 0 ) //DVDLowReadDiskID
{
if( (read32( (u32)ptr + i ) & 0xFCFFFFFF ) == 0x3C00A800 && (read32( (u32)ptr + i + 4 ) & 0xFCFFFFFF ) == 0x38000040 )
{
u32 Offset = (u32)ptr + i;
dbgprintf("Patch:[DVDLowReadDiskID] 0x%08X\n", Offset + SectionOffset );
value = *(vu32*)(Offset);
value&= 0xFFFF0000;
value|= 0x0000A700;
*(vu32*)(Offset) = value;
value = *(vu32*)(Offset+0x20);
value&= 0xFFFF0000;
value|= 0x0000C000;
*(vu32*)(Offset+0x20) = value;
value = *(vu32*)(Offset+0x24);
value&= 0xFFFF0000;
value|= 0x00002F00;
*(vu32*)(Offset+0x24) = value;
value = *(vu32*)(Offset+0x2C);
value&= 0xFFFF0000;
value|= 0x00002F08;
*(vu32*)(Offset+0x2C) = value;
write32( 0x01576D4, 0x38600000 );
PatchCount |= 512;
}
}
if( ConfigGetConfig(DML_CFG_CHEATS) || ConfigGetConfig( DML_CFG_DEBUGGER ) )
{
if( PatchCount == 1023 )
break;
} else {
if( PatchCount == 1007 )
break;
}
}
if( ConfigGetVideMode() & DML_VID_FORCE )
{
k=0;
for( i=0; i < size; i+=4 )
{
for( j=0; j <= GXNtsc480Int; ++j )
{
if( memcmp( ptr+i, GXMObjects[j], 0x3C ) == 0 )
{
dbgprintf("Patch:Found GX pattern %u at %08X\n", j, ptr+i );
switch( ConfigGetVideMode() & 15 )
{
case DML_VID_FORCE_PAL50:
{
memcpy( ptr+i, GXMObjects[GXPal528IntDf], 0x3C );
} break;
case DML_VID_FORCE_PAL60:
{
memcpy( ptr+i, GXMObjects[GXEurgb60Hz480IntDf], 0x3C );
} break;
case DML_VID_FORCE_NTSC:
{
memcpy( ptr+i, GXMObjects[GXNtsc480IntDf], 0x3C );
} break;
}
k++;
}
}
if( k > 4 )
break;
}
}
for( i=0; i < size; i+=4 )
{
if( read32( (u32)ptr + i ) != 0x4E800020 )
continue;
i+=4;
FuncPattern fp;
MPattern( (u8*)(ptr+i), size, &fp );
for( j=0; j < sizeof(FPatterns)/sizeof(FuncPattern); ++j )
{
if( FPatterns[j].Found ) //Skip already found patches
continue;
if( CPattern( &fp, &(FPatterns[j]) ) )
{
u32 FOffset = (u32)ptr + i;
dbgprintf("Patch:Found [%s]: 0x%08X\n", FPatterns[j].Name, FOffset + SectionOffset );
switch( FPatterns[j].PatchLength )
{
case 0xdead0001: // Patch for __GXSetVAT, fixes the dungeon map freeze in Wind Waker
{
switch( read32(0) >> 8 )
{
case 0x505A4C: // The Legend of Zelda: Collector's Edition
if( !(DOLSize == 3847012 || DOLSize == 3803812) ) // only patch the main.dol of the Zelda:ww game
break;
case 0x475A4C: // The Legend of Zelda: The Wind Waker
{
write32(FOffset, (read32(FOffset) & 0xff00ffff) | 0x220000);
memcpy((void *)(FOffset + 4), __GXSetVAT_patch, sizeof(__GXSetVAT_patch));
dbgprintf("Patch:Applied __GXSetVAT patch\n");
} break;
default:
break;
}
} break;
case 0xdead0002: // DVDLowRead
{
PatchFunc( (char*)FOffset );
} break;
case 0xdead0004: // Audiostreaming hack
{
switch( read32(0) >> 8 )
{
case 0x474544: // Eternal Darkness
break;
default:
{
write32( FOffset + 0xB4, 0x60000000 );
write32( FOffset + 0xC0, 0x60000000 );
} break;
}
} break;
case 0xdead000B: // PADRead hook
{
if( !ConfigGetConfig(DML_CFG_PADHOOK) )
break;
//Find blr
j=0;
while(1)
{
if( read32( FOffset + j ) == 0x4E800020 )
break;
j+=4;
}
dbgprintf("Patch:[PADRead hook] %08X\n", FOffset + j );
memcpy( (void*)0x2ECC, padipc, sizeof(padipc) );
PatchB( 0x2ECC, FOffset + j );
write32( 0x12FC, 0 );
} break;
// Widescreen hack by Extrems
case 0xdead000C: // C_MTXPerspective
{
if( !ConfigGetConfig(DML_CFG_FORCE_WIDE) )
break;
*(volatile float *)0x00000050 = 0.5625f;
memcpy((void*)(FOffset+ 28),(void*)(FOffset+ 36),44);
memcpy((void*)(FOffset+188),(void*)(FOffset+192),16);
*(unsigned int*)(FOffset+52) = 0x48000001 | ((*(unsigned int*)(FOffset+52) & 0x3FFFFFC) + 8);
*(unsigned int*)(FOffset+72) = 0x3C600000 | (0x80000050 >> 16); // lis 3, 0x8180
*(unsigned int*)(FOffset+76) = 0xC0230000 | (0x80000050 & 0xFFFF); // lfs 1, -0x1C (3)
*(unsigned int*)(FOffset+80) = 0xEC240072; // fmuls 1, 4, 1
} break;
case 0xdead000D: // C_MTXLightPerspective
{
if( !ConfigGetConfig(DML_CFG_FORCE_WIDE) )
break;
*(volatile float *)0x00000050 = 0.5625f;
*(u32*)(FOffset+36) = *(u32*)(FOffset+32);
memcpy((void*)(FOffset+ 28),(void*)(FOffset+ 36),60);
memcpy((void*)(FOffset+184),(void*)(FOffset+188),16);
*(u32*)(FOffset+68) += 8;
*(u32*)(FOffset+88) = 0x3C600000 | (0x80000050 >> 16); // lis 3, 0x8180
*(u32*)(FOffset+92) = 0xC0230000 | (0x80000050 & 0xFFFF); // lfs 1, -0x90 (3)
*(u32*)(FOffset+96) = 0xEC240072; // fmuls 1, 4, 1
} break;
default:
{
if( ConfigGetConfig( DML_CFG_CHEATS ) )
{
if( FPatterns[j].Patch == patch_fwrite_GC )
break;
}
if( FPatterns[j].Patch == (u8*)DVDGetDriveStatus )
{
if( (read32(0) >> 8) != 0x474754 && // Chibi-Robo!
(read32(0) >> 8) != 0x475041 ) // Pok<6F>mon Channel
break;
dbgprintf("Patch:DVDGetDriveStatus\n");
}
if( (FPatterns[j].Length >> 16) == 0xdead )
{
dbgprintf("DIP:Unhandled dead case:%08X\n", FPatterns[j].Length );
} else
{
memcpy( (void*)(FOffset), FPatterns[j].Patch, FPatterns[j].PatchLength );
if ((FPatterns[j].Patch == (u8 *)__dvdLowAudioStatusNULL) && ((read32(0) >> 8) == 0x47494B))
{
// Ikaruga resets to the main menu, if the returned status is 0(finished playing the stream), but it works if 1(still playing) is returned
write32(FOffset + 36, 0x38600001);
dbgprintf("Patch:LowAudioStatus patched for Ikaruga\n");
}
}
} break;
}
// If this is a patch group set all others of this group as found aswell
if( FPatterns[j].Group )
{
for( k=0; k < sizeof(FPatterns)/sizeof(FuncPattern); ++k )
{
if( FPatterns[k].Group == FPatterns[j].Group )
{
if( !FPatterns[k].Found ) // Don't overwrite the offset!
FPatterns[k].Found = -1; // Usually this holds the offset, to determinate it from a REALLY found pattern we set it -1 which still counts a logical TRUE
//dbgprintf("Setting [%s] to found!\n", FPatterns[k].Name );
}
}
}
}
}
}
}