2020-08-24 13:08:44 +09:00
from io import BytesIO
import tools
2020-09-26 11:33:17 +09:00
from fileutils import *
2020-08-24 13:08:44 +09:00
2020-09-28 17:02:59 +09:00
class UnmappedAddressError ( Exception ) : pass
class SectionCountFullError ( Exception ) : pass
class AddressOutOfRangeError ( Exception ) : pass
2020-09-26 11:33:17 +09:00
class DolFile ( object ) :
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
class SectionType ( ) :
Text = 0
Data = 1
2020-10-04 16:30:11 +09:00
maxTextSections = 7
maxDataSections = 11
offsetInfoLoc = 0
addressInfoLoc = 0x48
sizeInfoLoc = 0x90
bssInfoLoc = 0xD8
entryInfoLoc = 0xE0
2020-09-28 17:02:59 +09:00
def __init__ ( self , f = None ) :
2020-08-24 13:08:44 +09:00
self . textSections = [ ]
self . dataSections = [ ]
self . bssAddress = 0
self . bssSize = 0
self . entryPoint = 0x80003000
2020-08-24 19:10:23 +09:00
if f is None : return
2020-08-24 13:08:44 +09:00
# Read text and data section addresses and sizes
2020-10-04 16:30:11 +09:00
for i in range ( DolFile . maxTextSections + DolFile . maxDataSections ) :
f . seek ( DolFile . offsetInfoLoc + ( i << 2 ) )
2020-09-26 11:33:17 +09:00
offset = read_uint32 ( f )
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . addressInfoLoc + ( i << 2 ) )
2020-09-26 11:33:17 +09:00
address = read_uint32 ( f )
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . sizeInfoLoc + ( i << 2 ) )
2020-09-26 11:33:17 +09:00
size = read_uint32 ( f )
2020-08-24 13:08:44 +09:00
if offset > = 0x100 :
f . seek ( offset )
data = BytesIO ( f . read ( size ) )
2020-10-04 16:30:11 +09:00
if i < DolFile . maxTextSections :
2020-09-29 03:54:19 +09:00
self . textSections . append ( [ offset , address , size , data , DolFile . SectionType . Text ] )
2020-08-24 13:08:44 +09:00
else :
2020-09-29 03:54:19 +09:00
self . dataSections . append ( [ offset , address , size , data , DolFile . SectionType . Data ] )
2020-08-24 13:08:44 +09:00
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . bssInfoLoc )
2020-09-26 11:33:17 +09:00
self . bssAddress = read_uint32 ( f )
self . bssSize = read_uint32 ( f )
2020-08-24 13:08:44 +09:00
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . entryInfoLoc )
2020-09-26 11:33:17 +09:00
self . entryPoint = read_uint32 ( f )
2020-08-24 13:08:44 +09:00
2020-09-28 17:02:59 +09:00
self . _currLogicAddr = self . get_first_section ( ) [ 1 ]
2020-08-24 13:08:44 +09:00
self . seek ( self . _currLogicAddr )
f . seek ( 0 )
2020-09-28 17:02:59 +09:00
def __str__ ( self ) :
return " Nintendo DOL format executable for the Wii and Gamecube "
2020-08-24 13:08:44 +09:00
# Internal function for
2020-09-29 03:54:19 +09:00
def resolve_address ( self , gcAddr ) - > tuple :
""" Returns the data of the section that houses the given address \n
UnmappedAddressError is raised when the address is unmapped """
2020-09-26 11:33:17 +09:00
2020-09-29 03:54:19 +09:00
for offset , address , size , data , sectiontype in self . textSections :
2020-08-24 13:08:44 +09:00
if address < = gcAddr < address + size :
2020-09-29 03:54:19 +09:00
return offset , address , size , data , sectiontype
for offset , address , size , data , sectiontype in self . dataSections :
2020-08-24 13:08:44 +09:00
if address < = gcAddr < address + size :
2020-09-29 03:54:19 +09:00
return offset , address , size , data , sectiontype
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
raise UnmappedAddressError ( f " Unmapped address: 0x { gcAddr : X } " )
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
def seek_nearest_unmapped ( self , gcAddr , buffer = 0 ) - > int :
2020-08-24 13:08:44 +09:00
''' Returns the nearest unmapped address (greater) if the given address is already taken by data '''
2020-09-26 11:33:17 +09:00
2020-09-29 03:54:19 +09:00
for _ , address , size , _ , _ in self . textSections :
2020-08-24 13:08:44 +09:00
if address > ( gcAddr + buffer ) or address + size < gcAddr :
continue
gcAddr = address + size
2020-09-29 03:54:19 +09:00
for _ , address , size , _ , _ in self . dataSections :
2020-08-24 13:08:44 +09:00
if address > ( gcAddr + buffer ) or address + size < gcAddr :
continue
gcAddr = address + size
return gcAddr
2020-09-28 17:02:59 +09:00
@property
def sections ( self ) - > tuple :
""" Generator that yields each section ' s data """
2020-09-29 03:54:19 +09:00
2020-09-28 17:02:59 +09:00
for i in self . textSections :
yield i
for i in self . dataSections :
yield i
return
2020-09-26 11:33:17 +09:00
def get_final_section ( self ) - > tuple :
2020-09-29 03:54:19 +09:00
""" Returns the last section in the dol file as sorted by internal offset """
2020-08-24 13:08:44 +09:00
largestOffset = 0
indexToTarget = 0
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Text
2020-08-24 13:08:44 +09:00
for i , sectionData in enumerate ( self . textSections ) :
if sectionData [ 0 ] > largestOffset :
largestOffset = sectionData [ 0 ]
indexToTarget = i
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Text
2020-08-24 13:08:44 +09:00
for i , sectionData in enumerate ( self . dataSections ) :
if sectionData [ 0 ] > largestOffset :
largestOffset = sectionData [ 0 ]
indexToTarget = i
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Data
2020-09-28 17:02:59 +09:00
2020-09-29 03:54:19 +09:00
if targetType == DolFile . SectionType . Text :
2020-09-28 17:02:59 +09:00
return self . textSections [ indexToTarget ]
else :
return self . dataSections [ indexToTarget ]
def get_first_section ( self ) - > tuple :
2020-09-29 03:54:19 +09:00
""" Returns the first section in the dol file as sorted by internal offset """
2020-09-28 17:02:59 +09:00
smallestOffset = 0xFFFFFFFF
indexToTarget = 0
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Text
2020-09-28 17:02:59 +09:00
for i , sectionData in enumerate ( self . textSections ) :
if sectionData [ 0 ] < smallestOffset :
smallestOffset = sectionData [ 0 ]
indexToTarget = i
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Text
2020-09-28 17:02:59 +09:00
for i , sectionData in enumerate ( self . dataSections ) :
if sectionData [ 0 ] < smallestOffset :
smallestOffset = sectionData [ 0 ]
indexToTarget = i
2020-09-29 03:54:19 +09:00
targetType = DolFile . SectionType . Data
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
if targetType == DolFile . SectionType . Text :
2020-08-24 13:08:44 +09:00
return self . textSections [ indexToTarget ]
else :
return self . dataSections [ indexToTarget ]
# Unsupported: Reading an entire dol file
# Assumption: A read should not go beyond the current section
2020-09-26 11:33:17 +09:00
def read ( self , _size ) - > bytes :
2020-09-29 03:54:19 +09:00
_ , address , size , data , _ = self . resolve_address ( self . _currLogicAddr )
2020-08-24 19:10:23 +09:00
if self . _currLogicAddr + _size > address + size :
2020-09-29 03:54:19 +09:00
raise UnmappedAddressError ( " Read goes over current section " )
2020-08-24 13:08:44 +09:00
2020-08-24 19:10:23 +09:00
self . _currLogicAddr + = _size
return data . read ( _size )
2020-08-24 13:08:44 +09:00
# Assumption: A write should not go beyond the current section
2020-08-24 19:10:23 +09:00
def write ( self , _data ) :
2020-09-29 03:54:19 +09:00
_ , address , size , data , _ = self . resolve_address ( self . _currLogicAddr )
2020-08-24 19:10:23 +09:00
if self . _currLogicAddr + len ( _data ) > address + size :
2020-09-29 03:54:19 +09:00
raise UnmappedAddressError ( " Write goes over current section " )
2020-08-24 13:08:44 +09:00
2020-08-24 19:10:23 +09:00
data . write ( _data )
self . _currLogicAddr + = len ( _data )
2020-08-24 13:08:44 +09:00
def seek ( self , where , whence = 0 ) :
if whence == 0 :
2020-09-29 03:54:19 +09:00
_ , address , _ , data , _ = self . resolve_address ( where )
2020-08-24 13:08:44 +09:00
data . seek ( where - address )
self . _currLogicAddr = where
elif whence == 1 :
2020-09-29 03:54:19 +09:00
_ , address , _ , data , _ = self . resolve_address ( self . _currLogicAddr + where )
2020-08-24 13:08:44 +09:00
data . seek ( ( self . _currLogicAddr + where ) - address )
self . _currLogicAddr + = where
else :
2020-09-29 03:54:19 +09:00
raise NotImplementedError ( f " Unsupported whence type ' { whence } ' " )
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
def tell ( self ) - > int :
2020-08-24 13:08:44 +09:00
return self . _currLogicAddr
2020-09-28 17:02:59 +09:00
def save ( self , f ) :
2020-08-24 13:08:44 +09:00
f . seek ( 0 )
2020-09-28 17:02:59 +09:00
f . write ( b " \x00 " * self . get_full_size ( ) )
2020-08-24 13:08:44 +09:00
2020-10-04 16:30:11 +09:00
for i in range ( DolFile . maxTextSections + DolFile . maxDataSections ) :
if i < DolFile . maxTextSections :
2020-08-24 13:08:44 +09:00
if i < len ( self . textSections ) :
2020-09-29 03:54:19 +09:00
offset , address , size , data , _ = self . textSections [ i ]
2020-08-24 13:08:44 +09:00
else :
continue
else :
2020-10-04 16:30:11 +09:00
if i - DolFile . maxTextSections < len ( self . dataSections ) :
offset , address , size , data , _ = self . dataSections [ i - DolFile . maxTextSections ]
2020-08-24 13:08:44 +09:00
else :
continue
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . offsetInfoLoc + ( i << 2 ) )
2020-09-28 17:02:59 +09:00
write_uint32 ( f , offset ) #offset in file
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . addressInfoLoc + ( i << 2 ) )
2020-09-28 17:02:59 +09:00
write_uint32 ( f , address ) #game address
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . sizeInfoLoc + ( i << 2 ) )
2020-09-28 17:02:59 +09:00
write_uint32 ( f , size ) #size in file
2020-08-24 13:08:44 +09:00
f . seek ( offset )
f . write ( data . getbuffer ( ) )
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . bssInfoLoc )
2020-09-28 17:02:59 +09:00
write_uint32 ( f , self . bssAddress )
write_uint32 ( f , self . bssSize )
2020-08-24 13:08:44 +09:00
2020-10-04 16:30:11 +09:00
f . seek ( DolFile . entryInfoLoc )
2020-09-28 17:02:59 +09:00
write_uint32 ( f , self . entryPoint )
align_byte_size ( f , 256 )
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
def get_full_size ( self ) - > int :
2020-10-04 16:30:11 +09:00
try :
offset , _ , size , _ , _ = self . get_final_section ( )
return ( offset + size + 255 ) & - 256
except IndexError :
return 0x100
2020-09-29 03:54:19 +09:00
def get_section_size ( self , index : int , section : SectionType ) - > int :
""" Return the current size of the specified section \n
section : DolFile . SectionType """
if section == DolFile . SectionType . Text :
return self . textSections [ index ] [ 2 ]
else :
return self . dataSections [ index ] [ 2 ]
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
def append_text_sections ( self , sectionsList : list ) - > bool :
2020-09-28 17:02:59 +09:00
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
2020-08-24 13:08:44 +09:00
for i , dataSet in enumerate ( sectionsList ) :
2020-10-04 16:30:11 +09:00
if len ( self . textSections ) > = DolFile . maxTextSections :
raise SectionCountFullError ( f " Exceeded max text section limit of { DolFile . maxTextSections } " )
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
fOffset , _ , fSize , _ , _ = self . get_final_section ( )
_ , pAddress , pSize , _ , _ = self . textSections [ len ( self . textSections ) - 1 ]
2020-08-24 13:08:44 +09:00
data , address = dataSet
2020-09-29 03:54:19 +09:00
if not hasattr ( data , " getbuffer " ) :
if hasattr ( data , " read " ) :
data . seek ( 0 )
data = BytesIO ( data . read ( ) )
else :
data = BytesIO ( data )
2020-08-24 13:08:44 +09:00
offset = fOffset + fSize
if i < len ( sectionsList ) - 1 :
size = ( len ( data . getbuffer ( ) ) + 31 ) & - 32
else :
size = ( len ( data . getbuffer ( ) ) + 255 ) & - 256
if address is None :
address = self . seek_nearest_unmapped ( pAddress + pSize , size )
if address < 0x80000000 or address > = 0x81200000 :
2020-09-28 17:02:59 +09:00
raise AddressOutOfRangeError ( f " Address ' { address : 08X } ' of text section { i } is beyond scope (0x80000000 <-> 0x81200000) " )
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
self . textSections . append ( ( offset , address , size , data , DolFile . SectionType . Text ) )
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
def append_data_sections ( self , sectionsList : list ) - > bool :
2020-09-28 17:02:59 +09:00
""" Follows the list format: [tuple(<Bytes>Data, <Int>GameAddress or None), tuple(<Bytes>Data... """
2020-08-24 13:08:44 +09:00
for i , dataSet in enumerate ( sectionsList ) :
2020-10-04 16:30:11 +09:00
if len ( self . dataSections ) > = DolFile . maxDataSections :
raise SectionCountFullError ( f " Exceeded max data section limit of { DolFile . maxDataSections } " )
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
fOffset , _ , fSize , _ , _ = self . get_final_section ( )
_ , pAddress , pSize , _ , _ = self . dataSections [ len ( self . dataSections ) - 1 ]
2020-08-24 13:08:44 +09:00
data , address = dataSet
2020-09-29 03:54:19 +09:00
if not hasattr ( data , " getbuffer " ) :
if hasattr ( data , " read " ) :
data . seek ( 0 )
data = BytesIO ( data . read ( ) )
else :
data = BytesIO ( data )
2020-08-24 13:08:44 +09:00
offset = fOffset + fSize
if i < len ( sectionsList ) - 1 :
size = ( len ( data . getbuffer ( ) ) + 31 ) & - 32
else :
size = ( len ( data . getbuffer ( ) ) + 255 ) & - 256
if address is None :
address = self . seek_nearest_unmapped ( pAddress + pSize , size )
if address < 0x80000000 or address > = 0x81200000 :
2020-09-28 17:02:59 +09:00
raise AddressOutOfRangeError ( f " Address ' { address : 08X } ' of data section { i } is beyond scope (0x80000000 <-> 0x81200000) " )
2020-08-24 13:08:44 +09:00
2020-09-29 03:54:19 +09:00
self . dataSections . append ( ( offset , address , size , data , DolFile . SectionType . Data ) )
2020-08-24 13:08:44 +09:00
2020-08-25 18:28:22 +09:00
def insert_branch ( self , to : int , _from : int , lk = 0 ) :
2020-09-29 03:54:19 +09:00
""" Insert a branch instruction at _from \n
to : address to branch to \n
_from : address to branch from \n
lk : 0 | 1 , is branch linking ? """
2020-08-25 18:28:22 +09:00
self . seek ( _from )
2020-09-28 17:02:59 +09:00
write_uint32 ( self , ( to - _from ) & 0x3FFFFFD | 0x48000000 | lk )
2020-09-26 11:33:17 +09:00
def extract_branch_addr ( self , bAddr : int ) - > tuple :
""" Returns the branch offset of the given instruction,
and if the branch is conditional """
2020-08-25 18:28:22 +09:00
self . seek ( bAddr )
2020-09-28 17:02:59 +09:00
ppc = read_uint32 ( self )
2020-09-26 11:33:17 +09:00
conditional = False
if ( ppc >> 24 ) & 0xFF < 0x48 :
conditional = True
if conditional is True :
if ( ppc & 0x8000 ) :
offset = ( ppc & 0xFFFD ) - 0x10000
else :
offset = ppc & 0xFFFD
2020-08-25 18:28:22 +09:00
else :
2020-09-26 11:33:17 +09:00
if ( ppc & 0x2000000 ) :
offset = ( ppc & 0x3FFFFFD ) - 0x4000000
else :
offset = ppc & 0x3FFFFFD
2020-08-25 18:28:22 +09:00
2020-09-26 11:33:17 +09:00
return ( bAddr + offset , conditional )
def read_string ( self , addr : int = None , maxlen : int = 0 , encoding : str = " utf-8 " ) - > str :
""" Reads a null terminated string from the specified address """
if addr != None :
self . seek ( addr )
2020-08-24 13:08:44 +09:00
length = 0
2020-09-26 11:33:17 +09:00
string = " "
while ( char := self . read ( 1 ) ) != b " \x00 " :
try :
string + = char . decode ( encoding )
2020-10-04 16:30:11 +09:00
length + = 1
2020-09-26 11:33:17 +09:00
except UnicodeDecodeError :
print ( f " { char } at pos { length } , (address 0x { addr + length : 08X } ) is not a valid utf-8 character " )
return " "
2020-10-04 16:30:11 +09:00
if length > ( maxlen - 1 ) and maxlen != 0 :
return string
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
return string
2020-08-24 13:08:44 +09:00
2020-09-28 17:02:59 +09:00
def print_info ( self ) :
2020-10-04 16:30:11 +09:00
print ( " " )
print ( " |-- DOL INFO --| " . center ( 20 , " " ) )
print ( " " )
2020-09-29 03:54:19 +09:00
2020-10-04 16:30:11 +09:00
for i , ( offset , addr , size , _ , _ ) in enumerate ( self . textSections ) :
2020-09-28 17:02:59 +09:00
header = f " | Text section { i } | "
print ( " - " * len ( header ) + " \n " + header + " \n " + " - " * len ( header ) + f " \n File offset: \t 0x { offset : X } \n Virtual addr: \t 0x { addr : X } \n Size: \t \t 0x { size : X } \n " )
2020-10-04 16:30:11 +09:00
for i , ( offset , addr , size , _ , _ ) in enumerate ( self . dataSections ) :
2020-09-28 17:02:59 +09:00
header = f " | Data section { i } | "
print ( " - " * len ( header ) + " \n " + header + " \n " + " - " * len ( header ) + f " \n File offset: \t 0x { offset : X } \n Virtual addr: \t 0x { addr : X } \n Size: \t \t 0x { size : X } \n " )
header = " | BSS section | "
print ( " - " * len ( header ) + " \n " + header + " \n " + " - " * len ( header ) + f " \n Virtual addr: \t 0x { self . bssAddress : X } \n Size: \t \t 0x { self . bssSize : X } \n End: \t \t 0x { self . bssAddress + self . bssSize : X } \n " )
header = " | Miscellaneous Info | "
print ( " - " * len ( header ) + " \n " + header + " \n " + " - " * len ( header ) + f " \n Text sections: \t { len ( self . textSections ) } \n Data sections: \t { len ( self . dataSections ) } \n File length: \t 0x { self . get_full_size ( ) : X } bytes \n " )
2020-09-26 11:33:17 +09:00
if __name__ == " __main__ " :
# Example usage (Reading global string "mario" from Super Mario Sunshine (NTSC-U))
2020-08-24 13:08:44 +09:00
2020-09-28 17:02:59 +09:00
with open ( " Start.dol " , " rb " ) as f :
2020-09-26 11:33:17 +09:00
dol = DolFile ( f )
2020-08-24 13:08:44 +09:00
2020-09-26 11:33:17 +09:00
name = dol . read_string ( addr = 0x804165A0 )
print ( name )