mirror of
https://github.com/izzy2lost/2ship2harkinian-Android.git
synced 2026-06-19 01:20:08 -07:00
New relocation-generating program (#393)
* git subrepo clone git@github.com:EllipticEllipsis/fado.git tools/fado subrepo: subdir: "tools/fado" merged: "d202857b" upstream: origin: "git@github.com:EllipticEllipsis/fado.git" branch: "master" commit: "d202857b" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Makefile adjustments and additions to build fado * git subrepo pull --force tools/fado subrepo: subdir: "tools/fado" merged: "46c4d751" upstream: origin: "git@github.com:EllipticEllipsis/fado.git" branch: "master" commit: "46c4d751" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * git subrepo pull --force tools/fado subrepo: subdir: "tools/fado" merged: "88114ebc" upstream: origin: "git@github.com:EllipticEllipsis/fado.git" branch: "master" commit: "88114ebc" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Fix typo in makefile * Fix it, maybe? * git subrepo pull tools/fado subrepo: subdir: "tools/fado" merged: "f7efb10a9" upstream: origin: "git@github.com:EllipticEllipsis/fado.git" branch: "master" commit: "f7efb10a9" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596" * Update build tools
This commit is contained in:
@@ -113,6 +113,7 @@ ELF2ROM := tools/buildtools/elf2rom
|
||||
MKLDSCRIPT := tools/buildtools/mkldscript
|
||||
YAZ0 := tools/buildtools/yaz0
|
||||
ZAPD := tools/ZAPD/ZAPD.out
|
||||
FADO := tools/fado/fado.elf
|
||||
|
||||
OPTFLAGS := -O2 -g3
|
||||
ASFLAGS := -march=vr4300 -32 -Iinclude
|
||||
@@ -184,9 +185,11 @@ O_FILES := $(foreach f,$(S_FILES:.s=.o),build/$f) \
|
||||
$(foreach f,$(C_FILES:.c=.o),build/$f) \
|
||||
$(foreach f,$(BASEROM_FILES),build/$f.o)
|
||||
|
||||
OVL_RELOC_FILES := $(shell $(CPP) $(CPPFLAGS) $(SPEC) | grep -o '[^"]*_reloc.o' )
|
||||
|
||||
# Automatic dependency files
|
||||
# (Only asm_processor dependencies are handled for now)
|
||||
DEP_FILES := $(O_FILES:.o=.asmproc.d)
|
||||
# (Only asm_processor dependencies and reloc dependencies are handled for now)
|
||||
DEP_FILES := $(O_FILES:.o=.asmproc.d) $(OVL_RELOC_FILES:.o=.d)
|
||||
|
||||
# create build directories
|
||||
$(shell mkdir -p build/baserom $(foreach dir,$(SRC_DIRS) $(ASM_DIRS) $(ASSET_BIN_DIRS),build/$(dir)))
|
||||
@@ -262,9 +265,20 @@ $(ROM): $(ELF)
|
||||
$(ROMC): $(ROM)
|
||||
python3 tools/z64compress_wrapper.py $(COMPFLAGS) $(ROM) $@ $(ELF) build/$(SPEC)
|
||||
|
||||
$(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) build/ldscript.txt build/undefined_syms.txt
|
||||
$(ELF): $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT) $(O_FILES) $(OVL_RELOC_FILES) build/ldscript.txt build/undefined_syms.txt
|
||||
$(LD) -T build/undefined_syms.txt -T build/ldscript.txt --no-check-sections --accept-unknown-input-arch --emit-relocs -Map build/mm.map -o $@
|
||||
|
||||
## Order-only prerequisites
|
||||
# These ensure e.g. the O_FILES are built before the OVL_RELOC_FILES.
|
||||
# The intermediate phony targets avoid quadratically-many dependencies between the targets and prerequisites.
|
||||
|
||||
o_files: $(O_FILES)
|
||||
$(OVL_RELOC_FILES): | o_files
|
||||
|
||||
asset_files: $(TEXTURE_FILES_OUT) $(ASSET_FILES_OUT)
|
||||
$(O_FILES): | asset_files
|
||||
|
||||
.PHONY: o_files asset_files
|
||||
|
||||
#### Main commands ####
|
||||
|
||||
@@ -313,9 +327,11 @@ init:
|
||||
build/undefined_syms.txt: undefined_syms.txt
|
||||
$(CPP) $(CPPFLAGS) $< > build/undefined_syms.txt
|
||||
|
||||
build/ldscript.txt: $(SPEC)
|
||||
$(CPP) $(CPPFLAGS) $< > build/spec
|
||||
$(MKLDSCRIPT) build/spec $@
|
||||
build/$(SPEC): $(SPEC)
|
||||
$(CPP) $(CPPFLAGS) $< > $@
|
||||
|
||||
build/ldscript.txt: build/$(SPEC)
|
||||
$(MKLDSCRIPT) $< $@
|
||||
|
||||
build/asm/%.o: asm/%.s
|
||||
$(AS) $(ASFLAGS) $< -o $@
|
||||
@@ -334,13 +350,13 @@ build/data/%.o: data/%.s
|
||||
build/src/overlays/%.o: src/overlays/%.c
|
||||
$(CC_CHECK) $<
|
||||
$(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $<
|
||||
$(OBJDUMP_CMD)
|
||||
# TODO: `() || true` is currently necessary to suppress `Error 1 (ignored)` make warnings caused by `test`, but this will go away if
|
||||
# the following is moved to a separate rule that is only run once when all the required objects have been compiled.
|
||||
$(ZAPD) bovl -eh -i $@ -cfg $< --outputpath $(@D)/$(notdir $(@D))_reloc.s
|
||||
(test -f $(@D)/$(notdir $(@D))_reloc.s && $(AS) $(ASFLAGS) $(@D)/$(notdir $(@D))_reloc.s -o $(@D)/$(notdir $(@D))_reloc.o) || true
|
||||
@$(OBJDUMP) -d $@ > $(@:.o=.s)
|
||||
$(RM_MDEBUG)
|
||||
|
||||
build/src/overlays/%_reloc.o: build/$(SPEC)
|
||||
$(FADO) $$(tools/buildtools/reloc_prereq $< $(notdir $*)) -n $(notdir $*) -o $(@:.o=.s) -M $(@:.o=.d)
|
||||
$(AS) $(ASFLAGS) $(@:.o=.s) -o $@
|
||||
|
||||
build/src/%.o: src/%.c
|
||||
$(CC_CHECK) $<
|
||||
$(CC) -c $(CFLAGS) $(MIPS_VERSION) $(OPTFLAGS) -o $@ $<
|
||||
|
||||
@@ -640,7 +640,7 @@ beginseg
|
||||
include "build/src/code/audio/audio_init_params.o"
|
||||
include "build/src/code/jpegutils.o"
|
||||
include "build/src/code/jpegdecoder.o"
|
||||
include_readonly "build/src/code/z_game_over.o"
|
||||
include_data_with_rodata "build/src/code/z_game_over.o"
|
||||
include "build/src/code/z_construct.o"
|
||||
include "build/data/code/audio_tables.rodata.o"
|
||||
include "build/data/code/aspMain.rodata.o"
|
||||
|
||||
@@ -4,12 +4,14 @@ PROGRAMS := vtxdis
|
||||
|
||||
all: $(PROGRAMS)
|
||||
$(MAKE) -C ZAPD
|
||||
$(MAKE) -C fado
|
||||
$(MAKE) -C buildtools
|
||||
$(MAKE) -C z64compress
|
||||
|
||||
clean:
|
||||
$(RM) $(PROGRAMS)
|
||||
$(MAKE) -C ZAPD clean
|
||||
$(MAKE) -C fado clean
|
||||
$(MAKE) -C buildtools clean
|
||||
$(MAKE) -C z64compress clean
|
||||
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
elf2rom
|
||||
makeromfs
|
||||
mkldscript
|
||||
reloc_prereq
|
||||
yaz0
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
CC := gcc
|
||||
CFLAGS := -Wall -Wextra -pedantic -std=c99 -g -O2
|
||||
PROGRAMS := yaz0 makeromfs elf2rom mkldscript
|
||||
PROGRAMS := elf2rom makeromfs mkldscript reloc_prereq yaz0
|
||||
|
||||
ifeq ($(shell command -v clang >/dev/null 2>&1; echo $$?),0)
|
||||
CC := clang
|
||||
else
|
||||
CC := gcc
|
||||
endif
|
||||
|
||||
LLD ?= 0
|
||||
|
||||
ifeq ($(shell command -v ld.lld >/dev/null 2>&1; echo $$?),0)
|
||||
LLD := 1
|
||||
endif
|
||||
|
||||
ifneq ($(LLD),0)
|
||||
CFLAGS += -fuse-ld=lld
|
||||
endif
|
||||
all: $(PROGRAMS)
|
||||
|
||||
clean:
|
||||
$(RM) $(PROGRAMS)
|
||||
|
||||
mkldscript_SOURCES := mkldscript.c util.c
|
||||
elf2rom_SOURCES := elf2rom.c elf32.c n64chksum.c util.c
|
||||
yaz0_SOURCES := yaz0tool.c yaz0.c util.c
|
||||
makeromfs_SOURCES := makeromfs.c n64chksum.c util.c
|
||||
elf2rom_SOURCES := elf2rom.c elf32.c n64chksum.c util.c
|
||||
makeromfs_SOURCES := makeromfs.c n64chksum.c util.c
|
||||
mkldscript_SOURCES := mkldscript.c spec.c util.c
|
||||
reloc_prereq_SOURCES := reloc_prereq.c spec.c util.c
|
||||
yaz0_SOURCES := yaz0tool.c yaz0.c util.c
|
||||
|
||||
define COMPILE =
|
||||
$(1): $($1_SOURCES)
|
||||
|
||||
+229
-418
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "spec.h"
|
||||
#include "util.h"
|
||||
|
||||
void print_usage(char* prog_name) {
|
||||
printf("USAGE: %s SPEC OVERLAY_SEGMENT_NAME\n"
|
||||
"Search the preprocessed SPEC for an overlay segment name, \n"
|
||||
"e.g. \"ovl_En_Firefly\", and return a space-separated list of the files it\n"
|
||||
"includes. The relocation file must be the last include in the segment\n"
|
||||
"OVERLAY_SEGMENT_NAME, and have the filename \"OVERLAY_SEGMENT_NAME_reloc.o\",\n"
|
||||
"but can be in a different directory from the other files.\n",
|
||||
prog_name);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
char* spec_path;
|
||||
char* overlay_name;
|
||||
char* spec;
|
||||
size_t size;
|
||||
Segment segment;
|
||||
int exit_status = 0;
|
||||
bool segmentFound = false;
|
||||
|
||||
if (argc != 3) {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
spec_path = argv[1];
|
||||
overlay_name = argv[2];
|
||||
|
||||
// printf("spec path: %s\n", spec_path);
|
||||
// printf("overlay name: %s\n", overlay_name);
|
||||
|
||||
spec = util_read_whole_file(spec_path, &size);
|
||||
segmentFound = get_single_segment_by_name(&segment, spec, overlay_name);
|
||||
|
||||
if (!segmentFound) {
|
||||
fprintf(stderr, ERRMSG_START "no segment \"%s\" found\n" ERRMSG_END, overlay_name);
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
{
|
||||
size_t overlay_name_length;
|
||||
const char* reloc_suffix = "_reloc.o";
|
||||
char* expected_filename;
|
||||
|
||||
/* Relocation file must be the last `include` (so .ovl section is linked last) */
|
||||
if (strstr(segment.includes[segment.includesCount - 1].fpath, reloc_suffix) == NULL) {
|
||||
fprintf(stderr, ERRMSG_START "last include in overlay segment \"%s\" is not a `%s` file\n" ERRMSG_END,
|
||||
overlay_name, reloc_suffix);
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
overlay_name_length = strlen(overlay_name);
|
||||
expected_filename = malloc(overlay_name_length + strlen(reloc_suffix) + 1);
|
||||
strcpy(expected_filename, overlay_name);
|
||||
strcat(expected_filename, reloc_suffix);
|
||||
|
||||
if (strstr(segment.includes[segment.includesCount - 1].fpath, expected_filename) == NULL) {
|
||||
fprintf(stderr, ERRMSG_START "Relocation file \"%s\" should have filename \"%s\"\n" ERRMSG_END,
|
||||
segment.includes[segment.includesCount - 1].fpath, expected_filename);
|
||||
goto error_out;
|
||||
}
|
||||
free(expected_filename);
|
||||
}
|
||||
{
|
||||
int i;
|
||||
/* Skip `_reloc.o` include */
|
||||
for (i = 0; i < segment.includesCount - 1; i++) {
|
||||
printf("%s ", segment.includes[i].fpath);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
if (0) {
|
||||
error_out:
|
||||
exit_status = 1;
|
||||
}
|
||||
|
||||
free_single_segment_elements(&segment);
|
||||
free(spec);
|
||||
|
||||
return exit_status;
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "spec.h"
|
||||
|
||||
#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
static struct Segment* add_segment(struct Segment** segments, int* segments_count) {
|
||||
struct Segment* seg;
|
||||
|
||||
(*segments_count)++;
|
||||
*segments = realloc(*segments, *segments_count * sizeof(**segments));
|
||||
|
||||
seg = &(*segments)[*segments_count - 1];
|
||||
memset(seg, 0, sizeof(*seg));
|
||||
|
||||
seg->align = 16;
|
||||
|
||||
return seg;
|
||||
}
|
||||
|
||||
static char* skip_whitespace(char* str) {
|
||||
while (isspace(*str))
|
||||
str++;
|
||||
return str;
|
||||
}
|
||||
|
||||
// null terminates the current token and returns a pointer to the next token
|
||||
static char* token_split(char* str) {
|
||||
while (!isspace(*str)) {
|
||||
if (*str == 0)
|
||||
return str; // end of string
|
||||
str++;
|
||||
}
|
||||
*str = 0; // terminate token
|
||||
str++;
|
||||
|
||||
return skip_whitespace(str);
|
||||
}
|
||||
|
||||
// null terminates the current line and returns a pointer to the next line
|
||||
static char* line_split(char* str) {
|
||||
while (*str != '\n') {
|
||||
if (*str == 0)
|
||||
return str; // end of string
|
||||
str++;
|
||||
}
|
||||
*str = 0; // terminate line
|
||||
return str + 1;
|
||||
}
|
||||
|
||||
static bool parse_number(const char* str, unsigned int* num) {
|
||||
char* endptr;
|
||||
long int n = strtol(str, &endptr, 0);
|
||||
*num = n;
|
||||
return endptr > str;
|
||||
}
|
||||
|
||||
static bool parse_flags(char* str, unsigned int* flags) {
|
||||
unsigned int f = 0;
|
||||
|
||||
while (str[0] != 0) {
|
||||
char* next = token_split(str);
|
||||
|
||||
if (strcmp(str, "BOOT") == 0)
|
||||
f |= FLAG_BOOT;
|
||||
else if (strcmp(str, "OBJECT") == 0)
|
||||
f |= FLAG_OBJECT;
|
||||
else if (strcmp(str, "RAW") == 0)
|
||||
f |= FLAG_RAW;
|
||||
else if (strcmp(str, "NOLOAD") == 0)
|
||||
f |= FLAG_NOLOAD;
|
||||
else
|
||||
return false;
|
||||
|
||||
str = next;
|
||||
}
|
||||
*flags = f;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_quoted_string(char* str, char** out) {
|
||||
if (*str != '"')
|
||||
return false;
|
||||
|
||||
str++;
|
||||
*out = str;
|
||||
|
||||
while (*str != '"') {
|
||||
if (*str == 0)
|
||||
return false; // unterminated quote
|
||||
str++;
|
||||
}
|
||||
*str = 0;
|
||||
str++;
|
||||
|
||||
str = skip_whitespace(str);
|
||||
if (*str != 0)
|
||||
return false; // garbage after filename
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_pow_of_2(unsigned int n) {
|
||||
return (n & (n - 1)) == 0;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static const char* const stmtNames[] = {
|
||||
[STMT_address] = "address",
|
||||
[STMT_after] = "after",
|
||||
[STMT_align] = "align",
|
||||
[STMT_beginseg] = "beginseg",
|
||||
[STMT_compress] = "compress",
|
||||
[STMT_endseg] = "endseg",
|
||||
[STMT_entry] = "entry",
|
||||
[STMT_flags] = "flags",
|
||||
[STMT_include] = "include",
|
||||
[STMT_include_data_with_rodata] = "include_data_with_rodata",
|
||||
[STMT_name] = "name",
|
||||
[STMT_number] = "number",
|
||||
[STMT_romalign] = "romalign",
|
||||
[STMT_stack] = "stack",
|
||||
[STMT_increment] = "increment",
|
||||
[STMT_pad_text] = "pad_text",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
STMTId get_stmt_id_by_stmt_name(const char* stmtName, int lineNum) {
|
||||
STMTId stmt;
|
||||
|
||||
for (stmt = 0; stmt < ARRAY_COUNT(stmtNames); stmt++) {
|
||||
if (strcmp(stmtName, stmtNames[stmt]) == 0)
|
||||
return stmt;
|
||||
}
|
||||
util_fatal_error("line %i: unknown statement '%s'", lineNum, stmtName);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool parse_segment_statement(struct Segment* currSeg, STMTId stmt, char* args, int lineNum) {
|
||||
// ensure no duplicates (except for 'include' or 'pad_text')
|
||||
if (stmt != STMT_include && stmt != STMT_include_data_with_rodata && stmt != STMT_pad_text &&
|
||||
(currSeg->fields & (1 << stmt)))
|
||||
util_fatal_error("line %i: duplicate '%s' statement", lineNum, stmtNames[stmt]);
|
||||
|
||||
currSeg->fields |= 1 << stmt;
|
||||
currSeg->compress = false;
|
||||
|
||||
// statements valid within a segment definition
|
||||
switch (stmt) {
|
||||
case STMT_beginseg:
|
||||
util_fatal_error("line %i: '%s' inside of a segment definition", lineNum, stmtNames[stmt]);
|
||||
break;
|
||||
|
||||
case STMT_endseg:
|
||||
// verify segment data
|
||||
if (currSeg->name == NULL)
|
||||
util_fatal_error("line %i: no name specified for segment", lineNum);
|
||||
if (currSeg->includesCount == 0)
|
||||
util_fatal_error("line %i: no includes specified for segment", lineNum);
|
||||
return true;
|
||||
break;
|
||||
|
||||
case STMT_name:
|
||||
if (!parse_quoted_string(args, &currSeg->name))
|
||||
util_fatal_error("line %i: invalid name", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_after:
|
||||
if (!parse_quoted_string(args, &currSeg->after))
|
||||
util_fatal_error("line %i: invalid name for 'after'", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_address:
|
||||
if (!parse_number(args, &currSeg->address))
|
||||
util_fatal_error("line %i: expected number after 'address'", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_number:
|
||||
if (!parse_number(args, &currSeg->number))
|
||||
util_fatal_error("line %i: expected number after 'number'", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_flags:
|
||||
if (!parse_flags(args, &currSeg->flags))
|
||||
util_fatal_error("line %i: invalid flags", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_align:
|
||||
if (!parse_number(args, &currSeg->align))
|
||||
util_fatal_error("line %i: expected number after 'align'", lineNum);
|
||||
if (!is_pow_of_2(currSeg->align))
|
||||
util_fatal_error("line %i: alignment is not a power of two", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_romalign:
|
||||
if (!parse_number(args, &currSeg->romalign))
|
||||
util_fatal_error("line %i: expected number after 'romalign'", lineNum);
|
||||
if (!is_pow_of_2(currSeg->romalign))
|
||||
util_fatal_error("line %i: alignment is not a power of two", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_include:
|
||||
case STMT_include_data_with_rodata:
|
||||
currSeg->includesCount++;
|
||||
currSeg->includes = realloc(currSeg->includes, currSeg->includesCount * sizeof(*currSeg->includes));
|
||||
|
||||
if (!parse_quoted_string(args, &currSeg->includes[currSeg->includesCount - 1].fpath))
|
||||
util_fatal_error("line %i: invalid filename", lineNum);
|
||||
|
||||
currSeg->includes[currSeg->includesCount - 1].linkerPadding = 0;
|
||||
currSeg->includes[currSeg->includesCount - 1].dataWithRodata = (stmt == STMT_include_data_with_rodata);
|
||||
break;
|
||||
case STMT_increment:
|
||||
if (!parse_number(args, &currSeg->increment))
|
||||
util_fatal_error("line %i: expected number after 'increment'", lineNum);
|
||||
break;
|
||||
|
||||
case STMT_compress:
|
||||
currSeg->compress = true;
|
||||
break;
|
||||
|
||||
case STMT_pad_text:
|
||||
currSeg->includes[currSeg->includesCount - 1].linkerPadding += 0x10;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "warning: '%s' is not implemented\n", stmtNames[stmt]);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* `segments` should be freed with `free_rom_spec` after use.
|
||||
* Will write to the contents of `spec` to introduce string terminating '\0's.
|
||||
* `segments` also contains pointers to inside `spec`, so `spec` should not be freed before `segments`
|
||||
*/
|
||||
void parse_rom_spec(char* spec, struct Segment** segments, int* segment_count) {
|
||||
int lineNum = 1;
|
||||
char* line = spec;
|
||||
|
||||
struct Segment* currSeg = NULL;
|
||||
|
||||
// iterate over lines
|
||||
while (line[0] != 0) {
|
||||
char* nextLine = line_split(line);
|
||||
char* stmtName;
|
||||
|
||||
if (line[0] != 0) {
|
||||
stmtName = skip_whitespace(line);
|
||||
}
|
||||
|
||||
if (line[0] != 0 && stmtName[0] != 0) {
|
||||
char* args = token_split(stmtName);
|
||||
STMTId stmt = get_stmt_id_by_stmt_name(stmtName, lineNum);
|
||||
|
||||
if (currSeg != NULL) {
|
||||
bool segmentEnded = parse_segment_statement(currSeg, stmt, args, lineNum);
|
||||
if (segmentEnded) {
|
||||
currSeg = NULL;
|
||||
}
|
||||
} else {
|
||||
// commands valid outside a segment definition
|
||||
switch (stmt) {
|
||||
case STMT_beginseg:
|
||||
currSeg = add_segment(segments, segment_count);
|
||||
currSeg->includes = NULL;
|
||||
break;
|
||||
case STMT_endseg:
|
||||
util_fatal_error("line %i: '%s' outside of a segment definition", lineNum, stmtName);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "warning: '%s' is not implemented\n", stmtName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line = nextLine;
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses the spec, looking only for the segment with the name `segmentName`.
|
||||
* Returns true if the segment was found, false otherwise
|
||||
*
|
||||
* @param[out] dstSegment The Segment to be filled. Will only contain the data of the searched segment, or garbage if
|
||||
* the segment was not found. dstSegment must be previously allocated, a stack variable is recommended
|
||||
* @param[in,out] spec A null-terminated string containing the whole spec file. This string will be modified by this
|
||||
* function
|
||||
* @param[in] segmentName The name of the segment being searched
|
||||
*/
|
||||
bool get_single_segment_by_name(struct Segment* dstSegment, char* spec, const char* segmentName) {
|
||||
bool insideSegment = false;
|
||||
int lineNum = 1;
|
||||
char* line = spec;
|
||||
|
||||
memset(dstSegment, 0, sizeof(struct Segment));
|
||||
|
||||
// iterate over lines
|
||||
while (line[0] != '\0') {
|
||||
char* nextLine = line_split(line);
|
||||
char* stmtName = skip_whitespace(line);
|
||||
|
||||
if (stmtName[0] != '\0') {
|
||||
char* args = token_split(stmtName);
|
||||
STMTId stmt = get_stmt_id_by_stmt_name(stmtName, lineNum);
|
||||
|
||||
if (insideSegment) {
|
||||
bool segmentEnded = parse_segment_statement(dstSegment, stmt, args, lineNum);
|
||||
|
||||
if (stmt == STMT_name) {
|
||||
if (strcmp(segmentName, dstSegment->name) != 0) {
|
||||
// Not the segment we are looking for
|
||||
insideSegment = false;
|
||||
}
|
||||
} else if (segmentEnded) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (stmt == STMT_beginseg) {
|
||||
insideSegment = true;
|
||||
if (dstSegment->includes != NULL) {
|
||||
free(dstSegment->includes);
|
||||
}
|
||||
memset(dstSegment, 0, sizeof(struct Segment));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line = nextLine;
|
||||
lineNum++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees the elements of the passed Segment. Will not free the pointer itself
|
||||
*
|
||||
* @param segment
|
||||
*/
|
||||
void free_single_segment_elements(struct Segment* segment) {
|
||||
if (segment->includes != NULL) {
|
||||
free(segment->includes);
|
||||
}
|
||||
}
|
||||
|
||||
void free_rom_spec(struct Segment* segments, int segment_count) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < segment_count; i++) {
|
||||
if (segments[i].includes != NULL)
|
||||
free(segments[i].includes);
|
||||
}
|
||||
free(segments);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef SPEC_H
|
||||
#define SPEC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
STMT_address,
|
||||
STMT_after,
|
||||
STMT_align,
|
||||
STMT_beginseg,
|
||||
STMT_compress,
|
||||
STMT_endseg,
|
||||
STMT_entry,
|
||||
STMT_flags,
|
||||
STMT_include,
|
||||
STMT_include_data_with_rodata,
|
||||
STMT_name,
|
||||
STMT_number,
|
||||
STMT_romalign,
|
||||
STMT_stack,
|
||||
STMT_increment,
|
||||
STMT_pad_text,
|
||||
} STMTId;
|
||||
|
||||
enum {
|
||||
FLAG_BOOT = (1 << 0),
|
||||
FLAG_OBJECT = (1 << 1),
|
||||
FLAG_RAW = (1 << 2),
|
||||
FLAG_NOLOAD = (1 << 3),
|
||||
};
|
||||
|
||||
struct Include {
|
||||
char* fpath;
|
||||
int linkerPadding;
|
||||
uint8_t dataWithRodata;
|
||||
};
|
||||
|
||||
typedef struct Segment {
|
||||
uint32_t fields;
|
||||
char* name;
|
||||
char* after;
|
||||
uint32_t flags;
|
||||
uint32_t address;
|
||||
uint32_t stack;
|
||||
uint32_t align;
|
||||
uint32_t romalign;
|
||||
uint32_t increment;
|
||||
uint32_t entry;
|
||||
uint32_t number;
|
||||
struct Include* includes;
|
||||
int includesCount;
|
||||
bool compress;
|
||||
} Segment;
|
||||
|
||||
void parse_rom_spec(char* spec, struct Segment** segments, int* segment_count);
|
||||
|
||||
bool get_single_segment_by_name(struct Segment* dstSegment, char *spec, const char *segmentName);
|
||||
|
||||
void free_single_segment_elements(struct Segment *segment);
|
||||
|
||||
void free_rom_spec(struct Segment* segments, int segment_count);
|
||||
|
||||
#endif
|
||||
@@ -12,18 +12,18 @@ void util_fatal_error(const char *msgfmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
fputs("error: ", stderr);
|
||||
fputs(ERRMSG_START, stderr);
|
||||
|
||||
va_start(args, msgfmt);
|
||||
vfprintf(stderr, msgfmt, args);
|
||||
va_end(args);
|
||||
|
||||
fputc('\n', stderr);
|
||||
fputs(ERRMSG_END "\n", stderr);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// reads a whole file into memory, and returns a pointer to the data
|
||||
// reads a whole file into memory, and returns a malloc'd pointer to the data.
|
||||
void *util_read_whole_file(const char *filename, size_t *pSize)
|
||||
{
|
||||
FILE *file = fopen(filename, "rb");
|
||||
@@ -59,13 +59,13 @@ void *util_read_whole_file(const char *filename, size_t *pSize)
|
||||
void util_write_whole_file(const char *filename, const void *data, size_t size)
|
||||
{
|
||||
FILE *file = fopen(filename, "wb");
|
||||
|
||||
|
||||
if (file == NULL)
|
||||
util_fatal_error("failed to open file '%s' for writing: %s", filename, strerror(errno));
|
||||
|
||||
if (fwrite(data, size, 1, file) != 1)
|
||||
util_fatal_error("error writing to file '%s': %s", filename, strerror(errno));
|
||||
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -1,8 +1,15 @@
|
||||
#ifndef _UTIL_H_
|
||||
#define _UTIL_H_
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define ERRMSG_START "\x1b[91merror\x1b[97m: "
|
||||
#define ERRMSG_END "\x1b[0m"
|
||||
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
__attribute__((format(printf, 1, 2), noreturn))
|
||||
#endif
|
||||
void util_fatal_error(const char *msgfmt, ...);
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
IndentWidth: 4
|
||||
Language: Cpp
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
PointerAlignment: Left
|
||||
BreakBeforeBraces: Attach
|
||||
SpaceAfterCStyleCast: false
|
||||
Cpp11BracedListStyle: false
|
||||
IndentCaseLabels: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignOperands: true
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignTrailingComments: true
|
||||
SortIncludes: false
|
||||
@@ -0,0 +1,56 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# Custom
|
||||
build/
|
||||
.vscode
|
||||
@@ -0,0 +1,12 @@
|
||||
; DO NOT EDIT (unless you know what you are doing)
|
||||
;
|
||||
; This subdirectory is a git "subrepo", and this file is maintained by the
|
||||
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
|
||||
;
|
||||
[subrepo]
|
||||
remote = git@github.com:EllipticEllipsis/fado.git
|
||||
branch = master
|
||||
commit = f7efb10a9a65f27e9ccad7ce270234f20d386ac9
|
||||
parent = 90cfafec47fe4b73ad9009e9501e147e86025aa6
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
DEBUG ?= 0
|
||||
LLD ?= 0
|
||||
ASAN ?= 0
|
||||
EXPERIMENTAL?= 0
|
||||
|
||||
ELF := fado.elf
|
||||
|
||||
CC := $(shell ./find_program.sh gcc clang clang-[0-9][0-9] clang-[0-9])
|
||||
LD := $(shell ./find_program.sh ld ld.lld ld.lld-*)
|
||||
INC := -I include -I lib
|
||||
WARNINGS := -Wall -Wextra -Wpedantic -Wshadow -Werror=implicit-function-declaration -Wvla -Wno-unused-function
|
||||
CFLAGS := -std=c11
|
||||
LDFLAGS :=
|
||||
|
||||
ifeq ($(DEBUG),0)
|
||||
OPTFLAGS := -O2
|
||||
CFLAGS += -Werror
|
||||
else
|
||||
OPTFLAGS := -O0 -g3 -DDEBUG_ON
|
||||
endif
|
||||
|
||||
ifneq ($(ASAN),0)
|
||||
CFLAGS += -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=undefined
|
||||
endif
|
||||
|
||||
ifneq ($(LLD),0)
|
||||
LDFLAGS += -fuse-ld=lld
|
||||
else
|
||||
ifneq ($(LD),ld)
|
||||
LDFLAGS += -fuse-ld=lld
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(EXPERIMENTAL),0)
|
||||
CFLAGS += -DEXPERIMENTAL
|
||||
endif
|
||||
|
||||
# GCC is too stupid to be trusted with these warnings
|
||||
ifeq ($(CC),gcc)
|
||||
WARNINGS += -Wno-implicit-fallthrough -Wno-maybe-uninitialized
|
||||
endif
|
||||
|
||||
SRC_DIRS := $(shell find src -type d)
|
||||
C_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c))
|
||||
H_FILES := $(foreach dir,$(INC),$(wildcard $(dir)/*.h))
|
||||
O_FILES := $(foreach f,$(C_FILES:.c=.o),build/$f)
|
||||
|
||||
LIB_DIRS := $(shell find lib -type d)
|
||||
# exclude test file since we don't want it
|
||||
C_LIB_FILES := $(filter-out lib/vc_vector/vc_vector_test.c, $(foreach dir,$(LIB_DIRS),$(wildcard $(dir)/*.c)))
|
||||
O_LIB_FILES := $(foreach f,$(C_LIB_FILES:.c=.o),build/$f)
|
||||
|
||||
# Main targets
|
||||
all: $(ELF)
|
||||
|
||||
clean:
|
||||
$(RM) -r build $(ELF)
|
||||
|
||||
format:
|
||||
clang-format-11 -i $(C_FILES) $(H_FILES) lib/fairy/*
|
||||
|
||||
.PHONY: all clean format
|
||||
|
||||
# create build directories
|
||||
$(shell mkdir -p $(foreach dir,$(SRC_DIRS),build/$(dir)) $(foreach dir,$(LIB_DIRS),build/$(dir)))
|
||||
|
||||
$(ELF): $(O_FILES) $(O_LIB_FILES)
|
||||
$(CC) $(INC) $(WARNINGS) $(CFLAGS) $(OPTFLAGS) $(LDFLAGS) -o $@ $^
|
||||
|
||||
build/%.o: %.c $(H_FILES)
|
||||
$(CC) -c $(INC) $(WARNINGS) $(CFLAGS) $(OPTFLAGS) -o $@ $<
|
||||
|
||||
build/lib/%.o: lib/%.c
|
||||
$(CC) -c $(INC) $(WARNINGS) $(CFLAGS) $(OPTFLAGS) -o $@ $<
|
||||
@@ -0,0 +1,77 @@
|
||||
# fado
|
||||
*Fairy-Assisted (relocations for) Decomplied Overlays*
|
||||
<!-- Nice backronym... -->
|
||||
|
||||
Contains
|
||||
- **Fairy** a library for reading relocatable MIPS ELF object files (big-endian, suitable for Nintendo 64 games)
|
||||
- **Fado** a program for generating the `.ovl`/relocation section for Zelda64 overlay files
|
||||
- **Mido** an automatic dependency file generator
|
||||
|
||||
Compatible with both IDO and GCC (although [see below](N_B)). Both ordinary MIPS REL sections and RELA sections are now supported.
|
||||
|
||||
Output format is the standard "Zelda64" .ovl section, with the relocs divided by section, as used by
|
||||
- *The Legend of Zelda: Ocarina of Time* (all Nintendo 64/Gamecube/iQue releases)
|
||||
- *The Legend of Zelda: Majora's Mask* (all Nintendo 64/Gamecube releases)
|
||||
|
||||
In theory it will also work for other Nintendo 64 games that use this system, such as *Yoshi's Story*, but has yet to be tested with these.
|
||||
|
||||
|
||||
## Explanation
|
||||
|
||||
The overlay relocation sections used by Zelda64 is described [here](z64_relocation_section_format.md). Fado will produce a `.ovl` section compatible with this format, although as noted there, some compilers need persuasion to produce compatible objects.
|
||||
|
||||
|
||||
## How to use
|
||||
|
||||
Compile by running `make`.
|
||||
|
||||
A standalone invocation of Fado would look something like
|
||||
|
||||
```sh
|
||||
./fado.elf z_en_hs2.o -n ovl_En_Hs2 -o ovl_En_Hs2_reloc.s
|
||||
```
|
||||
This takes as input the compiled object file from the C file (e.g. [this one](https://github.com/zeldaret/oot/blob/eadc477187888e1ae078d021b4a00b1366f0c9a4/src/overlays/actors/ovl_En_Hs2/z_en_hs2.c)), the name of the overlay (`ovl_En_Hs2`) and will output an assembly file `ovl_En_Hs2_reloc.s` containing the relocation section. An example output is included in the repo [here](ovl_En_Hs_reloc.s). Fado will print information from the object file to assist with debugging, by splitting relocs by section, and for each, printing the type, offset, and associated symbol (or section if static):
|
||||
|
||||
```mips
|
||||
# TEXT RELOCS
|
||||
.word 0x45000084 # R_MIPS_HI16 0x000084 .data
|
||||
.word 0x4600008C # R_MIPS_LO16 0x00008C .data
|
||||
.word 0x450000B4 # R_MIPS_HI16 0x0000B4 .rodata
|
||||
.word 0x460000BC # R_MIPS_LO16 0x0000BC .rodata
|
||||
.word 0x450000C0 # R_MIPS_HI16 0x0000C0 func_80A6F1A4
|
||||
.word 0x460000C4 # R_MIPS_LO16 0x0000C4 func_80A6F1A4
|
||||
```
|
||||
|
||||
If invoking in a makefile, you will probably want to generate these from a predefined filelist, and with the appropriate dependencies. [The Ocarina of Time decomp repository](http://github.com/zeldaret/oot) contains an example of how to do this using a supplementary program to parse the `spec` format.
|
||||
|
||||
More information can be obtained by running
|
||||
|
||||
```sh
|
||||
./fado.elf --help
|
||||
```
|
||||
|
||||
which contains information on the various options, such as automatic dependency file generation, etc.
|
||||
|
||||
|
||||
## N.B.
|
||||
|
||||
- Fado expects the linker script to output symbols for the section sizes, and for them to be declared separately, in the format
|
||||
|
||||
```
|
||||
_SEGMENTNAMESegmentSECTIONSize
|
||||
```
|
||||
|
||||
e.g.
|
||||
|
||||
```
|
||||
_ovl_En_Hs2SegmentTextSize
|
||||
```
|
||||
|
||||
etc.
|
||||
|
||||
- By default Fado expects sections to be 0x10-aligned, as is usual for IDO. Some versions of GCC like to align sections to smaller widths, which Fado will handle appropriately, but the linker script must also address this, and at least the default settings seem unable to size the sections correctly due ot placing `fill`s in the wrong places. For now it is recommended to manually align sections to 0x10 if the compiler does not automatically.
|
||||
- The experimental flag `--alignment`/`-a` can be passed to Fado, and it will use the alignment declared by each section in the elf file instead of padding them to 0x10 bytes, It should be noted this option has not been fully tested because currently we don't have any linker script tool that can properly address the incorrect placing of `fill`s. Fado must be rebuilt passing `EXPERIMENTAL=1` to be able to use this flag.
|
||||
|
||||
- To prevent GCC producing non-compliant HI/LOs, you must pass *both* of the following compiler flags: `-mno-explicit-relocs -mno-split-addresses`. See [here](z64_relocation_section_format.md#hilo) for more details.
|
||||
|
||||
- It is recommended, though not strictly required, that `-fno-merge-constants` is used for GCC, to avoid unpredictable section sizes, and comply with the Zelda64 relocation format's expectation of at most one rodata section. See [here](z64_relocation_section_format.md#rodata) for more details.
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for i in "${@:2}"
|
||||
do
|
||||
RESULT=$(IFS=:;find $PATH -maxdepth 1 -name "$i" -print -quit 2> /dev/null | grep -o '[^/]*$')
|
||||
COUNT=$(echo "$RESULT" | wc -c)
|
||||
|
||||
if [ $COUNT -gt 1 ]
|
||||
then
|
||||
echo $RESULT
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo $1
|
||||
exit 0
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Copyright (C) 2021 Elliptic Ellipsis */
|
||||
/* SPDX-License-Identifier: AGPL-3.0-only */
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void Fado_Relocs(FILE* outputFile, int inputFilesCount, FILE** inputFiles, const char* ovlName);
|
||||
// void Fado_WriteRelocFile(FILE* outputFile, FILE** inputFiles, int inputFilesCount);
|
||||
@@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2021 Elliptic Ellipsis */
|
||||
/* SPDX-License-Identifier: AGPL-3.0-only */
|
||||
#pragma once
|
||||
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
struct option longOpt;
|
||||
char* helpArg;
|
||||
char* helpMsg;
|
||||
} OptInfo;
|
||||
|
||||
typedef struct {
|
||||
char* helpArg;
|
||||
char* helpMsg;
|
||||
} PosArgInfo;
|
||||
|
||||
/* Formatting sizes used by Help_PrintHelp. Change them before calling Help_PrintHelp to use custom values */
|
||||
extern size_t helpTextWidth;
|
||||
extern size_t helpDtIndent;
|
||||
extern size_t helpDdIndent;
|
||||
|
||||
void Help_PrintHelp(const char* prologue, size_t posArgCount, const PosArgInfo* posArgInfo, size_t optCount,
|
||||
const OptInfo* optInfo, const char* epilogue);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user