0.4.0: Add game switching, implement proper error handling thru out installer, and splash screen, probably more.
This commit is contained in:
parent
898f32f38c
commit
5239d2cd98
6 changed files with 238 additions and 97 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -3155,7 +3155,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "reversed-rooms-launcher"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"file-format",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![feature(let_chains)]
|
||||
[package]
|
||||
name = "reversed-rooms-launcher"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
@ -22,4 +22,4 @@ reqwest = { version = "0.12.15", features = ["blocking", "gzip", "json"] }
|
|||
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.
|
||||
codegen-units = 1 # Maximum size reduction optimizations.
|
||||
|
|
|
@ -469,8 +469,8 @@ impl Video {
|
|||
}
|
||||
|
||||
/// Get the size/resolution of the video as `(width, height)`.
|
||||
pub fn size(&self) -> (i32, i32) {
|
||||
(self.read().width, self.read().height)
|
||||
pub fn size(&self) -> (u32, u32) {
|
||||
(self.read().width.try_into().unwrap(), self.read().height.try_into().unwrap())
|
||||
}
|
||||
|
||||
/// Get the framerate of the video as frames per second.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{fs::create_dir_all, io::Write, path::Path};
|
||||
use std::fs::OpenOptions;
|
||||
use std::fs::{DirEntry, OpenOptions};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use reqwest::blocking::Client;
|
||||
|
@ -150,42 +151,61 @@ fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(),
|
|||
}
|
||||
|
||||
fn update_file_if_needed(dir: &Path, client: &Client, file_url: &str, file_type: &str) -> Result<(), String> {
|
||||
let current_file: Option<DirEntry> = {
|
||||
let mut current_file = None;
|
||||
for path in dir.read_dir().unwrap() {
|
||||
let path = path.unwrap();
|
||||
if path.file_name().into_string().unwrap().starts_with(format!("{}_", file_type).as_str()) {
|
||||
current_file = Some(path);
|
||||
}
|
||||
}
|
||||
current_file
|
||||
};
|
||||
|
||||
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());
|
||||
|
||||
if let Some(file) = current_file {
|
||||
if filename != file.file_name().into_string().unwrap().strip_prefix(format!("{}_", file_type).as_str()).unwrap() {
|
||||
update_file(client, file_path, file_type, file_url)?;
|
||||
} else {
|
||||
eprintln!("{} file already exists at: {}", file_type, file.path().display());
|
||||
}
|
||||
} else {
|
||||
eprintln!("{} file already exists at: {}", file_type, file_path.display());
|
||||
update_file(client, file_path, file_type, file_url)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_file(client: &Client, file_path: PathBuf, file_type: &str, file_url: &str) -> Result<(), String> {
|
||||
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());
|
||||
|
||||
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";
|
||||
|
||||
|
@ -227,6 +247,9 @@ fn refresh_hoyo_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(),
|
|||
|
||||
let icon_url = &game.display.icon.url;
|
||||
update_file_if_needed(data_dir, client, icon_url, "icon")?;
|
||||
|
||||
let bg_url = &game.display.background.url;
|
||||
update_file_if_needed(data_dir, client, bg_url, "background")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
203
src/main.rs
203
src/main.rs
|
@ -1,5 +1,4 @@
|
|||
// TODO: fix loading the save data lol
|
||||
#![windows_subsystem = "windows"]
|
||||
// #![windows_subsystem = "windows"]
|
||||
mod utils;
|
||||
mod components;
|
||||
|
||||
|
@ -7,13 +6,13 @@ use components::installer::refresh_install;
|
|||
use directories::ProjectDirs;
|
||||
use ::image::{DynamicImage, ImageReader};
|
||||
use iced::{
|
||||
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
|
||||
alignment::Vertical::{Bottom, Top}, border, event, font::{Stretch, Weight}, mouse::Interaction, widget::{button, center, 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::visual_helper::{get_game_background, get_game_icon_dynamic_image, style_container};
|
||||
use utils::visual_helper::{get_game_background, get_game_icon_dynamic_image, get_game_splash_dynamic_image, style_container};
|
||||
use std::{
|
||||
collections::HashMap, env, fs::{self, create_dir_all, read_to_string}, io::{Cursor, Write}, path::PathBuf
|
||||
};
|
||||
|
@ -77,6 +76,21 @@ enum PossibleGames {
|
|||
GenshinImpact,
|
||||
}
|
||||
|
||||
trait IdentifibleGameType {
|
||||
fn get_game_preferred_size(&self) -> (u32, u32);
|
||||
}
|
||||
|
||||
impl IdentifibleGameType for PossibleGames {
|
||||
fn get_game_preferred_size(&self) -> (u32, u32) {
|
||||
match self {
|
||||
PossibleGames::WutheringWaves => (1280, 760),
|
||||
PossibleGames::HonkaiStarRail => (1280, 720),
|
||||
PossibleGames::ZenlessZoneZero => (1280, 720),
|
||||
PossibleGames::GenshinImpact => (1280, 720),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Launcher {
|
||||
Loaded(Box<State>),
|
||||
|
@ -95,17 +109,21 @@ struct State {
|
|||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
background: Option<LauncherBackground>,
|
||||
splash_images: HashMap<PossibleGames, DynamicImage>,
|
||||
icon_images: HashMap<PossibleGames, DynamicImage>
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn get_background_element(&self) -> Element<Message> {
|
||||
match self.background.as_ref().unwrap() {
|
||||
LauncherBackground::Video(video) => VideoPlayer::new(video).into(),
|
||||
LauncherBackground::Image(img) => image(image::Handle::from_rgba(img.width(), img.height(), img.to_rgba8().into_raw())).into(),
|
||||
fn get_background_element(&self) -> Option<Element<Message>> {
|
||||
if let Some(background) = self.background.as_ref() {
|
||||
match background {
|
||||
LauncherBackground::Video(video) => Some(VideoPlayer::new(video).into()),
|
||||
LauncherBackground::Image(img) => Some(image(image::Handle::from_rgba(img.width(), img.height(), img.to_rgba8().into_raw())).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_game_icon_row(&self) -> Element<Message> {
|
||||
container(row![
|
||||
self.create_game_icon(&PossibleGames::WutheringWaves),
|
||||
|
@ -117,23 +135,36 @@ impl State {
|
|||
).align_x(Center).width(Length::Fill).into()
|
||||
}
|
||||
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.clone().into_handle())
|
||||
.width(Length::Fixed(52.0))
|
||||
.height(Length::Fixed(68.0))
|
||||
if let Some(icon) = self.icon_images.get(game) {
|
||||
let img = if &self.selected_game == game {
|
||||
image(icon.clone().into_handle())
|
||||
.width(Length::Fixed(52.0))
|
||||
.height(Length::Fixed(68.0))
|
||||
} else {
|
||||
image(icon.brighten(-85).blur(2.5).adjust_contrast(-15.0).into_handle())
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(64.0))
|
||||
};
|
||||
|
||||
mouse_area(
|
||||
img
|
||||
)
|
||||
.on_press(Message::GameSelected(game.clone()))
|
||||
.interaction(Interaction::Pointer)
|
||||
.into()
|
||||
} else {
|
||||
image(icon.brighten(-85).blur(2.5).adjust_contrast(-15.0).into_handle())
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(64.0))
|
||||
};
|
||||
|
||||
mouse_area(
|
||||
img
|
||||
)
|
||||
// .on_press(Message::GameSelected(game.clone()))
|
||||
.interaction(Interaction::Pointer)
|
||||
.into()
|
||||
text("loading").size(10).into()
|
||||
}
|
||||
}
|
||||
fn get_splash(&self) -> Option<Element<Message>> {
|
||||
if let Some(splash) = self.splash_images.get(&self.selected_game) {
|
||||
let preferred_size = self.selected_game.get_game_preferred_size();
|
||||
Some(image(splash.clone().into_handle())
|
||||
.width(Length::Fixed(preferred_size.0 as f32))
|
||||
.height(Length::Fixed(preferred_size.1 as f32)).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +184,6 @@ impl From<SavedState> for Box<State> {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
enum LoadError {
|
||||
File,
|
||||
Format,
|
||||
}
|
||||
|
||||
|
@ -166,8 +196,12 @@ enum SaveError {
|
|||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
EventOccurred(Event),
|
||||
DragStarted(),
|
||||
// GameSelected(PossibleGames),
|
||||
DragWindow,
|
||||
LoadIcons(HashMap<PossibleGames, DynamicImage>),
|
||||
LoadSplashes(HashMap<PossibleGames, DynamicImage>),
|
||||
LoadBackground(()),
|
||||
RefreshInstall(()),
|
||||
GameSelected(PossibleGames),
|
||||
Close,
|
||||
Minimize
|
||||
}
|
||||
|
@ -181,19 +215,21 @@ impl State {
|
|||
}
|
||||
|
||||
fn load(self) -> Result<State, LoadError> {
|
||||
let contents = read_to_string(Self::path()).map_err(|_| LoadError::File)?;
|
||||
if let Ok(contents) = read_to_string(Self::path()) {
|
||||
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: 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
|
||||
})
|
||||
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,
|
||||
splash_images: self.splash_images,
|
||||
icon_images: self.icon_images,
|
||||
})
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn save(&mut self) -> Result<(), SaveError> {
|
||||
|
@ -222,22 +258,29 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_icons() -> HashMap<PossibleGames, DynamicImage> {
|
||||
let mut icons: HashMap<PossibleGames, DynamicImage> = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let icon = get_game_icon_dynamic_image(&game);
|
||||
icons.insert(game, icon);
|
||||
};
|
||||
icons
|
||||
}
|
||||
|
||||
async fn get_splashes() -> HashMap<PossibleGames, DynamicImage> {
|
||||
let mut splashes: HashMap<PossibleGames, DynamicImage> = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let splash = get_game_splash_dynamic_image(&game);
|
||||
if let Some(splash) = splash {splashes.insert(game, splash)} else {continue};
|
||||
};
|
||||
splashes
|
||||
}
|
||||
|
||||
async fn empty() {}
|
||||
|
||||
impl Launcher {
|
||||
fn boot() -> (Self, Task<Message>) {
|
||||
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,
|
||||
..State::default()
|
||||
};
|
||||
(Self::Loaded(Box::new(final_state)), Task::none())
|
||||
(Self::Loaded(Box::new(State::default().load().unwrap())), Task::batch([Task::perform(empty(), Message::LoadBackground), Task::perform(get_icons(), Message::LoadIcons), Task::perform(get_splashes(), Message::LoadSplashes), Task::perform(empty(), Message::RefreshInstall)]))
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
|
@ -248,7 +291,7 @@ impl Launcher {
|
|||
match self {
|
||||
Launcher::Loaded(state) => {
|
||||
match message {
|
||||
Message::DragStarted() => {
|
||||
Message::DragWindow => {
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::drag(id)
|
||||
})
|
||||
|
@ -270,8 +313,34 @@ impl Launcher {
|
|||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
_ => Task::none()
|
||||
},
|
||||
Message::LoadSplashes(splashes) => {
|
||||
state.splash_images = splashes;
|
||||
Task::none()
|
||||
},
|
||||
Message::LoadBackground(()) => {
|
||||
state.background = Some(get_game_background(&PossibleGames::default()));
|
||||
let (width, height) = state.selected_game.get_game_preferred_size();
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::resize(id, Size { width: width as f32, height: height as f32 })
|
||||
})
|
||||
},
|
||||
Message::LoadIcons(icons) => {
|
||||
state.icon_images = icons;
|
||||
Task::none()
|
||||
},
|
||||
Message::RefreshInstall(_result) => {
|
||||
refresh_install().unwrap();
|
||||
Task::none()
|
||||
},
|
||||
Message::GameSelected(game) => {
|
||||
state.background = Some(get_game_background(&game));
|
||||
state.selected_game = game;
|
||||
let (width, height) = state.selected_game.get_game_preferred_size();
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::resize(id, Size { width: width as f32, height: height as f32 })
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +390,7 @@ impl Launcher {
|
|||
background: Some(Color::from_rgba8(0, 0, 0, 0.75).into()),
|
||||
..container::Style::default()
|
||||
}))
|
||||
.on_press(Message::DragStarted())
|
||||
.on_press(Message::DragWindow)
|
||||
.interaction(Interaction::Grab)
|
||||
];
|
||||
|
||||
|
@ -329,7 +398,7 @@ impl Launcher {
|
|||
text("The quick brown fox jumped over the lazy dog.").size(25).font(font),
|
||||
Space::new(Length::Fill, Length::Fixed(0.0)),
|
||||
opaque(mouse_area(button(text("Start").size(25).font(bolded_font).align_x(Center))
|
||||
.padding(Padding { top: 10.0, right: 70.0, bottom: 10.0, left: 70.0 })
|
||||
.padding(Padding { top: 10.0, right: 70.0, bottom: 10.0, left: 70.0 })
|
||||
.style(move |_, _| {
|
||||
button::Style {
|
||||
text_color: Color::from_rgba8(0, 0, 0, 1.0),
|
||||
|
@ -350,9 +419,21 @@ impl Launcher {
|
|||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let bg_element: Element<Message> = state.get_background_element();
|
||||
let bg_element = if let Some(bg) = state.get_background_element() {
|
||||
bg
|
||||
} else {
|
||||
center(text("Loading...").size(50)).style(|_| {container::Style { text_color: Some(Color::from_rgba8(255, 255, 255, 1.0)), background: Some(Color::from_rgba8(0, 0, 0, 1.0).into()), ..container::Style::default() }}).into()
|
||||
};
|
||||
|
||||
stack![bg_element, user_area].into()
|
||||
let mut final_stack = stack![bg_element];
|
||||
|
||||
if let Some(splash) = state.get_splash() {
|
||||
final_stack = final_stack.push(splash);
|
||||
}
|
||||
|
||||
final_stack = final_stack.push(user_area);
|
||||
|
||||
final_stack.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,9 @@ pub fn get_game_background(game: &PossibleGames) -> LauncherBackground {
|
|||
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"),
|
||||
PossibleGames::ZenlessZoneZero => proj_dirs.data_dir().join("hoyoverse/zzz"),
|
||||
PossibleGames::HonkaiStarRail => proj_dirs.data_dir().join("hoyoverse/hsr"),
|
||||
PossibleGames::GenshinImpact => proj_dirs.data_dir().join("hoyoverse/gi"),
|
||||
};
|
||||
|
||||
if !game_dir.exists() {
|
||||
|
@ -73,6 +73,20 @@ fn get_background_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Result<
|
|||
))
|
||||
}
|
||||
|
||||
pub fn get_game_splash_dynamic_image(game: &PossibleGames) -> Option<DynamicImage> {
|
||||
let proj_dirs = ProjectDirs::from("com", "RabbyDevs", "rr-launcher").unwrap();
|
||||
let file_data = get_splash_file(&proj_dirs, game);
|
||||
if let Some(data) = file_data {
|
||||
let data_cursor = Cursor::new(data);
|
||||
|
||||
Some(ImageReader::new(data_cursor)
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap())
|
||||
} else {None}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -118,6 +132,29 @@ fn get_hoyo_game_icon_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Vec
|
|||
std::fs::read(icon.expect("installation went wrong.").path()).unwrap()
|
||||
}
|
||||
|
||||
fn get_splash_file(proj_dirs: &ProjectDirs, game: &PossibleGames) -> Option<Vec<u8>> {
|
||||
let game_path = match game {
|
||||
PossibleGames::ZenlessZoneZero => "hoyoverse/zzz",
|
||||
PossibleGames::HonkaiStarRail => "hoyoverse/hsr",
|
||||
PossibleGames::GenshinImpact => "hoyoverse/gi",
|
||||
PossibleGames::WutheringWaves => "kuro/wuwa"
|
||||
};
|
||||
|
||||
let data_dir = proj_dirs.data_dir().join(game_path);
|
||||
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("splash_") {
|
||||
icon = Some(path);
|
||||
}
|
||||
}
|
||||
icon
|
||||
};
|
||||
|
||||
icon.map(|icon| std::fs::read(icon.path()).unwrap())
|
||||
}
|
||||
|
||||
fn rad(deg: f32) -> f32 {
|
||||
deg * std::f32::consts::PI / 180.0
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue