Search code examples
lualove2dluajit

Is there a way to package your Love2D game into a bytecode distributable?


i plan on comercially releasing games soon, so i would like to find out a way to compile to bytecode and then fuse it into an executable. I have been searching for a way for literal months, but i haven't found a way, let me tell you what happens when i do try to do a method.

So doing jit.version, i can see Love2D uses 2.1.0beta3, which is the version i use to do luajit -b test.lua test.bc. After i do do that i proceed to remove the contents of the .lua file, and add different code to it.

Let's show you what i originally had inside of test.lua (the code i compiled):

local test = {}

function test:load()
    print("Worked.")
end

return test

So this is the code i compile, i then remove the contents of it and add this instead:

local f = assert(love.filesystem.load("test.bc"))

return f()

and then in my main.lua file i do this:

function love.load()
    local testclass = require("test")
    testclass:load()
end

When i do that, i produce this error (Before you ask, there's no syntax error in the Lua code before compilation.):

Error

test.lua:1: Syntax error: test.bc: cannot load incompatible bytecode



Traceback

[love "callbacks.lua"]:228: in function 'handler'
[C]: in function 'load'
test.lua:1: in main chunk
[C]: in function 'require'
main.lua:3: in function 'load'
[love "callbacks.lua"]:136: in function <[love "callbacks.lua"]:135>
[C]: in function 'xpcall'
[C]: in function 'xpcall'

I genuinly have been looking for a way for literal months, if someone could help out, it'd be greatly appreciated!


Solution

  • So doing jit.version, i can see Love2D uses 2.1.0beta3, which is the version i use to do luajit [...]

    Unfortunately, the assumption that LuaJIT version numbers would be sane is wrong. LuaJIT has been on 2.1.0-beta3 for years, despite numerous changes; Mike Pall's motivation for stopping to explicitly release new versions is unclear, but it seems that you are supposed to just use latest master.

    Thus, to make sure that you're compiling your bytecode with the same LuaJIT version LÖVE uses, compile it from within LÖVE using string.dump.

    Note that - contrary to PUC Lua - string.dump generates portable bytecode on LuaJIT:

    The generated bytecode is portable and can be loaded on any architecture that LuaJIT supports, independent of word size or endianess. However, the bytecode compatibility versions must match. Bytecode stays compatible for dot releases (x.y.0 → x.y.1), but may change with major or minor releases (2.0 → 2.1) or between any beta release. Foreign bytecode (e.g. from Lua 5.1) is incompatible and cannot be loaded.

    You can also set a strip flag, which will be helpful to remove debug information to make reversing harder:

    An extra argument has been added to string.dump(). If set to true, 'stripped' bytecode without debug information is generated. This speeds up later bytecode loading and reduces memory usage. See also the -b command line option.

    So, let's first use the following main.lua, which assumes your project folder as the current working directory:

    -- Compile `test.lua` to bytecode, strip debug info
    local bytecode = string.dump(assert(love.filesystem.load("test.lua")), true)
    
    local f = assert(io.open("test.bc", "wb"))
    f:write(bytecode)
    f:close()
    
    print("Compiled `test.lua` to `test.bc`")
    love.event.quit()
    

    Now execute love . inside your project directory to compile test.lua to test.bc. After doing this, you can replace test.lua with:

    return assert(love.filesystem.load("test.bc"))()
    

    and replace main.lua with

    function love.load()
        local testclass = require("test")
        testclass:load()
    end
    

    Running this using love . prints Worked. as expected.


    Automatizing this build process using Lua scripts, shell scripts or Makefiles is left as an exercise to the reader.

    You could also try figuring out the exact LuaJIT commit LÖVE uses to compile that LuaJIT version yourself, which would allow you to use luajit -b test.lua test.bc.