mirror of
https://github.com/zeldaret/oot.git
synced 2024-12-27 07:07:09 +00:00
d4a6b21d46
subrepo: subdir: "tools/fado" merged: "8d896ee97" upstream: origin: "git@github.com:EllipticEllipsis/fado.git" branch: "master" commit: "8d896ee97" git-subrepo: version: "0.4.5" origin: "git@github.com:ingydotnet/git-subrepo.git" commit: "dbb99be"
418 lines
15 KiB
C
418 lines
15 KiB
C
/**
|
|
* Functions for working with N64 ELF files.
|
|
*/
|
|
/* Copyright (C) 2021 Elliptic Ellipsis */
|
|
/* SPDX-License-Identifier: AGPL-3.0-only */
|
|
#include "fairy.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "vc_vector/vc_vector.h"
|
|
#include "macros.h"
|
|
|
|
VerbosityLevel gVerbosity = VERBOSITY_NONE;
|
|
bool gUseElfAlignment = false;
|
|
|
|
int Fairy_DebugPrintf(const char* file, int line, const char* func, VerbosityLevel level, const char* fmt, ...) {
|
|
if (gVerbosity >= level) {
|
|
int ret = 0;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
if (gVerbosity >= VERBOSITY_DEBUG) {
|
|
ret += fprintf(stderr, "%s:%d:%s: ", file, line, func);
|
|
}
|
|
|
|
ret += vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Endian readers. MIPS is BE, so only need these */
|
|
static Elf32_Half Fairy_ReadHalf(const uint8_t* data) {
|
|
return data[0] << 8 | data[1] << 0;
|
|
}
|
|
|
|
static Elf32_Word Fairy_ReadWord(const uint8_t* data) {
|
|
return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0;
|
|
}
|
|
|
|
static bool Fairy_VerifyMagic(const uint8_t* data) {
|
|
return (data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F');
|
|
}
|
|
|
|
static uint16_t Fairy_Swap16(uint16_t x) {
|
|
return ((x & 0xFF) << 0x8) | ((x & 0xFF00) >> 0x8);
|
|
}
|
|
|
|
static uint32_t Fairy_Swap32(uint32_t x) {
|
|
return ((x & 0xFF) << 0x18) | ((x & 0xFF00) << 0x8) | ((x & 0xFF0000) >> 0x8) | ((x & 0xFF000000) >> 0x18);
|
|
}
|
|
|
|
/* Both GCC and Clang define these, so we can avoid an endian header altogether */
|
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
#define REEND16(x) Fairy_Swap16(x)
|
|
#define REEND32(x) Fairy_Swap32(x)
|
|
#else
|
|
#define REEND16(x) (x)
|
|
#define REEND32(x) (x)
|
|
#endif
|
|
|
|
const char* Fairy_StringFromDefine(const FairyDefineString* dict, int define) {
|
|
size_t i;
|
|
for (i = 0; dict[i].string != NULL; i++) {
|
|
if (dict[i].define == define) {
|
|
return dict[i].string;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the string 'initial' is contained in the string 'string'
|
|
* 'initial' must be null-terminated, 'string' ideally is.
|
|
*/
|
|
bool Fairy_StartsWith(const char* string, const char* initial) {
|
|
char s;
|
|
char i;
|
|
do {
|
|
s = *string++;
|
|
i = *initial++;
|
|
if (i == '\0') {
|
|
return true;
|
|
}
|
|
} while (s == i);
|
|
return false;
|
|
}
|
|
|
|
/* Reading functions */
|
|
|
|
/**
|
|
* Every reading function:
|
|
* - Returns the pointer to the struct
|
|
* - Takes the ouput struct or array as its first argument. This must be pre-allocated
|
|
* - Takes the input file as the second argument (At least until I am persuaded to read the whole file into RAM...)
|
|
* - The rest of the arguments are important information about the struct it is reading (offset and size, usually)
|
|
*/
|
|
|
|
FairyFileHeader* Fairy_ReadFileHeader(FairyFileHeader* header, FILE* file) {
|
|
fseek(file, 0, SEEK_SET);
|
|
assert(fread(header, sizeof(char), 0x34, file) == 0x34);
|
|
|
|
if (!Fairy_VerifyMagic(header->e_ident)) {
|
|
fprintf(stderr, "Not a valid ELF file.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (header->e_ident[EI_CLASS] != ELFCLASS32) {
|
|
fprintf(stderr, "Not a 32-bit ELF file.\n");
|
|
return NULL;
|
|
}
|
|
|
|
header->e_type = REEND16(header->e_type);
|
|
if (header->e_type != ET_REL) {
|
|
fprintf(stderr, "Not a relocatable object file.\n");
|
|
return NULL;
|
|
}
|
|
|
|
header->e_machine = REEND16(header->e_machine);
|
|
if (header->e_machine != EM_MIPS) {
|
|
fprintf(stderr, "Not a MIPS object file.\n");
|
|
return NULL;
|
|
}
|
|
|
|
header->e_version = REEND32(header->e_version);
|
|
header->e_entry = REEND32(header->e_entry);
|
|
header->e_phoff = REEND32(header->e_phoff);
|
|
header->e_shoff = REEND32(header->e_shoff);
|
|
header->e_flags = REEND32(header->e_flags);
|
|
header->e_ehsize = REEND16(header->e_ehsize);
|
|
header->e_phentsize = REEND16(header->e_phentsize);
|
|
header->e_phnum = REEND16(header->e_phnum);
|
|
header->e_shentsize = REEND16(header->e_shentsize);
|
|
header->e_shnum = REEND16(header->e_shnum);
|
|
header->e_shstrndx = REEND16(header->e_shstrndx);
|
|
|
|
return header;
|
|
}
|
|
|
|
/* tableOffset and number should be obtained from the file header */
|
|
FairySecHeader* Fairy_ReadSectionTable(FairySecHeader* sectionTable, FILE* file, size_t tableOffset, size_t number) {
|
|
size_t entrySize = sizeof(FairySecHeader);
|
|
size_t tableSize = number * entrySize;
|
|
|
|
fseek(file, tableOffset, SEEK_SET);
|
|
assert(fread(sectionTable, sizeof(char), tableSize, file) == tableSize);
|
|
|
|
/* Since the section table happens to only have entries of width 4, we can byteswap it by pretending it is a raw
|
|
* uint32_t array */
|
|
{
|
|
size_t i;
|
|
uint32_t* data = (uint32_t*)sectionTable;
|
|
for (i = 0; i < tableSize / sizeof(uint32_t); i++) {
|
|
data[i] = REEND32(data[i]);
|
|
}
|
|
}
|
|
|
|
return sectionTable;
|
|
}
|
|
|
|
size_t Fairy_ReadSymbolTable(FairySym** symbolTableOut, FILE* file, size_t tableOffset, size_t tableSize) {
|
|
size_t number = tableSize / sizeof(FairySym);
|
|
FairySym* symbolTable = malloc(tableSize);
|
|
|
|
*symbolTableOut = NULL;
|
|
|
|
if (symbolTable == NULL) {
|
|
return 0;
|
|
}
|
|
if (fseek(file, tableOffset, SEEK_SET) != 0 || fread(symbolTable, sizeof(char), tableSize, file) != tableSize) {
|
|
free(symbolTable);
|
|
return 0;
|
|
}
|
|
|
|
/* Reend the variables that are wider than bytes */
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < number; i++) {
|
|
symbolTable[i].st_name = REEND32(symbolTable[i].st_name);
|
|
symbolTable[i].st_value = REEND32(symbolTable[i].st_value);
|
|
symbolTable[i].st_size = REEND32(symbolTable[i].st_size);
|
|
symbolTable[i].st_shndx = REEND16(symbolTable[i].st_shndx);
|
|
}
|
|
}
|
|
|
|
*symbolTableOut = symbolTable;
|
|
return number;
|
|
}
|
|
|
|
/* Can be used for both the section header string table and the strtab */
|
|
char* Fairy_ReadStringTable(char* stringTable, FILE* file, size_t tableOffset, size_t tableSize) {
|
|
fseek(file, tableOffset, SEEK_SET);
|
|
assert(fread(stringTable, sizeof(char), tableSize, file) == tableSize);
|
|
return stringTable;
|
|
}
|
|
|
|
/* offset and number are attained from the section table, the returned pointer must be freed */
|
|
size_t Fairy_ReadRelocs(FairyRela** relocsOut, FILE* file, int type, size_t offset, size_t size) {
|
|
/* Final size of the relocation table, relocations of type SHT_REL need more space for extra addend of 0 */
|
|
size_t finalSize = (type == SHT_REL) ? ((size * sizeof(FairyRela)) / sizeof(FairyRel)) : size;
|
|
void* readBuf = malloc(size);
|
|
FairyRela* relocTable = malloc(finalSize);
|
|
|
|
*relocsOut = NULL;
|
|
|
|
if (readBuf == NULL) {
|
|
return 0;
|
|
}
|
|
if (relocTable == NULL) {
|
|
free(readBuf);
|
|
return 0;
|
|
}
|
|
if (fseek(file, offset, SEEK_SET) != 0 || fread(readBuf, sizeof(char), size, file) != size) {
|
|
free(readBuf);
|
|
free(relocTable);
|
|
return 0;
|
|
}
|
|
|
|
/* Reend the variables that are wider than bytes */
|
|
{
|
|
size_t i;
|
|
uint32_t* data = (uint32_t*)readBuf;
|
|
for (i = 0; i < size / sizeof(uint32_t); i++) {
|
|
data[i] = REEND32(data[i]);
|
|
}
|
|
}
|
|
|
|
/* Make the relocation table, for SHT_REL sections add an addend of 0 */
|
|
if (type == SHT_REL) {
|
|
size_t i;
|
|
FairyRel* rel = (FairyRel*)readBuf;
|
|
|
|
for (i = 0; i < size / sizeof(FairyRel); i++) {
|
|
relocTable[i].r_info = rel[i].r_info;
|
|
relocTable[i].r_offset = rel[i].r_offset;
|
|
relocTable[i].r_addend = 0;
|
|
}
|
|
} else {
|
|
memcpy(relocTable, readBuf, size);
|
|
}
|
|
free(readBuf);
|
|
|
|
*relocsOut = relocTable;
|
|
return finalSize / sizeof(FairyRela);
|
|
}
|
|
|
|
char* Fairy_GetSectionName(FairySecHeader* sectionTable, char* shstrtab, size_t index) {
|
|
return &shstrtab[sectionTable[index].sh_name];
|
|
}
|
|
|
|
/* Look up the index in the symbol table and return a pointer to the beginning of its string */
|
|
char* Fairy_GetSymbolName(FairySym* symtab, char* strtab, size_t index) {
|
|
return &strtab[symtab[index].st_name];
|
|
}
|
|
|
|
/* FairyFileInfo functions */
|
|
|
|
void Fairy_InitFile(FairyFileInfo* fileInfo, FILE* file) {
|
|
FairyFileHeader fileHeader;
|
|
FairySecHeader* sectionTable;
|
|
char* shstrtab;
|
|
int i;
|
|
|
|
assert(fileInfo != NULL);
|
|
assert(file != NULL);
|
|
|
|
fileInfo->progBitsSections = vc_vector_create(3, sizeof(Elf32_Section), NULL);
|
|
for (i = 0; i < 3; i++) {
|
|
fileInfo->progBitsSizes[i] = 0;
|
|
}
|
|
Fairy_ReadFileHeader(&fileHeader, file);
|
|
|
|
sectionTable = malloc(fileHeader.e_shnum * fileHeader.e_shentsize);
|
|
Fairy_ReadSectionTable(sectionTable, file, fileHeader.e_shoff, fileHeader.e_shnum);
|
|
|
|
shstrtab = malloc(sectionTable[fileHeader.e_shstrndx].sh_size * sizeof(char));
|
|
fseek(file, sectionTable[fileHeader.e_shstrndx].sh_offset, SEEK_SET);
|
|
assert(fread(shstrtab, sizeof(char), sectionTable[fileHeader.e_shstrndx].sh_size, file) ==
|
|
sectionTable[fileHeader.e_shstrndx].sh_size);
|
|
|
|
/* Search for the sections we need */
|
|
{
|
|
size_t currentIndex;
|
|
FairySecHeader currentSection;
|
|
for (currentIndex = 0; currentIndex < 3; currentIndex++) {
|
|
fileInfo->relocTablesInfo[currentIndex].sectionData = NULL;
|
|
}
|
|
|
|
for (currentIndex = 0; currentIndex < fileHeader.e_shnum; currentIndex++) {
|
|
size_t off = 0;
|
|
|
|
currentSection = sectionTable[currentIndex];
|
|
|
|
switch (currentSection.sh_type) {
|
|
case SHT_PROGBITS:
|
|
assert(vc_vector_push_back(fileInfo->progBitsSections, ¤tIndex));
|
|
|
|
{
|
|
FairySection sectionType = FAIRY_SECTION_OTHER;
|
|
const char* sectionName = &shstrtab[currentSection.sh_name + 1];
|
|
size_t alignedSize;
|
|
|
|
/* Ignore the leading "." */
|
|
if (strcmp(sectionName, "text") == 0) {
|
|
sectionType = FAIRY_SECTION_TEXT;
|
|
} else if (strcmp(sectionName, "data") == 0) {
|
|
sectionType = FAIRY_SECTION_DATA;
|
|
} else if (Fairy_StartsWith(sectionName, "rodata")) { /* May be several */
|
|
sectionType = FAIRY_SECTION_RODATA;
|
|
}
|
|
|
|
if (sectionType != FAIRY_SECTION_OTHER) {
|
|
if (gUseElfAlignment) {
|
|
/* Ensure the next file will start at its correct alignment */
|
|
fileInfo->progBitsSizes[sectionType] =
|
|
ALIGN(fileInfo->progBitsSizes[sectionType], currentSection.sh_addralign);
|
|
|
|
alignedSize = ALIGN(currentSection.sh_size, currentSection.sh_addralign);
|
|
|
|
FAIRY_DEBUG_PRINTF("%s section alignment: 0x%X\n", sectionName,
|
|
currentSection.sh_addralign);
|
|
FAIRY_DEBUG_PRINTF("%s section size before align: 0x%X\n", sectionName,
|
|
currentSection.sh_size);
|
|
FAIRY_DEBUG_PRINTF("%s section size after align: 0x%X\n", sectionName, alignedSize);
|
|
|
|
fileInfo->progBitsSizes[sectionType] += alignedSize;
|
|
} else {
|
|
fileInfo->progBitsSizes[sectionType] += ALIGN(currentSection.sh_size, 0x10);
|
|
}
|
|
|
|
FAIRY_DEBUG_PRINTF("%s section size: 0x%X\n", sectionName,
|
|
fileInfo->progBitsSizes[sectionType]);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case SHT_SYMTAB:
|
|
if (strcmp(&shstrtab[currentSection.sh_name + 1], "symtab") == 0) {
|
|
fileInfo->symtabInfo.sectionType = SHT_SYMTAB;
|
|
fileInfo->symtabInfo.sectionEntrySize = sizeof(FairySym);
|
|
fileInfo->symtabInfo.sectionEntryCount =
|
|
Fairy_ReadSymbolTable((FairySym**)&fileInfo->symtabInfo.sectionData, file,
|
|
currentSection.sh_offset, currentSection.sh_size);
|
|
}
|
|
break;
|
|
|
|
case SHT_STRTAB:
|
|
if (strcmp(&shstrtab[currentSection.sh_name + 1], "strtab") == 0) {
|
|
FAIRY_DEBUG_PRINTF("%s", "strtab found\n");
|
|
fileInfo->strtab = malloc(currentSection.sh_size);
|
|
Fairy_ReadStringTable(fileInfo->strtab, file, currentSection.sh_offset, currentSection.sh_size);
|
|
}
|
|
break;
|
|
|
|
case SHT_RELA:
|
|
off += 1;
|
|
case SHT_REL:
|
|
off += 5;
|
|
/* This assumes only one reloc section of each name */
|
|
// TODO: is this a problem?
|
|
{
|
|
FairySection relocSection = FAIRY_SECTION_OTHER;
|
|
|
|
/* Ignore the first 5/6 chars, which will always be ".rel."/".rela." */
|
|
if (strcmp(&shstrtab[currentSection.sh_name + off], "text") == 0) {
|
|
relocSection = FAIRY_SECTION_TEXT;
|
|
} else if (strcmp(&shstrtab[currentSection.sh_name + off], "data") == 0) {
|
|
relocSection = FAIRY_SECTION_DATA;
|
|
} else if (strcmp(&shstrtab[currentSection.sh_name + off], "rodata") == 0) {
|
|
relocSection = FAIRY_SECTION_RODATA;
|
|
} else {
|
|
break;
|
|
}
|
|
FAIRY_DEBUG_PRINTF("Found %s section\n", &shstrtab[currentSection.sh_name]);
|
|
|
|
fileInfo->relocTablesInfo[relocSection].sectionType = SHT_RELA;
|
|
fileInfo->relocTablesInfo[relocSection].sectionEntrySize = sizeof(FairyRela);
|
|
fileInfo->relocTablesInfo[relocSection].sectionEntryCount =
|
|
Fairy_ReadRelocs((FairyRela**)&fileInfo->relocTablesInfo[relocSection].sectionData, file,
|
|
currentSection.sh_type, currentSection.sh_offset, currentSection.sh_size);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(sectionTable);
|
|
free(shstrtab);
|
|
}
|
|
|
|
void Fairy_DestroyFile(FairyFileInfo* fileInfo) {
|
|
size_t i;
|
|
for (i = 0; i < ARRAY_COUNTU(fileInfo->relocTablesInfo); i++) {
|
|
if (fileInfo->relocTablesInfo[i].sectionData != NULL) {
|
|
FAIRY_DEBUG_PRINTF("Freeing reloc section %zd data\n", i);
|
|
free(fileInfo->relocTablesInfo[i].sectionData);
|
|
}
|
|
}
|
|
|
|
vc_vector_release(fileInfo->progBitsSections);
|
|
|
|
FAIRY_DEBUG_PRINTF("%s", "Freeing symtab data\n");
|
|
free(fileInfo->symtabInfo.sectionData);
|
|
|
|
FAIRY_DEBUG_PRINTF("%s", "Freeing strtab data\n");
|
|
free(fileInfo->strtab);
|
|
}
|