feat(sdk): Link LuaJIT for bytecode compilation
This removes the LuaJIT binary as a runtime dependency and decreases the complexity of the compilation, by not needing to juggle a bunch of temp files anymore. However, it was a bit of a pain to get everything set up in the end. Closes #4.
This commit is contained in:
parent
df06182ca0
commit
16bfe88101
4 changed files with 98 additions and 66 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -855,6 +855,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.25"
|
||||
|
@ -1358,6 +1364,17 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luajit2-sys"
|
||||
version = "0.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33bb7acccd5a0224645ba06eba391af5f7194ff1762c2545860b43afcfd41af2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"fs_extra",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
|
@ -1966,6 +1983,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"glob",
|
||||
"libloading",
|
||||
"luajit2-sys",
|
||||
"nanorand",
|
||||
"oodle-sys",
|
||||
"pin-project-lite",
|
||||
|
|
|
@ -22,3 +22,4 @@ tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "m
|
|||
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
||||
tracing = { version = "0.1.37", features = ["async-await"] }
|
||||
tracing-error = "0.2.0"
|
||||
luajit2-sys = "0.0.2"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::ffi::CString;
|
||||
use std::io::{Cursor, Read, Seek, Write};
|
||||
use std::path::Path;
|
||||
|
||||
|
@ -660,7 +661,11 @@ impl BundleFile {
|
|||
S: AsRef<str>,
|
||||
{
|
||||
match file_type {
|
||||
BundleFileType::Lua => lua::compile(name, sjson).await,
|
||||
BundleFileType::Lua => {
|
||||
let sjson =
|
||||
CString::new(sjson.as_ref()).wrap_err("failed to build CString from SJSON")?;
|
||||
lua::compile(name, sjson)
|
||||
}
|
||||
BundleFileType::Unknown(_) => {
|
||||
eyre::bail!("Unknown file type. Cannot compile from SJSON");
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use std::io::{Cursor, Write};
|
||||
use std::ffi::CStr;
|
||||
use std::io::Cursor;
|
||||
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use tokio::{fs, process::Command};
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use luajit2_sys as lua;
|
||||
|
||||
use crate::{
|
||||
binary::sync::WriteExt,
|
||||
bundle::file::{BundleFileVariant, UserFile},
|
||||
BundleFile, BundleFileType,
|
||||
};
|
||||
use crate::bundle::file::{BundleFileVariant, UserFile};
|
||||
use crate::{BundleFile, BundleFileType};
|
||||
|
||||
#[tracing::instrument(skip_all, fields(buf_len = data.as_ref().len()))]
|
||||
pub(crate) async fn decompile<T>(_ctx: &crate::Context, data: T) -> Result<Vec<UserFile>>
|
||||
|
@ -19,72 +18,81 @@ where
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn compile<S>(name: String, code: S) -> Result<BundleFile>
|
||||
pub fn compile<S>(name: String, code: S) -> Result<BundleFile>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
S: AsRef<CStr>,
|
||||
{
|
||||
let in_file_path = {
|
||||
let mut path = std::env::temp_dir();
|
||||
let name: String = std::iter::repeat_with(fastrand::alphanumeric)
|
||||
.take(10)
|
||||
.collect();
|
||||
path.push(name + "-dtmt.lua");
|
||||
let bytecode = unsafe {
|
||||
let state = lua::luaL_newstate();
|
||||
lua::luaL_openlibs(state);
|
||||
|
||||
path
|
||||
};
|
||||
lua::lua_pushstring(state, code.as_ref().as_ptr() as _);
|
||||
lua::lua_setglobal(state, b"code\0".as_ptr() as _);
|
||||
|
||||
let out_file_path = {
|
||||
let mut path = std::env::temp_dir();
|
||||
lua::lua_pushstring(state, name.as_ptr() as _);
|
||||
lua::lua_setglobal(state, b"name\0".as_ptr() as _);
|
||||
|
||||
let name: String = std::iter::repeat_with(fastrand::alphanumeric)
|
||||
.take(10)
|
||||
.collect();
|
||||
path.push(name + "-dtmt.luab");
|
||||
let run = b"return string.dump(loadstring(code, \"@\" .. name), false)\0";
|
||||
match lua::luaL_loadstring(state, run.as_ptr() as _) as u32 {
|
||||
lua::LUA_OK => {}
|
||||
lua::LUA_ERRSYNTAX => {
|
||||
let err = lua::lua_tostring(state, -1);
|
||||
let err = CStr::from_ptr(err).to_string_lossy().to_string();
|
||||
|
||||
path
|
||||
};
|
||||
lua::lua_close(state);
|
||||
|
||||
fs::write(&in_file_path, code.as_ref().as_bytes())
|
||||
.await
|
||||
.wrap_err_with(|| format!("failed to write file {}", in_file_path.display()))?;
|
||||
eyre::bail!("Invalid syntax: {}", err);
|
||||
}
|
||||
lua::LUA_ERRMEM => {
|
||||
lua::lua_close(state);
|
||||
eyre::bail!("Failed to allocate sufficient memory to compile LuaJIT bytecode")
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// TODO: Make executable name configurable
|
||||
Command::new("luajit")
|
||||
.arg("-bg")
|
||||
.arg("-F")
|
||||
.arg(name.clone() + ".lua")
|
||||
.arg("-o")
|
||||
.arg("Windows")
|
||||
.arg(&in_file_path)
|
||||
.arg(&out_file_path)
|
||||
.status()
|
||||
.await
|
||||
.wrap_err("failed to compile to LuaJIT byte code")?;
|
||||
match lua::lua_pcall(state, 0, 1, 0) as u32 {
|
||||
lua::LUA_OK => {
|
||||
// The binary data is pretty much guaranteed to contain NUL bytes,
|
||||
// so we can't rely on `lua_tostring` and `CStr` here. Instead we have to
|
||||
// explicitely query the string length and build our vector from that.
|
||||
// However, on the bright side, we don't have to go through any string types anymore,
|
||||
// and can instead treat it as raw bytes immediately.
|
||||
let mut len = 0;
|
||||
let data = lua::lua_tolstring(state, -1, &mut len) as *const u8;
|
||||
let data = std::slice::from_raw_parts(data, len).to_vec();
|
||||
|
||||
let mut data = Cursor::new(Vec::new());
|
||||
|
||||
let bytecode = {
|
||||
let mut data = fs::read(&out_file_path)
|
||||
.await
|
||||
.wrap_err_with(|| format!("failed to read file {}", out_file_path.display()))?;
|
||||
|
||||
// Add Fatshark's custom magic bytes
|
||||
data[1] = 0x46;
|
||||
data[2] = 0x53;
|
||||
data[3] = 0x82;
|
||||
lua::lua_close(state);
|
||||
|
||||
data
|
||||
}
|
||||
lua::LUA_ERRRUN => {
|
||||
let err = lua::lua_tostring(state, -1);
|
||||
let err = CStr::from_ptr(err).to_string_lossy().to_string();
|
||||
|
||||
lua::lua_close(state);
|
||||
|
||||
eyre::bail!("Failed to compile LuaJIT bytecode: {}", err);
|
||||
}
|
||||
lua::LUA_ERRMEM => {
|
||||
lua::lua_close(state);
|
||||
eyre::bail!("Failed to allocate sufficient memory to compile LuaJIT bytecode")
|
||||
}
|
||||
// We don't use an error handler function, so this should be unreachable
|
||||
lua::LUA_ERRERR => unreachable!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
data.write_u32(bytecode.len() as u32)?;
|
||||
// I believe this is supposed to be a uleb128, but it seems to be always 0x2 in binary.
|
||||
data.write_u64(0x2)?;
|
||||
data.write_all(&bytecode)?;
|
||||
// TODO: How are these twelve bytes generated?
|
||||
let mut data = vec![0x0; 12];
|
||||
// Set Fatshark's custom LuaJIT magic bytes
|
||||
data.extend_from_slice(&[0x1b, 0x46, 0x53, 0x82]);
|
||||
data.extend_from_slice(&bytecode[4..]);
|
||||
|
||||
let mut file = BundleFile::new(name, BundleFileType::Lua);
|
||||
let mut variant = BundleFileVariant::new();
|
||||
|
||||
variant.set_data(data.into_inner());
|
||||
variant.set_data(data);
|
||||
file.add_variant(variant);
|
||||
|
||||
Ok(file)
|
||||
|
|
Loading…
Add table
Reference in a new issue