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)]
[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
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 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)
@ -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 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",
};
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 let Some(file) = Assets::get(file_path) {
let data = Arc::new(file.data);
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();
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();
match Video::new(url::Url::from_file_path(temp_path).unwrap()) {
Ok(mut video) => {
video.set_looping(true);
LauncherBackground::Video(video)
},
Err(err) => {
panic!("{:#?}", err)
},
}
} else {
let img = ImageReader::new(Cursor::new(&*data))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
LauncherBackground::Image(img)
let temp_path = temp_file.path().to_str().unwrap().to_string();
match Video::new(url::Url::from_file_path(temp_path).unwrap()) {
Ok(mut video) => {
video.set_looping(true);
LauncherBackground::Video(video)
},
Err(err) => {
panic!("{:#?}", err)
},
}
} 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 {
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",
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 let Some(img_file) = Assets::get(file_path) {
let data_cursor = Cursor::new(img_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()
} else {
panic!("Missing icon for {:?}, path: {}", game, file_path)
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 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 {