0.3.0: remove rust-embed. the shackles have been broken.
This commit is contained in:
parent
7bfb71fff3
commit
647599d2bc
11 changed files with 1078 additions and 235 deletions
737
Cargo.lock
generated
737
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
29
Cargo.toml
29
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
#![feature(let_chains)]
|
||||
[package]
|
||||
name = "reversed-rooms-launcher"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
@ -13,13 +13,30 @@ serde_json = "1.0.140"
|
|||
tempfile = "3.19.1"
|
||||
iced_video_player = {path = "./iced_video_player"}
|
||||
url = "2.5.4"
|
||||
rust-embed = "8.7.0"
|
||||
file-format = "0.26.0"
|
||||
strum = "0.27.1"
|
||||
strum_macros = "0.27.1"
|
||||
reqwest = { version = "0.12.15", features = ["blocking", "gzip", "json"] }
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
lto = true # Link-time optimization.
|
||||
opt-level = 3 # Optimize for speed.
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
strip = true # Automatically strip symbols from the binary
|
||||
lto = "fat" # More aggressive Link Time Optimization
|
||||
opt-level = 3 # Optimize for speed
|
||||
codegen-units = 1 # Maximum size reduction optimizations
|
||||
panic = "abort" # Remove panic unwinding code
|
||||
debug = false # No debug symbols
|
||||
overflow-checks = false # Disable runtime integer overflow checks
|
||||
rpath = false # Don't embed runtime search path
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0 # No optimizations for faster compilation
|
||||
debug = true # Full debug info
|
||||
debug-assertions = true # Enable debug assertions
|
||||
overflow-checks = true # Enable runtime integer overflow checks
|
||||
lto = false # Disable link-time optimization
|
||||
incremental = true # Enable incremental compilation
|
||||
codegen-units = 256 # Use many codegen units for parallel compilation
|
||||
split-debuginfo = "unpacked" # Faster debug builds on some platforms
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 0 # No optimization for most dependencies
|
Binary file not shown.
Before Width: | Height: | Size: 603 KiB |
Binary file not shown.
Before Width: | Height: | Size: 213 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 281 KiB |
240
src/components/installer.rs
Normal file
240
src/components/installer.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
use std::{fs::create_dir_all, io::Write, path::Path};
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use reqwest::blocking::Client;
|
||||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const KURO_BASE_URL: &str = "https://prod-alicdn-gamestarter.kurogame.com/launcher/launcher/50004_obOHXFrFanqsaIEOmuKroCcbZkQRBC7c/G153";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct BackgroundFunctionCode {
|
||||
background: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct KuroVersionIndex {
|
||||
function_code: BackgroundFunctionCode
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct KuroBackgroundInformation {
|
||||
function_switch: i32,
|
||||
background_file: String,
|
||||
background_file_type: i32,
|
||||
first_frame_image: String,
|
||||
slogan: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct HoyoImageInformation {
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct HoyoDisplayInformation {
|
||||
name: String,
|
||||
icon: HoyoImageInformation,
|
||||
background: HoyoImageInformation
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct HoyoGameInformation {
|
||||
display: HoyoDisplayInformation
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct HoyoGameList {
|
||||
games: Vec<HoyoGameInformation>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
struct HoyoLauncherInformation {
|
||||
data: HoyoGameList
|
||||
}
|
||||
|
||||
pub fn refresh_install() -> Result<(), String> {
|
||||
let reqwest_client = build_client()?;
|
||||
let proj_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher")
|
||||
.ok_or_else(|| "Failed to get project directories".to_string())?;
|
||||
|
||||
if let Err(e) = refresh_kuro_install(&proj_dirs, &reqwest_client) {
|
||||
eprintln!("Error in Kuro install: {}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = refresh_hoyo_install(&proj_dirs, &reqwest_client) {
|
||||
eprintln!("Error in Hoyo install: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_client() -> Result<Client, String> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
|
||||
|
||||
Client::builder()
|
||||
.default_headers(headers)
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to build HTTP client: {}", e))
|
||||
}
|
||||
|
||||
fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(), String> {
|
||||
let data_dir_buf = proj_dirs.data_dir().join("kuro/wuwa");
|
||||
let data_dir = data_dir_buf.as_path();
|
||||
|
||||
create_dir_all(data_dir)
|
||||
.map_err(|e| format!("Failed to create directory: {}", e))?;
|
||||
|
||||
let kuro_url = KURO_BASE_URL.to_string();
|
||||
let index_url = format!("{}{}", kuro_url, "/index.json");
|
||||
|
||||
eprintln!("Fetching index from: {}", index_url);
|
||||
|
||||
let response = client
|
||||
.get(&index_url)
|
||||
.send()
|
||||
.map_err(|e| format!("Failed to send HTTP request: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("HTTP request failed with status {}", response.status()));
|
||||
}
|
||||
|
||||
eprintln!("Response headers: {:?}", response.headers());
|
||||
|
||||
let version_index: KuroVersionIndex = response
|
||||
.json()
|
||||
.map_err(|e| {
|
||||
format!("Failed to parse JSON - {}", e)
|
||||
})?;
|
||||
|
||||
eprintln!("Successfully parsed index.json: {:?}", version_index);
|
||||
|
||||
let bg_info_url = format!("{}{}{}{}",
|
||||
"https://prod-alicdn-gamestarter.kurogame.com/launcher/50004_obOHXFrFanqsaIEOmuKroCcbZkQRBC7c/G153",
|
||||
"/background/",
|
||||
version_index.function_code.background,
|
||||
"/en.json"
|
||||
);
|
||||
|
||||
eprintln!("Fetching background info from: {}", bg_info_url);
|
||||
|
||||
let bg_response = client
|
||||
.get(&bg_info_url)
|
||||
.send()
|
||||
.map_err(|e| format!("Failed to send background info request: {}", e))?;
|
||||
|
||||
if !bg_response.status().is_success() {
|
||||
return Err(format!("Background info request failed with status {}", bg_response.status()));
|
||||
}
|
||||
|
||||
let bg_information: KuroBackgroundInformation = bg_response
|
||||
.json()
|
||||
.map_err(|e| format!("Failed to parse background info JSON: {}", e))?;
|
||||
|
||||
eprintln!("Successfully parsed background info: {:?}", bg_information);
|
||||
|
||||
update_file_if_needed(data_dir, client, &bg_information.background_file, "background")?;
|
||||
update_file_if_needed(data_dir, client, &bg_information.slogan, "splash")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_file_if_needed(dir: &Path, client: &Client, file_url: &str, file_type: &str) -> Result<(), String> {
|
||||
let filename = extract_filename_from_url(file_url);
|
||||
let expected_file = format!("{}_{}", file_type, filename);
|
||||
let file_path = dir.join(&expected_file);
|
||||
|
||||
let file_exists = file_path.exists();
|
||||
|
||||
if !file_exists {
|
||||
eprintln!("Downloading {} file from: {}", file_type, file_url);
|
||||
let file_bytes = client
|
||||
.get(file_url)
|
||||
.send()
|
||||
.map_err(|e| format!("Failed to send request for {} file: {}", file_type, e))?
|
||||
.bytes()
|
||||
.map_err(|e| format!("Failed to get bytes for {} file: {}", file_type, e))?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&file_path)
|
||||
.map_err(|e| format!("Failed to create {} file ({}): {}", file_type, file_path.display(), e))?;
|
||||
|
||||
file.write_all(&file_bytes)
|
||||
.map_err(|e| format!("Failed to write {} file: {}", file_type, e))?;
|
||||
|
||||
file.flush()
|
||||
.map_err(|e| format!("Failed to flush {} file: {}", file_type, e))?;
|
||||
|
||||
eprintln!("Successfully downloaded {} file to: {}", file_type, file_path.display());
|
||||
} else {
|
||||
eprintln!("{} file already exists at: {}", file_type, file_path.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_hoyo_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(), String> {
|
||||
let hoyo_url = "https://sg-hyp-api.hoyoverse.com/hyp/hyp-connect/api/getGames?launcher_id=VYTpXlbWo8&language=en-us";
|
||||
|
||||
eprintln!("Fetching Hoyo launcher info from: {}", hoyo_url);
|
||||
|
||||
let response = client
|
||||
.get(hoyo_url)
|
||||
.send()
|
||||
.map_err(|e| format!("Failed to fetch Hoyo launcher info: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Hoyo launcher info request failed with status {}", response.status()));
|
||||
}
|
||||
|
||||
let hoyo_launcher_info: HoyoLauncherInformation = response
|
||||
.json()
|
||||
.map_err(|e| format!("Failed to parse Hoyo launcher info: {}", e))?;
|
||||
|
||||
eprintln!("Successfully parsed Hoyo launcher info");
|
||||
|
||||
for game in hoyo_launcher_info.data.games {
|
||||
let game_abbreviation = match game.display.name.as_str() {
|
||||
"Zenless Zone Zero" => "zzz",
|
||||
"Honkai Star Rail" => "hsr",
|
||||
"Genshin Impact" => "gi",
|
||||
_ => {
|
||||
eprintln!("Skipping unknown game: {}", game.display.name);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let data_dir_buf = proj_dirs.data_dir().join(format!("hoyoverse/{}", game_abbreviation));
|
||||
let data_dir = data_dir_buf.as_path();
|
||||
|
||||
create_dir_all(data_dir)
|
||||
.map_err(|e| format!("Failed to create directory for {}: {}", game_abbreviation, e))?;
|
||||
|
||||
eprintln!("Processing game: {} ({})", game.display.name, game_abbreviation);
|
||||
|
||||
let icon_url = &game.display.icon.url;
|
||||
update_file_if_needed(data_dir, client, icon_url, "icon")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_filename_from_url(url: &str) -> String {
|
||||
match url.split('/').next_back() {
|
||||
Some(filename) => String::from(filename),
|
||||
None => String::from("unknown_file"),
|
||||
}
|
||||
}
|
1
src/components/mod.rs
Normal file
1
src/components/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod installer;
|
151
src/main.rs
151
src/main.rs
|
@ -1,40 +1,38 @@
|
|||
// #![windows_subsystem = "windows"]
|
||||
#![windows_subsystem = "windows"]
|
||||
mod utils;
|
||||
mod components;
|
||||
|
||||
use file_format::FileFormat;
|
||||
use components::installer::refresh_install;
|
||||
use directories::ProjectDirs;
|
||||
use ::image::{DynamicImage, ImageReader};
|
||||
use iced::{
|
||||
alignment::Vertical::{Bottom, Top}, border, font::{self, Family, Stretch, Weight}, gradient, mouse::{self, Interaction}, wgpu::naga::back, widget::{button, center, column, container, image, mouse_area, opaque, row, stack, text, Column, Space}, window::{self, icon, Settings}, Alignment::Center, Color, Element, Font, Length::{self, Fill}, Padding, Point, Renderer, Size, Subscription, Task, Theme
|
||||
alignment::Vertical::{Bottom, Top}, border, event, font::{Stretch, Weight}, mouse::Interaction, widget::{button, column, container, image, mouse_area, opaque, row, stack, text, Space}, window::{self, icon, Settings}, Alignment::Center, Color, Element, Event, Font, Length, Padding, Size, Subscription, Task
|
||||
};
|
||||
use iced_video_player::{Video, VideoPlayer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
use utils::{img_utils::round_image, visual_helper::{get_game_background, get_game_icon_dynamic_image, style_container}};
|
||||
use utils::visual_helper::{get_game_background, get_game_icon_dynamic_image, style_container};
|
||||
use std::{
|
||||
collections::HashMap, env, fs::{self, create_dir_all, read_to_string}, io::{Cursor, Read, Write}, path::PathBuf, sync::Arc
|
||||
collections::HashMap, env, fs::{self, create_dir_all, read_to_string}, io::{Cursor, Write}, path::PathBuf
|
||||
};
|
||||
|
||||
trait InterfaceImage {
|
||||
fn into_handle(&self) -> image::Handle;
|
||||
fn into_handle(self) -> image::Handle;
|
||||
}
|
||||
|
||||
impl InterfaceImage for DynamicImage {
|
||||
fn into_handle(&self) -> image::Handle {
|
||||
fn into_handle(self) -> image::Handle {
|
||||
image::Handle::from_rgba(self.width(), self.height(), self.to_rgba8().into_raw())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(rust_embed::Embed)]
|
||||
#[folder = "resources"]
|
||||
struct Assets;
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
let segoe_assets = Assets::get("segoe-mdl2-assets.ttf").unwrap();
|
||||
let main_font = Assets::get("QuodlibetSans-Regular.ttf").unwrap();
|
||||
let icon_file = Assets::get("icon.png").unwrap();
|
||||
let segoe_assets = include_bytes!("../resources/segoe-mdl2-assets.ttf");
|
||||
let main_font = include_bytes!("../resources/QuodlibetSans-Regular.ttf");
|
||||
let icon_file = include_bytes!("../resources/icon.png");
|
||||
|
||||
let icon_image = ImageReader::new(Cursor::new(icon_file.data))
|
||||
let icon_image = ImageReader::new(Cursor::new(icon_file))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
|
@ -54,15 +52,16 @@ pub fn main() -> iced::Result {
|
|||
resizable: false,
|
||||
transparent: false,
|
||||
level: window::Level::Normal,
|
||||
exit_on_close_request: true,
|
||||
exit_on_close_request: false,
|
||||
..Settings::default()
|
||||
};
|
||||
|
||||
iced::application(Launcher::boot, Launcher::update, Launcher::view)
|
||||
.subscription(Launcher::subscription)
|
||||
.title(Launcher::title)
|
||||
.window(settings)
|
||||
.font(segoe_assets.data)
|
||||
.font(main_font.data)
|
||||
.font(segoe_assets)
|
||||
.font(main_font)
|
||||
.window_size((1280.0, 760.0))
|
||||
.run()
|
||||
}
|
||||
|
@ -78,7 +77,6 @@ enum PossibleGames {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum Launcher {
|
||||
Loading,
|
||||
Loaded(Box<State>),
|
||||
}
|
||||
|
||||
|
@ -119,7 +117,7 @@ impl State {
|
|||
fn create_game_icon(&self, game: &PossibleGames) -> Element<Message> {
|
||||
let icon = self.icon_images.get(game).unwrap();
|
||||
let img = if &self.selected_game == game {
|
||||
image(icon.into_handle())
|
||||
image(icon.clone().into_handle())
|
||||
.width(Length::Fixed(52.0))
|
||||
.height(Length::Fixed(68.0))
|
||||
} else {
|
||||
|
@ -131,7 +129,7 @@ impl State {
|
|||
mouse_area(
|
||||
img
|
||||
)
|
||||
.on_press(Message::GameSelected(game.clone()))
|
||||
// .on_press(Message::GameSelected(game.clone()))
|
||||
.interaction(Interaction::Pointer)
|
||||
.into()
|
||||
}
|
||||
|
@ -139,6 +137,7 @@ impl State {
|
|||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
selected_game: PossibleGames,
|
||||
installed_games: Vec<PossibleGames>,
|
||||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
|
@ -146,7 +145,7 @@ struct SavedState {
|
|||
|
||||
impl From<SavedState> for Box<State> {
|
||||
fn from(val: SavedState) -> Self {
|
||||
Box::new(State { installed_games: val.installed_games, installed_game_servers: val.installed_game_servers, db_software_installed: val.db_software_installed, ..State::default() })
|
||||
Box::new(State { selected_game: val.selected_game, installed_games: val.installed_games, installed_game_servers: val.installed_game_servers, db_software_installed: val.db_software_installed, ..State::default() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,67 +163,73 @@ enum SaveError {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Loaded(Result<SavedState, LoadError>),
|
||||
EventOccurred(Event),
|
||||
DragStarted(),
|
||||
GameSelected(PossibleGames),
|
||||
// GameSelected(PossibleGames),
|
||||
Close,
|
||||
Minimize
|
||||
}
|
||||
|
||||
impl State {
|
||||
// fn path() -> PathBuf {
|
||||
// path.push("launcher-state.json");
|
||||
fn path() -> PathBuf {
|
||||
let project_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher").unwrap();
|
||||
let path = project_dirs.config_dir();
|
||||
|
||||
// path
|
||||
// }
|
||||
path.join("launcher-state.json").to_path_buf()
|
||||
}
|
||||
|
||||
// fn load() -> Result<State, LoadError> {
|
||||
// let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?;
|
||||
fn load(self) -> Result<State, LoadError> {
|
||||
let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?;
|
||||
|
||||
// let saved_state: SavedState =
|
||||
// serde_json::from_str(&contents).map_err(|_| LoadError::Format)?;
|
||||
let saved_state: SavedState =
|
||||
serde_json::from_str(&contents).map_err(|_| LoadError::Format)?;
|
||||
|
||||
// Ok(State {
|
||||
// selected_game: PossibleGames::WutheringWaves,
|
||||
// installed_games: saved_state.installed_games,
|
||||
// installed_game_servers: saved_state.installed_game_servers,
|
||||
// db_software_installed: saved_state.db_software_installed,
|
||||
// })
|
||||
// }
|
||||
Ok(State {
|
||||
selected_game: saved_state.selected_game,
|
||||
installed_games: saved_state.installed_games,
|
||||
installed_game_servers: saved_state.installed_game_servers,
|
||||
db_software_installed: saved_state.db_software_installed,
|
||||
background: self.background,
|
||||
icon_images: self.icon_images
|
||||
})
|
||||
}
|
||||
|
||||
// async fn save(self) -> Result<(), SaveError> {
|
||||
// let saved_state = SavedState {
|
||||
// installed_games: self.installed_games,
|
||||
// installed_game_servers: self.installed_game_servers,
|
||||
// db_software_installed: self.db_software_installed,
|
||||
// };
|
||||
fn save(&mut self) -> Result<(), SaveError> {
|
||||
let saved_state = SavedState {
|
||||
selected_game: self.selected_game.clone(),
|
||||
installed_games: self.installed_games.clone(),
|
||||
installed_game_servers: self.installed_game_servers.clone(),
|
||||
db_software_installed: self.db_software_installed,
|
||||
};
|
||||
|
||||
// let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?;
|
||||
let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?;
|
||||
|
||||
// let path = Self::path();
|
||||
let path = Self::path();
|
||||
|
||||
// if let Some(dir) = path.parent() {
|
||||
// create_dir_all(dir).map_err(|_| SaveError::Write)?;
|
||||
// }
|
||||
if let Some(dir) = path.parent() {
|
||||
create_dir_all(dir).map_err(|_| SaveError::Write)?;
|
||||
}
|
||||
|
||||
// {
|
||||
// fs::write(path, json.as_bytes()).map_err(|_| SaveError::Write)?;
|
||||
// }
|
||||
{
|
||||
let mut file = fs::File::open(path).unwrap();
|
||||
file.write_all(json.as_bytes()).map_err(|_| SaveError::Write)?;
|
||||
file.flush().map_err(|_| SaveError::Write)?;
|
||||
}
|
||||
|
||||
// sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Launcher {
|
||||
fn boot() -> (Self, Task<Message>) {
|
||||
let launcher_bg = get_game_background(&State::default());
|
||||
refresh_install().unwrap();
|
||||
let launcher_bg = get_game_background(&PossibleGames::default());
|
||||
let mut icons = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let icon = get_game_icon_dynamic_image(&game);
|
||||
icons.insert(game, icon);
|
||||
}
|
||||
|
||||
let final_state = State {
|
||||
background: Some(launcher_bg),
|
||||
icon_images: icons,
|
||||
|
@ -239,14 +244,7 @@ impl Launcher {
|
|||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match self {
|
||||
Launcher::Loading => match message {
|
||||
Message::Loaded(Ok(save_state)) => {
|
||||
*self = Launcher::Loaded(save_state.into());
|
||||
Task::none()
|
||||
},
|
||||
_ => Task::none(),
|
||||
},
|
||||
Launcher::Loaded(_) => {
|
||||
Launcher::Loaded(state) => {
|
||||
match message {
|
||||
Message::DragStarted() => {
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
|
@ -262,6 +260,14 @@ impl Launcher {
|
|||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::minimize(id, true)
|
||||
})
|
||||
},
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
state.save().unwrap();
|
||||
window::get_latest().and_then(window::close)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
_ => Task::none()
|
||||
}
|
||||
|
@ -269,6 +275,15 @@ impl Launcher {
|
|||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
event::listen_with(|event, _status, _| {
|
||||
match event {
|
||||
Event::Window(window::Event::CloseRequested) => Some(Message::EventOccurred(event)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let mut font = Font::with_name("Quodlibet Sans");
|
||||
font.weight = Weight::Normal;
|
||||
|
@ -279,7 +294,6 @@ impl Launcher {
|
|||
bolded_font.stretch = Stretch::Normal;
|
||||
println!("Rerender triggered!");
|
||||
match self {
|
||||
Launcher::Loading => center(text("Loading...").size(50)).into(),
|
||||
Launcher::Loaded(state) => {
|
||||
let top_bar = stack![
|
||||
mouse_area(container(
|
||||
|
@ -295,7 +309,7 @@ impl Launcher {
|
|||
state.get_game_icon_row(),
|
||||
]
|
||||
)
|
||||
.padding(Padding { top: 5.0, right: 10.0, bottom: 5.0, left: 10.0 })
|
||||
.padding(Padding { top: 5.0, right: 10.0, bottom: 5.0, left: 5.0 })
|
||||
.align_y(Top)
|
||||
.align_x(Center)
|
||||
.width(Length::Fill)
|
||||
|
@ -341,4 +355,3 @@ impl Launcher {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
use std::{io::{Cursor, Write}, sync::Arc};
|
||||
use std::{fs::DirEntry, io::{Cursor, Write}, sync::Arc};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use file_format::FileFormat;
|
||||
use iced_video_player::Video;
|
||||
use ::image::{DynamicImage, ImageReader};
|
||||
use iced::{gradient, widget::container, Color};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use crate::{Assets, LauncherBackground, PossibleGames, State};
|
||||
use crate::{LauncherBackground, PossibleGames};
|
||||
|
||||
use super::img_utils::round_image;
|
||||
|
||||
pub fn get_game_background(state: &State) -> LauncherBackground {
|
||||
let file_path: &str = match state.selected_game {
|
||||
PossibleGames::WutheringWaves => "wutheringwaves-bg.mp4",
|
||||
PossibleGames::ZenlessZoneZero => "zenlesszonezero-bg.png",
|
||||
PossibleGames::HonkaiStarRail => "honkaistarrail-bg.png",
|
||||
PossibleGames::GenshinImpact => "genshinimpact-bg.png",
|
||||
};
|
||||
|
||||
if let Some(file) = Assets::get(file_path) {
|
||||
let data = Arc::new(file.data);
|
||||
pub fn get_game_background(game: &PossibleGames) -> LauncherBackground {
|
||||
let proj_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher").unwrap();
|
||||
let background_bytes = get_background_file(&proj_dirs, game).unwrap();
|
||||
let data = Arc::new(background_bytes);
|
||||
let file_format = FileFormat::from_bytes(&*data);
|
||||
|
||||
if file_format.extension() == "mp4" {
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
temp_file.write_all(&data).unwrap();
|
||||
|
@ -43,21 +39,50 @@ pub fn get_game_background(state: &State) -> LauncherBackground {
|
|||
.unwrap();
|
||||
LauncherBackground::Image(img)
|
||||
}
|
||||
|
||||
} else {
|
||||
panic!("Missing icon for {:?}, path: {}", state.selected_game, file_path)
|
||||
}
|
||||
|
||||
fn get_background_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Result<Vec<u8>, std::io::Error> {
|
||||
let game_dir = match game {
|
||||
PossibleGames::WutheringWaves => proj_dirs.data_dir().join("kuro/wuwa"),
|
||||
PossibleGames::ZenlessZoneZero => proj_dirs.data_dir().join("zzz"),
|
||||
PossibleGames::HonkaiStarRail => proj_dirs.data_dir().join("hsr"),
|
||||
PossibleGames::GenshinImpact => proj_dirs.data_dir().join("gi"),
|
||||
};
|
||||
|
||||
if !game_dir.exists() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Game directory does not exist: {:?}", game_dir)
|
||||
));
|
||||
}
|
||||
|
||||
let entries = std::fs::read_dir(&game_dir)?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let file_name = entry.file_name().into_string().unwrap_or_default();
|
||||
|
||||
if file_name.starts_with("background_") {
|
||||
return std::fs::read(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("No background file found in {:?}", game_dir)
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_game_icon_dynamic_image(game: &PossibleGames) -> DynamicImage {
|
||||
let file_path: &str = match game {
|
||||
PossibleGames::WutheringWaves => "wutheringwaves-icon.png",
|
||||
PossibleGames::ZenlessZoneZero => "zenlesszonezero-icon.png",
|
||||
PossibleGames::HonkaiStarRail => "honkaistarrail-icon.png",
|
||||
PossibleGames::GenshinImpact => "genshinimpact-icon.png",
|
||||
let proj_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher").unwrap();
|
||||
let file_data: &[u8] = match game {
|
||||
PossibleGames::WutheringWaves => include_bytes!("../../resources/wutheringwaves-icon.png"),
|
||||
PossibleGames::ZenlessZoneZero => &get_hoyo_game_icon_file(&proj_dirs, game),
|
||||
PossibleGames::HonkaiStarRail => &get_hoyo_game_icon_file(&proj_dirs, game),
|
||||
PossibleGames::GenshinImpact => &get_hoyo_game_icon_file(&proj_dirs, game),
|
||||
};
|
||||
if let Some(img_file) = Assets::get(file_path) {
|
||||
let data_cursor = Cursor::new(img_file.data);
|
||||
|
||||
let data_cursor = Cursor::new(file_data);
|
||||
|
||||
let img = ImageReader::new(data_cursor)
|
||||
.with_guessed_format()
|
||||
|
@ -68,9 +93,29 @@ pub fn get_game_icon_dynamic_image(game: &PossibleGames) -> DynamicImage {
|
|||
|
||||
round_image(img)
|
||||
.unwrap()
|
||||
} else {
|
||||
panic!("Missing icon for {:?}, path: {}", game, file_path)
|
||||
}
|
||||
|
||||
fn get_hoyo_game_icon_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Vec<u8> {
|
||||
let game_abbrevation = match game {
|
||||
PossibleGames::ZenlessZoneZero => "zzz",
|
||||
PossibleGames::HonkaiStarRail => "hsr",
|
||||
PossibleGames::GenshinImpact => "gi",
|
||||
_ => panic!("Wuwa inputted in hoyo only func")
|
||||
};
|
||||
|
||||
let data_dir = proj_dirs.data_dir().join(format!("hoyoverse/{}", game_abbrevation));
|
||||
let icon: Option<DirEntry> = {
|
||||
let mut icon = None;
|
||||
for path in data_dir.read_dir().unwrap() {
|
||||
let path = path.unwrap();
|
||||
if path.file_name().into_string().unwrap().starts_with("icon_") {
|
||||
icon = Some(path);
|
||||
}
|
||||
}
|
||||
icon
|
||||
};
|
||||
|
||||
std::fs::read(icon.expect("installation went wrong.").path()).unwrap()
|
||||
}
|
||||
|
||||
fn rad(deg: f32) -> f32 {
|
||||
|
|
Loading…
Reference in a new issue