phonehack patches
Table of Contents
- The stack
- 0001-cross-to-android.patch (build glue; required)
- 0002-phonehack-game-end-hook.patch (non-cheating hook; non-fatal)
- 0003-phonehack-combat-hook.patch (non-cheating hook; non-fatal)
- 0005-phonehack-speech-hook.patch (non-cheating hook; non-fatal)
- 0006-vault-guard-newsym.patch (upstream backport; non-fatal)
- 0009-phonehack-multi-stop-hook.patch (non-cheating hook; non-fatal)
- 0004-cross-to-host-test.patch (test-only build glue; not applied on Android)
- Rebasing the stack onto a new NetHack release
The patches phonehack carries against the pinned NetHack core (upstream/,
submodule tag NetHack-5.0.0_Released). They are deliberately minimal: build
glue, three tiny non-cheating event hooks, and one backport of upstream's own
post-release bug fix. We add no game logic of our own. All integration goes
through the shim windowport (see ARCHITECTURE), so these patches only add
cross-compile targets, three one-line upcalls, and a verbatim upstream fix.
They are applied idempotently by build/build-android-libs.sh (Android) and
build/test/build-host.sh (host test), not by a manual step.
The stack
0001-cross-to-android.patch (build glue; required)
Adds the CROSS_TO_ANDROID cross-compile target to NetHack's Makefile hint
system and fixes one link error. Touches only build hints + config.h +
libnhmain.c; no game code.
sys/unix/hints/include/cross-pre1.500- theCROSS_TO_ANDROIDvariable block (per-ABItargets/android/<abi>output dir,CROSS~/~HACKDIRsetup).sys/unix/hints/include/cross-pre2.500- the NDK clang recipe: toolchain paths fromANDROID_NDK~/~ANDROID_ABI~/~ANDROID_API(default NDK r27d), the AndroidCFLAGS(-DSECURE -DDLB -DLIBNH -DSHIM_GRAPHICS -DNOTTYGRAPHICS…,-fPIC, function/data sections), and theSYSOBJset built fromsys/libnh/libnhmain.c+ the shim windowport + minimal unix glue.sys/unix/hints/include/cross-post.500- thelibnh.aarchive rule and theandroidpkgtarget.include/config.h- excludesCROSS_TO_ANDROIDfrom the external-COMPRESS block (see the#undef COMPRESSnote in BUILDING): a failed fork on Android leaves a stray<save>.Zwhose later uncompress deletes the real save.sys/libnh/libnhmain.c- drops a duplicateafter_opt_showpathsthat the 5.0.0 release also defines insrc/earlyarg.c; the duplicate breaks the link. This is the one edit that is not pure build config.
Because the WASM port (CROSS_TO_WASM) already established this pattern
upstream, our block is modeled on it and slots into the same hint files.
0002-phonehack-game-end-hook.patch (non-cheating hook; non-fatal)
One line in src/end.c really_done(), right after fixup_death(how):
ph_note_game_end(how) latches the game-end type ('how') so
nethack_jni.c's nativeQueryEnd() can format the exact death/ascension line
for the in-app end-of-game screen. ph_note_game_end lives in
nethack_jni.c; the patch adds only the call. Applied non-fatally: if it can't
apply, nativeQueryEnd degrades to a killer-format-only prefix and the death
screen still works.
0003-phonehack-combat-hook.patch (non-cheating hook; non-fatal)
Three bare one-liner ph_combat_event(...) calls at upstream combat funnel
points, feeding the perceivable-combat UI:
src/uhitm.cknown_hitum()- the hero attacks a monster (read beforehmon()can free the target).src/mhitu.chitmsg()- a monster lands a blow on the hero.src/mon.cxkilled()- the hero kills a monster.
All logic lives in nethack_jni.c's ph_combat_event(), which fires only for
a perceivable combatant (canspotmon) and forwards only an event kind, a
Hallucination-safe name, and the target tile - never damage, to-hit, or HP (see
the non-cheating invariant in ARCHITECTURE). Applied non-fatally: if it can't
apply, the function is simply never called and the combat feed is off; the build
does not abort.
0005-phonehack-speech-hook.patch (non-cheating hook; non-fatal)
One macro in include/sndprocs.h: the no-soundlib SetVoice(mon, tone, vol,
moreinfo) (which upstream expands to nothing when no soundlib is integrated)
instead forwards (mon, vol, moreinfo) to ph_note_voice() in
nethack_jni.c. That shim function records the speaker's m_id + the
voice_* moreinfo flags for the current turn; shim_putstr then combines it
with PLINE_VERBALIZE (still set during putmesg, so read directly - no patch
needed for that half) to attribute spoken message lines. A speaker is named
only via canspotmon + distant_monnam at message time. set_voice()'s own
#ifdef SND_SPEECH body is untouched; zero game-behavior change. Applied
non-fatally: if it can't apply, speech attribution degrades to
spoken-vs-not-spoken (no speaker names, no bubbles) and the build goes on.
Not applied by the host test build (which patches only 0001 + 0004), so no
host-side stub is needed.
0006-vault-guard-newsym.patch (upstream backport; non-fatal)
A vault guard that has finished escorting you is parked at <0,0> ("never-never
land", vault.c parkguard()) and stays alive there until its temporary
corridor can be torn down. Two places then tried to redraw that off-map spot,
and newsym() answers an off-map coordinate with impossible(), so the player
got "Program in disorder!" right after "Suddenly, the guard disappears."
Backported verbatim from two post-5.0.0 upstream commits:
c42d35ea"avoid newsym(0,0) for vault guard" -src/monmove.cpostmov()takes thegd_move()return of 1 as "the guard moved" and redraws it at its new position, which is<0,0>. Guard the call withif (mtmp->mx), matching the identical guard 145 lines earlier.d13eceb2"eliminate one more source of vault guard newsym msgs" -src/display.csee_monsters()walksfmonand redraws every monster, parked guard included. Skip it via the newPARKEDMONSTER()macro ininclude/monst.h. This one fires on redraw and on restore, which for phonehack means every background-save/resume cycle, not just^R.
We take only the two guards and the macro, not that commit's MON_PARKED
mstate bit and its restore.c fixup: the macro upstream added doesn't use
the bit yet, and skipping it keeps the backport off the save format entirely.
Drop this patch at the next release bump if the fixes are in the new tag.
Applied non-fatally: without it the game still plays correctly (the message is
cosmetic; newsym() returns immediately and no game state is touched).
0009-phonehack-multi-stop-hook.patch (non-cheating hook; non-fatal)
One weak upcall in nomul() (src/hack.c): when a counted command (20s)
is cut short, it reports how many of the player's own count were left, so the
progress chip counts actions rather than turns and can show the line that
stopped it. Without it the chip falls back to counting turns.
0004-cross-to-host-test.patch (test-only build glue; not applied on Android)
Adds a CROSS_TO_HOST target mirroring CROSS_TO_ANDROID but with host ==
target (system cc~/~ar, no NDK, no -fPIC), so the off-device C harness in
build/test/ can build libnh.a natively. Same three hint files, plus it
relies on the after_opt_showpaths de-dup from 0001 to link. Applied only by
build/test/build-host.sh; the Android build never applies it.
Note the dependency: 0004 and 0001 edit adjacent regions of the same hint
files, so build-host.sh applies 0001 then 0004 in order and gates each on a
text sentinel rather than a reverse-check.
Rebasing the stack onto a new NetHack release
The whole point of the shim-windowport design is that a version bump is mechanical. Discipline:
- Keep the shim boundary. Do not add game-logic edits. If a new feature needs
data, prefer a read-only
nativeQuery*gated on the parked safe point (mapReady()) over a patch. Patches are a last resort and must stay one-liners whose logic lives innethack_jni.c. - Bump the submodule, then re-apply. Point
upstream/at the new tag andgit -C upstream apply patches/*.patch. Fix any offset/conflict, then refresh the patch files so they apply cleanly against the new base. The two event hooks (0002,0003) apply non-fatally, so drift there degrades a feature rather than breaking the build - but fix them anyway. - Re-verify the callback contract. Diff
win/shim/winshim.cfor new/changed callback names or signatures and mirror them in theshim_*dispatch innethack_jni.cand thecb*methods inNetHackBridge.kt(dispatch is by name; thefmtstring is advisory). - Re-check status enums. Compare
include/botl.h(theBL_enum andBL_MASK_*) againstGameSession's status handling and thecondtab, since the status bar decodes those indices. - Re-check glyph flags. If new
MG_*glyph flags appear, decide whether the Compose renderer must honor a corresponding rc option (as it does forhilite_pet~/~hilite_pile; see ARCHITECTURE). - Re-check the COMPRESS / build guards. Confirm the
config.hCOMPRESS block and thebuild-android-libs.sh#undef COMPRESS/REPRODUCIBLE_BUILDsentinels still apply against the new tree. - Rebuild and smoke.
cd android && ./gradlew assembleDebug, then run a new game: move, inventory, save, background, kill, restore. Run the host and on-device save-integrity harnesses (see BUILDING).
Reminder: remember to git -C upstream reset --hard between an Android build
and the host test build - each bakes its own target into the generated Makefiles
(the host-vs-Android pollution gotcha in BUILDING).