summaryrefslogtreecommitdiff
path: root/.github/calcrom/calcrom.cpp
diff options
context:
space:
mode:
Diffstat (limited to '.github/calcrom/calcrom.cpp')
-rw-r--r--.github/calcrom/calcrom.cpp181
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;
}