Compare commits

...

7 Commits

26 changed files with 743 additions and 501 deletions

View File

@ -8,10 +8,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1", features = [] }
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "1", features = [ "process-all", "dialog-open", "updater", "dialog-ask", "shell-open"] }
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
suppaftp = { version = "6.0.1", features = ["native-tls"] }
@ -27,7 +27,13 @@ futures-util = "0.3.30"
ssh2 = "0.9.4"
chrono = "0.4.38"
zip = "2.1.3"
tauri-plugin-dialog = "2"
tauri-plugin-shell = "2"
tauri-plugin-process = "2"
[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"

View File

@ -1,4 +1,4 @@
export TAURI_PRIVATE_KEY=$(cat ~/.tauri/fclauncher.key)
read -s PASSWORD
export TAURI_KEY_PASSWORD=$PASSWORD
cargo tauri build
NO_STRIP=true cargo tauri build

View File

@ -0,0 +1,11 @@
{
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
],
"permissions": [
"updater:default"
]
}

View File

@ -0,0 +1,20 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": [
"main"
],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:allow-open",
"dialog:allow-message",
"dialog:allow-ask",
"process:allow-restart",
"process:allow-exit",
"dialog:default",
"shell:default",
"process:default"
]
}

View File

@ -1,122 +1,169 @@
use std::fs::File;
use std::io::Cursor;
use std::io::Read;
use std::fs::File;
use std::io::Seek;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use crate::modpack;
use crate::modpack::get_modpacks;
use crate::modpack::get_versions;
use crate::modpack::VersionEntry;
use crate::sftp;
use crate::modpack;
use crate::ModpackEntry;
use ssh2::Sftp;
use ssh2::Session;
use chrono;
use zip::unstable::write::FileOptionsExt;
use zip::write::FileOptions;
use serde::Serialize;
use ssh2::Session;
use zip::write::SimpleFileOptions;
use zip::ZipArchive;
use zip::ZipWriter;
use tauri::Emitter;
//static USERNAME: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
//static PASSWORD: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
static SESSION: tauri::async_runtime::Mutex<Option<Session>> = tauri::async_runtime::Mutex::const_new(None);
static SESSION: tauri::async_runtime::Mutex<Option<Session>> =
tauri::async_runtime::Mutex::const_new(None);
#[tauri::command]
pub async fn login(username: String, password: String, window: tauri::Window) {
let res = sftp::connect(username, password);
if(res.is_ok()){
//*USERNAME.lock() = username;
//*PASSWORD.lock() = password;
*SESSION.lock().await = Some(res.unwrap());
window.emit("Login_Success", {});
}else{
window.emit("Login_Failed", {});
pub fn emit(event: &str, payload: impl Serialize + Clone, window: tauri::AppHandle) {
if !window.emit(event, payload).is_ok() {
println!("Failed to emit to window!");
}
}
#[tauri::command]
pub async fn drop_session(){
pub async fn login(username: String, password: String, window: tauri::AppHandle) {
let res = sftp::connect(username, password);
if res.is_ok() {
//*USERNAME.lock() = username;
//*PASSWORD.lock() = password;
*SESSION.lock().await = Some(res.unwrap());
emit("Login_Success", {}, window);
} else {
emit("Login_Failed", {}, window);
}
}
#[tauri::command]
pub async fn drop_session() {
let ref mut session = *SESSION.lock().await;
if let Some(session) = session {
session.disconnect(None, "disconnecting", None);
session.disconnect(None, "disconnecting", None).unwrap();
}
*session = None;
}
async fn update_modpacks(modpacks: Vec<modpack::ModpackEntry>) -> Result<(), String>{
async fn update_modpacks(modpacks: Vec<modpack::ModpackEntry>) -> Result<(), String> {
let data = serde_json::to_string(&modpacks).or(Err("Unable to serialize json"))?;
let reader = Cursor::new(data.as_bytes());
let ref mut session = *SESSION.lock().await;
if let Some(session) = session {
sftp::uplaod(None, session.clone(), PathBuf::from("/ftp/modpacks.json"), reader, format!("modpacks.json"), data.as_bytes().len()).await;
sftp::uplaod(
None,
session.clone(),
PathBuf::from("/ftp/modpacks.json"),
reader,
format!("modpacks.json"),
data.as_bytes().len(),
)
.await?;
}
Ok(())
Err(format!("Session doesnt exist?"))
}
async fn update_versions(id: String, versions: Vec<modpack::VersionEntry>) -> Result<(), String>{
async fn update_versions(id: String, versions: Vec<modpack::VersionEntry>) -> Result<(), String> {
let data = serde_json::to_string(&versions).or(Err("Unable to serialize json"))?;
let reader = Cursor::new(data.as_bytes());
let ref mut session = *SESSION.lock().await;
if let Some(session) = session {
sftp::uplaod(None, session.clone(), PathBuf::from(format!("/ftp/{}/versions.json", id)), reader, format!("modpacks.json"), data.as_bytes().len()).await;
sftp::uplaod(
None,
session.clone(),
PathBuf::from(format!("/ftp/{}/versions.json", id)),
reader,
format!("modpacks.json"),
data.as_bytes().len(),
)
.await?;
}
Ok(())
Err(format!("Session doesnt exist?"))
}
#[tauri::command]
pub async fn shift_up(id: String){
pub async fn shift_up(id: String, window: tauri::AppHandle) {
let mut modpacks = modpack::get_modpacks().await;
let mut index = 0;
for pack in modpacks.as_slice() {
if(pack.id == id){
if pack.id == id {
break;
}
index += 1;
}
if index != 0 {
modpacks.swap(index, index-1);
modpacks.swap(index, index - 1);
}
let res = update_modpacks(modpacks).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window);
}
update_modpacks(modpacks).await;
}
#[tauri::command]
pub async fn shift_down(id: String){
pub async fn shift_down(id: String, window: tauri::AppHandle) {
let mut modpacks = modpack::get_modpacks().await;
let mut index = 0;
for pack in modpacks.as_slice() {
if(pack.id == id){
if pack.id == id {
break;
}
index += 1;
}
if index != modpacks.len()-1 {
modpacks.swap(index, index+1);
if index != modpacks.len() - 1 {
modpacks.swap(index, index + 1);
}
let res = update_modpacks(modpacks).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window);
}
update_modpacks(modpacks).await;
}
#[tauri::command]
pub async fn add_pack(id: String, name: String){
pub async fn add_pack(id: String, name: String, window: tauri::AppHandle) {
{
let ref mut session = *SESSION.lock().await;
if let Some(session) = session{
sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await;
sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}/Versions", id))).await;
if let Some(session) = session {
let res = sftp::mkdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window.clone());
}
let res = sftp::mkdir(
session.clone(),
PathBuf::from(format!("/ftp/{}/Versions", id)),
)
.await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window.clone());
}
}
}
let versions: Vec<VersionEntry> = Vec::new();
update_versions(id.clone(), versions).await;
let res = update_versions(id.clone(), versions).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window.clone());
}
let mut modpacks = get_modpacks().await;
modpacks.push(modpack::ModpackEntry{id: id, name: name, last_updated: format!("{:?}", chrono::offset::Utc::now())});
update_modpacks(modpacks).await;
modpacks.push(modpack::ModpackEntry {
id: id,
name: name,
last_updated: format!("{:?}", chrono::offset::Utc::now()),
});
let res = update_modpacks(modpacks).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window);
}
}
#[tauri::command]
pub async fn remove_pack(id: String){
pub async fn remove_pack(id: String, window: tauri::AppHandle) {
let mut modpacks = get_modpacks().await;
let mut index = 0;
for pack in modpacks.clone() {
@ -126,60 +173,123 @@ pub async fn remove_pack(id: String){
}
index += 1;
}
update_modpacks(modpacks).await;
let res = update_modpacks(modpacks).await;
if !res.is_ok() {
emit("Error", res.unwrap_err(), window);
}
{
let ref mut session = *SESSION.lock().await;
if let Some(session) = session{
if let Some(session) = session {
sftp::rmdir(session.clone(), PathBuf::from(format!("/ftp/{}", id))).await;
}
}
}
#[tauri::command]
pub async fn update_pack(window: tauri::Window, id: String, path: String, version: String) -> Result<(), String>{
println!("Update modpack {}, to version {}, from file {}", id, version, path);
pub async fn update_pack(
window: tauri::AppHandle,
id: String,
path: String,
version: String,
) -> Result<(), String> {
println!(
"Update modpack {}, to version {}, from file {}",
id, version, path
);
let file = File::open(Path::new(path.as_str())).or(Err(format!("Unable to open file")))?;
let mut archive = ZipArchive::new(file).or(Err(format!("File not a zip archive!")))?;
let mut buf = Cursor::new(vec![]);
let mut out_archive = ZipWriter::new(&mut buf);
for i in 0..archive.len() {
let mut file = archive.by_index(i).or(Err(format!("error reading archive")))?;
if(file.name() == "overrides/version.txt"){
let mut file = archive
.by_index(i)
.or(Err(format!("error reading archive")))?;
if file.name() == "overrides/version.txt" {
continue;
}
out_archive.start_file(file.name(), SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated));
std::io::copy(&mut file, &mut out_archive);
let res = out_archive.start_file(
file.name(),
SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated),
);
if !res.is_ok() {
emit("Error", format!("Unable to start zip archive"), window);
return Err(format!("Unable to start zip archive"));
}
let res = std::io::copy(&mut file, &mut out_archive);
if !res.is_ok() {
emit("Error", format!("Unable to copy archive to ram"), window);
return Err(format!("Unable to copy archive to ram"));
}
}
let res = out_archive.start_file(
"overrides/version.txt",
SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated),
);
if !res.is_ok() {
emit("Error", format!("Unable to create version file"), window);
return Err(format!("Unable to create version file"));
}
let res = out_archive.write_all(version.as_bytes());
if !res.is_ok() {
emit("Error", format!("Unable to write to zip"), window);
return Err(format!("Unable to write to zip"));
}
let res = out_archive.finish();
if !res.is_ok() {
emit("Error", format!("Unable to finish zip"), window);
return Err(format!("Unable to finish zip"));
}
out_archive.start_file("overrides/version.txt", SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated));
out_archive.write_all(version.as_bytes());
out_archive.finish();
buf.rewind().unwrap();
let timestamp = format!("{:?}", chrono::offset::Utc::now());
let path = format!("Versions/{}-{}.mrpack", id, timestamp);
{
let ref mut session = *SESSION.lock().await;
if let Some(session) = session{
if let Some(session) = session {
let size = buf.clone().bytes().count();
let upload_path = format!("/ftp/{}/{}", id, path.clone());
println!("Uploading to {}", upload_path);
sftp::uplaod(Some(window), session.clone(), PathBuf::from(upload_path), &mut buf, path.clone(), size).await;
let res = sftp::uplaod(
Some(window.clone()),
session.clone(),
PathBuf::from(upload_path),
&mut buf,
path.clone(),
size,
)
.await;
if !res.is_ok() {
emit("Error", res.clone().unwrap_err(), window.clone());
return res;
}
}
}
let mut versions = get_versions(id.clone()).await?;
versions.push(VersionEntry{Version: version, Date: timestamp.clone(), File: path});
update_versions(id.clone(), versions).await;
versions.push(VersionEntry {
Version: version,
Date: timestamp.clone(),
File: path,
});
let res = update_versions(id.clone(), versions).await;
if !res.is_ok() {
emit("Error", res.clone().unwrap_err(), window.clone());
return res;
}
let mut modpacks = get_modpacks().await;
let mut index = 0;
for mut pack in modpacks.as_slice(){
for pack in modpacks.as_slice() {
if pack.id == id {
modpacks[index].last_updated = timestamp;
break;
}
index += 1;
}
update_modpacks(modpacks).await;
let res = update_modpacks(modpacks).await;
if !res.is_ok() {
emit("Error", res.clone().unwrap_err(), window.clone());
return res;
}
Ok(())
}

View File

@ -1,39 +1,67 @@
use futures_util::StreamExt;
use serde::Serialize;
use std::cmp::min;
use std::io::Write;
use std::path::PathBuf;
use futures_util::StreamExt;
use std::cmp::min;
use serde::Serialize;
use tauri::Emitter;
#[derive(Clone, Serialize)]
pub struct DownloadStatus {
pub downloaded: usize,
pub total: usize,
pub time_elapsed: usize,
pub download_name: String
pub download_name: String,
}
pub async fn download(window: Option<tauri::Window>, url: String , mut writer: impl Write, downloadName: String) -> Result<(), String> {
pub async fn download(
window: Option<tauri::AppHandle>,
url: String,
mut writer: impl Write,
downloadName: String,
) -> Result<(), String> {
let client = reqwest::Client::new();
let res = client.get(url).send().await.or(Err(format!("Failed to fetch from URL!")))?;
let total_size = res.content_length().ok_or(format!("Failed to get content length"))?;
let res = client
.get(url)
.send()
.await
.or(Err(format!("Failed to fetch from URL!")))?;
let total_size = res
.content_length()
.ok_or(format!("Failed to get content length"))?;
let mut downloaded: u64 = 0;
let mut stream = res.bytes_stream();
while let Some(item) = stream.next().await {
let chunk = item.or(Err(format!("Error while downloading file!")))?;
writer.write_all(&chunk).or(Err("Error writing to stream!"))?;
writer
.write_all(&chunk)
.or(Err("Error writing to stream!"))?;
let new = min(downloaded + (chunk.len() as u64), total_size);
downloaded = new;
println!("Downloading {}: {}MB / {}MB", downloadName.clone(), downloaded/(1024*1024), total_size/(1024*1024));
println!(
"Downloading {}: {}MB / {}MB",
downloadName.clone(),
downloaded / (1024 * 1024),
total_size / (1024 * 1024)
);
if let Some(window) = window.clone() {
if downloaded != total_size {
window.emit("download_progress", DownloadStatus{downloaded: downloaded as usize, total: total_size as usize, time_elapsed: 0, download_name: downloadName.clone()}).or(Err(format!("Unable to signal window")))?;
window
.emit(
"download_progress",
DownloadStatus {
downloaded: downloaded as usize,
total: total_size as usize,
time_elapsed: 0,
download_name: downloadName.clone(),
},
)
.or(Err(format!("Unable to signal window")))?;
} else {
window.emit("download_finished", true).or(Err(format!("Unable to signal window!")))?;
window
.emit("download_finished", true)
.or(Err(format!("Unable to signal window!")))?;
}
}
}
return Ok(());
}

View File

@ -1,44 +1,102 @@
use std::env;
use std::io::{Cursor, Seek, Read};
use std::path::{Components, Path, PathBuf};
use flate2::read::GzDecoder;
use std::env;
use std::io::{Cursor, Read, Seek};
use std::path::{Components, Path, PathBuf};
use tar::Archive;
use crate::system_dirs::get_local_data_directory;
use crate::https;
use crate::system_dirs::get_local_data_directory;
use crate::util;
fn check_java(version: u8) -> bool {
let dir = get_local_data_directory().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win" } else {"lin"}));
let dir = get_local_data_directory().join("java").join(format!(
"java-{}-{}",
version,
if env::consts::OS == "windows" {
"win"
} else {
"lin"
}
));
dir.exists()
}
pub async fn install_java(version: u8, window: tauri::Window) {
pub async fn install_java(version: u8, window: tauri::AppHandle) {
if check_java(version) {
return;
}
//let ftp_dir = PathBuf::new().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win.zip" } else {"lin.tar.gz"}));
let mut buff = Cursor::new(vec![]);
//ftp::ftp_retr(Some(window), ftp_dir, &mut buff, |window, data, size| util::download_callback(format!("Java {}", version), window,data, size)).unwrap();
https::download(Some(window.clone()), format!("https://gitea.piwalker.net/fclauncher/java/java-{}-{}", version, if env::consts::OS == "windows" { "win.zip" } else {"lin.tar.gz"}), &mut buff, format!("Java {}", version)).await;
std::fs::create_dir_all(get_local_data_directory().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win" } else {"lin"}))).unwrap();
https::download(
Some(window.clone()),
format!(
"https://gitea.piwalker.net/fclauncher/java/java-{}-{}",
version,
if env::consts::OS == "windows" {
"win.zip"
} else {
"lin.tar.gz"
}
),
&mut buff,
format!("Java {}", version),
)
.await;
std::fs::create_dir_all(get_local_data_directory().join("java").join(format!(
"java-{}-{}",
version,
if env::consts::OS == "windows" {
"win"
} else {
"lin"
}
)))
.unwrap();
buff.rewind().unwrap();
if env::consts::OS != "windows" {
let tar = GzDecoder::new(buff);
let mut archive = Archive::new(tar);
if !unpack_archive(archive, get_local_data_directory().join("java").join(format!("java-{}-lin", version))).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("java").join(format!("java-{}-lin", version))).unwrap();
if !unpack_archive(
archive,
get_local_data_directory()
.join("java")
.join(format!("java-{}-lin", version)),
)
.is_ok()
{
std::fs::remove_dir_all(
get_local_data_directory()
.join("java")
.join(format!("java-{}-lin", version)),
)
.unwrap();
}
} else {
if !zip_extract::extract(buff, get_local_data_directory().join("java").join(format!("java-{}-win", version)).as_path(), true).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("java").join(format!("java-{}-win", version))).unwrap();
if !zip_extract::extract(
buff,
get_local_data_directory()
.join("java")
.join(format!("java-{}-win", version))
.as_path(),
true,
)
.is_ok()
{
std::fs::remove_dir_all(
get_local_data_directory()
.join("java")
.join(format!("java-{}-win", version)),
)
.unwrap();
}
}
}
fn unpack_archive<T: Read>(mut archive: Archive<T>, dst: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
fn unpack_archive<T: Read>(
mut archive: Archive<T>,
dst: PathBuf,
) -> Result<(), Box<dyn std::error::Error>> {
for file in archive.entries()? {
let path = PathBuf::new().join(dst.clone());
let mut file = file?;

View File

@ -1,28 +1,28 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::collections::{HashMap, HashSet};
use std::{io::Cursor, path::PathBuf};
use std::io::Seek;
use self_update::cargo_crate_version;
use serde_json::{Map, Result, Value};
use serde::Serialize;
use serde::Deserialize;
use serde::Serialize;
use serde_json::{Map, Result, Value};
use std::collections::{HashMap, HashSet};
use std::io::Seek;
use std::{io::Cursor, path::PathBuf};
//mod ftp;
mod java;
mod prism;
mod system_dirs;
mod util;
mod modpack;
mod admin;
mod https;
mod java;
mod modpack;
mod prism;
mod sftp;
mod system_dirs;
mod util;
#[derive(Serialize, Deserialize)]
struct ModpackEntry{
struct ModpackEntry {
name: String,
id: String
id: String,
}
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
@ -31,12 +31,30 @@ fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
//modpack::get_modpacks();
//prism::install_prism();
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, modpack::get_modpacks, modpack::launch_modpack, modpack::get_versions, modpack::get_latest_version, prism::launch_prism, prism::install_prism, admin::login, admin::drop_session, admin::shift_up, admin::shift_down, admin::add_pack, admin::remove_pack, admin::update_pack])
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
greet,
modpack::get_modpacks,
modpack::launch_modpack,
modpack::get_versions,
modpack::get_latest_version,
prism::launch_prism,
prism::install_prism,
admin::login,
admin::drop_session,
admin::shift_up,
admin::shift_down,
admin::add_pack,
admin::remove_pack,
admin::update_pack
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -1,35 +1,34 @@
use crate::{java};
use crate::system_dirs::{get_data_directory, get_java_executable, get_local_data_directory, get_prism_executable};
use std::time::Duration;
use std::{env, thread};
use std::fs::File;
use std::process::Command;
use std::{io::Cursor, path::PathBuf};
use std::io::{Read, Seek, Write};
use crate::https;
use crate::java;
use crate::system_dirs::{
get_data_directory, get_java_executable, get_local_data_directory, get_prism_executable,
};
use crate::util;
use reqwest::IntoUrl;
use serde::de::value::Error;
use serde_json::Value;
use serde::Serialize;
use serde::Deserialize;
use crate::util;
use serde::Serialize;
use serde_json::Value;
use std::fs;
use crate::https;
use std::fs::File;
use std::io::{Read, Seek, Write};
use std::process::Command;
use std::time::Duration;
use std::{env, thread};
use std::{io::Cursor, path::PathBuf};
#[derive(Serialize, Deserialize, Clone)]
pub struct ModpackEntry{
pub struct ModpackEntry {
pub name: String,
pub id: String,
pub last_updated: String
pub last_updated: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct VersionEntry{
pub struct VersionEntry {
pub Version: String,
pub File: String,
pub Date: String
pub Date: String,
}
async fn get_modpack_name(id: String) -> String {
@ -43,11 +42,14 @@ async fn get_modpack_name(id: String) -> String {
return instance_name;
}
async fn check_modpack_needs_update(id: String) -> bool{
async fn check_modpack_needs_update(id: String) -> bool {
let mut instance_name = get_modpack_name(id.clone()).await;
if !get_local_data_directory().join("prism").join("instances").join(&mut instance_name).exists() {
if !get_local_data_directory()
.join("prism")
.join("instances")
.join(&mut instance_name)
.exists()
{
return true;
}
@ -56,9 +58,17 @@ async fn check_modpack_needs_update(id: String) -> bool{
return false;
}
let versions = versions.unwrap();
let latest = versions[versions.len()-1].Version.clone();
let latest = versions[versions.len() - 1].Version.clone();
let mut file = File::open(get_local_data_directory().join("prism").join("instances").join(instance_name).join(".minecraft").join("version.txt")).unwrap();
let mut file = File::open(
get_local_data_directory()
.join("prism")
.join("instances")
.join(instance_name)
.join(".minecraft")
.join("version.txt"),
)
.unwrap();
let mut current = String::new();
file.read_to_string(&mut current);
@ -66,36 +76,67 @@ async fn check_modpack_needs_update(id: String) -> bool{
return true;
}
return false;
}
#[tauri::command]
pub async fn launch_modpack(window: tauri::Window, id: String){
pub async fn launch_modpack(window: tauri::AppHandle, id: String) {
if check_modpack_needs_update(id.clone()).await {
install_modpack(window, id.clone()).await;
}
// Launch
let mut child = Command::new(get_local_data_directory().join("prism").join(get_prism_executable())).arg("-l").arg(get_modpack_name(id).await).spawn().unwrap();
let mut child = Command::new(
get_local_data_directory()
.join("prism")
.join(get_prism_executable()),
)
.arg("-l")
.arg(get_modpack_name(id).await)
.spawn()
.unwrap();
}
async fn install_modpack(window: tauri::Window, id: String){
async fn install_modpack(window: tauri::AppHandle, id: String) {
let versions = get_versions(id.clone()).await.unwrap();
let path = env::temp_dir().join(format!("{}.mrpack", get_modpack_name(id.clone()).await));
let mut file = File::create(path.clone()).unwrap();
let ftp_path = PathBuf::new().join(id.clone()).join(versions[versions.len()-1].File.clone().as_str());
let ftp_path = PathBuf::new()
.join(id.clone())
.join(versions[versions.len() - 1].File.clone().as_str());
println!("Downloading file {}", ftp_path.to_str().unwrap());
//ftp::ftp_retr(Some(window.clone()), ftp_path, &mut file, |window, data, size| util::download_callback(name.clone(), window, data, size)).unwrap();
https::download(Some(window.clone()), format!("https://gitea.piwalker.net/fclauncher/{}/{}", id.clone(), versions[versions.len()-1].File.clone()), &mut file, get_modpack_name(id.clone()).await).await;
let mut child = Command::new(get_local_data_directory().join("prism").join(get_prism_executable())).arg("-I").arg(path).spawn().unwrap();
https::download(
Some(window.clone()),
format!(
"https://gitea.piwalker.net/fclauncher/{}/{}",
id.clone(),
versions[versions.len() - 1].File.clone()
),
&mut file,
get_modpack_name(id.clone()).await,
)
.await;
let mut child = Command::new(
get_local_data_directory()
.join("prism")
.join(get_prism_executable()),
)
.arg("-I")
.arg(path)
.spawn()
.unwrap();
loop {
let version_path = get_local_data_directory().join("prism").join("instances").join(get_modpack_name(id.clone()).await).join(".minecraft").join("version.txt");
let version_path = get_local_data_directory()
.join("prism")
.join("instances")
.join(get_modpack_name(id.clone()).await)
.join(".minecraft")
.join("version.txt");
if version_path.clone().exists() {
let mut ver_file = File::open(version_path).unwrap();
let mut buf = String::new();
ver_file.read_to_string(&mut buf).unwrap();
if buf == versions[versions.len()-1].Version.clone().as_str() {
if buf == versions[versions.len() - 1].Version.clone().as_str() {
break;
}
}
@ -103,7 +144,11 @@ async fn install_modpack(window: tauri::Window, id: String){
}
thread::sleep(Duration::from_secs(1));
child.kill();
let info_path = get_local_data_directory().join("prism").join("instances").join(get_modpack_name(id.clone()).await).join("mmc-pack.json");
let info_path = get_local_data_directory()
.join("prism")
.join("instances")
.join(get_modpack_name(id.clone()).await)
.join("mmc-pack.json");
let mut info_file = File::open(info_path.clone()).unwrap();
let info_json: Value = serde_json::from_reader(info_file).unwrap();
let mut mc_version = "0.0";
@ -115,7 +160,11 @@ async fn install_modpack(window: tauri::Window, id: String){
let java = get_java_version(mc_version);
java::install_java(java, window.clone()).await;
let option_path = get_local_data_directory().join("prism").join("instances").join(get_modpack_name(id.clone()).await).join("instance.cfg");
let option_path = get_local_data_directory()
.join("prism")
.join("instances")
.join(get_modpack_name(id.clone()).await)
.join("instance.cfg");
let mut option_file = File::open(option_path.clone()).unwrap();
let mut buf = String::new();
option_file.read_to_string(&mut buf);
@ -123,18 +172,53 @@ async fn install_modpack(window: tauri::Window, id: String){
let mut set = false;
for line in buf.lines() {
if line.starts_with("JavaPath=") {
option_file.write_all(format!("JavaPath={}/java-{}-{}/bin/{}\n", get_local_data_directory().join("java").into_os_string().to_str().unwrap().replace("\\", "/"), java, if env::consts::OS == "windows" {"win"} else {"lin"}, get_java_executable()).as_bytes());
option_file.write_all(
format!(
"JavaPath={}/java-{}-{}/bin/{}\n",
get_local_data_directory()
.join("java")
.into_os_string()
.to_str()
.unwrap()
.replace("\\", "/"),
java,
if env::consts::OS == "windows" {
"win"
} else {
"lin"
},
get_java_executable()
)
.as_bytes(),
);
set = true;
} else {
option_file.write_all(format!("{}\n",line).as_bytes());
option_file.write_all(format!("{}\n", line).as_bytes());
}
}
if !set {
option_file.write_all(format!("JavaPath={}/java-{}-{}/bin/{}\n", get_local_data_directory().join("java").into_os_string().to_str().unwrap().replace("\\", "/"), java, if env::consts::OS == "windows" {"win"} else {"lin"}, get_java_executable()).as_bytes());
option_file.write_all(
format!(
"JavaPath={}/java-{}-{}/bin/{}\n",
get_local_data_directory()
.join("java")
.into_os_string()
.to_str()
.unwrap()
.replace("\\", "/"),
java,
if env::consts::OS == "windows" {
"win"
} else {
"lin"
},
get_java_executable()
)
.as_bytes(),
);
option_file.write_all("OverrideJavaLocation=true\n".as_bytes());
option_file.write_all("OverrideJava=true\n".as_bytes());
}
}
#[tauri::command]
@ -144,12 +228,19 @@ pub async fn get_modpacks() -> Vec<ModpackEntry> {
//if modpacks.is_empty() {
let mut buf = Cursor::new(vec![]);
//ftp::ftp_retr(None, PathBuf::new().join("modpacks.json"), &mut buf, |_, _, _| return);
https::download(None, format!("https://gitea.piwalker.net/fclauncher/modpacks.json"), &mut buf, format!("modpacks.json")).await;
https::download(
None,
format!("https://gitea.piwalker.net/fclauncher/modpacks.json"),
&mut buf,
format!("modpacks.json"),
)
.await;
buf.rewind();
let res = serde_json::from_reader(buf);
if !res.is_ok() {
println!("Result not ok!");
let paths = fs::read_dir(get_local_data_directory().join("prism").join("instances")).unwrap();
let paths =
fs::read_dir(get_local_data_directory().join("prism").join("instances")).unwrap();
for path in paths {
let path = path.unwrap();
if fs::metadata(path.path()).unwrap().is_file() {
@ -160,9 +251,13 @@ pub async fn get_modpacks() -> Vec<ModpackEntry> {
continue;
}
modpacks.push(ModpackEntry{name: name.clone(), id: name, last_updated: format!("")})
modpacks.push(ModpackEntry {
name: name.clone(),
id: name,
last_updated: format!(""),
})
}
return modpacks.clone()
return modpacks.clone();
}
let modpacks: Vec<ModpackEntry> = res.unwrap();
//println!("{}", v[0].name);
@ -175,20 +270,30 @@ pub async fn get_modpacks() -> Vec<ModpackEntry> {
}
#[tauri::command]
pub async fn get_versions(id: String) -> Result<Vec<VersionEntry>,String> {
pub async fn get_versions(id: String) -> Result<Vec<VersionEntry>, String> {
let mut versions: Vec<VersionEntry> = Vec::new();
let mut buf = Cursor::new(vec![]);
//ftp::ftp_retr(None, PathBuf::new().join(id).join("versions.json"), &mut buf, |_, _, _| return);
https::download(None, format!("https://gitea.piwalker.net/fclauncher/{}/versions.json", id.clone()), &mut buf, format!("{}/versions.json", id.clone())).await;
https::download(
None,
format!(
"https://gitea.piwalker.net/fclauncher/{}/versions.json",
id.clone()
),
&mut buf,
format!("{}/versions.json", id.clone()),
)
.await;
buf.rewind();
let versions: Vec<VersionEntry> = serde_json::from_reader(buf).or(Err(format!("Unable to parse json")))?;
let versions: Vec<VersionEntry> =
serde_json::from_reader(buf).or(Err(format!("Unable to parse json")))?;
//for version in v.as_array().unwrap() {
//versions.push(VersionEntry{version: version["Version"].as_str().unwrap().to_string(), file: version["File"].as_str().unwrap().to_string(), date: version["Date"].as_str().unwrap().to_string()});
//}
return Ok(versions.clone());
}
fn get_java_version(mc_version: &str) -> u8{
fn get_java_version(mc_version: &str) -> u8 {
let components: Vec<&str> = mc_version.split(".").collect();
let mut java = 8;
if components[1] == "17" {
@ -206,15 +311,14 @@ fn get_java_version(mc_version: &str) -> u8{
}
#[tauri::command]
pub async fn get_latest_version(id: String) -> Result<String, String>{
pub async fn get_latest_version(id: String) -> Result<String, String> {
let versions = get_versions(id).await.unwrap();
if(versions.len() == 0){
if (versions.len() == 0) {
return Ok(format!(""));
}
Ok(versions[versions.len()-1].Version.clone())
Ok(versions[versions.len() - 1].Version.clone())
}
//pub fn create_json(modpacks: Vec<ModpackEntry>) -> Result<serde_json::Value, String> {
//}

View File

@ -1,10 +1,20 @@
use std::{env, fs::File, io::{BufRead, Cursor, Seek, Write}, path::PathBuf, process::Command, str::FromStr};
use flate2::read::GzDecoder;
use std::{
env,
fs::File,
io::{BufRead, Cursor, Seek, Write},
path::PathBuf,
process::Command,
str::FromStr,
};
use tar::Archive;
use tauri::api::file;
use crate::{https, java, system_dirs::{get_local_data_directory, get_prism_executable}, util};
//use tauri::file;
use crate::{
https, java,
system_dirs::{get_local_data_directory, get_prism_executable},
util,
};
pub fn check_prism() -> bool {
let path = get_local_data_directory().join("prism");
@ -12,7 +22,7 @@ pub fn check_prism() -> bool {
}
#[tauri::command]
pub async fn install_prism(window: tauri::Window){
pub async fn install_prism(window: tauri::AppHandle) {
if check_prism() {
return;
}
@ -20,26 +30,59 @@ pub async fn install_prism(window: tauri::Window){
let mut buff = Cursor::new(vec![]);
let mut total = 0;
//ftp::ftp_retr(Some(window.clone()), path, &mut buff, |window: Option<tauri::Window>, data, size| util::download_callback("Prism Launcher".to_string(),window, data, size)).unwrap();
https::download(Some(window.clone()), format!("https://gitea.piwalker.net/fclauncher/prism/prism-{}", if env::consts::OS == "windows" {"win.zip"} else {"lin.tar.gz"}), &mut buff, format!("Prism Launcher")).await;
https::download(
Some(window.clone()),
format!(
"https://gitea.piwalker.net/fclauncher/prism/prism-{}",
if env::consts::OS == "windows" {
"win.zip"
} else {
"lin.tar.gz"
}
),
&mut buff,
format!("Prism Launcher"),
)
.await;
std::fs::create_dir_all(get_local_data_directory().join("prism")).unwrap();
buff.rewind().unwrap();
if env::consts::OS != "windows" {
let tar = GzDecoder::new(buff);
let mut archive = Archive::new(tar);
if !archive.unpack(get_local_data_directory().join("prism")).is_ok() {
if !archive
.unpack(get_local_data_directory().join("prism"))
.is_ok()
{
std::fs::remove_dir_all(get_local_data_directory().join("prism"));
}
} else {
if !zip_extract::extract(buff, get_local_data_directory().join("prism").as_path(), true).is_ok() {
if !zip_extract::extract(
buff,
get_local_data_directory().join("prism").as_path(),
true,
)
.is_ok()
{
std::fs::remove_dir_all(get_local_data_directory().join("prism"));
}
}
let mut buff = Cursor::new(vec![]);
//ftp::ftp_retr(Some(window.clone()), PathBuf::new().join("prism").join("prismlauncher.cfg"), &mut buff, |_, _, _| return).unwrap();
https::download(None, format!("https://gitea.piwalker.net/fclauncher/prism/prismlauncher.cfg"), &mut buff, format!("prismlauncher.cfg")).await;
https::download(
None,
format!("https://gitea.piwalker.net/fclauncher/prism/prismlauncher.cfg"),
&mut buff,
format!("prismlauncher.cfg"),
)
.await;
buff.rewind();
let mut file = File::create(get_local_data_directory().join("prism").join("prismlauncher.cfg")).unwrap();
let mut file = File::create(
get_local_data_directory()
.join("prism")
.join("prismlauncher.cfg"),
)
.unwrap();
loop {
let mut buf = String::new();
let count = buff.read_line(&mut buf).unwrap();
@ -47,19 +90,35 @@ pub async fn install_prism(window: tauri::Window){
break;
}
if buf.starts_with("JavaPath") {
buf = format!("JavaPath={}/java/java-21-{}\n", get_local_data_directory().to_str().unwrap().replace("\\", "/"), if env::consts::OS == "windows" { "win" } else { "lin" });
}else if buf.starts_with("LastHostname") {
buf = format!("LastHostname={}\n", gethostname::gethostname().to_str().unwrap());
buf = format!(
"JavaPath={}/java/java-21-{}\n",
get_local_data_directory()
.to_str()
.unwrap()
.replace("\\", "/"),
if env::consts::OS == "windows" {
"win"
} else {
"lin"
}
);
} else if buf.starts_with("LastHostname") {
buf = format!(
"LastHostname={}\n",
gethostname::gethostname().to_str().unwrap()
);
}
file.write_all(buf.as_bytes());
}
}
#[tauri::command]
pub fn launch_prism() {
let mut child = Command::new(get_local_data_directory().join("prism").join(get_prism_executable())).spawn().unwrap();
let mut child = Command::new(
get_local_data_directory()
.join("prism")
.join(get_prism_executable()),
)
.spawn()
.unwrap();
}

View File

@ -1,7 +1,3 @@
use std::io::prelude::*;
use std::net::TcpStream;
use std::path::Path;
use std::path::PathBuf;
use futures_util::future::BoxFuture;
use futures_util::io::BufReader;
use futures_util::io::Cursor;
@ -9,58 +5,98 @@ use futures_util::FutureExt;
use ssh2::OpenFlags;
use ssh2::Session;
use ssh2::Sftp;
use std::io::prelude::*;
use std::net::TcpStream;
use std::path::Path;
use std::path::PathBuf;
use tauri::Emitter;
use crate::https;
pub fn connect(username: String, password: String) -> Result<Session, String> {
let tcp = TcpStream::connect("gitea.piwalker.net:22").or(Err(format!("Unable to connect to host")))?;
let tcp = TcpStream::connect("gitea.piwalker.net:22")
.or(Err(format!("Unable to connect to host")))?;
let mut sess = Session::new().or(Err(format!("Unable to creat stream")))?;
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password(username.as_str(), password.as_str()).or(Err(format!("Invalid username or password")))?;
sess.userauth_password(username.as_str(), password.as_str())
.or(Err(format!("Invalid username or password")))?;
Ok(sess)
}
pub async fn uplaod(window: Option<tauri::Window>, sess: Session, path: PathBuf, mut reader: impl Read, upload_name: String, total_size: usize) -> Result<(), String>{
pub async fn uplaod(
window: Option<tauri::AppHandle>,
sess: Session,
path: PathBuf,
mut reader: impl Read,
upload_name: String,
total_size: usize,
) -> Result<(), String> {
let sftp = sess.sftp().or(Err("unable to open sftp session"))?;
let mut file = sftp.create(path.as_path()).or(Err("Unable to open file"))?;
let mut uploaded = 0;
while true {
let mut buf = vec![0u8; 1024*32];
let mut buf = vec![0u8; 1024 * 32];
let res = reader.read(&mut buf).unwrap();
if res <= 0 {
if let Some(window) = window.clone() {
window.emit("download_finished", true).or(Err(format!("Unable to signal window!")))?;
window
.emit("download_finished", true)
.or(Err(format!("Unable to signal window!")))?;
}
break;
}
file.write_all(buf.split_at(res).0).unwrap();
uploaded += res;
println!("Uploading {} {}MB / {}MB", upload_name, uploaded/(1024*1024), total_size/(1024*1024));
println!(
"Uploading {} {}MB / {}MB",
upload_name,
uploaded / (1024 * 1024),
total_size / (1024 * 1024)
);
if let Some(window) = window.clone() {
window.emit("download_progress", https::DownloadStatus{downloaded: uploaded as usize, total: total_size as usize, time_elapsed: 0, download_name: upload_name.clone()}).or(Err(format!("Unable to signal window")))?;
window
.emit(
"download_progress",
https::DownloadStatus {
downloaded: uploaded as usize,
total: total_size as usize,
time_elapsed: 0,
download_name: upload_name.clone(),
},
)
.or(Err(format!("Unable to signal window")))?;
}
}
Ok(())
}
pub async fn mkdir(sess: Session, path: PathBuf) -> Result<(), String>{
pub async fn mkdir(sess: Session, path: PathBuf) -> Result<(), String> {
let sftp = sess.sftp().or(Err("Unable to open sftp session"))?;
sftp.mkdir(path.as_path(), 0o775).or(Err(format!("Unable to create directory")))?;
sftp.mkdir(path.as_path(), 0o775)
.or(Err(format!("Unable to create directory")))?;
Ok(())
}
pub fn rmdir(sess: Session, path: PathBuf) -> BoxFuture<'static, ()>{
pub fn rmdir(sess: Session, path: PathBuf) -> BoxFuture<'static, ()> {
async move {
let sftp = sess.sftp().or(Err("Unable to open sftp session")).unwrap();
let dirs = sftp.readdir(path.as_path()).or(Err("unable to stat directory")).unwrap();
let dirs = sftp
.readdir(path.as_path())
.or(Err("unable to stat directory"))
.unwrap();
for dir in dirs {
if dir.1.is_dir() {
rmdir(sess.clone(), dir.0).await;
}else {
sftp.unlink(dir.0.as_path()).or(Err(format!("Unable to delete file"))).unwrap();
} else {
sftp.unlink(dir.0.as_path())
.or(Err(format!("Unable to delete file")))
.unwrap();
}
}
sftp.rmdir(path.as_path()).or(Err(format!("Unable to delete directory"))).unwrap();
}.boxed()
sftp.rmdir(path.as_path())
.or(Err(format!("Unable to delete directory")))
.unwrap();
}
.boxed()
}

View File

@ -1,6 +1,8 @@
use std::{env, path::{Path, PathBuf}};
use dirs::home_dir;
use std::{
env,
path::{Path, PathBuf},
};
pub fn get_local_data_directory() -> PathBuf {
dirs::data_local_dir().unwrap().join("FCLauncher")
@ -11,10 +13,23 @@ pub fn get_data_directory() -> PathBuf {
}
pub fn get_java_executable() -> String {
return format!("java{}", if env::consts::OS == "windows" { ".exe" } else { "" })
return format!(
"java{}",
if env::consts::OS == "windows" {
".exe"
} else {
""
}
);
}
pub fn get_prism_executable() -> String {
return format!("{}", if env::consts::OS == "windows" { "prismlauncher.exe" } else { "PrismLauncher" })
return format!(
"{}",
if env::consts::OS == "windows" {
"prismlauncher.exe"
} else {
"PrismLauncher"
}
);
}

View File

@ -1,24 +1,41 @@
use std::clone;
use tauri::Emitter;
use serde::Serialize;
#[derive(Clone, Serialize)]
pub struct DownloadStatus {
downloaded: usize,
total: usize,
time_elapsed: usize,
download_name: String
download_name: String,
}
pub fn download_callback(download_name: String, window: Option<tauri::Window>, count: usize, size: usize){
unsafe{
pub fn download_callback(
download_name: String,
window: Option<tauri::AppHandle>,
count: usize,
size: usize,
) {
unsafe {
static mut total: usize = 0;
total += count;
if let Some(window1) = window.clone() {
window1.emit("download_progress", DownloadStatus{downloaded: total, total: size, time_elapsed: 0, download_name: download_name});
window1.emit(
"download_progress",
DownloadStatus {
downloaded: total,
total: size,
time_elapsed: 0,
download_name: download_name,
},
);
}
println!("Downloading {}MB / {}MB", total/(1024*1024), size/(1024*1024));
println!(
"Downloading {}MB / {}MB",
total / (1024 * 1024),
size / (1024 * 1024)
);
if count == 0 {
if let Some(window2) = window {
window2.emit("download_finished", true);
@ -27,4 +44,3 @@ pub fn download_callback(download_name: String, window: Option<tauri::Window>, c
}
}
}

View File

@ -1,37 +1,36 @@
{
"build": {
"devPath": "../src",
"distDir": "../src",
"withGlobalTauri": true
"frontendDist": "../src"
},
"package": {
"productName": "FCLauncher",
"version": "1.0.4"
},
"tauri": {
"updater": {
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"createUpdaterArtifacts": "v1Compatible"
},
"productName": "FCLauncher",
"mainBinaryName": "FCLauncher",
"version": "1.0.5",
"identifier": "net.piwalker",
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDOEUwMjYxRUU2NEI5RgpSV1NmUytZZUp1RElBN3dEaGhpWG9JZVNQcFlnNFFzaXN0UnBsVmxNeVdWWnJoQmh4cGJRbjN3Ygo=",
"endpoints": [
"https://gitea.piwalker.net/fclauncher/app.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDOEUwMjYxRUU2NEI5RgpSV1NmUytZZUp1RElBN3dEaGhpWG9JZVNQcFlnNFFzaXN0UnBsVmxNeVdWWnJoQmh4cGJRbjN3Ygo="
},
"allowlist": {
"all": false,
"shell": {
"all": false,
"open": true
},
"dialog": {
"all": false,
"ask": true,
"open": true
},
"process": {
"all": true
]
}
},
"app": {
"security": {
"csp": null
},
"withGlobalTauri": true,
"windows": [
{
"title": "FCLauncher",
@ -39,21 +38,6 @@
"height": 600,
"resizable": false
}
],
"security": {
"csp": null
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "net.piwalker",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
}

View File

@ -1,9 +1,13 @@
const { invoke } = window.__TAURI__.tauri;
const { listen } = window.__TAURI__.event;
const { ask, open } = window.__TAURI__.dialog;
const { ask, open, message } = window.__TAURI__.dialog;
const downBar = document.querySelector(".progressFinished");
//import { listen } from '@tauri-apps/api';
const error = listen("Error", (error) => {
message(error.payload, {title: "Error", type: "error"});
});
var selected_pack = "";
var update_menu = document.getElementById("update");
var create_menu = document.getElementById("create");

View File

@ -1,9 +1,13 @@
const { invoke } = window.__TAURI__.tauri;
const { listen } = window.__TAURI__.event;
const { ask } = window.__TAURI__.dialog;
const { ask, message } = window.__TAURI__.dialog;
const downBar = document.querySelector(".progressFinished");
//import { listen } from '@tauri-apps/api';
const error = listen("Error", (error) => {
message(error.payload, {title: "Error", type: "error"});
});
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("back").addEventListener("click", back);

View File

@ -1,10 +1,14 @@
const { invoke } = window.__TAURI__.tauri;
const { listen } = window.__TAURI__.event;
const { ask } = window.__TAURI__.dialog;
const { ask, message } = window.__TAURI__.dialog;
const { exit } = window.__TAURI__.process;
const downBar = document.querySelector(".progressFinished");
//import { listen } from '@tauri-apps/api';
const error = listen("Error", (error) => {
message(error.payload, {title: "Error", type: "error"});
});
const download_progress = listen("download_progress", (progress) => {
console.log("Downloading");
//console.log("Downloaded "+progress.payload.downloaded/(1024*1024) +"MB / " + progress.payload.total/(1024*1024) + "MB");

View File

@ -1,3 +1,5 @@
# FCLauncher
Launcher for Familycraft Modpacks
A launcher made to facilitate the distribution of FamilyCraft, and other modpacks that I manage.
The launcher's core guiding principle is simplicity. It makes it extremely easy to install and use any of the provided modpacks with the click of a button. There is no need to worry about versions, different instances, or anything else. Simply select a modpack and click 'Play'.

View File

@ -1,12 +0,0 @@
[package]
name = "launcher"
version = "0.1.0"
edition = "2021"
[dependencies]
dirs = "5.0.1"
flate2 = "1.0.30"
serde_json = "1.0.117"
suppaftp = { version = "6.0.1", features = ["native-tls"] }
tar = "0.4.41"
zip-extract = "0.1.3"

View File

@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIUeFYZmMrAiIFZ9/QFwUS636XZrJMwDQYJKoZIhvcNAQEL
BQAwgYcxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdXeW9taW5nMREwDwYDVQQHDAhD
aGV5ZW5uZTERMA8GA1UECgwIUGVyc29uYWwxGzAZBgNVBAMMEmdpdGVhLnBpd2Fs
a2VyLm5ldDEjMCEGCSqGSIb3DQEJARYUc3dhbGtlckBwaXdhbGtlci5uZXQwHhcN
MjQwNjIyMDAzNzEwWhcNMjUwNjIyMDAzNzEwWjCBhzELMAkGA1UEBhMCVVMxEDAO
BgNVBAgMB1d5b21pbmcxETAPBgNVBAcMCENoZXllbm5lMREwDwYDVQQKDAhQZXJz
b25hbDEbMBkGA1UEAwwSZ2l0ZWEucGl3YWxrZXIubmV0MSMwIQYJKoZIhvcNAQkB
FhRzd2Fsa2VyQHBpd2Fsa2VyLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANOo7BOAUKhbBWodZqY8U34sQhK5Zj6WkVPQrFG1MWXX3KarDtBSuZ99
PjbIoDR+Xm5MuNcJMnbeG4+EH6SrNsogHoyn7m8XJAQ/1N6kHEii4qeDzMIbcNu6
7L54ZbONBw1Sygilnavp1iPY/2GzWH5ynaT4w4hQQrmDm8GlDNjxWGnw1CpOExAs
LdUP3sF6RNtN6dX1vgYMo9ziNtRazRmDANXykgrfBrPCyjUGDsI9wnqm21qoaQ/s
w506XovYI1Q6zWVu6cWUYyCFy4mABQxOOf7doJi4h6Wbxfp4WbNdcoBDHDN4nHzo
pdrMzJ8GlZD0aCmmU+8ERvIk+IXY6+kCAwEAAaNTMFEwHQYDVR0OBBYEFJ/4/N4x
fO/5nu/snApQO7Cw6CyCMB8GA1UdIwQYMBaAFJ/4/N4xfO/5nu/snApQO7Cw6CyC
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMFwno+imd5ApXP4
NsuX6db5GLiT6SHV65iactFUbnvqvK35KQMKVW03hOb2FPwAzEPARcPtFlENAWBl
mHphDwAmfLbHXHdiTAKJNFO7D/AOB4TG6geBFlhYvwHCVS17nzFRJvF/0APlgbO8
8f3XkmPBPudaGiuKHWdppdHCisk6CfYvNNnjguxihyUL/mDkwiKYQPcHsMYwdYM0
QWCcTNyCjnFK/pbo6dLyPAFpXE9becSEhbxvFziNelADRflLkOUSd+sfxmoLMMsA
EJajfocYQkAOiuh8uVzol9xsnKcZiujRoTSnndZsRVqfiNZaJbpvZoD/kY0aBXHo
SIh5Ff4=
-----END CERTIFICATE-----

View File

@ -1,56 +0,0 @@
use std::io::Write;
use std::path::PathBuf;
use suppaftp::FtpError;
use suppaftp::NativeTlsFtpStream;
use suppaftp::NativeTlsConnector;
use suppaftp::native_tls::Certificate;
use suppaftp::native_tls::TlsConnector;
fn ftp_connection_anonymous() -> Result<NativeTlsFtpStream, FtpError>{
ftp_connection("anonymous", "anonymous@")
}
fn ftp_connection(username: &str, password: &str) -> Result<NativeTlsFtpStream, FtpError>{
let ftp_stream = NativeTlsFtpStream::connect("gitea.piwalker.net:21").unwrap_or_else(|err|
panic!("{}", err)
);
let cert = include_bytes!("../res/vsftpd.crt");
let cert = Certificate::from_pem(cert).unwrap();
let mut ftp_stream = ftp_stream.into_secure(NativeTlsConnector::from(TlsConnector::builder().add_root_certificate(cert).build().unwrap()), "gitea.piwalker.net").unwrap();
ftp_stream.login("anonymous", "anonymous@").map(|_| Ok(ftp_stream)).unwrap()
}
pub fn ftp_retr(file: PathBuf , mut writer: impl Write, mut callback: impl FnMut(usize)) -> Result<bool, FtpError> {
let mut ftp_stream = ftp_connection_anonymous().unwrap();
let file = file.to_str().unwrap().replace("\\", "/");
let size = ftp_stream.size(&file)?;
let mut total = 0;
ftp_stream.retr(file.as_str(), |stream| {
let mut tx_bytes = [0u8; 1500];
loop {
let bytes_read = stream.read(&mut tx_bytes).unwrap();
writer.write_all(&mut tx_bytes.split_at(bytes_read).0).unwrap();
total += bytes_read;
if total == size || bytes_read == 0 {
break;
}else{
callback(bytes_read);
}
}
Ok(true)
})?;
Ok(true)
}
pub fn ftp_get_size(file: PathBuf) -> Result<usize, FtpError> {
let mut stream = ftp_connection_anonymous()?;
stream.size(file.to_str().unwrap().replace("\\", "/"))
}

View File

@ -1,54 +0,0 @@
use std::env;
use std::io::{Cursor, Seek, Read};
use std::path::{Components, Path, PathBuf};
use flate2::read::GzDecoder;
use tar::Archive;
use crate::system_dirs::get_local_data_directory;
use crate::ftp::{self, ftp_get_size};
use crate::util;
fn check_java(version: u8) -> bool {
let dir = get_local_data_directory().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win" } else {"lin"}));
dir.exists()
}
pub fn install_java(version: u8) {
if check_java(version) {
return;
}
let ftp_dir = PathBuf::new().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win.zip" } else {"lin.tar.gz"}));
let mut buff = Cursor::new(vec![]);
let mut total: usize = 0;
let size = ftp_get_size(ftp_dir.clone()).unwrap();
ftp::ftp_retr(ftp_dir, &mut buff, |data| util::download_callback(data, &mut total, size)).unwrap();
std::fs::create_dir_all(get_local_data_directory().join("java").join(format!("java-{}-{}", version, if env::consts::OS == "windows" { "win" } else {"lin"}))).unwrap();
buff.rewind().unwrap();
if env::consts::OS != "windows" {
let tar = GzDecoder::new(buff);
let mut archive = Archive::new(tar);
if !unpack_archive(archive, get_local_data_directory().join("java").join(format!("java-{}-lin", version))).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("java").join(format!("java-{}-lin", version))).unwrap();
}
} else {
if !zip_extract::extract(buff, get_local_data_directory().join("java").join(format!("java-{}-win", version)).as_path(), true).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("java").join(format!("java-{}-win", version))).unwrap();
}
}
}
fn unpack_archive<T: Read>(mut archive: Archive<T>, dst: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
for file in archive.entries()? {
let path = PathBuf::new().join(dst.clone());
let mut file = file?;
let file_path = file.path()?;
let mut file_path = file_path.components();
let _ = file_path.next();
let file_path = file_path.as_path();
file.unpack(path.join(file_path))?;
}
Ok(())
}

View File

@ -1,32 +0,0 @@
use std::fs::File;
use std::io::{Cursor, Read, Seek, Write};
use std::env::temp_dir;
use std::path::{Path, PathBuf};
use serde_json::{Result, Value};
use std::process::Command;
use std::process::Child;
mod ftp;
mod prism;
mod system_dirs;
mod java;
mod util;
fn main() {
prism::install_prism().unwrap();
//let mut data = Cursor::new(vec![]);
//ftp::ftpRetr(PathBuf::new().join("fcs7").join("versions.json"), &mut data, |_| return).unwrap();
//data.seek(std::io::SeekFrom::Start(0)).unwrap();
//let v: Value = serde_json::from_reader(data).unwrap();
//println!("fcs7/{}",v[v.as_array().unwrap().len()-1]["File"].as_str().unwrap());
//println!("{}", temp_dir().join("pack.mrpack").display());
//let mut file = File::create(temp_dir().join("pack.mrpack")).unwrap();
//ftp::ftpRetr(PathBuf::new().join("fcs7").join(v[v.as_array().unwrap().len()-1]["File"].as_str().unwrap()),file, |data| println!("Transferred {} Bytes", data)).unwrap();
//let output = Command::new("prismlauncher").arg("-I").arg(temp_dir().join("pack.mrpack")).spawn();
}
pub fn test(){}

View File

@ -1,45 +0,0 @@
use std::{env, io::{Cursor, Seek}, path::PathBuf};
use flate2::read::GzDecoder;
use tar::Archive;
use crate::{ftp, java, system_dirs::get_local_data_directory};
pub fn check_prism() -> bool {
let path = get_local_data_directory().join("prism");
path.exists()
}
pub fn install_prism() -> Result<(), Box<dyn std::error::Error>>{
if check_prism() {
return Ok(());
}
java::install_java(21);
let path = PathBuf::new().join("prism").join(format!("prism-{}",if env::consts::OS == "windows" {"win.zip"} else {"lin.tar.gz"}));
let size = ftp::ftp_get_size(path.clone())?;
let mut buff = Cursor::new(vec![]);
let mut total = 0;
ftp::ftp_retr(path, &mut buff, |data| {
total += data;
println!("Downloading Prism: {}MB / {}MB", total/(1024*1024), size/(1024*1024));
}).unwrap();
std::fs::create_dir_all(get_local_data_directory().join("prism"))?;
buff.rewind()?;
if env::consts::OS != "windows" {
let tar = GzDecoder::new(buff);
let mut archive = Archive::new(tar);
if !archive.unpack(get_local_data_directory().join("prism")).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("prism"));
}
} else {
if !zip_extract::extract(buff, get_local_data_directory().join("prism").as_path(), true).is_ok() {
std::fs::remove_dir_all(get_local_data_directory().join("prism"));
}
}
let buff = Cursor::new(vec![]);
ftp::ftp_retr(PathBuf, writer, callback)
Ok(())
}

View File

@ -1,8 +0,0 @@
use std::{env, path::{Path, PathBuf}};
use dirs::home_dir;
pub fn get_local_data_directory() -> PathBuf {
dirs::data_local_dir().unwrap().join("FCLauncher")
}

View File

@ -1,6 +0,0 @@
pub fn download_callback(count: usize, total: &mut usize, size: usize){
*total += count;
println!("Downloading {}MB / {}MB", *total/(1024*1024), size/(1024*1024));
}