Skip to content

Commit 6937041

Browse files
authored
Merge pull request #57 from MHFU-ModKit/hybrid/slab-headroom
Lua slab headroom: TLSF, mhfu.addr per entry, streamed scripts, turns out of the VM
2 parents e19370e + fdee231 commit 6937041

36 files changed

Lines changed: 1724 additions & 255 deletions

‎.pre-commit-config.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# SPDX-License-Identifier: MIT
22
# SPDX-FileCopyrightText: 2026 sp00ktober
3+
# vendored code stays as released
4+
exclude: ^framework/third_party/
35
repos:
46
- repo: https://github.com/pre-commit/pre-commit-hooks
57
rev: v6.0.0

‎REUSE.toml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ SPDX-FileCopyrightText = [
3131
"2026 sp00ktober",
3232
]
3333
SPDX-License-Identifier = "BSD-3-Clause"
34+
35+
# TLSF 3.1, github.com/mattconte/tlsf at deff9ab509341f264addbd3c8ada533678591905, unchanged
36+
[[annotations]]
37+
path = "framework/third_party/tlsf/**"
38+
SPDX-FileCopyrightText = "2006-2016 Matthew Conte"
39+
SPDX-License-Identifier = "BSD-3-Clause"

‎apps/studio/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ on the floor of the view (T hides it) is the turn: an arrow for frame 0, one for
6666
a handle for the end, which drags the clip's `turn`, or a fixed steer's angle, in whole degrees
6767
(Shift: 15). Every edit is one undo step.
6868

69-
Play in game saves, writes the port's clips and moves modules to the memory stick's `mods/lib` as
69+
Play in game saves, writes the port's modules to the memory stick's `mods/lib` as
7070
`mhfu-port inject` does, and asks the running port to play the move by name; it refuses a move
7171
whose clip sits in another anim than in the saved manifest, since the game holds the build
7272
injected from the file. It writes only the modules that changed, and asks for the move from the

‎framework/Makefile‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PRXS = build/mhfu_framework.prx build/mhfu_boot.prx
1111
.PHONY: all clean
1212

1313
# psp-prxgen copies EF_MIPS_ABI_EABI32 into the PRX, which old firmware rejects (0x800200D9)
14-
all: $(GEN)/addresses.gen.h $(GEN)/addresses.lua $(GEN)/lua_api.gen.h $(GEN)/prelude.lua
14+
all: $(GEN)/addresses.gen.h $(GEN)/addresses_table.gen.inc $(GEN)/lua_api.gen.h $(GEN)/prelude.lua
1515
docker run --rm --platform linux/amd64 --user "$$(id -u):$$(id -g)" -v "$(CURDIR)":/work -w /work \
1616
$(IMAGE) make -f Makefile.psp $(if $(findstring k,$(firstword -$(MAKEFLAGS))),-k)
1717
uv run python tools/patch_prx_eflags.py $(PRXS)
@@ -24,9 +24,10 @@ $(GEN)/addresses.gen.lua: $(ADDR).toml $(ADDR).py
2424
@mkdir -p $(@D)
2525
uv run python -m mhfu.addresses lua -o $@
2626

27-
# the copy the PRX embeds, without the doc comments (a third of the size)
28-
$(GEN)/addresses.lua: $(GEN)/addresses.gen.lua
29-
grep -v '^[[:space:]]*---' $< > $@
27+
# what lua_host builds mhfu.addr from
28+
$(GEN)/addresses_table.gen.inc: $(ADDR).toml $(ADDR).py
29+
@mkdir -p $(@D)
30+
uv run python -m mhfu.addresses c-table -o $@
3031

3132
# the Lua API's registration table and prototypes (lua_api.gen.inc comes with the header)
3233
$(GEN)/lua_api.gen.h: lua/meta/mhfu.d.lua tools/lua_api.py

‎framework/Makefile.psp‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@ objs_in = $(patsubst %,$(OBJDIR)/%.o,$(basename $(sort $(wildcard $(addsuffix
2727
CORE_OBJS := $(call objs_in,src/core)
2828
MOD_OBJS := $(foreach m,$(MODS),$(call objs_in,mods/$(m)))
2929
ALL_MODS := $(call objs_in,$(wildcard mods/*))
30+
# TLSF, the Lua slab's allocator, without its asserts
31+
ifneq ($(filter lua_host,$(MODS)),)
32+
MOD_OBJS += $(OBJDIR)/third_party/tlsf/tlsf.o
33+
endif
34+
$(OBJDIR)/third_party/%.o: CFLAGS += -DNDEBUG
3035
OBJS = $(CORE_OBJS) $(MOD_OBJS)
3136

32-
INCDIR = include src/core $(GEN)
37+
INCDIR = include src/core $(GEN) third_party/tlsf
3338
CFLAGS = -Os -G0 -Wall -fno-strict-aliasing
3439
# no exceptions, RTTI or static guards: the objects need no libstdc++ runtime
3540
CXXFLAGS = -std=gnu++17 -fno-exceptions -fno-rtti -fno-threadsafe-statics
36-
# .incbin in a mod's .S finds the generated prelude and address table
41+
# .incbin in a mod's .S finds the generated prelude
3742
ASFLAGS = -Wa,-I$(GEN)
3843
# a Lua binding defined but not declared in lua/meta/mhfu.d.lua fails here; declared but not
3944
# defined fails the link

‎framework/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ make -C framework clean
1818
| `build/mhfu_framework.prx` | the plugin |
1919
| `build/mhfu_boot.prx` | the real-PSP loader: a kernel plugin that loads the framework from its own directory once the game shows the title or a menu |
2020
| `build/*.elf` | the same with symbols and relocations |
21-
| `build/gen/addresses.gen.h`, `.lua` | the address map, rendered from `packages/mhfu`; Lua reads it as `mhfu.addr` |
21+
| `build/gen/addresses.gen.h`, `addresses_table.gen.inc` | the address map, rendered from `packages/mhfu`; Lua reads it as `mhfu.addr`, an entry built when first read |
2222
| `build/gen/lua_api.gen.h`, `.inc` | the `mhfu` table's C side, rendered from `lua/meta/mhfu.d.lua` |
2323

2424
`mods.manifest` lists the mods linked into the plugin, one directory under `mods/` per line; `#`
@@ -87,6 +87,9 @@ A library in `mods/lib/` runs once, when a mod first requires it:
8787
may be busy, `require` refuses to read a new library. A mod that needs a newer API checks
8888
`mhfu.api_version`. Errors from the boot load are written to `framework.log` right after it.
8989

90+
Mods share one 350 KB Lua heap. `framework.log` gives `live=` after each mod and library loads,
91+
and `VM ready` the largest free block, which bounds what a hot reload can compile.
92+
9093
An editor with the Lua language server (VS Code's Lua extension) gets completion and hover docs
9194
from `.luarc.json` at the repo root; `mhfu.addr` needs `make -C framework
9295
build/gen/addresses.gen.lua` once. Outside this repo, add `framework/lua/meta` and

‎framework/include/mhfu/steer.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ typedef struct {
7272
uint8_t stuck_main, stuck_sub, stuck_mode; /* entered on a class-2 wall: em75's (0,6) mode 1 */
7373
uint8_t _pad[3];
7474
uint16_t keys[MHFU_STEER_KEYS]; /* YAW every KEY_STEP clip frames, the last at the clip's end:
75-
* the clips module's `_turns` (mhfu_port.travel.Turn) */
75+
* the port's turns module (mhfu_port.travel.Turn) */
7676
} mhfu_steer_spec_t;
7777

7878
/* struct STEER_STATE in addresses.toml */

‎framework/lua/meta/mhfu.d.lua‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ function mhfu.move_block() end
647647

648648
---How a move turns and stops (mhfu/steer.h); every field is optional.
649649
---@class mhfu.MoveSteer
650-
---@field curve? string YAW while the clip plays: the entry's string in the clips module's `_turns`
650+
---@field curve? string YAW while the clip plays: the entry's string in `<port>_turns.lua`
651651
---@field turn? "still"|"hunter"|"away"|"fixed" also: toward or away from the hunter at `rate`, or `total` over `frames`
652652
---@field rate? integer YAW units an AI frame toward or away, default 64 (the Tigrex charge's)
653653
---@field total? number degrees for "fixed", positive the way YAW grows

‎framework/mods/lua_host/addr.cpp‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/* SPDX-License-Identifier: MIT */
2+
/* SPDX-FileCopyrightText: 2026 sp00ktober */
3+
/* mhfu.addr: an entry of addresses.toml becomes a Lua value the first time a script reads it, so
4+
* the slab holds only the entries scripts use. */
5+
#include <string.h>
6+
7+
#include "mhfu/mhfu.h"
8+
#include "lua_host.h"
9+
#include "addresses_table.gen.inc" /* k_mhfu_addr */
10+
11+
static void push_entry(lua_State *L, const mhfu_addr_entry *e)
12+
{
13+
int n = 0;
14+
if (e->fields) {
15+
while (e->fields[n].name) n++;
16+
lua_createtable(L, 0, n);
17+
for (int i = 0; i < n; i++) {
18+
lua_pushinteger(L, (lua_Integer)e->fields[i].value);
19+
lua_setfield(L, -2, e->fields[i].name);
20+
}
21+
} else if (e->names) {
22+
while (e->names[n]) n++;
23+
lua_createtable(L, n, 0);
24+
for (int i = 0; i < n; i++) {
25+
lua_pushstring(L, e->names[i]);
26+
lua_rawseti(L, -2, i + 1);
27+
}
28+
} else {
29+
lua_pushinteger(L, (lua_Integer)e->value);
30+
}
31+
}
32+
33+
/* __index: builds the entry and keeps it in mhfu.addr. */
34+
static int addr_index(lua_State *L)
35+
{
36+
if (lua_type(L, 2) != LUA_TSTRING) return 0;
37+
const char *key = lua_tostring(L, 2);
38+
for (const mhfu_addr_entry *e = k_mhfu_addr; e->name; e++) {
39+
if (strcmp(e->name, key) != 0) continue;
40+
push_entry(L, e);
41+
lua_pushvalue(L, 2);
42+
lua_pushvalue(L, -2);
43+
lua_rawset(L, 1);
44+
return 1;
45+
}
46+
return 0;
47+
}
48+
49+
/* __pairs: builds every entry, then iterates with the base library's next (upvalue 1). */
50+
static int addr_pairs(lua_State *L)
51+
{
52+
for (const mhfu_addr_entry *e = k_mhfu_addr; e->name; e++) {
53+
lua_getfield(L, 1, e->name);
54+
lua_pop(L, 1);
55+
}
56+
lua_pushvalue(L, lua_upvalueindex(1));
57+
lua_pushvalue(L, 1);
58+
lua_pushnil(L);
59+
return 3;
60+
}
61+
62+
void mhfu_lua_addr_install(lua_State *L)
63+
{
64+
lua_newtable(L);
65+
lua_createtable(L, 0, 2);
66+
lua_pushcfunction(L, addr_index);
67+
lua_setfield(L, -2, "__index");
68+
lua_getglobal(L, "next");
69+
lua_pushcclosure(L, addr_pairs, 1);
70+
lua_setfield(L, -2, "__pairs");
71+
lua_setmetatable(L, -2);
72+
lua_setfield(L, -2, "addr");
73+
}

‎framework/mods/lua_host/bind_move.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static int hex(int c)
7474

7575
/* t.steer (mhfu/steer.h): turn = "still" | "hunter" | "away" | "fixed", rate (YAW units a
7676
* frame), total (degrees) over frames, dir (degrees), walls, stuck = { main, sub, mode }, curve
77-
* = the clips module's _turns string for the entry */
77+
* = the entry's string in the port's turns module */
7878
static void steer_of(lua_State *L, int t, mhfu_steer_spec_t *sp)
7979
{
8080
mhfu_steer_init_spec(sp);

0 commit comments

Comments
 (0)