diff options
Diffstat (limited to '.github')
-rw-r--r-- | .github/calcrom/BuildAnalyzer.cpp | 143 | ||||
-rw-r--r-- | .github/calcrom/BuildAnalyzer.h | 68 | ||||
-rw-r--r-- | .github/calcrom/ElfFile.cpp | 130 | ||||
-rw-r--r-- | .github/calcrom/ElfFile.h | 60 | ||||
-rw-r--r-- | .github/calcrom/Glob.cpp | 16 | ||||
-rw-r--r-- | .github/calcrom/Glob.h | 20 | ||||
-rw-r--r-- | .github/calcrom/Makefile | 31 | ||||
-rw-r--r-- | .github/calcrom/calcrom.cpp | 181 | ||||
-rw-r--r-- | .github/calcrom/webhook.sh | 6 |
9 files changed, 534 insertions, 121 deletions
diff --git a/.github/calcrom/BuildAnalyzer.cpp b/.github/calcrom/BuildAnalyzer.cpp new file mode 100644 index 00000000..4fc36d81 --- /dev/null +++ b/.github/calcrom/BuildAnalyzer.cpp @@ -0,0 +1,143 @@ +#include <algorithm> +#include <cstring> +#include <iostream> +#include "BuildAnalyzer.h" +#include "Glob.h" +#include "ElfFile.h" + +string default_version(""); + +void BuildAnalyzer::AnalyzeObject(path fname_s) { +#ifndef NDEBUG + cerr << fname_s << endl; +#endif //NDEBUG + string ext = fname_s.extension(); + SourceType sourceType = ext == ".s" ? SOURCE_ASM : SOURCE_C; + fname_s = builddir / relative(fname_s, srcbase); + fname_s = fname_s.replace_extension(".o"); + if (!exists(fname_s)) { + throw runtime_error("No such file: " + fname_s.string()); + } + + Elf32File elf(fname_s); + + // Analyze sections + for (Elf32_Shdr & hdr : elf.GetSectionHeaders()) { + string shname = elf.GetSectionName(hdr); + SectionType sectionType = GetSectionType(shname); + if (sectionType != SECTION_OTHER) { + sizes[sectionType][sourceType] += (hdr.sh_size + 3) & ~3; + auto data = elf.ReadSectionData<unsigned>(hdr); +//#ifndef NDEBUG +// unordered_set<unsigned> unique_addrs; +//#endif + for (const auto & word : data) { + if (word == 0) { + continue; // might be a relocation + } + if (find_if(program.GetProgramHeaders().cbegin(), program.GetProgramHeaders().cend(), [&word](const auto & phdr) { + return phdr.p_vaddr <= word && word < phdr.p_vaddr + phdr.p_memsz; + }) != program.GetProgramHeaders().cend()) { +//#ifndef NDEBUG +// unique_addrs.insert(word); +//#endif + n_hardcoded++; + } + } +//#ifndef NDEBUG +// if (!version.empty()) { +// for (const auto & word : unique_addrs) { +// cerr << "hardcoded " << version << " pointer to " << hex << word << endl; +// } +// } +//#endif + } else if (hdr.sh_type == SHT_RELA) { + n_relocations += elf.GetSectionElementCount<Elf32_Rela>(hdr); + } + } +} + +void BuildAnalyzer::reset() { + memset(sizes, 0, sizeof(sizes)); + if (!version.empty()) { + sizes[SECTION_TEXT][SOURCE_ASM] = 0x800; // libsyscall.a + } + n_hardcoded = 0; + n_relocations = 0; +} + +BuildAnalyzer::BuildAnalyzer(path &_basedir, path &_subdir, string &_version) : + basedir(_basedir), + subdir(_subdir), + version(_version) +{ + reset(); + srcbase = basedir / subdir; + builddir = srcbase / "build" / version; + if (!exists(srcbase)) { + throw runtime_error("No such directory: " + srcbase.string()); + } + + string elfpat = builddir + "/*.elf"; + Glob globber(elfpat, GLOB_TILDE | GLOB_BRACE | GLOB_NOSORT); + if (globber.size() == 0) { + throw runtime_error("unable to find an ELF file with section data"); + } + program.open(globber[0], Elf32File::sections | Elf32File::programs); +} + +BuildAnalyzer &BuildAnalyzer::operator()() { + if (analyzed) { + reset(); + } + string pattern = srcbase.string() + "/{src,asm,lib/{src,asm},lib/*/{src,asm},modules/*/{asm,src}}/*.{c,s,cpp}"; + for (char const * & fname : Glob(pattern, GLOB_TILDE | GLOB_BRACE | GLOB_NOSORT)) { + if (string(fname).find("lib/syscall/") == string::npos) { + AnalyzeObject(fname); + } + } + analyzed = true; + return *this; +} + +ostream &operator<<(ostream &strm, BuildAnalyzer &_this) { + if (!_this.analyzed) { + strm << "Haven't analyzed this ROM, please call the BuildAnalyzer instance" << endl; + return strm; + } + + strm << "Analysis of " << (_this.version.empty() ? _this.subdir.string() : _this.version) << " binary:" << endl; + // Report code + unsigned total_text = _this.sizes[SECTION_TEXT][SOURCE_C] + _this.sizes[SECTION_TEXT][SOURCE_ASM]; + if (total_text != 0) { + double total_text_d = total_text; + double src_text_d = _this.sizes[SECTION_TEXT][SOURCE_C]; + double asm_text_d = _this.sizes[SECTION_TEXT][SOURCE_ASM]; + strm << " " << total_text << " total bytes of code" << endl; + strm << " " << _this.sizes[SECTION_TEXT][SOURCE_C] << " bytes of code in src (" << (src_text_d / total_text_d * 100.0) << "%)" << endl; + strm << " " << _this.sizes[SECTION_TEXT][SOURCE_ASM] << " bytes of code in asm (" << (asm_text_d / total_text_d * 100.0) << "%)" << endl; + } + strm << endl; + // Report data + unsigned total_data = _this.sizes[SECTION_DATA][SOURCE_C] + _this.sizes[SECTION_DATA][SOURCE_ASM]; + if (total_data != 0) { + double total_data_d = total_data; + double src_data_d = _this.sizes[SECTION_DATA][SOURCE_C]; + double asm_data_d = _this.sizes[SECTION_DATA][SOURCE_ASM]; + strm << " " << total_data << " total bytes of data" << endl; + strm << " " << _this.sizes[SECTION_DATA][SOURCE_C] << " bytes of data in src (" << (src_data_d / total_data_d * 100.0) << "%)" << endl; + strm << " " << _this.sizes[SECTION_DATA][SOURCE_ASM] << " bytes of data in asm (" << (asm_data_d / total_data_d * 100.0) << "%)" << endl; + } + strm << endl; + // Report hardcoded pointers + unsigned total_pointers = _this.n_hardcoded + _this.n_relocations; + if (total_pointers != 0) { + double total_pointers_d = total_pointers; + double hardcoded_pointers_d = _this.n_hardcoded; + double relocated_pointers_d = _this.n_relocations; + strm << " " << total_pointers << " total pointers" << endl; + strm << " " << _this.n_relocations << " properly-linked pointers (" << (relocated_pointers_d / total_pointers_d * 100.0) << "%)" << endl; + strm << " " << _this.n_hardcoded << " hard-coded pointers (" << (hardcoded_pointers_d / total_pointers_d * 100.0) << "%)" << endl; + } + return strm; +} diff --git a/.github/calcrom/BuildAnalyzer.h b/.github/calcrom/BuildAnalyzer.h new file mode 100644 index 00000000..0b13c2cf --- /dev/null +++ b/.github/calcrom/BuildAnalyzer.h @@ -0,0 +1,68 @@ +#ifndef CALCROM_BUILDANALYZER_H +#define CALCROM_BUILDANALYZER_H + +#include <filesystem> +#include <utility> +#include <vector> +#include <unordered_set> +#include "ElfFile.h" + +using namespace std; +using namespace std::filesystem; + +extern string default_version; + +enum SectionType { + SECTION_DATA, + SECTION_TEXT, + SECTION_MAX, + SECTION_OTHER = -1 +}; + +enum SourceType { + SOURCE_C, + SOURCE_ASM, + SOURCE_MAX +}; + +static enum SourceType GetSourceType(const path &fname) { + string ext = fname.extension(); + return ext == ".s" ? SOURCE_ASM : SOURCE_C; +} + +static enum SectionType GetSectionType(const string &shname) { + if (shname == ".text" || shname == ".init" || shname == ".itcm") { + return SECTION_TEXT; + } else if (shname == ".data" || shname == ".rodata" || shname == ".sdata" || shname == ".dtcm") { + return SECTION_DATA; + } else { + return SECTION_OTHER; + } +} + +class BuildAnalyzer { + bool analyzed = false; + path basedir; + path subdir; + string version = ""; + path srcbase; + string builddir; + Elf32File program; + + // Accumulate sizes + // src asm + // data _____|_____ + // text | + unsigned sizes[SECTION_MAX][SOURCE_MAX] = {{0, 0}, {0, 0}}; + unsigned n_hardcoded = 0; + unsigned n_relocations = 0; + + void reset(); + void AnalyzeObject(path fname_s); +public: + BuildAnalyzer(path &_basedir, path &_subdir, string &_version = default_version); + BuildAnalyzer &operator()(); + friend ostream &operator<<(ostream &strm, BuildAnalyzer &_this); +}; + +#endif //CALCROM_BUILDANALYZER_H diff --git a/.github/calcrom/ElfFile.cpp b/.github/calcrom/ElfFile.cpp new file mode 100644 index 00000000..52ad2d06 --- /dev/null +++ b/.github/calcrom/ElfFile.cpp @@ -0,0 +1,130 @@ +#include <iostream> +#include <cassert> +#include <algorithm> +#include <cstring> +#include "ElfFile.h" + +void Elf32File::ReadElfHeaderAndVerify() { + handle.seekg(0); + handle.read((char *)ehdr.e_ident, EI_NIDENT); + assert(memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0); + assert(ehdr.e_ident[EI_CLASS] == ELFCLASS32); + assert(ehdr.e_ident[EI_DATA] == ELFDATA2LSB); + assert(ehdr.e_ident[EI_VERSION] == EV_CURRENT); + handle.read((char*)&ehdr + EI_NIDENT, sizeof(ehdr) - EI_NIDENT); + assert(ehdr.e_shentsize == sizeof(Elf32_Shdr)); +} + +void Elf32File::ReadSectionHeaders() { + assert(shdr.empty()); + shdr.resize(ehdr.e_shnum); + handle.seekg(ehdr.e_shoff); + handle.read((char*)shdr.data(), ehdr.e_shnum * ehdr.e_shentsize); +} + +void Elf32File::ReadProgramHeaders() { + assert(phdr.empty()); + phdr.resize(ehdr.e_phnum); + handle.seekg(ehdr.e_phoff); + handle.read((char*)phdr.data(), ehdr.e_phnum * ehdr.e_phentsize); +} + +void Elf32File::ReadShstrtab() { + assert(shstrtab.empty()); + shstrtab.resize(shdr[ehdr.e_shstrndx].sh_size); + handle.seekg(shdr[ehdr.e_shstrndx].sh_offset); + handle.read((char*)shstrtab.data(), shdr[ehdr.e_shstrndx].sh_size); +} + +void Elf32File::ReadStrtab() { + assert(strtab.empty()); + int i; + for (i = 1; i < ehdr.e_shnum; i++) { + if (i == ehdr.e_shstrndx) continue; + if (shdr[i].sh_type == SHT_STRTAB) break; + } + assert(i != ehdr.e_shnum); + strtab.resize(shdr[i].sh_size); + handle.seekg(shdr[i].sh_offset); + handle.read((char*)strtab.data(), shdr[i].sh_size); +} + +void Elf32File::ReadSymtab() { + assert(symtab.empty()); + auto sec = find_if(shdr.begin(), shdr.end(), [](Elf32_Shdr const& candidate) { return candidate.sh_type == SHT_SYMTAB; }); + assert(sec != shdr.end()); + symtab.resize(sec->sh_size); + handle.seekg(sec->sh_offset); + handle.read((char*)symtab.data(), sec->sh_size); +} + +Elf32File::Elf32File(path const& filename, elfload load) { + open(filename, load); +} + +void Elf32File::open(path const& filename, elfload load) { + assert(!is_open()); + handle.open(filename, ios::binary); + assert(handle.good()); + ReadElfHeaderAndVerify(); + if (load & sections) { + ReadSectionHeaders(); + ReadShstrtab(); + } + if (load & programs) { + ReadProgramHeaders(); + } + if (load & symbols) { + assert(load & sections); + ReadStrtab(); + ReadSymtab(); + } +} + +void Elf32File::close() { + if (is_open()) { + handle.close(); + } + memset(&ehdr, 0, sizeof(ehdr)); + shdr.clear(); + symtab.clear(); + phdr.clear(); + shstrtab.clear(); + strtab.clear(); +} + +bool Elf32File::is_open() const { + return handle.is_open(); +} + +Elf32File::~Elf32File() { + close(); +} + +vector<Elf32_Shdr> &Elf32File::GetSectionHeaders() { + return shdr; +} + +vector<Elf32_Phdr> &Elf32File::GetProgramHeaders() { + return phdr; +} + +string Elf32File::GetSectionName(const Elf32_Shdr §ion) const { + return string(shstrtab.data() + section.sh_name); +} + +string Elf32File::GetSymbolName(const Elf32_Sym &symbol) const { + return string(strtab.data() + symbol.st_name); +} + +Elf32_Sym &Elf32File::operator[](const string &name) { + return *find_if(symtab.begin(), symtab.end(), [&](Elf32_Sym const &sym) { return name == GetSymbolName(sym); }); +} + +Elf32_Sym &Elf32File::at(const string &name) { + auto ret = find_if(symtab.begin(), symtab.end(), [&](Elf32_Sym const &sym) { return name == GetSymbolName(sym); }); + if (ret == symtab.end()) { + throw runtime_error("no symbol named " + name); + } + return *ret; +} diff --git a/.github/calcrom/ElfFile.h b/.github/calcrom/ElfFile.h new file mode 100644 index 00000000..85e01800 --- /dev/null +++ b/.github/calcrom/ElfFile.h @@ -0,0 +1,60 @@ +#ifndef CALCROM_ELFFILE_H +#define CALCROM_ELFFILE_H + +#include <vector> +#include <fstream> +#include <filesystem> +#include <elf.h> + +using namespace std; +using namespace std::filesystem; + +class Elf32File { + ifstream handle; + Elf32_Ehdr ehdr; + vector<Elf32_Shdr> shdr; + vector<Elf32_Sym> symtab; + vector<Elf32_Phdr> phdr; + vector<char> shstrtab; + vector<char> strtab; + +private: + void ReadElfHeaderAndVerify(); + void ReadSectionHeaders(); + void ReadProgramHeaders(); + void ReadShstrtab(); + void ReadStrtab(); + void ReadSymtab(); +public: + typedef unsigned int elfload; + static const elfload sections = 1; + static const elfload programs = 2; + static const elfload symbols = 4; + + Elf32File() = default; + Elf32File(path const& filename, elfload load = sections); + ~Elf32File(); + void open(path const& filename, elfload load = sections); + void close(); + bool is_open() const; + template <typename _V> + vector<_V> ReadSectionData(const Elf32_Shdr &_shdr) { + vector<_V> ret; + ret.resize((_shdr.sh_size + sizeof(_V) - 1) / sizeof(_V)); + handle.seekg(_shdr.sh_offset); + handle.read((char *)ret.data(), _shdr.sh_size); + return ret; + } + template <typename _V> + size_t GetSectionElementCount(const Elf32_Shdr &_shdr) { + return (_shdr.sh_size + sizeof(_V) - 1) / sizeof(_V); + } + vector<Elf32_Shdr>& GetSectionHeaders(); + vector<Elf32_Phdr>& GetProgramHeaders(); + string GetSectionName(const Elf32_Shdr §ion) const; + string GetSymbolName(const Elf32_Sym &symbol) const; + Elf32_Sym &operator[](const string &name); + Elf32_Sym &at(const string &name); +}; + +#endif //CALCROM_ELFFILE_H diff --git a/.github/calcrom/Glob.cpp b/.github/calcrom/Glob.cpp new file mode 100644 index 00000000..cb519233 --- /dev/null +++ b/.github/calcrom/Glob.cpp @@ -0,0 +1,16 @@ +#include <stdexcept> +#include "Glob.h" + +Glob::Glob(const char *pattern, int _glob_flags) : glob_flags(_glob_flags) { + int result = glob(pattern, glob_flags, nullptr, &glob_result); + if (result) { + throw runtime_error(string("Glob(") + pattern + ") failed with error " + to_string(result)); + } + assign(glob_result.gl_pathv, glob_result.gl_pathv + glob_result.gl_pathc); +} + +Glob::Glob(const string& pattern, int _glob_flags) : Glob(pattern.c_str(), _glob_flags) {} + +Glob::~Glob() { + globfree(&glob_result); +} diff --git a/.github/calcrom/Glob.h b/.github/calcrom/Glob.h new file mode 100644 index 00000000..69667628 --- /dev/null +++ b/.github/calcrom/Glob.h @@ -0,0 +1,20 @@ +#ifndef CALCROM_GLOB_H +#define CALCROM_GLOB_H + +#include <vector> +#include <string> +#include <glob.h> + +using namespace std; + +class Glob : public vector<const char *> { + glob_t glob_result; + int glob_flags; +public: + // Call glob with the supplied pattern + Glob(const char * pattern, int _glob_flags); + Glob(const string& pattern, int _glob_flags); + ~Glob(); +}; + +#endif //CALCROM_GLOB_H diff --git a/.github/calcrom/Makefile b/.github/calcrom/Makefile index e3b6ff50..31b58803 100644 --- a/.github/calcrom/Makefile +++ b/.github/calcrom/Makefile @@ -1,5 +1,8 @@ CXX := g++ -CXXFLAGS := -O3 -std=c++11 +CXXFLAGS := -g -O2 -std=c++17 +ifeq ($(DEBUG),) +CXXFLAGS += -DNDEBUG +endif ifeq ($(OS),Windows_NT) EXE := .exe @@ -7,11 +10,31 @@ else EXE := endif +CXXSRCS := calcrom.cpp BuildAnalyzer.cpp ElfFile.cpp Glob.cpp +CXXOBJS := $(CXXSRCS:%.cpp=%.o) + +DEPDIR := .deps +DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d + TARGET := calcrom$(EXE) -.PHONY: all +.PHONY: all clean all: $(TARGET) -$(TARGET): calcrom.cpp - $(CXX) $(CXXFLAGS) -o $@ $^ +clean: + $(RM) -r $(TARGET) $(CXXOBJS) $(DEPDIR) + +$(TARGET): $(CXXOBJS) + $(CXX) $(LDFLAGS) -o $@ $^ + +%.o: %.cpp +%.o: %.cpp $(DEPDIR)/%.d | $(DEPDIR) + $(CXX) $(CXXFLAGS) $(DEPFLAGS) -c -o $@ $< + +$(DEPDIR): ; @mkdir -p $@ + +DEPFILES := $(CXXSRCS:%.cpp=$(DEPDIR)/%.d) +$(DEPFILES): + +include $(wildcard $(DEPFILES)) 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; } diff --git a/.github/calcrom/webhook.sh b/.github/calcrom/webhook.sh index c15e9b50..60726c7d 100644 --- a/.github/calcrom/webhook.sh +++ b/.github/calcrom/webhook.sh @@ -7,12 +7,14 @@ fi build_name=$1 url=$2 -map_file=$(dirname "$0")/../../arm9/build/diamond.us/arm9.elf.xMAP +build_subdir=${1//poke/}.us +arm9name=${arm9name:-arm9.elf} +map_file=$(dirname "$0")/../../arm9/build/${build_subdir}/${arm9name}.xMAP if [ ! -f $map_file ]; then echo "$map_file does not exist!" exit 1 fi make -C ${GITHUB_WORKSPACE}/.github/calcrom -output=$(${GITHUB_WORKSPACE}/.github/calcrom/calcrom ${GITHUB_WORKSPACE} | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g') +output=$(${GITHUB_WORKSPACE}/.github/calcrom/calcrom -d ${GITHUB_WORKSPACE} -9 arm9 -7 arm7 diamond.us pearl.us | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g') curl -d "{\"username\": \"$CALCROM_DISCORD_WEBHOOK_USERNAME\", \"avatar_url\": \"$CALCROM_DISCORD_WEBHOOK_AVATAR_URL\", \"content\":\"\`\`\`$build_name progress:\\n$output\`\`\`\"}" -H "Content-Type: application/json" -X POST $url |