modified: Cargo.toml
modified: src/config.rs modified: src/launcher.rs modified: src/main.rs modified: src/minecraft.rs modified: src/util.rs modified: src/www/portable.html
This commit is contained in:
parent
9cd8fdb3f5
commit
e90b025568
@ -22,4 +22,10 @@ log = "0.4.26"
|
|||||||
env_logger = "0.11.7"
|
env_logger = "0.11.7"
|
||||||
toml = "0.8.20"
|
toml = "0.8.20"
|
||||||
nicotine = { git = "https://gitea.awain.net/alterwain/Nicotine.git", version = "0.1.22" }
|
nicotine = { git = "https://gitea.awain.net/alterwain/Nicotine.git", version = "0.1.22" }
|
||||||
rfd = "0.14"
|
rfd = "0.14"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
@ -49,6 +49,24 @@ impl LauncherConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assets_path(&self) -> PathBuf {
|
||||||
|
let mut p = self.launcher_dir();
|
||||||
|
p.push("assets");
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn libraries_path(&self) -> PathBuf {
|
||||||
|
let mut p = self.launcher_dir();
|
||||||
|
p.push("libraries");
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instances_path(&self) -> PathBuf {
|
||||||
|
let mut p = self.launcher_dir();
|
||||||
|
p.push("instances");
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
pub fn config_path(&self) -> PathBuf {
|
pub fn config_path(&self) -> PathBuf {
|
||||||
let mut p = self.launcher_dir();
|
let mut p = self.launcher_dir();
|
||||||
p.push("config.toml");
|
p.push("config.toml");
|
||||||
|
358
src/launcher.rs
358
src/launcher.rs
@ -5,12 +5,13 @@ use std::path::PathBuf;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::prelude::BASE64_STANDARD;
|
use base64::prelude::BASE64_STANDARD;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use crate::config::{LauncherCredentials, LauncherServer};
|
use crate::config::{LauncherCredentials, LauncherServer};
|
||||||
use crate::minecraft;
|
use crate::minecraft;
|
||||||
|
use crate::minecraft::multimc::Pack;
|
||||||
use crate::minecraft::session::SignUpResponse;
|
use crate::minecraft::session::SignUpResponse;
|
||||||
use crate::minecraft::versions::Version;
|
use crate::minecraft::versions::Version;
|
||||||
use crate::{config::LauncherConfig, minecraft::versions::VersionConfig, util};
|
use crate::{config::LauncherConfig, minecraft::versions::VersionConfig, util};
|
||||||
@ -167,8 +168,7 @@ impl Launcher {
|
|||||||
|
|
||||||
pub fn get_instances_list(&self) -> Vec<(String, String, String)> {
|
pub fn get_instances_list(&self) -> Vec<(String, String, String)> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
let mut instances = self.config.launcher_dir();
|
let instances = self.config.instances_path();
|
||||||
instances.push("instances");
|
|
||||||
if let Ok(entries) = std::fs::read_dir(instances) {
|
if let Ok(entries) = std::fs::read_dir(instances) {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if entry.is_err() { continue; }
|
if entry.is_err() { continue; }
|
||||||
@ -180,7 +180,7 @@ impl Launcher {
|
|||||||
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
||||||
let c_type = config.r#type;
|
let c_type = config.r#type;
|
||||||
let c_type = c_type.as_str();
|
let c_type = c_type.as_str();
|
||||||
v.push((config.id, c_type.to_string(), format!("data:image/png;base64,{}", BASE64_STANDARD.encode(match c_type {
|
v.push((entry.file_name().into_string().unwrap(), c_type.to_string(), format!("data:image/png;base64,{}", BASE64_STANDARD.encode(match c_type {
|
||||||
"old_alpha" => include_bytes!("www/icons/alpha.png").to_vec(),
|
"old_alpha" => include_bytes!("www/icons/alpha.png").to_vec(),
|
||||||
"old_beta" => include_bytes!("www/icons/beta.png").to_vec(),
|
"old_beta" => include_bytes!("www/icons/beta.png").to_vec(),
|
||||||
"release" | "snapshot" => include_bytes!("www/icons/release.png").to_vec(),
|
"release" | "snapshot" => include_bytes!("www/icons/release.png").to_vec(),
|
||||||
@ -194,8 +194,7 @@ impl Launcher {
|
|||||||
|
|
||||||
pub fn get_screenshots(&self) -> Vec<(String, String)> {
|
pub fn get_screenshots(&self) -> Vec<(String, String)> {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
let mut instances = self.config.launcher_dir();
|
let instances = self.config.instances_path();
|
||||||
instances.push("instances");
|
|
||||||
if let Ok(entries) = std::fs::read_dir(instances) {
|
if let Ok(entries) = std::fs::read_dir(instances) {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
if entry.is_err() { continue; }
|
if entry.is_err() { continue; }
|
||||||
@ -230,18 +229,15 @@ impl Launcher {
|
|||||||
token = server.credentials.password.clone();
|
token = server.credentials.password.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut instances = self.config.launcher_dir();
|
let mut instances = self.config.instances_path();
|
||||||
instances.push("instances");
|
|
||||||
instances.push(&instance_name);
|
instances.push(&instance_name);
|
||||||
instances.push("client.json");
|
instances.push("client.json");
|
||||||
|
|
||||||
let mut client_jar = self.config.launcher_dir();
|
let mut client_jar = self.config.instances_path();
|
||||||
client_jar.push("instances");
|
|
||||||
client_jar.push(&instance_name);
|
client_jar.push(&instance_name);
|
||||||
client_jar.push("client.jar");
|
client_jar.push("client.jar");
|
||||||
|
|
||||||
let mut instance_dir = self.config.launcher_dir();
|
let mut instance_dir = self.config.instances_path();
|
||||||
instance_dir.push("instances");
|
|
||||||
instance_dir.push(&instance_name);
|
instance_dir.push(&instance_name);
|
||||||
instance_dir.push("data");
|
instance_dir.push("data");
|
||||||
let _ = std::fs::create_dir_all(&instance_dir);
|
let _ = std::fs::create_dir_all(&instance_dir);
|
||||||
@ -258,8 +254,7 @@ impl Launcher {
|
|||||||
|
|
||||||
cmd.arg(["-Xmx", &self.config.ram_amount.to_string(), "M"].concat());
|
cmd.arg(["-Xmx", &self.config.ram_amount.to_string(), "M"].concat());
|
||||||
|
|
||||||
let mut natives_path = self.config.launcher_dir();
|
let mut natives_path = self.config.instances_path();
|
||||||
natives_path.push("instances");
|
|
||||||
natives_path.push(&instance_name);
|
natives_path.push(&instance_name);
|
||||||
natives_path.push("natives");
|
natives_path.push("natives");
|
||||||
|
|
||||||
@ -267,28 +262,28 @@ impl Launcher {
|
|||||||
cmd.arg(["-Dminecraft.client.jar=", client_jar.to_str().unwrap()].concat());
|
cmd.arg(["-Dminecraft.client.jar=", client_jar.to_str().unwrap()].concat());
|
||||||
cmd.arg("-cp");
|
cmd.arg("-cp");
|
||||||
|
|
||||||
|
let mut minecraft_arguments = None;
|
||||||
|
|
||||||
if let Ok(data) = std::fs::read(&instances) {
|
if let Ok(data) = std::fs::read(&instances) {
|
||||||
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
let config: VersionConfig = serde_json::from_slice(&data).unwrap();
|
||||||
|
minecraft_arguments = Some(config.minecraftArguments);
|
||||||
let mut libraries_cmd = Vec::new();
|
let mut libraries_cmd = Vec::new();
|
||||||
for library in config.libraries {
|
for library in config.libraries {
|
||||||
if let Some(classifier) = &library.downloads.classifiers {
|
if let Some(classifier) = &library.downloads.classifiers {
|
||||||
if let Some(natives) = &classifier.natives {
|
if let Some(natives) = &classifier.natives {
|
||||||
let rel_path = &natives.path;
|
let rel_path = &natives.path;
|
||||||
let mut libs = self.config.launcher_dir();
|
let libs = self.config.libraries_path();
|
||||||
libs.push("libraries");
|
|
||||||
let rel_path = [libs.to_str().unwrap(), "\\", &rel_path.replace("/", "\\")].concat();
|
let rel_path = [libs.to_str().unwrap(), "\\", &rel_path.replace("/", "\\")].concat();
|
||||||
let data = std::fs::read(rel_path).unwrap();
|
let data = std::fs::read(rel_path).unwrap();
|
||||||
|
|
||||||
let _ = zip_extract::extract(Cursor::new(data), &natives_path, true);
|
let _ = zip_extract::extract(Cursor::new(data), &natives_path, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut libs = self.config.launcher_dir();
|
let mut libs = self.config.libraries_path();
|
||||||
libs.push("libraries");
|
|
||||||
libs.push(library.to_pathbuf_file(false));
|
libs.push(library.to_pathbuf_file(false));
|
||||||
if library.name.contains("com.mojang:authlib") {
|
if library.name.contains("com.mojang:authlib") {
|
||||||
if let Some(server) = special_server {
|
if let Some(server) = special_server {
|
||||||
let mut patched_auth = self.config.launcher_dir();
|
let mut patched_auth = self.config.libraries_path();
|
||||||
patched_auth.push("libraries");
|
|
||||||
patched_auth.push(library.to_pathbuf_file(true));
|
patched_auth.push(library.to_pathbuf_file(true));
|
||||||
let _ = nicotine::patch_jar(libs.to_str().unwrap(), patched_auth.to_str().unwrap(), [b"https://sessionserver.mojang.com/session/minecraft/".as_slice(), b".minecraft.net".as_slice()].as_slice(), &[&[if self.config.allow_http { "http://" } else { "https://" }, &server.domain, ":", &server.session_server_port.to_string(), "/api/"].concat(), &server.domain]);
|
let _ = nicotine::patch_jar(libs.to_str().unwrap(), patched_auth.to_str().unwrap(), [b"https://sessionserver.mojang.com/session/minecraft/".as_slice(), b".minecraft.net".as_slice()].as_slice(), &[&[if self.config.allow_http { "http://" } else { "https://" }, &server.domain, ":", &server.session_server_port.to_string(), "/api/"].concat(), &server.domain]);
|
||||||
libraries_cmd.push([patched_auth.to_str().unwrap(), ";"].concat());
|
libraries_cmd.push([patched_auth.to_str().unwrap(), ";"].concat());
|
||||||
@ -302,14 +297,31 @@ impl Launcher {
|
|||||||
cmd.arg(libraries_cmd.concat());
|
cmd.arg(libraries_cmd.concat());
|
||||||
cmd.arg(config.mainClass.clone());
|
cmd.arg(config.mainClass.clone());
|
||||||
|
|
||||||
let mut game_dir = self.config.launcher_dir();
|
let mut game_dir = self.config.instances_path();
|
||||||
game_dir.push("instances");
|
|
||||||
game_dir.push(&instance_name);
|
game_dir.push(&instance_name);
|
||||||
game_dir.push("data");
|
game_dir.push("data");
|
||||||
|
|
||||||
let mut assets_dir = self.config.launcher_dir();
|
let mut assets_dir = self.config.assets_path();
|
||||||
assets_dir.push("assets");
|
|
||||||
cmd.args(["--username", username, "--version", &instance_name, "--gameDir", game_dir.to_str().unwrap(), "--assetsDir", assets_dir.to_str().unwrap(), "--assetIndex", &config.assetIndex.id, "--uuid", &uuid, "--accessToken", &token, "--userProperties", "{}", "--userType", "mojang", "--width", "925", "--height", "530"]);
|
let minecraft_arguments = minecraft_arguments.unwrap();
|
||||||
|
let minecraft_arguments = minecraft_arguments.split(" ");
|
||||||
|
for minecraft_argument in minecraft_arguments {
|
||||||
|
cmd.arg(match minecraft_argument {
|
||||||
|
"${auth_player_name}" => username,
|
||||||
|
"${version_name}" => &instance_name,
|
||||||
|
"${game_directory}" => game_dir.to_str().unwrap(),
|
||||||
|
"${assets_root}" => assets_dir.to_str().unwrap(),
|
||||||
|
"${assets_index_name}" => &config.assetIndex.as_ref().unwrap().id,
|
||||||
|
"${auth_uuid}" => &uuid,
|
||||||
|
"${auth_access_token}" => &token,
|
||||||
|
"${user_properties}" => "{}",
|
||||||
|
"${user_type}" => "mojang",
|
||||||
|
"${version_type}" => "modified",
|
||||||
|
_ => minecraft_argument
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//cmd.args(["--username", username, "--version", &instance_name, "--gameDir", game_dir.to_str().unwrap(), "--assetsDir", assets_dir.to_str().unwrap(), "--assetIndex", &config.assetIndex.id, "--uuid", &uuid, "--accessToken", &token, "--userProperties", "{}", "--userType", "mojang", "--width", "925", "--height", "530"]);
|
||||||
assets_dir.push("skins");
|
assets_dir.push("skins");
|
||||||
let _ = std::fs::remove_dir_all(assets_dir);
|
let _ = std::fs::remove_dir_all(assets_dir);
|
||||||
if let Some(server) = special_server {
|
if let Some(server) = special_server {
|
||||||
@ -318,6 +330,7 @@ impl Launcher {
|
|||||||
cmd.arg("--port");
|
cmd.arg("--port");
|
||||||
cmd.arg(server.port.to_string());
|
cmd.arg(server.port.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child = cmd.spawn().unwrap();
|
let mut child = cmd.spawn().unwrap();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@ -349,37 +362,272 @@ impl Launcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn import_multimc(&self, instance_path: PathBuf, sender: UnboundedSender<(u8, String)>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
let (sx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
|
||||||
|
let instance_name = instance_path.file_name().unwrap().to_str().unwrap();
|
||||||
|
let instance_name = [&instance_name[..instance_name.len()-4], "_", &util::random_string(4)].concat();
|
||||||
|
let mut instance_dir = self.config.instances_path();
|
||||||
|
instance_dir.push(&instance_name);
|
||||||
|
let _ = std::fs::create_dir_all(&instance_dir);
|
||||||
|
let data = std::fs::read(instance_path)?;
|
||||||
|
zip_extract::extract(Cursor::new(data), &instance_dir, true)?;
|
||||||
|
let mut multimc_data = instance_dir.clone();
|
||||||
|
multimc_data.push(".minecraft");
|
||||||
|
let mut data_dir = instance_dir.clone();
|
||||||
|
data_dir.push("data");
|
||||||
|
std::fs::rename(multimc_data, data_dir)?;
|
||||||
|
|
||||||
|
let mut pack_path = instance_dir.clone();
|
||||||
|
pack_path.push("mmc-pack.json");
|
||||||
|
println!("pack_path {}", pack_path.to_str().unwrap());
|
||||||
|
let multimc_data = std::fs::read(pack_path)?;
|
||||||
|
let pack_mmc: Pack = serde_json::from_slice(&multimc_data)?;
|
||||||
|
|
||||||
|
let mut minecraft_config = None;
|
||||||
|
let mut forge_version = None;
|
||||||
|
|
||||||
|
for component in pack_mmc.components.iter().filter(|c| c.cachedName.is_some()) {
|
||||||
|
match component.cachedName.as_ref().unwrap().as_str() {
|
||||||
|
"Minecraft" => minecraft_config = Some(crate::minecraft::versions::find_version_object(&component.version).await?),
|
||||||
|
"Forge" => {
|
||||||
|
forge_version = Some(component.version.clone());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut overall_size = 0;
|
||||||
|
let mut cnt = 0;
|
||||||
|
|
||||||
|
let mut client_json_path = self.config.instances_path();
|
||||||
|
client_json_path.push(&instance_name);
|
||||||
|
client_json_path.push("client.json");
|
||||||
|
|
||||||
|
if minecraft_config.is_some() {
|
||||||
|
let config = minecraft_config.clone().unwrap();
|
||||||
|
let config_cl = config.clone();
|
||||||
|
|
||||||
|
overall_size = config.downloads.as_ref().unwrap().client.size as usize;
|
||||||
|
cnt = 0;
|
||||||
|
|
||||||
|
let client_jar_url = config.downloads.as_ref().unwrap().client.url.clone();
|
||||||
|
|
||||||
|
let mut client_jar_path = self.config.instances_path();
|
||||||
|
client_jar_path.push(&instance_name);
|
||||||
|
client_jar_path.push("client.jar");
|
||||||
|
|
||||||
|
std::fs::write(&client_json_path, serde_json::to_string_pretty(&config_cl).unwrap())?;
|
||||||
|
|
||||||
|
let _ = util::download_file(&client_jar_url, client_jar_path.to_str().unwrap(), sx.clone(), "Downloading client.jar", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
|
||||||
|
let libraries = self.config.libraries_path();
|
||||||
|
|
||||||
|
for i in 0..config.libraries.len() {
|
||||||
|
let library = &config.libraries[i];
|
||||||
|
if let Some(artifact) = &library.downloads.artifact {
|
||||||
|
let mut dl_path = libraries.clone();
|
||||||
|
let mut dl_pp = libraries.clone();
|
||||||
|
dl_pp.push(library.to_pathbuf_path());
|
||||||
|
let _ = std::fs::create_dir_all(dl_pp);
|
||||||
|
dl_path.push(library.to_pathbuf_file(false));
|
||||||
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
|
overall_size += artifact.size as usize;
|
||||||
|
let _ = util::download_file(&artifact.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading libraries", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(classifiers) = &library.downloads.classifiers {
|
||||||
|
if let Some(natives) = &classifiers.natives {
|
||||||
|
let mut dl_path = libraries.clone();
|
||||||
|
dl_path.push(&natives.path);
|
||||||
|
let t_p = dl_path.to_str().unwrap().split("/").collect::<Vec<&str>>();
|
||||||
|
let t_p = t_p[..t_p.len()-1].join("/");
|
||||||
|
let _ = std::fs::create_dir_all(&t_p);
|
||||||
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
|
overall_size += natives.size as usize;
|
||||||
|
let _ = util::download_file(&natives.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading natives", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let assets_path = self.config.assets_path();
|
||||||
|
|
||||||
|
let mut indexes = assets_path.clone();
|
||||||
|
indexes.push("indexes");
|
||||||
|
let _ = std::fs::create_dir_all(indexes);
|
||||||
|
|
||||||
|
let mut objects = assets_path.clone();
|
||||||
|
objects.push("objects");
|
||||||
|
let _ = std::fs::create_dir_all(objects);
|
||||||
|
|
||||||
|
let mut index = assets_path.clone();
|
||||||
|
index.push(config.assetIndex.as_ref().unwrap().to_path());
|
||||||
|
|
||||||
|
let _ = util::download_file(&config.assetIndex.as_ref().unwrap().url, index.to_str().unwrap(), sx.clone(), "Downloading assets indexes", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
|
||||||
|
let asset_index = config.assetIndex.as_ref().unwrap().url.clone();
|
||||||
|
|
||||||
|
overall_size += config.assetIndex.as_ref().unwrap().size as usize;
|
||||||
|
overall_size += config.assetIndex.as_ref().unwrap().totalSize as usize;
|
||||||
|
|
||||||
|
let assets = crate::minecraft::assets::fetch_assets_list(&asset_index).await.unwrap().objects;
|
||||||
|
|
||||||
|
for (_key, asset) in assets {
|
||||||
|
let mut single_object = assets_path.clone();
|
||||||
|
single_object.push(asset.to_path());
|
||||||
|
|
||||||
|
let mut single_object_path = assets_path.clone();
|
||||||
|
single_object_path.push(asset.to_small_path());
|
||||||
|
let _ = std::fs::create_dir_all(single_object_path);
|
||||||
|
|
||||||
|
if File::open(single_object.to_str().unwrap()).await.is_err() {
|
||||||
|
let _ = util::download_file(&asset.to_url(), single_object.to_str().unwrap(), sx.clone(), "Downloading assets objects", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if minecraft_config.is_some() && forge_version.is_some() {
|
||||||
|
let mut forge_installer_path = self.config.libraries_path();
|
||||||
|
forge_installer_path.push("forge_installer.jar");
|
||||||
|
|
||||||
|
let mut forge_installer_unpack = self.config.libraries_path();
|
||||||
|
forge_installer_unpack.push("installer_unpacked");
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&forge_installer_unpack)?;
|
||||||
|
|
||||||
|
let forge_installer_url = format!("https://maven.minecraftforge.net/net/minecraftforge/forge/{}-{}/forge-{}-{}-installer.jar", minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap(), minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap());
|
||||||
|
|
||||||
|
let _ = util::download_file(&forge_installer_url, forge_installer_path.to_str().unwrap(), sx.clone(), "Downloading forge installer", true).await;
|
||||||
|
cnt += 1;
|
||||||
|
|
||||||
|
let forge_installer_data = std::fs::read(&forge_installer_path)?;
|
||||||
|
|
||||||
|
zip_extract::extract(Cursor::new(forge_installer_data), &forge_installer_unpack, true)?;
|
||||||
|
|
||||||
|
let mut forge_library_path = self.config.libraries_path();
|
||||||
|
forge_library_path.push("net");
|
||||||
|
forge_library_path.push("minecraftforge");
|
||||||
|
forge_library_path.push("forge");
|
||||||
|
forge_library_path.push(format!("{}-{}", minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap()));
|
||||||
|
|
||||||
|
let mut forge_version_json = forge_installer_unpack.clone();
|
||||||
|
forge_version_json.push("version.json");
|
||||||
|
|
||||||
|
let mut forge_installer_library = forge_installer_unpack.clone();
|
||||||
|
forge_installer_library.push("maven");
|
||||||
|
forge_installer_library.push("net");
|
||||||
|
forge_installer_library.push("minecraftforge");
|
||||||
|
forge_installer_library.push("forge");
|
||||||
|
forge_installer_library.push(format!("{}-{}", minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap()));
|
||||||
|
forge_installer_library.push(format!("forge-{}-{}.jar", minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap()));
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&forge_library_path)?;
|
||||||
|
|
||||||
|
forge_library_path.push(format!("forge-{}-{}.jar", minecraft_config.as_ref().unwrap().id, forge_version.as_ref().unwrap()));
|
||||||
|
|
||||||
|
std::fs::copy(&forge_installer_library, &forge_library_path)?;
|
||||||
|
|
||||||
|
let version_json = std::fs::read(&forge_version_json)?;
|
||||||
|
let version_json: VersionConfig = serde_json::from_slice(&version_json)?;
|
||||||
|
|
||||||
|
let mut edited = minecraft_config.clone().unwrap();
|
||||||
|
edited.mainClass = version_json.mainClass.clone();
|
||||||
|
edited.minecraftArguments = version_json.minecraftArguments;
|
||||||
|
edited.libraries.retain(|l| !version_json.libraries.iter().any(|t| t.name == l.name));
|
||||||
|
for i in 0..version_json.libraries.len() {
|
||||||
|
edited.libraries.push(version_json.libraries[i].clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(&client_json_path, serde_json::to_string_pretty(&edited).unwrap())?;
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(forge_installer_unpack)?;
|
||||||
|
std::fs::remove_file(forge_installer_path)?;
|
||||||
|
|
||||||
|
let libraries = self.config.libraries_path();
|
||||||
|
|
||||||
|
for i in 0..edited.libraries.len() {
|
||||||
|
let library = &edited.libraries[i];
|
||||||
|
if let Some(artifact) = &library.downloads.artifact {
|
||||||
|
let mut dl_path = libraries.clone();
|
||||||
|
let mut dl_pp = libraries.clone();
|
||||||
|
dl_pp.push(library.to_pathbuf_path());
|
||||||
|
let _ = std::fs::create_dir_all(dl_pp);
|
||||||
|
dl_path.push(library.to_pathbuf_file(false));
|
||||||
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
|
overall_size += artifact.size as usize;
|
||||||
|
let _ = util::download_file(&artifact.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading libraries", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(classifiers) = &library.downloads.classifiers {
|
||||||
|
if let Some(natives) = &classifiers.natives {
|
||||||
|
let mut dl_path = libraries.clone();
|
||||||
|
dl_path.push(&natives.path);
|
||||||
|
let t_p = dl_path.to_str().unwrap().split("/").collect::<Vec<&str>>();
|
||||||
|
let t_p = t_p[..t_p.len()-1].join("/");
|
||||||
|
let _ = std::fs::create_dir_all(&t_p);
|
||||||
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
|
overall_size += natives.size as usize;
|
||||||
|
let _ = util::download_file(&natives.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading natives", false).await;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut current_size = 0;
|
||||||
|
let mut current_cnt = 0;
|
||||||
|
while let Some((size, status)) = rx.recv().await {
|
||||||
|
current_size += size;
|
||||||
|
current_cnt += 1;
|
||||||
|
let _ = sender.send((((current_size as f32 / overall_size as f32) * 100.0) as u8, status));
|
||||||
|
if current_cnt >= cnt {
|
||||||
|
let _ = sender.send((100, "_".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new_vanilla_instance(&mut self, config: VersionConfig, version_object: &Version, sender: UnboundedSender<(u8, String)>) {
|
pub async fn new_vanilla_instance(&mut self, config: VersionConfig, version_object: &Version, sender: UnboundedSender<(u8, String)>) {
|
||||||
|
|
||||||
let (sx, mut rx) = mpsc::unbounded_channel();
|
let (sx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
let root = self.config.launcher_dir();
|
let mut instances = self.config.instances_path();
|
||||||
let mut instances = root.clone();
|
|
||||||
instances.push("instances");
|
|
||||||
instances.push(&config.id);
|
instances.push(&config.id);
|
||||||
|
|
||||||
let _ = std::fs::create_dir_all(&instances);
|
let _ = std::fs::create_dir_all(&instances);
|
||||||
|
|
||||||
instances.push("client.jar");
|
instances.push("client.jar");
|
||||||
|
|
||||||
let mut overall_size = config.downloads.client.size as usize;
|
let mut overall_size = config.downloads.as_ref().unwrap().client.size as usize;
|
||||||
let mut cnt = 0;
|
let mut cnt = 0;
|
||||||
|
|
||||||
let client_jar_url = config.downloads.client.url;
|
let client_jar_url = config.downloads.as_ref().unwrap().client.url.clone();
|
||||||
|
|
||||||
let mut client_json_path = root.clone();
|
let mut client_json_path = self.config.instances_path();
|
||||||
client_json_path.push("instances");
|
|
||||||
client_json_path.push(config.id);
|
client_json_path.push(config.id);
|
||||||
client_json_path.push("client.json");
|
client_json_path.push("client.json");
|
||||||
|
|
||||||
let _ = util::download_file(&version_object.url, client_json_path.to_str().unwrap(), sx.clone(), "Downloading client.json");
|
let _ = util::download_file(&version_object.url, client_json_path.to_str().unwrap(), sx.clone(), "Downloading client.json", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
|
|
||||||
let _ = util::download_file(&client_jar_url, instances.to_str().unwrap(), sx.clone(), "Downloading client.jar");
|
let _ = util::download_file(&client_jar_url, instances.to_str().unwrap(), sx.clone(), "Downloading client.jar", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
|
|
||||||
let mut libraries = root.clone();
|
let libraries = self.config.libraries_path();
|
||||||
libraries.push("libraries");
|
|
||||||
|
|
||||||
for i in 0..config.libraries.len() {
|
for i in 0..config.libraries.len() {
|
||||||
let library = &config.libraries[i];
|
let library = &config.libraries[i];
|
||||||
@ -391,7 +639,7 @@ impl Launcher {
|
|||||||
dl_path.push(library.to_pathbuf_file(false));
|
dl_path.push(library.to_pathbuf_file(false));
|
||||||
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
overall_size += artifact.size as usize;
|
overall_size += artifact.size as usize;
|
||||||
let _ = util::download_file(&artifact.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading libraries");
|
let _ = util::download_file(&artifact.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading libraries", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,15 +653,14 @@ impl Launcher {
|
|||||||
let _ = std::fs::create_dir_all(&t_p);
|
let _ = std::fs::create_dir_all(&t_p);
|
||||||
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
if File::open(dl_path.to_str().unwrap()).await.is_err() {
|
||||||
overall_size += natives.size as usize;
|
overall_size += natives.size as usize;
|
||||||
let _ = util::download_file(&natives.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading natives");
|
let _ = util::download_file(&natives.url, dl_path.to_str().unwrap(), sx.clone(), "Downloading natives", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut assets_path = root.clone();
|
let assets_path = self.config.assets_path();
|
||||||
assets_path.push("assets");
|
|
||||||
|
|
||||||
let mut indexes = assets_path.clone();
|
let mut indexes = assets_path.clone();
|
||||||
indexes.push("indexes");
|
indexes.push("indexes");
|
||||||
@ -424,15 +671,15 @@ impl Launcher {
|
|||||||
let _ = std::fs::create_dir_all(objects);
|
let _ = std::fs::create_dir_all(objects);
|
||||||
|
|
||||||
let mut index = assets_path.clone();
|
let mut index = assets_path.clone();
|
||||||
index.push(config.assetIndex.to_path());
|
index.push(config.assetIndex.as_ref().unwrap().to_path());
|
||||||
|
|
||||||
let _ = util::download_file(&config.assetIndex.url, index.to_str().unwrap(), sx.clone(), "Downloading assets indexes");
|
let _ = util::download_file(&config.assetIndex.as_ref().unwrap().url.clone(), index.to_str().unwrap(), sx.clone(), "Downloading assets indexes", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
|
|
||||||
let asset_index = config.assetIndex.url;
|
let asset_index = config.assetIndex.as_ref().unwrap().url.clone();
|
||||||
|
|
||||||
overall_size += config.assetIndex.size as usize;
|
overall_size += config.assetIndex.as_ref().unwrap().size as usize;
|
||||||
overall_size += config.assetIndex.totalSize as usize;
|
overall_size += config.assetIndex.as_ref().unwrap().totalSize as usize;
|
||||||
|
|
||||||
let assets = crate::minecraft::assets::fetch_assets_list(&asset_index).await.unwrap().objects;
|
let assets = crate::minecraft::assets::fetch_assets_list(&asset_index).await.unwrap().objects;
|
||||||
|
|
||||||
@ -445,7 +692,7 @@ impl Launcher {
|
|||||||
let _ = std::fs::create_dir_all(single_object_path);
|
let _ = std::fs::create_dir_all(single_object_path);
|
||||||
|
|
||||||
if File::open(single_object.to_str().unwrap()).await.is_err() {
|
if File::open(single_object.to_str().unwrap()).await.is_err() {
|
||||||
let _ = util::download_file(&asset.to_url(), single_object.to_str().unwrap(), sx.clone(), "Downloading assets objects");
|
let _ = util::download_file(&asset.to_url(), single_object.to_str().unwrap(), sx.clone(), "Downloading assets objects", false).await;
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,19 +713,14 @@ impl Launcher {
|
|||||||
|
|
||||||
pub fn init_dirs(&self) {
|
pub fn init_dirs(&self) {
|
||||||
let root = self.config.launcher_dir();
|
let root = self.config.launcher_dir();
|
||||||
std::fs::create_dir_all(&root);
|
let _ = std::fs::create_dir_all(&root);
|
||||||
// instances assets libraries config.toml
|
// instances assets libraries config.toml
|
||||||
let mut instances = root.clone();
|
let instances = self.config.instances_path();
|
||||||
instances.push("instances");
|
let assets = self.config.assets_path();
|
||||||
|
let libraries = self.config.libraries_path();
|
||||||
|
|
||||||
let mut assets = root.clone();
|
let _ = std::fs::create_dir_all(&instances);
|
||||||
assets.push("assets");
|
let _ = std::fs::create_dir_all(&assets);
|
||||||
|
let _ = std::fs::create_dir_all(&libraries);
|
||||||
let mut libraries = root.clone();
|
|
||||||
libraries.push("libraries");
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&instances);
|
|
||||||
std::fs::create_dir_all(&assets);
|
|
||||||
std::fs::create_dir_all(&libraries);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
12
src/main.rs
12
src/main.rs
@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use launcher::Launcher;
|
use launcher::Launcher;
|
||||||
@ -37,7 +39,7 @@ impl ApplicationHandler for App {
|
|||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
let window = event_loop.create_window(Window::default_attributes().with_inner_size(LogicalSize::new(900, 600)).with_min_inner_size(LogicalSize::new(900, 600)).with_title("XCraft")).unwrap();
|
let window = event_loop.create_window(Window::default_attributes().with_inner_size(LogicalSize::new(900, 600)).with_min_inner_size(LogicalSize::new(900, 600)).with_title("XCraft")).unwrap();
|
||||||
let webview = WebViewBuilder::new()
|
let webview = WebViewBuilder::new()
|
||||||
.with_asynchronous_custom_protocol("xcraft".into(), move |wid, request, responder| {
|
.with_asynchronous_custom_protocol("xcraft".into(), move |_wid, request, responder| {
|
||||||
let uri = request.uri().to_string();
|
let uri = request.uri().to_string();
|
||||||
if let Ok(msg) = serde_json::from_slice(request.body()) {
|
if let Ok(msg) = serde_json::from_slice(request.body()) {
|
||||||
let _ = SENDER.lock().unwrap().as_ref().unwrap().send((uri, Some(msg), responder));
|
let _ = SENDER.lock().unwrap().as_ref().unwrap().send((uri, Some(msg), responder));
|
||||||
@ -120,6 +122,14 @@ async fn main() {
|
|||||||
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: Vec::new() }).unwrap()));
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: Vec::new() }).unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"import_multimc" => {
|
||||||
|
if let Some(instance_path) = FileDialog::new().add_filter("Archive", &["zip"]).pick_file() {
|
||||||
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_loading".to_string(), "sidebar_off".to_string()] }).unwrap()));
|
||||||
|
if let Err(e) = launcher.import_multimc(instance_path, sx.clone()).await {
|
||||||
|
println!("Error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"download_vanilla" => {
|
"download_vanilla" => {
|
||||||
let version = params.unwrap().params[0].clone();
|
let version = params.unwrap().params[0].clone();
|
||||||
if let Ok(versions) = crate::minecraft::versions::fetch_versions_list().await {
|
if let Ok(versions) = crate::minecraft::versions::fetch_versions_list().await {
|
||||||
|
@ -27,17 +27,18 @@ pub mod versions {
|
|||||||
pub snapshot: String
|
pub snapshot: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct VersionConfig {
|
pub struct VersionConfig {
|
||||||
pub assetIndex: ConfigAssetIndex,
|
pub assetIndex: Option<ConfigAssetIndex>,
|
||||||
pub mainClass: String,
|
pub mainClass: String,
|
||||||
pub downloads: ConfigDownloads,
|
pub minecraftArguments: String,
|
||||||
|
pub downloads: Option<ConfigDownloads>,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
pub libraries: Vec<VersionLibrary>
|
pub libraries: Vec<VersionLibrary>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct VersionLibrary {
|
pub struct VersionLibrary {
|
||||||
pub downloads: LibraryDownloads,
|
pub downloads: LibraryDownloads,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -82,13 +83,13 @@ pub mod versions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct LibraryClassifiers {
|
pub struct LibraryClassifiers {
|
||||||
#[serde(rename = "natives-windows")]
|
#[serde(rename = "natives-windows")]
|
||||||
pub natives: Option<LibraryNatives>
|
pub natives: Option<LibraryNatives>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct LibraryNatives {
|
pub struct LibraryNatives {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub sha1: String,
|
pub sha1: String,
|
||||||
@ -96,13 +97,13 @@ pub mod versions {
|
|||||||
pub url: String
|
pub url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct LibraryDownloads {
|
pub struct LibraryDownloads {
|
||||||
pub artifact: Option<LibraryArtifact>,
|
pub artifact: Option<LibraryArtifact>,
|
||||||
pub classifiers: Option<LibraryClassifiers>
|
pub classifiers: Option<LibraryClassifiers>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct LibraryArtifact {
|
pub struct LibraryArtifact {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub sha1: String,
|
pub sha1: String,
|
||||||
@ -110,19 +111,19 @@ pub mod versions {
|
|||||||
pub url: String
|
pub url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct ConfigDownloads {
|
pub struct ConfigDownloads {
|
||||||
pub client: ConfigDownloadsClient
|
pub client: ConfigDownloadsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct ConfigDownloadsClient {
|
pub struct ConfigDownloadsClient {
|
||||||
pub sha1: String,
|
pub sha1: String,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub url: String
|
pub url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct ConfigAssetIndex {
|
pub struct ConfigAssetIndex {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub sha1: String,
|
pub sha1: String,
|
||||||
@ -154,6 +155,14 @@ pub mod versions {
|
|||||||
let resp: VersionConfig = serde_json::from_slice(&resp)?;
|
let resp: VersionConfig = serde_json::from_slice(&resp)?;
|
||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_version_object(version: &str) -> Result<VersionConfig, Box<dyn Error + Send + Sync>> {
|
||||||
|
let versions = fetch_versions_list().await?;
|
||||||
|
let versions = versions.versions;
|
||||||
|
let version = versions.iter().find(|v| v.id == version).unwrap();
|
||||||
|
let config = fetch_version_object(version).await?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod session {
|
pub mod session {
|
||||||
@ -199,6 +208,22 @@ pub mod session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod multimc {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Pack {
|
||||||
|
pub components: Vec<Component>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Component {
|
||||||
|
pub cachedName: Option<String>,
|
||||||
|
pub version: String,
|
||||||
|
pub uid: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod server {
|
pub mod server {
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
@ -11,11 +11,11 @@ pub fn random_string(len: usize) -> String {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn download_file(url: &str, file_path: &str, sender: UnboundedSender<(usize, String)>, status: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn download_file(url: &str, file_path: &str, sender: UnboundedSender<(usize, String)>, status: &str, join: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let url = url.to_string();
|
let url = url.to_string();
|
||||||
let file_path = file_path.to_string();
|
let file_path = file_path.to_string();
|
||||||
let status = status.to_string();
|
let status = status.to_string();
|
||||||
tokio::spawn( async move {
|
let g = tokio::spawn( async move {
|
||||||
if let Ok(mut res) = surf::get(url).await {
|
if let Ok(mut res) = surf::get(url).await {
|
||||||
let mut downloaded = 0;
|
let mut downloaded = 0;
|
||||||
let mut buf = vec![0; 8192]; // Buffer for reading chunks
|
let mut buf = vec![0; 8192]; // Buffer for reading chunks
|
||||||
@ -36,6 +36,9 @@ pub fn download_file(url: &str, file_path: &str, sender: UnboundedSender<(usize,
|
|||||||
let _ = sender.send((0, status.clone()));
|
let _ = sender.send((0, status.clone()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if join {
|
||||||
|
g.await;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,8 +189,8 @@
|
|||||||
</button>
|
</button>
|
||||||
<p class="text-sm text-gray-500 mt-1">No installation required. Saves everything in a local folder.</p>
|
<p class="text-sm text-gray-500 mt-1">No installation required. Saves everything in a local folder.</p>
|
||||||
|
|
||||||
<button onclick="installMinecraft()"
|
<button
|
||||||
class="mt-4 w-full bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded transition">
|
class="mt-4 w-full bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded transition cursor-not-allowed">
|
||||||
Install Minecraft
|
Install Minecraft
|
||||||
</button>
|
</button>
|
||||||
<p class="text-sm text-gray-500 mt-1">Installs Minecraft on your system for long-term use.</p>
|
<p class="text-sm text-gray-500 mt-1">Installs Minecraft on your system for long-term use.</p>
|
||||||
@ -230,7 +230,7 @@
|
|||||||
<div class="mt-4 text-gray-500 text-sm">or</div>
|
<div class="mt-4 text-gray-500 text-sm">or</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="mt-3 w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition">
|
onclick="importMultiMcInstance()" class="mt-3 w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition">
|
||||||
<i class="fa-solid fa-file-zipper"></i> Add MultiMC Instance
|
<i class="fa-solid fa-file-zipper"></i> Add MultiMC Instance
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -598,6 +598,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importMultiMcInstance() {
|
||||||
|
$.get("import_multimc", processParams);
|
||||||
|
}
|
||||||
|
|
||||||
function uploadSkin() {
|
function uploadSkin() {
|
||||||
$.post({ url: "upload_skin", data: JSON.stringify({ params: [accountNick, accountDomain] }) }, processParams);
|
$.post({ url: "upload_skin", data: JSON.stringify({ params: [accountNick, accountDomain] }) }, processParams);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user