Repository navigation
Half-Life 2 (experimental) and one checkout/installer for both games - #22
Merged
Merged
Conversation
The Linux depot layout (module folders, program name, excluded modules, indirect call roots) now comes from tools/games.py, picked by `game` in config/local.json. Portal 2 stays the default, and its prepared layout is byte-identical. elfprep also accepts relocations without a symbol, which the Half-Life 2 modules use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 20th anniversary build is vectorised with SSSE3: 400 functions used pshufb, palignr, movddup, pmaddubsw and a few SSE2 forms the lifter did not know (psadbw, pextrw/pinsrw, movntq). The remaining 36 untranslated functions are CPU-dispatched (AES-NI, PCLMUL, SSE4.1) and stay off since CPUID does not report those extensions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Half-Life 2 asks for SDK 1.60 versions (SteamUser023, UGC020...) and for ISteamTimeline V004 of SDK 1.61. The generator now merges several Steamworks.NET packages into one table keyed by interface version, and the generated methods call the host object's vtable slot directly: the dylib's flat functions are nothing more than that tail call, but they follow the layout of the dylib's own SDK, which would be wrong for a version from another SDK. Portal 2's 772 entries are unchanged. Also adds SteamInternal_SteamAPI_Init, SteamInternal_GameServer_Init_V2 (SDK 1.59+) and a no-op SteamAPI_SetBreakpadAppID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iOS Steam stub defines the flat functions by name and has no vtables, so calling slots for every version broke Portal 2 on iPhone (caught by test_steam_stub_generated_is_up_to_date). Only versions from another SDK than the loaded dylib's go through the vtable slot now; Portal 2's generated bodies are byte-identical to before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 20th anniversary build is compiled with _FORTIFY_SOURCE and a newer glibc: __printf_chk, __vfprintf_chk, __fdelt_chk, __longjmp_chk, __sigsetjmp/siglongjmp (with the signal mask), __rawmemchr, __strtok_r and the XSI __xpg_basename/__xpg_strerror_r. Also geteuid/getgid/getegid, getlogin_r, rand_r (glibc's algorithm), fcntl with Linux commands and flags, signal() limited to SIG_DFL/SIG_IGN like sigaction, valloc, dladdr and a one-frame backtrace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getaddrinfo/freeaddrinfo and getifaddrs/freeifaddrs build their lists in guest memory in the i386 Linux layout, with Linux flags, families and EAI_* codes. Also getpeername, getsockopt (sharing setsockopt's option table), shutdown and inet_pton/inet_ntop/inet_ntoa. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/proc/self/exe and the default run arguments assumed portal2_linux and -game portal2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Portal 2 ships mssmp3.asi and binkawin.asi in bin/linux32 of the game copy; Half-Life 2 has them only in the Linux depot. The build and the merged file view now look in both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The static pass only lists imports reached by known call sites, and the first launch trapped on memalign right away. All imports of the prepared modules now have a shim: the memalign family, sqrt/exp2f/fmaxf/fminf, fscanf/swscanf, wcstoll, the fortified wide and stpcpy/fread variants, nanosleep/sleep/times, raise, pthread_setname_np, isatty, ctermid, setbuf, freopen64, shm_open, __assert_fail and tcgetattr/tcsetattr for non-terminals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Message boxes are logged instead of shown, SDL_GetWindowWMInfo reports that there is no X11 window, and Direct3DCreate9Ex answers D3DERR_NOTAVAILABLE so the engine uses Direct3DCreate9. Fontconfig configurations keep one reference per handle across FcConfigSetCurrent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fcntl translates POSIX record locks (struct flock and flock64 in the i386 layout): the launcher's single-instance lock used F_SETLK. - CPUID-gated instructions (AES-NI, PCLMUL, SSE4.x, AVX, RDRAND, PadLock) now trap where they stand instead of rejecting the whole function: Crypto++ keeps AES-NI and portable paths in one function and picks one from CPUID, which does not report AES. - A guest SIGILL handler may be registered (Crypto++ probes SSE2 and CPUID under signal + setjmp); it is never delivered. - discover_game.py writes the roots of the configured game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Used by the crypto code of Half-Life 2's filesystem_stdio.so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Translate mssmixer.mix, the Miles mixer plugin Half-Life 2 loads, and hide untranslated .mix files like .asi ones. Its segment save/restore (push/pop ds, es) is lifted for a flat model. - vaudio_miles sets a SIGSEGV handler around AIL_startup: registered, never delivered, like the Crypto++ SIGILL probes. - The launcher leaves SteamAppId set but empty; treat it as missing so SteamAPI_InitEx gets the configured app id. Its failure log now lists the app id and the interface versions. - More indirect call roots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The size parameter of StartPlaytimeTracking, StopPlaytimeTracking and CreateQueryUGCDetailsRequest is named unNumPublishedFileIDs, which the generator did not recognise; Half-Life 2 calls StartPlaytimeTracking at start-up. The arrays are read by Steam. Portal 2 gains the same three methods of UGC016; the iOS stub is regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Half-Life 2's ServerBrowser asks IsRefreshing(NULL) at start-up. Server list requests are not bridged, so the guest can only hold the null handle; any other value traps. Also more indirect call roots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2's engine reads and sets terminal attributes for its text console when stdin is a terminal, so a visible launch from a shell trapped. The shim maps flags, c_cc indices and speed codes both ways and still traps on a Linux bit that Darwin cannot represent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2's GCC interleaves unrelated moves between the table load, the GOT add and the jump, and sometimes folds the load into the add itself. The lifter missed these switches, so every case surfaced as a runtime trap. 336 more switches now become local gotos; the real target is still checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2 engine.so schedules an SSE conversion (cvttss2si into an unrelated register) between the table load and the GOT add, which crashed the game right after a save loaded. Instructions with implicit register writes (xchg, div, string ops, ...) still stop the recognition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macOS's legacy 2.1 context has no GL_ARB_sampler_objects: glGenSamplers resolves but fails with GL_INVALID_OPERATION. HL2's togl enables sampler objects anyway, so every texture lost its mipmap and anisotropic filtering (noisy, aliased surfaces). The runtime now adds -gl_disablesamplerobjects on the OpenGL path unless the player chose either option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2's shaderapivk creates Direct3D through Direct3DCreate9Ex and has no fallback to Direct3DCreate9, so the Metal path stopped at 'Failed to create D3D9!'. The generator now describes IDirect3D9Ex, IDirect3DDevice9Ex and IDirect3DSwapChain9Ex, and the runtime calls the Direct3DCreate9Ex that libd3d9metal already exports. CheckResourceResidency and GetPresentStats stay logged stubs (the stats struct differs on i386). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2's shaderapivk.so loads switch entries with [B + I - D], I being the index already multiplied by 4 in another block. The bound is not tracked there, so entries are read while they land on code, as for unbounded absolute tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…very run_game now sets P2_D3D9METAL from config d3d9metal_dylib (written by setup.sh) unless already set, so dev launches pick Metal like the app does. discover_game forwards arguments after -- to the game, to discover roots past a +load. Also adds the shaderapivk roots found on the Metal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2 calls fminf/fmaxf tens of thousands of times a second, each through the name chain of the math provider. The checks also cover the direct pthread routes (TLS keys, pthread_self) that HL2's tier0 calls ~430 000 times a second; those routes came with the November 6 audit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hidden Metal run executing `jpeg` trapped at 0x148af37c (materialsystem.so+0xaf37c, target not recompiled). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…el samples The game is CPU-bound and single-threaded; translated code is ~5-6 ARM instructions per x86 instruction and spends ~38% of its time in the call protocol, which bounds what lifter work can still gain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_game, gameprep, make_app, run_game, discover_game and bench_game take --game (GAME= in the Makefile) and read and write the game's own paths, so building Half-Life 2 no longer overwrites Portal 2's build. The HL2 binary is hl2-native, also inside its app. run_game passes the game's SteamAppId and libsteam_api.dylib, which the runtime would otherwise take from Portal 2's top-level keys. make_app snapshots the game's merged config, so the app's language detection looks at the right game folder. iPhone tools stay Portal 2 only and say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without --game, setup.sh builds each game whose data and Linux binaries it finds, so a Portal 2 user who adds Half-Life 2 just re-runs it. A game installed in Steam without its depot is skipped with the download_depot hint instead of stopping the other. The refusal to switch games is gone: the games no longer share build/. --detect prints the plan without changing anything (used by the tests and handy for support). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each game's app, log and saves get their own questions, and the installer's shared build folder its own yes. Another game's data folder stays protected from a user_dir that overlaps it. The running check covers hl2-native, and is skipped against the tests' fake home so a real game does not skip the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The installer now finds Portal 2 and Half-Life 2 the way setup.sh does, shows one checkbox per game (ticked when its data and Linux binaries are there) and runs setup.sh --game once per ticked game, the progress bar split between them. It becomes "Natif Installer" (zip Natif-Installer-<version>.zip), named in one place in build_installer.sh; the bundle identifier and the build folder in Application Support stay the first release's, so an update rebuilds incrementally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_ios.py took Portal 2's roots, program, name and icon for granted, so an HL2 checkout could not lift or link. It now reads them from the game profile and writes the game into the Info.plist (P2Game), from which runtime/ios/game_ios.c picks the data folder (hl2-userdata), the Steam app id of the iOS stub, the default for back taps and the ICvar layout (VEngineCvar004, FindCommandBase at slot 10, checked in depot 224). setup.sh --ios accepts HL2 with its own bundle identifier, so it does not replace Portal 2 on the phone; ios_device.sh copies the HL2 folders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overlay's buttons were Portal 2's, hard-coded. They now come from a table per game (touch_layout.c); Portal 2's is unchanged, checked against the old formulas. HL2 gets fire (dragging the finger also aims, since the same thumb fires and looks), alt-fire, reload, use, jump, crouch and sprint as toggles (a thumb cannot hold them while moving or looking; the sprint stops when the move stick is released), and small buttons in the top bar for previous/next weapon (mouse wheel, under the game's weapon list), flashlight, quick save and pause. Back taps and the firing vibration stay Portal 2 only, and the HL2 settings page drops the Portals group. Checks cover the layouts on three screen sizes and the profiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install instructions start with the Natif Installer, then ./setup.sh, for either game or both; the one-checkout-per-game advice and the separate HL2 clone are gone. usage.md describes the shared config/local.json with its games section, game selection and per-game build paths. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
language.py only looked at portal2/resource and appmanifest_620.acf, so the Half-Life 2 app always started in English. It now follows the "game" and steam_appid of the config it is given: the app's snapshot and run_game's merged config are the game's own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
save_sync.py only knows Portal 2's folders: say so instead of failing on a missing portal2-userdata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With two games, a yes for one game's saves could move the other's custom user_dir when both overlap, and the data and installer build folders were offered even if a game or depot had been put inside them. Every folder offered is now checked against the games, the depots, this checkout and the other game's saves. Found by Codex's review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime took the first "steam_appid" or "steam_api_dylib" anywhere in the file, so a games.hl2 section written before Portal 2's keys (HL2 set up first, or a hand-edited config) gave Portal 2 HL2's app id and Steamworks library. The lookup now tracks JSON depth and only matches top-level keys, which are Portal 2's and the shared ones; other games get theirs from run_game.py or their app's launcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2's vgui loads a fresh configuration and adds resource/*.ttf (HUD icons, GorDIN, DejaVu) with FcConfigAppFontAddFile; runtime/fonts.c already bridges these on the Mac, the iOS build did not link. One configuration, game fonts indexed once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HL2 starts Steam with SteamInternal_SteamAPI_Init and calls its newer interfaces (SteamUser023, SteamClient021, UGC 020...) by vtable slot, not through flat functions: the stub had neither, so the game trapped, then jumped past the 64-entry zero table. The generator now emits a vtable per slot-called version with the same answers as the flat functions (logged on, Steam ID, auth ticket), and SteamInput's Init answers true so HL2 stops re-initialising Steam every few seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the HUD Without -w/-h HL2 started at 640x480 stretched over the screen; it now always gets the render size (Portal 2 still only with the Resolution setting). In game the camera stick's resting ring sat under alt-fire and use, and the camera-mode button over the health readout: the ring moves toward the centre and the button to the top bar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Steam on a Mac can keep steamapps/common/Portal 2 with only an empty portal2/ folder. With depot 661 present, setup.sh took it for the game and stopped with "does not look like Portal 2" before building Half-Life 2; the installer would have ticked it. Both now require portal2/pak01_dir.vpk (hl2_complete/gameinfo.txt for Half-Life 2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck saving Throwing a grenade then quick saving in d1_canals_01 trapped on client.so 0x5a0940 (target not recompiled): a case of a switch dispatched with 'movl %ebx,%eax; addl table(%ebx,%edx,4),%eax; jmp *%eax', which the lifter and table_roots do not follow. All 21 distinct targets of that 27-entry table are added. Found in the iOS simulator; the Mac build lifts the same code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How to build and install it, its touch layout and why sprint and crouch toggle, why back taps are off, what the simulator showed and what is untested on a device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FcConfigAppFontAddFile grows the index with realloc while another thread may be matching a font (Codex review). Additions, matches and listings now hold one mutex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPhone build of Half-Life 2 now follows the one-checkout layout: its sources and objects live under build/hl2 and its app is build/hl2/ios/<target>/HalfLife2.app (Portal 2 keeps build/ios/<target>/Portal2.app). build_ios.py, run_ios_sim.sh and ios_device.sh take --game and read the game's merged config (localconfig.game_config, through the new tools/ios_game.py for the shell scripts) instead of the top-level keys, which are Portal 2's: game_root, linux_root, language and ios_bundle_id would otherwise have been Portal 2's for HL2. setup.sh --ios builds, signs and installs the iPhone app of every selected game; each game gets its own ios_bundle_id (local.hl2recomp.<team> in games.hl2), so the HL2 app does not replace Portal 2's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
P2_BUNDLE_ID (or the same ios_bundle_id for both games) would make the second install replace the first app, and its data marker skip the second game's data copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe identifier is the game's plus .probe; building it for one game and launching it for another used two identifiers. Also fill @GAMe@ of the shared Info.plist template. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s limit The README title and status now cover Half-Life 2 and its episodes. Episode One, Episode Two and Lost Coast start from the menu (SwitchExpansion) and their first maps render on Metal. On 8 Nov. every build held 120 fps on the 120 Hz display; PGO saves ~5 % CPU per frame, not worth shipping a profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run from a dev checkout, uninstall.sh only knew that checkout's config, so the installer's build folder could be offered for the Trash while the installer's config put a game, a depot or saves inside it. Its paths now join the protected list (found by Codex's review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Portal 2 depot with engine.so but not server.so (interrupted download) was found, then check_game stopped the whole run, so a complete Half-Life 2 was never built. Detection now requires the same files as check_game; the incomplete game is skipped with its download_depot hint (Codex's review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Source's Brightness option (mat_monitorgamma) sets a D3D9 gamma ramp. Direct3D 9, and libd3d9metal with it, applies a ramp only to a fullscreen device, and on the Mac the game runs windowed or in desktop fullscreen: the slider did nothing in Half-Life 2, so dark interiors could not be brightened. For a windowed device the ramp now also goes to its SDL window (SDL_SetWindowGammaRamp), which is what Portal 2's OpenGL path already does. The default ramp is the identity and leaves the display alone, except to undo a ramp set earlier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Going back to the default brightness sent a linear ramp, which would replace the display's calibration ramp while the game has focus. The display's ramp is now read once before the first change and restored when the game returns to the identity. The state follows the device's window, so a recreated window starts clean. Review by Codex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With two Apple accounts in Xcode (a free team and a paid team), sign.py
took the first "Apple Development" identity of the keychain, from the
other team: the iPhone refused the app ("A valid provisioning profile
for this executable was not found"). The identity is now the one whose
certificate is in the profile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The same static recompiler now builds Half-Life 2 (Steam app 220, the 20th anniversary build, mod
hl2_complete, with Episode One, Episode Two and Lost Coast) next to Portal 2, on Mac and iPhone.tools/games.py): program, modules, indirect call roots (config/hl2-roots.json), Steam app and depot, app name, data folder, build paths. Portal 2's paths are unchanged (build/game,build/portal2-native,build/ios); Half-Life 2 builds inbuild/hl2.--game portal2|hl2(GAME=in the Makefile); other games' settings live under"games"inconfig/local.json, existing Portal 2 configs work unchanged../setup.shbuilds every game it finds (--game portal2|hl2|all,--detectto preview).uninstall.shhandles both.jecxz/jcxz, CPUID-gated AES-NI paths, more GCC PIC switch forms.IDirect3D9Exforshaderapivk.so, event queries reported reached once their frame is presented (Source's frame sync no longer serializes CPU and GPU: 83 → ~118 fps), d3d9Ex user memory for SYSTEMMEM textures (fixed HL2's sky on Metal).HalfLife2.app, Info.plistP2Game), touch layout for HL2 (fire/aim on the right thumb, toggle sprint/crouch, weapon switch, flashlight, quick save), iOS Steam stub for SDK 1.60, fonts. Runs on an iPhone 15 Pro Max (first short test).Tested:
make check(165 Python tests, 241 C checks); fresh clone./setup.shbuilding HL2 end to end (9 min) and the app starting on Metal; both games built from one checkout and launched hidden; HL2 main game played a few hours (Mac); Episode One/Two and Lost Coast first maps load and render; HL2 in the iOS Simulator (menu, moving, firing, flashlight, quick save). HL2 started and played briefly on an iPhone 15 Pro Max. Not tested:setup.sh --iosend to end, the installer's Build button end to end, machines other than an M3 Pro.🤖 Generated with Claude Code