Skip to content

README: say the mod is Windows only and why the Android LeviLauncher rejects the zip - #2

Open
SnakyWarrior wants to merge 61 commits into
mainfrom
claude/android-levi-launcher-compat-fma7tq
Open

SnakyWarrior wants to merge 61 commits into
mainfrom
claude/android-levi-launcher-compat-fma7tq

Conversation

@SnakyWarrior

Copy link
Copy Markdown
Owner

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d

…rejects the zip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
A separate mod in android/ built with the preloader-android SDK, since the
Windows dll cannot load on Android. It finds the game's classes by RTTI,
logs their vtables, and turns allowSplitScreen on when the game exports
its symbol; otherwise it lists the candidates in the log. A GitHub Actions
workflow builds the arm64 package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…etup-android, which failed installing sdkmanager

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
The log from a device showed libminecraftpe.so has .rodata in its first r-x
segment, so 0.1.0 found no classes. Also log PlayerListPacket as a check
that the RTTI search works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…lot 105)

The device log showed the AppPlatform vtable. Aligned with the virtual
function order in LeviLamina's AppPlatform.h, its pure virtual slots 77, 79
and 81 are swapBuffers, getSystemLocale and pickImage, so allowSplitScreen
is slot 105; slot 106 starts with fmov s0,#1.0 as
splitScreenRenderDistScalar(numPlayers) should. AppPlatform_android does not
override it. The mod finds the slot this way at runtime and makes it return
true, logging what the game alone would have answered. The log no longer
dumps every class with AppPlatform in its name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…Screen calls, log gamepad keys

0.1.2's log showed allowSplitScreen patched at slot 105, but the Download
copy was made once right after patching, so it could not show whether the
game ever asked. Now every line goes to both logs, the number of
allowSplitScreen calls is logged every 10 seconds, and gamepad button key
events reaching the game are logged (without consuming them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.3 on a device: the game asks allowSplitScreen (it alone would answer
false, the mod answers true) and gamepad keys reach the game, but every
controller moves player 1. To see how Java hands controller input to the
game, list the exported JNI functions about keys, controllers and input,
read from the library's dynamic symbol table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…e, always at the same link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
… where the game reads it

0.1.4's log: no JNI function carries controller buttons; Java only reports
devices added/removed (JellyBeanDeviceManager), so the game reads gamepads
from the native input queue. This version swaps the game's GOT entry for
AInputEvent_getDeviceId for a wrapper that logs each device's source, its
gamepad keys, and every distinct call site in the game with the code around
it. It also logs the code of the input JNI functions and of
allowSplitScreen, to disassemble offline. Smaller log: no vtable dumps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.5's log: allowSplitScreen on Android is a real check (two AppPlatform
calls, slots 218 and 230, against a threshold near 2.2e9, likely memory), so
the game has split screen and keeps it off on this device.
AInputEvent_getDeviceId is not in the game's GOT, nativeKeyHandler only
handles the back key, and onInputDeviceAdded passes the device id to a
native device manager. To find where gamepad input is read, log the
Android input functions the game imports (AInput*, AKeyEvent*,
AMotionEvent*, Paddleboat*, GameActivity*) and the RTTI names of its
gamepad/controller/device classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…Event_fromJava

0.1.6's log: the game runs on GameActivity (AGDK); it exports
GameActivityKeyEvent_fromJava and GameActivityMotionEvent_fromJava, whose
structs start with the Android device id, and has GamePadMapper and
InputDeviceMapper classes. Hook GameActivityKeyEvent_fromJava (env, jobject,
out) and, after it fills the struct, log the device and source of gamepad
key events, plus the first few structs raw to check the layout. The motion
event converter takes many arguments that vary between AGDK versions, so it
is only dumped, not hooked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.7's log: GameActivityKeyEvent_fromJava works as hooked; the two
controllers are devices 11 and 12, so the game gets the device of each
button. The JellyBeanDeviceManager JNI functions all end in a call on one
static object (adrp/add x0; bl). Find that object and the methods from the
code (checked against the 0.1.5 dump: object +0x1407CA98, methods
+0xBF9E118/+0xBF9E4C4/+0xBF9E620), log its RTTI class name and the methods'
code, and log the object's memory whenever it changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.8's log: the controller manager (+0x1407CA98) keeps one slot per
controller (count 1, then 2; 16 per-controller arrays grow), but every call
it makes to the game's gamepad system (+0x14273550) passes controller id 0
in w1: connected (+0x11EDB07C), triggers as buttons (+0x11EDA940), details
(+0x11EDB18C/1D4/1E8), state (+0x11EDAB28). So Android's build feeds every
controller to controller 0.

To remap them, the device behind each call is needed. This version finds
the gamepad system and its functions from the manager's code (checked on
the 0.1.8 dump), hooks them with register-saving assembly trampolines, and
logs for each new call site the arguments and the frame-pointer chain of
callers, plus call counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.9's log: the gamepad system is GameControllerManager (+0x14273550). A
button press goes GameActivity (+0x82EBBC0) -> Minecraft's input loop
(+0x88A2580 keys, +0x88A2538 other) -> +0x88A1A8C -> controller manager
(+0xBF9E0E0) -> handlers (+0xBFB9..., +0xBFBA...) -> GameControllerManager
with controller 0; the trampolines ran without trouble. To see where the
device id is read and turned into the manager's slot, dump the code before
each in-game frame of the chain (once each).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.1.10's log showed the controller manager's dispatch function (+0xBF9E080):
every controller event goes through it as a std::variant whose alternative
points to an event that starts with the Android device id, and its visitors
call GameControllerManager with controller 0.

Now each gamepad device gets a controller number in the order it is first
used (0 = player 1, 1 = player 2, up to 4). While dispatch, or the manager's
add/remove device function, runs for a device, that number is the thread's
current controller, and every GameControllerManager call on that thread with
controller 0 gets it instead (register-saving trampolines, now 16). Dispatch
is found from its code (the variant index check before the manager's add
function, checked on the 0.1.10 dump) and GameControllerManager's functions
from the manager's code and dispatch's visitors. Events are read with
process_vm_readv, so a bad pointer cannot crash the game. New config switch
separateControllers (default on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…checks)

0.2.0 on a device: the second controller became controller 1; the game was
told controller 1 connected and its buttons went to controller 1, but no
player joined for it.

MinecraftGame's vtable (dumped by 0.1.1) lines up with LeviLamina's
IMinecraftGame.h around these slots: slot 210 is
addSubClientInstanceAndAssociateToUser(int) ("mov w2, #1; b
_addSubClientInstanceAndAssociateToUserInternal"), so 172 is
isSplitscreenJoinEnabled (an atomic bool load) and 173
canAddSplitscreenSubClient(int). Find slot 210 by that code, then make the
two checks answer true (canAdd only for controllers 1-3), logging what the
game would have answered, and log calls to addSubClient. New config switch
forceSplitscreenJoin (default on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.1 on a device: isSplitscreenJoinEnabled is asked and answered true, but
canAddSplitscreenSubClient is never asked: the game's own "press Start to
join" does not react to controller 1 on Android. So when a controller other
than the first presses Start (Android key code 108, key down; key events are
dispatch's variant kind 0, the key code is int 12 of GameActivityKeyEvent),
call MinecraftGame::addSubClientInstanceAndAssociateToUser(controller) on the
MinecraftGame captured from isSplitscreenJoinEnabled, after the game handled
the event, on the same thread, once per controller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.2 on a device: Start on controller 1 called
addSubClientInstanceAndAssociateToUser(1); inside it the game itself asked
canAddSplitscreenSubClient(1) and would answer true; it returned, but no
player appeared. The internal function connects a user for the controller
first and continues in a lambda that receives a
Social::UserPlatformConnectionResult. Watch that lambda's std::function
operator() (vtable slot 6, found by its RTTI name) to log the result, and
dump the internal function's and the callback's code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.3's dump of _addSubClientInstanceAndAssociateToUserInternal(game,
controller, true): it gets the user manager (MinecraftGame virtual 53) and
asks its virtual 28 (controller, true). If that answers true it starts an
asynchronous user sign-in for the controller (user manager virtual 11 with
a callback), which on Android never comes back; if false it creates the new
player at once (a 7536-byte object built with the game). While the mod adds
a player for controller 1-3, virtual 28 now answers false; it logs what the
game would have answered. The 0.2.3 callback watch (a nested lambda that was
never called) is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.4 on a device: the user manager's check (virtual 28) is a plain
'return 0', so the game already took the branch that creates the player at
once (a 7536-byte object, then a 7096-byte shared_ptr block holding it,
with MinecraftGame+1480 set while adding), yet no player appears.

To see what happens next: every slot of MinecraftGame's vtable goes through
a generated register-saving trampoline (VTrampolines.S, 320 slots); for 60
seconds after Start the first call of each virtual function is logged with
its caller and arguments, then a call count per slot. MinecraftGame+1480 is
logged every 10 seconds when it changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.5's trace: during addSubClientInstanceAndAssociateToUser(1) MinecraftGame
calls only virtual 53 (getUserManager), 173 (canAddSplitscreenSubClient,
answered true) and 55 (getPrimaryClientInstance, from +0x9CEC848), then
returns: the creating branch (which calls virtual 285) never runs, so the
check at +0x9CED20C refuses on something about the primary client. Dump the
0x1100 bytes before the internal add function, which hold that check and
the function calling virtual 55.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.6's dump of the check before creating a player (+0x9CED20C in 1.26,
W(game, controller, uint8_t* out, bool)): it needs canAddSplitscreenSubClient,
game+512 set, not (primary client virtual 113 and a GameControllerManager
check), a GameControllerManager check(controller), user manager virtual 6
true, user manager virtual 39 false and (game+496)[25] <= 4; then it calls
user manager virtual 7. It refused, so the creating branch never ran.

Hook W (found as the first bl after 'add x2, sp, #164' in the internal add
function); while the mod adds a player for controller 1-3 and W refuses,
answer true. Log the two game fields and user manager virtuals 6, 39 and 7,
to see which condition failed. The 0.2.6 dump is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.7 on a device: the add-player check refused because user manager
virtual 6(1) is false (no user for controller 1). Forcing it through made the
game take the creating branch (MinecraftGame virtual 285 from +0x9CED930) and
the screen visibly re-initialised, but no second player appeared; the user
manager's virtual 7 was then called with -1 (no user).

To see what the new player asks of the user manager, its vtable (72 slots)
is traced as well (trampolines 320-391; 400 generated now), user manager
virtual 7 logs its caller, and the trace summary names the class of each slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.8's trace: while the new player is created the game asks the user manager
virtuals 5, 8(1), 70(1), 41, 42, 49, 50, 9, 53(1), 38(1) and 54(1), then calls
virtual 7(-1) and the player is gone: it has no user. To make it borrow
player 1's user (as on Windows), the answers are needed.

New register-saving detours that call the original and see its result
(PostTrampolines.S, 16): they log x0/x1/d0, the two words x0 points to, and
the first four words at x8 (for results returned in memory, e.g. a
shared_ptr). Watched on those user manager functions, whose code is dumped
too; the first 80 results after Start are logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.2.9's results and code: Social::UserManager keeps a vector of
shared_ptr<User> (+56, mutex +80). Virtual 5 returns the primary user and
virtual 53 the user of a controller (empty for controller 1), both through
x8; virtual 8 starts a new user sign-in for a controller (via virtual 70).
The new player was created with no user (virtual 7 was then called with -1).

As the Windows mod does, a controller the mod adds as player 2-4 now borrows
player 1's user: virtual 53 returns the primary user for it instead of
nothing, and virtual 6 ('has a user') answers true. The detour returns a
non-trivial two-pointer type, so it uses x8 like the game's shared_ptr
(checked in the generated AArch64 code) and moves it without touching the
reference count. Fewer user manager results are logged; the code dumps are
gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.3.0 on a device: the second player joined and played. The user then left
with it, and pressing Start again did nothing, even after reloading the
world: the mod tried to add a player only once per controller per run.

Start on controller 1-3 now adds a player again whenever the game itself
says a player can be added for that controller (its own
canAddSplitscreenSubClient answer, remembered for the add); the add-player
check is only forced then, so Start on a controller that already plays does
not add a second player. Presses closer than 3 seconds count once.
MinecraftGame virtuals 10 and 16 (queueSubclientRemoval, releaseClientSubId)
are logged with their caller. Lending player 1's user stays as in 0.3.0
(only when the game finds no user): the user the game makes works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…s it again

0.3.1 on a device: the second player joined; Start on its controller again
(to open or close the pause menu) started a second add for controller 1.
The game's own check refused (user manager virtual 39(1): the controller
already has a player) but canAddSplitscreenSubClient(1) said true, so the mod
forced it; the game then released sub client 0 and crashed.

Now the mod remembers the controllers it added a player for and does nothing
on their Start (it is just Start, the pause menu), until the game removes a
split-screen player (queueSubclientRemoval / releaseClientSubId), so they can
join again; and the check is never forced when virtual 39 says the
controller is taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…player

As on Windows, where player 1 keeps keyboard and mouse: controller numbers
start at 1, so the first controller no longer moves player 1; Start on it
adds player 2 (then 3, 4). Config firstControllerIsPlayer1 (default off)
gives the first controller to player 1 as before. The Android README says
what works and what is not ported yet. The diagnostics stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
The Windows mod's menus are LeviLamina forms: a ModalFormRequestPacket
(form id at +0x30, JSON at +0x38 after the 0x30-byte Packet) sent to one
player, and the answer read in
PacketHandlerDispatcherInstance<ModalFormResponsePacket>::handle. On Android
the plan is to send through the server's LoopbackPacketSender::sendToClient
(NetworkIdentifier, Packet, SubClientId).

This version finds those classes by RTTI and logs their vtables (getId
should answer 100 and 101), traces LoopbackPacketSender's calls while in a
world (to confirm which slot is sendToClient and to keep the object), and
watches the PlayerAuthInputPacket dispatcher (every local player sends it:
its NetworkIdentifier, SubClientId and the ServerNetworkHandler) and the
ModalFormResponsePacket dispatcher. The trace now has 600 trampolines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
0.5.0's log: LoopbackPacketSender virtual 7 is sendToClient(NetworkIdentifier,
Packet, SubClientId) (called with the local players' NetworkIdentifier); all
local players share that identifier and differ by sub client id; the
dispatcher's callback is the ServerNetworkHandler; ModalFormRequestPacket's
getId (slot 2) returns 100 and getSerializationMode reads +0x50, the response
101 and +0x58.

Now: the server's sender is taken from the first sendToClient call; a
ModalFormRequestPacket is built with the game's vtable (form id +0x30, JSON
+0x38, mode +0x50, checked by a static_assert under libc++) and its handler
pointing at the PacketHandlerDispatcherInstance<ModalFormRequestPacket>
object (found as the data word holding its vtable). Five seconds after an
added player first sends input, it gets a two-button test form, sent from the
PlayerAuthInputPacket handler (the server thread); the answer is logged from
the ModalFormResponsePacket handler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…t only the leaving player

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…eGame, like the game does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
… log the leave flags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…r for players without a controller; look for the layout option

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…e the longest silent), never give an extra controller to player 1, fix the new-player loop; log keys after a swap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…hoose-your-player picker that closes after 10 seconds; styled menus

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…d and turned on at start

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…ic (Menus.cpp: openSettings, offerPicker, startJoin, checkControllers); find the game's folder from the files it has open; menu texts in the game's language

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…crash); texts in the phone's language when the game names none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
… in config.json for Start), not outside a world or while it loads; player 1 keeps the touch screen; menus come sooner; a leaving player waits 3 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…no join prompt on Android); player 1's touch can be taken over by a controller and its controller freed from the menu, as on Windows; menu language from the phone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…controller once that player is gone (crash); the pause menu's Split screen button in the Android pack; trace the pause menu and the join listener

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…ller is blocked (a player handing over its controller never left)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…on, and a leaving player still gets its releases (crash); trace what the game does with presses of a controller that plays nobody (its join prompt)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…e code of the leave crash's frames

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…ntInstance functions that set the leave-requested byte)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…Game's) to find the one the pause menu's Save & Leave calls

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…(ClientInstance virtual 15, what the pause menu's Save & Leave calls), not by setting its leave bytes (crash)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…argument points to (to find the Split screen button's press)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…sets up their handlers (for the Split screen button)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…oves) to find the Split screen button's press

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…screen menu (its press is found by the FNV-1a hash of button.splitscreen_menu), as on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
…real offsets (+8, +16)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
… is player 1's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
… menu like Resume; open Split screen menus are closed and opened again when the players or their controllers change, as on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wX3aSVRyAv4YE8yrCQa4d
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.

2 participants