#porting lua code to rust

60 messages · Page 1 of 1 (latest)

shell grail
#

here is the script ive been trying to port
https://github.com/ethangreen-dev/lovely-injector/blob/master/crates/lovely-core/src/patch/regex.rs

what it does is it uses regex to find a pattern, and then inject a "payload" into the position provided to the script.
im porting it to lua since im making a modloader for balatro which runs fully in lua, but since im using love2d i cant use any luarocks or loverocks packages.

i have some of the packages (if thats what we call them in rust) ported to lua such as rope,
i also moved this package for regex https://github.com/Roblox/luau-regexp/tree/main/src/RegEx from luau to lua, and it seems like for the most part it works with my code (although i had to make some tweaks to it to add methods like get_group(index) or get_group_from_name(index)) and it does inject some parts of the script correctly, but then it seems to mess up really badly in some places causing stuff like this:


        if self.STATE if G.STATE == G.STATES.SMDS_BOOSTER_OPENED then
    SMODS.OPENED_BOOSTER.config.center:update_pack(dt)
end

 == self.STATES.PLAY_TAROT then 
            self:update_play_tarot(dt)
        end

here is the github for my lua code, if theres anything else you guys need to see to help me understand whats going on let me know! although i think the issue is just in my script here: https://github.com/3XPLwastaken/repowhichimusingtogethelpandiwillprivatelater/blob/main/code.lua

open forge
#

it does inject some parts of the script correctly, but then it seems to mess up really badly in some places causing stuff like this:
What do you mean by "causing stuff like this," you shared a code snippet beneath it?

#

oh wait, you are injecting a script into a script?

#

so it is injecting it not just at the wrong byte position but at a totally different line? huh

merry robin
#

@shell grail Do you know if Balatro modding is sandboxed at all? I know the game but I've never looked into modding it before (didn't know it was made in Lua! :P)
From what I can see Love2D uses LuaJIT meaning, if you really wanted (and it's not sandboxed to disallow it), you could port this Rust code by not rewriting it

shell grail
# open forge so it is injecting it not just at the wrong byte position but at a totally diffe...

it does actually inject somewhat in the correct area, but inside of the if statement

[[patches]]
[patches.regex]
target = "game.lua"
pattern = '''(?<indent>[\t ]*)if self\.STATE == self\.STATES\.TAROT_PACK then'''
position = "before"
payload = '''
if G.STATE == G.STATES.SMDS_BOOSTER_OPENED then
    SMODS.OPENED_BOOSTER.config.center:update_pack(dt)
end

'''
line_prepend = '$indent'```

it should look something like this:

```lua

if G.STATE == G.STATES.SMDS_BOOSTER_OPENED then
    SMODS.OPENED_BOOSTER.config.center:update_pack(dt)
end

        if self.STATE == self.STATES.PLAY_TAROT then 
            self:update_play_tarot(dt)
        end```
shell grail
#

i dont believe it is sandboxed at all, but the issue is that one of the first lines for the game says that it turns off jit when on certain operating systems or architectures

merry robin
#

I don't believe the FFI relies on JIT

shell grail
#
if (love.system.getOS() == 'OS X' ) and (jit.arch == 'arm64' or jit.arch == 'arm') then jit.off() end```
#

yeah i just searched it up, it doesnt 🥳

merry robin
shell grail
#

ffi comes bundled right? i shouldnt have to install any other packages for this i hope, the main thing with my modloader is that you can just drag the mod files into the love2d code and then change main.lua to mainoriginal.lua and change the modloaders mainfile to be "main.lua" so i can hook into and change everything required for the mods to run

merry robin
#

Are familiar with how it works, or how to do FFI from Rust?

shell grail
#

nope i have no idea how to use it

merry robin
#

I'd make sure it works by trying to require 'ffi' real quick before you get further into it :P

#

You know, I just realized
Rust is fully capable of creating Lua modules regardless of JIT or FFI 🤔

#

Like Lua can just load dylibs on its own, it just can't perform FFI with them typically

shell grail
#

yup ffi exists and works!

merry robin
#

Especially since you already have a non-Lua module lib

merry robin
# shell grail nope i have no idea how to use it

Fundamentally it's pretty simple.
FFI works via two languages each pretending the other is C and using that as a common basis for communication. Any functions you want to expose in Rust, any structs you want to pass/recieve, etc., all need to be explicitly marked as compiling as if they were C, otherwise the language on the other end (in this case it is actually C) won't know how to use any of it. For functions you do that likers pub extern "C" fn my_ffi_func(num: u8) -> u8 { num + 20 } and for types it's```rs
#[rep(C)]
struct MyData { x: u32, y: u32

#

For functions if you want them discoverable through the dylib you also need to mark them as #[no_mangle] so Rust knows to leave their names intact

#

(You'd only make a non-no_mangle C function if you were like, passing it as a pointer or something)

#

Then you just tell Cargo to compile to a dylib with```toml
[lib]
crate_type = [ "cdylib" ]

#

Compile it and it'll produce a .so for Linux or a .dll for Windows.

#

As simple as that for Rust. You just need to make C functions (and potentially types) to expose your logic

#

Note that any types you pass need to be repr(C) all the way down. Basic types will be inherently (numbers, bools, arrays I think, etc.) but Structs won't be, including stdlib ones like String or Vec

shell grail
#

but what about the packages that the rust code relies on such like these:

use regex_cursor::Input;
use regex_cursor::engines::meta::Regex;
use regex_cursor::regex_automata::util::syntax;
use regex_cursor::regex_automata::util::interpolate;```

also would that mean it would be limited to windows and linux only? i do also want this to work mobile devices aswell which is why ive been doing all of this purely in lua so far
merry robin
#

Those will get statically compiled into the dylib no different than any other binary :)

#

It means it'll be limited to any platforms you compile for. Cross compilation in Rust is easy and Rust supports lots of platforms but you will need to compile for each one, such is the limitation of native code.

#

Rust can compile to mobile devices

merry robin
# shell grail nope i have no idea how to use it

The FFI lib in Lua is quite neat. The steps are to define to LuaJIT what your native data is (what functions you have, what types, etc.), load the dynamic library, and then access the data from the correct lib. It looks something like```lua
local ffi = require 'ffi' -- Import the module.

ffi.cdef[[
// This function basically just takes a C header file directly.
// Embed all your symbols here.

uint8_t my_ffi_func(uint8_t num);
typedef struct MyData { uint32_t x; uint32_t y; } MyData;
]]

local lib = ffi.load("my_dy_lib") -- Load the file.

print(lib.my_ffi_func(20)) -- Should print '40'

#

You can see I'm defining a function with the same signature as the one in Rust, takes a byte, returns a byte. I've also defined the struct although it isn't used.

#

After doing that and loading it you can literally just call it. LuaJIT handles everything else

shell grail
#

wait so does the code inside ffi.cdef automatically turn into my_dy_lib?

merry robin
#

It's weird, eh?

#

Basically Lua knows what symbols you've defined and then binds it to an actual library lazily once you try to access them

shell grail
#

gyg

#

huh***

#

weird

merry robin
#

gyg

shell grail
#

gyg

merry robin
shell grail
#

that is true but im still wondering how i would allow it to use regex_automata inside of the ffi code

merry robin
#

Forgive the awful function name :P

#

The C functions themselves don't need to do much work at all, they just act to allow access to your proper logic

#

They can do all the work, nothing wrong with it, they just don't need to

#

In this context you can see I've taken a pointer to some kind of State object. This is a very common pattern in C. What you can do is create a Struct for Lua to use, allocate it on the heap, give Lua an opaque pointer to it, and then make several functions that take said pointer and dereference it.

#

The nice thing about this pattern is that your data doesn't need to be repr(C), Lua only ever holds a pointer to it and doesn't actually know what's behind it, your Rust does though and acts on it like normal.

#

Doing this also requires a destructor function that takes the pointer and properly deallocates it, an interaction you can tell LuaJIT to do automatically once the data in Lua has been garbage collected

#

No leaks!

#

That's likely all over complicated for what you want to do, though

#

I imagine some basic functions should suit your needs fine

shell grail
#

alright thank you!

#

ill try and get this code to work with FFI and ill let you know if i run into any other issues

#

thank you!

merry robin
#

No problem :)
Just ping me if you run into anything