Miscellaneous changes #266

Merged
lucas merged 49 commits from feat/misc into master 2025-07-02 16:25:44 +02:00
40 changed files with 844 additions and 95 deletions

View file

@ -1,6 +1,7 @@
# https://jake-shadle.github.io/xwin/ # https://jake-shadle.github.io/xwin/
FROM debian:bullseye-slim as xwin FROM debian:bullseye-slim AS xwin
# renovate: datasource=github-releases depName=xwin packageName=Jake-Shadle/xwin
ARG XWIN_VERSION=0.5.2 ARG XWIN_VERSION=0.5.2
ARG XWIN_PREFIX="xwin-$XWIN_VERSION-x86_64-unknown-linux-musl" ARG XWIN_PREFIX="xwin-$XWIN_VERSION-x86_64-unknown-linux-musl"
ADD https://github.com/Jake-Shadle/xwin/releases/download/$XWIN_VERSION/$XWIN_PREFIX.tar.gz /root/$XWIN_PREFIX.tar.gz ADD https://github.com/Jake-Shadle/xwin/releases/download/$XWIN_VERSION/$XWIN_PREFIX.tar.gz /root/$XWIN_PREFIX.tar.gz
@ -31,7 +32,7 @@ RUN set -eux; \
# And to keep that to a minimum, we still delete the stuff we don't need. # And to keep that to a minimum, we still delete the stuff we don't need.
rm -rf /root/.xwin-cache; rm -rf /root/.xwin-cache;
FROM rust:slim-bullseye as linux FROM rust:slim-bullseye AS linux
RUN set -eux; \ RUN set -eux; \
apt-get update; \ apt-get update; \
@ -58,8 +59,9 @@ WORKDIR /src/dtmt
COPY lib/oodle/*.so lib/oodle/*.a /src/ COPY lib/oodle/*.so lib/oodle/*.a /src/
FROM linux as msvc FROM linux AS msvc
# renovate: datasource=github-releases depName=llvm packageName=llvm/llvm-project
ARG LLVM_VERSION=18 ARG LLVM_VERSION=18
ENV KEYRINGS /usr/local/share/keyrings ENV KEYRINGS /usr/local/share/keyrings

10
.gitmodules vendored
View file

@ -1,11 +1,11 @@
[submodule "lib/luajit2-sys"]
path = lib/luajit2-sys
url = https://github.com/sclu1034/luajit2-sys.git
[submodule "lib/color-eyre"] [submodule "lib/color-eyre"]
path = lib/color-eyre path = lib/color-eyre
url = https://github.com/sclu1034/color-eyre.git url = https://github.com/sclu1034/color-eyre.git
branch = "fork" branch = "fork"
[submodule "lib/ansi-parser"] [submodule "lib/ansi-parser"]
path = lib/ansi-parser path = lib/ansi-parser
url = https://gitlab.com/lschwiderski/ansi-parser.git url = https://gitlab.com/lschwiderski/ansi-parser.git
branch = "issue/outdated-nom" branch = "issue/outdated-nom"
[submodule "lib/luajit2-sys/luajit"]
path = lib/luajit2-sys/luajit
url = https://github.com/LuaJIT/LuaJIT.git

View file

@ -10,5 +10,35 @@
"baseBranches": [ "baseBranches": [
"$default", "$default",
"/^release\\/.*/" "/^release\\/.*/"
],
"ignorePaths": [
"lib/color_eyre/**",
"lib/ansi-parser/**",
"lib/luajit2-sys/**",
"**/target/**"
],
"customManagers": [
{
"customType": "regex",
"description": "Update _VERSION variables in Dockerfiles",
"fileMatch": [
"(^|/|\\.)Dockerfile$",
"(^|/)Dockerfile\\.[^/]*$"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-]+?)(?: depName=(?<depName>.+?))? packageName=(?<packageName>.+?)(?: versioning=(?<versioning>[a-z-]+?))?\\s(?:ENV|ARG) .+?_VERSION=(?<currentValue>.+?)\\s"
]
}
],
"packageRules": [
{
"matchDatasources": [
"github-releases"
],
"matchPackageNames": [
"llvm/llvm-project"
],
"extractVersion": "^llvmorg-(?<version>\\d+)\\.\\d+\\.\\d+$"
}
] ]
} }

12
Cargo.lock generated
View file

@ -379,9 +379,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.13" version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -2173,9 +2173,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.172" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -2184,7 +2184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -4581,7 +4581,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]

View file

@ -16,8 +16,10 @@ ansi-parser = "0.9.1"
ansi_term = "0.12.1" ansi_term = "0.12.1"
async-recursion = "1.0.5" async-recursion = "1.0.5"
bincode = "2.0.0" bincode = "2.0.0"
bindgen = "0.70.1"
bitflags = "2.5.0" bitflags = "2.5.0"
byteorder = "1.4.3" byteorder = "1.4.3"
cc = { version = "1.2.27", features = ["parallel"] }
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "string", "unicode"] } clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "string", "unicode"] }
cli-table = { version = "0.5.0", default-features = false, features = ["derive"] } cli-table = { version = "0.5.0", default-features = false, features = ["derive"] }
color-eyre = { path = "lib/color-eyre" } color-eyre = { path = "lib/color-eyre" }
@ -28,11 +30,13 @@ druid = { version = "0.8", features = ["im", "serde", "image", "png", "jpeg", "b
druid-widget-nursery = "0.1" druid-widget-nursery = "0.1"
dtmt-shared = { path = "lib/dtmt-shared" } dtmt-shared = { path = "lib/dtmt-shared" }
fastrand = "2.1.0" fastrand = "2.1.0"
fs_extra = "1.1.0"
futures = "0.3.25" futures = "0.3.25"
futures-util = "0.3.24" futures-util = "0.3.24"
glob = "0.3.0" glob = "0.3.0"
interprocess = "2.1.0" interprocess = "2.1.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
libc = "0.2.174"
luajit2-sys = { path = "lib/luajit2-sys" } luajit2-sys = { path = "lib/luajit2-sys" }
minijinja = { version = "2.0.1", default-features = false, features = ["serde"] } minijinja = { version = "2.0.1", default-features = false, features = ["serde"] }
nanorand = "0.8.0" nanorand = "0.8.0"

View file

@ -469,7 +469,7 @@ async fn patch_boot_bundle(state: Arc<ActionState>, deployment_info: &str) -> Re
} }
.instrument(tracing::trace_span!("read boot bundle")) .instrument(tracing::trace_span!("read boot bundle"))
.await .await
.wrap_err_with(|| format!("Failed to read bundle '{}'", BOOT_BUNDLE_NAME))?; .wrap_err_with(|| format!("Failed to read bundle '{BOOT_BUNDLE_NAME}'"))?;
{ {
tracing::trace!("Adding mod package file to boot bundle"); tracing::trace!("Adding mod package file to boot bundle");

View file

@ -208,7 +208,7 @@ pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> {
for p in paths { for p in paths {
let path = bundle_dir.join(p); let path = bundle_dir.join(p);
let backup = bundle_dir.join(format!("{}.bak", p)); let backup = bundle_dir.join(format!("{p}.bak"));
let res = async { let res = async {
tracing::debug!( tracing::debug!(

View file

@ -363,7 +363,7 @@ fn extract_legacy_mod<R: Read + Seek>(
for i in 0..file_count { for i in 0..file_count {
let mut f = archive let mut f = archive
.by_index(i) .by_index(i)
.wrap_err_with(|| format!("Failed to get file at index {}", i))?; .wrap_err_with(|| format!("Failed to get file at index {i}"))?;
let Some(name) = f.enclosed_name().map(|p| p.to_path_buf()) else { let Some(name) = f.enclosed_name().map(|p| p.to_path_buf()) else {
let err = eyre::eyre!("File name in archive is not a safe path value.").suggestion( let err = eyre::eyre!("File name in archive is not a safe path value.").suggestion(
@ -426,7 +426,7 @@ pub(crate) async fn import_from_file(state: ActionState, info: FileInfo) -> Resu
let mod_info = api let mod_info = api
.mods_id(id) .mods_id(id)
.await .await
.wrap_err_with(|| format!("Failed to query mod {} from Nexus", id))?; .wrap_err_with(|| format!("Failed to query mod {id} from Nexus"))?;
let version = match api.file_version(id, timestamp).await { let version = match api.file_version(id, timestamp).await {
Ok(version) => version, Ok(version) => version,
@ -461,13 +461,13 @@ pub(crate) async fn import_from_file(state: ActionState, info: FileInfo) -> Resu
pub(crate) async fn import_from_nxm(state: ActionState, uri: String) -> Result<ModInfo> { pub(crate) async fn import_from_nxm(state: ActionState, uri: String) -> Result<ModInfo> {
let url = uri let url = uri
.parse() .parse()
.wrap_err_with(|| format!("Invalid Uri '{}'", uri))?; .wrap_err_with(|| format!("Invalid Uri '{uri}'"))?;
let api = NexusApi::new(state.nexus_api_key.to_string())?; let api = NexusApi::new(state.nexus_api_key.to_string())?;
let (mod_info, file_info, data) = api let (mod_info, file_info, data) = api
.handle_nxm(url) .handle_nxm(url)
.await .await
.wrap_err_with(|| format!("Failed to download mod from NXM uri '{}'", uri))?; .wrap_err_with(|| format!("Failed to download mod from NXM uri '{uri}'"))?;
let nexus = NexusInfo::from(mod_info); let nexus = NexusInfo::from(mod_info);
import_mod(state, Some((nexus, file_info.version)), data).await import_mod(state, Some((nexus, file_info.version)), data).await
@ -524,7 +524,7 @@ pub(crate) async fn import_mod(
let data = api let data = api
.picture(url) .picture(url)
.await .await
.wrap_err_with(|| format!("Failed to download Nexus image from '{}'", url))?; .wrap_err_with(|| format!("Failed to download Nexus image from '{url}'"))?;
let img = image_data_to_buffer(&data)?; let img = image_data_to_buffer(&data)?;

View file

@ -47,7 +47,7 @@ fn notify_nxm_download(
.to_ns_name::<GenericNamespaced>() .to_ns_name::<GenericNamespaced>()
.expect("Invalid socket name"), .expect("Invalid socket name"),
) )
.wrap_err_with(|| format!("Failed to connect to '{}'", IPC_ADDRESS)) .wrap_err_with(|| format!("Failed to connect to '{IPC_ADDRESS}'"))
.suggestion("Make sure the main window is open.")?; .suggestion("Make sure the main window is open.")?;
tracing::debug!("Connected to main process at '{}'", IPC_ADDRESS); tracing::debug!("Connected to main process at '{}'", IPC_ADDRESS);
@ -159,7 +159,7 @@ fn main() -> Result<()> {
loop { loop {
let res = server.accept().wrap_err_with(|| { let res = server.accept().wrap_err_with(|| {
format!("IPC server failed to listen on '{}'", IPC_ADDRESS) format!("IPC server failed to listen on '{IPC_ADDRESS}'")
}); });
match res { match res {

View file

@ -108,20 +108,19 @@ impl std::fmt::Debug for AsyncAction {
match self { match self {
AsyncAction::DeployMods(_) => write!(f, "AsyncAction::DeployMods(_state)"), AsyncAction::DeployMods(_) => write!(f, "AsyncAction::DeployMods(_state)"),
AsyncAction::ResetDeployment(_) => write!(f, "AsyncAction::ResetDeployment(_state)"), AsyncAction::ResetDeployment(_) => write!(f, "AsyncAction::ResetDeployment(_state)"),
AsyncAction::AddMod(_, info) => write!(f, "AsyncAction::AddMod(_state, {:?})", info), AsyncAction::AddMod(_, info) => write!(f, "AsyncAction::AddMod(_state, {info:?})"),
AsyncAction::DeleteMod(_, info) => { AsyncAction::DeleteMod(_, info) => {
write!(f, "AsyncAction::DeleteMod(_state, {:?})", info) write!(f, "AsyncAction::DeleteMod(_state, {info:?})")
} }
AsyncAction::SaveSettings(_) => write!(f, "AsyncAction::SaveSettings(_state)"), AsyncAction::SaveSettings(_) => write!(f, "AsyncAction::SaveSettings(_state)"),
AsyncAction::CheckUpdates(_) => write!(f, "AsyncAction::CheckUpdates(_state)"), AsyncAction::CheckUpdates(_) => write!(f, "AsyncAction::CheckUpdates(_state)"),
AsyncAction::LoadInitial((path, is_default)) => write!( AsyncAction::LoadInitial((path, is_default)) => write!(
f, f,
"AsyncAction::LoadInitial(({:?}, {:?}))", "AsyncAction::LoadInitial(({path:?}, {is_default:?}))"
path, is_default
), ),
AsyncAction::Log(_) => write!(f, "AsyncAction::Log(_)"), AsyncAction::Log(_) => write!(f, "AsyncAction::Log(_)"),
AsyncAction::NxmDownload(_, uri) => { AsyncAction::NxmDownload(_, uri) => {
write!(f, "AsyncAction::NxmDownload(_state, {})", uri) write!(f, "AsyncAction::NxmDownload(_state, {uri})")
} }
} }
} }
@ -448,7 +447,7 @@ impl AppDelegate<State> for Delegate {
if let Err(err) = open::that_detached(Arc::as_ref(url)) { if let Err(err) = open::that_detached(Arc::as_ref(url)) {
tracing::error!( tracing::error!(
"{:?}", "{:?}",
Report::new(err).wrap_err(format!("Failed to open url '{}'", url)) Report::new(err).wrap_err(format!("Failed to open url '{url}'"))
); );
} }

View file

@ -76,7 +76,7 @@ impl ColorExt for Color {
fn darken(&self, fac: f32) -> Self { fn darken(&self, fac: f32) -> Self {
let (r, g, b, a) = self.as_rgba(); let (r, g, b, a) = self.as_rgba();
let rgb = Rgb::from(r as f32, g as f32, b as f32); let rgb = Rgb::from(r as f32, g as f32, b as f32);
let rgb = rgb.lighten(-1. * fac); let rgb = rgb.lighten(-fac);
Self::rgba( Self::rgba(
rgb.get_red() as f64, rgb.get_red() as f64,
rgb.get_green() as f64, rgb.get_green() as f64,

View file

@ -5,6 +5,7 @@ use druid::{
use crate::state::{State, ACTION_SET_DIRTY, ACTION_START_SAVE_SETTINGS}; use crate::state::{State, ACTION_SET_DIRTY, ACTION_START_SAVE_SETTINGS};
#[allow(dead_code)]
pub struct DisabledButtonController; pub struct DisabledButtonController;
impl<T: Data> Controller<T, Button<T>> for DisabledButtonController { impl<T: Data> Controller<T, Button<T>> for DisabledButtonController {

View file

@ -34,9 +34,9 @@ pub fn error<T: Data>(err: Report, _parent: WindowHandle) -> WindowDesc<T> {
// The second to last one, the context to the root cause // The second to last one, the context to the root cause
let context = err.chain().nth(count - 2).unwrap(); let context = err.chain().nth(count - 2).unwrap();
(format!("{first}!"), format!("{}: {}", context, root)) (format!("{first}!"), format!("{context}: {root}"))
} else { } else {
("An error occurred!".to_string(), format!("{}: {}", first, root)) ("An error occurred!".to_string(), format!("{first}: {root}"))
} }
} }
}; };

View file

@ -348,7 +348,7 @@ fn build_mod_details_info() -> impl Widget<State> {
let nexus_link = Maybe::or_empty(|| { let nexus_link = Maybe::or_empty(|| {
let link = Label::raw().lens(NexusInfo::id.map( let link = Label::raw().lens(NexusInfo::id.map(
|id| { |id| {
let url = format!("https://nexusmods.com/warhammer40kdarktide/mods/{}", id); let url = format!("https://nexusmods.com/warhammer40kdarktide/mods/{id}");
let mut builder = RichTextBuilder::new(); let mut builder = RichTextBuilder::new();
builder builder
.push("Open on Nexusmods") .push("Open on Nexusmods")

View file

@ -94,10 +94,10 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
match bundle_name { match bundle_name {
IdString64::String(name) => { IdString64::String(name) => {
println!("{:016x} {}", bundle_hash, name); println!("{bundle_hash:016x} {name}");
} }
IdString64::Hash(hash) => { IdString64::Hash(hash) => {
println!("{:016x}", hash); println!("{hash:016x}");
} }
} }
@ -110,7 +110,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
println!("\t{:016x}.{:<12} {}", file.name, extension, name); println!("\t{:016x}.{:<12} {}", file.name, extension, name);
} }
IdString64::Hash(hash) => { IdString64::Hash(hash) => {
println!("\t{:016x}.{}", hash, extension); println!("\t{hash:016x}.{extension}");
} }
} }
} }
@ -127,10 +127,10 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
match bundle_name { match bundle_name {
IdString64::String(name) => { IdString64::String(name) => {
println!("{:016x} {}", bundle_hash, name); println!("{bundle_hash:016x} {name}");
} }
IdString64::Hash(hash) => { IdString64::Hash(hash) => {
println!("{:016x}", hash); println!("{hash:016x}");
} }
} }
} }
@ -158,7 +158,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
for bundle in bundles { for bundle in bundles {
found = true; found = true;
println!("{:016x}", bundle); println!("{bundle:016x}");
} }
if !found { if !found {

View file

@ -287,6 +287,34 @@ where
P1: AsRef<Path> + std::fmt::Debug, P1: AsRef<Path> + std::fmt::Debug,
P2: AsRef<Path> + std::fmt::Debug, P2: AsRef<Path> + std::fmt::Debug,
{ {
let ctx = if ctx.game_dir.is_some() {
tracing::debug!(
"Got game directory from config: {}",
ctx.game_dir.as_ref().unwrap().display()
);
ctx
} else {
let game_dir = path
.as_ref()
.parent()
.and_then(|parent| parent.parent())
.map(|p| p.to_path_buf());
tracing::info!(
"No game directory configured, guessing from bundle path: {:?}",
game_dir
);
Arc::new(sdk::Context {
game_dir,
lookup: Arc::clone(&ctx.lookup),
ljd: ctx.ljd.clone(),
revorb: ctx.revorb.clone(),
ww2ogg: ctx.ww2ogg.clone(),
})
};
let bundle = { let bundle = {
let data = fs::read(path.as_ref()).await?; let data = fs::read(path.as_ref()).await?;
let name = Bundle::get_name_from_path(&ctx, path.as_ref()); let name = Bundle::get_name_from_path(&ctx, path.as_ref());
@ -445,7 +473,7 @@ where
} }
} }
Err(err) => { Err(err) => {
let err = err.wrap_err(format!("Failed to decompile file {}", name)); let err = err.wrap_err(format!("Failed to decompile file {name}"));
tracing::error!("{:?}", err); tracing::error!("{:?}", err);
} }
}; };

View file

@ -147,7 +147,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
let patch_number = matches let patch_number = matches
.get_one::<u16>("patch") .get_one::<u16>("patch")
.map(|num| format!("{:03}", num)); .map(|num| format!("{num:03}"));
let output_path = matches let output_path = matches
.get_one::<PathBuf>("output") .get_one::<PathBuf>("output")
@ -156,7 +156,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
let mut output_path = bundle_path.clone(); let mut output_path = bundle_path.clone();
if let Some(patch_number) = patch_number.as_ref() { if let Some(patch_number) = patch_number.as_ref() {
output_path.set_extension(format!("patch_{:03}", patch_number)); output_path.set_extension(format!("patch_{patch_number:03}"));
} }
output_path output_path
@ -196,7 +196,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
span.record("output_path", output_path.display().to_string()); span.record("output_path", output_path.display().to_string());
span.record("raw", sub_matches.get_flag("raw")); span.record("raw", sub_matches.get_flag("raw"));
span.record("target_name", target_name.display().to_string()); span.record("target_name", target_name.display().to_string());
span.record("file_type", format!("{:?}", file_type)); span.record("file_type", format!("{file_type:?}"));
} }
} }

View file

@ -38,7 +38,7 @@ enum OutputFormat {
fn format_byte_size(size: usize) -> String { fn format_byte_size(size: usize) -> String {
if size < 1024 { if size < 1024 {
format!("{} Bytes", size) format!("{size} Bytes")
} else if size < 1024 * 1024 { } else if size < 1024 * 1024 {
format!("{} kB", size / 1024) format!("{} kB", size / 1024)
} else if size < 1024 * 1024 * 1024 { } else if size < 1024 * 1024 * 1024 {

View file

@ -1,4 +1,5 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command, ValueEnum}; use clap::{value_parser, Arg, ArgAction, ArgMatches, Command, ValueEnum};
use cli_table::{print_stdout, WithTitle}; use cli_table::{print_stdout, WithTitle};
@ -156,6 +157,8 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
BufReader::new(Box::new(f)) BufReader::new(Box::new(f))
}; };
let lookup = Arc::make_mut(&mut ctx.lookup);
let group = sdk::murmur::HashGroup::from(*group); let group = sdk::murmur::HashGroup::from(*group);
let mut added = 0; let mut added = 0;
@ -165,15 +168,15 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
let total = { let total = {
for line in lines.into_iter() { for line in lines.into_iter() {
let value = line?; let value = line?;
if ctx.lookup.find(&value, group).is_some() { if lookup.find(&value, group).is_some() {
skipped += 1; skipped += 1;
} else { } else {
ctx.lookup.add(value, group); lookup.add(value, group);
added += 1; added += 1;
} }
} }
ctx.lookup.len() lookup.len()
}; };
let out_path = matches let out_path = matches
@ -190,7 +193,7 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
}) })
.with_section(|| out_path.display().to_string().header("Path:"))?; .with_section(|| out_path.display().to_string().header("Path:"))?;
ctx.lookup lookup
.to_csv(f) .to_csv(f)
.await .await
.wrap_err("Failed to write dictionary to disk")?; .wrap_err("Failed to write dictionary to disk")?;

View file

@ -164,7 +164,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
.iter() .iter()
.map(|(path_tmpl, content_tmpl)| { .map(|(path_tmpl, content_tmpl)| {
env.render_str(path_tmpl, &render_ctx) env.render_str(path_tmpl, &render_ctx)
.wrap_err_with(|| format!("Failed to render template: {}", path_tmpl)) .wrap_err_with(|| format!("Failed to render template: {path_tmpl}"))
.and_then(|path| { .and_then(|path| {
env.render_named_str(&path, content_tmpl, &render_ctx) env.render_named_str(&path, content_tmpl, &render_ctx)
.wrap_err_with(|| format!("Failed to render template '{}'", &path)) .wrap_err_with(|| format!("Failed to render template '{}'", &path))

View file

@ -1,6 +1,5 @@
#![feature(io_error_more)] #![feature(io_error_more)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(result_flattening)]
#![feature(test)] #![feature(test)]
#![windows_subsystem = "console"] #![windows_subsystem = "console"]
@ -12,6 +11,7 @@ use clap::value_parser;
use clap::{command, Arg}; use clap::{command, Arg};
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::{Context, Result}; use color_eyre::eyre::{Context, Result};
use sdk::murmur::Dictionary;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::fs::File; use tokio::fs::File;
use tokio::io::BufReader; use tokio::io::BufReader;
@ -107,8 +107,9 @@ async fn main() -> Result<()> {
let r = BufReader::new(f); let r = BufReader::new(f);
let mut ctx = ctx.write().await; let mut ctx = ctx.write().await;
if let Err(err) = ctx.lookup.from_csv(r).await { match Dictionary::from_csv(r).await {
tracing::error!("{:#}", err); Ok(lookup) => ctx.lookup = Arc::new(lookup),
Err(err) => tracing::error!("{:#}", err),
} }
}) })
}; };

@ -1 +1 @@
Subproject commit 228b8ca37ee79ab9afa45c40da415e4dcb029751 Subproject commit bdefeef09803df45bdf6dae7f3ae289e58427e3a

View file

@ -19,7 +19,7 @@ pub const TIME_FORMAT: &[FormatItem] = format_description!("[hour]:[minute]:[sec
pub fn format_fields(w: &mut Writer<'_>, field: &Field, val: &dyn std::fmt::Debug) -> Result { pub fn format_fields(w: &mut Writer<'_>, field: &Field, val: &dyn std::fmt::Debug) -> Result {
if field.name() == "message" { if field.name() == "message" {
write!(w, "{:?}", val) write!(w, "{val:?}")
} else { } else {
Ok(()) Ok(())
} }
@ -70,7 +70,7 @@ where
writer, writer,
"[{}] [{:>5}] ", "[{}] [{:>5}] ",
time, time,
color.bold().paint(format!("{}", level)) color.bold().paint(format!("{level}"))
)?; )?;
ctx.field_format().format_fields(writer.by_ref(), event)?; ctx.field_format().format_fields(writer.by_ref(), event)?;

@ -1 +0,0 @@
Subproject commit 6d94a4dd2c296bf1f044ee4c70fb10dca4c1c241

View file

@ -0,0 +1,20 @@
[package]
name = "luajit2-sys"
version = "0.0.2"
description = "LuaJIT-2.1 FFI Bindings"
authors = ["Aaron Loucks <aloucks@cofront.net>"]
edition = "2021"
keywords = ["lua", "luajit", "script"]
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/aloucks/luajit2-sys"
documentation = "https://docs.rs/luajit2-sys"
links = "luajit"
[dependencies]
libc = { workspace = true }
[build-dependencies]
bindgen = { workspace = true }
cc = { workspace = true }
fs_extra = { workspace = true }

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/LICENSE-2.0
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

217
lib/luajit2-sys/build.rs Normal file
View file

@ -0,0 +1,217 @@
use cc::Build;
use fs_extra::dir;
use fs_extra::dir::CopyOptions;
use std::env;
use std::path::PathBuf;
use std::process::{Command, Stdio};
const LIB_NAME: &str = "luajit";
const LUAJIT_HEADERS: [&str; 4] = ["lua.h", "lualib.h", "lauxlib.h", "luajit.h"];
const LUAJIT_SRC: [&str; 65] = [
// LJCORE_O
// The MSVC toolchain cannot compile this assembler file,
// as it contains GNU-specific directives
// "lj_vm.S",
"lj_gc.c",
"lj_err.c",
"lj_char.c",
"lj_bc.c",
"lj_obj.c",
"lj_buf.c",
"lj_str.c",
"lj_tab.c",
"lj_func.c",
"lj_udata.c",
"lj_meta.c",
"lj_debug.c",
"lj_state.c",
"lj_dispatch.c",
"lj_vmevent.c",
"lj_vmmath.c",
"lj_strscan.c",
"lj_strfmt.c",
"lj_strfmt_num.c",
"lj_api.c",
"lj_profile.c",
"lj_lex.c",
"lj_parse.c",
"lj_bcread.c",
"lj_bcwrite.c",
"lj_load.c",
"lj_ir.c",
"lj_opt_mem.c",
"lj_opt_fold.c",
"lj_opt_narrow.c",
"lj_opt_dce.c",
"lj_opt_loop.c",
"lj_opt_split.c",
"lj_opt_sink.c",
"lj_mcode.c",
"lj_snap.c",
"lj_record.c",
"lj_crecord.c",
"lj_ffrecord.c",
"lj_asm.c",
"lj_trace.c",
"lj_gdbjit.c",
"lj_ctype.c",
"lj_cdata.c",
"lj_cconv.c",
"lj_ccall.c",
"lj_ccallback.c",
"lj_carith.c",
"lj_clib.c",
"lj_cparse.c",
"lj_lib.c",
"lj_alloc.c",
// LJLIB_O
"lib_aux.c",
"lib_base.c",
"lib_math.c",
"lib_bit.c",
"lib_string.c",
"lib_table.c",
"lib_io.c",
"lib_os.c",
"lib_package.c",
"lib_debug.c",
"lib_jit.c",
"lib_ffi.c",
"lib_init.c",
];
fn build_gcc(src_dir: &str) {
let mut buildcmd = Command::new("make");
if let Ok(flags) = env::var("CARGO_MAKEFLAGS") {
buildcmd.env("MAKEFLAGS", flags);
} else {
buildcmd.arg("-j8");
}
buildcmd.current_dir(src_dir);
buildcmd.stderr(Stdio::inherit());
buildcmd.arg("--no-silent");
// We do need to cross-compile even here, so that `lj_vm.o` is created
// for the correct architecture.
if env::var("CARGO_CFG_WINDOWS").is_ok() {
buildcmd.arg("TARGET_SYS=Windows");
buildcmd.arg("CROSS=x86_64-w64-mingw32-");
}
if cfg!(target_pointer_width = "32") {
buildcmd.arg("HOST_CC='gcc -m32'");
buildcmd.arg("-e");
} else {
buildcmd.arg("HOST_CC='gcc'");
}
let mut child = buildcmd.spawn().expect("failed to run make");
child
.wait()
.map(|status| status.success())
.expect("Failed to build LuaJIT");
}
fn build_msvc(src_dir: &str, out_dir: &str) {
let mut cc = Build::new();
// cc can't handle many of the `clang-dl`-specific flags, so
// we need to port them manually from a `make -n` run.
cc.out_dir(out_dir)
// `llvm-as` (which the clang-based toolchain for MSVC would use to compile `lj_vm.S`
// assembler) doesn't support some of the GNU-specific directives.
// However, the previous host-targeted compilation already created the
// object, so we simply link that.
.object(format!("{src_dir}/lj_vm.o"))
.define("_FILE_OFFSET_BITS", "64")
.define("_LARGEFILE_SOURCE", None)
.define("LUA_MULTILIB", "\"lib\"")
.define("LUAJIT_UNWIND_EXTERNAL", None)
.flag("-fcolor-diagnostics")
// Disable warnings
.flag("/W0")
.flag("/U _FORTIFY_SOURCE")
// Link statically
.flag("/MT")
// Omit frame pointers
.flag("/Oy");
for f in LUAJIT_SRC {
cc.file(format!("{src_dir}/{f}"));
}
cc.compile(LIB_NAME);
}
fn main() {
let luajit_dir = format!("{}/luajit", env!("CARGO_MANIFEST_DIR"));
let out_dir = env::var("OUT_DIR").unwrap();
let src_dir = format!("{out_dir}/luajit/src");
dbg!(&luajit_dir);
dbg!(&out_dir);
dbg!(&src_dir);
let mut copy_options = CopyOptions::new();
copy_options.overwrite = true;
dir::copy(&luajit_dir, &out_dir, &copy_options).expect("Failed to copy LuaJIT source");
// The first run builds with and for the host architecture.
// This also creates all the tools and generated sources that a compilation needs.
build_gcc(&src_dir);
// Then, for cross-compilation, we can utilize those generated
// sources to re-compile just the library.
if env::var("CARGO_CFG_WINDOWS").is_ok() {
build_msvc(&src_dir, &out_dir);
println!("cargo:rustc-link-search={out_dir}");
} else {
println!("cargo:rustc-link-search=native={src_dir}");
}
println!("cargo:lib-name={LIB_NAME}");
println!("cargo:include={src_dir}");
println!("cargo:rustc-link-lib=static={LIB_NAME}");
let mut bindings = bindgen::Builder::default();
for header in LUAJIT_HEADERS {
println!("cargo:rerun-if-changed={luajit_dir}/src/{header}");
bindings = bindings.header(format!("{luajit_dir}/src/{header}"));
}
let bindings = bindings
.allowlist_var("LUA.*")
.allowlist_var("LUAJIT.*")
.allowlist_type("lua_.*")
.allowlist_type("luaL_.*")
.allowlist_function("lua_.*")
.allowlist_function("luaL_.*")
.allowlist_function("luaJIT.*")
.ctypes_prefix("libc")
.impl_debug(true)
.use_core()
.detect_include_paths(true)
.formatter(bindgen::Formatter::Rustfmt)
.sort_semantically(true)
.merge_extern_blocks(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
let bindings = if env::var("CARGO_CFG_WINDOWS").is_ok() {
bindings
.clang_arg("-I/xwin/sdk/include/ucrt")
.clang_arg("-I/xwin/sdk/include/um")
.clang_arg("-I/xwin/sdk/include/shared")
.clang_arg("-I/xwin/crt/include")
.generate()
.expect("Failed to generate bindings")
} else {
bindings.generate().expect("Failed to generate bindings")
};
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Failed to write bindings");
}

@ -0,0 +1 @@
Subproject commit 70f4b15ee45a6137fe6b48b941faea79d72f7159

167
lib/luajit2-sys/src/lib.rs Normal file
View file

@ -0,0 +1,167 @@
#![no_std]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(clippy::deprecated_semver)]
#![allow(clippy::missing_safety_doc)]
//! # LuaJIT 2.1
//!
//! <http://luajit.org>
//!
//! <http://www.lua.org/manual/5.1/manual.html>
//!
//! ## Performance considerations
//!
//! The _Not Yet Implemented_ guide documents which language features will be JIT compiled
//! into native machine code.
//!
//! <http://wiki.luajit.org/NYI>
mod ffi {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
pub use ffi::*;
use core::ptr;
// These are defined as macros
/// <https://www.lua.org/manual/5.1/manual.html#lua_pop>
#[inline]
pub unsafe fn lua_pop(L: *mut lua_State, idx: libc::c_int) {
lua_settop(L, -(idx) - 1)
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_newtable>
#[inline]
pub unsafe fn lua_newtable(L: *mut lua_State) {
lua_createtable(L, 0, 0)
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_register>
#[inline]
pub unsafe fn lua_register(L: *mut lua_State, name: *const libc::c_char, f: lua_CFunction) {
lua_pushcfunction(L, f);
lua_setglobal(L, name);
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_pushcfunction>
#[inline]
pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0);
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_strlen>
#[inline]
pub unsafe fn lua_strlen(L: *mut lua_State, idx: libc::c_int) -> usize {
lua_objlen(L, idx)
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isfunction>
#[inline]
pub unsafe fn lua_isfunction(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TFUNCTION as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_istable>
#[inline]
pub unsafe fn lua_istable(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TTABLE as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_islightuserdata>
#[inline]
pub unsafe fn lua_islightuserdata(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TLIGHTUSERDATA as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isnil>
#[inline]
pub unsafe fn lua_isnil(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TNIL as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isboolean>
#[inline]
pub unsafe fn lua_isboolean(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TBOOLEAN as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isthread>
#[inline]
pub unsafe fn lua_isthread(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TTHREAD as i32) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isnone>
#[inline]
pub unsafe fn lua_isnone(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) == LUA_TNONE) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_isnoneornil>
#[inline]
pub unsafe fn lua_isnoneornil(L: *mut lua_State, idx: libc::c_int) -> libc::c_int {
(lua_type(L, idx) <= 0) as i32
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_pushliteral>
#[inline]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &str) {
lua_pushlstring(L, s.as_ptr() as _, s.len() as _);
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_setglobal>
#[inline]
pub unsafe fn lua_setglobal(L: *mut lua_State, k: *const libc::c_char) {
lua_setfield(L, LUA_GLOBALSINDEX, k);
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_getglobal>
#[inline]
pub unsafe fn lua_getglobal(L: *mut lua_State, k: *const libc::c_char) {
lua_getfield(L, LUA_GLOBALSINDEX, k)
}
/// <https://www.lua.org/manual/5.1/manual.html#lua_tostring>
#[inline]
pub unsafe fn lua_tostring(L: *mut lua_State, idx: libc::c_int) -> *const libc::c_char {
lua_tolstring(L, idx, ptr::null_mut())
}
// Additional compatibility items that are defined as macros
/// `luaL_newstate()`
#[inline]
#[deprecated(since = "Lua 5.1", note = "replace with `luaL_newstate()`")]
pub unsafe fn lua_open() -> *mut lua_State {
luaL_newstate()
}
/// `lua_pushvalue(L, LUA_REGISTRYINDEX)`
#[inline]
#[deprecated(
since = "Lua 5.1",
note = "replace with `lua_pushvalue(L, LUA_REGISTRYINDEX)`"
)]
pub unsafe fn lua_getregistry(L: *mut lua_State) {
lua_pushvalue(L, LUA_REGISTRYINDEX)
}
/// `lua_gc(L, LUA_GCCOUNT as _, 0)`
#[inline]
#[deprecated(
since = "Lua 5.1",
note = "replace with `lua_gc(L, LUA_GCCOUNT as _, 0)`"
)]
pub unsafe fn lua_getgccount(L: *mut lua_State) -> libc::c_int {
lua_gc(L, LUA_GCCOUNT as _, 0)
}
/// `lua_Reader`
#[deprecated(since = "Lua 5.1", note = "replace with `lua_Reader`")]
pub type lua_Chunkreader = lua_Reader;
/// `lua_Writer`
#[deprecated(since = "Lua 5.1", note = "replace with `lua_Writer`")]
pub type lua_Chunkwriter = lua_Writer;

View file

@ -99,7 +99,7 @@ impl Api {
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn mods_id(&self, id: u64) -> Result<Mod> { pub async fn mods_id(&self, id: u64) -> Result<Mod> {
let url = BASE_URL_GAME.join(&format!("mods/{}.json", id))?; let url = BASE_URL_GAME.join(&format!("mods/{id}.json"))?;
let req = self.client.get(url); let req = self.client.get(url);
self.send(req).await self.send(req).await
} }

View file

@ -11,7 +11,7 @@ fn main() {
} else { } else {
"oo2core_win64" "oo2core_win64"
}; };
println!("cargo:rustc-link-lib=static={}", lib_name); println!("cargo:rustc-link-lib=static={lib_name}");
} else { } else {
println!("cargo:rustc-link-lib=static=oo2corelinux64"); println!("cargo:rustc-link-lib=static=oo2corelinux64");
println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=stdc++");

View file

@ -52,6 +52,7 @@ impl From<OodleLZ_CheckCRC> for bindings::OodleLZ_CheckCRC {
#[tracing::instrument(skip(data))] #[tracing::instrument(skip(data))]
pub fn decompress<I>( pub fn decompress<I>(
data: I, data: I,
out_size: usize,
fuzz_safe: OodleLZ_FuzzSafe, fuzz_safe: OodleLZ_FuzzSafe,
check_crc: OodleLZ_CheckCRC, check_crc: OodleLZ_CheckCRC,
) -> Result<Vec<u8>> ) -> Result<Vec<u8>>
@ -59,7 +60,7 @@ where
I: AsRef<[u8]>, I: AsRef<[u8]>,
{ {
let data = data.as_ref(); let data = data.as_ref();
let mut out = vec![0; CHUNK_SIZE]; let mut out = vec![0; out_size];
let verbosity = if tracing::enabled!(tracing::Level::INFO) { let verbosity = if tracing::enabled!(tracing::Level::INFO) {
bindings::OodleLZ_Verbosity_OodleLZ_Verbosity_Minimal bindings::OodleLZ_Verbosity_OodleLZ_Verbosity_Minimal

View file

@ -44,10 +44,10 @@ impl<T: FromBinary> FromBinary for Vec<T> {
pub mod sync { pub mod sync {
use std::ffi::CStr; use std::ffi::CStr;
use std::io::{self, Read, Seek, SeekFrom}; use std::io::{self, Read, Seek, SeekFrom, Write};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use color_eyre::eyre::WrapErr; use color_eyre::eyre::{self, WrapErr};
use color_eyre::{Help, Report, Result, SectionExt}; use color_eyre::{Help, Report, Result, SectionExt};
macro_rules! make_read { macro_rules! make_read {
@ -123,7 +123,7 @@ pub mod sync {
}; };
} }
pub trait ReadExt: ReadBytesExt + Seek { pub trait ReadExt: Read + Seek {
fn read_u8(&mut self) -> io::Result<u8> { fn read_u8(&mut self) -> io::Result<u8> {
ReadBytesExt::read_u8(self) ReadBytesExt::read_u8(self)
} }
@ -131,7 +131,6 @@ pub mod sync {
make_read!(read_u32, read_u32_le, u32); make_read!(read_u32, read_u32_le, u32);
make_read!(read_u64, read_u64_le, u64); make_read!(read_u64, read_u64_le, u64);
make_skip!(skip_u8, read_u8, u8);
make_skip!(skip_u32, read_u32, u32); make_skip!(skip_u32, read_u32, u32);
// Implementation based on https://en.wikipedia.com/wiki/LEB128 // Implementation based on https://en.wikipedia.com/wiki/LEB128
@ -181,9 +180,17 @@ pub mod sync {
res res
} }
} }
fn read_bool(&mut self) -> Result<bool> {
match ReadExt::read_u8(self)? {
0 => Ok(false),
1 => Ok(true),
v => eyre::bail!("Invalid value for boolean '{}'", v),
}
}
} }
pub trait WriteExt: WriteBytesExt + Seek { pub trait WriteExt: Write + Seek {
fn write_u8(&mut self, val: u8) -> io::Result<()> { fn write_u8(&mut self, val: u8) -> io::Result<()> {
WriteBytesExt::write_u8(self, val) WriteBytesExt::write_u8(self, val)
} }
@ -191,6 +198,10 @@ pub mod sync {
make_write!(write_u32, write_u32_le, u32); make_write!(write_u32, write_u32_le, u32);
make_write!(write_u64, write_u64_le, u64); make_write!(write_u64, write_u64_le, u64);
fn write_bool(&mut self, val: bool) -> io::Result<()> {
WriteBytesExt::write_u8(self, if val { 1 } else { 0 })
}
fn write_padding(&mut self) -> io::Result<usize> { fn write_padding(&mut self) -> io::Result<usize> {
let pos = self.stream_position()?; let pos = self.stream_position()?;
let size = 16 - (pos % 16) as usize; let size = 16 - (pos % 16) as usize;
@ -207,8 +218,8 @@ pub mod sync {
} }
} }
impl<R: ReadBytesExt + Seek + ?Sized> ReadExt for R {} impl<R: Read + Seek + ?Sized> ReadExt for R {}
impl<W: WriteBytesExt + Seek + ?Sized> WriteExt for W {} impl<W: Write + Seek + ?Sized> WriteExt for W {}
pub(crate) fn _read_up_to<R>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize> pub(crate) fn _read_up_to<R>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize>
where where
@ -236,7 +247,7 @@ pub mod sync {
fn read_string_len(mut r: impl Read, len: usize) -> Result<String> { fn read_string_len(mut r: impl Read, len: usize) -> Result<String> {
let mut buf = vec![0; len]; let mut buf = vec![0; len];
r.read_exact(&mut buf) r.read_exact(&mut buf)
.wrap_err_with(|| format!("Failed to read {} bytes", len))?; .wrap_err_with(|| format!("Failed to read {len} bytes"))?;
let res = match CStr::from_bytes_until_nul(&buf) { let res = match CStr::from_bytes_until_nul(&buf) {
Ok(s) => { Ok(s) => {
@ -248,6 +259,6 @@ pub mod sync {
res.wrap_err("Invalid binary for UTF8 string") res.wrap_err("Invalid binary for UTF8 string")
.with_section(|| format!("{}", String::from_utf8_lossy(&buf)).header("ASCI:")) .with_section(|| format!("{}", String::from_utf8_lossy(&buf)).header("ASCI:"))
.with_section(|| format!("{:x?}", buf).header("Bytes:")) .with_section(|| format!("{buf:x?}").header("Bytes:"))
} }
} }

View file

@ -15,17 +15,18 @@ use super::filetype::BundleFileType;
#[derive(Debug)] #[derive(Debug)]
struct BundleFileHeader { struct BundleFileHeader {
variant: u32, variant: u32,
unknown_1: u8, external: bool,
size: usize, size: usize,
unknown_1: u8,
len_data_file_name: usize, len_data_file_name: usize,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct BundleFileVariant { pub struct BundleFileVariant {
property: u32, property: u32,
data: Vec<u8>, data: Vec<u8>,
data_file_name: Option<String>, data_file_name: Option<String>,
// Seems to be related to whether there is a data path. external: bool,
unknown_1: u8, unknown_1: u8,
} }
@ -39,6 +40,7 @@ impl BundleFileVariant {
property: 0, property: 0,
data: Vec::new(), data: Vec::new(),
data_file_name: None, data_file_name: None,
external: false,
unknown_1: 0, unknown_1: 0,
} }
} }
@ -63,21 +65,30 @@ impl BundleFileVariant {
self.data_file_name.as_ref() self.data_file_name.as_ref()
} }
pub fn external(&self) -> bool {
self.external
}
pub fn unknown_1(&self) -> u8 {
self.unknown_1
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn read_header<R>(r: &mut R) -> Result<BundleFileHeader> fn read_header<R>(r: &mut R) -> Result<BundleFileHeader>
where where
R: Read + Seek, R: Read + Seek,
{ {
let variant = r.read_u32()?; let variant = r.read_u32()?;
let unknown_1 = r.read_u8()?; let external = r.read_bool()?;
let size = r.read_u32()? as usize; let size = r.read_u32()? as usize;
r.skip_u8(1)?; let unknown_1 = r.read_u8()?;
let len_data_file_name = r.read_u32()? as usize; let len_data_file_name = r.read_u32()? as usize;
Ok(BundleFileHeader { Ok(BundleFileHeader {
size, size,
unknown_1, external,
variant, variant,
unknown_1,
len_data_file_name, len_data_file_name,
}) })
} }
@ -88,7 +99,7 @@ impl BundleFileVariant {
W: Write + Seek, W: Write + Seek,
{ {
w.write_u32(self.property)?; w.write_u32(self.property)?;
w.write_u8(self.unknown_1)?; w.write_bool(self.external)?;
let len_data_file_name = self.data_file_name.as_ref().map(|s| s.len()).unwrap_or(0); let len_data_file_name = self.data_file_name.as_ref().map(|s| s.len()).unwrap_or(0);
@ -106,6 +117,26 @@ impl BundleFileVariant {
} }
} }
impl std::fmt::Debug for BundleFileVariant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut out = f.debug_struct("BundleFileVariant");
out.field("property", &self.property);
if self.data.len() <= 5 {
out.field("data", &format!("{:x?}", &self.data));
} else {
out.field(
"data",
&format!("{:x?}.. ({} bytes)", &self.data[..5], &self.data.len()),
);
}
out.field("data_file_name", &self.data_file_name)
.field("external", &self.external)
.finish()
}
}
bitflags! { bitflags! {
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct Properties: u32 { pub struct Properties: u32 {
@ -204,6 +235,7 @@ impl BundleFile {
let s = r let s = r
.read_string_len(header.len_data_file_name) .read_string_len(header.len_data_file_name)
.wrap_err("Failed to read data file name")?; .wrap_err("Failed to read data file name")?;
Some(s) Some(s)
} else { } else {
None None
@ -216,6 +248,7 @@ impl BundleFile {
property: header.variant, property: header.variant,
data, data,
data_file_name, data_file_name,
external: header.external,
unknown_1: header.unknown_1, unknown_1: header.unknown_1,
}; };
@ -243,7 +276,7 @@ impl BundleFile {
for variant in self.variants.iter() { for variant in self.variants.iter() {
w.write_u32(variant.property())?; w.write_u32(variant.property())?;
w.write_u8(variant.unknown_1)?; w.write_bool(variant.external)?;
let len_data_file_name = variant.data_file_name().map(|s| s.len()).unwrap_or(0); let len_data_file_name = variant.data_file_name().map(|s| s.len()).unwrap_or(0);
@ -359,18 +392,16 @@ impl BundleFile {
Ok(files) Ok(files)
} }
#[tracing::instrument(name = "File::decompiled", skip_all)] #[tracing::instrument(
name = "File::decompiled",
skip_all,
fields(file = self.name(false, None), file_type = self.file_type().ext_name(), variants = self.variants.len())
)]
pub async fn decompiled(&self, ctx: &crate::Context) -> Result<Vec<UserFile>> { pub async fn decompiled(&self, ctx: &crate::Context) -> Result<Vec<UserFile>> {
let file_type = self.file_type(); let file_type = self.file_type();
if tracing::enabled!(tracing::Level::DEBUG) { // The `Strings` type handles all variants combined.
tracing::debug!( // For the other ones, each variant will be its own file.
name = self.name(true, None),
variants = self.variants.len(),
"Attempting to decompile"
);
}
if file_type == BundleFileType::Strings { if file_type == BundleFileType::Strings {
return strings::decompile(ctx, &self.variants); return strings::decompile(ctx, &self.variants);
} }

View file

@ -1,4 +1,5 @@
use color_eyre::{eyre, Result}; use color_eyre::eyre;
use color_eyre::Result;
use serde::Serialize; use serde::Serialize;
use crate::murmur::Murmur64; use crate::murmur::Murmur64;

View file

@ -162,6 +162,7 @@ impl Bundle {
// TODO: Optimize to not reallocate? // TODO: Optimize to not reallocate?
let mut raw_buffer = oodle::decompress( let mut raw_buffer = oodle::decompress(
&compressed_buffer, &compressed_buffer,
oodle::CHUNK_SIZE,
OodleLZ_FuzzSafe::No, OodleLZ_FuzzSafe::No,
OodleLZ_CheckCRC::No, OodleLZ_CheckCRC::No,
) )
@ -359,6 +360,7 @@ where
// TODO: Optimize to not reallocate? // TODO: Optimize to not reallocate?
let mut raw_buffer = oodle::decompress( let mut raw_buffer = oodle::decompress(
&compressed_buffer, &compressed_buffer,
oodle::CHUNK_SIZE,
OodleLZ_FuzzSafe::No, OodleLZ_FuzzSafe::No,
OodleLZ_CheckCRC::No, OodleLZ_CheckCRC::No,
)?; )?;

View file

@ -1,8 +1,11 @@
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::{ffi::OsString, path::PathBuf}; use std::sync::Arc;
use crate::murmur::{Dictionary, HashGroup, IdString64, Murmur32, Murmur64}; use crate::murmur::{Dictionary, HashGroup, IdString64, Murmur32, Murmur64};
#[derive(Clone)]
pub struct CmdLine { pub struct CmdLine {
cmd: OsString, cmd: OsString,
args: Vec<OsString>, args: Vec<OsString>,
@ -52,7 +55,7 @@ impl From<&CmdLine> for Command {
} }
pub struct Context { pub struct Context {
pub lookup: Dictionary, pub lookup: Arc<Dictionary>,
pub ljd: Option<CmdLine>, pub ljd: Option<CmdLine>,
pub revorb: Option<String>, pub revorb: Option<String>,
pub ww2ogg: Option<String>, pub ww2ogg: Option<String>,
@ -62,7 +65,7 @@ pub struct Context {
impl Context { impl Context {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
lookup: Dictionary::new(), lookup: Arc::new(Dictionary::new()),
ljd: None, ljd: None,
revorb: None, revorb: None,
ww2ogg: None, ww2ogg: None,

View file

@ -48,6 +48,7 @@ struct Row {
group: HashGroup, group: HashGroup,
} }
#[derive(Clone)]
pub struct Entry { pub struct Entry {
value: String, value: String,
long: Murmur64, long: Murmur64,
@ -73,6 +74,7 @@ impl Entry {
} }
} }
#[derive(Clone)]
pub struct Dictionary { pub struct Dictionary {
entries: Vec<Entry>, entries: Vec<Entry>,
} }
@ -88,10 +90,12 @@ impl Dictionary {
Self { entries: vec![] } Self { entries: vec![] }
} }
pub async fn from_csv<R>(&mut self, r: R) -> Result<()> pub async fn from_csv<R>(r: R) -> Result<Self>
where where
R: AsyncRead + std::marker::Unpin + std::marker::Send, R: AsyncRead + std::marker::Unpin + std::marker::Send,
{ {
let mut entries = vec![];
let r = AsyncDeserializer::from_reader(r); let r = AsyncDeserializer::from_reader(r);
let mut records = r.into_deserialize::<Row>(); let mut records = r.into_deserialize::<Row>();
@ -112,10 +116,10 @@ impl Dictionary {
group: record.group, group: record.group,
}; };
self.entries.push(entry); entries.push(entry);
} }
Ok(()) Ok(Self { entries })
} }
pub async fn to_csv<W>(&self, w: W) -> Result<()> pub async fn to_csv<W>(&self, w: W) -> Result<()>
@ -161,7 +165,7 @@ impl Dictionary {
self.entries.push(entry); self.entries.push(entry);
} }
pub fn find(&mut self, value: &String, group: HashGroup) -> Option<&Entry> { pub fn find(&self, value: &String, group: HashGroup) -> Option<&Entry> {
self.entries self.entries
.iter() .iter()
.find(|e| e.value == *value && e.group == group) .find(|e| e.value == *value && e.group == group)

View file

@ -50,7 +50,7 @@ impl fmt::LowerHex for Murmur64 {
impl fmt::Display for Murmur64 { impl fmt::Display for Murmur64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016X}", self) write!(f, "{self:016X}")
} }
} }
@ -158,7 +158,7 @@ impl fmt::LowerHex for Murmur32 {
impl fmt::Display for Murmur32 { impl fmt::Display for Murmur32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:08X}", self) write!(f, "{self:08X}")
} }
} }