use serde::Deserialize;
use crate::error::LaunchError;
use crate::models::minecraft::{Library, MinecraftVersionJson};
macro_rules! lwjgl_bytes {
($arch:literal, $ver:literal) => {
include_bytes!(concat!(
"../../assets/LWJGL/",
$arch,
"/",
$ver,
".json"
))
.as_ref()
};
}
fn arm_lwjgl_data(arch: &str, version: &str) -> Option<&'static [u8]> {
let version = if version.contains("2.9") { "2.9.4" } else { version };
match (arch, version) {
("aarch64", "2.9.4") => Some(lwjgl_bytes!("aarch64", "2.9.4")),
("aarch64", "3.1.2") => Some(lwjgl_bytes!("aarch64", "3.1.2")),
("aarch64", "3.2.2") => Some(lwjgl_bytes!("aarch64", "3.2.2")),
("aarch64", "3.3.1") => Some(lwjgl_bytes!("aarch64", "3.3.1")),
("aarch64", "3.3.2") => Some(lwjgl_bytes!("aarch64", "3.3.2")),
("aarch", "2.9.4") => Some(lwjgl_bytes!("aarch", "2.9.4")),
("aarch", "3.3.1") => Some(lwjgl_bytes!("aarch", "3.3.1")),
_ => None,
}
}
pub fn process_json(version: &mut MinecraftVersionJson) -> Result<(), LaunchError> {
let mapped_arch = match std::env::consts::ARCH {
"aarch64" => "aarch64",
"arm" => "aarch",
_ => return Ok(()), };
let version_jinput = find_version(&version.libraries, &["net.java.jinput:jinput-platform:", "net.java.jinput:jinput:"]);
let version_lwjgl = find_version(&version.libraries, &["org.lwjgl:lwjgl:", "org.lwjgl.lwjgl:lwjgl:"]);
if version_jinput.is_some() {
version.libraries.retain(|lib| !lib.name.contains("jinput"));
}
if let Some(lwjgl_ver) = version_lwjgl {
version.libraries.retain(|lib| !lib.name.contains("lwjgl"));
match arm_lwjgl_data(mapped_arch, &lwjgl_ver) {
Some(bytes) => {
let set: LwjglLibrarySet = serde_json::from_slice(bytes)?;
version.libraries.extend(set.libraries);
}
None => {
}
}
}
Ok(())
}
fn find_version(libs: &[Library], prefixes: &[&str]) -> Option<String> {
libs.iter()
.find(|lib| prefixes.iter().any(|p| lib.name.starts_with(p)))
.and_then(|lib| lib.name.split(':').last())
.map(|v| v.to_string())
}
#[derive(Deserialize)]
struct LwjglLibrarySet {
libraries: Vec<Library>,
}
pub fn uses_lwjgl2(version: &MinecraftVersionJson) -> bool {
version
.libraries
.iter()
.any(|lib| lib.name.starts_with("org.lwjgl.lwjgl:lwjgl:2."))
}
#[cfg(target_os = "linux")]
pub fn xrandr_in_path() -> bool {
std::env::var_os("PATH")
.map(|p| {
std::env::split_paths(&p)
.any(|dir| dir.join("xrandr").is_file())
})
.unwrap_or(false)
}
#[cfg(target_os = "linux")]
pub async fn write_xrandr_stub(dir: &std::path::Path) -> Result<(), LaunchError> {
use std::os::unix::fs::PermissionsExt;
let stub = dir.join("xrandr");
if stub.exists() {
return Ok(());
}
tokio::fs::create_dir_all(dir).await?;
let script = "\
#!/bin/sh
# Minimal xrandr stub — used by LWJGL 2 on systems without the real xrandr.
W=1920; H=1080
if command -v xdpyinfo >/dev/null 2>&1; then
RES=$(xdpyinfo 2>/dev/null | awk '/dimensions:/{print $2}' | head -1)
if [ -n \"$RES\" ]; then W=${RES%x*}; H=${RES#*x}; fi
fi
printf 'Screen 0: minimum 8 x 8, current %s x %s, maximum 32767 x 32767\\n' \"$W\" \"$H\"
printf 'HDMI-1 connected %sx%s+0+0 (normal left inverted right x axis y axis) 0mm x 0mm\\n' \"$W\" \"$H\"
printf ' %sx%s 60.00*+\\n' \"$W\" \"$H\"
";
tokio::fs::write(&stub, script).await?;
tokio::fs::set_permissions(&stub, std::fs::Permissions::from_mode(0o755)).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_lib(name: &str) -> Library {
Library {
name: name.to_string(),
rules: None,
natives: None,
downloads: None,
url: None,
loader: None,
}
}
fn libs(names: &[&str]) -> Vec<Library> {
names.iter().map(|n| make_lib(n)).collect()
}
#[test]
fn find_version_returns_last_colon_segment() {
let l = libs(&["org.lwjgl:lwjgl:3.3.1", "org.lwjgl:lwjgl-opengl:3.3.1"]);
assert_eq!(find_version(&l, &["org.lwjgl:lwjgl:"]), Some("3.3.1".into()));
}
#[test]
fn find_version_returns_none_when_absent() {
let l = libs(&["com.example:something:1.0"]);
assert_eq!(find_version(&l, &["org.lwjgl:lwjgl:"]), None);
}
#[test]
fn find_version_matches_multiple_prefixes() {
let l = libs(&["org.lwjgl.lwjgl:lwjgl:2.9.4"]);
let v = find_version(&l, &["org.lwjgl:lwjgl:", "org.lwjgl.lwjgl:lwjgl:"]);
assert_eq!(v, Some("2.9.4".into()));
}
#[test]
fn arm_lwjgl_data_normalises_29x() {
assert!(arm_lwjgl_data("aarch64", "2.9.0").is_some());
assert!(arm_lwjgl_data("aarch64", "2.9.1").is_some());
assert!(arm_lwjgl_data("aarch64", "2.9.4").is_some());
}
#[test]
fn arm_lwjgl_data_returns_none_for_unknown() {
assert!(arm_lwjgl_data("aarch64", "4.0.0").is_none());
assert!(arm_lwjgl_data("x86_64", "3.3.1").is_none());
}
#[test]
fn process_json_noop_on_current_arch() {
if matches!(std::env::consts::ARCH, "aarch64" | "arm") {
return; }
let mut version = MinecraftVersionJson {
id: "1.20.4".into(),
version_type: "release".into(),
assets: None,
asset_index: None,
downloads: None,
libraries: libs(&["org.lwjgl:lwjgl:3.3.1", "org.lwjgl:lwjgl-opengl:3.3.1"]),
arguments: None,
minecraft_arguments: None,
java_version: None,
main_class: None,
has_natives: false,
};
let original_count = version.libraries.len();
process_json(&mut version).unwrap();
assert_eq!(version.libraries.len(), original_count);
}
}