diff --git a/Cargo.lock b/Cargo.lock index 2e3679a..ef8b80e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/lib/sdk/Cargo.toml b/lib/sdk/Cargo.toml index e504a76..5c2ef1a 100644 --- a/lib/sdk/Cargo.toml +++ b/lib/sdk/Cargo.toml @@ -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" diff --git a/lib/sdk/src/bundle/file.rs b/lib/sdk/src/bundle/file.rs index 853003a..2efa9b2 100644 --- a/lib/sdk/src/bundle/file.rs +++ b/lib/sdk/src/bundle/file.rs @@ -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, { 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"); } diff --git a/lib/sdk/src/filetype/lua.rs b/lib/sdk/src/filetype/lua.rs index 87ac629..3ce8bdd 100644 --- a/lib/sdk/src/filetype/lua.rs +++ b/lib/sdk/src/filetype/lua.rs @@ -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(_ctx: &crate::Context, data: T) -> Result> @@ -19,72 +18,81 @@ where } #[tracing::instrument(skip_all)] -pub(crate) async fn compile(name: String, code: S) -> Result +pub fn compile(name: String, code: S) -> Result where - S: AsRef, + S: AsRef, { - 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 _); + + lua::lua_pushstring(state, name.as_ptr() as _); + lua::lua_setglobal(state, b"name\0".as_ptr() as _); + + 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(); + + lua::lua_close(state); + + eyre::bail!("Invalid syntax: {}", err); + } + lua::LUA_ERRMEM => { + lua::lua_close(state); + eyre::bail!("Failed to allocate sufficient memory to compile LuaJIT bytecode") + } + _ => unreachable!(), + } + + 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(); + + 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!(), + } }; - let out_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.luab"); - - path - }; - - fs::write(&in_file_path, code.as_ref().as_bytes()) - .await - .wrap_err_with(|| format!("failed to write file {}", in_file_path.display()))?; - - // 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")?; - - 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; - - data - }; - - 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)