Skip to content

Add a FUComplete build of the MHFU profile (PokeeFUC) - #4

Draft
Oddity-git wants to merge 96 commits into
mainfrom
MHFU-to-FUComplete-port
Draft

Oddity-git wants to merge 96 commits into
mainfrom
MHFU-to-FUComplete-port

Conversation

@Oddity-git

Copy link
Copy Markdown
Owner

Changes

Before: Pokee accepts only USA Freedom Unite (ULUS-10391). A FUComplete image is refused at the installer.

After: a build configured with -DMHFU_TITLE=FUC, named PokeeFUC, installs and runs a FUComplete image (Monster Hunter Portable 2nd G, ULJM-05500, patched by FUCTool) with Pokee's extras. The default FU build behaves as before.

This replaces #1, which GitHub closed when its branch was renamed to MHFU-to-FUComplete-port. The code and commits are the same.

How:

  • Title switch (host/title.hpp, MHFU_TITLE): separate corpus, overlays, game and player data (Pokee/FUC), and a per-feature flag for each extra.
  • Installer: accepts ULJM05500 only with FUC/PRELOAD.BIN and a plain ELF EBOOT.BIN; an unpatched image is sent to FUCTool; each build points a disc of the other title to the right build.
  • FUComplete's own code changes: its loader's changes to the game's code are compiled ahead of time, its patched overlays are dumped and built into libraries, and saves written with an earlier key still load.
  • Extras ported to FUComplete's addresses: analog camera, hold to gather, aspect Fill, Give Any Rarity Item, Empty item slot, Hide HUD, lock-on and damage numbers, UI Scaling, the sharpness gauge, the gather icon, layered armor, Eat in the Guild Hall, Item Sets and Change Equipment in the Guild Hall.
  • Kept off for FUComplete: Pokee's DATA.BIN mods (FUComplete has its own file replacer), True Attack Value (FUComplete shows true raw itself) and Show Key Quests (Freedom Unite quest-name addresses).
  • Texture packs read KTX2 (Basis Universal) images and a textures.ini with a byte order mark.
  • Name and install: user-visible "Pokee" reads "PokeeFUC" in that build, and a source build installs per user beside Pokee, in the app menu.
  • HLE: sceIoMkdir, which FUComplete uses to create its save folder.
  • CI fixes: coverage flags for C++ only, overlay and HUD fixtures pushed to the Android emulator, the save-test flake, the reviewed cppcheck exception's line, a non-UTF-8 diff, and changed headers that hold no code no longer failing the coverage gate.

Related issues

Replaces #1.

Validation

  • Linux: both titles build, and every test passes (63 in FU, 64 in FUC) on the current head, including new installer disc-check tests on synthetic ISO images.
  • A FUComplete boot reaches the village at full speed with every ported extra hooked in.
  • Checked by playing on Linux: the sharpness gauge, layered armor, KTX2 texture packs and the camera.
  • Changed-line coverage on the PR's last run was 83% (gate 80%).

Not verified

  • After merging Pokee's latest main: the analog camera's tilt in play (its new camera code and the FUComplete tilt fix share a game setting), and the Guild Hall features (Item Sets, Eat in the Guild Hall, Change Equipment).
  • macOS and Windows were not built.
  • Still red, as on main: the C++ formatting check and three cppcheck findings in Pokee's own code.

Checklist

  • I followed CONTRIBUTING.md, including the language, source-provenance, game-data and privacy rules. FUComplete's sources were read only for facts (disc id, file paths, NID, addresses); none of their code is included.
  • I updated affected documentation, or no documentation changes are needed.

Generated by Claude Code

claude and others added 30 commits October 19, 2026 20:46
FUComplete patches Monster Hunter Portable 2nd G (ULJM-05500), not Freedom
Unite, so it needs its own executable, corpus and data folder. Configuring
with -DMHFU_TITLE=FUC selects it:

- the installer accepts a FUComplete image: disc id ULJM05500, the FUC
  folder, and an EBOOT.BIN that is already a plain ELF; an unpatched
  Portable 2nd G is refused with a pointer to FUCTool
- data in Pokee/FUC; corpus, overlays, game and analysis in *_fuc
- overlay slots from the executable's *.ovl sections
- the Freedom Unite extras (camera, HUD, layered armor, damage numbers,
  gather icon, sharpness gauge, item box, lock-on) and DATA.BIN mods off
- sceIoMkdir, which FUComplete's loader imports
- databin.py reads a plain DATA.BIN; the scripts read the title from the
  build directory

The default build is unchanged apart from sceIoMkdir.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The automatic codegen jumped to a local label when a J or JAL target was
decoded in the same unit, before checking whether the target is a PSP
import stub. FUComplete's plain executable keeps its stubs as `jr ra; nop`
right after .text, so module_start's calls ran the stub's return instead of
the HLE import and the game never started its main thread. Import stubs are
now checked first, and a regression test covers a call into a stub that sits
in the caller's unit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's Portable 2nd G has the same camera, aim, gather, carve,
fishing-spot and farm code as Freedom Unite at other addresses. Each
address the two features use now has its FUComplete value beside the
Freedom Unite one, selected at compile time by the build's title, with the
same instruction checks before anything is hooked or read:

- the camera update at 0x08886B54 (0x2FC lower), its 38 checked words,
  the aim pitch clamp (0x2948 higher), the hunter list at 0x089C6CB4;
- game_task's aim function, weapon table and bow case;
- the gather search, area lookup, stage-id table, carve table, spot search,
  lobby_task's farm handler and game_sub's farm counts;
- the overlay slots and the GameSys pointer in guest_layout.hpp.

Every hunter field offset matched: only global addresses and calls differ.
title.hpp gets a flag per extra; analog camera and hold to gather are on
for both titles, lock-on stays Freedom Unite's and is kept out of the
camera driver on FUComplete. The camera-unit scan in CMake looks for the
title's update address, and main.cpp prepares the camera for a FUComplete
corpus that matches its executable. Tests use the title's addresses and
skip the extras that are still Freedom Unite's only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The FUComplete build installs beside the standard one, so everything the
player reads says which is which: the window title, the menu's header and
About row, the setup screens, the installer's dialogs and messages, and
the data folder and log messages. MHFU_APP_NAME in title.hpp is "Pokee"
for Freedom Unite and "PokeeFUC" for FUComplete. It is a macro so it joins
the string literals it appears in. The executable, its data folder
(Pokee/FUC) and code identifiers keep their names, and the Freedom Unite
build's text is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
A semicolon ended the dialog text after "passed its checks", so the
question whether to copy the image, its size and both paths were a
discarded expression (GCC warns: ignoring the result of operator+). The
dialog showed the check result with Copy / Use it where it is buttons and
no question. The two parts are one string again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Extras that are still Freedom Unite's only did nothing in the FUComplete
build, while the menu still offered them: UI Scaling and Hide HUD among
others. Each such row is now drawn only where its feature runs, by the
title's flag for it in title.hpp: Aspect ratio, UI Scaling, Hide HUD,
Empty item slot, the Lock-on section, Damage numbers, Modern sharpness
gauge, Gather icon, Layered armor and Give Any Rarity Item. A feature
ported later brings its row back with its flag. Freedom Unite's menu is
unchanged. The renderer UI test exercises those rows only where they are
shown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
packaging/linux/install_user.sh copies a build's executable, bundled
libraries and overlay libraries into ~/.local/share/pokee or
~/.local/share/pokee-fuc with a launcher that raises the stack limit, and
installs a .desktop entry and the emblem as its icon under ~/.local/share,
so the game is in the application menu without root, Flatpak tooling or a
container SDK. The build's title picks the name, app id, directory and
entry, so the FUComplete build installs as PokeeFUC
(io.github.teamgdb.PokeeFUC) next to Pokee and never replaces it. Running
it again updates the copy; --uninstall removes it.

The window now tells the desktop its app id (SDL_SetAppMetadata, before
video starts in the game and in the setup dialogs), so the window is
matched to its own entry, icon and taskbar group.

Verified on Linux: the entry validates with desktop-file-validate, and
the installed launcher starts the FUComplete build with the per-user data
folder and loads its save (headless).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's loader rewrites words of the executable's code when the game
starts: jumps from the file I/O into its own code at 0x08A35910 (its file
replacer), and the immediates of the save keys for its Save Region
option. A generated unit runs the words it was translated from, and falls
through its own instructions without asking the dispatcher, so none of
those changes ran: the hooks never reached FUComplete's code.

The FUComplete build now watches the executable's code sections during
the first minute. Once the loader's changes have held for a second, it
writes the executable with them as EBOOT.FUC.ELF beside EBOOT.ELF and says
how to generate the corpus from it. main.cpp starts from EBOOT.FUC.ELF
when the linked corpus was generated from it, so the hooks are compiled
code that jumps into FUComplete's (interpreted) loader. A configuration
that patches other words writes the file again; MHFU_FUC_CODE_PATCHES=0
turns the watch off. Freedom Unite's build is unchanged.

Verified on Linux with FUComplete's default configuration: the loader
changed 48 words; a corpus generated from EBOOT.FUC.ELF rebuilt 4 of 67
units; the game starts from that file, finds nothing left to change, and
FUComplete's hook ran (its buffer at 0x08A35890 holds the index and digits
of the file being read, which nothing else writes). Speed stayed at 100%.
fuc_code_patches_tests covers finding the code sections, counting changed
words, writing the patched executable and the watch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
With a game option on (the byte GameSys copies to +0x6ADE0, 0x088496F4 in
FUComplete), the follow update picks again, every update, the vertical
preset whose height is nearest its view and points camera +0x70 at it
(0x088876A8 in Freedom Unite, 0x088873AC in FUComplete). That dropped the
preset the driver had bent for the tilt, so the tilt eased back to the
game's level as soon as it was made, even with the stick held. Seen in
FUComplete, whose new character had the option on; the code is the same
in Freedom Unite.

While the driver has bent the preset for an update, the option reads as
off for that update and is put back afterwards. Both of the update's reads
of it join the camera's code signature for each title. A test checks that
the update sees it off only while the driver owns the pitch, and that the
player's setting is restored each time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's Save Region option changes, in the game's code, the key the
game passes to the savedata utility. With that change compiled in, a save
written before it (with the executable's own key) failed its PARAM.SFO hash
and the game called it broken.

The savedata HLE now keeps every key a game passes in savedata_keys.txt in
the data folder. When a save does not match the hash the current key gives,
it loads with an earlier key of the same game, and says so; the game's
next save writes the folder with the current key, so the save moves to it
in the normal course of play. A damaged save matches no key and stays
broken. Freedom Unite passes one key, so nothing changes there.

Verified on Linux with a save made while FUComplete's patches did not run:
with them compiled in, it failed with the current key, loaded with the
earlier one, and the game reached Pokke Village with the hunter. Tests cover
keeping the keys, the fallback, a save written again, and damaged data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's loader patches overlays as they load: lobby_task gets 9
words changed, jumps into its own code, and game_task is changed too. Such
an overlay matches no library built from DATA.BIN, so it ran in the
interpreter: slower, and the wrappers on its functions could not be
installed. Without the aim wrapper a drawn sword and shield (action 1001)
counted as aiming and the analog camera stopped turning.

The FUComplete build now dumps every overlay it has no library for into
overlay_dumps in its data folder (MHFU_DUMP_OVERLAYS still names another
directory; 0 now turns dumping off). A dump is named after its slot and its
image, overlay_09A5A580_lobby_task.bin, since the slot is shared, and is
written once a run. scripts/build_overlay_dumps.sh builds a library from
every dump with add_overlay.py; libraries are named by the image's hash,
so a patched overlay gets its own beside the unpatched one.

Verified on Linux: FUComplete's lobby_task was dumped and built, the
village installed it instead of interpreting it, and the interpreter ran
17 thousand instructions on the way to the village instead of 7.3
million. game_task needs a quest to be dumped. Tests cover the dump names
and directory for both titles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Each line of the follow camera's trace now ends with the followed hunter's
bytes +0x298, +0x299 and +0x29C, which choose among the update's fixed
heights and the preset, and the vertical snap option, or -1 where they
cannot be read. These told the drifting tilt in FUComplete apart from the
snap option: the option was off and the preset was used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Fill widens the game's view to the window's shape by writing the aspect
ratio into the camera object the projection is built from. FUComplete's
executable has the same set-up, reset, projection and culling code 0xDC0
higher; only the store of the camera object differs (0x089C6CB4). The
signature has its FUComplete table beside Freedom Unite's, picked by the
title, main.cpp prepares it for a FUComplete corpus that matches its
executable, and present_frame runs it under the title's kGameAspect, so
the Aspect ratio row is back in the FUComplete menu.

Verified: the FUComplete executable passes the signature at start (no
mismatch logged); the aspect tests pass for both titles. Not yet looked at
on a wide window.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The test cleared MHFU_GIVE_ANY_RARITY by setting it to an empty string.
On Windows, _putenv_s removes a variable given an empty value; on Linux
and macOS setenv keeps it, and an empty value of a switch reads as on, so
"giving at any rarity is off by default" failed there. The helper now
unsets the variable for an empty value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's executable has the item menu's give check at 0x08829FC4, the
item id lookup at 0x08826F8C, the pouch slot at 0x088567AC and the item
table at 0x0899A238, with the same instructions as Freedom Unite's apart
from the calls and the table's address. The check's address, the item
table, GameSys and the signature each have their FUComplete value beside
Freedom Unite's, picked by the title, and the wrapper is installed under
the title's kGiveRarity, so the row is back on the FUComplete Mods page.

Verified: every FUComplete word of the signature against the executable;
the give-rarity tests pass for both titles. Not verified in play: giving
needs another hunter in the Guild Hall.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The item bar's step (0x088DAB44), the pouch's packing, the HUD strip's
calls, the name check and print, and game_task's first selection (from
0x09A63328 in FUComplete's game_task) have the same instructions in
FUComplete as in Freedom Unite at their own addresses. Every address,
return address and call word of the feature has its FUComplete value
beside Freedom Unite's in empty_item_slot.hpp, the signatures have their
FUComplete tables, and the wrappers go in under the title's
kEmptyItemSlot, so the row is back in the FUComplete menu.

Verified: all 34 FUComplete words of the two signatures against the
executable and FUComplete's patched game_task; the tests pass for both
titles. Not yet looked at in a quest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Hiding the HUD wraps the display list's packet insert, the text queue
appends and the character draw in the executable, and recognises the
HUD's functions in game_task and lobby_task by their prologues.
FUComplete has all of them at its own addresses (the insert at
0x088595E8, the quest HUD at 0x09A5B508 in its game_task, the lobby
callback at 0x09A673D0 in its lobby_task) with the same instructions;
only the calls and the text context's address (0x08A5E278) differ.

game_hud.cpp picks each address with at(Freedom Unite, FUComplete) by the
title, the signature has its FUComplete table, CMake scans the corpus for
the title's HUD functions, and the HUD is watched under the title's
kHideHud, so the row and the F7 key work in the FUComplete build.

Verified on Linux: every FUComplete word of the signature and of the
overlay prologues (against FUComplete's patched game_task and lobby_task);
in the village, F7 hid the name over the hunter and brought it back. The
HUD tests pass for both titles. The quest HUD not yet looked at.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Both read the quest's entity list and the camera object, and damage
numbers watch game_task's hit and status-tick functions. In FUComplete:

- the entity manager is at 0x09D146E0 (game_sub's 456 loads of it read
  lui 0x09D1, +0x46E0 where Freedom Unite's read lui 0x09D2, -0x67A0);
- the hunter vtable is at 0x089B6378, found by its entries;
- lock-on's camera-object check reads the same code 0xDC0 higher;
- the hit adjustment is at 0x09AC2BD8 in its game_task (an 80-word match)
  and the status tick at 0x09ACD860.

The world extras in present_frame are split by feature (damage numbers,
lock-on and its hunt portraits, the gather icon) under their own title
flags, as is the renderer's note of the world camera; kLockOn and
kDamageNumbers are now on for both titles, and a carve's monster icon
needs lock-on's list. The hunt portraits' sheet is a runtime texture
address not yet traced in FUComplete, so they may not show there.

Verified: the lock-on, damage-number and analog camera tests pass for both
titles (lock-on's tests now use the title's addresses). Not yet looked at
in a quest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
These extras read the game's HUD textures. Traced with MHFU_TRACE_CAPTURE
in a FUComplete quest: the HUD's sheets sit 0x4400 below Freedom
Unite's. The item atlas is at 0x091830A0 (the item bar's icon and the
map's marks are drawn from it), the HUD sheet at 0x091527A0, and the five
window-frame textures UI Scaling recognises at 0x0915F820, 0x09160020,
0x09160820, 0x0915FFA0 and 0x091600A0; the area map stays at 0x0419C200.
The hunt icon's sheet and palette take the same shift (0x09197900,
0x091AA110); no monster icon was in the capture to confirm them.

The gather icon's pictures read the item records, icon sprites, sprite
rectangles and tints, which FUComplete's executable finds at 0x0899A23C,
0x08938014, 0x089366F0 and 0x08937FA4 (gather_art.cpp has their code's
FUComplete table). UI Scaling runs on Hide HUD's tracking of the 2D
draws, now on for FUComplete; the sharpness gauge and the gather icon get
their own title flags. Layered armor stays off: FUComplete's equipment
code and records differ.

Tests use the title's texture and table addresses; the HUD replay
fixtures are Freedom Unite captures and run for that title only. All
tests pass for both titles. Not yet looked at in a FUComplete quest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
UI Scaling sizes the hunter's name plate under the clock as Status. It
told the plate's 8 by 16 end apart by its place, x 96, where Freedom
Unite's plate (8, 56)-(104, 72) ends. FUComplete's plate is 8 shorter,
(8, 56)-(96, 72), so its end at x 88 was no piece: it kept the game's
place while the rest of the plate shrank toward the clock, and floated
beside it. The end's place now follows the title.

Found by replaying a FUComplete quest's MHFU_TRACE_CAPTURE log with the
player's sizes (map 65, status 70, items 50, names 70): the end was the
plate's only piece left out, and is now sized with it; every other quest
HUD quad of that capture is a piece. The replay tool now also replays a
capture in a FUComplete build; only its Freedom Unite fixtures are
skipped there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The settings tests drew the plate's end at x 96, Freedom Unite's place,
and failed in the FUComplete build since its end moved to x 88. They now
draw it at hud_scale::kPlateEndLeft. Both titles' suites pass again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The profile README's FUComplete table lists every extra as on at
FUComplete's addresses, except the fast item box, which FUComplete has
already, and layered armor, which stays off. It names the data that
moved (GameSys, the camera object, the entity manager, the HUD's
textures) and the shorter name plate. COMPATIBILITY.md gets a section
for the FUComplete build with what a player and scripted runs checked on
Linux, and what is still unchecked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The modern sharpness gauge finds the colour ends of the hunter's weapon
from the weapon records, the model classes and the class tables, and
the knife's sprites and colours, all data in the executable. Those were
still Freedom Unite's addresses, so in FUComplete the gauge found no ends
and the colour stayed whole instead of emptying as it is used.

FUComplete keeps them lower, read from its own code: the weapon records
at 0x089574C8 and the class tables at 0x08976738 (0x088DD4DC, the colour
lookup), the model classes at 0x08975984 (0x0885CCE0), the knife's
sprites and colours at 0x08935DA0 and 0x08935DA8 (0x0884111C). The
hunter's sharpness fields are the same. Reported by the player; the tests
pass for both titles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
The Mods page said "Mods need the game's disc image, which is not open"
in the FUComplete build, where the disc is open: the list of Freedom
Unite mods is off there on purpose, because FUComplete replaces files
with its own loader. The page now says so, and where FUComplete's own
replacements go (its NATIVEPSP folder in ms0/PSP/SAVEDATA/FUCDAT) and that
FUCTool sets them up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Layered armor stands in for the armor model lookup, the hunter's load
step and task, and the lobby's equipment calls, and reads the armor
tables, the equipment box and the hunter records. FUComplete has all of
it with the same instructions and record layout at its own addresses:

- the lookup at 0x088517A8, the sex lookup at 0x088515D0, the part check
  at 0x08851B68, the resolve at 0x088D8CD0, the file load at 0x088DE504,
  the part model at 0x08851994 and the resident load at 0x08866E08;
- the load step at 0x088C37AC, and the hunter task at 0x088BF3F8: the same
  dispatch on the task word, compiled with a 32-byte frame where Freedom
  Unite's has 4576, and the hunter in s2;
- lobby_task's 13 calls, each matched with a 48-word window and checked by
  the call before it (its two copies of one block included);
- the armor tables 0x2C24 lower, the text block at 0x089C7510, GameSys,
  and the hunter, file and model objects at 0x09A4099C, 0x09A4AE04 and
  0x09A4AE1C, each read from FUComplete's own instructions and checked by
  the number of its uses (14, 122 and 62 in the executable, 20, 161 and 23
  in lobby_task, as in Freedom Unite).

Each has its FUComplete value beside Freedom Unite's, picked by the
title, with FUComplete signature tables, and CMake scans the corpus for
the title's functions. kLayeredArmor is now on for both titles, so the
Layered armor page is back in the FUComplete menu.

Verified on Linux: every FUComplete word of the nine signatures against
the executable; in the village, with every part set to nothing, the
hunter was drawn bare instead of in the worn armor. The tests pass for
both titles. Not yet checked: chosen pieces, the equipment change, a
quest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
FUComplete's packs live in textures/ULJM05500, but the Video page and the
import row still pointed at textures/ULUS10391.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
A textures.ini saved by a Windows editor starts with a UTF-8 byte order
mark. The line was "\xEF\xBB\xBF[options]", which is no section header,
so the pack's hash setting was never read and the import refused it:
"[options] does not say which hash the pack uses". PPSSPP reads such
files. trim() now drops a leading byte order mark, in each of the
texture pack's ini readers. A test checks a pack whose ini starts with
one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
libc++ (macOS, Android) does not pull in <sstream> through the other
headers, and Ruff flags base_text in add_overlay.py as unused (F841).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Packs made for PPSSPP increasingly ship their images as KTX2 files holding
Basis Universal UASTC or ETC1S data, often zstd-supercompressed, which
stb_image cannot read. TexturePack::decode now recognises the KTX2
identifier and transcodes the first level to RGBA with the unmodified
Basis Universal 2.1.0 transcoder and the zstd 1.5.7 decompressor it
ships, both vendored under third_party/basis_universal and recorded in
SOURCE_PROVENANCE.md and THIRD_PARTY_NOTICES.md. Hash-named images in a
pack's top folder may be .ktx2 too. HDR data is refused.

Tested with synthetic UASTC+zstd, ETC1S and HDR images in the texture
pack suite, and with a community Portable 2nd G pack of 6101 KTX2 images
in the FUComplete build: title, loading screen, HUD and village drawn
from it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
Oddity-git and others added 30 commits October 21, 2026 00:02
The game writes the camera's field of view every frame, then its projection
update builds the view only when that value differs from the one it last built
with. Wrapping that update lets a new Video setting scale the value after the
game sets it and before the projection is built, so the change shows at once,
in the village and in quests, without a restart. Scaling after the update
instead would grow every frame, because the value is not written again before
the next wrap.

Video > Field of view offers 50% to 150% of the game's own field of view
(video.fov_scale, MHFU_FOV_SCALE). The target is checked to be a plausible
field of view before it is written. MHFU_TRACE_FOV prints the projection
camera's value and when the update is wrapped.

The wrapper is installed only while the setting is not 100%, and it passes
through when it is. Tests cover the scaling and its refusal of values outside
memory or outside a field of view's range, and the wrapper being installed;
both suites pass. A scripted run to the village with the setting at 140% drew
the view wider and the camera's field of view read 1.41565 instead of 1.01118.
…tion

The player confirmed the Video field of view scale changes the view while the
game runs. FUComplete's own option is the player's field of view in a quest and
is read by its loader at boot, so the live control is Pokee's, not that one.
The projection update's wrapper is now called: it scales the field of view
the game wrote (0.8 at 125% builds from 1.0) before the update runs.
MHFU_TRACE_FOV prints the camera's field of view once, and again only when
it changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmTUbk7nDg9Ffkqb3wtMax
release_linux.sh takes --title (or MHFU_TITLE) and, for FUC, uses
game_fuc/EBOOT.ELF (the loader-patched EBOOT.FUC.ELF) and the FUC ISO,
builds the FUC corpus and overlays, stages the .code files the patched
overlays need, and names the tarball pokee-fuc. The Flatpak gets its own
app id, desktop file, metainfo and launcher, so PokeeFUC and Pokee install
side by side.

The FU path is unchanged: same app id, names, game directory and executable
hash check.
The landing page now says what PokeeFUC is, how to make the patched image
with FUComplete and FUCTool, and how to install the Linux release or build
from source. The base Pokee/Freedom Unite project and its credits stay, under
their own section.

The profile README's first build block now generates from EBOOT.FUC.ELF after
the first start, not from the disc's executable: FUComplete's loader rewrites
the code at start-up, and the corpus has to come from the loader's result.
It separates the two kinds of mod that matter here: game-content mods,
which go through FUComplete's File Replacer and NATIVEPSP, and features
built into PokeeFUC itself in C++. The first half covers the data folder,
FUCTool's nativePSP generation, enabling the replacer, testing with a copied
data folder, and what is still unknown (file names, the whitelist, quest
packs, size changes). The second half covers title flags, signature checks,
hooks, tests, traces and the rules.

Linked from AGENTS.md and the root README.
A release already contains the corpus from EBOOT.FUC.ELF. Its first start
writes that file and runs the unpatched executable once, then the next start
uses the compiled code. The README and the open work now say so; the
first-run message itself is left for the next build.
The arm64 APK installs beside Pokee, keeps its data across updates signed with
the same key, and asks for the patched image on first start. It is a test
build, checked on the emulator only so far. Uninstalling an earlier APK
deletes its saves, so install over it.
The arm64 build boots from the patched image on a phone, the touch controls
work, and 60/90/120 fps interpolation no longer bursts models after the
vertex flush. Fill with Interface at the edges keeps menus and loot notices
whole. The README, COMPATIBILITY and the open work now say so; versionCode
714 is the build tested.
The offline Gathering Hall offers the Guild's four-hunter quests to a
hunter alone. With game.hub_scaling on, a large monster in a quest
started from the hall keeps 65% of its health: its current and most
health are cut when it appears, and put back when the switch goes off.
MHFU_HUB_SCALING_HEALTH sets another percentage (1 to 100).

The gate is the stage loaded just before the quest started, traced in
FUComplete: the offline hall is stage 85, Pokke Village 139. The
accepted quest's id is not kept in guest memory during the hunt, so the
place it came from is what tells the two apart. An active ad hoc session
turns the scaling off, so online play is untouched, and village quests
never change. The trace prints the stage and each cut.

The unit test covers the hall, the village, an online session, the flat
cut and the restore. Seen in game: a 1-star Guild Bulldrome quest felt
like its village counterpart, and a village quest printed last stage 139
with scaling off.
Scale Guild Hall quests for one hunter, offline
Take Pokee's True Attack Value element and status display, the bowgun
reticle and stamina fill with the interface sizes, and the v0.4.4
formatting. The new true_element extra starts as kTrueElement = kTrueAttack
(Freedom Unite only, where FUComplete has its own True Raw), gated in the
frame call and described by the same menu row.

true_element_tests set MHFU_TRUE_ATTACK to empty on Linux, which the
switch reads as on; unset it instead, as true_attack_tests already does.
System > Saves gets a guided Sync saves screen: choose a folder (a path on
a computer, a document tree on Android), upload this device's save, then
Sync now on the other device. The folder holds only this game's save files,
so Google Drive, Syncthing, Nextcloud or a shared folder all work.

The two copies are compared by a SHA-256 of PARAM.SFO and the data files it
lists: upload when only this device changed, download when only the folder
changed, ask when both did, and keep the copy being replaced as a backup.
Upload/Use-the-folder buttons force one direction. The README has the setup
steps for Android (Drive app and the folder picker) and Linux (rclone,
Insync, Syncthing).

Android keeps the picked folder permission across restarts and reads/writes
the save through its documents bridge. Tests cover the hash, the copy, the
decision and finding the game's save folder.
On a 60 Hz panel 45 fps is 1.5 refreshes a present: the cadence
alternates one and two refreshes and reads as judder. The governor's
ladder now keeps a rate only when the display's refresh divides by it,
so 60 Hz offers 60 and 30, 90 Hz offers 90, 45 and 30, and 120 Hz offers
120, 60 and 30. An unusual refresh with none of the known rates on its
grid keeps the old ladder so there is still a way down. A display change
rebuilds it.

Android has no environment to set MHFU_TRACE_RENDER in, so Performance:
Log now also prints the render split, showing where the renderer's CPU
time goes as well as where it waited.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants