diff options
Diffstat (limited to '.github/calcrom/calcrom.cpp')
-rw-r--r-- | .github/calcrom/calcrom.cpp | 181 |
1 files changed, 66 insertions, 115 deletions
diff --git a/.github/calcrom/calcrom.cpp b/.github/calcrom/calcrom.cpp index 1cb6f9fe..8b0c1b42 100644 --- a/.github/calcrom/calcrom.cpp +++ b/.github/calcrom/calcrom.cpp @@ -1,14 +1,18 @@ /* * CALCROM.CPP - * © PikalaxALT 2020 + * © PikalaxALT 2020-2021 * - * Simple C++ executable to measure the completion rate of Pokémon Diamond + * Permission is granted to copy and/or modify this code under GPL 3.0. + * + * Simple C++ executable to measure the completion rate of Nintendo DS * reverse engineering (decompilation). + * Similar in scope to calcrom.pl from pret-agb projects, but designed + * to cope with restrictions imposed by the DS toolchain. * * Requirements: * - Must have C++11 compliant compiler. * - MacOS X: Must provide elf.h on the include (-I) path. - * - Must be placed in ".travis/calcrom/". + * - Must be placed in ".github/calcrom/". * * Changelog: * - 0.1.0 (26 May 2020): @@ -19,135 +23,82 @@ * Extra security on ELF header * - 0.1.3 (30 Jun 2020): * Account for diamond/pearl split + * - 0.2.0 (30 Aug 2021): + * Support hgss + * - 0.2.1 (31 Aug 2021): + * Make calcrom more generic and configurable via command line + * - 0.2.2 (18 Sep 2021): + * Handle errors when paths are missing + * - 0.2.3 (10 Nov 2021): + * Refactor classes into separate objects to improve future maintainability + * Report hardcoded pointers */ #include <iostream> #include <fstream> -#include <sstream> -#include <elf.h> -#include <glob.h> -#include <string.h> #include <vector> #include <string> +#include <algorithm> +#include <filesystem> +#include "BuildAnalyzer.h" using namespace std; +using namespace std::filesystem; -struct Glob : public vector<char const *> { - glob_t glob_result; +class missing_option : public invalid_argument { public: - Glob(string const & pattern) { - int result = glob(pattern.c_str(), GLOB_TILDE | GLOB_BRACE, NULL, &glob_result); - if (result) { - stringstream ss; - ss << "Glob(" << pattern << ") failed with error " << result << endl; - throw runtime_error(ss.str()); - } - assign(glob_result.gl_pathv, glob_result.gl_pathv + glob_result.gl_pathc); - }; - void operator~() { - globfree(&glob_result); - } + missing_option(string& error) : invalid_argument{error.c_str()} {} }; -void analyze(string basedir, string subdir, string version) { - fstream elf; - Elf32_Ehdr ehdr; - vector<Elf32_Shdr> shdr; - stringstream pattern; - - // Accumulate sizes - // src asm - // data _____|_____ - // text | - unsigned sizes[2][2] = {{0, 0}, {0, 0}}; - char * shstrtab = NULL; - size_t shstrsz = 0; - stringstream builddir; - builddir << subdir << "/build/" << version; - pattern << basedir << "/" << subdir << "/{src,asm,lib/src,lib/{libc,libnns,NitroSDK}/src,modules/*/{src,asm}}/*.{c,s,cpp}"; - for (char const * & fname : Glob(pattern.str())) - { - string fname_s(fname); - string ext = fname_s.substr(fname_s.rfind('.'), 4); - bool is_asm = ext == ".s"; - fname_s = fname_s.replace(fname_s.find(subdir), 4, builddir.str()); - fname_s = fname_s.replace(fname_s.rfind('.'), 4, ".o"); - elf.open(fname_s, ios_base::in | ios_base::binary); - if (!elf.good()) { - cerr << "Error: file not found: " << fname_s << endl; - return; - } - elf.read((char *)&ehdr, sizeof(ehdr)); - if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 - || ehdr.e_ehsize != sizeof(Elf32_Ehdr) - || ehdr.e_shentsize != sizeof(Elf32_Shdr)) - { - elf.close(); - stringstream ss; - ss << "Error validating " << fname_s << " as an ELF file" << endl; - throw runtime_error(ss.str()); - } - // Read ELF sections - elf.seekg(ehdr.e_shoff); - shdr.resize(ehdr.e_shnum); - elf.read((char *)shdr.data(), ehdr.e_shnum * ehdr.e_shentsize); - - // Read .shstrtab - if (shstrsz < shdr[ehdr.e_shstrndx].sh_size) { - shstrtab = (char *)realloc(shstrtab, shdr[ehdr.e_shstrndx].sh_size); - shstrsz = shdr[ehdr.e_shstrndx].sh_size; - } - elf.seekg(shdr[ehdr.e_shstrndx].sh_offset); - elf.read(shstrtab, shdr[ehdr.e_shstrndx].sh_size); - elf.close(); - - // Analyze sections - for (Elf32_Shdr & hdr : shdr) { - string shname = shstrtab + hdr.sh_name; - bool is_text = (shname == ".text" || shname == ".init" || shname == ".itcm"); - bool is_data = (shname == ".data" || shname == ".rodata" || shname == ".sdata" || shname == ".dtcm"); - size_t size = hdr.sh_size + (hdr.sh_size & 3 ? 4 - (hdr.sh_size & 3) : 0); - if (is_text || is_data) - { - sizes[is_text][is_asm] += size; +struct Options { + path arm9subdir = ""; + path arm7subdir = "sub"; + path projectdir = "."; + vector<string> romnames; + Options(int argc, char ** argv) { + for (int i = 1; i < argc; i++) { + string arg = argv[i]; + if (arg == "-9") { + if (++i == argc) throw missing_option(arg); + arm9subdir = argv[i]; + } else if (arg == "-7") { + if (++i == argc) throw missing_option(arg); + arm7subdir = argv[i]; + } else if (arg == "-d") { + if (++i == argc) throw missing_option(arg); + projectdir = argv[i]; + } else if (arg[0] != '-') { + romnames.push_back(arg); + } else { + throw invalid_argument(arg); } } } - free(shstrtab); - - cout << "Analysis of " << (version.empty() ? subdir : version) << " binary:" << endl; - // Report code - unsigned total_text = sizes[1][0] + sizes[1][1]; - double total_text_d = total_text; - double src_text_d = sizes[1][0]; - double asm_text_d = sizes[1][1]; - cout << " " << total_text << " total bytes of code" << endl; - cout << " " << sizes[1][0] << " bytes of code in src (" << (src_text_d / total_text_d * 100.0) << "%)" << endl; - cout << " " << sizes[1][1] << " bytes of code in asm (" << (asm_text_d / total_text_d * 100.0) << "%)" << endl; - cout << endl; - // Report data - unsigned total_data = sizes[0][0] + sizes[0][1]; - double total_data_d = total_data; - double src_data_d = sizes[0][0]; - double asm_data_d = sizes[0][1]; - cout << " " << total_data << " total bytes of data" << endl; - cout << " " << sizes[0][0] << " bytes of data in src (" << (src_data_d / total_data_d * 100.0) << "%)" << endl; - cout << " " << sizes[0][1] << " bytes of data in asm (" << (asm_data_d / total_data_d * 100.0) << "%)" << endl; - // Let vectors fall to gc -} + int main() { + for (string &romname: romnames) { + cout << BuildAnalyzer(projectdir, arm9subdir, romname)() << endl; + } + cout << BuildAnalyzer(projectdir, arm7subdir)(); + return 0; + } +}; int main(int argc, char ** argv) { - if (argc < 2) { - cout << "usage: calcrom PROJECT_DIR" << endl; - throw invalid_argument("missing required argument: PROJECT_DIR\n"); + try { + Options options(argc, argv); + return options.main(); + } catch (missing_option& e) { + cerr << "Missing value for option " << e.what() << endl; + return 1; + } catch (invalid_argument& e) { + cerr << "Unrecognized option flag: " << e.what() << endl; + return 1; + } catch (runtime_error& e) { + cerr << e.what() << endl; + return 1; + } catch (exception& e) { + cerr << "Unhandled exception: " << e.what() << endl; + return 1; } - - analyze(argv[1], "arm9", "diamond.us"); - cout << endl; - analyze(argv[1], "arm9", "pearl.us"); - cout << endl; - analyze(argv[1], "arm7", ""); - - return 0; } |