2021-02-12 21:57:06 +00:00
#!/usr/bin/env python3
2022-01-17 00:43:07 +00:00
import argparse , json , os , signal , time , colorama , multiprocessing
colorama . init ( )
2021-05-23 00:00:10 +00:00
2021-05-26 23:40:48 +00:00
EXTRACTED_ASSETS_NAMEFILE = " .extracted-assets.json "
2021-11-09 01:51:45 +00:00
2021-05-26 23:40:48 +00:00
def SignalHandler ( sig , frame ) :
print ( f ' Signal { sig } received. Aborting... ' )
mainAbort . set ( )
# Don't exit immediately to update the extracted assets file.
2020-03-17 04:31:30 +00:00
2021-05-30 15:09:59 +00:00
def ExtractFile ( xmlPath , outputPath , outputSourcePath ) :
2021-05-23 00:00:10 +00:00
if globalAbort . is_set ( ) :
# Don't extract if another file wasn't extracted properly.
return
2022-01-17 00:43:07 +00:00
execStr = f " tools/ZAPD/ZAPD.out e -eh -i { xmlPath } -b baserom/ -o { outputPath } -osf { outputSourcePath } -gsf 1 -rconf tools/ZAPDConfigs/MqDbg/Config.xml { ZAPDArgs } "
2021-10-17 11:32:09 +00:00
if " overlays " in xmlPath :
execStr + = " --static "
2021-05-23 00:00:10 +00:00
if globalUnaccounted :
2022-01-17 00:43:07 +00:00
execStr + = " -Wunaccounted "
2020-03-17 04:31:30 +00:00
2021-05-23 00:00:10 +00:00
print ( execStr )
exitValue = os . system ( execStr )
if exitValue != 0 :
globalAbort . set ( )
print ( " \n " )
print ( " Error when extracting from file " + xmlPath , file = os . sys . stderr )
print ( " Aborting... " , file = os . sys . stderr )
print ( " \n " )
2020-03-17 04:31:30 +00:00
2020-12-26 11:39:52 +00:00
def ExtractFunc ( fullPath ) :
2021-05-23 00:00:10 +00:00
* pathList , xmlName = fullPath . split ( os . sep )
objectName = os . path . splitext ( xmlName ) [ 0 ]
outPath = os . path . join ( " assets " , * pathList [ 2 : ] , objectName )
outSourcePath = outPath
2020-03-17 04:31:30 +00:00
2021-07-28 02:16:03 +00:00
if fullPath in globalExtractedAssetsTracker :
timestamp = globalExtractedAssetsTracker [ fullPath ] [ " timestamp " ]
modificationTime = int ( os . path . getmtime ( fullPath ) )
if modificationTime < timestamp :
# XML has not been modified since last extraction.
return
2021-05-26 23:40:48 +00:00
2021-05-28 04:03:47 +00:00
currentTimeStamp = int ( time . time ( ) )
2021-05-23 00:00:10 +00:00
2021-05-30 15:09:59 +00:00
ExtractFile ( fullPath , outPath , outSourcePath )
2021-05-23 00:00:10 +00:00
2021-05-26 23:40:48 +00:00
if not globalAbort . is_set ( ) :
# Only update timestamp on succesful extractions
if fullPath not in globalExtractedAssetsTracker :
globalExtractedAssetsTracker [ fullPath ] = globalManager . dict ( )
globalExtractedAssetsTracker [ fullPath ] [ " timestamp " ] = currentTimeStamp
2021-07-28 02:16:03 +00:00
def initializeWorker ( abort , unaccounted : bool , extractedAssetsTracker : dict , manager ) :
2021-05-23 00:00:10 +00:00
global globalAbort
global globalUnaccounted
2021-05-26 23:40:48 +00:00
global globalExtractedAssetsTracker
global globalManager
2021-05-23 00:00:10 +00:00
globalAbort = abort
globalUnaccounted = unaccounted
2021-05-26 23:40:48 +00:00
globalExtractedAssetsTracker = extractedAssetsTracker
globalManager = manager
2020-05-26 16:53:53 +00:00
2022-01-17 00:43:07 +00:00
def processZAPDArgs ( argsZ ) :
badZAPDArg = False
for z in argsZ :
if z [ 0 ] == ' - ' :
print ( f " { colorama . Fore . LIGHTRED_EX } error { colorama . Fore . RESET } : argument \" { z } \" starts with \" - \" , which is not supported. " , file = os . sys . stderr )
badZAPDArg = True
if badZAPDArg :
exit ( 1 )
ZAPDArgs = " " . join ( f " - { z } " for z in argsZ )
print ( " Using extra ZAPD arguments: " + ZAPDArgs )
return ZAPDArgs
2020-12-28 23:37:52 +00:00
def main ( ) :
2021-02-12 21:57:06 +00:00
parser = argparse . ArgumentParser ( description = " baserom asset extractor " )
parser . add_argument ( " -s " , " --single " , help = " asset path relative to assets/, e.g. objects/gameplay_keep " )
2021-05-23 00:00:10 +00:00
parser . add_argument ( " -f " , " --force " , help = " Force the extraction of every xml instead of checking the touched ones. " , action = " store_true " )
2022-01-17 00:43:07 +00:00
parser . add_argument ( " -j " , " --jobs " , help = " Number of cpu cores to extract with. " )
2021-05-23 00:00:10 +00:00
parser . add_argument ( " -u " , " --unaccounted " , help = " Enables ZAPD unaccounted detector warning system. " , action = " store_true " )
2022-01-17 00:43:07 +00:00
parser . add_argument ( " -Z " , help = " Pass the argument on to ZAPD, e.g. `-ZWunaccounted` to warn about unaccounted blocks in XMLs. Each argument should be passed separately, *without* the leading dash. " , metavar = " ZAPD_ARG " , action = " append " )
2021-02-12 21:57:06 +00:00
args = parser . parse_args ( )
2022-01-17 00:43:07 +00:00
global ZAPDArgs
ZAPDArgs = processZAPDArgs ( args . Z ) if args . Z else " "
2021-05-26 23:40:48 +00:00
global mainAbort
2022-01-17 00:43:07 +00:00
mainAbort = multiprocessing . Event ( )
manager = multiprocessing . Manager ( )
2021-05-26 23:40:48 +00:00
signal . signal ( signal . SIGINT , SignalHandler )
extractedAssetsTracker = manager . dict ( )
2021-07-28 02:16:03 +00:00
if os . path . exists ( EXTRACTED_ASSETS_NAMEFILE ) and not args . force :
2021-05-30 15:09:59 +00:00
with open ( EXTRACTED_ASSETS_NAMEFILE , encoding = ' utf-8 ' ) as f :
2021-05-26 23:40:48 +00:00
extractedAssetsTracker . update ( json . load ( f , object_hook = manager . dict ) )
2021-05-23 00:00:10 +00:00
2021-02-12 21:57:06 +00:00
asset_path = args . single
if asset_path is not None :
2021-05-23 00:00:10 +00:00
fullPath = os . path . join ( " assets " , " xml " , asset_path + " .xml " )
2021-07-28 02:16:03 +00:00
if not os . path . exists ( fullPath ) :
2022-01-17 00:43:07 +00:00
print ( f " Error. File { fullPath } does not exist. " , file = os . sys . stderr )
2021-07-28 02:16:03 +00:00
exit ( 1 )
initializeWorker ( mainAbort , args . unaccounted , extractedAssetsTracker , manager )
# Always extract if -s is used.
if fullPath in extractedAssetsTracker :
del extractedAssetsTracker [ fullPath ]
2021-05-23 00:00:10 +00:00
ExtractFunc ( fullPath )
2021-02-12 21:57:06 +00:00
else :
z_message_PAL, message_data_static and surrounding doc (#996)
* Initial progress on z_message_PAL, very messy
* Fix merge
* Some more progress
* Fix merge
* More z_message_PAL
* Small progress
* More small progress
* message_data_static files OK
* Prepare z_message_tables
* Matched another function, small updates
* Attempt to use asm-processor static-symbols branch
* Refactor text id declarations
* Begin large text codes parser function
* Fix merge
* Refactor done
* Build OK, add color and highscore names
* Remove encoded text headers and automatically encode during build
* Fix kanfont
* Various cleanups
* DISP macros
* Another match aside data
* Further progress
* Small improvements
* Deduplicate magic values for text control codes, small improvements
* Tiny progress
* Minor cleanups
* Clean up z_message_PAL comment
* Progress on large functions
* Further progress on large functions
* Changes to mkldscript to link .data in the .rodata section
* data OK
* Few improvements
* Use gDPLoadTextureBlock macros where appropriate
* rm z_message_tables, progress on large functions
* 2 more matches
* Improvements
* Small progress
* More progress on big function
* progress
* match func_80107980
* match Message_Update
* match func_8010BED8
* done
* Progress on remaining large functions
* Small progress on largest function
* Another match, extract text and move to assets, improve text build system
* Small nonmatchings improvements
* docs wip
* Largest function maybe equivalent
* Fix merge
* Document do_action values, largest function is almost instruction-matching
* Rename NAVI do_action to NONE, as that appears to be how that value is used in practice
* Fix merge
* one match
* Last function is instruction-matching
* Fix
* Improvements thanks to engineer124
* Stack matched thanks to petrie911, now just a/v/low t regalloc issues, some cleanup
* More variables labeled, use text state enum everywhere
* More labels and names
* Fix
* Actor_IsTalking -> Actor_TalkRequested
* Match func_8010C39C and remove unused asm
* More docs
* Mostly ocarina related docs
* All msgModes named
* Fix assetclean
* Cleanup
* Extraction fixes and headers
* Suggestions
* Review suggestions
* Change text extraction again, only extract if the headers do not already exist
* Fix
* Use ast for charmap, fix assetclean for real this time
* Review suggestions
* BGM ids and ran formatter
* Review comments
* rename include_readonly to include_data_with_rodata
* Remove leading 0s in number directives
* Review suggestions for message_data_static
* textbox pos enum comments, rename several enum names from Message to TextBox
Co-authored-by: Thar0 <maximilianc64@gmail.com>
Co-authored-by: Zelllll <56516451+Zelllll@users.noreply.github.com>
Co-authored-by: petrie911 <pmontag@DESKTOP-LG8A167.localdomain>
Co-authored-by: Roman971 <romanlasnier@hotmail.com>
2021-11-23 01:20:30 +00:00
extract_text_path = " assets/text/message_data.h "
if os . path . isfile ( extract_text_path ) :
extract_text_path = None
extract_staff_text_path = " assets/text/message_data_staff.h "
if os . path . isfile ( extract_staff_text_path ) :
extract_staff_text_path = None
# Only extract text if the header does not already exist, or if --force was passed
if args . force or extract_text_path is not None or extract_staff_text_path is not None :
print ( " Extracting text " )
from tools import msgdis
msgdis . extract_all_text ( extract_text_path , extract_staff_text_path )
2021-02-12 21:57:06 +00:00
xmlFiles = [ ]
2021-05-26 23:40:48 +00:00
for currentPath , _ , files in os . walk ( os . path . join ( " assets " , " xml " ) ) :
2021-05-23 00:00:10 +00:00
for file in files :
fullPath = os . path . join ( currentPath , file )
if file . endswith ( " .xml " ) :
xmlFiles . append ( fullPath )
2021-02-12 21:57:06 +00:00
2021-08-30 00:19:52 +00:00
try :
2022-01-17 00:43:07 +00:00
numCores = int ( args . jobs or 0 )
if numCores < = 0 :
numCores = 1
print ( " Extracting assets with " + str ( numCores ) + " CPU core " + ( " s " if numCores > 1 else " " ) + " . " )
with multiprocessing . get_context ( " fork " ) . Pool ( numCores , initializer = initializeWorker , initargs = ( mainAbort , args . unaccounted , extractedAssetsTracker , manager ) ) as p :
2021-08-30 00:19:52 +00:00
p . map ( ExtractFunc , xmlFiles )
2022-01-17 00:43:07 +00:00
except ( multiprocessing . ProcessError , TypeError ) :
2021-08-30 00:19:52 +00:00
print ( " Warning: Multiprocessing exception ocurred. " , file = os . sys . stderr )
print ( " Disabling mutliprocessing. " , file = os . sys . stderr )
initializeWorker ( mainAbort , args . unaccounted , extractedAssetsTracker , manager )
for singlePath in xmlFiles :
ExtractFunc ( singlePath )
2021-05-23 00:00:10 +00:00
2021-05-26 23:40:48 +00:00
with open ( EXTRACTED_ASSETS_NAMEFILE , ' w ' , encoding = ' utf-8 ' ) as f :
serializableDict = dict ( )
for xml , data in extractedAssetsTracker . items ( ) :
serializableDict [ xml ] = dict ( data )
json . dump ( dict ( serializableDict ) , f , ensure_ascii = False , indent = 4 )
if mainAbort . is_set ( ) :
2021-05-23 00:00:10 +00:00
exit ( 1 )
2020-12-28 23:37:52 +00:00
if __name__ == " __main__ " :
2022-01-17 00:43:07 +00:00
main ( )