You are viewing a potentially older version of this package. View all versions.
Thunderstore-lovely-0.9.0 icon

lovely

Lovely is a runtime lua injector for LÖVE 2d

Date uploaded 2 weeks ago
Version 0.9.0
Download link Thunderstore-lovely-0.9.0.zip
Downloads 3470
Dependency string Thunderstore-lovely-0.9.0

README

Lovely is a runtime lua injector for LÖVE 2d

Lovely is a lua injector which embeds code into a LÖVE 2d game at runtime. Unlike executable patchers, mods can be installed, updated, and removed over and over again without requiring a partial or total game reinstallation. This is accomplished through in-process lua API detouring and an easy to use (and distribute) patch system.

Manual Installation

  1. Download the latest release.
  2. Open the .zip archive, copy dwmapi.dll into the game directory. You can navigate to the location by right-clicking the game in Steam, hovering "Manage", and selecting "Browse local files".
  3. Install one or more mods into %AppData%/Balatro/Mods.
  4. Run the game.

Important: Mods with Lovely patch files (lovely.toml or in lovely/*.toml) must be installed into their own directory within %AppData%/Balatro/Mods. No exceptions!

Patches

Note that the patch format is unstable and prone to change until Lovely is out of early development.

Patch files define where and how code injection occurs within the game process. For example, this is a patch for the modloader Steamodded:

[manifest]
version = "1.0.0"
dump_lua = true
priority = 0

# Define a var substitution rule. This searches for lines that begin with ${{lovely:var_name}} (var_name from this example, it can really be anything) 
# and replaces each match with the provided value.
# This example would transform print("${lovely:var_name}") to print("Hello world!").
# USEFUL: For when you want to reduce the complexity of repetitive injections, eg. embedding release version numbers in multiple locations.
[vars]
var_name = "Hello world!"

# Inject one or more lines of code before, after, or at (replacing) a line which matches the provided pattern.
# USEFUL: For when you need to add / modify a small amount of code to setup initialization routines, etc.
[[patches]]
[patches.pattern]
target = "game.lua"
pattern = "self.SPEEDFACTOR = 1"
position = "after"
payload = '''
initSteamodded()
print('${{lovely:var_name}}')
'''
match_indent = true
overwrite = false

# Append or prepend the contents of one or more files onto the target.
# USEFUL: For when you *only* care about getting your code into the game, nothing else. This does NOT inject it as a new module.
[[patches]]
[patches.copy]
target = "main.lua"
position = "append"
sources = [
    "core/core.lua",
    "core/deck.lua",
    "core/joker.lua",
    "core/sprite.lua",
    "debug/debug.lua",
    "loader/loader.lua",
]

# Inject a new module into the game *before* a target file it loaded.
# USEFUL: For when you want to silo your code into a separate require-able module OR inject a "global" dependency before game / mod code begins execution.
[[patches]]
[patches.module]
source = "nativefs.lua"
before = "main.lua"
name = "nativefs"

Patch variants

This file contains two patch definitions - a pattern patch, which (currently) changes a single line at a position offset to some pattern match, and a copy patch, which reads one or more input lua files and either appends or prepends them onto the target. The former is used when you need to surgically embed code at specific locations in the target (very useful for modloader init routines), and the latter is designed for use when you need to bulk inject position-independent code into the game.

Patch files

Patch files are loaded from mod directories inside of %AppData%/Balatro/Mods. Lovely will load any patch files present within Mods/ModName/lovely/ or load a single patch from %AppData/Balatro/Mods/ModName/lovely.toml. If multiple patches are loaded they will be injected into the game in the order in which they are found.

Paths defined within the patch are rooted by the mod's directory. For example, core/deck.lua is resolved to %AppData%/Balatro/Steamodded/core/deck.lua.

Patch targets

Each patch definition has a single patch target. These targets are the relative paths of source files when dumped from the game with a tool like 7zip. For example, one can target a top-level file like main.lua, or one in a subdirectory like engine/event.lua.

Patch debugging

Lovely dumps patched lua source files to %AppData%/Balatro/Mods/lovely/dump.

Not yet implemented

  • manifest.priority
  • manifest.dump_lua
  • manifest.version

CHANGELOG

v0.9.0

Hello! Just wanted to take this moment to thank everyone in this amazing community for their ingenuity and creativity and wonderful drive. It's really rare for a game to persist for as long as Balatro and I'm so proud that I have been able to be a part of it for the past (almost) two years.

I can't even begin to explain how thankful I am to have the incredible @WilsontheWolf and @cg-223. They have taken care of so much work between now and v0.8.0 and I am so lucky to have them as maintainers. They deserve so much appreciation for keeping up the pressure and making this release happen.

And remember to thank the hardworking people that make the mods, they keep this community alive.

You are obligated to try these mods out: (and legally obligated to try out every mod you see elsewhere, too)

Entropy, Tangents, Matador, LobotomyCorp, Amulet, Hatchet, Gros Balatro, Stocking Stuffer, MrBones, Insignia, MyDreamJournal, Balatro Plus, Arrow, TooManyJokers, Multiverse, 45DegreeRotatedBeatblock, Basslatro, Joyous Spring, Incognito, Galdur, Hyperfixation, Hot Potato, UltraViolet, Qualatro, Corrupted Nether, Fusion Jokers, Fortlatro, 1 in 10000 Chance for Withered Foxy Jumpscare Every Second, BeatblockPlus, Beattools, Scruffy Plays Pikmen, Lovely Mobile Maker Revo's Vault Dilatro

Install guide

OS Download
Windows lovely-x86_64-pc-windows-msvc.zip
Mac (Arm) lovely-aarch64-apple-darwin.tar.gz
Mac (x86) lovely-x86_64-apple-darwin.tar.gz
Linux lovely-x86_64-unknown-linux-gnu.tar.gz

Mobile Mods!!!!

@WilsontheWolf has created https://lmm.shorty.systems, it lets you mod Balatro on mobile with lovely by porting the Steam version of the game. Try it out.

[!IMPORTANT] BREAKING CHANGES:

  • Module patches with load_now = true that fail to load or execute now properly propagate errors instead of failing silently.
  • On Linux native, the mod directory has changed from ~/.config/<game>/Mods to ~/.local/share/<game>/Mods to follow XDG standards. You'll need to move your mods to the new location.

Contributors

Big thanks to the following contributors:

  • @cg223 for multiple targets support, copy patch payloads, nested folder loading, and runtime API additions
  • @WilsontheWolf for the runtime API (reload_patches, variable store), load_now error handling, Linux directory fix, and luaL_loadbuffer hook, and lots more
  • @kasimeka for fixing the --vanilla segfault on Unix and adding Nix support
  • @SpomJ for improving Linux installation documentation
  • @hliuson for release workflow improvements

Notable Changes

[!IMPORTANT] Most mods WILL NOT WORK AS ZIPS until modloaders have added support. NO EXCEPTIONS!

Zip mods

TL;DR: Mods can now be distributed as zip files.

Lovely can now load mods packaged as zips. Packaging is simple, just drop your mod files into the archive into either the top-level or into a folder, both are valid.

mod.zip
  lovely.toml
  ...

works the same as

mod.zip
  mod/
    lovely.toml
    ...

Recursive patch loading

TL;DR: Patch files are loaded recursively, clean your lovely dirs.

Patch files inside the lovely/ directory are loaded recursively from subdirectories.

For example:

tangents/
    lovely/
      questionable_feature.toml
      features/
        real_feature.toml
        content.toml
      tweaks/
        balanced.toml

Multiple targets per patch

TL;DR: Pattern, regex, and copy patches can target multiple files.

Patch away with reckless abandon:

[[patch]]
[patch.pattern]
target = ["main.lua", "game.lua", "init.lua"]
pattern = "old_code"
payload = "new_code"

Copy patch payloads

TL;DR: Copy patch payload field

Copy patches now have an optional payload field that lets you inject code without the fuss of a separate file:

[[patch]]
[patch.copy]
target = "main.lua"
position = "append"
payload = """
-- Inline code here
print("Injected!")
"""

Runtime API additions

TL;DR: New Lua APIs for runtime patch manipulation.

The lovely module now exposes several new functions for runtime control:

  • lovely.reload_patches() - Reload all patches from disk without restarting. This does not reapply patches, you need a restart for that.
  • lovely.apply_patches(buffer_name, buffer_content) - Apply patches to arbitrary buffers at runtime. You can patch shaders with this! And whatever you want, really.
  • lovely.set_var(key, value) - Store a var in the store.
  • lovely.get_var(key) - Retrieve a var from the store.
  • lovely.remove_var(key) - Remove and return a var from the store.

Example usage:

-- Store configuration
lovely.set_var("is_tangents_questionable", "true")

-- Retrieve it later
if lovely.get_var("is_tangents_questionable") == "true" then
	panic!("What?")
end

-- Apply patches to a dynamically loaded buffer
local patched = lovely.apply_patches("buffer_name", buffer)

-- Force a patch reload (not a restart)
lovely.reload_patches()

Module patches now apply Lovely patches

TL;DR: You can patch injected modules.

Injected modules can now be patch targets! =[lovely modname "filename"]

Bug Fixes

  • Fixed hardcoded game names in Linux launch scripts (run_lovely_linux.sh)
  • Fixed segfault when running with --vanilla flag on Unix
  • Fixed double-panic behavior on Windows
  • Fixed panic when obtaining extension from extensionless files
  • Fixed incorrect mod directory on Linux (#325)

What's Changed

New Contributors

Full Changelog: https://github.com/ethangreen-dev/lovely-injector/compare/v0.8.0...v0.9.0

v0.7.1

What's Changed

Full Changelog: https://github.com/ethangreen-dev/lovely-injector/compare/v0.7.0...v0.7.1

v0.7.0

Contributors

A huge thanks goes out to the following contributors:

  • @flakywanderer for improving log messages with patch metadata.
  • @WilsontheWolf for fixing the logging of panics on Unix targets.
  • @drgrib for improving the Steam Deck install guide.
  • @vgskye for completing much of the work on the Lovely Linux target.
  • @asquared31415 for updating the Linux PR.
  • @Rick1029 for writing a guide on running Lovely and Love2D games natively on Linux.

Notable Changes

#103 Expanded patch targets

TL;DR: You can specify more patch targets.

Patches can now target other mods and even love2d itself. Since non-Balatro buffer names can be complex you should use --dump-all and the corresponding .txt file in dump/ to determine the name of your target.

[!IMPORTANT] Patching other mods can cause problems if done incorrectly - problems that can be impossible to debug even with a complete dump. Be careful with this tool, communicate your intent with other devs as needed, document what you've done, and remember to have fun!

#114 Strict patch application ordering

TL;DR: Pattern and regex order inconsistencies have been fixed.

Lovely patches have a strict order in which they are applied: module → copy → pattern → regex, however this was not the case in v0.6.0. This version (and all the ones preceding it) merged the pattern and regex patches together such that they were applied in the same step in the order that they were defined within the patch file. This has been resolved by separating and applying these patches sequentially.

#103 Improved debugging via --dump-all

TL;DR: Use --dump-all to dump everything.

Every encountered buffer can be dumped, not just the ones that have been patched. Alongside each is a text file which contains the internal name of the buffer - use this name in the target field of patches.

#121 Hacky patch directory interpolation

TL;DR: Use {{lovely_hack::patch_dir}} in your payloads to access the patch's mod directory.

This variable will be replaced with the parent directory of the patch (either ../lovely.toml or ../../lovely/patch.toml, for example. It's a hacky and temporary solution that WILL be replaced at some point in the future, but it'll work fine for now.

#66 + #102 Linux support

TL;DR: Native Linux support but you must build it yourself (for now).

Follow the guide written by @Rick1029: https://github.com/Rick1029/lovely-injector/

What's Changed

New Contributors

Full Changelog: https://github.com/ethangreen-dev/lovely-injector/compare/v0.6.0...v0.7.0

v0.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/ethangreen-dev/lovely-injector/compare/v0.4.0...v0.6.0

v0.4.0

What's Changed

New Contributors

Full Changelog: https://github.com/ethangreen-dev/lovely-injector/compare/v0.3.1...v0.4.0

v0.3.1

  • [9f1c346] Implement native print override This fixes missed output from Lua print calls

  • [436fcc7] Implement lua module injection. This allows modules to be injected and require-ed. Initially implemented for nativefs.

  • [436fcc7] Add Lovely metadata injection. This makes it possible for a Lua caller to query information about the Lovely environment.

  • [7ceeace] Add LOVELY_INTEGRITY global const to patched source files. This has been implemented to enable runtime integrity checking for Ankh.

  • [ffeb76f] Fix missing target name check in PatternPatch.