pureikyubu/Docs/RE/Apploader.txt
ogamespec c21147ccff Cumulative changes all around project, regarding Stage 1
Docs: New TODO moved to EMU, old renamed to old_todo
Docs: Old coding style is deprecated (old_style.txt)
Docs: Removed mention about CubeDocumented and fixed old emails
RE: boot.s, proved first asm line (lis instruction parameter)
RE: Added PAL and NTSC Boot and IPL IDA files
RE: Some work on NTSC IPL (identified many lib calls, including OS, GX)
RE: Added EXI Bootrom descrambler by segher
RE: GXInit
RE: Internal GX lib structures (GXPrivate.h)
RE: More details on lomem (OS versions)
RE: OSInit and OS.c
RE: OSAlloc (heap allocator)
RE: Very first code of Metrowerk runtime (__start.c)
Docs: Added copy of http://gcdev.narod.ru
Source\Utils: Command processor (Cmd.c)
Source\Utils: File wrapper
Source\Utils: Gekko disasm cleaned up and ported to plain C
Source\Utils: Double-linked lists
Source\Utils: Ported old Profiler code
Source\Utils: String utils
2015-09-04 19:45:10 +03:00

704 lines
23 KiB
Text

Gamecube apploader reverse engineering.
This reversing is not pretending to be compilable, and can be used for
education purposes only. I dont hold any responsibility for damage of your
Gamecube clock settings or memory card saves or else, produced from using
code/information represented in this document.
Version 1.1. (26 Apr 2005)
Additions and corrections by tmbinc.
------------------------------------------------------------------------------
Overview.
Complete Luigi's Apploader. Reverse work by org <ogamespec@gmail.com>
Other games seems to contain the same code with minor changes due to
revision, so you may think, that apploader is the same for all games.
Except Datel's FreeLoader and ActionReplay DVDs, which are using custom
apploader binary (simplified Nintendo's apploader).
Apploader is a small program layer between GC bootrom and main
application. Since main application's executable binary format may vary,
apploader is needed to boot executable in main memory, using bootrom's
low-level DVD read routines. In practice, all known game apploaders are using
Nintendo's DOL format for executable files.
Format of apploader binary is simple. First goes apploader header, and then
actual apploader code.
Apploader header :
typedef struct
{
char revision[16]; // Revision compile date (example "2001/12/17")
u32 entryPoint; // Apploader's entrypoint
s32 size; // Size of apploader code
s32 trailerSize; // Size of safe trailer
u8 padding[4]; // zeroes
} LDRImage;
Full apploader size is sum of size and trailerSize, rounded up to 32 bytes.
Trailer is need for future apploader versions.
Apploader is located at 0x2440 offset on DVD and loaded by bootrom at
0x81200000 address. Then bootrom jumps to apploader's entrypoint :
void AplEntry(
void (**Init)(void (*OSReportCallback)(char *msg, ...)),
BOOL (**Main)(void **addr, s32 *length, s32 *offset),
void* (**Close)())
{
// Return location of apploader's Init, Main and Close routines.
*Init = AplInit;
*Main = AplMain;
*Close = AplClose;
}
After executing of entrypoint, bootrom will call in order: apploader's Init,
then Main, then Close. Main is called in loop, until it will not return 0.
--------------------------------------------------------------------------
PART I. Apploader Init.
Init call is clearing apploader's internal data, and saving there callback
of OSReport call (which is located inside IPL). OSReport is used to output
some debug messages, during apploader runtime.
Apploader's internal data :
// Some DVD transfers require multiple times to load.
BOOL SecondTimeForThePart;
void* SecondTimeAddr;
s32 SecondTimeLength;
s32 SecondTimeOffset;
int SecondTimePart;
DVDBB2 bb2; // DVD boot block
DolImage DolImage; // main DOL executable header
int Section; // DOL section
int Part; // Apploader's step
u32 +0x148 // ?? only cleared in Init
// OSReport routine itself is placed somewhere in IPL
void (*OSReport)(char *msg, ...);
#pragma pack(32)
// First 32 bytes of DVDBI2 will be loaded here. This is a bit hacky.
// We dont use whole DVDBI2 (its too big), to save some memory.
// 32 bytes - because its minimum DVD DMA transfer size (?)
s32 debugMonSize;
s32 simMemSize;
u32 argOffset;
u32 debugFlag;
int FSTHigh; // 1, if FST is resided on DVD above DOL
u8 dummy[12]; // ending bytes
#pragma pack(0)
void AplInit(void (*OSReportCallback)(char *msg, ...))
{
// Clear some structures
memset(&bb2, 0, sizeof(DVDBB2));
memset(&DolImage, 0, sizeof(DolImage));
Section = 0;
Part = 0;
[+0x148] = 0;
OSReport = OSReportCallback; // Save report callback
// Print apploader version info
OSReport("Apploader Initialized. $ Revision: 28 $\n");
OSReport("This Apploader built %s %s\n", __DATE__, __TIME__);
}
--------------------------------------------------------------------------
PART II. Apploader Main.
Main is called in loop by IPL, until all data not loaded. IPL is using Main
output parameters, to load data from DVD. IPL keeps loading new data, until
Main will not return 0, signaling that all data loaded.
Just read through comments to understand code.
BOOL AplMain(void **addr, s32 *length, s32 *offset)
{
OSLoMem *LoMem = OSPhysicalToCached(0x0000);
switch(Part)
{
// Load BB2 structure.
case 0:
case 1:
*addr = &bb2;
*length = sizeof(DVDBB2);
*offset = DVD_BB2_OFFSET;
Part = 2;
DCInvalidateRange(*addr, *length);
break;
// Check FST length and load first 32 bytes of BI2 structure.
case 2:
u32 FSTLength = bb2.FSTLength;
u32 FSTMaxLength = bb2.FSTMaxLength;
if(FSTLength > FSTMaxLength)
{
OSReport(
"APPLOADER ERROR >>> FSTLength(%d) in BB2 is greater "
"than FSTMaxLength(%d)\n", FSTLength, FSTMaxLength );
PPCHalt();
}
*addr = &debugMonSize;
*length = 32;
*offset = DVD_BI2_OFFSET;
Part = 3;
DCInvalidateRange(*addr, *length);
break;
// Setup some OS lomem variables by BI2 values, and check memory configuration.
// Copy DVD BI2 in main memory.
case 3:
__OSDebugMonSize = debugMonSize;
__OSDebugMon = OSRoundDown32B(__OSPhysMemSize - __OSDebugMonSize);
__OSSimMemSize = simMemSize;
// Check debug monitor size alignment
if(__OSDebugMonSize & 31)
{
OSReport(
"APPLOADER ERROR >>> Debug monitor size (%d) should "
"be a multiple of 32\n", __OSDebugMonSize );
PPCHalt();
}
// Check console simulated memory size alignment
if(__OSSimMemSize & 31)
{
OSReport(
"APPLOADER ERROR >>> simulated memory size (%d) "
"should be a multiple of 32\n", __OSSimMemSize );
PPCHalt();
}
if(__OSSimMemSize == 0)
{
__OSSimMemSize = __OSPhysMemSize;
}
if(__OSSimMemSize < __OSPhysMemSize)
{
if(__OSDebugMonSize >= (__OSPhysMemSize - __OSSimMemSize))
{
OSReport(
"APPLOADER ERROR >>> [Physical memory size(0x%x)] "
"- [Console simulated memory size(0x%x)]\n"
"APPLOADER ERROR >>> must be greater than debug
"monitor size(0x%x)\n",
__OSPhysMemSize, __OSSimMemSize, __OSDebugMonSize );
PPCHalt();
}
// Move down FST address
bb2.FSTAddress = OSRoundDown32B(__OSSimMemSize - bb2.FSTMaxSize);
}
else
{
if(__OSSimMemSize == __OSPhysMemSize)
{
bb2.FSTAddress = OSRoundDown32B(__OSDebugMon - bb2.FSTMaxSize);
}
else
{
OSReport(
"APPLOADER ERROR >>> Physical memory size is 0x%x bytes.\n"
"APPLOADER ERROR >>> Console simulated memory size "
"must be smaller than or equal to the Physical memory size\n",
__OSPhysMemSize, __OSSimMemSize );
PPCHalt();
}
}
// Load whole BI2 in main memory. Reside it before FST.
__OSBootInfo2 = bb2.FSTAddress - OS_BI2_SIZE;
*addr = __OSBootInfo2;
*length = OS_BI2_SIZE;
*offset = DVD_BI2_OFFSET;
Part = 4;
DCInvalidateRange(*addr, *length);
break;
// Load FST, only if FST placed before boot file.
case 4:
FSTHigh = bb2.bootFilePosition < bb2.FSTPosition;
if(FSTHigh)
{
// Continue on part 5. Load FST later.
Part = 5;
}
else
{
*addr = bb2.FSTAddress;
*length = OSRoundUp32B(bb2.FSTLength);
*offset = bb2.FSTPosition;
Part = 5;
if(*addr > 0x81700000)
{
OSReport(
"APPLOADER ERROR >>> Illegal FST "
"destination address! (0x%x)\n", *addr );
PPCHalt();
}
DCInvalidateRange(*addr, *length);
LoadBigData(addr, length, offset, &Part);
break;
}
// Load main DOL header.
case 5:
*addr = &DolImage;
*length = sizeof(DolImage);
*offset = bb2.bootFilePosition;
Part = 6;
DCInvalidateRange(*addr, *length);
break;
// Check out DOL properties. Clear BSS.
case 6:
s32 dolSize = DOLSize();
// Halt apploader, if DOL size exceeds limit.
if( (dolSize > __OSBootInfo2->dolLimit) && __OSBootInfo2->dolLimit)
{
OSReport(
"APPLOADER ERROR >>> Total size of text/data sections "
"of the dol file are too big (%d(0x%08x) bytes). "
"Currently the limit is set as %d(0x%08x) bytes\n",
dolSize, __OSBootInfo2->dolLimit );
PPCHalt();
}
// Save DOL size in OS globals
if(FSTHigh) __OSDOLSize = dolSize + OSRoundUp32B(bb2.FSTLength);
else __OSDOLSize = dolSize;
// Address limit?
#define DOL_ADDRESS_LIMIT 0x80700000 // production boards
#define DOL_ADDRESS_LIMITD 0x81200000 // development boards
if(!(OSGetConsoleType() & OS_CONSOLE_DEVELOPMENT))
{
if(AddressLimit(DOL_ADDRESS_LIMIT) == FALSE)
{
OSReport(
"APPLOADER ERROR >>> One of the sections in the dol file "
"exceeded its boundary. All the sections should not exceed "
"0x%08x (production mode).\n", DOL_ADDRESS_LIMIT );
PPCHalt();
}
}
else
{
// Check twice : first for production mode, then for development.
if(AddressLimit(DOL_ADDRESS_LIMIT) == FALSE)
{
OSReport(
"APPLOADER WARNING >>> One of the sections in the dol file "
"exceeded its boundary. All the sections should not exceed "
"0x%08x in production mode.\n", DOL_ADDRESS_LIMIT );
// Do not halt apploader (show only warning).
}
if(AddressLimit(DOL_ADDRESS_LIMITD) == FALSE)
{
OSReport(
"APPLOADER ERROR >>> One of the sections in the dol file "
"exceeded its boundary. All the sections should not exceed "
"0x%08x (development mode).\n", DOL_ADDRESS_LIMITD );
PPCHalt();
}
}
// Clear BSS
memset(DolImage.bss, 0, DolImage.bssLen);
DCFlushRange(DolImage.bss, DolImage.bssLen);
Part = 7;
// Load text sections.
case 7:
if(Section<DOL_MAX_TEXT)
{
if(DolImage.textData[Section])
{
*addr = DolImage.text[Section];
*length = OSRoundUp32B(DolImage.textLen[Section]);
*offset = bb2.bootFilePosition + DolImage.textData[Section];
if( (*addr + *length) > 0x81200000 )
{
OSReport(
"APPLOADER ERROR >>> Too big text segment! (0x%x - 0x%x)\n",
*addr, *addr + *length );
PPCHalt();
}
Section++;
DCInvalidateRange(*addr, *length);
break;
}
}
else
{
Section = 0;
Part = 8;
}
// Load data sections.
case 8:
if(Section<DOL_MAX_DATA)
{
if(DolImage.dataData[Section])
{
*addr = DolImage.data[Section];
*length = OSRoundUp32B(DolImage.dataLen[Section]);
*offset = bb2.bootFilePosition + DolImage.dataData[Section];
if( (*addr + *length) > 0x81200000 )
{
OSReport(
"APPLOADER ERROR >>> Too big data segment! (0x%x - 0x%x)\n",
*addr, *addr + *length );
PPCHalt();
}
Section++;
DCInvalidateRange(*addr, *length);
break;
}
}
else
{
Section = 0;
Part = 9;
}
// Load FST, only if FST placed after boot file.
case 9:
if(FSTHigh)
{
*addr = bb2.FSTAddress;
*length = OSRoundUp32B(bb2.FSTLength);
*offset = bb2.FSTPosition;
Part = 10;
if(*addr > 0x81700000)
{
OSReport(
"APPLOADER ERROR >>> Illegal FST "
"destination address! (0x%x)\n", *addr );
PPCHalt();
}
DCInvalidateRange(*addr, *length);
LoadBigData(addr, length, offset, &Part);
break;
}
else
{
// Continue on part 10.
Part = 10;
}
// Init misc. low memory variables. Continue on part 11.
case 10:
InitLoMem(LoMem);
Part = 11;
// Read button state of 4th controller and save it in OS global.
// It's for testing pre-master dvds (NR Discs). But as NR discs gets
// written without change to the dvdroms the retail apploaders contains
// it, too. If you plug in a special device (dunno what it is exactly -
// controller with special id? some bit set?) the apploader starts making
// a crc of the whole disc and compare that to a crc stored on memory card.
case 11:
PADStatus padst;
PadRead(PAD_CHAN3, &padst);
__OSPADButton = padst.button;
// All data loaded. End of apploader.
return FALSE;
// This part will be called, if DVD need to load more, than 64 sectors.
case 12:
ASSERT(SecondTimeForThePart == TRUE);
LoadBigData(addr, length, offset, &Part);
break;
// Unknown part. Abort data loading.
default:
return FALSE;
}
// We need to load more data. Tell IPL to do that.
return TRUE;
}
--------------------------------------------------------------------------
PART III. Apploader Close.
Close call is executed after apploader's Main.
void* AplClose(void)
{
// Return entrypoint of main DOL executable to IPL
return DolImage.entry;
}
Now IPL jumps to DOL's entrypoint (__start).
------------------------------------------------------------------------------
Appendix A: Helper calls.
Calculate full size of DOL text and data sections.
s32 DOLSize(void)
{
DolImage *dol = &DolImage;
s32 totalBytes = 0, i;
for(i=0; i<DOL_MAX_TEXT; i++)
{
if(dol->textData[i])
{
// Aligned to 32 byte boundary
totalBytes += OSRoundUp32B(dol->textLen[i]);
}
}
for(i=0; i<DOL_MAX_DATA; i++)
{
if(dol->dataData[i])
{
// Aligned to 32 byte boundary
totalBytes += OSRoundUp32B(dol->dataLen[i]);
}
}
return totalBytes;
}
Return FALSE, if any DOL section, including BSS exceed address limit.
BOOL AddressLimit(u32 limit)
{
DolImage *dol = &DolImage;
s32 i, end;
// Text sections
for(i=0; i<DOL_MAX_TEXT; i++)
{
if(dol->textData[i])
{
end = dol->text[i] + dol->textLen[i];
if( ((dol->text[i] < 0x81100000) || (end > 0x81300000)) && (end > limit) )
{
return FALSE;
}
}
}
// Data sections
for(i=0; i<DOL_MAX_DATA; i++)
{
if(dol->dataData[i])
{
end = dol->data[i] + dol->dataLen[i];
if( ((dol->data[i] < 0x81100000) || (end > 0x81300000)) && (end > limit) )
{
return FALSE;
}
}
}
// BSS
end = dol->bss + dol->bssLen;
if( ((dol->bss < 0x81100000) || (end > 0x81300000)) && (end > limit) )
{
return FALSE;
}
return TRUE;
}
Some big transfers requires multiple load times for single apploader part.
(Limit is set to 64 DVD sectors - WHY?).
void LoadBigData(void **addr, s32 *length, s32 *offset, int *part)
{
if(SecondTimeForThePart)
{
SecondTimeForThePart = FALSE;
*addr = SecondTimeAddr;
*length = SecondTimeLength;
*offset = SecondTimeOffset;
*part = SecondTimePart;
}
else
{
if(*length > 64*2048) // 64 DVD sectors
{
// Initiate multiple DVD load.
SecondTimeForThePart = TRUE;
SecondTimeAddr = *addr + 64*2048;
SecondTimeLength = *length - 64*2048;
SecondTimeOffset = *offset + 64*2048;
SecondTimePart = *part;
*length = 64*2048;
*part = 12;
}
}
}
Init some OS low memory variables.
void InitLoMem(OSLoMem *LoMem)
{
LoMem->bi.magic = OS_BOOTINFO_MAGIC; // 0xD15EA5E
LoMem->bi.version = 1;
// Clear debug interface (db/DBInterface.h)
LoMem->db.bPresent =
LoMem->db.exceptionMask =
LoMem->db.ExceptionDestination =
LoMem->db.exceptionReturn = NULL;
}
Read PAD input buffer (buttons state). If PAD is not polled, return zeroes.
void PadRead(int port, u32 *buf)
{
// If controller polling enabled
if(SI_POLL & (0x80 >> port))
{
// Read SI input buffer
buf[0] = SI_CHAN_INBUFH(port);
buf[1] = SI_CHAN_INBUFL(port);
}
else
{
buf[0] = buf[1] = 0;
}
}
------------------------------------------------------------------------------
Appendix B: DVD structures.
#define DVD_BB2_OFFSET 0x420
#define DVD_BI2_OFFSET 0x440
#define OS_BI2_SIZE 0x2000 // Size of BI2 structure
// DVD Boot Block
typedef struct
{
u32 bootFilePosition; // offset of main DOL executable
u32 FSTPosition; // offset of primary FST
u32 FSTLength; // primary FST size (in bytes)
u32 FSTMaxLength; // size of biggest additional FST
u32 FSTAddress; // FST address in main memory
u32 userPosition;
u32 userLength;
u8 padding0[4]; // reserved, should be 0
} DVDBB2;
FST is File String Table. User area on DVD is where all files are placed.
On some DVDs, user area is configured wrong. To know correct user area, you
should parse whole FST and find min and max file offsets.
// DVD Boot Info
typedef struct
{
s32 debugMonSize; // size of debug monitor
s32 simMemSize; // simulated memory size (in bytes)
u32 argOffset; // command line arguments
u32 debugFlag; // debug present (set to 3, if CodeWarrior on GDEV)
u32 TRKLocation; // target resident kernel location
s32 TRKSize; // size of TRK
u32 countryCode; // country code
u32 ? +1C
u32 ? +20
u32 ? +24
u32 dolLimit; // maximum total size of DOL text/data sections (0 - unlimited)
u32 ? +2C
u8 padded0[OS_BI2_SIZE - 0x30]; // reserved, should be 0
} DVDBI2;
Somewhere in +1C, +20 or +24 must present :
u32 longFileNameSupport; // 1: use long file names, 0: use 8.3 names
u32 padSpec; // controller version for PADSetSpec
------------------------------------------------------------------------------
Appendix C: Used OS low memory variables.
s32 __OSPhysMemSize AT_ADDRESS(OS_BASE_CACHED | 0x0028);
s32 __OSDebugMonSize AT_ADDRESS(OS_BASE_CACHED | 0x00E8);
void* __OSDebugMon AT_ADDRESS(OS_BASE_CACHED | 0x00EC);
s32 __OSSimMemSize AT_ADDRESS(OS_BASE_CACHED | 0x00F0);
DVDBI2* __OSBootInfo2 AT_ADDRESS(OS_BASE_CACHED | 0x00F4);
s32 __OSDOLSize AT_ADDRESS(OS_BASE_CACHED | 0x30D4);
u16 __OSPADButton AT_ADDRESS(OS_BASE_CACHED | 0x30E4);
------------------------------------------------------------------------------
Strange Things.
What's [+0x148] in apploader data? Its only cleared in Init, but not used.
Why FST loading is so strange : if FST placed above DOL, then its loaded after
DOL, otherwise its loaded before DOL.
if(FSTHigh) __OSDOLSize = dolSize + OSRoundUp32B(bb2.FSTLength);
else __OSDOLSize = dolSize;
Why so?
Why FST is loaded in pieces, if its greater than 64 DVD sectors, but big DOL
sections are not loaded in same way?
Some code is placed after apploader data. Is it garbage or what?
------------------------------------------------------------------------------
ToDo.
Need more BI2 details.
Get Zelda apploader and crosscheck it with current reversing. Maybe some
interesting things will be discovered.
Count how many apploader revisions are present today.