mirror of
https://github.com/Xeeynamo/sotn-decomp.git
synced 2024-11-26 22:40:33 +00:00
Add mwccgap (#1131)
OK. Here goes. Version 0.0.1 alpha of the MWCC Global Assembly Processor (mwccgap). It's currently very simple/limited, but it appears to work for `src/servant/tt_000/10E8.c`. There is lot more that can be done to improve mwccgap - i.e. supporting .rodata migration would be a good addition, but let's see how far we can get with it in it's current state. Note that the Makefile could do with some improvements - we don't nede to use mwccgap for any C file that *dont* have INCLUDE_ASM macros (it's a waste of time) so these could be ignored, i.e. for SSSV I do something like this to find the files that need fixing up: ``` GLOBAL_ASM_C_FILES := $(shell $(GREP) GLOBAL_ASM $(SRC_DIR) </dev/null 2>/dev/null) ``` .. although this is perhaps too simple given that SOTN has a mix of PSP and PSX functions (and therefore there may be INCLUDE_ASM for a PSX function but none for PSP functions...
This commit is contained in:
parent
51ddf51bb3
commit
b6a0ddbeb6
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -13,3 +13,6 @@
|
||||
[submodule "tools/maspsx"]
|
||||
path = tools/maspsx
|
||||
url = https://github.com/mkst/maspsx.git
|
||||
[submodule "tools/mwccgap"]
|
||||
path = tools/mwccgap
|
||||
url = https://github.com/mkst/mwccgap.git
|
||||
|
6
Makefile
6
Makefile
@ -15,12 +15,12 @@ LD := $(CROSS)ld
|
||||
CPP := $(CROSS)cpp
|
||||
OBJCOPY := $(CROSS)objcopy
|
||||
AS_FLAGS += -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0
|
||||
PSXCC_FLAGS := -quiet -mcpu=3000 -fgnu-linker -mgas -gcoff
|
||||
PSXCC_FLAGS := -quiet -mcpu=3000 -fgnu-linker -mgas -gcoff
|
||||
CC_FLAGS += -G0 -w -O2 -funsigned-char -fpeephole -ffunction-cse -fpcc-struct-return -fcommon -fverbose-asm -msoft-float -g
|
||||
CPP_FLAGS += -Iinclude -Iinclude/psxsdk -undef -Wall -fno-builtin
|
||||
CPP_FLAGS += -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -D__mips__ -D__mips -Dpsx -D__psx__ -D__psx -D_PSYQ -D__EXTENSIONS__ -D_MIPSEL -D_LANGUAGE_C -DLANGUAGE_C -DNO_LOGS -DHACKS -DUSE_INCLUDE_ASM
|
||||
CPP_FLAGS += -D_internal_version_$(VERSION) -DSOTN_STR
|
||||
LD_FLAGS := -nostdlib --no-check-sections
|
||||
LD_FLAGS := -nostdlib --no-check-sections
|
||||
|
||||
# Directories
|
||||
ASM_DIR := asm/$(VERSION)
|
||||
@ -62,7 +62,7 @@ M2C_DIR := $(TOOLS_DIR)/m2c
|
||||
M2C_APP := $(M2C_DIR)/m2c.py
|
||||
M2C := $(PYTHON) $(M2C_APP)
|
||||
M2C_ARGS := -P 4
|
||||
SOTNSTR := $(PYTHON) $(TOOLS_DIR)/sotn_str/sotn_str.py process
|
||||
SOTNSTR := $(PYTHON) $(TOOLS_DIR)/sotn_str/sotn_str.py process
|
||||
MASPSX_DIR := $(TOOLS_DIR)/maspsx
|
||||
MASPSX_APP := $(MASPSX_DIR)/maspsx.py
|
||||
MASPSX := $(PYTHON) $(MASPSX_APP) --expand-div --aspsx-version=2.34
|
||||
|
@ -1,16 +1,24 @@
|
||||
GNUASPSP := mipsel-linux-gnu-as -I include/ -G0 -march=r6000 -mabi=eabi
|
||||
MWASPSP := bin/wibo bin/asm_psp_elf.exe -gnu
|
||||
ASPSP := $(GNUASPSP)
|
||||
WIBO := bin/wibo
|
||||
MWCCPSP := bin/mwccpsp.exe
|
||||
|
||||
GNULDPSP := mipsel-linux-gnu-ld
|
||||
MWLDPSP := bin/wibo bin/mwldpsp.exe -partial -nostdlib -msgstyle gcc -sym full,elf -g
|
||||
LDPSP := $(GNULDPSP)
|
||||
GNUASPSP := mipsel-linux-gnu-as -I include/ -G0 -march=r6000 -mabi=eabi
|
||||
MWASPSP := $(WIBO) bin/asm_psp_elf.exe -gnu
|
||||
ASPSP := $(GNUASPSP)
|
||||
|
||||
PSP_BUILD_DIR := build/pspeu
|
||||
CCPSP := MWCIncludes=bin/ bin/wibo bin/mwccpsp.exe
|
||||
PSP_EU_TARGETS := tt_000
|
||||
SPLAT_PIP := splat split
|
||||
MWCPP_APP := python3 tools/mwcpp.py
|
||||
GNULDPSP := mipsel-linux-gnu-ld
|
||||
MWLDPSP := $(WIBO) bin/mwldpsp.exe -partial -nostdlib -msgstyle gcc -sym full,elf -g
|
||||
LDPSP := $(GNULDPSP)
|
||||
|
||||
MWCCGAP_DIR := $(TOOLS_DIR)/mwccgap
|
||||
MWCCGAP_APP := $(MWCCGAP_DIR)/mwccgap.py
|
||||
MWCCGAP := $(PYTHON) $(MWCCGAP_APP)
|
||||
|
||||
PSP_BUILD_DIR := build/pspeu
|
||||
CCPSP := MWCIncludes=bin/ $(WIBO) $(MWCCPSP)
|
||||
PSP_EU_TARGETS := tt_000
|
||||
SPLAT_PIP := splat split
|
||||
|
||||
MWCCPSP_FLAGS := -gccinc -Iinclude -D_internal_version_$(VERSION) -O0 -c -lang c -sdatathreshold 0
|
||||
|
||||
define list_src_files_psp
|
||||
$(foreach dir,$(ASM_DIR)/$(1),$(wildcard $(dir)/**.s))
|
||||
@ -27,15 +35,25 @@ build_pspeu: tt_000_psp
|
||||
|
||||
extract_pspeu: $(addprefix $(PSP_BUILD_DIR)/,$(addsuffix .ld,$(PSP_EU_TARGETS)))
|
||||
|
||||
bin/wibo:
|
||||
$(WIBO):
|
||||
wget -O $@ https://github.com/decompals/wibo/releases/download/0.6.13/wibo
|
||||
sha256sum --check bin/wibo.sha256
|
||||
chmod +x bin/wibo
|
||||
bin/mwccpsp.exe: bin/wibo bin/mwccpsp_3.0.1_147
|
||||
sha256sum --check $(WIBO).sha256
|
||||
chmod +x $(WIBO)
|
||||
$(MWCCPSP): $(WIBO) bin/mwccpsp_3.0.1_147
|
||||
|
||||
$(PSP_BUILD_DIR)/%.c.o: %.c bin/mwccpsp.exe
|
||||
$(MWCCGAP_APP):
|
||||
git submodule init $(MWCCGAP_DIR)
|
||||
git submodule update $(MWCCGAP_DIR)
|
||||
|
||||
$(PSP_BUILD_DIR)/%.c.o: %.c $(MWCCPSP) $(MWCCGAP_APP)
|
||||
mkdir -p $(dir $@)
|
||||
$(MWCPP_APP) $< -o $<.post.c && (($(CCPSP) -gccinc -Iinclude -D_internal_version_$(VERSION) -O0 -c -lang c -sdatathreshold 0 -o $@ $<.post.c && rm $<.post.c) || (rm $<.post.c && exit 1))
|
||||
if grep -q INCLUDE_ASM $<; then \
|
||||
$(MWCCGAP) $< $@ --mwcc-path $(MWCCPSP) --use-wibo --wibo-path $(WIBO) --asm-dir-prefix asm/pspeu $(MWCCPSP_FLAGS) ; \
|
||||
else \
|
||||
$(CCPSP) $< -o $@ $(MWCCPSP_FLAGS) ; \
|
||||
fi
|
||||
|
||||
|
||||
$(PSP_BUILD_DIR)/asm/psp%.s.o: asm/psp%.s
|
||||
mkdir -p $(dir $@)
|
||||
$(ASPSP) -o $@ $<
|
||||
@ -45,7 +63,7 @@ $(PSP_BUILD_DIR)/assets/servant/tt_000/header.bin.o: assets/servant/tt_000/heade
|
||||
mkdir -p $(dir $@)
|
||||
mipsel-linux-gnu-ld -r -b binary -o $@ $<
|
||||
|
||||
tt_000_psp: $(PSP_BUILD_DIR)/tt_000.bin
|
||||
tt_000_psp: $(PSP_BUILD_DIR)/tt_000.bin $(PSP_BUILD_DIR)/assets/servant/tt_000/header.bin.o
|
||||
|
||||
$(PSP_BUILD_DIR)/tt_%.bin: $(PSP_BUILD_DIR)/tt_%.elf
|
||||
$(OBJCOPY) -O binary $< $@
|
||||
@ -53,6 +71,3 @@ $(PSP_BUILD_DIR)/tt_%.ld: $(CONFIG_DIR)/splat.pspeu.tt_%.yaml $(PSX_BASE_SYMS) $
|
||||
$(SPLAT_PIP) $<
|
||||
$(PSP_BUILD_DIR)/tt_%.elf: $(PSP_BUILD_DIR)/tt_%.ld $$(call list_o_files_psp,servant/tt_$$*)
|
||||
$(call link,tt_$*,$@)
|
||||
|
||||
# cannot remove it for some reason? makefile bug?
|
||||
_ignoreme_tt_000: $(PSP_BUILD_DIR)/src/servant/tt_000_psp/80.c.o $(PSP_BUILD_DIR)/asm/pspeu/servant/tt_000/data/4C80.data.s.o $(BUILD_DIR)/asm/pspeu/servant/tt_000/data/5E00.rodata.s.o
|
||||
|
@ -24,7 +24,13 @@ options:
|
||||
- ".rodata"
|
||||
- ".bss"
|
||||
ld_bss_is_noload: True
|
||||
# disasm_unknown: True
|
||||
disasm_unknown: True
|
||||
asm_inc_header: |
|
||||
.set noat /* allow manual use of $at */
|
||||
.set noreorder /* don't insert nops after branches */
|
||||
.include "macro.inc"
|
||||
sha1: c8c34ac1d46b31e2e5336df271aa2409f44c9d01
|
||||
|
||||
segments:
|
||||
- [0x0, bin, header]
|
||||
- name: tt_000
|
||||
|
@ -95,5 +95,5 @@ def apply(config, args):
|
||||
apply_psx_base(config, version, name)
|
||||
else:
|
||||
apply_psx_bin(config, version, name)
|
||||
config["arch"] = "mipsel"
|
||||
config["arch"] = "mipsel:4000" if version == "pspeu" else "mipsel"
|
||||
config["objdump_executable"] = "mipsel-linux-gnu-objdump"
|
||||
|
@ -5,6 +5,11 @@
|
||||
#define SFX_BAT_SCREECH SOUND_BAT_SCREECH
|
||||
#define SFX_BAT_NOTIFY SE_UI_OVERWRITE_MSG
|
||||
|
||||
#ifdef VERSION_PSP
|
||||
#undef INCLUDE_ASM
|
||||
#define INCLUDE_ASM(FOLDER, NAME)
|
||||
#endif
|
||||
|
||||
#ifndef VERSION_PSP
|
||||
s32 D_801748D8[0x80];
|
||||
Collider D_80174AD8;
|
||||
@ -67,18 +72,8 @@ ServantDesc g_ServantDesc = {
|
||||
#endif
|
||||
|
||||
#ifdef VERSION_PSP
|
||||
extern ServantDesc g_ServantDesc;
|
||||
extern s32 D_80174D3C;
|
||||
|
||||
void DestroyEntity();
|
||||
s32 func_80174864(void);
|
||||
s32 func_801746A0(s32 arg0);
|
||||
void ProcessEvent();
|
||||
void CreateEventEntity(Entity* entityParent, s32 entityId, s32 params);
|
||||
|
||||
void func_80173F74();
|
||||
void func_80173F30();
|
||||
|
||||
void DestroyEntity(Entity* entity);
|
||||
#endif
|
||||
|
||||
void func_801710E8(Entity* entity, AnimationFrame* anim) {
|
||||
@ -197,7 +192,10 @@ s32 func_801713C8(Entity* entity) {
|
||||
return 0;
|
||||
if (entity->hitPoints >= 0x7000)
|
||||
return 0;
|
||||
return entity->hitPoints > 0;
|
||||
if (entity->hitPoints <= 0)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit e8bddb02576e73aecb90f813bfb452417c37b257
|
||||
Subproject commit f5fc9026f0b164966ea7bc864335216f9f02ba02
|
1
tools/mwccgap
Submodule
1
tools/mwccgap
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 63417712bcf77aaa24b83e0e899493de42393ef7
|
120
tools/mwcpp.py
120
tools/mwcpp.py
@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from typing import TextIO
|
||||
|
||||
r_lui = r"\s*lui\s*\$(.*),\s\%hi\((.*)\)"
|
||||
r_lui_addiu_combo = r"\s*addiu\s*\$(.*),\s\$(.*),\s\%lo\((.*)\)"
|
||||
r_hilo = r"\/\*\s*\w*\s*\w*\s*(\w*)\s*\*\/\s*\w*\s*\$\w*,.*(?:%hi|%lo).*"
|
||||
r_hi = r"%hi\((.*)\)"
|
||||
r_lo = r"%lo\((.*)\)\(|%lo\((.*)\)"
|
||||
r_jalr = r"jalr\s*\$([a-z][a-z0-9])"
|
||||
r_jlabel = r"jlabel \.(.*)"
|
||||
|
||||
|
||||
def process_asm_line(asm_f: TextIO, line: str) -> str | None:
|
||||
if line == "":
|
||||
return None
|
||||
|
||||
# skip glabel
|
||||
if line.startswith("glabel"):
|
||||
return ""
|
||||
|
||||
# skip .size
|
||||
if line.startswith(".size"):
|
||||
return ""
|
||||
|
||||
match_jlabel = re.search(r_jlabel, line)
|
||||
if match_jlabel:
|
||||
label_name = match_jlabel.group(1)
|
||||
line = f"{label_name}:\n"
|
||||
|
||||
# do not use '.' on label names
|
||||
line = line.replace(".L", "L")
|
||||
|
||||
# remove trailing hash comment
|
||||
if "#" in line:
|
||||
line = line.split("#")[0] + "\n"
|
||||
|
||||
# jalr needs two arguments
|
||||
jalr_match = re.search(r_jalr, line)
|
||||
if jalr_match:
|
||||
reg_name = jalr_match.group(1)
|
||||
return line.replace(reg_name, f"ra, ${reg_name}")
|
||||
|
||||
r_hilo_match = re.search(r_hilo, line)
|
||||
if r_hilo_match:
|
||||
raw_data = r_hilo_match.group(1)
|
||||
if len(raw_data) == 8:
|
||||
return f".word 0x{raw_data[6:8]}{raw_data[4:6]}{raw_data[2:4]}{raw_data[0:2]}\n"
|
||||
|
||||
# return unpatched assembly line
|
||||
return line
|
||||
|
||||
|
||||
def include_asm(c_line: str, out: TextIO, version: str) -> None:
|
||||
match = re.search(r'\s*INCLUDE_ASM\(\s*"(.*)",\s*(\w*)\)', c_line)
|
||||
if match:
|
||||
base_path = match.group(1)
|
||||
func_name = match.group(2)
|
||||
out.write(f"asm void {func_name}() {{\n")
|
||||
with open(f"asm/{version}/{base_path}/{func_name}.s", "r") as f:
|
||||
try:
|
||||
while True:
|
||||
line = f.readline()
|
||||
patched_line = process_asm_line(f, line)
|
||||
if patched_line == None:
|
||||
break
|
||||
out.write(patched_line)
|
||||
except Exception as ex:
|
||||
print(f"line {line} caused an exception")
|
||||
raise ex
|
||||
out.write(f"}}\n")
|
||||
else:
|
||||
out.write(c_line)
|
||||
|
||||
|
||||
def process_file(file_name_in: str, out: TextIO, version: str) -> None:
|
||||
with open(file_name_in, "r") as fin:
|
||||
while True:
|
||||
line = fin.readline()
|
||||
if not line:
|
||||
return None
|
||||
if line.startswith("INCLUDE_ASM"):
|
||||
try:
|
||||
include_asm(line, out, version)
|
||||
except Exception as ex:
|
||||
print(f"{line} caused an exception")
|
||||
raise
|
||||
else:
|
||||
out.write(line)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Pre-process Metrowerks C files to overcome certain compiler limitations"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version", required=False, type=str, default="pspeu", help="Game version"
|
||||
)
|
||||
parser.add_argument(
|
||||
"input",
|
||||
type=str,
|
||||
help="Input C file to pre-process",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=str,
|
||||
help="Output file, stdout if not specified",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.version == None:
|
||||
args.version = "pspeu"
|
||||
if args.output:
|
||||
with open(args.output, "w") as fout:
|
||||
process_file(args.input, fout, args.version)
|
||||
else:
|
||||
process_file(args.input, sys.stdout, args.version)
|
@ -1,132 +0,0 @@
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from mwcpp import process_file
|
||||
|
||||
|
||||
class TestProcessFile(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUp(self) -> None:
|
||||
self.seed = f"{random.randint(0, 100000)}"
|
||||
self.c_file_name = f"test_file_{self.seed}.c"
|
||||
self.asm_dir_name = f"asm/{self.seed}"
|
||||
self.output_name = f"test_processed_file_{self.seed}.c"
|
||||
os.makedirs(f"{self.asm_dir_name}/ovl")
|
||||
return super().setUp(self)
|
||||
|
||||
@classmethod
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree(self.asm_dir_name)
|
||||
os.remove(self.c_file_name)
|
||||
os.remove(self.output_name)
|
||||
return super().tearDown(self)
|
||||
|
||||
def helper_process_lines(self, assembly):
|
||||
with open(self.c_file_name, "w") as f:
|
||||
f.write(f'INCLUDE_ASM("ovl", func_name)')
|
||||
with open(f"{self.asm_dir_name}/ovl/func_name.s", "w") as f:
|
||||
f.write(assembly)
|
||||
with open(self.output_name, "w") as f:
|
||||
process_file(self.c_file_name, f, self.seed)
|
||||
with open(self.output_name, "r") as f:
|
||||
return "".join(f.readlines())
|
||||
|
||||
def test_process_basic_include_asm(self):
|
||||
with open(self.c_file_name, "w") as f:
|
||||
f.writelines(
|
||||
[
|
||||
"ignore this line\n",
|
||||
"// this too\n" f'INCLUDE_ASM("ovl", func_name)\n',
|
||||
f'// INCLUDE_ASM("ovl", func_name)\n',
|
||||
"",
|
||||
]
|
||||
)
|
||||
with open(f"{self.asm_dir_name}/ovl/func_name.s", "w") as f:
|
||||
f.write("glabel function_name_should_be_skipped\n")
|
||||
f.write("/* 0 */ nop\n")
|
||||
f.write(".size this_part_should_be_skipped\n")
|
||||
with open(self.output_name, "w") as f:
|
||||
process_file(self.c_file_name, f, self.seed)
|
||||
with open(self.output_name, "r") as f:
|
||||
lines = "".join(f.readlines())
|
||||
|
||||
self.assertEqual(
|
||||
lines,
|
||||
"""ignore this line
|
||||
// this too
|
||||
asm void func_name() {
|
||||
/* 0 */ nop
|
||||
}
|
||||
// INCLUDE_ASM("ovl", func_name)
|
||||
""",
|
||||
)
|
||||
|
||||
def test_fix_jalr(self):
|
||||
self.assertEqual(
|
||||
self.helper_process_lines(
|
||||
"""jalr $v0
|
||||
"""
|
||||
),
|
||||
"""asm void func_name() {
|
||||
jalr $ra, $v0
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_fix_jlabel(self):
|
||||
self.assertEqual(
|
||||
self.helper_process_lines(
|
||||
"""jlabel .LHELLO
|
||||
"""
|
||||
),
|
||||
"""asm void func_name() {
|
||||
LHELLO:
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_fix_lo_hi(self):
|
||||
self.assertEqual(
|
||||
self.helper_process_lines(
|
||||
"""/* XXXX 09012348 DEADBEEF */ lui $v1, %hi(D_92EFFDE)
|
||||
/* XXXX 09012344 BADC0FFE */ lh $a0, %lo(D_92EFFDE)
|
||||
"""
|
||||
),
|
||||
"""asm void func_name() {
|
||||
.word 0xEFBEADDE
|
||||
.word 0xFE0FDCBA
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_fix_dot_on_label_names(self):
|
||||
self.assertEqual(
|
||||
self.helper_process_lines(
|
||||
"""jmp .LABEL
|
||||
.LABEL:
|
||||
"""
|
||||
),
|
||||
"""asm void func_name() {
|
||||
jmp LABEL
|
||||
LABEL:
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
def test_remove_trailing_hash_comment(self):
|
||||
self.assertEqual(
|
||||
self.helper_process_lines(
|
||||
"""nop# comment
|
||||
"""
|
||||
),
|
||||
"""asm void func_name() {
|
||||
nop
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user