0.3.0: remove rust-embed. the shackles have been broken.

This commit is contained in:
RabbyDevs 2025-05-04 03:42:53 +03:00
parent 7bfb71fff3
commit 647599d2bc
11 changed files with 1078 additions and 235 deletions

737
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
#![feature(let_chains)] #![feature(let_chains)]
[package] [package]
name = "reversed-rooms-launcher" name = "reversed-rooms-launcher"
version = "0.2.0" version = "0.3.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@ -13,13 +13,30 @@ serde_json = "1.0.140"
tempfile = "3.19.1" tempfile = "3.19.1"
iced_video_player = {path = "./iced_video_player"} iced_video_player = {path = "./iced_video_player"}
url = "2.5.4" url = "2.5.4"
rust-embed = "8.7.0"
file-format = "0.26.0" file-format = "0.26.0"
strum = "0.27.1" strum = "0.27.1"
strum_macros = "0.27.1" strum_macros = "0.27.1"
reqwest = { version = "0.12.15", features = ["blocking", "gzip", "json"] }
[profile.release] [profile.release]
strip = true # Automatically strip symbols from the binary. strip = true # Automatically strip symbols from the binary
lto = true # Link-time optimization. lto = "fat" # More aggressive Link Time Optimization
opt-level = 3 # Optimize for speed. opt-level = 3 # Optimize for speed
codegen-units = 1 # Maximum size reduction optimizations. 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
View 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
View file

@ -0,0 +1 @@
pub mod installer;

View file

@ -1,40 +1,38 @@
// #![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
mod utils; mod utils;
mod components;
use file_format::FileFormat; use components::installer::refresh_install;
use directories::ProjectDirs;
use ::image::{DynamicImage, ImageReader}; use ::image::{DynamicImage, ImageReader};
use iced::{ 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 iced_video_player::{Video, VideoPlayer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; 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::{ 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 { trait InterfaceImage {
fn into_handle(&self) -> image::Handle; fn into_handle(self) -> image::Handle;
} }
impl InterfaceImage for DynamicImage { 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()) 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 { pub fn main() -> iced::Result {
let segoe_assets = Assets::get("segoe-mdl2-assets.ttf").unwrap(); let segoe_assets = include_bytes!("../resources/segoe-mdl2-assets.ttf");
let main_font = Assets::get("QuodlibetSans-Regular.ttf").unwrap(); let main_font = include_bytes!("../resources/QuodlibetSans-Regular.ttf");
let icon_file = Assets::get("icon.png").unwrap(); 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() .with_guessed_format()
.unwrap() .unwrap()
.decode() .decode()
@ -54,15 +52,16 @@ pub fn main() -> iced::Result {
resizable: false, resizable: false,
transparent: false, transparent: false,
level: window::Level::Normal, level: window::Level::Normal,
exit_on_close_request: true, exit_on_close_request: false,
..Settings::default() ..Settings::default()
}; };
iced::application(Launcher::boot, Launcher::update, Launcher::view) iced::application(Launcher::boot, Launcher::update, Launcher::view)
.subscription(Launcher::subscription)
.title(Launcher::title) .title(Launcher::title)
.window(settings) .window(settings)
.font(segoe_assets.data) .font(segoe_assets)
.font(main_font.data) .font(main_font)
.window_size((1280.0, 760.0)) .window_size((1280.0, 760.0))
.run() .run()
} }
@ -78,7 +77,6 @@ enum PossibleGames {
#[derive(Debug)] #[derive(Debug)]
enum Launcher { enum Launcher {
Loading,
Loaded(Box<State>), Loaded(Box<State>),
} }
@ -119,7 +117,7 @@ impl State {
fn create_game_icon(&self, game: &PossibleGames) -> Element<Message> { fn create_game_icon(&self, game: &PossibleGames) -> Element<Message> {
let icon = self.icon_images.get(game).unwrap(); let icon = self.icon_images.get(game).unwrap();
let img = if &self.selected_game == game { let img = if &self.selected_game == game {
image(icon.into_handle()) image(icon.clone().into_handle())
.width(Length::Fixed(52.0)) .width(Length::Fixed(52.0))
.height(Length::Fixed(68.0)) .height(Length::Fixed(68.0))
} else { } else {
@ -131,7 +129,7 @@ impl State {
mouse_area( mouse_area(
img img
) )
.on_press(Message::GameSelected(game.clone())) // .on_press(Message::GameSelected(game.clone()))
.interaction(Interaction::Pointer) .interaction(Interaction::Pointer)
.into() .into()
} }
@ -139,6 +137,7 @@ impl State {
#[derive(Debug, Default, Clone, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
struct SavedState { struct SavedState {
selected_game: PossibleGames,
installed_games: Vec<PossibleGames>, installed_games: Vec<PossibleGames>,
installed_game_servers: Vec<PossibleGames>, installed_game_servers: Vec<PossibleGames>,
db_software_installed: bool, db_software_installed: bool,
@ -146,7 +145,7 @@ struct SavedState {
impl From<SavedState> for Box<State> { impl From<SavedState> for Box<State> {
fn from(val: SavedState) -> Self { 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)] #[derive(Debug, Clone)]
enum Message { enum Message {
Loaded(Result<SavedState, LoadError>), EventOccurred(Event),
DragStarted(), DragStarted(),
GameSelected(PossibleGames), // GameSelected(PossibleGames),
Close, Close,
Minimize Minimize
} }
impl State { impl State {
// fn path() -> PathBuf { fn path() -> PathBuf {
// path.push("launcher-state.json"); 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> { fn load(self) -> Result<State, LoadError> {
// let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?; let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?;
// let saved_state: SavedState = let saved_state: SavedState =
// serde_json::from_str(&contents).map_err(|_| LoadError::Format)?; serde_json::from_str(&contents).map_err(|_| LoadError::Format)?;
// Ok(State { Ok(State {
// selected_game: PossibleGames::WutheringWaves, selected_game: saved_state.selected_game,
// installed_games: saved_state.installed_games, installed_games: saved_state.installed_games,
// installed_game_servers: saved_state.installed_game_servers, installed_game_servers: saved_state.installed_game_servers,
// db_software_installed: saved_state.db_software_installed, db_software_installed: saved_state.db_software_installed,
// }) background: self.background,
// } icon_images: self.icon_images
})
}
// async fn save(self) -> Result<(), SaveError> { fn save(&mut self) -> Result<(), SaveError> {
// let saved_state = SavedState { let saved_state = SavedState {
// installed_games: self.installed_games, selected_game: self.selected_game.clone(),
// installed_game_servers: self.installed_game_servers, installed_games: self.installed_games.clone(),
// db_software_installed: self.db_software_installed, 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() { if let Some(dir) = path.parent() {
// create_dir_all(dir).map_err(|_| SaveError::Write)?; 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 { impl Launcher {
fn boot() -> (Self, Task<Message>) { 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(); let mut icons = HashMap::new();
for game in PossibleGames::iter() { for game in PossibleGames::iter() {
let icon = get_game_icon_dynamic_image(&game); let icon = get_game_icon_dynamic_image(&game);
icons.insert(game, icon); icons.insert(game, icon);
} }
let final_state = State { let final_state = State {
background: Some(launcher_bg), background: Some(launcher_bg),
icon_images: icons, icon_images: icons,
@ -239,14 +244,7 @@ impl Launcher {
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match self { match self {
Launcher::Loading => match message { Launcher::Loaded(state) => {
Message::Loaded(Ok(save_state)) => {
*self = Launcher::Loaded(save_state.into());
Task::none()
},
_ => Task::none(),
},
Launcher::Loaded(_) => {
match message { match message {
Message::DragStarted() => { Message::DragStarted() => {
window::get_latest().and_then(move |id: window::Id| { 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::get_latest().and_then(move |id: window::Id| {
window::minimize(id, true) 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() _ => 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> { fn view(&self) -> Element<Message> {
let mut font = Font::with_name("Quodlibet Sans"); let mut font = Font::with_name("Quodlibet Sans");
font.weight = Weight::Normal; font.weight = Weight::Normal;
@ -279,7 +294,6 @@ impl Launcher {
bolded_font.stretch = Stretch::Normal; bolded_font.stretch = Stretch::Normal;
println!("Rerender triggered!"); println!("Rerender triggered!");
match self { match self {
Launcher::Loading => center(text("Loading...").size(50)).into(),
Launcher::Loaded(state) => { Launcher::Loaded(state) => {
let top_bar = stack![ let top_bar = stack![
mouse_area(container( mouse_area(container(
@ -295,7 +309,7 @@ impl Launcher {
state.get_game_icon_row(), 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_y(Top)
.align_x(Center) .align_x(Center)
.width(Length::Fill) .width(Length::Fill)
@ -340,5 +354,4 @@ impl Launcher {
} }
} }
} }
} }

View file

@ -1,76 +1,121 @@
use std::{io::{Cursor, Write}, sync::Arc}; use std::{fs::DirEntry, io::{Cursor, Write}, sync::Arc};
use directories::ProjectDirs;
use file_format::FileFormat; use file_format::FileFormat;
use iced_video_player::Video; use iced_video_player::Video;
use ::image::{DynamicImage, ImageReader}; use ::image::{DynamicImage, ImageReader};
use iced::{gradient, widget::container, Color}; use iced::{gradient, widget::container, Color};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::{Assets, LauncherBackground, PossibleGames, State}; use crate::{LauncherBackground, PossibleGames};
use super::img_utils::round_image; use super::img_utils::round_image;
pub fn get_game_background(state: &State) -> LauncherBackground { pub fn get_game_background(game: &PossibleGames) -> LauncherBackground {
let file_path: &str = match state.selected_game { let proj_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher").unwrap();
PossibleGames::WutheringWaves => "wutheringwaves-bg.mp4", let background_bytes = get_background_file(&proj_dirs, game).unwrap();
PossibleGames::ZenlessZoneZero => "zenlesszonezero-bg.png", let data = Arc::new(background_bytes);
PossibleGames::HonkaiStarRail => "honkaistarrail-bg.png", let file_format = FileFormat::from_bytes(&*data);
PossibleGames::GenshinImpact => "genshinimpact-bg.png",
};
if let Some(file) = Assets::get(file_path) { if file_format.extension() == "mp4" {
let data = Arc::new(file.data); let mut temp_file = NamedTempFile::new().unwrap();
let file_format = FileFormat::from_bytes(&*data); temp_file.write_all(&data).unwrap();
if file_format.extension() == "mp4" {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(&data).unwrap();
let temp_path = temp_file.path().to_str().unwrap().to_string(); let temp_path = temp_file.path().to_str().unwrap().to_string();
match Video::new(url::Url::from_file_path(temp_path).unwrap()) { match Video::new(url::Url::from_file_path(temp_path).unwrap()) {
Ok(mut video) => { Ok(mut video) => {
video.set_looping(true); video.set_looping(true);
LauncherBackground::Video(video) LauncherBackground::Video(video)
}, },
Err(err) => { Err(err) => {
panic!("{:#?}", err) panic!("{:#?}", err)
}, },
}
} else {
let img = ImageReader::new(Cursor::new(&*data))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
LauncherBackground::Image(img)
} }
} else { } else {
panic!("Missing icon for {:?}, path: {}", state.selected_game, file_path) let img = ImageReader::new(Cursor::new(&*data))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
LauncherBackground::Image(img)
} }
} }
pub fn get_game_icon_dynamic_image(game: &PossibleGames) -> DynamicImage { fn get_background_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Result<Vec<u8>, std::io::Error> {
let file_path: &str = match game { let game_dir = match game {
PossibleGames::WutheringWaves => "wutheringwaves-icon.png", PossibleGames::WutheringWaves => proj_dirs.data_dir().join("kuro/wuwa"),
PossibleGames::ZenlessZoneZero => "zenlesszonezero-icon.png", PossibleGames::ZenlessZoneZero => proj_dirs.data_dir().join("zzz"),
PossibleGames::HonkaiStarRail => "honkaistarrail-icon.png", PossibleGames::HonkaiStarRail => proj_dirs.data_dir().join("hsr"),
PossibleGames::GenshinImpact => "genshinimpact-icon.png", PossibleGames::GenshinImpact => proj_dirs.data_dir().join("gi"),
}; };
if let Some(img_file) = Assets::get(file_path) {
let data_cursor = Cursor::new(img_file.data); if !game_dir.exists() {
return Err(std::io::Error::new(
let img = ImageReader::new(data_cursor) std::io::ErrorKind::NotFound,
.with_guessed_format() format!("Game directory does not exist: {:?}", game_dir)
.unwrap() ));
.decode()
.unwrap()
.resize(128, 128, ::image::imageops::FilterType::Lanczos3);
round_image(img)
.unwrap()
} else {
panic!("Missing icon for {:?}, path: {}", game, file_path)
} }
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 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),
};
let data_cursor = Cursor::new(file_data);
let img = ImageReader::new(data_cursor)
.with_guessed_format()
.unwrap()
.decode()
.unwrap()
.resize(128, 128, ::image::imageops::FilterType::Lanczos3);
round_image(img)
.unwrap()
}
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 { fn rad(deg: f32) -> f32 {