2021-04-13 09:47:52 +02:00
#!/usr/bin/env python3
from typing import List , Dict , Set , Union
from pathlib import Path
2021-08-22 23:55:26 +02:00
import subprocess
2021-04-13 09:47:52 +02:00
import sys
import ninja_syntax
from glob import glob
2021-04-18 15:26:00 +02:00
# Configuration:
2021-04-13 09:47:52 +02:00
VERSIONS = [ " us " , " jp " ]
2021-04-18 15:26:00 +02:00
DO_SHA1_CHECK = True
2021-06-21 06:30:57 +02:00
2021-04-18 15:26:00 +02:00
# Paths:
2021-04-13 09:47:52 +02:00
ROOT = Path ( __file__ ) . parent . parent . parent
2021-08-18 19:10:11 +02:00
BUILD_TOOLS = ( ROOT / " tools " / " build " ) . relative_to ( ROOT )
2021-04-13 09:47:52 +02:00
YAY0_COMPRESS_TOOL = f " { BUILD_TOOLS } /yay0/Yay0compress "
CRC_TOOL = f " { BUILD_TOOLS } /rom/n64crc "
def rm_recursive ( path : Path ) :
if path . exists ( ) :
if path . is_dir ( ) :
for f in path . iterdir ( ) :
rm_recursive ( f )
path . rmdir ( )
else :
path . unlink ( )
def exec_shell ( command : List [ str ] ) - > str :
ret = subprocess . run ( command , stdout = subprocess . PIPE , text = True )
return ret . stdout
2021-10-22 16:01:27 +02:00
def write_ninja_rules ( ninja : ninja_syntax . Writer , cpp : str , cppflags : str , extra_cflags : str , use_ccache : bool ,
2021-10-26 05:26:38 +02:00
non_matching : bool , debug : bool ) :
2021-04-13 09:47:52 +02:00
# platform-specific
if sys . platform == " darwin " :
iconv = " tools/iconv.py UTF-8 SHIFT-JIS "
elif sys . platform == " linux " :
iconv = " iconv --from UTF-8 --to SHIFT-JIS "
else :
raise Exception ( f " unsupported platform { sys . platform } " )
2021-08-25 20:08:35 +02:00
ccache = " "
2021-08-22 23:55:26 +02:00
2021-08-25 20:08:35 +02:00
if use_ccache :
ccache = " ccache "
try :
subprocess . call ( [ " ccache " ] , stdout = subprocess . DEVNULL , stderr = subprocess . DEVNULL )
except FileNotFoundError :
ccache = " "
2021-08-22 23:55:26 +02:00
2021-04-13 09:47:52 +02:00
cross = " mips-linux-gnu- "
2021-08-24 19:11:05 +02:00
cc = f " { BUILD_TOOLS } /cc/gcc/gcc "
2021-09-25 09:31:44 +02:00
cc_ido = f " { BUILD_TOOLS } /cc/ido5.3/cc "
2022-01-29 02:01:26 +01:00
cc_272_dir = f " { BUILD_TOOLS } /cc/gcc2.7.2/ "
cc_272 = f " { cc_272_dir } /gcc "
2021-08-24 19:11:05 +02:00
cxx = f " { BUILD_TOOLS } /cc/gcc/g++ "
2021-04-13 09:47:52 +02:00
2021-10-22 16:01:27 +02:00
CPPFLAGS_COMMON = " -Iver/$version/build/include -Iinclude -Isrc -Iassets/$version -D_LANGUAGE_C -D_FINALROM " \
2021-09-25 09:31:44 +02:00
" -DVERSION=$version -DF3DEX_GBI_2 -D_MIPS_SZLONG=32 "
2021-10-22 16:01:27 +02:00
CPPFLAGS = " -w " + CPPFLAGS_COMMON + " -nostdinc "
2022-01-29 02:01:26 +01:00
CPPFLAGS_272 = " -Iver/$version/build/include -Iinclude -Isrc -Iassets/$version -D_LANGUAGE_C -D_FINALROM " \
2021-10-22 16:01:27 +02:00
" -DVERSION=$version -DF3DEX_GBI_2 -D_MIPS_SZLONG=32 -nostdinc "
2021-06-21 06:30:57 +02:00
2022-10-18 13:43:04 +02:00
cflags = f " -c -G0 -O2 -gdwarf-2 -x c -B { BUILD_TOOLS } /cc/gcc/ { extra_cflags } "
2022-01-29 02:01:26 +01:00
cflags_272 = f " -c -G0 -mgp32 -mfp32 -mips3 { extra_cflags } "
cflags_272 = cflags_272 . replace ( " -ggdb3 " , " -g1 " )
2021-07-28 13:47:05 +02:00
2021-04-13 09:47:52 +02:00
ninja . variable ( " python " , sys . executable )
ninja . rule ( " ld " ,
description = " link($version) $out " ,
2021-08-22 15:10:06 +02:00
command = f " { cross } ld -T ver/$version/build/undefined_syms.txt -T ver/$version/undefined_syms_auto.txt -T ver/$version/undefined_funcs_auto.txt -Map $mapfile --no-check-sections -T $in -o $out " ,
2021-04-13 09:47:52 +02:00
)
2022-10-28 03:42:27 +02:00
Z64_DEBUG = " "
if debug :
Z64_DEBUG = " -gS -R .data -R .note -R .eh_frame -R .gnu.attributes -R .comment -R .options "
2021-04-13 09:47:52 +02:00
ninja . rule ( " z64 " ,
description = " rom $out " ,
2022-10-28 03:42:27 +02:00
command = f " { cross } objcopy $in $out -O binary { Z64_DEBUG } && { BUILD_TOOLS } /rom/n64crc $out " ,
2021-04-13 09:47:52 +02:00
)
2021-06-21 06:30:57 +02:00
ninja . rule ( " sha1sum " ,
description = " check $in " ,
2021-08-25 07:08:42 +02:00
command = " sha1sum -c $in && touch $out " if DO_SHA1_CHECK else " touch $out " ,
2021-06-21 06:30:57 +02:00
)
2021-04-13 09:47:52 +02:00
2021-08-22 15:10:06 +02:00
ninja . rule ( " cpp " ,
description = " cpp $in " ,
command = f " { cpp } $in { cppflags } -P -o $out "
)
2021-04-13 09:47:52 +02:00
ninja . rule ( " cc " ,
2021-10-25 13:44:55 +02:00
description = " gcc $in " ,
2022-09-10 19:04:04 +02:00
command = f " bash -o pipefail -c ' { cpp } { CPPFLAGS } { cppflags } $cppflags -MD -MF $out.d $in -o - | { iconv } | { ccache } { cc } { cflags } $cflags - -o $out ' " ,
2021-04-13 09:47:52 +02:00
depfile = " $out.d " ,
deps = " gcc " ,
)
2021-09-25 09:31:44 +02:00
ninja . rule ( " cc_ido " ,
2021-10-25 13:44:55 +02:00
description = " ido $in " ,
2021-10-30 05:07:59 +02:00
command = f " { ccache } { cc_ido } -w { CPPFLAGS_COMMON } { cppflags } $cppflags -c -mips1 -O0 -G0 -non_shared -Xfullwarn -Xcpluscomm -o $out $in " ,
2021-10-22 16:01:27 +02:00
)
2022-01-29 02:01:26 +01:00
ninja . rule ( " cc_272 " ,
description = " cc_272 $in " ,
command = f " bash -o pipefail -c ' COMPILER_PATH= { cc_272_dir } { cc_272 } { CPPFLAGS_272 } { cppflags } $cppflags { cflags_272 } $cflags $in -o $out && mips-linux-gnu-objcopy -N $in $out ' " ,
2021-09-25 09:31:44 +02:00
)
2021-08-22 23:55:26 +02:00
ninja . rule ( " cxx " ,
description = " cxx $in " ,
2022-09-10 19:04:04 +02:00
command = f " bash -o pipefail -c ' { cpp } { CPPFLAGS } { cppflags } $cppflags -MD -MF $out.d $in -o - | { iconv } | { ccache } { cxx } { cflags } $cflags - -o $out ' " ,
2021-04-13 09:47:52 +02:00
depfile = " $out.d " ,
deps = " gcc " ,
)
2022-09-28 22:52:12 +02:00
ninja . rule ( " dead_cc " ,
description = " dead_cc $in " ,
command = f " mips-linux-gnu-objcopy --redefine-sym sqrtf=dead_sqrtf $in $out " ,
)
2021-04-13 09:47:52 +02:00
ninja . rule ( " bin " ,
description = " bin $in " ,
command = f " { cross } ld -r -b binary $in -o $out " ,
)
ninja . rule ( " as " ,
description = " as $in " ,
command = f " { cross } as -EB -march=vr4300 -mtune=vr4300 -Iinclude $in -o $out " ,
)
ninja . rule ( " img " ,
description = " img($img_type) $in " ,
command = f " $python { BUILD_TOOLS } /img/build.py $img_type $in $out $img_flags " ,
)
ninja . rule ( " img_header " ,
description = " img_header $in " ,
2022-06-12 17:33:32 +02:00
command = f " $python { BUILD_TOOLS } /img/header.py $in $out $c_name " ,
2021-04-13 09:47:52 +02:00
)
2021-07-12 11:15:00 +02:00
ninja . rule ( " bin_inc_c " ,
description = " bin_inc_c $out " ,
2022-06-12 17:33:32 +02:00
command = f " $python { BUILD_TOOLS } /bin_inc_c.py $in $out $c_name " ,
2021-07-12 11:15:00 +02:00
)
2022-08-10 15:36:38 +02:00
ninja . rule ( " pal_inc_c " ,
description = " pal_inc_c $out " ,
command = f " $python { BUILD_TOOLS } /pal_inc_c.py $in $out $c_name " ,
)
2021-04-13 09:47:52 +02:00
ninja . rule ( " yay0 " ,
description = " yay0 $in " ,
command = f " { BUILD_TOOLS } /yay0/Yay0compress $in $out " ,
)
ninja . rule ( " sprite " ,
description = " sprite $sprite_name " ,
command = f " $python { BUILD_TOOLS } /sprites/sprite.py $out $sprite_dir " ,
)
ninja . rule ( " sprite_combine " ,
description = " sprite_combine $in " ,
command = f " $python { BUILD_TOOLS } /sprites/combine.py $out $in " ,
)
ninja . rule ( " sprite_header " ,
description = " sprite_header $sprite_name " ,
command = f " $python { BUILD_TOOLS } /sprites/header.py $out $sprite_dir $sprite_id " ,
)
ninja . rule ( " msg " ,
description = " msg $in " ,
command = f " $python { BUILD_TOOLS } /msg/parse_compile.py $in $out " ,
)
ninja . rule ( " msg_combine " ,
description = " msg_combine $out " ,
command = f " $python { BUILD_TOOLS } /msg/combine.py $out $in " ,
)
ninja . rule ( " mapfs " ,
description = " mapfs $out " ,
2021-07-16 13:08:22 +02:00
command = f " $python { BUILD_TOOLS } /mapfs/combine.py $version $out $in " ,
2021-04-13 09:47:52 +02:00
)
2021-05-08 08:54:34 +02:00
ninja . rule ( " pack_title_data " ,
description = " pack_title_data $out " ,
command = f " $python { BUILD_TOOLS } /mapfs/pack_title_data.py $out $in " ,
)
2021-04-18 15:26:00 +02:00
ninja . rule ( " map_header " , command = f " $python { BUILD_TOOLS } /mapfs/map_header.py $in > $out " )
2021-07-16 11:28:37 +02:00
ninja . rule ( " pm_charset " , command = f " $python { BUILD_TOOLS } /pm_charset.py $out $in " )
ninja . rule ( " pm_charset_palettes " , command = f " $python { BUILD_TOOLS } /pm_charset_palettes.py $out $in " )
2021-08-18 18:28:32 +02:00
with Path ( " tools/permuter_settings.toml " ) . open ( " w " ) as f :
2021-08-22 23:55:26 +02:00
f . write ( f " compiler_command = \" { cc } { CPPFLAGS . replace ( ' $version ' , ' us ' ) } { cflags } -DPERMUTER -fforce-addr \" \n " )
2021-08-18 18:28:32 +02:00
f . write ( f " assembler_command = \" { cross } as -EB -march=vr4300 -mtune=vr4300 -Iinclude \" \n " )
2022-07-08 14:57:08 +02:00
f . write ( f " compiler_type = \" gcc \" \n " )
2021-08-18 18:28:32 +02:00
f . write (
"""
[ preserve_macros ]
" gs?[DS]P.* " = " void "
OVERRIDE_FLAG_CHECK = " int "
OS_K0_TO_PHYSICAL = " int "
" G_.* " = " int "
" TEXEL.* " = " int "
PRIMITIVE = " int "
2021-09-22 13:17:46 +02:00
[ decompme . compilers ]
" tools/build/cc/gcc/gcc " = " gcc2.8.1 "
2021-08-18 18:28:32 +02:00
""" )
2021-04-13 09:47:52 +02:00
def write_ninja_for_tools ( ninja : ninja_syntax . Writer ) :
ninja . rule ( " cc_tool " ,
description = " cc_tool $in " ,
command = f " cc -w $in -O3 -o $out " ,
)
ninja . build ( YAY0_COMPRESS_TOOL , " cc_tool " , f " { BUILD_TOOLS } /yay0/Yay0compress.c " )
ninja . build ( CRC_TOOL , " cc_tool " , f " { BUILD_TOOLS } /rom/n64crc.c " )
class Configure :
def __init__ ( self , version : str ) :
self . version = version
self . version_path = ROOT / f " ver/ { version } "
self . linker_entries = None
2021-10-26 05:26:38 +02:00
def split ( self , assets : bool , code : bool , debug : bool ) :
2021-04-13 09:47:52 +02:00
import split
modes = [ " ld " ]
if assets :
2022-06-12 17:33:32 +02:00
modes . extend ( [ " bin " , " Yay0 " , " img " , " vtx " , " gfx " , " pm_map_data " , " pm_msg " , " pm_npc_sprites " , " pm_charset " ,
2021-07-31 16:32:19 +02:00
" pm_charset_palettes " , " pm_effect_loads " , " pm_effect_shims " ] )
2021-04-13 09:47:52 +02:00
if code :
modes . extend ( [ " code " , " c " , " data " , " rodata " ] )
2021-10-26 05:26:38 +02:00
splat_file = [ str ( self . version_path / " splat.yaml " ) ]
if debug :
splat_file + = [ str ( self . version_path / " splat-debug.yaml " ) ]
2021-04-13 09:47:52 +02:00
split . main (
2021-10-26 05:26:38 +02:00
splat_file ,
2021-04-13 09:47:52 +02:00
modes ,
2021-10-29 19:57:15 +02:00
verbose = False ,
2021-04-13 09:47:52 +02:00
)
self . linker_entries = split . linker_writer . entries [ : ]
2021-04-18 15:26:00 +02:00
self . asset_stack = split . config [ " asset_stack " ]
2021-04-13 09:47:52 +02:00
def build_path ( self ) - > Path :
return Path ( f " ver/ { self . version } /build " )
2021-08-22 15:10:06 +02:00
def undefined_syms_path ( self ) - > Path :
return self . build_path ( ) / " undefined_syms.txt "
2021-04-13 09:47:52 +02:00
def elf_path ( self ) - > Path :
# TODO: read basename and build_path from splat.yaml
return Path ( f " ver/ { self . version } /build/papermario.elf " )
def rom_path ( self ) - > Path :
return self . elf_path ( ) . with_suffix ( " .z64 " )
def rom_ok_path ( self ) - > Path :
return self . elf_path ( ) . with_suffix ( " .ok " )
def linker_script_path ( self ) - > Path :
# TODO: read from splat.yaml
return Path ( f " ver/ { self . version } /papermario.ld " )
def map_path ( self ) - > Path :
return self . elf_path ( ) . with_suffix ( " .map " )
def resolve_src_paths ( self , src_paths : List [ Path ] ) - > List [ str ] :
out = [ ]
for path in src_paths :
2021-04-18 15:26:00 +02:00
path = self . resolve_asset_path ( path )
2021-04-13 09:47:52 +02:00
if path . is_dir ( ) :
out . extend ( glob ( str ( path ) + " /**/* " , recursive = True ) )
else :
out . append ( str ( path ) )
return out
2021-04-18 15:26:00 +02:00
def resolve_asset_path ( self , path : Path ) - > Path :
parts = list ( path . parts )
if parts [ 0 ] == " assets " :
for d in self . asset_stack :
parts [ 1 ] = d
new_path = Path ( " / " . join ( parts ) )
if new_path . exists ( ) :
return new_path
# ¯\_(ツ)_/¯
return path
2021-10-26 05:26:38 +02:00
def write_ninja ( self , ninja : ninja_syntax . Writer , skip_outputs : Set [ str ] , non_matching : bool , debug : bool ) :
2021-04-13 09:47:52 +02:00
import segtypes
2021-09-30 11:48:03 +02:00
import segtypes . common . data
2021-08-22 23:55:26 +02:00
import segtypes . n64 . Yay0
2021-04-13 09:47:52 +02:00
assert self . linker_entries is not None
built_objects = set ( )
generated_headers = [ ]
def build ( object_paths : Union [ Path , List [ Path ] ] , src_paths : List [ Path ] , task : str , variables : Dict [ str , str ] = { } ) :
if not isinstance ( object_paths , list ) :
object_paths = [ object_paths ]
object_strs = [ str ( obj ) for obj in object_paths ]
needs_build = False
for object_path in object_paths :
if object_path . suffixes [ - 1 ] == " .o " :
built_objects . add ( str ( object_path ) )
2022-08-10 15:36:38 +02:00
elif object_path . suffixes [ - 1 ] == " .h " or task == " bin_inc_c " or task == " pal_inc_c " :
2021-04-13 09:47:52 +02:00
generated_headers . append ( str ( object_path ) )
# don't rebuild objects if we've already seen all of them
if not str ( object_path ) in skip_outputs :
needs_build = True
if needs_build :
skip_outputs . update ( object_strs )
implicit = [ ]
order_only = [ ]
if task == " yay0 " :
implicit . append ( YAY0_COMPRESS_TOOL )
2022-05-27 15:00:53 +02:00
elif task in [ " cc " , " cxx " ] :
2021-04-13 09:47:52 +02:00
order_only . append ( " generated_headers_ " + self . version )
ninja . build (
object_strs , # $out
task ,
self . resolve_src_paths ( src_paths ) , # $in
variables = { " version " : self . version , * * variables } ,
implicit = implicit ,
order_only = order_only ,
)
# Build objects
for entry in self . linker_entries :
seg = entry . segment
if isinstance ( seg , segtypes . n64 . header . N64SegHeader ) :
build ( entry . object_path , entry . src_paths , " as " )
2021-09-30 11:48:03 +02:00
elif isinstance ( seg , segtypes . common . asm . CommonSegAsm ) or ( isinstance ( seg , segtypes . common . data . CommonSegData ) and not seg . type [ 0 ] == " . " ) :
2021-04-13 09:47:52 +02:00
build ( entry . object_path , entry . src_paths , " as " )
2021-09-30 11:48:03 +02:00
elif isinstance ( seg , segtypes . common . c . CommonSegC ) or ( isinstance ( seg , segtypes . common . data . CommonSegData ) and seg . type [ 0 ] == " . " ) :
2021-07-28 13:47:05 +02:00
cflags = None
if isinstance ( seg . yaml , dict ) :
cflags = seg . yaml . get ( " cflags " )
elif len ( seg . yaml ) > = 4 :
cflags = seg . yaml [ 3 ]
# default cflags where not specified
if cflags is None :
if " nusys " in entry . src_paths [ 0 ] . parts :
cflags = " "
elif " os " in entry . src_paths [ 0 ] . parts : # libultra
cflags = " "
else : # papermario
cflags = " -fforce-addr "
2022-05-27 15:00:53 +02:00
# c
2021-04-13 09:47:52 +02:00
task = " cc "
2021-08-22 23:55:26 +02:00
if entry . src_paths [ 0 ] . suffixes [ - 1 ] == " .cpp " :
task = " cxx "
2021-10-22 16:01:27 +02:00
2021-09-25 09:31:44 +02:00
if seg . name . endswith ( " osFlash " ) :
task = " cc_ido "
2022-01-29 02:01:26 +01:00
elif " gcc_272 " in cflags :
task = " cc_272 "
2021-10-22 16:01:27 +02:00
2022-01-29 02:01:26 +01:00
cflags = cflags . replace ( " gcc_272 " , " " )
2021-07-28 13:47:05 +02:00
2022-09-28 22:52:12 +02:00
# Dead cod
if isinstance ( seg , segtypes . common . c . CommonSegC ) and seg . rom_start > = 0xEA0900 :
obj_path = str ( entry . object_path )
init_obj_path = Path ( obj_path + " .dead " )
build ( init_obj_path , entry . src_paths , task , variables = {
" cflags " : cflags ,
" cppflags " : f " -DVERSION_ { self . version . upper ( ) } " ,
} )
build (
entry . object_path ,
[ init_obj_path ] ,
" dead_cc " ,
)
# Not dead cod
else :
2022-10-16 00:24:05 +02:00
if seg . get_most_parent ( ) . name not in [ " main " , " engine1 " , " engine2 " ] :
cflags + = " -fno-common "
2022-09-28 22:52:12 +02:00
build ( entry . object_path , entry . src_paths , task , variables = {
" cflags " : cflags ,
" cppflags " : f " -DVERSION_ { self . version . upper ( ) } " ,
} )
2021-07-12 11:15:00 +02:00
# images embedded inside data aren't linked, but they do need to be built into .inc.c files
2021-09-30 11:48:03 +02:00
if isinstance ( seg , segtypes . common . group . CommonSegGroup ) :
2021-07-12 11:15:00 +02:00
for seg in seg . subsegments :
if isinstance ( seg , segtypes . n64 . img . N64SegImg ) :
flags = " "
2022-09-28 22:52:12 +02:00
if seg . n64img . flip_h :
2021-07-12 11:15:00 +02:00
flags + = " --flip-x "
2022-09-28 22:52:12 +02:00
if seg . n64img . flip_v :
2021-07-12 11:15:00 +02:00
flags + = " --flip-y "
2022-05-05 16:08:16 +02:00
src_paths = [ seg . out_path ( ) . relative_to ( ROOT ) ]
2021-07-12 11:15:00 +02:00
inc_dir = self . build_path ( ) / " include " / seg . dir
bin_path = self . build_path ( ) / seg . dir / ( seg . name + " .png.bin " )
build ( bin_path , src_paths , " img " , variables = {
" img_type " : seg . type ,
" img_flags " : flags ,
} )
2022-06-12 17:33:32 +02:00
c_sym = seg . create_symbol (
addr = seg . vram_start , in_segment = True , type = " data " , define = True
)
vars = { " c_name " : c_sym . name }
build ( inc_dir / ( seg . name + " .png.h " ) , src_paths , " img_header " , vars )
build ( inc_dir / ( seg . name + " .png.inc.c " ) , [ bin_path ] , " bin_inc_c " , vars )
2021-07-12 11:15:00 +02:00
elif isinstance ( seg , segtypes . n64 . palette . N64SegPalette ) :
2022-05-05 16:08:16 +02:00
src_paths = [ seg . out_path ( ) . relative_to ( ROOT ) ]
2021-07-12 11:15:00 +02:00
inc_dir = self . build_path ( ) / " include " / seg . dir
bin_path = self . build_path ( ) / seg . dir / ( seg . name + " .pal.bin " )
build ( bin_path , src_paths , " img " , variables = {
" img_type " : seg . type ,
" img_flags " : " " ,
} )
2022-06-12 17:33:32 +02:00
c_sym = seg . create_symbol (
addr = seg . vram_start , in_segment = True , type = " data " , define = True
)
vars = { " c_name " : c_sym . name }
2022-08-10 15:36:38 +02:00
build ( inc_dir / ( seg . name + " .pal.inc.c " ) , [ bin_path ] , " pal_inc_c " , vars )
2021-09-30 11:48:03 +02:00
elif isinstance ( seg , segtypes . common . bin . CommonSegBin ) :
2021-04-13 09:47:52 +02:00
build ( entry . object_path , entry . src_paths , " bin " )
elif isinstance ( seg , segtypes . n64 . Yay0 . N64SegYay0 ) :
compressed_path = entry . object_path . with_suffix ( " " ) # remove .o
build ( compressed_path , entry . src_paths , " yay0 " )
build ( entry . object_path , [ compressed_path ] , " bin " )
elif isinstance ( seg , segtypes . n64 . img . N64SegImg ) :
flags = " "
2022-09-28 22:52:12 +02:00
if seg . n64img . flip_h :
2021-04-13 09:47:52 +02:00
flags + = " --flip-x "
2022-09-28 22:52:12 +02:00
if seg . n64img . flip_v :
2021-04-13 09:47:52 +02:00
flags + = " --flip-y "
2021-07-12 11:15:00 +02:00
bin_path = entry . object_path . with_suffix ( " .bin " )
inc_dir = self . build_path ( ) / " include " / seg . dir
build ( bin_path , entry . src_paths , " img " , variables = {
2021-04-13 09:47:52 +02:00
" img_type " : seg . type ,
" img_flags " : flags ,
} )
2021-07-12 11:15:00 +02:00
build ( entry . object_path , [ bin_path ] , " bin " )
2021-04-13 09:47:52 +02:00
2022-06-12 17:33:32 +02:00
# c_sym = seg.create_symbol(
# addr=seg.vram_start, in_segment=True, type="data", define=True
# )
# vars = {"c_name": c_sym.name}
2021-07-12 11:15:00 +02:00
build ( inc_dir / ( seg . name + " .png.h " ) , entry . src_paths , " img_header " )
2021-04-13 09:47:52 +02:00
elif isinstance ( seg , segtypes . n64 . palette . N64SegPalette ) :
2021-07-12 11:15:00 +02:00
bin_path = entry . object_path . with_suffix ( " .bin " )
build ( bin_path , entry . src_paths , " img " , variables = {
2021-04-13 09:47:52 +02:00
" img_type " : seg . type ,
" img_flags " : " " ,
} )
2021-07-12 11:15:00 +02:00
build ( entry . object_path , [ bin_path ] , " bin " )
2021-07-16 11:28:37 +02:00
elif seg . type == " pm_npc_sprites " :
2021-04-13 09:47:52 +02:00
sprite_yay0s = [ ]
for sprite_id , sprite_dir in enumerate ( entry . src_paths , 1 ) :
sprite_name = sprite_dir . name
bin_path = entry . object_path . with_suffix ( " " ) / ( sprite_name + " .bin " )
yay0_path = bin_path . with_suffix ( " .Yay0 " )
sprite_yay0s . append ( yay0_path )
variables = {
" sprite_id " : sprite_id ,
" sprite_name " : sprite_name ,
2021-04-18 15:26:00 +02:00
" sprite_dir " : str ( self . resolve_asset_path ( sprite_dir ) ) ,
2021-04-13 09:47:52 +02:00
}
build ( bin_path , [ sprite_dir ] , " sprite " , variables = variables )
build ( yay0_path , [ bin_path ] , " yay0 " )
build (
self . build_path ( ) / " include " / seg . dir / seg . name / ( sprite_name + " .h " ) ,
[ sprite_dir ] ,
" sprite_header " ,
variables = variables ,
)
build ( entry . object_path . with_suffix ( " .bin " ) , sprite_yay0s , " sprite_combine " )
build ( entry . object_path , [ entry . object_path . with_suffix ( " .bin " ) ] , " bin " )
2021-07-16 11:28:37 +02:00
elif seg . type == " pm_msg " :
2021-04-13 09:47:52 +02:00
msg_bins = [ ]
for section_idx , msg_path in enumerate ( entry . src_paths ) :
bin_path = entry . object_path . with_suffix ( " " ) / f " { section_idx : 02X } .bin "
msg_bins . append ( bin_path )
build ( bin_path , [ msg_path ] , " msg " )
build ( [
entry . object_path . with_suffix ( " .bin " ) ,
self . build_path ( ) / " include " / " message_ids.h " ,
] , msg_bins , " msg_combine " )
build ( entry . object_path , [ entry . object_path . with_suffix ( " .bin " ) ] , " bin " )
2021-07-16 11:28:37 +02:00
elif seg . type == " pm_map_data " :
2021-04-13 09:47:52 +02:00
bin_yay0s : List [ Path ] = [ ] # flat list of (uncompressed path, compressed? path) pairs
2021-05-08 08:54:34 +02:00
src_dir = Path ( " assets/x " ) / seg . name
2021-04-13 09:47:52 +02:00
for path in entry . src_paths :
name = path . stem
2021-05-08 08:54:34 +02:00
out_dir = entry . object_path . with_suffix ( " " ) . with_suffix ( " " )
bin_path = out_dir / f " { name } .bin "
2021-04-13 09:47:52 +02:00
if name . startswith ( " party_ " ) :
compress = True
build ( bin_path , [ path ] , " img " , variables = {
" img_type " : " party " ,
" img_flags " : " " ,
} )
2021-05-08 08:54:34 +02:00
elif name == " title_data " :
compress = True
logotype_path = out_dir / " title_logotype.bin "
copyright_path = out_dir / " title_copyright.bin "
2021-07-16 13:08:22 +02:00
copyright_pal_path = out_dir / " title_copyright.pal " # jp only
2021-05-08 08:54:34 +02:00
press_start_path = out_dir / " title_press_start.bin "
build ( logotype_path , [ src_dir / " title/logotype.png " ] , " img " , variables = {
" img_type " : " rgba32 " ,
" img_flags " : " " ,
} )
build ( press_start_path , [ src_dir / " title/press_start.png " ] , " img " , variables = {
" img_type " : " ia8 " ,
" img_flags " : " " ,
} )
2021-07-16 13:08:22 +02:00
if self . version == " jp " :
build ( copyright_path , [ src_dir / " title/copyright.png " ] , " img " , variables = {
" img_type " : " ci4 " ,
" img_flags " : " " ,
} )
build ( copyright_pal_path , [ src_dir / " title/copyright.png " ] , " img " , variables = {
" img_type " : " palette " ,
" img_flags " : " " ,
} )
imgs = [ logotype_path , copyright_path , press_start_path , copyright_pal_path ]
else :
build ( copyright_path , [ src_dir / " title/copyright.png " ] , " img " , variables = {
" img_type " : " ia8 " ,
" img_flags " : " " ,
} )
imgs = [ logotype_path , copyright_path , press_start_path ]
build ( bin_path , imgs , " pack_title_data " )
2021-04-13 09:47:52 +02:00
elif name . endswith ( " _bg " ) :
compress = True
2021-05-08 08:54:34 +02:00
bin_path = self . build_path ( ) / bin_path
2021-04-13 09:47:52 +02:00
build ( bin_path , [ path ] , " img " , variables = {
" img_type " : " bg " ,
" img_flags " : " " ,
} )
elif name . endswith ( " _tex " ) :
compress = False
bin_path = path
2021-04-18 15:26:00 +02:00
elif name . endswith ( " _shape " ) :
map_name = " _ " . join ( name . split ( " _ " ) [ : - 1 ] )
# Handle map XML files, if they exist (TODO: have splat output these)
map_xml = self . resolve_asset_path ( Path ( f " assets/ { self . version } " ) / seg . dir / seg . name / ( map_name + " .xml " ) )
if map_xml . exists ( ) :
# Build a header file for this map
build (
self . build_path ( ) / " include " / seg . dir / seg . name / ( map_name + " .h " ) ,
[ map_xml ] ,
" map_header " ,
)
# NOTE: we don't build the map xml into a _shape or _hit file (yet); the Star Rod Map Editor
# is able to build the xml nonmatchingly into assets/star_rod_build/mapfs/*.bin for people
# who want that (i.e. modders). 'star_rod_build' should be added to asset_stack also.
compress = True
bin_path = path
2021-04-13 09:47:52 +02:00
else :
compress = True
bin_path = path
if compress :
2021-07-16 13:08:22 +02:00
yay0_path = out_dir / f " { name } .Yay0 "
2021-04-13 09:47:52 +02:00
build ( yay0_path , [ bin_path ] , " yay0 " )
else :
yay0_path = bin_path
bin_yay0s . append ( bin_path )
bin_yay0s . append ( yay0_path )
# combine
build ( entry . object_path . with_suffix ( " " ) , bin_yay0s , " mapfs " )
build ( entry . object_path , [ entry . object_path . with_suffix ( " " ) ] , " bin " )
2021-07-16 11:28:37 +02:00
elif seg . type == " pm_charset " :
rasters = [ ]
for src_path in entry . src_paths :
out_path = self . build_path ( ) / seg . dir / seg . name / ( src_path . stem + " .bin " )
build ( out_path , [ src_path ] , " img " , variables = {
" img_type " : " ci4 " ,
" img_flags " : " " ,
} )
rasters . append ( out_path )
build ( entry . object_path . with_suffix ( " " ) , rasters , " pm_charset " )
build ( entry . object_path , [ entry . object_path . with_suffix ( " " ) ] , " bin " )
elif seg . type == " pm_charset_palettes " :
palettes = [ ]
for src_path in entry . src_paths :
out_path = self . build_path ( ) / seg . dir / seg . name / " palette " / ( src_path . stem + " .bin " )
build ( out_path , [ src_path ] , " img " , variables = {
" img_type " : " palette " ,
" img_flags " : " " ,
} )
palettes . append ( out_path )
build ( entry . object_path . with_suffix ( " " ) , palettes , " pm_charset_palettes " )
build ( entry . object_path , [ entry . object_path . with_suffix ( " " ) ] , " bin " )
2021-07-31 16:32:19 +02:00
elif seg . type in [ " pm_effect_loads " , " pm_effect_shims " ] :
2021-07-21 16:46:58 +02:00
build ( entry . object_path , entry . src_paths , " as " )
2021-07-16 11:28:37 +02:00
elif seg . type == " linker " or seg . type == " linker_offset " :
pass
2021-04-13 09:47:52 +02:00
else :
raise Exception ( f " don ' t know how to build { seg . __class__ . __name__ } ' { seg . name } ' " )
2021-08-22 15:10:06 +02:00
# Run undefined_syms through cpp
ninja . build (
str ( self . undefined_syms_path ( ) ) ,
" cpp " ,
str ( self . version_path / " undefined_syms.txt " )
)
2021-04-13 09:47:52 +02:00
# Build elf, z64, ok
2021-10-26 05:26:38 +02:00
additional_objects = [ str ( self . undefined_syms_path ( ) ) ]
2021-04-13 09:47:52 +02:00
ninja . build (
str ( self . elf_path ( ) ) ,
" ld " ,
str ( self . linker_script_path ( ) ) ,
2021-10-26 05:26:38 +02:00
implicit = [ str ( obj ) for obj in built_objects ] + additional_objects ,
2021-04-13 09:47:52 +02:00
variables = { " version " : self . version , " mapfile " : str ( self . map_path ( ) ) } ,
)
ninja . build (
str ( self . rom_path ( ) ) ,
" z64 " ,
str ( self . elf_path ( ) ) ,
implicit = [ CRC_TOOL ] ,
2021-10-26 05:26:38 +02:00
variables = { " version " : self . version } ,
2021-04-13 09:47:52 +02:00
)
ninja . build (
str ( self . rom_ok_path ( ) ) ,
" sha1sum " ,
f " ver/ { self . version } /checksum.sha1 " ,
implicit = [ str ( self . rom_path ( ) ) ] ,
)
ninja . build ( " generated_headers_ " + self . version , " phony " , generated_headers )
def make_current ( self , ninja : ninja_syntax . Writer ) :
current = Path ( " ver/current " )
try :
current . unlink ( )
except Exception :
pass
current . symlink_to ( self . version )
ninja . build ( " ver/current/build/papermario.z64 " , " phony " , str ( self . rom_path ( ) ) )
if __name__ == " __main__ " :
from argparse import ArgumentParser
parser = ArgumentParser ( description = " Paper Mario build.ninja generator " )
parser . add_argument ( " version " , nargs = " * " , default = [ ] , help = " Version(s) to configure for. Most tools will operate on the first-provided only. Supported versions: " + ' , ' . join ( VERSIONS ) )
parser . add_argument ( " --cpp " , help = " GNU C preprocessor command " )
2021-08-25 14:34:03 +02:00
parser . add_argument ( " -c " , " --clean " , action = " store_true " , help = " Delete assets and previously-built files " )
2021-04-13 09:47:52 +02:00
parser . add_argument ( " --splat " , default = " tools/splat " , help = " Path to splat tool to use " )
parser . add_argument ( " --split-code " , action = " store_true " , help = " Re-split code segments to asm files " )
parser . add_argument ( " --no-split-assets " , action = " store_true " , help = " Don ' t split assets from the baserom(s) " )
2021-07-28 13:47:05 +02:00
parser . add_argument ( " -d " , " --debug " , action = " store_true " , help = " Generate debugging information " )
parser . add_argument ( " -n " , " --non-matching " , action = " store_true " , help = " Compile nonmatching code. Combine with --debug for more detailed debug info " )
2021-08-25 14:34:03 +02:00
parser . add_argument ( " -w " , " --no-warn " , action = " store_true " , help = " Inhibit compiler warnings " )
2021-08-29 18:48:23 +02:00
parser . add_argument ( " --ccache " , action = " store_true " , help = " Use ccache " )
2021-04-13 09:47:52 +02:00
args = parser . parse_args ( )
exec_shell ( [ " make " , " -C " , str ( ROOT / args . splat ) ] )
# on macOS, /usr/bin/cpp defaults to clang rather than gcc (but we need gcc's)
2021-04-18 04:31:33 +02:00
if args . cpp is None and sys . platform == " darwin " and " Free Software Foundation " not in exec_shell ( [ " cpp " , " --version " ] ) :
2022-08-28 06:12:47 +02:00
if " Free Software Foundation " in exec_shell ( [ " cpp-12 " , " --version " ] ) :
args . cpp = " cpp-12 "
2021-08-09 17:59:05 +02:00
else :
print ( " error: system C preprocessor is not GNU! " )
print ( " This is a known issue on macOS - only clang ' s cpp is installed by default. " )
print ( " Use ' brew ' to obtain GNU cpp, then run this script again with the --cpp option, e.g. " )
2022-08-28 06:12:47 +02:00
print ( " ./configure --cpp cpp-12 " )
2021-08-09 17:59:05 +02:00
exit ( 1 )
2021-04-13 09:47:52 +02:00
# default version behaviour is to only do those that exist
if len ( args . version ) > 0 :
versions = args . version
else :
versions = [ ]
for version in VERSIONS :
rom = ROOT / f " ver/ { version } /baserom.z64 "
2021-08-18 19:10:11 +02:00
print ( f " configure: looking for baserom { rom . relative_to ( ROOT ) } " , end = " " )
2021-04-13 09:47:52 +02:00
if rom . exists ( ) :
print ( " ...found " )
versions . append ( version )
else :
print ( " ...missing " )
if len ( versions ) == 0 :
print ( " error: no baseroms found " )
exit ( 1 )
if args . clean :
print ( " configure: cleaning... " )
exec_shell ( [ " ninja " , " -t " , " clean " ] )
for version in versions :
rm_recursive ( ROOT / f " assets/ { version } " )
rm_recursive ( ROOT / f " ver/ { version } /assets " )
rm_recursive ( ROOT / f " ver/ { version } /build " )
rm_recursive ( ROOT / f " ver/ { version } /.splat_cache " )
2021-07-28 13:47:05 +02:00
cflags = " "
cppflags = " "
if args . non_matching :
cppflags + = " -DNON_MATCHING "
if args . debug :
cflags + = " -ggdb3 " # we can generate more accurate debug info in non-matching mode
cppflags + = " -DDEBUG " # e.g. affects ASSERT macro
elif args . debug :
# g1 doesn't affect codegen
2021-11-01 02:26:58 +01:00
cflags + = " -ggdb3 "
2021-07-28 13:47:05 +02:00
2021-08-25 14:34:03 +02:00
if not args . no_warn :
2022-10-16 16:22:18 +02:00
cflags + = " -Wmissing-braces -Wimplicit -Wredundant-decls -Wstrict-prototypes "
2021-08-25 14:34:03 +02:00
2021-04-13 09:47:52 +02:00
# add splat to python import path
sys . path . append ( str ( ( ROOT / args . splat ) . resolve ( ) ) )
ninja = ninja_syntax . Writer ( open ( str ( ROOT / " build.ninja " ) , " w " ) , width = 9999 )
2021-10-26 05:26:38 +02:00
write_ninja_rules ( ninja , args . cpp or " cpp " , cppflags , cflags , args . ccache , args . non_matching , args . debug )
2021-04-13 09:47:52 +02:00
write_ninja_for_tools ( ninja )
skip_files = set ( )
all_rom_oks : List [ str ] = [ ]
first_configure = None
for version in versions :
print ( f " configure: configuring version { version } " )
configure = Configure ( version )
if not first_configure :
first_configure = configure
2021-10-26 05:26:38 +02:00
configure . split ( not args . no_split_assets , args . split_code , args . debug )
configure . write_ninja ( ninja , skip_files , args . non_matching , args . debug )
2021-04-13 09:47:52 +02:00
all_rom_oks . append ( str ( configure . rom_ok_path ( ) ) )
first_configure . make_current ( ninja )
ninja . build ( " all " , " phony " , all_rom_oks )
ninja . default ( " all " )