From 37523c19b9f4c96aa9462712a225a73f60332320 Mon Sep 17 00:00:00 2001 From: Hexalotl <15166449+Hexalotl@users.noreply.github.com> Date: Wed, 7 Feb 2024 23:14:13 -0800 Subject: [PATCH] Adding additional quick start guides and adding boundary calculating script (#244) * Adding script to automate config file updates * Adding doc covering basics of decomp.me * Adding doc covering m2c basics * Updating Ghidra doc with note about special inline functions * Updating README with links to new docs --- README.MD | 2 + config/assets.yml | 78 ++-- config/rel_slices.yml | 100 ++--- docs/decomp_basics.md | 48 ++- docs/decomp_me_basics.md | 26 ++ .../decomp_me_compiler_settings.png | Bin 0 -> 13923 bytes .../ghidra_inline_register_function.png | Bin 0 -> 34172 bytes docs/doc_assets/m2c_compiler_settings.png | Bin 0 -> 5695 bytes docs/ghidra_setup.md | 9 +- docs/m2c_basics.md | 16 + requirements.txt | 3 +- tools/tu_config.py | 370 ++++++++++++++++++ 12 files changed, 558 insertions(+), 94 deletions(-) create mode 100644 docs/decomp_me_basics.md create mode 100644 docs/doc_assets/decomp_me_compiler_settings.png create mode 100644 docs/doc_assets/ghidra_inline_register_function.png create mode 100644 docs/doc_assets/m2c_compiler_settings.png create mode 100644 docs/m2c_basics.md create mode 100644 tools/tu_config.py diff --git a/README.MD b/README.MD index f5f9506f..196b7c63 100644 --- a/README.MD +++ b/README.MD @@ -57,7 +57,9 @@ Use `--recursive` when cloning to have ppcdis in the repository. - [Dumping Game Files](./docs/extract_game.md) - [Ghidra Setup](./docs/ghidra_setup.md) - [Generating Decomp Context](./docs/generating_decomp_context.md) +- [decomp.me Basics](./docs/decomp_me_basics.md) - [Ghidra Basics](./docs/ghidra_basics.md) +- [m2c Basics](./docs/m2c_basics.md) - [Decomp Basics](./docs/decomp_basics.md) ## Credits diff --git a/config/assets.yml b/config/assets.yml index d727bc97..0c436826 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -148,6 +148,11 @@ config/rel.yml: addrs: [0x80650880, 0x806508C0] type: vtx # m_msg + wipe1_v: + addrs: [0x80652AD0, 0x80652C60] + type: vtx + g_wipe1_txt: + addrs: [0x80652C60, 0x80653460] msg/con_kaiwa2_w1_tex: addrs: [0x80657360, 0x80657B60] msg/con_kaiwa2_w2_tex: @@ -167,11 +172,40 @@ config/rel.yml: addrs: [0x8065FD4C, 0x80674F90] aBTD_island_ldr: addrs: [0x80674F90, 0x806817CC] - wipe1_v: - addrs: [0x80652AD0, 0x80652C60] - type: vtx - g_wipe1_txt: - addrs: [0x80652C60, 0x80653460] + aKOI_obj_e_koinobori_a_pal: + addrs: [0x806C5900, 0x806C5920] + type: pal16 + obj_e_koinobori_b_pal: + addrs: [0x806C5920, 0x806C5940] + type: pal16 + # ac_lotus + aLOT_obj_01_lotus_pal: + addrs: [0x806C59E0, 0x806C5A00] + type: pal16 + obj_02_lotus_pal: + addrs: [0x806C5A00, 0x806C5A20] + type: pal16 + obj_03_lotus_pal: + addrs: [0x806C5A20, 0x806C5A40] + type: pal16 + obj_04_lotus_pal: + addrs: [0x806C5A40, 0x806C5A60] + type: pal16 + obj_05_lotus_pal: + addrs: [0x806C5A60, 0x806C5A80] + type: pal16 + obj_06_lotus_pal: + addrs: [0x806C5A80, 0x806C5AA0] + type: pal16 + obj_07_lotus_pal: + addrs: [0x806C5AA0, 0x806C5AC0] + type: pal16 + obj_08_lotus_pal: + addrs: [0x806C5AC0, 0x806C5AE0] + type: pal16 + obj_09_lotus_pal: + addrs: [0x806C5AE0, 0x806C5B00] + type: pal16 mFM_beach_pal_0: addrs: [0x80C59CA8, 0x80C59CC8] type: pal16 @@ -356,37 +390,3 @@ config/rel.yml: addrs: [0x80F8C448, 0x80F8C468] type: pal16 # ac_koinobori - aKOI_obj_e_koinobori_a_pal: - addrs: [0x806C5900, 0x806C5920] - type: pal16 - obj_e_koinobori_b_pal: - addrs: [0x806C5920, 0x806C5940] - type: pal16 - # ac_lotus - aLOT_obj_01_lotus_pal: - addrs: [0x806C59E0, 0x806C5A00] - type: pal16 - obj_02_lotus_pal: - addrs: [0x806C5A00, 0x806C5A20] - type: pal16 - obj_03_lotus_pal: - addrs: [0x806C5A20, 0x806C5A40] - type: pal16 - obj_04_lotus_pal: - addrs: [0x806C5A40, 0x806C5A60] - type: pal16 - obj_05_lotus_pal: - addrs: [0x806C5A60, 0x806C5A80] - type: pal16 - obj_06_lotus_pal: - addrs: [0x806C5A80, 0x806C5AA0] - type: pal16 - obj_07_lotus_pal: - addrs: [0x806C5AA0, 0x806C5AC0] - type: pal16 - obj_08_lotus_pal: - addrs: [0x806C5AC0, 0x806C5AE0] - type: pal16 - obj_09_lotus_pal: - addrs: [0x806C5AE0, 0x806C5B00] - type: pal16 diff --git a/config/rel_slices.yml b/config/rel_slices.yml index a8b3638c..c0ba750a 100644 --- a/config/rel_slices.yml +++ b/config/rel_slices.yml @@ -34,8 +34,6 @@ m_banti.c: .rodata: [0x80641388, 0x806413E0] .data: [0x8064F548, 0x8064F658] .bss: [0x8125A830, 0x8125AC80] -m_bg_tex.c: - .bss: [0x8125AC80, 0x81263080] m_bg_item.c: .text: [0x80378858, 0x803789F8] .rodata: [0x806413E0, 0x806413F0] @@ -61,7 +59,7 @@ m_cockroach.c: m_collision_obj.c: .text: [0x80394518, 0x80395B24] .rodata: [0x80641CE8, 0x80641D20] - .data: [0x80651208,0x806512D8] + .data: [0x80651208, 0x806512D8] .bss: [0x812663D0, 0x81266400] m_common_data.c: .text: [0x80395B24, 0x80395BE8] @@ -100,9 +98,9 @@ m_fbdemo_triforce.c: .text: [0x803A0ABC, 0x803A0F00] .rodata: [0x80641F60, 0x80641FA0] m_fbdemo_wipe1.c: - .text: [0x803A0F00, 0x803A12F8] - .rodata: [0x80641FA0, 0x80641FC0] - .data: [0x80652AD0, 0x80653558] + .text: [0x803A0F00, 0x803A12F8] + .rodata: [0x80641FA0, 0x80641FC0] + .data: [0x80652AD0, 0x80653558] m_fbdemo_fade.c: .text: [0x803A12F8, 0x803A1508] .rodata: [0x80641FC0, 0x80641FD8] @@ -315,8 +313,6 @@ m_view.c: m_roll_lib.c: .text: [0x803F6570, 0x803F76F0] .rodata: [0x806434D8, 0x80643538] -sys_stacks.c: - .bss: [0x812F5670, 0x812F9670] m_cpak.c: .text: [0x80403830, 0x80403874] .data: [0x8065EC98, 0x8065ECA0] @@ -395,7 +391,7 @@ ac_animal_logo_misc.c: .text: [0x804117D4, 0x80411A60] ac_ball.c: .text: [0x80411F64, 0x80413DD4] - .rodata: [0x80643A90,0x80643B60] + .rodata: [0x80643A90, 0x80643B60] .data: [0x8065FBF8, 0x8065FC58] .bss: [0x812F96E0, 0x812F96E8] ac_birth_control.c: @@ -430,22 +426,13 @@ ac_haniwa.c: .text: [0x80427624, 0x80428F64] .rodata: [0x806440B8, 0x806440F8] .data: [0x80683D08, 0x80683E98] -ac_koinobori.c: - .text: [0x805B27B8, 0x805B2AE0] - .data: [0x806C58A0, 0x806C5940] - .rodata: [0x8064A978, 0x8064A990] -ac_lotus.c: - .text: [0x805B2AE0, 0x805B3010] - .rodata: [0x8064A990, 0x8064A9B0] - .data: [0x806C5940, 0x806C5B58] - .bss: [0x81327F48, 0x81327F68] ac_psnowman.c: .text: [0x80484098, 0x80484694] .rodata: [0x80644C30, 0x80644C60] .data: [0x8068A458, 0x8068A480] ac_rope.c: .text: [0x804967A4, 0x80496AB8] - .rodata: [0x80644DB0,0x80644DB8] + .rodata: [0x80644DB0, 0x80644DB8] .data: [0x8068BB80, 0x8068BBE0] ac_set_manager.c: .text: [0x80496AB8, 0x80496F50] @@ -527,14 +514,14 @@ ac_t_rei1.c: ac_t_rei2.c: .text: [0x804A99AC, 0x804A9B00] .data: [0x8068EF38, 0x8068EF78] -ac_t_tumbler.c: - .text: [0x804A9CC4, 0x804A9F24] - .rodata: [0x80645F10, 0x80645F18] - .data: [0x8068EFE0, 0x8068F040] ac_t_tama.c: .text: [0x804A9B00, 0x804A9CC4] .data: [0x8068EF78, 0x8068EFE0] .rodata: [0x80645F00, 0x80645F10] +ac_t_tumbler.c: + .text: [0x804A9CC4, 0x804A9F24] + .rodata: [0x80645F10, 0x80645F18] + .data: [0x8068EFE0, 0x8068F040] ac_t_umbrella.c: .text: [0x804A9F24, 0x804AA4C8] .data: [0x8068F040, 0x8068F310] @@ -547,7 +534,7 @@ ac_t_zinnia1.c: .text: [0x804AA72C, 0x804AA880] .data: [0x8068F370, 0x8068F3B0] ac_t_zinnia2.c: - .text: [0x804AA880, 0x804AA9d4] + .text: [0x804AA880, 0x804AA9D4] .data: [0x8068F3B0, 0x8068F3F0] ac_tools.c: .text: [0x804AC034, 0x804AC2D8] @@ -583,8 +570,8 @@ ef_room_sunshine_museum.c: .rodata: [0x806464B8, 0x80646508] .data: [0x8069C0A0, 0x8069C0C8] ef_room_sunshine_minsect.c: - .text: [0x804D0F3C,0x804D1BBC] - .rodata: [0x80646508,0x80646558] + .text: [0x804D0F3C, 0x804D1BBC] + .rodata: [0x80646508, 0x80646558] .data: [0x8069C0C8, 0x8069C0F0] .bss: [0x81300BD0, 0x81300BD8] m_huusui_room_ovl.c: @@ -608,6 +595,9 @@ ac_fuusen.c: m_mail_check_ovl.c: .text: [0x8050F06C, 0x8050F838] .data: [0x8069F320, 0x8069FA40] +ac_dummy.c: + .text: [0x8050F838, 0x8050F848] + .data: [0x8069FA40, 0x8069FA68] m_all_grow_ovl.c: .text: [0x8050F848, 0x80515340] .rodata: [0x80649098, 0x80649110] @@ -660,7 +650,7 @@ ac_npc_engineer.c: ac_npc_rtc.c: .text: [0x80573044, 0x80574134] .rodata: [0x80649A08, 0x80649A40] - .data: [0x806BF648,0x806BF788] + .data: [0x806BF648, 0x806BF788] ac_npc_sendo.c: .text: [0x80574134, 0x80576468] .rodata: [0x80649A40, 0x80649A58] @@ -668,7 +658,7 @@ ac_npc_sendo.c: .bss: [0x8131B258, 0x8131B298] ac_ev_majin.c: .text: [0x80592A40, 0x80593158] - .rodata: [0x80649D98,0x80649DA0] + .rodata: [0x80649D98, 0x80649DA0] .data: [0x806C2B50, 0x806C2BD0] ac_boat.c: .text: [0x805A6CF4, 0x805A856C] @@ -680,24 +670,30 @@ ac_douzou.c: .data: [0x806C4DF0, 0x806C5018] ac_dump.c: .text: [0x805AE704, 0x805AECE8] - .rodata: [0x8064A7E8,0x8064A808] + .rodata: [0x8064A7E8, 0x8064A808] .data: [0x806C5018, 0x806C5120] -ac_dummy.c: - .text: [0x8050F838, 0x8050F848] - .data: [0x8069FA40, 0x8069FA68] ac_kago.c: .text: [0x805B1A08, 0x805B1D50] .data: [0x806C5750, 0x806C57A8] +ac_koinobori.c: + .text: [0x805B27B8, 0x805B2AE0] + .data: [0x806C58A0, 0x806C5940] + .rodata: [0x8064A978, 0x8064A990] +ac_lotus.c: + .text: [0x805B2AE0, 0x805B3010] + .rodata: [0x8064A990, 0x8064A9B0] + .data: [0x806C5940, 0x806C5B58] + .bss: [0x81327F48, 0x81327F68] ac_mikuji.c: .text: [0x805B414C, 0x805B44C4] .data: [0x806C5C10, 0x806C5CA0] ac_nameplate.c: - .text: [0x805B63FC,0x805B65C4] - .data: [0x806C6110,0x806C6138] + .text: [0x805B63FC, 0x805B65C4] + .data: [0x806C6110, 0x806C6138] ac_radio.c: - .text: [0x805B887C,0x805B8C7C] - .rodata: [0x8064AB58,0x8064AB68] - .data: [0x806C6558,0x806C65A0] + .text: [0x805B887C, 0x805B8C7C] + .rodata: [0x8064AB58, 0x8064AB68] + .data: [0x806C6558, 0x806C65A0] ac_shrine.c: .text: [0x805BA4D8, 0x805BB8D0] .rodata: [0x8064ABD0, 0x8064AC18] @@ -714,7 +710,7 @@ ac_tama.c: .data: [0x806C7110, 0x806C7140] ac_toudai.c: .text: [0x805BEA00, 0x805BFC28] - .rodata: [0x8064ACB0,0x8064AD28] + .rodata: [0x8064ACB0, 0x8064AD28] .data: [0x806C7200, 0x806C72D0] ac_train0.c: .text: [0x805BFC28, 0x805C0614] @@ -788,18 +784,16 @@ m_passwordChk_ovl.c: .data: [0x806D1D18, 0x806D1D50] .bss: [0x813413D0, 0x813413F8] ac_weather.c: - .text: [0x8060193C, 0x80602E70] - .rodata: [0x8064BAE8, 0x8064BB08] - .data: [0x806D1D50, 0x806D1DA0] -ac_weather_fine.c: - .data: [0x806D1DA0, 0x806D1DB8] + .text: [0x8060193C, 0x80602E70] + .rodata: [0x8064BAE8, 0x8064BB08] + .data: [0x806D1D50, 0x806D1DA0] ac_weather_rain.c: .text: [0x80602E70, 0x80603494] - .rodata: [0x8064BB08,0x8064BB30] + .rodata: [0x8064BB08, 0x8064BB30] .data: [0x806D1DB8, 0x806D1DF8] ac_weather_snow.c: .text: [0x80603494, 0x80603B44] - .rodata: [0x8064BB30, 0x8064BB88] + .rodata: [0x8064BB30, 0x8064BB88] .data: [0x806D1DF8, 0x806D1E10] ac_weather_sakura.c: .text: [0x80603B44, 0x8060420C] @@ -817,10 +811,8 @@ first_game.c: .text: [0x80629CA8, 0x80629D4C] sys_romcheck.c: .text: [0x80629D4C, 0x80629D8C] -sys_dynamic.c: - .bss: [0x813413F8, 0x81361820] m_play.c: - .text: [0x80629D8C,0x8062B630] + .text: [0x80629D8C, 0x8062B630] .rodata: [0x8064D1B8, 0x8064D1C0] .data: [0x806D46D0, 0x806D4958] .bss: [0x81361820, 0x8148DA60] @@ -849,8 +841,10 @@ m_prenmi.c: audio.c: .text: [0x8062DC04, 0x8062E96C] .rodata: [0x8064D340, 0x8064D360] - .data: [0x806D4CB0,0x806D4D40] - .bss: [0x8148DA78,0x8148DA7C] + .data: [0x806D4CB0, 0x806D4D40] + .bss: [0x8148DA78, 0x8148DA7C] +ac_weather_fine.c: + .data: [0x806D1DA0, 0x806D1DB8] # dataobject.obj files data/combi/data_combi.c: @@ -881,3 +875,9 @@ data/field/bg/earth_pal.c: .data: [0x80C90100, 0x80C90280] data/field/bg/rail_pal.c: .data: [0x80F8C2C8, 0x80F8C460] +m_bg_tex.c: + .bss: [0x8125AC80, 0x81263080] +sys_stacks.c: + .bss: [0x812F5670, 0x812F9670] +sys_dynamic.c: + .bss: [0x813413F8, 0x81361820] diff --git a/docs/decomp_basics.md b/docs/decomp_basics.md index 13dffb03..2466a985 100644 --- a/docs/decomp_basics.md +++ b/docs/decomp_basics.md @@ -1,7 +1,32 @@ # Decompilation Basics And Tips -## Determining Slice Boundaries -When adding in a new Translation Unit (TU) you currently need to manually calculate the addresses for the TU's section boundaries. You can determine the boundaries for a new TU by using the [symbol map files extracted from the game](./extract_game.md) and adding an offset to the addresses. +## Adding TU Boundaries And Asset Boundaries With TU Config Tool +Adding boundaries for the binary sections and assets of a given TU can be done by using the [TU Config script](../tools/tu_config.py) inside of the tools folder. Using this tool allows you to more quickly add in the address boundaries for each section of a TU and optionally add in any assets to the asset config file. + +To use this tool follow the steps below: +1. Run the command: + +``` console +python3 ./tools/tu_config.py +``` + +2. Type the name of the TU you want to add to the config files. +3. If it detects that the TU had data assets it will prompt you if you would like to add them to the assets config file. +4. For each data symbol you will be given the option to add them to the config file or not. +5. If you are adding the symbol to the config file it will optionally ask you if you know the data type. +6. After the tool has finished, run the [configure script](../configure.py). + +A list of tool parameters can be found below: + +| Argument | Description | +|------------------------|------------------------------------------------------------------------------------------------| +| `--symbol-map` | Path to the [symbol map](./extract_game.md). | +| `--binary-slices-file` | Path to the binary slices config file. Defaults to [rel_slices.yml](../config/rel_slices.yml). | +| `--asset-slices-file` | Path to the asset slices config file. Defaults to [assets.yml](../config/assets.yml). | + + +## Determining Slice Boundaries (Manual) +If you do not use the TU Config tool, you will need to manually add in the slice boundaries to the config file. You can determine the boundaries for a new TU by using the [symbol map files extracted from the game](./extract_game.md) and adding an offset to the addresses. If using the symbol map, search for `.o` to find each applicable section, its start address and end address (usually the address of the next address with a different TU name attached to it). Note that some TUs may or may not have certain sections. You can determine this by searching through the symbol map and noting which sections are found. @@ -48,6 +73,15 @@ Once the boundaries have been determined, paste them into the [slices file](../c > .text: [0x8050F838, 0x8050F848] >~~~ +## Determining Asset Boundaries +We declare asset data such as textures and palettes in the [assets config file](../config/assets.yml) and include them into the C file in which they are referenced. This process follows similar steps as above where a new entry for each data object is declared in the config file using the starting and ending address range. You can include the data type if it is known. Optionally this step can be done with the [TU Config tool](../tools/tu_config.py) instead of manually updating the file. + +Once the data address range has been added to the config file, you can add it to the C source file using an `#include` statement following the format of `#include "assets/OBJECT_NAME"` where `OBJECT_NAME` is the name of the data object. + +> :warning: Due to how the configure script scans through files, if you used `.c_inc` files you currently need to "hint" to the configure script that these files are referenced by using them in the root C file. An example can be found in [`ac_lotus.c`](../src/ac_lotus.c) and [`ac_lotus_draw.c_inc`](../src/ac_lotus_draw.c_inc) + +After the steps above have been completed, run the [configure script](../configure.py). + ## Generating Assembly Text File To use sites such as [decomp.me](https://decomp.me) or [m2c](https://simonsoftware.se/other/m2c.html) you will need to paste in the assembly code you wish to match. The easiest way to get the assembly is by first generating an assembly text file with symbols included. To create this file run the following command at the root of the repository: @@ -56,4 +90,12 @@ To use sites such as [decomp.me](https://decomp.me) or [m2c](https://simonsoftwa python3 tools/ppcdis/disassembler.py config/rel.yml build/rel_labels.pickle build/rel_relocs.pickle rel.s -m config/symbols.yml ~~~ -This will generate a `rel.s` file. Once generated open the file and search for the name of the function you wish to match and copy the assembly listed in the file for that function. \ No newline at end of file +This will generate a `rel.s` file. Once generated open the file and search for the name of the function you wish to match and copy the assembly listed in the file for that function. + +### Copying Function Assembly +To copy the assembly for a specific function, follow the steps below: +1. Open the generated `rel.s` file. +2. Search for the name of the function. Search for the first line with the format of `.global FUNCTION_NAME` where `FUNCTION_NAME` is the name of the function you are searching for. +3. Search for the line at the bottom of the assembly code block following the format `.size FUNCTION_NAME, . - FUNCTION_NAME` where `FUNCTION_NAME` is the name of the function you are searching for. +4. Select all of the lines between those two lines, include the two lines themselves. +5. Paste the copied assembly into the tool of your choice. \ No newline at end of file diff --git a/docs/decomp_me_basics.md b/docs/decomp_me_basics.md new file mode 100644 index 00000000..c626c4e7 --- /dev/null +++ b/docs/decomp_me_basics.md @@ -0,0 +1,26 @@ +# Decomp.me Basics +[decomp.me](https://decomp.me/) is an online collaborative tool that allows reverse engineers to match functions against the original code and share their progress with others. + +## Creating A Scratch +A "scratch" refers to a WIP playground that you can use to iterate on a function or functions until you've reached a match. + +In order to create a new scratch, follow the steps below: +1. Open [decomp.me](https://decomp.me/). +2. Login or register for a new account. +3. Click on `New scratch` in the top-right corner of the page. +4. Select `Gamecube/Wii` as the target platform. +5. Under the compiler section select from the "preset" dropdown either `Animal Crossing (REL)` or `Animal Crossing (DOL)` depending on where the function you are reverse engineering is located. For most game-specific code you will using the `REL` option. + +![decomp.me compiler settings](./doc_assets/decomp_me_compiler_settings.png) + +6. Copy the assembly for the function you want to match [from the assembly file you generated](decomp_basics.md). +7. Copy the context for the function from the [context file you generated for it](./generating_decomp_context.md). +8. Press the `Create Scratch` button. + +## Matching +After your scratch has been created you can begin reverse engineering and writing your own C code. You can use tools such as [m2c](./m2c_basics.md) or [Ghidra](ghidra_basics.md) to help assist you. As you make modifications to your scratch you can see how close you are to matching the function. + +Some important notes when matching: +* Variable ordering matters. If you're finding that you're stuck on the last percent or two of a function, try re-arranging the declaration of your variables. +* What you write may affect the assembly the comes before or after it. You may find that as you fill out an earlier/later section that your function matching can increase. +* Only include what you need in your pasted in context. If you include other functions/data you may find that it adds to the "matched" code. In these cases you may need to only forward-declare the respective function(s) and data. \ No newline at end of file diff --git a/docs/doc_assets/decomp_me_compiler_settings.png b/docs/doc_assets/decomp_me_compiler_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..f612c3fbb1b42700011131e4bb8336e2284e12fc GIT binary patch literal 13923 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 AdjJ3c literal 0 HcmV?d00001 diff --git a/docs/doc_assets/ghidra_inline_register_function.png b/docs/doc_assets/ghidra_inline_register_function.png new file mode 100644 index 0000000000000000000000000000000000000000..5e6ac500ea0d7d8be046eaae5dc620fe36e8a48e GIT binary patch literal 34172 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

t>o zKcKkvi<_i((Y|XW{8abZcW1{NT_F2@tLKjvr{ouvL1=F=+t)>jh=QDSKVs~FBct?7 zm*XZSrZKiJug3rzfNTC{ah>CE@evkhh440n4HneZ_y{ffDPp^)r{u|Y3e`48rMl4s zQiYphcsuxArVMv_=Zr~0C|v zcA5I#i;=_&p3BHsn1YU&vug)`$7MmQfeU-cvdzz#uKLYB1BrrJ#VukQos3diYo z65SMX8ZFoOg69ao9M=@;HIqnFu9#6jc_~wQCEk&nI4&qid?2obGFeoT-&z0sEUOsU z(+u%*-6tr)lsz-&(B18inUpcpyrxIMGx)r-239G7fK-0(9K;VdCFe!z_$7jvVU0Ing=#rU?Z_cZ_0Bor)S zuqc$BY%E@6dWYJQ0&mD374{w>ko z5%c-95}rou(swQ43)YNmMLOsmwH@lb0o_cJ^L56w@iZd#VZDP*iq^TY>9WZ!8PmqU zZpkaZ(o5cYAfcVSmIMV}E>D?sWr7vAnR8cg8ONbLSj$o7C12&N=HYw?M;qK0(qZN( z*r>&hqVvPV32|=&L`Hze(l-@bGuax4Y4Z4rkJD0Eo7dDwD~UqfkpNnHO{cf@^YeE0 zcGtb0^3F+BO?c5q~rRcdX;<)(IGuSrqax4H3I{K07ueeh4psZCErB`;N~mB z@lm~LEi>Cg+Z}waU9%oV4pnn?_{ItjPa-u%(e9<2dCjQ;r>~zQs^{iSF5}eGEVMsX zVqHM(u|qn#$y!qAT*1x|_m)u#!o{b9-6m5c8Hg~Qxi`Iw%!gmPd`KTDe*fr^Ez)Dp z5LeIAm2_Q15h`qf8gN2Tj6$5Ja;2vrpZ3;5a&KW}>}fT9d)X zUf!+WhWqxb71ZEnIPTpMSlGzO+WY|8%!7NGdOkk>}u;gf_w z4WI)fJ=8wpKQGhv^JkUze+E(qmiXO{>}o;`tH7tBv71c?HKV z-AR>jC?2X-djD+wewLu*ik_syfW*&WVqQVXy`Y%#H6_)voz0(`&7L-s*NP!=;(Qv6 zQz7c1M}en~L>biEURJU;C>!_Y)gUVDzFy_~>m7{k6Ud83y`2>ldmCXUL9ih4zcp%?wTmzTH?nypgUd{M{ zo&4*9wd*&Na(`QBVpA>VEDQ~omin@kU(7H!SV8aGuYb?$0zO+GcW)vK5X z^84<`C+(NtJ*^~V5TE$2QI8NyzWaz89+TK8TGk#sK+H=YV!h@jmD=sw)bi^>*w93d z%y@-ejz{cc!7E}*xw9{TJCMLi`i8*Z6^b$jP@=QProLGSYGSKY0XGUe@4^h z8Q3giIpHtwv2DfS^^9jy)-jrQaX1;;CqUcltf}`&PfVCXiw_Wsz%MaOJn(HxIu6|k zm_9++Nd99t$9HW@0zYgf;W@{|+_)?z={$5t*S__eLd(r+Te7b3?yha#nil4haxyUZ z{=|Bm;bbfD>{%%b38}x6?@(;EwsnD%O+b)VLzzVT6azMn&$W_6k>Twb*J@2&K$uKR zCq*iOcV((JC&-84?NGV7*2ny!s~;o07Vd`R3aNo$9?XWUfT)@$p*BMRGSp1 z=%=U9Ba0=1Kvm}UViW7aU>qBelMs5it79Yt$M$99#?i9)4dVg_dcCouYfx9@R6R6q8KW9S*w>9EAB=U}N`~Q$`!sQ*_ z{+)OtzsO6bo`-iuOhBP>&pGET0C4381+FhP*oqy(td{dGIj#Kg< zKY^ecDpu{M6{4?PF;NhKg6ue>M`e}P{ns_SbH~SPy%RVj9I{>)I(EvXV1N={!>0{w21!YubHb8QS3OgP2O$*=C|r1~sYV zggA*``S|KbD{hTGz7yVvRW467a4H&!@0%CKp;x&|nnB1Al(3M+FGEQi5qv39&*~LP z%T0W)*qcni5Vqoht!F$@B7J> zQ#L*iUPDsdxP8KATi>LU7e)-rY?UCg=+b^S6F4XU3hnT0#w+WA|lzx-+F+ewT*G!eR z&mg7YO0v5O<3CTb0mbWI!p1L2>kon+7j}X>3@=+){(Im57Z^PtaO0bVPVJ;C01U*n z8D$}FUpgg|u3Kvf_we_)`nMtoHn2bKl1$pE;nK6B1zkmv0cSo7cDQqP{^%S4H`Gi@ zSa;mD=z)at<26{}h{3GgU5Y@gf5X(tvbNQ7Aw@-9kR+XiT`>h!wESw=My5cw`mRhh z^oYn_vX)T)1uib(mX%XmQ=W8%^^2H1cibi22R0)l%uW`k zt@r9WOln|4<;Mpl4riR=9z`IUEDMJe(0Ue0b@nx^A>=%8k!MKiYYaVxZgciUc$?B` z?!GvsAc2mdluR>6wdDH zCEZ~KHxWwe#1pOqpIG=q;3pFl?JbKA|C zt~FTVx0`WoSJ9JjV90qd51D{^qs&h^4t+~ zE_Kq;JNhfKf`^FK+!rIk3fY{^cj&}3%ij%X*n@qZbj2BUE&HH>`>h^&WLVmaK5G3Z z%@dJAE^FM>>R-pKm4NZ*gPvr&A7Zf)IMe4ao}oh)o~vD8;#tQano8ddE^fcFKwwJ1APtjU?ZFasPP?~D=*V0; z0&2WBuZ|lDdgDu68;&jw<+W9iv2`G_HtxG@*gwk} z_0Lwu+LjrJD^CXQ#1AeG0O2f@=L*AB))6=sqI)J;SWI-lEyI|U6ZAel3xP!j!-{^z ze78I7u9bU#o|;IBSyR^1u-ip#haf*DFgZK3j5q{A-G8|CfIXtzZ(kB)QUyUqI{5)b z{8Yr%W$bk1!36KBsa+QrugdPat?}eZes=bimPW3>rw$O-e0bO<&Td~@LKN;?QFlF+_rxIQ1FKvPxA#M1}~f44Fk9S z*w_DOeOMn=jV0=fE3ekevvaFL^>%rp^)H}XL<(92&17PUd~_mu@K)%GiiipP^t)G_ z2Xy%V8{_*Y{8s~NLNOHn8rk1;QcM1sd}?JpEYF%glRGSI@J|nhZ_QiYeXR}V%R=8z(rl5iD>hZMnVxP#YZqP)`><8 z8i>rmYUwt#&DpsM+6QIbt-1Q*#Ka4Exw%iL(#%3v_3~BDAhp#0mT7|$p&3(t3Q9w) z_cb*7IT1}9@r^D22$tsXHjpgFauRvO(9lpKWVQ4YiT?n~Bl-fWBP;?`y5lwP365oV z51NX;qdK3u^(6VCBV%rE?n1YJm5|HlS@?0VfUNI${yGeimXsVO65d=U?%M5PH=05Y zfIyVrAu6Y0%6T?e$AXnwn`@+el+mo6O{ zI$!_IGEhWKw$?RM|Dk*IQT!9_y^&qd9d_opqCEJ{*>gI`32fC3*fFf=L15U<)ZHx1 zMs?xM>Bq-e^|--V^V}NXC74?Dfeilwv-IwMJHWZuvEl>NfUtVp`CH!)d#g6vzk<=& z|CU%%sX7`R8nV0d%65-)yKMb(Z^Ay58vl3f@pUH)HS_lZd;=mi>&Z(1O%sz3ug{3| ze;PXZFnMq~QRwwXCf9-1|k$p^d=KPT%0Ux1#)cB zt8><47n=ktK;#uMmz>`D{fHO#&?v3ho=Bkcj;R35xVjt4r3R6 zAZRYS)@0}8Y>6hDW(&&7T7+Y{<{IV?WRRVoJU2LuKZ+e*wFSf)l@>yM`|uEP(OtIF zuKr;6zFhfXOFJPyGN+}XA-F9IJm*i|rXPhEnIDAtZ+w-|`Pj_gca8*aCv?5Mg#xo+ zHQ=7Ur9W%Hd2k}!$gPA{2%r9&6K@ZL%-sWGyP`B2Ub1F`5%`Iu@<`bz$LsfC4o2Vi)N6@x$2I`0;3s>H!PC2FfD4 zt14ZpulQ)4bhUK8neaFPyE3v*Wfz+W2W0-Wk@k;r0@U!_-@IIutxo_LID1PzzT5Ox z>xX=x+E|`FtEs6g%j(ne_o;Rn(EJI>^S|lX%Uiz*VYKGsY87(J9O3# zc9yf3lRHI?O^-&?NBLi28~;u$|AXlG`<9A6u_AqMpk9BNF4X^QrN>aKvZKInhL^3N z*1-Q49^~IOY@A|)1(p8*axQli)4W4Ria0SYb=V#O%EE9oyB?n0id+2Id^i$epbGGt za5MXewT{QFuSNivE!-s)JYHD;@)XJ;khrvF#$@Q~3eVRLG_*n!`gT?zlJ+|#S%nDW z)+6SBh=8NAh;Je5IEeV?Y;VIjJTVG%{7mGVHwLLvE8D*Z#ZMwr|2>GW*|qUO+iA7W z09NNXSX|Ud3$EZ@B=g~lbV~9;fY(3(DeOp{v1Zd&K&NMQBz*6>8T|+Y*5=OM3E5E* zb4nR+k?$I#rnB$Aht36*r-~-1^Q6+@K?;5}h4D)N1n!`pP(BcAeD^GlJU;4-5+itb zBQZ{Uoya96_qTJsfmVp_02xa=AWFZSRz8M3d~8Q69^^4-IZMyM1BdH$ARxTq*k?x* z&>+pFsx`ar=L!|66&d8@e^*j8XJ=omd$i5~1fmlqOc1~@@)0qUP?YMe?KucAxeKyX z)$hkETCBft8v}?-_CLT}^p#(7+oryKTL{*Uj)Ypu&Al-8WO7_GV0BCYIbB1L1*kL- zoXn+c)HYvtdMi(R$v9kSVC#Zw)B!Q)5&!w*pjWlG0ZuJG_ggNnhM|SUIvH%=^Pfjx ztvk!mdCmAn@(%tHb^1}_@3j)>x(I#MNK9vI6$hkLJ1bS2;|45)?OW&ub zmoYb$VPM4Q4dgBPJ)ZK&*RS<;bpeR6F6jFRE3p4C+YSlCpOjT$lnxb10GJyr&@l}l zDQXzb@aynt(5R2H#7^^!MB8UxMeIs$oi2Uysn87GgIfg!zz^xV(UcrC-1J5?s=+ zeRd#f{{_JH?-=%flmzWOeKsliJIhl!Lk&fpot^P*GbafAXH?A9C&xbov?IJNbqnIp z{~AWAo%en^Yz&kFZMx7%OsCtQPYPv$qJSTvX8-=i;n<&Dd(QE@5U7-6Jb9HV_V=aX zB&#CXvS*=Uh=lZn94iNQQgEvUmrQ(4m#BQIJ2*{iRF^Ju;@`3SR<0Pbnao& zdzdk2&6;V|J;Gs&ko}jvCepV+(WVU6Uv!g|a(*D|IJU+O!v`a`+w3a8pO#{p{axq% z`ziDe-uEe1lV)BWMptqUTFz39Fct=9rE+=M^$FKFkJ!1z_Wp_IBXLo02B)?UUxuFq zyccqr83mBon<@GAmK9}U6B!gC)R@c*s|rF&zDF!K^D`6B?>?hLIl)hUGjZC{MKhfj zeDB3P6Sm)~v1LSjvI`Zp*v{kEy@&v{^;H3yaJVHd+9*?zjWn#MnVx&a>2|POfmjVT zC#_!I?%3@M&5574fV3{Wpu*$ab3$gHTaH#3=>c& z3>ma17Q+rhPx%uMrw4d4qITA0wr=t zBi^F=Hn6X@hbA!ALUdq|q?0M0!brMcu?EQ8aV2FqnBYmlXbb3Z5B@lf;;!l&qIIT% zxK6>ugeC3*3s=s*8M8u>uvya~hC6m=NH$I>dAf%C5wYJVyyNETrL;o0WsRRUVkD4m z)+vtsysQ?8Co0*X0_9c-kA~y;=P3;%^Cfw{d|R_Ig{iLzLSmX`Otn`2{{FH7+l3yU zo|3!a5SFthE4e5-C+M?>1x33wn+(jPkCbMu;c+#~ZxcgUBtbwsq-oQ!VV&YxM|%+w zw8ck{W9JpD8EI--;p!F_$%&RFrdckDBfT3~d1&)ywxitpUHAm+2FxkQ!YSRe{|;

~@>X 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 literal 0 HcmV?d00001 diff --git a/docs/doc_assets/m2c_compiler_settings.png b/docs/doc_assets/m2c_compiler_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..680efc1ad9ab555121034c117006f6c1ca342407 GIT binary patch literal 5695 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 None: + self.symbol_name = name + self.start_address = start + self.end_address = start + size + + def get_address_range(self)->tuple[int, int]: + return self.start_address, self.end_address + +class SliceSection: + section_symbol: SymbolInfo = None + symbols: list[SymbolInfo] = None + + def __init__(self, symbol: SymbolInfo) -> None: + self.section_symbol = symbol + self.symbols = [] + +class SliceInfo: + sections: list[SliceSection] = None + + def __init__(self) -> None: + self.sections = [] + + def get_address_range(self)->tuple[int, int]: + start_address = self.sections[0].section_symbol.start_address + end_address = self.sections[-1].section_symbol.end_address + if len(self.sections[-1].symbols) > 0: + end_address = self.sections[-1].symbols[-1].end_address + + return start_address, end_address + +class Address_Sort_Entry: + key : str = None + value: CommentedMap = None + starting_address: int = None + + def __init__(self, entry_key: str, entry_value: CommentedMap, entry_starting_address: int) -> None: + self.key = entry_key + self.value = entry_value + self.starting_address = entry_starting_address +#endregion + +#region Constants +# Dictionary for the offsets we need to apply to the addresses from the map +address_offset_map : dict[str, int] = { + ".text": int("0x803702A8", 16), + ".rodata": int("0x80641260", 16), + ".data": int("0x8064D500", 16), + ".bss": int("0x8125A7C0", 16) +} + +prioritized_addresses: list[str] = [".text", ".rodata", ".data", ".bss"] + +script_dir: str = os.path.dirname(os.path.realpath(__file__)) +root_dir: str = os.path.abspath(os.path.join(script_dir, "..")) +default_map_path: str = os.path.join(root_dir, "dump/foresta.map") +default_binary_slice_file_path: str = os.path.join(root_dir, "config/rel_slices.yml") +default_asset_slice_file_path: str = os.path.join(root_dir, "config/assets.yml") + +specific_tu_pattern_format = r"\s*([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+(?:([0-9a-fA-F]+)\s+(.+?)|\.\.\.data\.\d \(entry of \.data\))\s+({tu_name})\s*" +general_symbol_pattern = re.compile(specific_tu_pattern_format.format(tu_name = ".+\.o")) + +slice_boundary_format = "[{start_address}, {end_address}]" +#endregion + +#region Sorting +def sort_by_starting_address(data: CommentedMap, address_sort_keys: list[str])->CommentedMap: + if len(data) <= 1: + return data + + ordered_entries : list[Address_Sort_Entry] = [] + for key in data.keys(): + entry = data[key] + starting_address = 0 + + for address_key in address_sort_keys: + if address_key not in entry: + continue + + starting_address = entry[address_key] + break + + ordered_entries.append(Address_Sort_Entry(key, entry, starting_address)) + + ordered_entries.sort(key=lambda entry: entry.starting_address) + + ordered_map = CommentedMap() + for ordered_entry in ordered_entries: + ordered_map[ordered_entry.key] = ordered_entry.value + if ordered_entry.key not in data.ca.items: + continue + + ordered_map.ca.items[ordered_entry.key] = data.ca.items[ordered_entry.key] + + return ordered_map +#endregion + +#region Symbol Gathering +def get_symbol_from_map_match(symbol_match: Match, address_offset: int)->SymbolInfo: + name = symbol_match.group(5) + start_address = int(symbol_match.group(1), 16) + address_offset + size = int(symbol_match.group(2), 16) + return SymbolInfo(name, start_address, size) + +def gather_symbols_for_section(address_offset: int, file_reader:TextIOWrapper, slice_info: SliceInfo, starting_match: Match): + section_tu_name = starting_match.group(6) + section_symbol = get_symbol_from_map_match(starting_match, address_offset) + section = SliceSection(section_symbol) + slice_info.sections.append(section) + + # Keep reading until the end of the section has been reached + line: str = None + while True: + line = file_reader.readline() + if not line: + return + if "entry of .data" in line: + continue + break + + next_match: Match = general_symbol_pattern.match(line) + while True: + # Check if the next match belongs to this group or not + curr_match = next_match + if not curr_match: + break + + curr_match_tu_name = curr_match.group(6) + if curr_match_tu_name != section_tu_name: + break + + curr_match_symbol_name = curr_match.group(5) + if curr_match_symbol_name in address_offset_map: + gather_symbols_for_section(address_offset, file_reader, slice_info, starting_match) + break + + # Make symbol for current match + symbol = get_symbol_from_map_match(curr_match, address_offset) + + # Check the next match to get a more accurate ending address + next_line = file_reader.readline() + if not next_line: + # Eof reached. Just add as is + section.symbols.append(symbol) + + # Match against the next line + next_match = general_symbol_pattern.match(next_line) + if not next_match: + # Non matching line + section.symbols.append(symbol) + + # Use start address as the end boundary for the slice + next_match_start_address = int(next_match.group(1), 16) + address_offset + symbol.end_address = next_match_start_address + section.symbols.append(symbol) + +def gather_tu_symbols(tu_name: str, map_path: str)->dict[str, SliceInfo]: + gathered_symbols: dict[str, SliceInfo] = {} + tu_regex = re.compile(specific_tu_pattern_format.format(tu_name = tu_name)) + + with open(map_path, "r", encoding="utf-8", newline="\n") as file_reader: + while True: + line = file_reader.readline() + if not line: + break + + # Check if the line matches the TU name + match = tu_regex.match(line) + if not match: + continue + + # It is a match + slice_name = match.group(5) + if slice_name not in address_offset_map: + continue + + # Add to dictionary + offset = address_offset_map[slice_name] + slice_info = SliceInfo() + gathered_symbols[slice_name] = slice_info + + gather_symbols_for_section(offset, file_reader, slice_info, match) + + return gathered_symbols +#endregion + +#region Asset Slices Config File +def update_asset_slice_config(tu_name: str, binary_slice_file_path: str, asset_slice_file_path: str, symbols_for_tu: dict[str, SliceInfo]): + if ".data" not in symbols_for_tu: + return + + print("Add data entries to: " + asset_slice_file_path + "? (y/n)") + reply = input().lower() + if reply != "y" and reply != "yes": + return + + yaml = YAML(typ="rt") + data: CommentedMap = None + with open(asset_slice_file_path, "r", encoding="utf-8", newline="\n") as file_reader: + data = yaml.load(file_reader) + + binary_commented_map : CommentedMap = None + binary_commented_map_key: str = None + if "rel" in binary_slice_file_path: + binary_commented_map_key = "config/rel.yml" + else: + binary_commented_map_key = "config/dol.yml" + + binary_commented_map = data[binary_commented_map_key] + + for section in symbols_for_tu[".data"].sections: + for asset_symbol in section.symbols: + print("Add entry for: " + asset_symbol.symbol_name + "? (y/n)") + reply = input().lower() + if reply != "y" and reply != "yes": + continue + + print("What is the asset type? (optional)") + asset_type = input() + + asset_commented_map : CommentedMap = None + if binary_commented_map.__contains__(asset_symbol.symbol_name): + asset_commented_map = binary_commented_map[asset_symbol.symbol_name] + else: + asset_commented_map = CommentedMap() + binary_commented_map.insert(len(binary_commented_map), asset_symbol.symbol_name, asset_commented_map) + binary_commented_map.ca.insert(asset_symbol.symbol_name, asset_symbol.symbol_name) + + # Add in the address range + address_commented_seq: CommentedSeq = None + if asset_commented_map.__contains__("addrs"): + # Re-use the same commented section + address_commented_seq = asset_commented_map["addrs"] + address_commented_seq.clear() + else: + address_commented_seq: CommentedSeq = CommentedSeq() + + # Assign to the slice section + asset_commented_map["addrs"] = address_commented_seq + + # Add in the start and end address + start_address, end_address = asset_symbol.get_address_range() + address_commented_seq.fa.set_flow_style() + address_commented_seq.append(scalarint.HexCapsInt(start_address)) + address_commented_seq.append(scalarint.HexCapsInt(end_address)) + + # Add in the asset type + if not asset_type or asset_type is None: + # Type not specified + if asset_commented_map.__contains__("type"): + # Using a previous entry where the type was used, so delete it + asset_commented_map.__delitem__("type") + continue + + asset_commented_map["type"] = asset_type + + # Sort by starting address and replace + data[binary_commented_map_key] = sort_by_starting_address(binary_commented_map, ["addrs"]) + + # Write out to file + with open(asset_slice_file_path, "w", encoding="utf-8", newline="\n") as file_writer: + yaml.dump(data, file_writer) +#endregion + +#region Slice Config File +def update_binary_slice_config(tu_name: str, slice_file_path: str, symbols_for_tu: dict[str, SliceInfo]): + yaml = YAML(typ="rt") + yaml.indent(mapping=4, sequence=4, offset=4) + data: CommentedMap = None + with open(slice_file_path, "r", encoding="utf-8", newline="\n") as file_reader: + data = yaml.load(file_reader) + + tu_c_file_name = tu_name.replace(".o", ".c") + slice_commented_map : CommentedMap = None + if data.__contains__(tu_c_file_name): + print("TU already exists in file. Overwrite values? (y/n)") + reply = input().lower() + if reply != "y" and reply != "yes": + return + + # Re-use the existing commented map + slice_commented_map = data[tu_c_file_name] + else: + # Create a new commented map + slice_commented_map : CommentedMap = CommentedMap() + + # Add to the end of the file + data.insert(len(data), tu_c_file_name, slice_commented_map) + + for slice_name, slice_info in symbols_for_tu.items(): + if len(slice_info.sections) == 0: + # No symbols for this TU + continue + + address_commented_seq: CommentedSeq = None + if slice_commented_map.__contains__(slice_name): + # Re-use the same commented section + address_commented_seq = slice_commented_map[slice_name] + address_commented_seq.clear() + else: + address_commented_seq: CommentedSeq = CommentedSeq() + + # Assign to the slice section + slice_commented_map[slice_name] = address_commented_seq + + # Add in the start and end address + start_address, end_address = slice_info.get_address_range() + address_commented_seq.fa.set_flow_style() + address_commented_seq.append(scalarint.HexCapsInt(start_address)) + address_commented_seq.append(scalarint.HexCapsInt(end_address)) + + # Sort by address + data = sort_by_starting_address(data, prioritized_addresses) + + # Write out to file + with open(slice_file_path, "w", encoding="utf-8", newline="\n") as file_writer: + yaml.dump(data, file_writer) +#endregion + +#region Main +def main(): + parser = argparse.ArgumentParser(prog="Translation Unit Config Updater", description="Adds the corresponding addresses to slice config files") + parser.add_argument("tu_name", nargs="?", help="Name of the translation unit to get addresses for") + parser.add_argument("-map", "--symbol-map", dest="symbol_map", help="Path to the symbol map file used for reference", action="store") + parser.add_argument("-binary", "--binary-slices-file", dest="binary_slices_file", help="Path to the binary slices file to write to", action="store") + parser.add_argument("-asset", "--asset-slices-file", dest="asset_slices_file", help="Path to the asset slices file to write to", action="store") + args = parser.parse_args() + + # Make sure the translation unit name ends with .o + tu_name = args.tu_name + if tu_name[-2:] != ".o": + tu_name = tu_name + ".o" + + symbol_map_path = args.symbol_map + if not symbol_map_path: + symbol_map_path = default_map_path + + binary_slices_file = args.binary_slices_file + if not binary_slices_file: + binary_slices_file = default_binary_slice_file_path + + asset_slices_file = args.asset_slices_file + if not asset_slices_file: + asset_slices_file = default_asset_slice_file_path + + # Get the symbols for the TU + symbols_for_tu = gather_tu_symbols(tu_name, symbol_map_path) + + # Make a call to update the binary file + update_binary_slice_config(tu_name, binary_slices_file, symbols_for_tu) + update_asset_slice_config(tu_name, binary_slices_file, asset_slices_file, symbols_for_tu) + +if __name__ == "__main__": + main() +#endregion \ No newline at end of file