OPENHOOPS / 2K14Final team archive · November 9, 2026Reference libraryChecksums

Dated source reference. Current release status: handoff state. Full original relative paths are in the developer archive.

Actual game startup investigation — October 12, 2026

Historical startup investigation. OriginalCD protection/LockApp and partialMacWine results below do not describe the current Windows game/API checkpoint. The supportedISO executable has completed Windows regulation/OT matches; onboarding removal, portable saves and API/college mechanics are implemented with scoped tests. MacWine was removed; Mac/Linux game delivery is deferred. See current state and API verification.

Current scope (October13,2026): Windows-first deep editing and a public extension API; Mac/Linux compatibility follows later. Earlier native-port goals and next-step proposals in this evidence history are superseded. Technical observations and verification limits remain as recorded.

October12 status at this historical milestone: the valid user-supplied ISO variant boots under Wine/Rosetta on this Mac. Game-window captures verify the intro renders; the user reports a working menu and progression to loading a game. Gameplay is not verified. This is the original Windows game, not a native OpenHoops engine. The OptiPlex now builds and runs the bounded native components on Windows x86/x64, and CDB captures startup memory without killing the reference game. Windows creates a responsive game window but remains at LockApp, preventing visual menu verification. Windows evidence and setup.

Latest startup lead: Windows snapshot 03 verifies the same 92-byte WinMain and captures the formerly missing target at 0x02164310. Its 659-byte body contains a repeated call to 0x004E0190, passing ECX from [0x015D08F0] and continuing while EAX is nonzero. This is a concrete application-loop candidate, not yet verified gameplay/frame semantics. Exact bytes, branches and limits. Earlier milestones below retain their historical findings; the bootstrap target is no longer uncaptured.

The original CD-protected executable, separately preserved, reaches its SecuROM protection code and produces error 8016:

A required security module cannot be activated. This program cannot be executed (8016).

This original-version failure was captured without a debugger attached in run 04. It is not inferred merely from protection-related strings. The user subsequently restored loose alternate files, which had invalid PE headers, then supplied their ISO. Valid ISO extractions differ from those loose files. The older protection failure does not describe the current ISO variant's boot result.

ISO variant boots; runtime startup recovered

Source: user-supplied rld-nba2k14.iso, with exact ISO and member hashes in ISO inspection. Preserved extracted files live in resources/executable-variants/rld-iso/Crack/. Static inspection records imports, exports, entry bytes and section layout; the EXE's .text is still encoded on disk. The DLL has two ordinal exports. No claim of exhaustive binary safety auditing is made.

Created another APFS working copy at ~/.local/share/openhoops/run/reference-rld, overlaid only its EXE and rld.dll, and mapped it as R: inside the existing research prefix. The original game corpus and archived variant are unchanged. tools/launch_reference.py --variant rld-iso pins both variant hashes and records post-run integrity. All three initial variant runs load the game through Wine 11.18; logs show Direct3D 9 and WineD3D graphics activity. Graphics diagnostics include framebuffer/state errors, so this is not a rendering-correctness or performance certification.

  • Run reference-rld-01: 45-second non-debugger run, stopped by harness timeout. No visual capture during this run.
  • Run reference-rld-memory-02: visible intro captured from the specific NBA 2K14 window. Debugger reads 4,864 bytes after 45 seconds. An attach-correlated WoW64 fault occurs at 0xFFF88190; process exits afterward. No independent loading-crash inference.
  • Run reference-rld-boot-03: visible intro captured again; attempted Return/Escape events targeted only the Wine process, with no confirmed skip response. Debugger reads 12,352 bytes after 90 seconds; again exits after attachment. The user reports the game reached its menu and worked until loading into a match during these tests. Run association is not established, so record this as user observation rather than an independently captured menu screenshot or proven loading crash.

CUA app selection timed out for the Wine app path and did not recognize the process name. Fallback queried CoreGraphics for the exact NBA 2K14 window and captured that window alone with screencapture -l. Intro screenshot. No unrelated desktop capture was saved.

The WinMain body at 0x006A8200..0x006A825C now decodes as a complete 92-byte routine. It stores the instance handle through the context accessor 0x00DE3AC0, obtains the current module and loads icon resource 99, supplies icon/title-related values to two helpers, clears context +0x14, invokes 0x00D9C5E0, calls 0x004E13B0 at 0x006A824D, then returns the context's first word with ret 0x10. Helper semantics are not fully verified; do not invent their implementations.

The next routine begins with 0x004E13B0 -> jmp 0x02164310. That destination has not yet been captured. Its surrounding body still contains obfuscated/trampoline control flow, so recovering WinMain does not close the entire startup/main-loop gap. Other captured code at CRT startup, subsystem initialization and context helpers is byte-identical to corresponding readable original-CD ranges; captured WinMain and the bootstrap range are not. The report records comparisons per range, not an assumption that all versions are interchangeable.

Reproduce the six byte captures and checks with .venv/bin/python tools/recover_runtime_boot.py. Runtime evidence attributes all 17,216 bytes to exact logs/variant hashes. Read-only Ghidra export transiently overlays only WinMain's captured 92 bytes into the existing project and discards changes. Its inferred import signatures are wrong in places: instruction-level stack pushes show GetModuleHandleA(NULL) followed by LoadIconA(module, 99), despite misleading pseudocode argument grouping. No portable native implementation is claimed by this export.

This was an earlier startup investigation; Windows is now the primary runtime target. Continue targeted initialization/loop analysis only when it serves onboarding, API execution boundaries, match launch or a supported edit. Mac/Linux runtime work is deferred. Keep the earlier Wine debugger results as historical evidence, not a current native-port work queue.

Reference environment and reproduction

Rosetta successfully executed arch -x86_64 /usr/bin/uname -m, returning x86_64. No Wine, Homebrew, MacPorts, Docker, Podman or QEMU command was initially found, and the inspected application directories did not contain a Windows VM application. This does not establish that the user has no Windows machine elsewhere.

Downloaded Gcenx's Wine 11.18 macOS build, retained the original archive, verified its SHA-256 against the GitHub asset digest, and installed it locally under ~/.local/share/openhoops/toolchains/wine-11.18. wine --version returned wine-11.18; the host binary is Mach-O x86_64. Its WoW64 components loaded the original PE32 game in the reference runs. Toolchain identity.

An APFS copy-on-write copy of all game files lives at ~/.local/share/openhoops/run/reference-game. An isolated Wine prefix lives at ~/.local/share/openhoops/run/wine-prefix-11.18; drive G points at that working copy. Wine's default links from its user folders to the host's Documents/Desktop/etc. were replaced with directories inside this prefix. This is a separate test environment, not a security sandbox. The game was never launched from NBA2K14-extracted.

After the runs, all 4,906 baseline file hashes matched in both the original corpus and working copy, with no extra non-.DS_Store files. Integrity evidence.

The installed reference setup can be exercised with a fresh evidence name:

.venv/bin/python tools/launch_reference.py --seconds 15 --name reference-next --richedit

Launch harness enforces a time limit, a 64 MiB log ceiling, original/working executable identity, separate working directory and prefix, and prefix-scoped cleanup. It refuses to overwrite an earlier run. It does not install or execute community hooks. --relay enables a narrow startup API list; --richedit captures Wine's diagnostic trace of the protection dialog's text. Disabled Mono/Gecko overrides avoid unrelated prefix downloads. Wine's built-in MSVCR100 was selected in the captured run, even though the supplied runtime DLL is present; exact Windows-runtime equivalence is not established.

Run Purpose Observed result
01 Basic load/exception trace, 45 seconds Game image loads at 0x00400000; process remains alive until harness timeout. No menu verified.
02 Selected API trace plus attempted debugger attachment Protection code probes named devices, extracts drm_dialogs.dll, creates a SonyDADC SecuROM window. WineDbg attachment prints its PID but produces no stack before its 20-second timeout. Exit 84 follows debugger termination and must not be treated as an independent game failure code.
03 API trace without debugger, 18 seconds Same protection dialog; NBA2K14 title and Launch Analysis control observed in API calls.
04 RichEdit trace without debugger, 15 seconds Exact security-module failure and 8016 text above, with the program's SecuROM help URL.

The actual error text is in reference-launch-04.log, around lines 1188–1260. Run 02 records the emitted DLL and protection window around lines 10506–10511. Full logs, commands and hashes are retained under analysis/startup. GUI inspection through CUA was unavailable because the Mac was locked; no unlock was attempted. The diagnostic text comes from executed application calls, not a desktop screenshot.

No D3D9 module load or Direct3DCreate9 call was observed in these bounded traces. MoltenVK initialization messages belong to the Wine environment and do not establish game renderer initialization. The error is not evidence that changing DX9 to Metal would fix startup. The precise protection check that yields 8016 has not been identified; do not substitute guesses about a missing CD, driver or debugger for that missing evidence.

Retained original imports: the key static correction

The PE header points at import descriptors at RVA 0x02B42FF8, with IAT RVA 0x02B42020. Those entries explain the loader/protection environment but did not connect most readable game code to its Windows APIs. Looking only at that table yielded almost no useful window/message-loop cross-references.

The executable also retains its original import descriptors at RVA 0x00C000CC. We located them by finding the original DLL-name RVAs and their contiguous 20-byte descriptors. Ten descriptors, followed by a null descriptor at 0x00C00194, describe 242 import slots across ADVAPI32, CRYPT32, GDI32, KERNEL32, ole32, OLEAUT32, SHELL32, USER32, WS2_32 and MSVCR100. The original INT and IAT values match for every slot in the supplied file.

Recovery script validates file-backed ranges, terminators, printable names, DLL names and every slot. Its result was independently compared with pefile 2024.8.26 parsing the retained directory: DLLs, counts, explicit names, ordinals and IAT addresses all match. pefile additionally resolves some ordinal imports by its known-name database; those inferred names are kept separate from names actually stored in the file. Complete import evidence.

This is metadata recovery. We did not replace the executable's PE import directory or claim that doing so produces a working unprotected executable. Direct3D and X3DAudio delayed imports remain separate from these 242 normal import slots.

Identified game-side startup boundaries

All addresses below are preferred virtual addresses for image base 0x00400000, tied to executable SHA-256 becdfca16a88f656bc20dde608bd38becf0b8e04bf41c8041d344e37d20e1d67. Their identification is static; execution past protection has not been observed.

Address Evidence and interpretation
0x00E0E2B0 Window-creation function candidate. Calls RegisterClassA through original IAT 0x00EC2300 and CreateWindowExA through 0x00EC2354; writes returned HWND at object +0x850.
0x00E0B860 Window-procedure callback registered by the above function. Address is explicitly stored in its class structure.
0x00E0B9A0 Windows message pump. Loads original PeekMessageA, DispatchMessageA, TranslateMessage slots at 0x00EC22D8/DC/E0, removes queued messages, and recognizes WM_QUIT (0x12). Instructions store its quit flag at object +0x940 and exit value at +0x944. This is not yet identified as the complete simulation/frame loop.
0x00DDC6B0 Broader graphics-subsystem initialization candidate. Its instruction stream includes a call to 0x00DDC650 at 0x00DDC907, plus numerous unresolved initialization dependencies.
0x00DDC650 Iterates eight records at stride 0x474 and calls 0x00DDC0F0 with successive adapter indices.
0x00DDC0F0 Calls Direct3DCreate9(32) through thunk 0x00E1BBB6. Subsequent COM offsets correspond to adapter information, display-mode enumeration and format/depth checks. This is capability/mode enumeration, not proof of device creation or a rendered frame.
0x00E1BBB6 Direct3DCreate9 delayed-import jump thunk through IAT 0x0146B5AC.

The COM interpretation is cross-checked against Wine 11.18's IDirect3D9 interface declaration, archived locally. The original instruction lists, cross-references and bounded pseudocode exports are in boot functions, D3D enumeration, and adapter caller.

Ghidra's prior whole-image analysis was partial: several meaningful routines already had readable instructions but no function owner. We seeded four instruction-backed boundaries and applied original IAT labels in a read-only Ghidra run. Its log confirms changes were discarded from the persistent project. No source executable was patched. Decompiler output still has incorrect/unknown stack signatures, especially around imported calls; use instruction evidence before reconstructing arguments or types.

The three earlier raw branch leads are now validated instructions: 0x00E0D6FB jumps to the message pump, 0x00E0E845 calls window creation, and 0x00D94477 calls graphics initialization. Capstone decoded each from a function boundary, and an independent read-only Ghidra export agrees on instruction alignment. Their runtime reachability remains unverified. The original candidate report is retained as historical evidence; boot-dispatch evidence supersedes its alignment uncertainty.

Graphics modules and their work loop

The binary retains an existing graphics-module interface, useful for defining the eventual native platform boundary. These are original RTTI names recovered from the file, not names invented by our decompiler:

Selection index Static object VA Vtable VA Original decorated type name
0 0x0146ABF0 0x00FDF2E4 .?AUVCGPUTHREAD_DEFAULT_GRAPHICS_MODULE@@
1 0x0146ABF4 0x00FDF334 .?AUVCGPUTHREAD_LOCKSTEP_GRAPHICS_MODULE@@
2 0x0146ABF8 0x00FDF384 .?AUVCGPUTHREAD_EXTERNAL_GRAPHICS_MODULE@@
3 0x0146AC00 0x00FDF384 .?AUVCGPUTHREAD_EXTERNAL_GRAPHICS_MODULE@@
4 0x0146AC08 0x00FDF294 .?AUVCGPUTHREAD_PLUGIN_GRAPHICS_MODULE@@

The object-selection table is at 0x00FDF278. Each object points to its vtable; the preceding vtable word points to an MSVC complete-object locator, whose type-descriptor name supplies the attribution above. The two external entries are distinct objects sharing a table; their behavioral difference is unknown. These names do not establish a supported public plugin API or native portability.

For the default and lockstep modules, three methods now have concrete static meanings:

Vtable byte offset Target Instruction-backed effect
+0x24 0x00E0D6F0 Gets global state via 0x00DA3620, loads the graphics/platform object at +0x21D34, and tail-jumps to the Windows message pump 0x00E0B9A0.
+0x2C 0x00E0E830 Loads the same object and calls window creation 0x00E0E2B0.
+0x34 0x00E0D700 Writes the object's +0x944 exit value through the supplied EDX pointer and returns its +0x940 quit flag.

Initialization at 0x00E0EC90 sets up synchronization/storage, clears quit/window fields, and selects a module using the global index at 0x02121EB0. At 0x00E0EE03 it indexes the object table, stores the selected module at object +0x864, calls that module's first method, and stores its return at +0x860. The precise meaning of that return is unresolved; do not name it a thread handle. Initialization then calls 0x00E0EBE0, which dispatches startup by module identity. One route passes callback 0x00E0EBC0 through module method +4; that callback jumps to the work loop. The default-mode route calls the thread-creation helper 0x00DA1B90 with EDX pointing at the string "pusher" at 0x0213D8EC. That address is file-backed string data, not a callback; an initial candidate interpretation was corrected by reading its bytes and the callee. The actual callback chain is established below.

The loop at 0x00E0E4C0 sets floating-point control state, records GetCurrentThreadId, polls the module's quit-status method, services the message pump on specific paths, updates cursor visibility, and removes work records from a linked list rooted at object +0x500. Work records have a state at +8 and dispatch index at +0xC; the loop uses indexed branch tables at 0x00E0E7E8 and 0x00E0E80C. Submission at 0x00E0E860 either handles selected work synchronously or links the record into that queue. Calls to synchronization helpers and timestamp collection surround these operations.

This is a graphics-thread work loop, not yet the complete simulation/main loop. In particular, Ghidra cannot reconstruct its indexed branches reliably: it reports an unresolved jump table and emits an apparent early return. The original instruction stream continues through dispatch handlers and branches back to 0x00E0E504 at 0x00E0E7D7. Do not translate that incomplete pseudocode literally or treat its apparent return as the original behavior. The static exporter preserves the branch-table entries separately; allowed input ranges and all operation meanings are not yet proved.

The subsystem initializer at 0x00D94450 increments a global count at 0x020E38A0, proceeds only when the new count is one, and calls 0x00DDC6B0 among a larger ordered set of subsystem routines. This is not established as WinMain. A raw caller at 0x0309F67D has a local push 0x0309F686; jmp 0x00D94450 pattern inside protected code; its reachable predecessor is unknown, so it does not close the protected-entry-to-game gap.

The graphics initializer closes another static part of the chain: at 0x00DDC6F1 it requests a 0x948-byte object, calls constructor 0x00DDC020, and stores the result in global state +0x21D34. That constructor installs vtable 0x00FD2F84 at the object's +0x490 thread subobject. Its original RTTI is .?AVTHREADER@VCGPUTHREAD@@, and its first method is the stack-argument worker wrapper 0x00E0EBD0. The initializer calls 0x00E0EC90 at 0x00DDC7B5.

Helper 0x00DA1B90 stores the supplied thread name and parent-object argument in the thread subobject, then calls original IAT 0x00EC2140 (CreateThread) at 0x00DA1BE6. It supplies entry 0x00DA1AD0 and the thread subobject as the Windows thread parameter. That entry retrieves the subobject's first virtual method, pushes the stored parent-object argument and calls the method at 0x00DA1B07. Through 0x00E0EBD0, this reaches 0x00E0E4C0. The helper also invokes SetThreadPriorityBoost; the entry invokes other setup/cleanup helpers whose full semantics remain unresolved. These are static links, not observed successful thread creation in the protected reference run.

0x00D94450 subsystem init
  -> 0x00DDC6B0 graphics init
     -> allocate 0x948; 0x00DDC020 constructor
     -> 0x00E0EC90 graphics-thread initialization
        -> 0x00E0EBE0 module-specific startup
           -> 0x00DA1B90 thread helper (default-mode route)
              -> CreateThread(entry=0x00DA1AD0, parameter=thread subobject)
                 -> THREADER vtable[0] = 0x00E0EBD0
                    -> 0x00E0E4C0 graphics work loop

Reproduce the byte/range checks with .venv/bin/python tools/recover_boot_dispatch.py. The script is locked to the original executable SHA-256 and checks all reads against file-backed section bounds. It preserves fourteen complete instruction ranges, three aligned branch edges, five object/RTTI chains, the shared method slots and four indexed tables, then verifies the original executable is unchanged. Independent exports: platform dispatch and graphics thread. No native implementation or execution parity is claimed for these newly mapped routines.

Protected startup evidence and remaining gap

Retained CRT entry and WinMain target — 3:10 PM heartbeat

The original GetStartupInfoW import at 0x00EC2080 leads to retained CRT startup at 0x00E234DC. Its instruction stream contains startup locking, initializer calls, command-line parsing through _acmdln, show-command selection and exit handling. At 0x00E23627 it calls 0x006A8200, passing the image instance 0x00400000, zero previous instance, command-line remainder and show command. This is strong static evidence of a WinMain-shaped target. Read-only Ghidra analysis independently confirms the call; it does not establish that the protection layer reaches it.

A retained stub at 0x00E2376F calls 0x00E23BA3, then jumps to the CRT routine at 0x00E23774. The first callee's semantics are not verified here. Do not label the stub as the recovered original entry point: the PE header still enters protection at 0x02B62B60, and the transfer to the retained CRT is unknown. The WinMain target's file bytes decode into implausible ordinary startup instructions and remain unsuitable for reconstruction as executable game logic.

One new 35-second working-copy launch addressed a specific question: are the WinMain bytes materialized by the time protection displays its error? With Wine 11.18, a delayed winedbg --command attachment successfully read 64 bytes at 0x006A8200 and 32 bytes at 0x00E234DC. Both regions match the original file exactly. Thus the sampled WinMain prefix is still in its original protected form at this snapshot. This does not prove that no other regions were unpacked, or that these bytes never changed earlier.

The run displayed SecuROM 8016 before attachment, at Wine timestamp 80277.473. Attachment was followed by a WoW64 page fault involving 0xFFF88190, first logged at 80284.302, with an ntdll stack in the debugger. Treat that as an attach-correlated fault with unresolved cause, not as an ordinary game crash or the cause of 8016. The debugger nevertheless completed the requested reads and detached with return code zero. The harness stopped the prefix at its time limit; its final game process return code is not an independent spontaneous-exit diagnosis. Original and working executable hashes remained unchanged. No menu, gameplay or native-game boot was verified.

Reproduce the archived evidence checks without launching the game:

.venv/bin/python tools/recover_crt_startup.py

The CRT/startup report records exact disassembly, source and tool versions/hashes, independent Ghidra evidence, original import identities, runtime bytes, comparisons and log locations. The launch report records the exact command and stopping condition. The debugger invocation follows Wine's 11.18 manual source and implementation; both are archived and hash-attributed in the report. No executable patch was made.

The previous loading-loop caller leads, 0x009F1120 and 0x009F2D30, were also exported read-only to Ghidra loading callers. They confirm calls into loading setup, but do not identify the full main loop. Follow the now-specific WinMain target when comparing the alternate executable; avoid expanding loading classes as a substitute for boot recovery.

The PE entry is 0x02B62B60 in Sitext, with overlapping/obfuscated control flow. Runtime trace records callbacks at 0x02781C82 and 0x027814EA within Stext addresses beyond that section's file-backed extent. This establishes that startup materializes executable content absent at those file positions. It does not establish that all game code is unpacked or that the game entry point has been reached. Entry bytes and runtime-address evidence.

The path from the protected entry to normal game initialization is still missing. That earlier next step was subsequently satisfied by the separate playable ISO-based Windows working copy; follow the Windows deep-editing plan for current work. The D3D boot contract now follows the worker through actual device creation, scene begin/end and Present; 18 mocked-dependency probes verify the original CreateDevice wrapper's ABI/data flow. The application-side startup investigation now verifies window/device submission order and identifies readable frame-submission callers as loading-animation loops. The higher-level game entry/main loop remains unresolved. Graphics initialization connects to the worker bootstrap statically; the "pusher" name is not a missing callback. Preserve the distinction between the graphics worker and the game simulation/main loop. Proper original-import signatures are still needed before trusting decompiler arguments. Do not return to asset-tool expansion or unrelated tiny routines. Do not repeat the same 8016 launch without a changed executable/environment or a specific new diagnostic question.

Latest continuation: original Windows match verified

See APPLICATION-LOOP.md for the physical-input diagnosis, first visually verified Windows match, complete dispatcher and timer recovery, and current uncertainties. This supersedes earlier handoffs that had not yet captured 0x004E0190 or reached gameplay. Native game boot remains unfinished.