mod encryption; mod router; mod sql; use actix_web::{ rt, App, HttpServer, get, HttpResponse, HttpRequest, web, dev::Service, http::header::ContentType }; use clap::Parser; use std::fs; use std::sync::Mutex; use std::time::Duration; use actix_web::dev::{Server, ServerHandle}; use lazy_static::lazy_static; #[get("/index.css")] async fn css(_req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .insert_header(ContentType(mime::TEXT_CSS)) .body(include_file!("webui/dist/index.css")) } #[get("/index.js")] async fn js(_req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .insert_header(ContentType(mime::APPLICATION_JAVASCRIPT_UTF_8)) .body(include_file!("webui/dist/index.js")) } #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { #[arg(short, long, default_value_t = 8080, help = "Port to listen on")] port: u16, #[arg(short, long, default_value_t = 8000, help = "Assets port to listen on")] asset_port: u16, #[arg(long, default_value = "./", help = "Path to store database files")] path: String, #[arg(long, default_value = "./asset/", help = "Path to store database files")] asset_path: String, #[arg(long, default_value_t = false, help = "Enable assets server")] enable_asset_server: bool, #[arg(long, default_value_t = false, help = "Serve gree headers with https. WILL NOT ACCEPT HTTPS REQUESTS")] https: bool, #[arg(long, default_value = "http://127.0.0.1:51376", help = "Address to NPPS4 server for sif account linking")] npps4: String, //below options are for the "Help" page #[arg(long, default_value = "", help = "Link to patched android global apk for this server.")] global_android: String, #[arg(long, default_value = "", help = "Link to patched android japan apk for this server.")] japan_android: String, #[arg(long, default_value = "", help = "Link to patched iOS global apk for this server.")] global_ios: String, #[arg(long, default_value = "", help = "Link to patched iOS japan apk for this server.")] japan_ios: String, #[arg(long, default_value = "", help = "Link to asset server.")] assets_url: String, #[arg(long, default_value_t = 0, help = "Max time returned by the server, in the JSON \"timestamp\" key.")] max_time: u64, #[arg(long, default_value_t = false, help = "Disable webui, act completely like the original server")] hidden: bool, #[arg(long, default_value_t = false, help = "Purge dead user accounts on startup")] purge: bool, #[arg(long, default_value_t = false, help = "Disable user account imports")] disable_imports: bool, #[arg(long, default_value_t = false, help = "Disable user account exports")] disable_exports: bool, #[arg(long, default_value = "", help = "Asset hash for English iOS client.")] en_ios_asset_hash: String, #[arg(long, default_value = "", help = "Asset hash for JP iOS client.")] jp_ios_asset_hash: String, #[arg(long, default_value = "", help = "Asset hash for English Android client.")] en_android_asset_hash: String, #[arg(long, default_value = "", help = "Asset hash for JP Android client.")] jp_android_asset_hash: String } #[actix_web::main] async fn run_server(in_thread: bool) -> std::io::Result<()> { let args = get_args(); let port = args.port; let enable_asset_server = args.enable_asset_server; let asset_port = args.asset_port; if args.purge { println!("Purging accounts..."); let ct = crate::router::userdata::purge_accounts(); println!("Purged {} accounts", ct); } let webui_server = HttpServer::new(|| App::new() .wrap_fn(|req, srv| { println!("WebUI Request: {}", req.path()); srv.call(req) }) .app_data(web::PayloadConfig::default().limit(1024 * 1024 * 25)) .service(css) .service(js) .default_service(web::route().to(router::request)) ).bind(("0.0.0.0", port))?.run(); println!("WebUI Server started: http://0.0.0.0:{}", port); println!("Database path is set to {}", args.path); println!("Sif1 transfer requests will attempt to contact NPPS4 at {}", args.npps4); let mut asset_server: Option = None; if enable_asset_server { asset_server = Some(HttpServer::new(|| App::new() .wrap_fn(|req, srv| { println!("Assets Request: {}", req.path()); srv.call(req) }) .app_data(web::PayloadConfig::default().limit(1024 * 1024 * 1024)) .default_service(web::route().to(router::asset_request)) ).bind(("0.0.0.0", asset_port))?.run()); println!("Assets Server started: http://0.0.0.0:{}", asset_port); println!("Assets path is set to {}", args.asset_path); } if args.https { println!("Note: gree is set to https mode. http requests will fail on jp clients."); } if in_thread { set_running(true).await; let handle = webui_server.handle(); rt::spawn(webui_server); let mut handle2: Option = None; if asset_server.is_some() { let server = asset_server.unwrap(); handle2 = Some(server.handle()); rt::spawn(server); } while get_running().await { actix_web::rt::time::sleep(Duration::from_millis(100)).await; } handle.stop(false).await; if handle2.is_some() { handle2.unwrap().stop(false).await; } println!("Stopped"); return Ok(()); } if asset_server.is_some() { let _ = futures::join!(webui_server, asset_server.unwrap()); } else { webui_server.await? } Ok(()) } #[actix_web::main] async fn stop_server() { set_running(false).await; println!("Stopping"); } fn main() -> std::io::Result<()> { run_server(false) } pub fn get_args() -> Args { Args::parse() } pub fn get_data_path(file_name: &str) -> String { let args = get_args(); let mut path = args.path; while path.ends_with('/') { path.pop(); } fs::create_dir_all(&path).unwrap(); format!("{}/{}", path, file_name) } // include_file macro: includes a file compressed at compile time, and decompresses it on reference. Decreases binary size #[macro_export] macro_rules! include_file { ( $s:expr ) => { { let file = include_flate_codegen::deflate_file!($s); let ret = $crate::decode(file); std::string::String::from_utf8(ret).unwrap() } }; } pub fn decode(bytes: &[u8]) -> Vec { use std::io::{Cursor, Read}; let mut dec = libflate::deflate::Decoder::new(Cursor::new(bytes)); let mut ret = Vec::new(); dec.read_to_end(&mut ret).unwrap(); ret } #[macro_export] macro_rules! lock_onto_mutex { ($mutex:expr) => {{ loop { match $mutex.lock() { Ok(value) => { break value; } Err(_) => { $mutex.clear_poison(); actix_web::rt::time::sleep(std::time::Duration::from_millis(15)).await; } } } }}; } lazy_static! { static ref RUNNING: Mutex = Mutex::new(false); } async fn set_running(running: bool) { let mut result = lock_onto_mutex!(RUNNING); *result = running; } async fn get_running() -> bool { let result = lock_onto_mutex!(RUNNING); *result }