mirror of
https://github.com/PrimeDecomp/prime.git
synced 2025-12-09 21:07:41 +00:00
@@ -1,15 +0,0 @@
|
||||
CC := gcc
|
||||
CFLAGS := -O3 -Wall -s
|
||||
|
||||
default: all
|
||||
|
||||
all: elf2dol metroidbuildinfo
|
||||
|
||||
elf2dol: elf2dol.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
metroidbuildinfo: metroidbuildinfo.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
$(RM) elf2dol metroidbuildinfo
|
||||
@@ -1,224 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
################################################################################
|
||||
# Description #
|
||||
################################################################################
|
||||
# calcprogress: Used to calculate the progress of the Metroid Prime decomp. #
|
||||
# Prints to stdout for now, but eventually will have some form of storage, #
|
||||
# i.e. CSV, so that it can be used for a webpage display. #
|
||||
# #
|
||||
# Usage: No arguments needed #
|
||||
################################################################################
|
||||
|
||||
|
||||
|
||||
|
||||
###############################################
|
||||
# #
|
||||
# Imports #
|
||||
# #
|
||||
###############################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
import re
|
||||
import math
|
||||
import argparse
|
||||
import json
|
||||
|
||||
###############################################
|
||||
# #
|
||||
# Constants #
|
||||
# #
|
||||
###############################################
|
||||
|
||||
MEM1_HI = 0x81200000
|
||||
MEM1_LO = 0x80004000
|
||||
|
||||
MW_WII_SYMBOL_REGEX = r"^\s*"\
|
||||
r"(?P<SectOfs>\w{8})\s+"\
|
||||
r"(?P<Size>\w{6})\s+"\
|
||||
r"(?P<VirtOfs>\w{8})\s+"\
|
||||
r"(?P<FileOfs>\w{8})\s+"\
|
||||
r"(\w{1,2})\s+"\
|
||||
r"(?P<Symbol>[0-9A-Za-z_<>$@.*]*)\s*"\
|
||||
r"(?P<Object>\S*)"
|
||||
|
||||
MW_GC_SYMBOL_REGEX = r"^\s*"\
|
||||
r"(?P<SectOfs>\w{8})\s+"\
|
||||
r"(?P<Size>\w{6})\s+"\
|
||||
r"(?P<VirtOfs>\w{8})\s+"\
|
||||
r"(\w{1,2})\s+"\
|
||||
r"(?P<Symbol>[0-9A-Za-z_<>$@.*]*)\s*"\
|
||||
r"(?P<Object>\S*)"
|
||||
|
||||
REGEX_TO_USE = MW_GC_SYMBOL_REGEX
|
||||
|
||||
TEXT_SECTIONS = ["init", "text"]
|
||||
DATA_SECTIONS = [
|
||||
"rodata", "data", "bss", "sdata", "sbss", "sdata2", "sbss2",
|
||||
"ctors", "_ctors", "dtors", "ctors$99", "_ctors$99", "ctors$00", "dtors$99",
|
||||
"extab", "extabindex", "extab_", "extabindex_", "_extab", "_exidx"
|
||||
]
|
||||
|
||||
# DOL info
|
||||
TEXT_SECTION_COUNT = 7
|
||||
DATA_SECTION_COUNT = 11
|
||||
|
||||
SECTION_TEXT = 0
|
||||
SECTION_DATA = 1
|
||||
|
||||
# Progress flavor
|
||||
codeFrac = 1499 # total code "item" amount
|
||||
dataFrac = 250 # total data "item" amount
|
||||
codeItem = "energy" # code flavor item
|
||||
dataItem = "missiles" # data flavor item
|
||||
|
||||
###############################################
|
||||
# #
|
||||
# Entrypoint #
|
||||
# #
|
||||
###############################################
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Calculate progress.")
|
||||
parser.add_argument("dol", help="Path to DOL")
|
||||
parser.add_argument("map", help="Path to map")
|
||||
parser.add_argument("-o", "--output", help="JSON output file")
|
||||
args = parser.parse_args()
|
||||
|
||||
# HACK: Check asm or src in obj_file.mk
|
||||
# to avoid counting .comm/.lcomm as decompiled
|
||||
asm_objs = []
|
||||
with open('obj_files.mk', 'r') as file:
|
||||
for line in file:
|
||||
if "asm/" in line:
|
||||
asm_objs.append(line.strip().rsplit('/', 1)[-1].rstrip('\\'))
|
||||
|
||||
# Sum up DOL section sizes
|
||||
dol_handle = open(args.dol, "rb")
|
||||
|
||||
# Seek to virtual addresses
|
||||
dol_handle.seek(0x48)
|
||||
|
||||
# Read virtual addresses
|
||||
text_starts = list()
|
||||
for i in range(TEXT_SECTION_COUNT):
|
||||
text_starts.append(int.from_bytes(dol_handle.read(4), byteorder='big'))
|
||||
data_starts = list()
|
||||
for i in range(DATA_SECTION_COUNT):
|
||||
data_starts.append(int.from_bytes(dol_handle.read(4), byteorder='big'))
|
||||
|
||||
# Read lengths
|
||||
text_sizes = list()
|
||||
for i in range(TEXT_SECTION_COUNT):
|
||||
text_sizes.append(int.from_bytes(dol_handle.read(4), byteorder='big'))
|
||||
data_sizes = list()
|
||||
for i in range(DATA_SECTION_COUNT):
|
||||
data_sizes.append(int.from_bytes(dol_handle.read(4), byteorder='big'))
|
||||
|
||||
|
||||
|
||||
# BSS address + length
|
||||
bss_start = int.from_bytes(dol_handle.read(4), byteorder='big')
|
||||
bss_size = int.from_bytes(dol_handle.read(4), byteorder='big')
|
||||
bss_end = bss_start + bss_size
|
||||
|
||||
|
||||
dol_code_size = 0
|
||||
dol_data_size = 0
|
||||
for i in range(DATA_SECTION_COUNT):
|
||||
# Ignore sections inside BSS
|
||||
if (data_starts[i] >= bss_start) and (data_starts[i] + data_sizes[i] <= bss_end): continue
|
||||
dol_data_size += data_sizes[i]
|
||||
|
||||
dol_data_size += bss_size
|
||||
|
||||
for i in text_sizes:
|
||||
dol_code_size += i
|
||||
|
||||
# Open map file
|
||||
mapfile = open(args.map, "r")
|
||||
symbols = mapfile.readlines()
|
||||
|
||||
decomp_code_size = 0
|
||||
decomp_data_size = 0
|
||||
section_type = None
|
||||
|
||||
# Find first section
|
||||
first_section = 0
|
||||
while (symbols[first_section].startswith(".") == False and "section layout" not in symbols[first_section]): first_section += 1
|
||||
assert(first_section < len(symbols)), "Map file contains no sections!!!"
|
||||
|
||||
cur_object = None
|
||||
cur_size = 0
|
||||
j = 0
|
||||
for i in range(first_section, len(symbols)):
|
||||
# New section
|
||||
if (symbols[i].startswith(".") == True or "section layout" in symbols[i]):
|
||||
# Grab section name (i.e. ".init section layout" -> "init")
|
||||
sectionName = re.search(r"\.*(?P<Name>\w+)\s", symbols[i]).group("Name")
|
||||
# Determine type of section
|
||||
section_type = SECTION_DATA if (sectionName in DATA_SECTIONS) else SECTION_TEXT
|
||||
# Parse symbols until we hit the next section declaration
|
||||
else:
|
||||
if "UNUSED" in symbols[i]: continue
|
||||
if "entry of" in symbols[i]:
|
||||
if j == i - 1:
|
||||
if section_type == SECTION_TEXT:
|
||||
decomp_code_size -= cur_size
|
||||
else:
|
||||
decomp_data_size -= cur_size
|
||||
cur_size = 0
|
||||
#print(f"Line* {j}: {symbols[j]}")
|
||||
#print(f"Line {i}: {symbols[i]}")
|
||||
continue
|
||||
assert(section_type != None), f"Symbol found outside of a section!!!\n{symbols[i]}"
|
||||
match_obj = re.search(REGEX_TO_USE, symbols[i])
|
||||
# Should be a symbol in ASM (so we discard it)
|
||||
if (match_obj == None):
|
||||
#print(f"Line {i}: {symbols[i]}")
|
||||
continue
|
||||
# Has the object file changed?
|
||||
last_object = cur_object
|
||||
cur_object = match_obj.group("Object").strip()
|
||||
if last_object != cur_object or cur_object in asm_objs: continue
|
||||
# Is the symbol a file-wide section?
|
||||
symb = match_obj.group("Symbol")
|
||||
if (symb.startswith("*fill*")) or (symb.startswith(".") and symb[1:] in TEXT_SECTIONS or symb[1:] in DATA_SECTIONS): continue
|
||||
# For sections that don't start with "."
|
||||
if (symb in DATA_SECTIONS): continue
|
||||
# If not, we accumulate the file size
|
||||
cur_size = int(match_obj.group("Size"), 16)
|
||||
j = i
|
||||
if (section_type == SECTION_TEXT):
|
||||
decomp_code_size += cur_size
|
||||
else:
|
||||
decomp_data_size += cur_size
|
||||
|
||||
# Calculate percentages
|
||||
codeCompletionPcnt = (decomp_code_size / dol_code_size) # code completion percent
|
||||
dataCompletionPcnt = (decomp_data_size / dol_data_size) # data completion percent
|
||||
bytesPerCodeItem = dol_code_size / codeFrac # bytes per code item
|
||||
bytesPerDataItem = dol_data_size / dataFrac # bytes per data item
|
||||
|
||||
codeCount = math.floor(decomp_code_size / bytesPerCodeItem)
|
||||
dataCount = math.floor(decomp_data_size / bytesPerDataItem)
|
||||
|
||||
print("Progress:")
|
||||
print(f"\tCode sections: {decomp_code_size} / {dol_code_size}\tbytes in src ({codeCompletionPcnt:%})")
|
||||
print(f"\tData sections: {decomp_data_size} / {dol_data_size}\tbytes in src ({dataCompletionPcnt:%})")
|
||||
print("\nYou have {} out of {} {} and collected {} out of {} {}.".format(codeCount, codeFrac, codeItem, dataCount, dataFrac, dataItem))
|
||||
|
||||
if args.output:
|
||||
data = {
|
||||
"dol": {
|
||||
"code": decomp_code_size,
|
||||
"code/total": dol_code_size,
|
||||
"data": decomp_data_size,
|
||||
"data/total": dol_data_size,
|
||||
}
|
||||
}
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(data, f)
|
||||
@@ -8,50 +8,53 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
root_dir = os.path.abspath(os.path.join(script_dir, ".."))
|
||||
src_dir = os.path.join(root_dir, "src")
|
||||
include_dirs = [
|
||||
os.path.join(root_dir, "include"),
|
||||
os.path.join(root_dir, "libc"),
|
||||
os.path.join(root_dir, "include"),
|
||||
os.path.join(root_dir, "libc"),
|
||||
]
|
||||
|
||||
include_pattern = re.compile(r'^#include\s*[<"](.+?)[>"]$')
|
||||
guard_pattern = re.compile(r'^#ifndef\s+(.*)$')
|
||||
guard_pattern = re.compile(r"^#ifndef\s+(.*)$")
|
||||
|
||||
defines = set()
|
||||
|
||||
|
||||
def import_h_file(in_file, r_path) -> str:
|
||||
rel_path = os.path.join(root_dir, r_path, in_file)
|
||||
if os.path.exists(rel_path):
|
||||
return import_c_file(rel_path)
|
||||
return import_c_file(rel_path)
|
||||
for include_dir in include_dirs:
|
||||
inc_path = os.path.join(include_dir, in_file)
|
||||
if os.path.exists(inc_path):
|
||||
return import_c_file(inc_path)
|
||||
inc_path = os.path.join(include_dir, in_file)
|
||||
if os.path.exists(inc_path):
|
||||
return import_c_file(inc_path)
|
||||
else:
|
||||
print("Failed to locate", in_file)
|
||||
return ""
|
||||
print("Failed to locate", in_file)
|
||||
return ""
|
||||
|
||||
|
||||
def import_c_file(in_file) -> str:
|
||||
in_file = os.path.relpath(in_file, root_dir)
|
||||
out_text = ''
|
||||
out_text = ""
|
||||
|
||||
with open(in_file) as file:
|
||||
for idx, line in enumerate(file):
|
||||
guard_match = guard_pattern.match(line.strip())
|
||||
if idx == 0:
|
||||
if guard_match:
|
||||
if guard_match[1] in defines:
|
||||
break
|
||||
defines.add(guard_match[1])
|
||||
print("Processing file", in_file)
|
||||
include_match = include_pattern.match(line.strip())
|
||||
if include_match:
|
||||
out_text += f"/* \"{in_file}\" line {idx} \"{include_match[1]}\" */\n"
|
||||
out_text += import_h_file(include_match[1], os.path.dirname(in_file))
|
||||
out_text += f"/* end \"{include_match[1]}\" */\n"
|
||||
else:
|
||||
out_text += line
|
||||
for idx, line in enumerate(file):
|
||||
guard_match = guard_pattern.match(line.strip())
|
||||
if idx == 0:
|
||||
if guard_match:
|
||||
if guard_match[1] in defines:
|
||||
break
|
||||
defines.add(guard_match[1])
|
||||
print("Processing file", in_file)
|
||||
include_match = include_pattern.match(line.strip())
|
||||
if include_match:
|
||||
out_text += f'/* "{in_file}" line {idx} "{include_match[1]}" */\n'
|
||||
out_text += import_h_file(include_match[1], os.path.dirname(in_file))
|
||||
out_text += f'/* end "{include_match[1]}" */\n'
|
||||
else:
|
||||
out_text += line
|
||||
|
||||
return out_text
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="""Create a context file which can be used for decomp.me"""
|
||||
@@ -69,4 +72,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
from sys import argv
|
||||
|
||||
def nextU32(f):
|
||||
dat = f.read(4)
|
||||
return int.from_bytes(dat, 'big')
|
||||
|
||||
with open(argv[1], 'rb') as dol:
|
||||
offsets = [nextU32(dol) for i in range(18)]
|
||||
addresses = [nextU32(dol) for i in range(18)]
|
||||
sizes = [nextU32(dol) for i in range(18)]
|
||||
|
||||
target = int(argv[2], 16)
|
||||
|
||||
for i in range(0, 0x18):
|
||||
offset = offsets[i]
|
||||
size = sizes[i]
|
||||
if offset <= target < offset + size:
|
||||
section = i
|
||||
delta = target - offset
|
||||
break
|
||||
|
||||
print(hex(addresses[section] + delta))
|
||||
40
tools/download_dtk.py
Normal file
40
tools/download_dtk.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import argparse
|
||||
import urllib.request
|
||||
import os
|
||||
import stat
|
||||
from pathlib import Path
|
||||
|
||||
REPO = "https://github.com/encounter/decomp-toolkit"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("tag_file", help="file containing GitHub tag")
|
||||
parser.add_argument("output", type=Path, help="output file path")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.tag_file, "r") as f:
|
||||
tag = f.readline().rstrip()
|
||||
|
||||
uname = os.uname()
|
||||
suffix = ""
|
||||
platform = uname.sysname.lower()
|
||||
if platform == "darwin":
|
||||
platform = "macos"
|
||||
elif platform == "windows":
|
||||
suffix = ".exe"
|
||||
arch = uname.machine.lower()
|
||||
if arch == "amd64":
|
||||
arch = "x86_64"
|
||||
|
||||
url = f"{REPO}/releases/download/{tag}/dtk-{platform}-{arch}{suffix}"
|
||||
output = args.output
|
||||
# print(f"Downloading {url} to {output}")
|
||||
urllib.request.urlretrieve(url, output)
|
||||
|
||||
st = os.stat(output)
|
||||
os.chmod(output, st.st_mode | stat.S_IEXEC)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
504
tools/elf2dol.c
504
tools/elf2dol.c
@@ -1,504 +0,0 @@
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifndef MAX
|
||||
//! Get the maximum of two values
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
//! Get the minimum of two values
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
#define EI_NIDENT 16
|
||||
|
||||
typedef struct {
|
||||
unsigned char e_ident[EI_NIDENT];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint32_t e_entry;
|
||||
uint32_t e_phoff;
|
||||
uint32_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
} Elf32_Ehdr;
|
||||
|
||||
#define EI_CLASS 4
|
||||
#define EI_DATA 5
|
||||
#define EI_VERSION 6
|
||||
#define EI_PAD 7
|
||||
#define EI_NIDENT 16
|
||||
|
||||
#define ELFCLASS32 1
|
||||
#define ELFDATA2MSB 2
|
||||
#define EV_CURRENT 1
|
||||
|
||||
#define ET_EXEC 2
|
||||
#define EM_PPC 20
|
||||
|
||||
typedef struct {
|
||||
uint32_t p_type;
|
||||
uint32_t p_offset;
|
||||
uint32_t p_vaddr;
|
||||
uint32_t p_paddr;
|
||||
uint32_t p_filesz;
|
||||
uint32_t p_memsz;
|
||||
uint32_t p_flags;
|
||||
uint32_t p_align;
|
||||
} Elf32_Phdr;
|
||||
|
||||
#define PT_LOAD 1
|
||||
#define PF_R 4
|
||||
#define PF_W 2
|
||||
#define PF_X 1
|
||||
|
||||
int verbosity = 0;
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
|
||||
#define swap32(x) (x)
|
||||
#define swap16(x) (x)
|
||||
|
||||
#else
|
||||
|
||||
static inline uint32_t swap32(uint32_t v) {
|
||||
return (v >> 24) | ((v >> 8) & 0x0000FF00) | ((v << 8) & 0x00FF0000) |
|
||||
(v << 24);
|
||||
}
|
||||
|
||||
static inline uint16_t swap16(uint16_t v) { return (v >> 8) | (v << 8); }
|
||||
|
||||
#endif /* BIG_ENDIAN */
|
||||
|
||||
typedef struct {
|
||||
|
||||
uint32_t text_off[7];
|
||||
uint32_t data_off[11];
|
||||
uint32_t text_addr[7];
|
||||
uint32_t data_addr[11];
|
||||
uint32_t text_size[7];
|
||||
uint32_t data_size[11];
|
||||
uint32_t bss_addr;
|
||||
uint32_t bss_size;
|
||||
uint32_t entry;
|
||||
uint32_t pad[7];
|
||||
} DOL_hdr;
|
||||
|
||||
#define HAVE_BSS 1
|
||||
|
||||
#define MAX_TEXT_SEGMENTS 7
|
||||
#define MAX_DATA_SEGMENTS 11
|
||||
|
||||
#define DOL_ALIGNMENT 32
|
||||
|
||||
#define DOL_ALIGN(x) (((x) + DOL_ALIGNMENT - 1) & ~(DOL_ALIGNMENT - 1))
|
||||
|
||||
typedef struct {
|
||||
DOL_hdr header;
|
||||
int text_cnt;
|
||||
int data_cnt;
|
||||
uint32_t text_elf_off[7];
|
||||
uint32_t data_elf_off[11];
|
||||
uint32_t flags;
|
||||
FILE* elf;
|
||||
} DOL_map;
|
||||
|
||||
void usage(const char* name) {
|
||||
fprintf(stderr, "Usage: %s [-h] [-v] [--] elf-file dol-file\n", name);
|
||||
fprintf(stderr, " Convert an ELF file to a DOL file (by segments)\n");
|
||||
fprintf(stderr, " Options:\n");
|
||||
fprintf(stderr, " -h Show this help\n");
|
||||
fprintf(stderr, " -v Be more verbose (twice for even more)\n");
|
||||
}
|
||||
|
||||
#define die(x) \
|
||||
{ \
|
||||
fprintf(stderr, x "\n"); \
|
||||
exit(1); \
|
||||
}
|
||||
#define perrordie(x) \
|
||||
{ \
|
||||
perror(x); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
void ferrordie(FILE* f, const char* str) {
|
||||
if (ferror(f)) {
|
||||
fprintf(stderr, "Error while ");
|
||||
perrordie(str);
|
||||
} else if (feof(f)) {
|
||||
fprintf(stderr, "EOF while %s\n", str);
|
||||
exit(1);
|
||||
} else {
|
||||
fprintf(stderr, "Unknown error while %s\n", str);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void add_bss(DOL_map* map, uint32_t paddr, uint32_t memsz) {
|
||||
if (map->flags & HAVE_BSS) {
|
||||
uint32_t curr_start = swap32(map->header.bss_addr);
|
||||
uint32_t curr_size = swap32(map->header.bss_size);
|
||||
if (paddr < curr_start)
|
||||
map->header.bss_addr = swap32(paddr);
|
||||
// Total BSS size should be the end of the last bss section minus the
|
||||
// start of the first bss section.
|
||||
if (paddr + memsz > curr_start + curr_size)
|
||||
map->header.bss_size = swap32(paddr + memsz - curr_start);
|
||||
} else {
|
||||
map->header.bss_addr = swap32(paddr);
|
||||
map->header.bss_size = swap32(memsz);
|
||||
map->flags |= HAVE_BSS;
|
||||
}
|
||||
}
|
||||
|
||||
void read_elf_segments(DOL_map* map, const char* elf) {
|
||||
int read, i;
|
||||
Elf32_Ehdr ehdr;
|
||||
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Reading ELF file...\n");
|
||||
|
||||
map->elf = fopen(elf, "rb");
|
||||
if (!map->elf)
|
||||
perrordie("Could not open ELF file");
|
||||
|
||||
read = fread(&ehdr, sizeof(ehdr), 1, map->elf);
|
||||
if (read != 1)
|
||||
ferrordie(map->elf, "reading ELF header");
|
||||
|
||||
if (memcmp(&ehdr.e_ident[0], "\177ELF", 4))
|
||||
die("Invalid ELF header");
|
||||
if (ehdr.e_ident[EI_CLASS] != ELFCLASS32)
|
||||
die("Invalid ELF class");
|
||||
if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB)
|
||||
die("Invalid ELF byte order");
|
||||
if (ehdr.e_ident[EI_VERSION] != EV_CURRENT)
|
||||
die("Invalid ELF ident version");
|
||||
if (swap32(ehdr.e_version) != EV_CURRENT)
|
||||
die("Invalid ELF version");
|
||||
if (swap16(ehdr.e_type) != ET_EXEC)
|
||||
die("ELF is not an executable");
|
||||
if (swap16(ehdr.e_machine) != EM_PPC)
|
||||
die("Machine is not PowerPC");
|
||||
if (!swap32(ehdr.e_entry))
|
||||
die("ELF has no entrypoint");
|
||||
|
||||
map->header.entry = ehdr.e_entry;
|
||||
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Valid ELF header found\n");
|
||||
|
||||
uint16_t phnum = swap16(ehdr.e_phnum);
|
||||
uint32_t phoff = swap32(ehdr.e_phoff);
|
||||
Elf32_Phdr* phdrs;
|
||||
|
||||
if (!phnum || !phoff)
|
||||
die("ELF has no program headers");
|
||||
|
||||
if (swap16(ehdr.e_phentsize) != sizeof(Elf32_Phdr))
|
||||
die("Invalid program header entry size");
|
||||
|
||||
phdrs = malloc(phnum * sizeof(Elf32_Phdr));
|
||||
|
||||
if (fseek(map->elf, phoff, SEEK_SET) < 0)
|
||||
ferrordie(map->elf, "reading ELF program headers");
|
||||
read = fread(phdrs, sizeof(Elf32_Phdr), phnum, map->elf);
|
||||
if (read != phnum)
|
||||
ferrordie(map->elf, "reading ELF program headers");
|
||||
|
||||
for (i = 0; i < phnum; i++) {
|
||||
if (swap32(phdrs[i].p_type) == PT_LOAD) {
|
||||
uint32_t offset = swap32(phdrs[i].p_offset);
|
||||
uint32_t paddr = swap32(phdrs[i].p_vaddr);
|
||||
uint32_t filesz = swap32(phdrs[i].p_filesz);
|
||||
uint32_t memsz = swap32(phdrs[i].p_memsz);
|
||||
uint32_t flags = swap32(phdrs[i].p_flags);
|
||||
if (memsz) {
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "PHDR %d: 0x%x [0x%x] -> 0x%08x [0x%x] flags 0x%x\n",
|
||||
i, offset, filesz, paddr, memsz, flags);
|
||||
if (flags & PF_X) {
|
||||
// TEXT segment
|
||||
if (!(flags & PF_R))
|
||||
fprintf(stderr, "Warning: non-readable segment %d\n", i);
|
||||
if (flags & PF_W)
|
||||
fprintf(stderr, "Warning: writable and executable segment %d\n", i);
|
||||
if (filesz > memsz) {
|
||||
fprintf(stderr,
|
||||
"Error: TEXT segment %d memory size (0x%x) smaller than "
|
||||
"file size (0x%x)\n",
|
||||
i, memsz, filesz);
|
||||
exit(1);
|
||||
} else if (memsz > filesz) {
|
||||
add_bss(map, paddr + filesz, memsz - filesz);
|
||||
}
|
||||
if (map->text_cnt >= MAX_TEXT_SEGMENTS) {
|
||||
die("Error: Too many TEXT segments");
|
||||
}
|
||||
map->header.text_addr[map->text_cnt] = swap32(paddr);
|
||||
map->header.text_size[map->text_cnt] = swap32(filesz);
|
||||
map->text_elf_off[map->text_cnt] = offset;
|
||||
map->text_cnt++;
|
||||
} else {
|
||||
// DATA or BSS segment
|
||||
if (!(flags & PF_R))
|
||||
fprintf(stderr, "Warning: non-readable segment %d\n", i);
|
||||
if (filesz == 0) {
|
||||
// BSS segment
|
||||
add_bss(map, paddr, memsz);
|
||||
} else {
|
||||
// DATA segment
|
||||
if (filesz > memsz) {
|
||||
fprintf(stderr,
|
||||
"Error: segment %d memory size (0x%x) is smaller than "
|
||||
"file size (0x%x)\n",
|
||||
i, memsz, filesz);
|
||||
exit(1);
|
||||
}
|
||||
if (map->data_cnt >= MAX_DATA_SEGMENTS) {
|
||||
die("Error: Too many DATA segments");
|
||||
}
|
||||
map->header.data_addr[map->data_cnt] = swap32(paddr);
|
||||
map->header.data_size[map->data_cnt] = swap32(filesz);
|
||||
map->data_elf_off[map->data_cnt] = offset;
|
||||
map->data_cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (verbosity >= 1)
|
||||
fprintf(stderr, "Skipping empty program header %d\n", i);
|
||||
}
|
||||
} else if (verbosity >= 1) {
|
||||
fprintf(stderr, "Skipping program header %d of type %d\n", i,
|
||||
swap32(phdrs[i].p_type));
|
||||
}
|
||||
}
|
||||
if (verbosity >= 2) {
|
||||
fprintf(stderr, "Segments:\n");
|
||||
for (i = 0; i < map->text_cnt; i++) {
|
||||
fprintf(stderr, " TEXT %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i,
|
||||
swap32(map->header.text_addr[i]),
|
||||
swap32(map->header.text_size[i]), map->text_elf_off[i]);
|
||||
}
|
||||
for (i = 0; i < map->data_cnt; i++) {
|
||||
fprintf(stderr, " DATA %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i,
|
||||
swap32(map->header.data_addr[i]),
|
||||
swap32(map->header.data_size[i]), map->data_elf_off[i]);
|
||||
}
|
||||
if (map->flags & HAVE_BSS)
|
||||
fprintf(stderr, " BSS segment: 0x%08x [0x%x]\n",
|
||||
swap32(map->header.bss_addr), swap32(map->header.bss_size));
|
||||
}
|
||||
}
|
||||
|
||||
void map_dol(DOL_map* map) {
|
||||
uint32_t fpos;
|
||||
int i;
|
||||
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Laying out DOL file...\n");
|
||||
|
||||
fpos = DOL_ALIGN(sizeof(DOL_hdr));
|
||||
|
||||
for (i = 0; i < map->text_cnt; i++) {
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, " TEXT segment %d at 0x%x\n", i, fpos);
|
||||
map->header.text_off[i] = swap32(fpos);
|
||||
fpos = DOL_ALIGN(fpos + swap32(map->header.text_size[i]));
|
||||
}
|
||||
for (i = 0; i < map->data_cnt; i++) {
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, " DATA segment %d at 0x%x\n", i, fpos);
|
||||
map->header.data_off[i] = swap32(fpos);
|
||||
fpos = DOL_ALIGN(fpos + swap32(map->header.data_size[i]));
|
||||
}
|
||||
|
||||
if (map->text_cnt == 0) {
|
||||
if (verbosity >= 1)
|
||||
fprintf(stderr,
|
||||
"Note: adding dummy TEXT segment to work around IOS bug\n");
|
||||
map->header.text_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr)));
|
||||
}
|
||||
if (map->data_cnt == 0) {
|
||||
if (verbosity >= 1)
|
||||
fprintf(stderr,
|
||||
"Note: adding dummy DATA segment to work around IOS bug\n");
|
||||
map->header.data_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr)));
|
||||
}
|
||||
}
|
||||
|
||||
#define BLOCK (1024 * 1024)
|
||||
|
||||
void fcpy(FILE* dst, FILE* src, uint32_t dst_off, uint32_t src_off,
|
||||
uint32_t size) {
|
||||
int left = size;
|
||||
int read;
|
||||
int written;
|
||||
int block;
|
||||
void* blockbuf;
|
||||
|
||||
if (fseek(src, src_off, SEEK_SET) < 0)
|
||||
ferrordie(src, "reading ELF segment data");
|
||||
if (fseek(dst, dst_off, SEEK_SET) < 0)
|
||||
ferrordie(dst, "writing DOL segment data");
|
||||
|
||||
blockbuf = malloc(MIN(BLOCK, left));
|
||||
|
||||
while (left) {
|
||||
block = MIN(BLOCK, left);
|
||||
read = fread(blockbuf, 1, block, src);
|
||||
if (read != block) {
|
||||
free(blockbuf);
|
||||
ferrordie(src, "reading ELF segment data");
|
||||
}
|
||||
written = fwrite(blockbuf, 1, block, dst);
|
||||
if (written != block) {
|
||||
free(blockbuf);
|
||||
ferrordie(dst, "writing DOL segment data");
|
||||
}
|
||||
left -= block;
|
||||
}
|
||||
free(blockbuf);
|
||||
}
|
||||
|
||||
void fpad(FILE* dst, uint32_t dst_off, uint32_t size) {
|
||||
uint32_t i;
|
||||
|
||||
if (fseek(dst, dst_off, SEEK_SET) < 0)
|
||||
ferrordie(dst, "writing DOL segment data");
|
||||
for (i = 0; i < size; i++)
|
||||
fputc(0, dst);
|
||||
}
|
||||
|
||||
void write_dol(DOL_map* map, const char* dol) {
|
||||
FILE* dolf;
|
||||
int written;
|
||||
int i;
|
||||
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Writing DOL file...\n");
|
||||
|
||||
dolf = fopen(dol, "wb");
|
||||
if (!dolf)
|
||||
perrordie("Could not open DOL file");
|
||||
|
||||
if (verbosity >= 2) {
|
||||
fprintf(stderr, "DOL header:\n");
|
||||
for (i = 0; i < MAX(1, map->text_cnt); i++)
|
||||
fprintf(stderr, " TEXT %d @ 0x%08x [0x%x] off 0x%x\n", i,
|
||||
swap32(map->header.text_addr[i]),
|
||||
swap32(map->header.text_size[i]),
|
||||
swap32(map->header.text_off[i]));
|
||||
for (i = 0; i < MAX(1, map->data_cnt); i++)
|
||||
fprintf(stderr, " DATA %d @ 0x%08x [0x%x] off 0x%x\n", i,
|
||||
swap32(map->header.data_addr[i]),
|
||||
swap32(map->header.data_size[i]),
|
||||
swap32(map->header.data_off[i]));
|
||||
if (swap32(map->header.bss_addr) && swap32(map->header.bss_size))
|
||||
fprintf(stderr, " BSS @ 0x%08x [0x%x]\n", swap32(map->header.bss_addr),
|
||||
swap32(map->header.bss_size));
|
||||
fprintf(stderr, " Entry: 0x%08x\n", swap32(map->header.entry));
|
||||
fprintf(stderr, "Writing DOL header...\n");
|
||||
}
|
||||
|
||||
// Write DOL header with aligned text and data section sizes
|
||||
DOL_hdr aligned_header = map->header;
|
||||
for (i = 0; i < ARRAY_COUNT(aligned_header.text_size); i++)
|
||||
aligned_header.text_size[i] =
|
||||
swap32(DOL_ALIGN(swap32(aligned_header.text_size[i])));
|
||||
for (i = 0; i < ARRAY_COUNT(aligned_header.data_size); i++)
|
||||
aligned_header.data_size[i] =
|
||||
swap32(DOL_ALIGN(swap32(aligned_header.data_size[i])));
|
||||
written = fwrite(&aligned_header, sizeof(DOL_hdr), 1, dolf);
|
||||
if (written != 1)
|
||||
ferrordie(dolf, "writing DOL header");
|
||||
|
||||
for (i = 0; i < map->text_cnt; i++) {
|
||||
uint32_t size = swap32(map->header.text_size[i]);
|
||||
uint32_t padded_size = DOL_ALIGN(size);
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Writing TEXT segment %d...\n", i);
|
||||
fcpy(dolf, map->elf, swap32(map->header.text_off[i]), map->text_elf_off[i],
|
||||
size);
|
||||
if (padded_size > size)
|
||||
fpad(dolf, swap32(map->header.text_off[i]) + size, padded_size - size);
|
||||
}
|
||||
for (i = 0; i < map->data_cnt; i++) {
|
||||
uint32_t size = swap32(map->header.data_size[i]);
|
||||
uint32_t padded_size = DOL_ALIGN(size);
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "Writing DATA segment %d...\n", i);
|
||||
fcpy(dolf, map->elf, swap32(map->header.data_off[i]), map->data_elf_off[i],
|
||||
size);
|
||||
if (padded_size > size)
|
||||
fpad(dolf, swap32(map->header.data_off[i]) + size, padded_size - size);
|
||||
}
|
||||
|
||||
if (verbosity >= 2)
|
||||
fprintf(stderr, "All done!\n");
|
||||
|
||||
fclose(map->elf);
|
||||
fclose(dolf);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
char** arg;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
arg = &argv[1];
|
||||
argc--;
|
||||
|
||||
while (argc && *arg[0] == '-') {
|
||||
if (!strcmp(*arg, "-h")) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
} else if (!strcmp(*arg, "-v")) {
|
||||
verbosity++;
|
||||
} else if (!strcmp(*arg, "--")) {
|
||||
arg++;
|
||||
argc--;
|
||||
break;
|
||||
} else {
|
||||
fprintf(stderr, "Unrecognized option %s\n", *arg);
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
arg++;
|
||||
argc--;
|
||||
}
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char* elf_file = arg[0];
|
||||
const char* dol_file = arg[1];
|
||||
|
||||
DOL_map map;
|
||||
|
||||
memset(&map, 0, sizeof(map));
|
||||
|
||||
read_elf_segments(&map, elf_file);
|
||||
map_dol(&map);
|
||||
write_dol(&map, dol_file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define VERSION_MAX_LEN (size_t)(35)
|
||||
#define METROID_BUILD_INFO_TAG "!#$MetroidBuildInfo!#$"
|
||||
|
||||
void* memmem(const void* l, size_t l_len, const void* s, size_t s_len) {
|
||||
register char *cur, *last;
|
||||
const char* cl = (const char*)l;
|
||||
const char* cs = (const char*)s;
|
||||
|
||||
/* we need something to compare */
|
||||
if (l_len == 0 || s_len == 0)
|
||||
return NULL;
|
||||
|
||||
/* "s" must be smaller or equal to "l" */
|
||||
if (l_len < s_len)
|
||||
return NULL;
|
||||
|
||||
/* special case where s_len == 1 */
|
||||
if (s_len == 1)
|
||||
return memchr(l, (int)*cs, l_len);
|
||||
|
||||
/* the last position where its possible to find "s" in "l" */
|
||||
last = (char*)cl + l_len - s_len;
|
||||
|
||||
for (cur = (char*)cl; cur <= last; cur++)
|
||||
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
|
||||
return cur;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(stdout, "\t--- METROID BUILD INFO ---\n"
|
||||
"\tWritten by Phillip \"Antidote\" Stephens\n"
|
||||
"\tReleased under the MIT License\n\n"
|
||||
"\tSets the MetroidBuildInfo tag value in a given binary\n"
|
||||
"\tThe version string can be a maximum of 35 characters,\n"
|
||||
"\texcluding null terminator\n"
|
||||
"\t--------------------------\n"
|
||||
|
||||
);
|
||||
fprintf(stdout, "Usage:\n"
|
||||
"\tmetroidbuildinfo <binary> <build_file>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Let's try to get the source binary */
|
||||
FILE* source = fopen(argv[1], "rb");
|
||||
if (!source) {
|
||||
fprintf(stderr, "Unable to open '%s'\nPlease ensure the file exists!\n", argv[1]);
|
||||
return -2;
|
||||
}
|
||||
|
||||
char build_string[36] = {0};
|
||||
FILE* build = fopen(argv[2], "rb");
|
||||
|
||||
if (!build) {
|
||||
fprintf(stderr, "Unable to open '%s'\nPlease ensure the file exists!\n", argv[2]);
|
||||
return -3;
|
||||
}
|
||||
size_t read_len = fread(build_string, 1, 35, build);
|
||||
fclose(build);
|
||||
|
||||
if (read_len <= 0) {
|
||||
fprintf(stderr, "Empty file %s specified for build version!\n", argv[2]);
|
||||
return -4;
|
||||
}
|
||||
|
||||
build_string[strcspn(build_string, "\r\n")] = '\0';
|
||||
|
||||
/* Get source length */
|
||||
fseek(source, 0, SEEK_END);
|
||||
size_t source_len = ftell(source);
|
||||
rewind(source);
|
||||
void* source_buf = malloc(source_len);
|
||||
if (source_buf == NULL) {
|
||||
fprintf(stderr, "Unable to allocate buffer of size %zubytes!\n", source_len);
|
||||
return -5;
|
||||
}
|
||||
fread(source_buf, 1, source_len, source);
|
||||
fclose(source);
|
||||
|
||||
/* Find the build info tag so we can stuff our version info in the binary */
|
||||
void* ptr =
|
||||
memmem(source_buf, source_len, METROID_BUILD_INFO_TAG, strlen(METROID_BUILD_INFO_TAG));
|
||||
if (ptr == NULL) {
|
||||
fprintf(stderr, "Unable to find build info tag in source!\n");
|
||||
return -6;
|
||||
}
|
||||
|
||||
/* Lets actually copy over the build string */
|
||||
strcpy(ptr + strlen(METROID_BUILD_INFO_TAG), build_string);
|
||||
|
||||
/* Now attempt to open the target file */
|
||||
FILE* target = fopen(argv[1], "wb");
|
||||
|
||||
if (!target) {
|
||||
fprintf(stderr, "Unable to open '%s'\nPlease ensure you have write permissions!\n", argv[1]);
|
||||
return -7;
|
||||
}
|
||||
|
||||
/* Finally write the buffer to the target file */
|
||||
fwrite(source_buf, 1, source_len, target);
|
||||
fclose(target);
|
||||
|
||||
/* Don't leak */
|
||||
free(source_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user