ethangreen-dev / lovely-injector

A runtime lua injector for games built with LÖVE
MIT License
67 stars 11 forks source link

initial test on Balatro+ (App Store and Apple Arcade edition) #86

Open yozlet opened 4 days ago

yozlet commented 4 days ago

If anyone's thinking of adding support for the Mac App Store version of Balatro, and the Apple Arcade ("Balatro+") release, here's some initial information from a couple of hours playing around with it. Caveats: I have very little lua experience and even less rust, nor am I an iOS/macOS developer, so consider everything here vague at best and unverified. I haven't even bought Balatro on Steam, so all my experience is with the AA version on iPadOS and macOS.

All the exploration below is with the macOS version of Balatro+, though I'm guessing the packages for the other Apple OSes have very few differences: the source includes the logic for iOS/iPadOS and tvOS. Similarly, it would surprise me if there's any major difference between the non-Arcade App Store package and the Apple Arcade Balatro+ package.

The Balatro+ (Apple Arcade version) package includes:

(According to the file, Clockwork Valley is Maarten de Meyer. The file also lists these open source components: LOVE, ENet, FreeType, GLAD, glslang, Kepler Project's lua-compat-5.3, lua-enet, LuaJIT, Lua's UTF-8 module, LuaSocket, LZ4, LodePNG, TinyEXR, UTF8-CPP, xxHash, dr_flac, stb_image, libmpg123, OpenAL Soft.)

My guess is that the Balatro binary is basically love but with extra logic compiled in for exposing Core Data, CloudKit, GameKit, and other Apple APIs, all gathered into a love.platform table.

Fascinatingly – and wonderfully – most if not all of the new logic that uses those APIs seems to be out in the relative open in the included lua source, rather than more hidden in that custom binary. You can, for example, see lots of specific handling for tvOS.

So that's the good news. Now for some bad news:

At this point I need to stress that I am not an experienced C or macOS hacker, and I may well have missed some major windows that are still open. More importantly, the Balatro binary could have been written with command line arguments that allow for custom logic injection, even though the standard love command doesn't have any. (Maybe we should just ask Maarten de Meyer.)

Talking of the standard love command, what happens when you point a normal love2d build at Balatro+'s game folder? It actually gets pretty far: it produces a nice big window, loads the m6x11plus.ttf font, and uses it to display the traceback showing that it got about three lines into main.lua's love.load event before hitting a call to love.platform.earlyInit - which, of course, doesn't exist in the standard love2d API.

But that's far enough to show that those new APIs are what's getting in the way, and fortunately all the interaction points are clear in the lua code. Some of those methods are easily stubbed/faked, but others have been moved in as the primary load-bearing I/O points for loading and saving the player's profile and progress.

The main issue I hit with lovely: currently, on macOS, it demands that love exist within a .app folder - why? I was using a homebrew-installed binary and ended up commenting those lines out and rebuilding. (Yes, I could have faked it, but I was in the source anyway.)

I'm leaving this here for now. I'm happy to answer questions and try things out, but please don't expect me to do much more work on this. Others who have more experience will likely get much further, faster.

WilsontheWolf commented 4 days ago

Does the balatro .app have a Frameworks/Lua.framework from the apple arcade? If not it's probably like the iOS version, which I've been working on trying to get lovely to work on, but haven't quite got success

yozlet commented 2 days ago

@WilsontheWolf No Frameworks/ folder and no .framework files anywhere. Yup, I'm fairly sure this is the same as the iOS version.

In the meantime, I've made a little more progress since my first message. I'm following the path of using the supplied Balatro binary to read a modified version of the game's source, rather than trying to get the standard love command to work. This is because of all the extra love.platform functions - I'd rather not have to stub or reimplement them yet.

The Balatro binary seems to accept a couple of the same flags that love does:

/Applications/Balatro.app/Contents/MacOS/Balatro --version prints LOVE 11.5 (Mysterious Mysteries) - so yes, this is basically a love build with extra stuff thrown in.

/Applications/Balatro.app/Contents/MacOS/Balatro --game $NAME looks for a folder relative to the game's save data location, which is ~/Library/Containers/com.playstack.balatroarcade/Data/.

I tried copying the game/ folder (which has all the lua source and resources) out of /Applications/ and into somewhere more usable, then added a symlink to it in the Data/ folder above.

/Applications/Balatro.app/Contents/MacOS/Balatro --game game tries to run the copied game folder but produces this error:

Error: [love "boot.lua"]:278: module 'conf' not found:
    no field package.preload['conf']
    no file 'conf' in LOVE paths.
    no file './conf.lua'
    no file '/usr/local/share/luajit-2.1/conf.lua'
    no file '/usr/local/share/lua/5.1/conf.lua'
    no file '/usr/local/share/lua/5.1/conf/init.lua'
    no file './conf.so'
    no file '/usr/local/lib/lua/5.1/conf.so'
    no file '/usr/local/lib/lua/5.1/loadall.so'
stack traceback:
    [love "boot.lua"]:354: in function <[love "boot.lua"]:350>
    [C]: in function 'error'
    [love "boot.lua"]:278: in function <[love "boot.lua"]:126>
    [C]: in function 'xpcall'
    [love "boot.lua"]:364: in function <[love "boot.lua"]:357>
    [C]: in function 'xpcall'

... which is weird, since there's definitely a conf.lua in that folder. I've also tried running this command while my working directory is the game folder (so ./conf.lua exists) but it still can't find it.

Note that when invoking Balatro --game $NAME, it shows a different error depending on whether the $NAME path resolves to a folder with a main.lua file in it. So it's definitely finding the right folder and trying to run it. But for some reason, that bit of boot.lua (which is in the Balatro binary) isn't looking in the same folder for conf.lua. And yet, when $NAME points to the original game folder (inBalatro.app/Contents/Resources/game/) it starts up just fine.

WilsontheWolf commented 2 days ago

Okay. In that case, you can probably extract the game.love and stick it into a regular macos build of balatro, and then you should be able to use lovely

yozlet commented 2 days ago

... and minutes later, some actual success:

The problem seems to be that the Balatro binary can't properly read files outside of the /Applications/Balatro.app/ folder. (See, I'd probably know things like this if I was a proper macOS dev.) So I (as root) copied /Contents/Resources/game/ to /Contents/Resources/moddedgame/, tweaked some of the strings in the en-us.lua locale file, and ran

/Applications/Balatro.app/Contents/MacOS/Balatro --game '/Applications/Balatro.app/Contents/Resources/moddedgame/'

... and it booted up fine, and showed the altered strings. Even better, it loaded my saved game profile as normal.

So I'm thinking that a modding process that copies the source over and runs the lua transformations on the copied version may be enough here?

yozlet commented 2 days ago

@WilsontheWolf Yep, either keeping it unzipped or compressing to a .love file, either should work as long as it ends up in the Balatro.app folder.