diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bb13f46..835be83 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - platform: [ubuntu-20.04, windows-latest, macos-12] + platform: [ubuntu-20.04, windows-latest, macos-11] runs-on: ${{ matrix.platform }} steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 10507e9..902e6bb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -105,7 +105,7 @@ jobs: strategy: fail-fast: false matrix: - platform: [ubuntu-20.04, windows-latest, macos-12] + platform: [ubuntu-20.04, windows-latest, macos-11] runs-on: ${{ matrix.platform }} steps: # NOTE - there is technically a race condition here if multiple releases go out diff --git a/public/images/jak1/basin.jpg b/public/images/jak1/basin.jpg new file mode 100644 index 0000000..500db08 Binary files /dev/null and b/public/images/jak1/basin.jpg differ diff --git a/public/images/jak1/cave.jpg b/public/images/jak1/cave.jpg new file mode 100644 index 0000000..357a1f1 Binary files /dev/null and b/public/images/jak1/cave.jpg differ diff --git a/public/images/jak1/citadel.jpg b/public/images/jak1/citadel.jpg new file mode 100644 index 0000000..c96ca4c Binary files /dev/null and b/public/images/jak1/citadel.jpg differ diff --git a/public/images/jak1/end.jpg b/public/images/jak1/end.jpg new file mode 100644 index 0000000..57cb416 Binary files /dev/null and b/public/images/jak1/end.jpg differ diff --git a/public/images/jak1/finalboss.jpg b/public/images/jak1/finalboss.jpg new file mode 100644 index 0000000..2373991 Binary files /dev/null and b/public/images/jak1/finalboss.jpg differ diff --git a/public/images/jak1/firecanyon.jpg b/public/images/jak1/firecanyon.jpg new file mode 100644 index 0000000..528263f Binary files /dev/null and b/public/images/jak1/firecanyon.jpg differ diff --git a/public/images/jak1/geyser.jpg b/public/images/jak1/geyser.jpg new file mode 100644 index 0000000..a3f5589 Binary files /dev/null and b/public/images/jak1/geyser.jpg differ diff --git a/public/images/jak1/jungle.jpg b/public/images/jak1/jungle.jpg new file mode 100644 index 0000000..2b16a82 Binary files /dev/null and b/public/images/jak1/jungle.jpg differ diff --git a/public/images/jak1/klaww.jpg b/public/images/jak1/klaww.jpg new file mode 100644 index 0000000..f3a5bd3 Binary files /dev/null and b/public/images/jak1/klaww.jpg differ diff --git a/public/images/jak1/lavatube.jpg b/public/images/jak1/lavatube.jpg new file mode 100644 index 0000000..6510c0e Binary files /dev/null and b/public/images/jak1/lavatube.jpg differ diff --git a/public/images/jak1/lpc.jpg b/public/images/jak1/lpc.jpg new file mode 100644 index 0000000..ff365d7 Binary files /dev/null and b/public/images/jak1/lpc.jpg differ diff --git a/public/images/jak1/misty.jpg b/public/images/jak1/misty.jpg new file mode 100644 index 0000000..89d2b05 Binary files /dev/null and b/public/images/jak1/misty.jpg differ diff --git a/public/images/jak1/mountainpass.jpg b/public/images/jak1/mountainpass.jpg new file mode 100644 index 0000000..6d465f1 Binary files /dev/null and b/public/images/jak1/mountainpass.jpg differ diff --git a/public/images/jak1/sandover.jpg b/public/images/jak1/sandover.jpg new file mode 100644 index 0000000..bd0e6f7 Binary files /dev/null and b/public/images/jak1/sandover.jpg differ diff --git a/public/images/jak1/sentinel.jpg b/public/images/jak1/sentinel.jpg new file mode 100644 index 0000000..c4436ee Binary files /dev/null and b/public/images/jak1/sentinel.jpg differ diff --git a/public/images/jak1/snowy.jpg b/public/images/jak1/snowy.jpg new file mode 100644 index 0000000..8a2fe3e Binary files /dev/null and b/public/images/jak1/snowy.jpg differ diff --git a/public/images/jak1/swamp.jpg b/public/images/jak1/swamp.jpg new file mode 100644 index 0000000..5e4f328 Binary files /dev/null and b/public/images/jak1/swamp.jpg differ diff --git a/public/images/jak1/village2.jpg b/public/images/jak1/village2.jpg new file mode 100644 index 0000000..2dad417 Binary files /dev/null and b/public/images/jak1/village2.jpg differ diff --git a/public/images/jak1/village3.jpg b/public/images/jak1/village3.jpg new file mode 100644 index 0000000..e758870 Binary files /dev/null and b/public/images/jak1/village3.jpg differ diff --git a/src-tauri/src/commands/game.rs b/src-tauri/src/commands/game.rs index 6ea030e..5233d0c 100644 --- a/src-tauri/src/commands/game.rs +++ b/src-tauri/src/commands/game.rs @@ -1,8 +1,16 @@ -use std::path::Path; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; +use log::info; use tauri::{api::path::config_dir, Manager}; +use walkdir::WalkDir; -use crate::config::LauncherConfig; +use crate::{ + config::LauncherConfig, + util::game_milestones::{get_jak1_milestones, GameTaskStatus, MilestoneCriteria}, +}; use super::CommandError; @@ -68,3 +76,132 @@ pub async fn reset_game_settings(game_name: String) -> Result<(), CommandError> )) } } + +fn get_saves_highest_milestone( + path: &PathBuf, + milestones: &Vec, +) -> Option<(String, i32)> { + // Read the file's bytes and generate a list of all completed tasks + let mut tasks: HashMap = HashMap::new(); + let save_bytes = match std::fs::read(path) { + Ok(bytes) => bytes, + Err(err) => { + log::error!("Failed to read save file: {:?}", err); + return None; + } + }; + + let mut reading_tasks = false; + let mut tasks_remaining = 0; + // Iterate through bytes in 16 byte chunks + for chunk in save_bytes.chunks(16) { + if reading_tasks { + // Otherwise, it's a task, parse it + let new_game_task = GameTaskStatus { + introduced: chunk[0] != 0 && chunk[0] != 1 && chunk[0] != 6 && chunk[0] != 7, + completed: chunk[0] == 7, + }; + tasks.insert(chunk[11], new_game_task); + tasks_remaining -= 1; + if tasks_remaining <= 0 { + break; + } + } else { + // Check to see if we've reached the task list + if chunk[14] == 0x2C && chunk[15] == 0x01 { + // Retrieve the amount of tasks that we need to iterate through + reading_tasks = true; + tasks_remaining = i32::from_le_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]); + info!("Found {} tasks", tasks_remaining); + } + } + } + + // Iterate through the milestones backwards + for (index, milestone) in milestones.iter().rev().enumerate() { + for task_id in &milestone.introduced { + if tasks.contains_key(&task_id) && tasks[&task_id].introduced { + return Some((milestone.name.to_owned(), (milestones.len() - index) as i32)); + } + } + for task_id in &milestone.completed { + if tasks.contains_key(&task_id) && tasks[&task_id].completed { + return Some((milestone.name.to_owned(), (milestones.len() - index) as i32)); + } + } + } + + return None; +} + +// Returns the most significant milestone in the game the user has achieved +// this is determined by scanning the user's save files +// and displaying a relevant screenshot in the frontend to reflect their progress +// +// Otherwise, it will default to a default picture (geyser) +#[tauri::command] +pub async fn get_furthest_game_milestone(game_name: String) -> Result { + // TODO - currently only checking Jak 1 + // TODO - It would be cool if the launcher had save-game editing features and the like + // Scan each save file, we inspect the `game-save`'s tag list. + // - to find the beginning of the tags, scan 16 bytes at a time, find the group that ends with `00 01 00 64` aka 1 element type 100 (name) + // - the name tag always comes first + // - next, we continue to scan until we find type `300` which is the task-list + // - this group also says how many tasks there are, each are 16 bytes as well + // - then it's just a matter of going through each task and seeing if it's completed or not, they are in order of the `game-task` enum + // - for each entity-perm, byte 0-4 corresponds with it's `task-status`: + // (invalid 0) + // (unknown 1) + // (need-hint 2) + // (need-introduction 3) + // (need-reminder-a 4) + // (need-reminder 5) + // (need-reward-speech 6) + // (need-resolution 7) + // - byte 11 corresponds with it's task id + // there is also a task status field but we don't really care about it, the task-status entry is sufficient + let game_save_dir = if let Some(config_dir) = config_dir() { + let expected_dir = config_dir.join("OpenGOAL").join(game_name).join("saves"); + if !expected_dir.exists() { + info!( + "Expected save directory {} does not exist", + expected_dir.display() + ); + return Ok("geyser".to_owned()); + } + expected_dir + } else { + info!("Couldn't determine game save directory"); + return Ok("geyser".to_owned()); + }; + + let milestones = get_jak1_milestones(); + + // Scan the directory recursively for any `*.bin` files + // Check each save's contents, we don't assume save 0 is the only important one + let mut highest_milestone_idx = 0; + let mut furthest_milestone_name = "geyser".to_owned(); + // TODO - a find all X in a dir function would be nice + for entry in WalkDir::new(&game_save_dir) + .into_iter() + .filter_map(|e| e.ok()) + { + if let Some(ext) = entry.path().extension() { + if ext == "bin" { + info!("Scanning save {}", entry.path().display()); + match get_saves_highest_milestone(&entry.into_path(), &milestones) { + Some((name, idx)) => { + info!("Furthest milestone {} at index {}", name, idx); + if idx > highest_milestone_idx { + highest_milestone_idx = idx; + furthest_milestone_name = name.to_owned(); + } + } + None => {} + } + } + } + } + + return Ok(furthest_milestone_name); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6c28471..e4c18dd 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -160,6 +160,7 @@ fn main() { commands::config::cleanup_enabled_texture_packs, commands::game::reset_game_settings, commands::game::uninstall_game, + commands::game::get_furthest_game_milestone, commands::features::update_texture_pack_data, commands::features::extract_new_texture_pack, commands::features::list_extracted_texture_pack_info, diff --git a/src-tauri/src/util.rs b/src-tauri/src/util.rs index 4b5f3a7..86fe8bd 100644 --- a/src-tauri/src/util.rs +++ b/src-tauri/src/util.rs @@ -1,4 +1,5 @@ pub mod file; +pub mod game_milestones; pub mod network; pub mod os; pub mod tar; diff --git a/src-tauri/src/util/game_milestones.rs b/src-tauri/src/util/game_milestones.rs new file mode 100644 index 0000000..63ce691 --- /dev/null +++ b/src-tauri/src/util/game_milestones.rs @@ -0,0 +1,229 @@ +use std::vec; + +pub struct MilestoneCriteria { + pub name: String, + // some milestones are considered reached once you've completed something + // (ie. collecting a cell in an area) + pub completed: Vec, + // others are reached when they've been introduced + // (ie. preparing for a boss fight) + pub introduced: Vec, +} + +pub struct GameTaskStatus { + pub introduced: bool, + pub completed: bool, +} + +pub fn get_jak1_milestones() -> Vec { + return vec![ + MilestoneCriteria { + name: "geyser".to_string(), + completed: vec![], + introduced: vec![], + }, + MilestoneCriteria { + // (village1-yakow 10) + // (village1-mayor-money 11) + // (village1-uncle-money 12) + // (village1-oracle-money1 13) + // (village1-oracle-money2 14) + // (beach-ecorocks 15) + // (village1-buzzer 75) + name: "sandover".to_string(), + completed: vec![10, 11, 12, 13, 14, 75], + introduced: vec![15], + }, + MilestoneCriteria { + // (beach-ecorocks 15) + // (beach-pelican 16) + // (beach-flutflut 17) + // (beach-seagull 18) + // (beach-cannon 19) + // (beach-buzzer 20) + // (beach-gimmie 21) + // (beach-sentinel 22) + name: "sentinel".to_string(), + completed: vec![15, 16, 17, 18, 19, 20, 21, 22], + introduced: vec![], + }, + MilestoneCriteria { + // (jungle-eggtop 2) + // (jungle-lurkerm 3) + // (jungle-tower 4) + // (jungle-fishgame 5) + // (jungle-plant 6) + // (jungle-buzzer 7) + // (jungle-canyon-end 8) + // (jungle-temple-door 9) + name: "jungle".to_string(), + completed: vec![2, 3, 4, 5, 6, 7, 8, 9], + introduced: vec![], + }, + MilestoneCriteria { + // (misty-muse 23) + // (misty-boat 24) + // (misty-warehouse 25) + // (misty-cannon 26) + // (misty-bike 27) + // (misty-buzzer 28) + // (misty-bike-jump 29) + // (misty-eco-challenge 30) + // (leaving-misty 114) + name: "misty".to_string(), + completed: vec![23, 24, 25, 26, 27, 28, 29, 30, 114], + introduced: vec![], + }, + MilestoneCriteria { + // (firecanyon-buzzer 68) + // (firecanyon-end 69) + // (firecanyon-assistant 102) + name: "firecanyon".to_string(), + completed: vec![68, 69], + introduced: vec![102], + }, + MilestoneCriteria { + // (village2-gambler-money 31) + // (village2-geologist-money 32) + // (village2-warrior-money 33) + // (village2-oracle-money1 34) + // (village2-oracle-money2 35) + // (firecanyon-buzzer 68) + // (firecanyon-end 69) + // (village2-buzzer 76) + // (firecanyon-assistant 102) + name: "village2".to_string(), + completed: vec![31, 32, 33, 34, 35, 68, 69], + introduced: vec![76, 102], + }, + MilestoneCriteria { + // (rolling-race 52) + // (rolling-robbers 53) + // (rolling-moles 54) + // (rolling-plants 55) + // (rolling-lake 56) + // (rolling-buzzer 57) + // (rolling-ring-chase-1 58) + // (rolling-ring-chase-2 59) + name: "basin".to_string(), + completed: vec![52, 53, 54, 55, 56, 57, 58, 59], + introduced: vec![], + }, + MilestoneCriteria { + // (swamp-billy 36) + // (swamp-flutflut 37) + // (swamp-battle 38) + // (swamp-tether-1 39) + // (swamp-tether-2 40) + // (swamp-tether-3 41) + // (swamp-tether-4 42) + // (swamp-buzzer 43) + // (swamp-arm 104) + name: "swamp".to_string(), + completed: vec![36, 37, 38, 39, 40, 41, 42, 43, 104], + introduced: vec![], + }, + MilestoneCriteria { + // (sunken-platforms 44) + // (sunken-pipe 45) + // (sunken-slide 46) + // (sunken-room 47) + // (sunken-sharks 48) + // (sunken-buzzer 49) + // (sunken-top-of-helix 50) + // (sunken-spinning-room 51) + name: "lpc".to_string(), + completed: vec![44, 45, 46, 47, 48, 49, 50, 51], + introduced: vec![], + }, + MilestoneCriteria { + // (ogre-boss 86) + // (village2-levitator 103) + name: "klaww".to_string(), + completed: vec![103], + introduced: vec![86], + }, + MilestoneCriteria { + // (ogre-boss 86) + // (ogre-end 87) + // (ogre-buzzer 88) + // (ogre-secret 110) + name: "mountainpass".to_string(), + completed: vec![86, 88, 110], + introduced: vec![87], + }, + MilestoneCriteria { + // (village3-extra1 74) + // (village3-buzzer 77) + // (village3-miner-money1 96) + // (village3-miner-money2 97) + // (village3-miner-money3 98) + // (village3-miner-money4 99) + // (village3-oracle-money1 100) + // (village3-oracle-money2 101) + // (village3-button 105) + name: "village3".to_string(), + completed: vec![74, 77, 96, 97, 98, 99, 100, 101, 105], + introduced: vec![], + }, + MilestoneCriteria { + // (cave-gnawers 78) + // (cave-dark-crystals 79) + // (cave-dark-climb 80) + // (cave-robot-climb 81) + // (cave-swing-poles 82) + // (cave-spider-tunnel 83) + // (cave-platforms 84) + // (cave-buzzer 85) + name: "cave".to_string(), + completed: vec![78, 79, 80, 81, 82, 83, 84, 85], + introduced: vec![], + }, + MilestoneCriteria { + // (snow-eggtop 60) + // (snow-ram 61) + // (snow-fort 62) + // (snow-ball 63) + // (snow-bunnies 64) + // (snow-buzzer 65) + // (snow-bumpers 66) + // (snow-cage 67) + name: "snowy".to_string(), + completed: vec![60, 61, 62, 63, 64, 65, 66, 67], + introduced: vec![], + }, + MilestoneCriteria { + // (lavatube-end 89) + // (lavatube-buzzer 90) + // (lavatube-balls 107) + // (lavatube-start 108) + // (assistant-village3 115) + name: "lavatube".to_string(), + completed: vec![90, 107, 108, 115], + introduced: vec![89], + }, + MilestoneCriteria { + // (citadel-sage-green 70) + // (citadel-sage-blue 71) + // (citadel-sage-red 72) + // (citadel-sage-yellow 73) + // (lavatube-end 89) + // (citadel-buzzer 91) + name: "citadel".to_string(), + completed: vec![71, 72, 73, 89, 91], + introduced: vec![70], + }, + MilestoneCriteria { + // (citadel-sage-green 70) + name: "finalboss".to_string(), + completed: vec![70], + introduced: vec![], + }, + MilestoneCriteria { + // (finalboss-movies 112) + name: "end".to_string(), + completed: vec![], + introduced: vec![112], + }, + ]; +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6928b4a..d15e235 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -19,9 +19,6 @@ "dmg", "updater" ], - "appimage": { - "bundleMediaFramework": true - }, "identifier": "OpenGOAL-Launcher", "icon": [ "icons/32x32.png", diff --git a/src/assets/images/background-jak1.webp b/src/assets/images/background-jak1.webp deleted file mode 100644 index bc3802f..0000000 Binary files a/src/assets/images/background-jak1.webp and /dev/null differ diff --git a/src/assets/images/background-jak2.webp b/src/assets/images/background-jak2.webp index 5807a58..fac1726 100644 Binary files a/src/assets/images/background-jak2.webp and b/src/assets/images/background-jak2.webp differ diff --git a/src/assets/videos/background-jak1.webm b/src/assets/videos/background-jak1.webm deleted file mode 100644 index f9dc060..0000000 Binary files a/src/assets/videos/background-jak1.webm and /dev/null differ diff --git a/src/assets/videos/background-jak2.webm b/src/assets/videos/background-jak2.webm deleted file mode 100644 index 32bda89..0000000 Binary files a/src/assets/videos/background-jak2.webm and /dev/null differ diff --git a/src/components/background/Background.svelte b/src/components/background/Background.svelte index 759f06f..fe9d663 100644 --- a/src/components/background/Background.svelte +++ b/src/components/background/Background.svelte @@ -1,17 +1,16 @@ {#if $location.pathname == "/jak1" || $location.pathname == "/"} -