Add Tauri launcher with Svelte UI
- Accounts: panel login/sign-up, Microsoft device-code sign-in, offline play; tokens encrypted with a key kept in the OS credential store - Home: instance rail, live server status, news feed, social links, Play button with download progress, speed and ETA - Full settings: accounts, memory/resolution/after-launch behaviour, Java and GC presets, Minecraft keybind editor applied to options.txt, theme overrides, storage manager, self-update via GitHub releases - Game console, crash dialog with hints, per-instance overrides - Live admin branding (colours, fonts, backgrounds, custom CSS) - Online install tests for vanilla/Fabric/Quilt/Forge/NeoForge (CI) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
60 files changed
+9424
-47
No files matched your search
@@ -268,7 +268,10 @@ fn write_argfile(path: &Path, args: &[String]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn spawn(cmd: &Command, java_major: u32) -> Result<tokio::process::Child> {
|
||||
/// Start the game. With `capture` the game's stdout/stderr are piped back to
|
||||
/// us (for the console); without it they're discarded, which is required
|
||||
/// when the launcher exits while the game keeps running.
|
||||
pub fn spawn(cmd: &Command, java_major: u32, capture: bool) -> Result<tokio::process::Child> {
|
||||
std::fs::create_dir_all(&cmd.cwd)?;
|
||||
let mut proc = tokio::process::Command::new(&cmd.java);
|
||||
let total_len: usize = cmd.args.iter().map(|a| a.len() + 3).sum();
|
||||
@@ -280,10 +283,8 @@ pub fn spawn(cmd: &Command, java_major: u32) -> Result<tokio::process::Child> {
|
||||
} else {
|
||||
proc.args(&cmd.args);
|
||||
}
|
||||
proc.current_dir(&cmd.cwd)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
let out = || if capture { std::process::Stdio::piped() } else { std::process::Stdio::null() };
|
||||
proc.current_dir(&cmd.cwd).stdin(std::process::Stdio::null()).stdout(out()).stderr(out());
|
||||
#[cfg(windows)]
|
||||
proc.creation_flags(0x0800_0000);
|
||||
proc.spawn().with_context(|| format!("starting {}", cmd.java.display()))
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//! Real-network installs against Mojang / Fabric / Forge / NeoForge.
|
||||
//! Ignored by default; CI runs them with `cargo test -p scopenet-core --test online -- --ignored`.
|
||||
|
||||
use scopenet_core::install::{install, InstallSpec};
|
||||
use scopenet_core::launch::{self, Auth, GcPreset, LaunchOptions};
|
||||
use scopenet_core::{progress, Layout};
|
||||
use scopenet_shared::Loader;
|
||||
|
||||
async fn run(mc: &str, loader: Loader) {
|
||||
let root = std::env::var("SCOPENET_TEST_ROOT").map(std::path::PathBuf::from).unwrap_or_else(|_| std::env::temp_dir().join("scopenet-online"));
|
||||
let layout = Layout::new(&root);
|
||||
let client = scopenet_core::http::client();
|
||||
let game_dir = layout.instance_dir(&format!("{mc}-{}", loader.as_str()));
|
||||
let spec = InstallSpec { mc_version: mc.into(), loader, loader_version: None, java_override: None, game_dir: game_dir.clone(), concurrency: 16, deep_verify: false };
|
||||
let installed = install(&client, &layout, &spec, &progress::noop()).await.unwrap_or_else(|e| panic!("{mc} {loader:?}: {e:#}"));
|
||||
|
||||
for path in installed.classpath(&layout) {
|
||||
assert!(path.exists(), "missing classpath entry {}", path.display());
|
||||
}
|
||||
assert!(installed.java.exists(), "java missing at {}", installed.java.display());
|
||||
|
||||
let opts = LaunchOptions {
|
||||
auth: Auth { username: "CiBot".into(), uuid: scopenet_shared::offline_uuid("CiBot"), access_token: "0".into(), user_type: "legacy".into(), xuid: None },
|
||||
game_dir,
|
||||
memory_min_mb: 512,
|
||||
memory_max_mb: 2048,
|
||||
gc: GcPreset::G1,
|
||||
extra_jvm_args: vec![],
|
||||
resolution: None,
|
||||
fullscreen: false,
|
||||
join_server: Some(("example.org".into(), 25565)),
|
||||
version_label: "ci".into(),
|
||||
launcher_name: "scopenet".into(),
|
||||
launcher_version: "ci".into(),
|
||||
};
|
||||
let cmd = launch::build(&installed, &layout, &opts);
|
||||
let joined = cmd.args.join(" ");
|
||||
assert!(!joined.contains("${"), "unsubstituted placeholder in: {joined}");
|
||||
|
||||
// The downloaded runtime must actually run.
|
||||
let out = std::process::Command::new(scopenet_core::java::sibling_exe(&installed.java, false)).arg("-version").output().unwrap();
|
||||
assert!(out.status.success());
|
||||
println!("{mc} {loader:?}: OK ({} classpath entries, java {})", installed.classpath(&layout).len(), installed.java_major);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn vanilla_latest_release() {
|
||||
let m = scopenet_core::meta::mojang_manifest(&scopenet_core::http::client()).await.unwrap();
|
||||
run(&m.latest.release, Loader::Vanilla).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn vanilla_legacy_1_8_9() {
|
||||
run("1.8.9", Loader::Vanilla).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn fabric_1_21_1() {
|
||||
run("1.21.1", Loader::Fabric).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn quilt_1_20_1() {
|
||||
run("1.20.1", Loader::Quilt).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn forge_1_20_1() {
|
||||
run("1.20.1", Loader::Forge).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn neoforge_1_21_1() {
|
||||
run("1.21.1", Loader::NeoForge).await;
|
||||
}
|
||||
Reference in new issue
Block a user