diff options
Diffstat (limited to 'tools/o2narc/o2narc.cpp')
-rw-r--r-- | tools/o2narc/o2narc.cpp | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/tools/o2narc/o2narc.cpp b/tools/o2narc/o2narc.cpp new file mode 100644 index 00000000..e4b46e2a --- /dev/null +++ b/tools/o2narc/o2narc.cpp @@ -0,0 +1,317 @@ +#include <iostream> +#include <fstream> +#include <getopt.h> +#include <cstring> +#include <vector> +#include "elf.h" +#include "Narc.h" + +using namespace std; + +static Elf32_Sym NullSym { + .st_name = -1u, + .st_value = -1u, + .st_size = 0 +}; + +static Elf32_Shdr NullShdr { + .sh_name = -1u, + .sh_offset = -1u +}; + +class Symtab : public vector<Elf32_Sym> { +public: + char * strtab; + Symtab() : strtab(nullptr) {} + Elf32_Sym & operator[](const char * name) { + for (auto& value : *this) { + if (strcmp(strtab + value.st_name, name) == 0) + return value; + } + return NullSym; + } +}; + +class ShdrTab : public vector<Elf32_Shdr> { +public: + char * shstrtab; + ShdrTab() : shstrtab(nullptr) {} + Elf32_Shdr & operator[](const int idx) { + return data()[idx]; + } + Elf32_Shdr & operator[](const char * name) { + for (auto& value : *this) { + if (strcmp(shstrtab + value.sh_name, name) == 0) + return value; + } + return NullShdr; + } +}; + +class Elf { + fstream handle; + Elf32_Ehdr ehdr; + Elf32_Phdr * phdr; + uint32_t symnum; +public: + ShdrTab shdr; + Symtab symtab; + Elf(const char * filename) { + // Read the ELF header + phdr = nullptr; + handle.open(filename, ios_base::in | ios_base::binary); + if (!handle.good()) { + cerr << "ERROR: Unable to open file '" << filename << "' for reading" << endl; + exit(1); + } + handle.read((char *)&ehdr, sizeof(ehdr)); + if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { + handle.close(); + cerr << "ERROR: Opened file is not a valid ELF" << endl; + exit(1); + } + + // Read the section headers + shdr.resize(ehdr.e_shnum); + handle.seekg(ehdr.e_shoff); + handle.read((char *)shdr.data(), ehdr.e_shnum * ehdr.e_shentsize); + + // Read the program headers + phdr = new Elf32_Phdr [ehdr.e_phnum]; + handle.seekg(ehdr.e_phoff); + handle.read((char *)phdr, ehdr.e_phnum * ehdr.e_phentsize); + + // Read the section string table + shdr.shstrtab = new char[shdr[ehdr.e_shstrndx].sh_size]; + handle.seekg(shdr[ehdr.e_shstrndx].sh_offset); + handle.read(shdr.shstrtab, shdr[ehdr.e_shstrndx].sh_size); + + // Read the symbol table + for (int i = 0; i < ehdr.e_shnum; i++) { + switch (shdr[i].sh_type) + { + case SHT_SYMTAB: + if (!symtab.empty()) { + handle.close(); + cerr << "ERROR: double symtab" << endl; + exit(1); + } + symnum = shdr[i].sh_size / sizeof(Elf32_Sym); + symtab.resize(symnum); + handle.seekg(shdr[i].sh_offset); + handle.read((char *)symtab.data(), shdr[i].sh_size); + break; + case SHT_STRTAB: + if (i == ehdr.e_shstrndx) + break; + if (symtab.strtab != nullptr) { + handle.close(); + cerr << "ERROR: double strtab" << endl; + exit(1); + } + symtab.strtab = new char[shdr[i].sh_size]; + handle.seekg(shdr[i].sh_offset); + handle.read(symtab.strtab, shdr[i].sh_size); + break; + } + } + } + + void operator ~() { + delete[] symtab.strtab; + delete[] shdr.shstrtab; + delete[] phdr; + } + + void * read(Elf32_Sym & sym) { + // Reads the value of a symbol + if (sym.st_size == 0) + return nullptr; + if (shdr.empty()) + return nullptr; + Elf32_Shdr & sec = shdr[sym.st_shndx]; + size_t size = (sym.st_size + 3) & ~3; + off_t off = sym.st_value - sec.sh_addr + sec.sh_offset; + auto ret = new char[size]; + handle.seekg(off); + handle.read(ret, sym.st_size); + if (sym.st_size & 3) + memset(ret + sym.st_size, 0, size - sym.st_size); + return ret; + } + + void * read(Elf32_Shdr & sec) { + // Reads the contents of an ELF section + if (sec.sh_size == 0) + return nullptr; + size_t size = (sec.sh_size + 3) & ~3; + auto ret = new char[size]; + handle.seekg(sec.sh_offset); + handle.read(ret, sec.sh_size); + if (sec.sh_size & 3) + memset(ret + sec.sh_size, 0, size - sec.sh_size); + return ret; + } +}; + +static inline void usage() { + cout << "Usage: o2narc [-f|--flatten] infile outfile" << endl; + cout << endl; + cout << "Arguments:" << endl; + cout << "\tinfile\tELF object file with symbols __size and __data" << endl; + cout << "\toutfile\tOutput NARC file" << endl; + cout << "Options:" << endl; + cout << "\t-f|--flatten\tDon't generate NARC headers" << endl; +} + +int main(int argc, char ** argv) { + // CLI arguments + int flatten = 0; + char padding = '\xFF'; + static option options [] { + { "flatten", no_argument, &flatten, 1 }, + { "padding", required_argument, nullptr, 'p' }, + {nullptr, 0, nullptr, 0} + }; + int opt_index; + int c; + while ((c = getopt_long(argc, argv, "fp:", options, &opt_index)) != -1) + { + if (c == 'f') { + flatten = 1; + } else if (c == 'p') { + padding = *optarg; + } + } + argv += optind; + argc -= optind; + if (argc < 2) { + usage(); + cerr << "Insufficient arguments: missing " << (argc == 0 ? "infile, " : "") << "outfile" << endl; + return 1; + } + if (argc > 2) { + usage(); + cerr << "Excess arguments: first unrecognized '" << argv[2] << "'" << endl; + return 1; + } + char * infname = argv[0]; + char * outfname = argv[1]; + + // Read the ELF file + Elf elf(infname); + // .rodata contains the data + Elf32_Shdr & rodata_sec = elf.shdr[".rodata"]; + char * _rodata = (char *)elf.read(rodata_sec); + if (_rodata == nullptr) { + cerr << "ERROR: Missing required section .rodata" << endl; + exit(1); + } + + fstream ofile; + ofile.open(outfname, ios_base::out | ios_base::binary); + if (!ofile.good()) { + cerr << "ERROR: Unable to open '" << outfname << "' for writing" << endl; + exit(1); + } + + if (!flatten) // then build the NARC chunks + { + // .data contains the size table + Elf32_Shdr & data_sec = elf.shdr[".data"]; + uint32_t * _data = (uint32_t *)elf.read(data_sec); + + if (_data == nullptr) { + cerr << "ERROR: Missing required section .data" << endl; + exit(1); + } + + uint16_t count; + size_t narc_size; + size_t size_aln; + size_t size = *_data; + + if (data_sec.sh_size == sizeof(uint32_t)) + { + size_aln = (size + 3) & ~3; + count = rodata_sec.sh_size / size_aln; + } + else + { + size_aln = -1u; + count = data_sec.sh_size / sizeof(uint32_t); + } + // NARC header: 16 + // FATB header: 12 + 8 * count + // FNTB header: 8 + // GMIF header: 8 + data_sym.st_size + narc_size = ( + sizeof(NarcHeader) + + sizeof(FileAllocationTable) + sizeof(FileAllocationTableEntry) * count + + sizeof(FileNameTable) + sizeof(FileNameTableEntry) + + sizeof(FileImages) + (rodata_sec.sh_size + 3) & ~3 + ); + + NarcHeader header{ + .Id = *(uint32_t *) "NARC", + .ByteOrderMark = 0xFFFE, + .Version = 0x100, + .FileSize = static_cast<uint32_t>(narc_size), + .ChunkSize = sizeof(NarcHeader), + .ChunkCount = 3 + }; + FileAllocationTable fat{ + .Id = *(uint32_t *) "BTAF", + .ChunkSize = static_cast<uint32_t>(sizeof(FileAllocationTable) + sizeof(FileAllocationTableEntry) * count), + .FileCount = count, + .Reserved = 0 + }; + auto fat_entries = new FileAllocationTableEntry[count]; + for (int i = 0; i < count; i++) + { + // Each element of the size array corresponds to + // a NARC member + if (data_sec.sh_size > sizeof(uint32_t)) { + size = _data[i]; + size_aln = (size + 3) & ~3; + } + fat_entries[i].Start = i == 0 ? 0 : (fat_entries[i - 1].End + 3) & ~3; + fat_entries[i].End = fat_entries[i].Start + _data[data_sec.sh_size == sizeof(uint32_t) ? 0 : i]; + // Padding + for (int j = size; j < size_aln; j++) + { + _rodata[fat_entries[i].End + j] = padding; + } + } + // These NARCs have empty FNTs + FileNameTable fnt{ + .Id = *(uint32_t *) "BTNF", + .ChunkSize = static_cast<uint32_t>(sizeof(FileNameTable) + sizeof(FileNameTableEntry)) + }; + FileNameTableEntry fnt_entry{ + .Offset = 4, + .FirstFileId = 0, + .Utility = 1 + }; + FileImages fimg{ + .Id = *(uint32_t *) "GMIF", + .ChunkSize = static_cast<uint32_t>(sizeof(FileImages) + (rodata_sec.sh_size + 3) & ~3) + }; + + ofile.write((char *) &header, sizeof(header)); + ofile.write((char *) &fat, sizeof(fat)); + ofile.write((char *) fat_entries, sizeof(FileAllocationTableEntry) * count); + ofile.write((char *) &fnt, sizeof(fnt)); + ofile.write((char *) &fnt_entry, sizeof(fnt_entry)); + ofile.write((char *) &fimg, sizeof(fimg)); + // Cleanup + delete[] fat_entries; + delete[] _data; + } + // NARC members are contiguous in memory + ofile.write(_rodata, (rodata_sec.sh_size + 3) & ~3); + // Cleanup + delete[] _rodata; + ofile.close(); + return 0; +} |