365 lines
22 KiB
Rust
365 lines
22 KiB
Rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
use std::sync::Mutex;
|
|
|
|
use launcher::Launcher;
|
|
use rfd::FileDialog;
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::process::Command;
|
|
use tokio::runtime::Runtime;
|
|
use tokio::sync::mpsc::{self, UnboundedSender};
|
|
use winit::application::ApplicationHandler;
|
|
use winit::event_loop::EventLoop;
|
|
use winit::event::WindowEvent;
|
|
use winit::window::{Window, WindowId};
|
|
use winit::event_loop::ActiveEventLoop;
|
|
use wry::dpi::LogicalSize;
|
|
use wry::http::Response;
|
|
use wry::{RequestAsyncResponder, WebView, WebViewBuilder};
|
|
|
|
mod config;
|
|
mod launcher;
|
|
mod util;
|
|
mod minecraft;
|
|
|
|
static SENDER: Mutex<Option<UnboundedSender<(String, Option<UIMessage>, RequestAsyncResponder)>>> = Mutex::new(None);
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct UIMessage {
|
|
params: Vec<String>
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct App {
|
|
window: Option<Window>,
|
|
webview: Option<WebView>
|
|
}
|
|
|
|
impl ApplicationHandler for App {
|
|
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 webview = WebViewBuilder::new()
|
|
.with_asynchronous_custom_protocol("xcraft".into(), move |_wid, request, responder| {
|
|
let uri = request.uri().to_string();
|
|
if let Ok(msg) = serde_json::from_slice(request.body()) {
|
|
let _ = SENDER.lock().unwrap().as_ref().unwrap().send((uri, Some(msg), responder));
|
|
return;
|
|
}
|
|
let _ = SENDER.lock().unwrap().as_ref().unwrap().send((uri, None, responder));
|
|
})
|
|
.with_url("xcraft://custom/ui")
|
|
.build(&window)
|
|
.unwrap();
|
|
|
|
self.window = Some(window);
|
|
self.webview = Some(webview);
|
|
}
|
|
|
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
|
if event == WindowEvent::CloseRequested {
|
|
event_loop.exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let event_loop = EventLoop::new().unwrap();
|
|
let mut app = App::default();
|
|
|
|
let rt = Runtime::new().unwrap();
|
|
|
|
rt.spawn(async move {
|
|
let (snd, mut receiver) = mpsc::unbounded_channel();
|
|
*SENDER.lock().unwrap() = Some(snd);
|
|
|
|
let mut launcher = Launcher::default();
|
|
|
|
let (sx, mut dl_rec) = mpsc::unbounded_channel();
|
|
let (mut lx, mut logs_rec) = mpsc::unbounded_channel();
|
|
|
|
loop {
|
|
if let Some((ui_action, params, responder)) = receiver.recv().await {
|
|
let ui_action = &ui_action[16..];
|
|
match ui_action {
|
|
"github" => {
|
|
Command::new("cmd").args(["/C", "start", "https://github.com/alterdekim/XCraft"]).spawn().unwrap();
|
|
},
|
|
"jquery" => responder.respond(Response::new(include_bytes!("js/jquery.js"))),
|
|
"skinview3d" => responder.respond(Response::new(include_bytes!("js/skinview3d.js"))),
|
|
"tailwind" => responder.respond(Response::new(include_bytes!("js/tailwind.js"))),
|
|
"ui" => responder.respond(Response::new(include_bytes!("www/portable.html"))),
|
|
"portable" => {
|
|
launcher.config.set_portable(true);
|
|
launcher.init_dirs();
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_login".to_string()] }).unwrap()))
|
|
}
|
|
"installation" => {
|
|
launcher.init_dirs();
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_login".to_string()] }).unwrap()))
|
|
}
|
|
"check_installation" => {
|
|
if launcher.is_portable() {
|
|
launcher.config.set_portable(true);
|
|
launcher.init_dirs();
|
|
if !launcher.is_config_exist() {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_login".to_string()] }).unwrap()))
|
|
} else {
|
|
launcher.load_config();
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_add".to_string(), "sidebar_on".to_string()] }).unwrap()))
|
|
}
|
|
}
|
|
}
|
|
"sign_up" => {
|
|
let user_name = params.as_ref().unwrap().params.first().unwrap();
|
|
launcher.init_config(user_name.to_string());
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_add".to_string(), "sidebar_on".to_string()] }).unwrap()));
|
|
}
|
|
"fetch_official_versions" => {
|
|
if let Ok(versions) = crate::minecraft::versions::fetch_versions_list().await {
|
|
let versions: Vec<String> = versions.versions.iter().filter(|t| {
|
|
if (!launcher.config.show_alpha && t.r#type == "old_alpha") || (!launcher.config.show_beta && t.r#type == "old_beta") || (!launcher.config.show_snapshots && t.r#type == "snapshot") {
|
|
return false;
|
|
}
|
|
true
|
|
}).map(|t| t.id.clone()).collect();
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: [ vec!["set_downloadable_versions".to_string()], versions ].concat() }).unwrap()));
|
|
} else {
|
|
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" => {
|
|
let version = params.unwrap().params[0].clone();
|
|
if let Ok(versions) = crate::minecraft::versions::fetch_versions_list().await {
|
|
let version = versions.versions.iter().find(|t| t.id.clone() == version);
|
|
if let Some(version) = version {
|
|
match crate::minecraft::versions::fetch_version_object(version).await {
|
|
Ok(config ) => {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["show_loading".to_string(), "sidebar_off".to_string()] }).unwrap()));
|
|
launcher.new_vanilla_instance(config, version, sx.clone()).await;
|
|
}
|
|
Err(e) => {
|
|
println!("Error: {}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"fetch_instances_list" => {
|
|
let resp = launcher.get_instances_list();
|
|
let mut v: Vec<String> = Vec::new();
|
|
v.push("set_instances_list".to_string());
|
|
for (id, release_type, img) in resp {
|
|
v.push(id);
|
|
v.push(release_type);
|
|
v.push(img);
|
|
}
|
|
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: v }).unwrap()));
|
|
}
|
|
"get_skin" => {
|
|
let params = params.unwrap().params;
|
|
let nickname = params[0].clone();
|
|
let domain = params[1].clone();
|
|
if let Some(server) = launcher.find_credentials(&nickname, &domain) {
|
|
let resp = util::get_image(&["http", if launcher.config.allow_http { "" } else { "s" }, "://", &domain, ":", &server.session_server_port.to_string(), "/api/skin/s", &server.credentials.uuid].concat()).await;
|
|
if let Ok(resp) = resp {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["get_skin".to_string(), resp] }).unwrap()));
|
|
}
|
|
}
|
|
}
|
|
"get_cape" => {
|
|
let params = params.unwrap().params;
|
|
let nickname = params[0].clone();
|
|
let domain = params[1].clone();
|
|
if let Some(server) = launcher.find_credentials(&nickname, &domain) {
|
|
let resp = util::get_image(&["http", if launcher.config.allow_http { "" } else { "s" }, "://", &domain, ":", &server.session_server_port.to_string(), "/api/cape/a", &server.credentials.uuid].concat()).await;
|
|
if let Ok(resp) = resp {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["get_cape".to_string(), resp] }).unwrap()));
|
|
}
|
|
}
|
|
}
|
|
"upload_skin" => {
|
|
let params = params.unwrap().params;
|
|
if let Some(server) = launcher.find_credentials(¶ms[0], ¶ms[1]) {
|
|
if let Some(skin_path) = FileDialog::new().add_filter("Images", &["png"]).pick_file() {
|
|
|
|
let msg = launcher.upload_skin(skin_path, &server.credentials.uuid, &server.credentials.password, &[if launcher.config.allow_http {"http"} else {"https"}, "://", &server.domain, ":", &server.session_server_port.to_string(), "/api/upload"].concat()).await;
|
|
match msg {
|
|
Ok(msg ) => {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), msg] }).unwrap()));
|
|
},
|
|
Err(_e) => {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), "Error uploading new skin".to_string()] }).unwrap()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"upload_cape" => {
|
|
let params = params.unwrap().params;
|
|
if let Some(server) = launcher.find_credentials(¶ms[0], ¶ms[1]) {
|
|
if let Some(skin_path) = FileDialog::new().add_filter("Images", &["png"]).pick_file() {
|
|
|
|
let msg = launcher.upload_cape(skin_path, &server.credentials.uuid, &server.credentials.password, &[if launcher.config.allow_http {"http"} else {"https"}, "://", &server.domain, ":", &server.session_server_port.to_string(), "/api/upload_cape"].concat()).await;
|
|
if let Ok(msg) = msg {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), msg] }).unwrap()));
|
|
} else {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), "Error uploading new cape".to_string()] }).unwrap()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"set_skin_model" => {
|
|
let params = params.unwrap().params;
|
|
if let Some(server) = launcher.find_credentials(¶ms[0], ¶ms[1]) {
|
|
let msg = launcher.set_skin_model(params[2].parse().unwrap(), &server.credentials.uuid, &server.credentials.password, &[if launcher.config.allow_http {"http"} else {"https"}, "://", &server.domain, ":", &server.session_server_port.to_string(), "/api/set_model"].concat()).await;
|
|
if let Ok(msg) = msg {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), msg] }).unwrap()));
|
|
} else {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), String::new(), "Error setting skin model".to_string()] }).unwrap()));
|
|
}
|
|
}
|
|
}
|
|
"fetch_credentials_list" => {
|
|
let resp = launcher.get_servers_list().await;
|
|
let mut v: Vec<String> = Vec::new();
|
|
v.push("fetch_credentials_list".to_string());
|
|
for (domain, nickname, _image) in resp {
|
|
v.push(domain);
|
|
v.push(nickname);
|
|
}
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: v }).unwrap()));
|
|
}
|
|
"fetch_servers_list" => {
|
|
let resp = launcher.get_servers_list().await;
|
|
let mut v: Vec<String> = Vec::new();
|
|
v.push("fetch_servers_list".to_string());
|
|
for (domain, nickname, image) in resp {
|
|
v.push(domain);
|
|
v.push(nickname);
|
|
v.push(image.unwrap_or(String::new()));
|
|
}
|
|
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: v }).unwrap()));
|
|
}
|
|
"check_download_status" => {
|
|
if let Ok((percent, text)) = dl_rec.try_recv() {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["update_downloads".to_string(), text, percent.to_string()] }).unwrap()));
|
|
} else {
|
|
responder.respond(Response::new(vec![]));
|
|
}
|
|
}
|
|
"check_logs_status" => {
|
|
if let Ok(text) = logs_rec.try_recv() {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["update_logs".to_string(), text] }).unwrap()));
|
|
} else {
|
|
responder.respond(Response::new(vec![]));
|
|
}
|
|
}
|
|
"run_instance" => {
|
|
let instance_name = params.unwrap().params[0].clone();
|
|
logs_rec.close();
|
|
(lx, logs_rec) = mpsc::unbounded_channel();
|
|
launcher.launch_instance(instance_name, lx.clone(), None).await;
|
|
}
|
|
"run_server_instance" => {
|
|
let params = params.unwrap().params;
|
|
let instance_name = params[0].clone();
|
|
let domain = params[1].clone();
|
|
let nickname = params[2].clone();
|
|
logs_rec.close();
|
|
(lx, logs_rec) = mpsc::unbounded_channel();
|
|
let s = launcher.config.servers().iter().find(|s| s.domain == domain && s.credentials.username == nickname);
|
|
launcher.launch_instance(instance_name, lx.clone(), s).await;
|
|
}
|
|
"locate_java" => {
|
|
if let Ok(java_path) = java_locator::locate_file("java.exe") {
|
|
launcher.config.java_path = java_path.clone();
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["locate_java".to_string(), [&java_path, "java.exe"].join("\\")] }).unwrap()));
|
|
} else {
|
|
// todo: implement error notifications
|
|
}
|
|
}
|
|
"add_server_login" => {
|
|
let params = ¶ms.unwrap().params;
|
|
let (status, msg) = launcher.login_user_server(params[0].clone(), params[1].clone(), params[2].clone()).await;
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), status.to_string(), msg.to_string()] }).unwrap()));
|
|
}
|
|
"add_server" => {
|
|
let params = ¶ms.unwrap().params;
|
|
let (status, msg) = launcher.register_user_server(params[0].clone(), params[1].clone(), params[2].clone()).await;
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["add_server_response".to_string(), status.to_string(), msg.to_string()] }).unwrap()));
|
|
}
|
|
"fetch_settings" => {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["fetch_settings_response".to_string(), launcher.config.show_alpha.to_string(), launcher.config.show_beta.to_string(), launcher.config.show_snapshots.to_string(), launcher.config.java_path.clone(), launcher.config.ram_amount.to_string(), launcher.config.enable_blur.to_string(), launcher.config.allow_http.to_string()] }).unwrap()));
|
|
}
|
|
"save_bg" => {
|
|
let params = ¶ms.unwrap().params;
|
|
let mut p = launcher.config.launcher_dir();
|
|
p.push("bg.base64");
|
|
let _ = std::fs::write(p, ¶ms[0]);
|
|
}
|
|
"fetch_bg" => {
|
|
let mut p = launcher.config.launcher_dir();
|
|
p.push("bg.base64");
|
|
if let Ok(data) = std::fs::read(p) {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["fetch_bg".to_string(), String::from_utf8(data).unwrap()] }).unwrap()));
|
|
} else if let Ok(Some(data)) = launcher::get_random_bg().await {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["fetch_bg".to_string(), data] }).unwrap()));
|
|
}
|
|
}
|
|
"update_settings" => {
|
|
let params = ¶ms.unwrap().params;
|
|
launcher.config.java_path = params[3].clone();
|
|
launcher.config.ram_amount = params[4].parse().unwrap();
|
|
launcher.config.show_alpha = params[0].parse().unwrap();
|
|
launcher.config.show_beta = params[1].parse().unwrap();
|
|
launcher.config.show_snapshots = params[2].parse().unwrap();
|
|
launcher.config.enable_blur = params[5].parse().unwrap();
|
|
launcher.config.allow_http = params[6].parse().unwrap();
|
|
launcher.save_config();
|
|
}
|
|
"open_file" => {
|
|
let params = ¶ms.unwrap().params;
|
|
let _ = Command::new("cmd").args(["/C", "start", ¶ms[0]]).spawn();
|
|
}
|
|
"load_screenshots" => {
|
|
let screenshots = launcher.get_screenshots();
|
|
let mut svec = Vec::new();
|
|
for (path,data) in screenshots {
|
|
svec.push(path);
|
|
svec.push(data);
|
|
}
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: [vec!["load_screenshots".to_string()], svec].concat() }).unwrap()));
|
|
}
|
|
"check_updates" => {
|
|
//
|
|
if let Ok(Some(file_url)) = launcher::check_updates().await {
|
|
responder.respond(Response::new(serde_json::to_vec(&UIMessage { params: vec!["sidebar_off".to_string(), "show_loading".to_string(), "update_downloads".to_string(), "Updating launcher...".to_string(), "100".to_string()] }).unwrap()));
|
|
let current_exe = std::env::current_exe().unwrap();
|
|
let mut downloaded_file = std::env::current_dir().unwrap();
|
|
downloaded_file.push("launcher_new.exe");
|
|
util::simple_download(&file_url, downloaded_file.to_str().unwrap()).await.unwrap();
|
|
let _ = std::fs::rename(¤t_exe, "old_launcher.bak");
|
|
let _ = std::fs::rename(downloaded_file, current_exe);
|
|
let _ = std::fs::remove_file("old_launcher.bak");
|
|
std::process::exit(0);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
event_loop.run_app(&mut app).unwrap();
|
|
} |