diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c7b900f --- /dev/null +++ b/.clang-format @@ -0,0 +1,23 @@ +IndentWidth: 4 +Language: Cpp +UseTab: Never +ColumnLimit: 120 +PointerAlignment: Left +BreakBeforeBraces: Attach +SpaceAfterCStyleCast: false +Cpp11BracedListStyle: false +IndentCaseLabels: true +BinPackArguments: true +BinPackParameters: true +AlignAfterOpenBracket: Align +AlignOperands: true +BreakBeforeTernaryOperators: true +BreakBeforeBinaryOperators: None +AllowShortBlocksOnASingleLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlignEscapedNewlines: Left +AlignTrailingComments: true +SortIncludes: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..caf62ab --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,9 @@ +Checks: '-*,readability-braces-around-statements,readability-inconsistent-declaration-parameter-name' +WarningsAsErrors: '' +HeaderFilterRegex: '(src|include)\/.*\.h$' +FormatStyle: 'file' +CheckOptions: + # Require argument names to match exactly (instead of allowing a name to be a prefix/suffix of another) + # Note: 'true' is expected by clang-tidy 12+ but '1' is used for compatibility with older versions + - key: readability-inconsistent-declaration-parameter-name.Strict + value: 1 diff --git a/format.py b/format.py new file mode 100755 index 0000000..8f1d5f6 --- /dev/null +++ b/format.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 + +import argparse +import glob +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +from functools import partial +from typing import List + +# clang-format, clang-tidy and clang-apply-replacements default version +# This specific version is used when available, for more consistency between contributors +CLANG_VER = 14 + +# Clang-Format options (see .clang-format for rules applied) +FORMAT_OPTS = "-i -style=file" + +# Clang-Tidy options (see .clang-tidy for checks enabled) +TIDY_OPTS = "-p ." +TIDY_FIX_OPTS = "--fix --fix-errors" + +# Clang-Apply-Replacements options (used for multiprocessing) +APPLY_OPTS = "--format --style=file" + +# Compiler options used with Clang-Tidy +# Normal warnings are disabled with -Wno-everything to focus only on tidying +INCLUDES = "-Iinclude -Isrc -Ibuild -I." +DEFINES = "-D_LANGUAGE_C -DNON_MATCHING -D_MIPS_SZLONG=32" +COMPILER_OPTS = f"-fno-builtin -std=gnu90 -m32 -Wno-everything {INCLUDES} {DEFINES}" + + +def get_clang_executable(allowed_executables: List[str]): + for executable in allowed_executables: + try: + subprocess.check_call([executable, "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + return executable + except FileNotFoundError or subprocess.CalledProcessError: + pass + return None + + +def get_tidy_version(tidy_executable: str): + tidy_version_run = subprocess.run([tidy_executable, "--version"], stdout=subprocess.PIPE, universal_newlines=True) + match = re.search(r"LLVM version ([0-9]+)", tidy_version_run.stdout) + return int(match.group(1)) + + +CLANG_FORMAT = get_clang_executable([f"clang-format-{CLANG_VER}", "clang-format"]) +if CLANG_FORMAT is None: + sys.exit(f"Error: neither clang-format nor clang-format-{CLANG_VER} found") + +CLANG_TIDY = get_clang_executable([f"clang-tidy-{CLANG_VER}", "clang-tidy"]) +if CLANG_TIDY is None: + sys.exit(f"Error: neither clang-tidy nor clang-tidy-{CLANG_VER} found") + +CLANG_APPLY_REPLACEMENTS = get_clang_executable([f"clang-apply-replacements-{CLANG_VER}", "clang-apply-replacements"]) + +# Try to detect the clang-tidy version and add --fix-notes for version 13+ +# This is used to ensure all fixes are applied properly in recent versions +if get_tidy_version(CLANG_TIDY) >= 13: + TIDY_FIX_OPTS += " --fix-notes" + + +def list_chunks(list: List, chunk_length: int): + for i in range(0, len(list), chunk_length): + yield list[i : i + chunk_length] + + +def run_clang_format(files: List[str]): + exec_str = f"{CLANG_FORMAT} {FORMAT_OPTS} {' '.join(files)}" + subprocess.run(exec_str, shell=True) + + +def run_clang_tidy(files: List[str]): + exec_str = f"{CLANG_TIDY} {TIDY_OPTS} {TIDY_FIX_OPTS} {' '.join(files)} -- {COMPILER_OPTS}" + subprocess.run(exec_str, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def run_clang_tidy_with_export(tmp_dir: str, files: List[str]): + (handle, tmp_file) = tempfile.mkstemp(suffix=".yaml", dir=tmp_dir) + os.close(handle) + + exec_str = f"{CLANG_TIDY} {TIDY_OPTS} --export-fixes={tmp_file} {' '.join(files)} -- {COMPILER_OPTS}" + subprocess.run(exec_str, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def run_clang_apply_replacements(tmp_dir: str): + exec_str = f"{CLANG_APPLY_REPLACEMENTS} {APPLY_OPTS} {tmp_dir}" + subprocess.run(exec_str, shell=True) + + +def add_final_new_line(file: str): + # https://backreference.org/2010/05/23/sanitizing-files-with-no-trailing-newline/index.html + # "gets the last character of the file pipes it into read, which will exit with a nonzero exit + # code if it encounters EOF before newline (so, if the last character of the file isn't a newline). + # If read exits nonzero, then append a newline onto the file using echo (if read exits 0, + # that satisfies the ||, so the echo command isn't run)." (https://stackoverflow.com/a/34865616) + exec_str = f"tail -c1 {file} | read -r _ || echo >> {file}" + subprocess.run(exec_str, shell=True) + + +def format_files(src_files: List[str], extra_files: List[str], nb_jobs: int): + if nb_jobs != 1: + print(f"Formatting files with {nb_jobs} jobs") + else: + print(f"Formatting files with a single job (consider using -j to make this faster)") + + # Format files in chunks to improve performance while still utilizing jobs + file_chunks = list(list_chunks(src_files, (len(src_files) // nb_jobs) + 1)) + + print("Running clang-format...") + # clang-format only applies changes in the given files, so it's safe to run in parallel + with multiprocessing.get_context("fork").Pool(nb_jobs) as pool: + pool.map(run_clang_format, file_chunks) + + print("Running clang-tidy...") + if nb_jobs > 1: + # clang-tidy may apply changes in #included files, so when running it in parallel we use --export-fixes + # then we call clang-apply-replacements to apply all suggested fixes at the end + tmp_dir = tempfile.mkdtemp() + + try: + with multiprocessing.get_context("fork").Pool(nb_jobs) as pool: + pool.map(partial(run_clang_tidy_with_export, tmp_dir), file_chunks) + + run_clang_apply_replacements(tmp_dir) + finally: + shutil.rmtree(tmp_dir) + else: + run_clang_tidy(src_files) + + print("Adding missing final new lines...") + # Adding final new lines is safe to do in parallel and can be applied to all types of files + with multiprocessing.get_context("fork").Pool(nb_jobs) as pool: + pool.map(add_final_new_line, src_files + extra_files) + + print("Done formatting files.") + + +def main(): + parser = argparse.ArgumentParser(description="Format files in the codebase to enforce most style rules") + parser.add_argument( + "--show-paths", + dest="show_paths", + action="store_true", + help="Print the paths to the clang-* binaries used", + ) + parser.add_argument("files", metavar="file", nargs="*") + parser.add_argument( + "-j", + dest="jobs", + type=int, + nargs="?", + default=1, + help="number of jobs to run (default: 1 without -j, number of cpus with -j)", + ) + args = parser.parse_args() + + if args.show_paths: + import shutil + + print("CLANG_FORMAT ->", shutil.which(CLANG_FORMAT)) + print("CLANG_TIDY ->", shutil.which(CLANG_TIDY)) + print("CLANG_APPLY_REPLACEMENTS ->", shutil.which(CLANG_APPLY_REPLACEMENTS)) + + nb_jobs = args.jobs or multiprocessing.cpu_count() + if nb_jobs > 1: + if CLANG_APPLY_REPLACEMENTS is None: + sys.exit( + f"Error: neither clang-apply-replacements nor clang-apply-replacements-{CLANG_VER} found (required to use -j)" + ) + + if args.files: + files = args.files + extra_files = [] + else: + files = glob.glob("src/**/*.c", recursive=True) + extra_files = glob.glob("assets/**/*.xml", recursive=True) + + format_files(files, extra_files, nb_jobs) + + +if __name__ == "__main__": + main() diff --git a/src/3A80.c b/src/3A80.c index 9dcac6d..5c00f53 100644 --- a/src/3A80.c +++ b/src/3A80.c @@ -9,9 +9,9 @@ * located whenever plugged in, but its only functional with the debug profiler. * It will, however, allocate a much bigger pool if set to non-0. */ -s32 gExpansionRAMStart = FALSE; +u32 gExpansionRAMStart = FALSE; -extern struct MainPool **gMainPool; // gMainPool +extern struct MainPool** gMainPool; // gMainPool void func_80003860(void); s32 func_80007A58(void); @@ -20,39 +20,45 @@ s32 func_80007A58(void); * Convert any valid address to its virtual (KSEG0) counterpart. */ uintptr_t convert_addr_to_virt_addr(uintptr_t addr) { - uintptr_t retaddr = 0x00000000; // any invalid cases are treated as NULL return. + uintptr_t retaddr = NULL; // any invalid cases are treated as NULL return. // convert physical (in installed memory range) to virtual. - if (addr < (size_t) osMemSize) { + if (addr < osMemSize) { retaddr = addr | 0x80000000; - // convert segmented to virtual. + // convert segmented to virtual. } else if (addr < 0x10000000U) { retaddr = Memmap_GetSegmentVaddr(addr); - // convert a fragment pre-relocated address to a post-relocated virtual address. - } else if ((addr >= 0x81000000U) && (addr < 0x90000000U)) { // is the address in fragment space? convert it to its post-relocated address. + // convert a fragment pre-relocated address to a post-relocated virtual address. + } else if ((addr >= 0x81000000U) && + (addr < 0x90000000U)) { // is the address in fragment space? convert it to its post-relocated address. retaddr = Memmap_GetFragmentVaddr(addr); - // pass-through addresses that are already virtual (in installed memory range) - } else if ((addr >= 0x80000000U) && (addr < (uintptr_t) (osMemSize + 0x80000000U))) { + // pass-through addresses that are already virtual (in installed memory range) + } else if ((addr >= 0x80000000U) && (addr < (uintptr_t)(osMemSize + 0x80000000U))) { retaddr = addr; } return retaddr; } -/* - * Copy memory from one address to the other. (why is this function not in HAL_libc?) +/** + * Copy memory from one address to the other. */ void HAL_Memcpy(u32* dest, u32* src, int size) { - while (size --> 0) { + while (size-- > 0) { *(dest++) = *(src++); } } +// init_main_pools ? void func_80002F58(void) { - // wat? mem sizes are only ever 0x400000 or 0x800000. This check makes no sense. - // Effectively, it checks if the expansion RAM is in. But why not just use all - // of it, or at least do the correct check of osMemSize == 0x800000? - if ((gExpansionRAMStart != 0) && ((u32) osMemSize > 0x600000U)) { + /** + * wat? mem sizes are only ever 0x400000 or 0x800000. This check makes no sense + * in normal contexts. However, since osGetMemSize checks each MB at a time, if + * the 7MB check fails for whatever reason it can theoretically set osMemSize to + * 0x600000. This is not known to occur in practical contexts, but there may be + * some other unknown reason for this. + */ + if ((gExpansionRAMStart > 0) && (osMemSize > 0x600000U)) { main_pool_init(&gPool, POOL_END_6MB); } else { main_pool_init(&gPool, POOL_END_4MB); @@ -62,18 +68,20 @@ void func_80002F58(void) { gMainPool = mem_pool_try_init(0x10000, 0); } -void *func_80002FDC(s32 size) { +// main_malloc ? +void* func_80002FDC(s32 size) { return mem_pool_alloc(gMainPool, size); } -void func_80003004(void *arg0) { +// main_free ? +void func_80003004(void* arg0) { mem_pool_free(gMainPool, arg0); } void HAL_DrawRect(Gfx** dlist, s32 ulx, s32 lrx, u16 color) { - s32 uly = 0xF; - s32 lry = 0x11; - Gfx *gfx = *dlist; + s32 uly = 15; + s32 lry = 17; + Gfx* gfx = *dlist; if (func_80007A58() != 0) { ulx <<= 1; @@ -88,54 +96,80 @@ void HAL_DrawRect(Gfx** dlist, s32 ulx, s32 lrx, u16 color) { *dlist = gfx; } -void func_8000310C(Gfx** dlist) { - struct MainPool *pool = main_pool_get_pool(); - s32 temp_s1 = main_pool_get_available() - gExpansionRAMStart; +/** + * Render the memory profiler bar and print the MEM display. + */ +void profiler_draw_mem_display(Gfx** dlist) { + struct MainPool* pool = main_pool_get_pool(); // get pool pointer + /** + * Get the available memory offset by gExpansionRAMStart variable. This variable is weird; it + * seems to be used in reference to osMemSize checks >= 0x600000 which would only be true + * in Expansion RAM contexts. However, this game does not use the expansion RAM in any + * other context. This variable is negative when gExpansionRAMStart is too big, and not when + * it isnt. It is therefore reasonable to assume that this variable would represent how much + * memory is leftover and if negative would mean this cant fit into 4MB, which would therefore + * mean that the developers need to trim more memory to get it to fit into 4MB. + */ + s32 available = main_pool_get_available() - gExpansionRAMStart; - if (temp_s1 >= 0) - { - s32 base = 30; - s32 sp48 = ((u32) ( K0_TO_PHYS(pool->start)) >> 15) + base; - s32 sp44 = ((u32) ( K0_TO_PHYS(pool->listHeadL)) >> 15) + base; - s32 sp40 = ((u32) ( K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + base; - s32 sp3C = ((u32) ( K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + base; + if (available >= 0) { + // calculate approximation coordinates based on the current pool addresses in order + // to display them on the memory profiler. + s32 baseX = 30; + s32 startX = ((u32)(K0_TO_PHYS(pool->start)) >> 15) + baseX; + s32 headLX = ((u32)(K0_TO_PHYS(pool->listHeadL)) >> 15) + baseX; + s32 headRX = ((u32)(K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + baseX; + s32 endX = ((u32)(K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + baseX; - HAL_DrawRect(dlist, base, sp48, 0xFBCB); - HAL_DrawRect(dlist, sp48, sp44, 0xFFCB); - HAL_DrawRect(dlist, sp44, sp40, 0x2ABF); - HAL_DrawRect(dlist, sp40, sp3C, 0xFFCB); - HAL_Printf(base, 0x14, "MEM: +%XH (+%dK)", temp_s1, temp_s1 / 1024); - } - else - { - s32 base = 30; - s32 sp34 = ((u32) ( K0_TO_PHYS(pool->start)) >> 15) + base; - s32 sp30 = ((u32) ( K0_TO_PHYS(pool->listHeadL)) >> 15) + base; - s32 sp2C = ((u32) ( K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + base; - s32 sp28 = ((u32) ( K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + base; - HAL_DrawRect(dlist, base, sp34, 0xFBCB); - HAL_DrawRect(dlist, sp34, sp2C, 0xFFCB); - HAL_DrawRect(dlist, sp2C, sp30, 0xF94B); - HAL_DrawRect(dlist, sp30, sp28, 0xFFCB); - HAL_Printf(base, 0x14, "MEM: -%XH (-%dK)", -temp_s1, -temp_s1 / 1024); + // draw the rects. + HAL_DrawRect(dlist, baseX, startX, GPACK_RGBA5551(248, 120, 40, 1)); // orange + HAL_DrawRect(dlist, startX, headLX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow + HAL_DrawRect(dlist, headLX, headRX, GPACK_RGBA5551(40, 80, 248, 1)); // blue + HAL_DrawRect(dlist, headRX, endX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow + + // how many bytes and kilobytes are available? + HAL_Printf(baseX, 20, "MEM: +%XH (+%dK)", available, available / 1024); + } else { + // same as above. + s32 baseX = 30; + s32 startX = ((u32)(K0_TO_PHYS(pool->start)) >> 15) + baseX; + s32 headLX = ((u32)(K0_TO_PHYS(pool->listHeadL)) >> 15) + baseX; + s32 headRX = ((u32)(K0_TO_PHYS(pool->listHeadR) - gExpansionRAMStart) >> 15) + baseX; + s32 endX = ((u32)(K0_TO_PHYS(pool->end) - gExpansionRAMStart) >> 15) + baseX; + + // draw the rects. if we are negative in the memory, we are using red for the backwards + // allocations to indicate too much memory is being used. + HAL_DrawRect(dlist, baseX, startX, GPACK_RGBA5551(248, 120, 40, 1)); // orange + HAL_DrawRect(dlist, startX, headRX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow + HAL_DrawRect(dlist, headRX, headLX, GPACK_RGBA5551(248, 40, 40, 1)); // red + HAL_DrawRect(dlist, headLX, endX, GPACK_RGBA5551(248, 248, 40, 1)); // yellow + + // how many bytes and kilobytes are available? + HAL_Printf(baseX, 20, "MEM: -%XH (-%dK)", -available, -available / 1024); } } -/* +/** * Clear memory address area. */ void HAL_Memclear(u64* dest, u32 size) { - while( size --> 0 ) { + while (size-- > 0) { *(dest++) = -1; } } -s32 func_80003348(u64* arg0) { +/** + * Unused function for checking some 64-bit range (possibly some display list?) for values + * cooresponding to 0x8040000080400000. This could have been for some memory test however + * the code for such a test is not present in this ROM, so we can only guess this function's + * intended usage. + */ +// check_stub_mem_area ? +s32 func_80003348(u64* ptr) { s32 ret = 0; - while (*(arg0++) == 0x8040000080400000) - { + while (*(ptr++) == 0x8040000080400000) { ret++; } - return ret * sizeof(*arg0); + return ret * sizeof(*ptr); }