Skip to content

Commit

Permalink
Splits firmware installation progress into a modal window (#1229)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Jan 6, 2025
1 parent b6d26c9 commit ab581d0
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 147 deletions.
13 changes: 13 additions & 0 deletions gui/src/rt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ pub fn spawn(task: impl Future<Output = ()> + 'static) {
})
}

/// Yields execution back to the runtime to process pending events.
pub fn yield_now() -> impl Future<Output = ()> {
let mut yielded = false;

std::future::poll_fn(move |cx| match std::mem::replace(&mut yielded, true) {
true => Poll::Ready(()),
false => {
cx.waker().wake_by_ref();
Poll::Pending
}
})
}

/// The returned handle will be valid until the event loop exited.
///
/// # Panics
Expand Down
208 changes: 129 additions & 79 deletions gui/src/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ pub use self::data::DataRootError;

use self::data::{read_data_root, write_data_root};
use crate::data::{DataError, DataMgr};
use crate::rt::yield_now;
use crate::ui::{
error, open_dir, open_file, spawn_handler, FileType, PlatformExt, RuntimeExt, SetupWizard,
error, open_dir, open_file, spawn_handler, FileType, InstallFirmware, PlatformExt, RuntimeExt,
SetupWizard,
};
use crate::vfs::{FsType, FS_TYPE};
use erdp::ErrorDisplay;
use obfw::ps4::{PartData, PartReader};
use obfw::{DumpReader, ItemReader};
use redb::{Database, DatabaseError};
use slint::{ComponentHandle, PlatformError, SharedString};
use slint::{CloseRequestResponse, ComponentHandle, PlatformError, SharedString};
use std::cell::Cell;
use std::error::Error;
use std::fs::File;
use std::future::Future;
use std::io::{ErrorKind, Write};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use thiserror::Error;
Expand Down Expand Up @@ -67,13 +72,13 @@ pub async fn run_setup() -> Result<Option<DataMgr>, SetupError> {
win.on_browse_firmware({
let win = win.as_weak();

move || crate::rt::spawn(browse_firmware(win.unwrap()))
move || spawn_handler(&win, |w| browse_firmware(w))
});

win.on_install_firmware({
let win = win.as_weak();

move || install_firmware(win.unwrap())
move || spawn_handler(&win, |w| install_firmware(w))
});

win.on_finish({
Expand Down Expand Up @@ -162,27 +167,27 @@ async fn set_data_root(win: SetupWizard) {
let path = Path::new(input.as_str());

if !path.is_absolute() {
win.set_error_message("Path must be absolute.".into());
error(Some(&win), "Path must be absolute.").await;
return;
} else if !path.is_dir() {
win.set_error_message("Path must be a directory.".into());
error(Some(&win), "Path must be a directory.").await;
return;
}

// Create data manager to see if path is writable.
let mgr = match DataMgr::new(path) {
Ok(v) => v,
Err(e) => {
win.set_error_message(
format!("Failed to create data manager: {}.", e.display()).into(),
);
let m = slint::format!("Failed to create data manager: {}.", e.display());
error(Some(&win), m).await;
return;
}
};

// Save.
if let Err(e) = write_data_root(input) {
win.set_error_message(format!("Failed to save data location: {}.", e.display()).into());
let m = slint::format!("Failed to save data location: {}.", e.display());
error(Some(&win), m).await;
return;
}

Expand All @@ -200,7 +205,7 @@ async fn browse_firmware(win: SetupWizard) {
let path = match path.into_os_string().into_string() {
Ok(v) => v,
Err(_) => {
win.set_error_message("Path to a firmware dump must be unicode.".into());
error(Some(&win), "Path to a firmware dump must be unicode.").await;
return;
}
};
Expand All @@ -209,12 +214,13 @@ async fn browse_firmware(win: SetupWizard) {
win.set_firmware_dump(path.into());
}

fn install_firmware(win: SetupWizard) {
async fn install_firmware(win: SetupWizard) {
// Get dump path.
let path = win.get_firmware_dump();

if path.is_empty() {
win.set_error_message("You need to select a firmware dump before proceed.".into());
let m = "You need to select a firmware dump before proceed.";
error(Some(&win), m).await;
return;
}

Expand All @@ -225,7 +231,8 @@ fn install_firmware(win: SetupWizard) {
{
Ok(v) => v,
Err(e) => {
win.set_error_message(format!("Failed to open {}: {}.", path, e.display()).into());
let m = slint::format!("Failed to open {}: {}.", path, e.display());
error(Some(&win), m).await;
return;
}
};
Expand All @@ -235,97 +242,119 @@ fn install_firmware(win: SetupWizard) {
let dmgr = match DataMgr::new(root.as_str()) {
Ok(v) => v,
Err(e) => {
let m = format!(
let m = slint::format!(
"Failed to create data manager on {}: {}.",
root,
e.display()
);

win.set_error_message(m.into());
error(Some(&win), m).await;
return;
}
};

win.invoke_show_firmware_installer();
win.set_firmware_status("Initializing...".into());

// Spawn thread to extract the dump.
let win = win.as_weak();

std::thread::spawn(move || {
// Extract.
let n = dump.items();
let mut p = 0u32;
let e =
match extract_firmware_dump(
&mut dump,
&dmgr,
|v| drop(win.upgrade_in_event_loop(move |w| w.set_firmware_status(v.into()))),
|| {
p += 1;

drop(win.upgrade_in_event_loop(move |w| {
w.set_firmware_progress(p as f32 / n as f32)
}));
},
) {
Ok(_) => {
drop(win.upgrade_in_event_loop(|w| w.invoke_set_firmware_finished(true)));
return;
}
Err(e) => e,
};
// Setup progress window.
let pw = match InstallFirmware::new() {
Ok(v) => v,
Err(e) => {
let m = slint::format!(
"Failed to create firmware progress window: {}.",
e.display()
);

// Show error.
let m = format!("Failed to install {}: {}.", path, e.display());
error(Some(&win), m).await;
return;
}
};

drop(win.upgrade_in_event_loop(move |w| {
w.invoke_set_firmware_finished(false);
w.set_error_message(m.into());
}));
});
}
pw.set_status("Initializing...".into());
pw.window()
.on_close_requested(|| CloseRequestResponse::KeepWindowShown);

fn extract_firmware_dump(
dump: &mut DumpReader<File>,
dmgr: &DataMgr,
mut status: impl FnMut(String),
mut step: impl FnMut(),
) -> Result<(), FirmwareError> {
loop {
if let Err(e) = pw.show() {
let m = slint::format!("Failed to show firmware progress window: {}.", e.display());
error(Some(&win), m).await;
return;
}

// Make progress window modal.
let pw = match pw.set_modal(&win) {
Ok(v) => v,
Err(e) => {
let m = slint::format!(
"Failed to make firmware progress window modal: {}.",
e.display()
);

error(Some(&win), m).await;
return;
}
};

// Setup progress updater.
let n = dump.items();
let mut p = 0u32;
let mut step = || {
p += 1;
pw.set_progress(p as f32 / n as f32);
yield_now()
};

yield_now().await;

// Extract.
let e = loop {
// Get next item.
let mut item = match dump.next_item().map_err(FirmwareError::NextItem)? {
Some(v) => v,
None => break,
let mut item = match dump.next_item() {
Ok(Some(v)) => v,
Ok(None) => break None,
Err(e) => break Some(FirmwareError::NextItem(e)),
};

// Update status.
let name = item.to_string();

status(format!("Extracting {name}..."));
pw.set_status(slint::format!("Extracting {name}..."));

yield_now().await;

// Extract item.
let r: Result<(), Box<dyn Error>> = match &mut item {
ItemReader::Ps4Part(r) => {
extract_partition(dmgr, r, &mut status, &mut step).map_err(|e| e.into())
}
ItemReader::Ps4Part(r) => extract_partition(&pw, &dmgr, r, &mut step)
.await
.map_err(|e| e.into()),
};

if let Err(e) = r {
return Err(FirmwareError::ExtractItem(name, e));
break Some(FirmwareError::ExtractItem(name, e));
}

step();
step().await;
};

// Check status.
if let Err(e) = pw.hide() {
let m = slint::format!("Failed to close firmware progress window: {}.", e.display());
error(Some(pw.deref()), m).await;
}

Ok(())
drop(pw);
yield_now().await;

match e {
Some(e) => {
let m = slint::format!("Failed to install {}: {}.", path, e.display());
error(Some(&win), m).await;
}
None => win.invoke_set_firmware_finished(),
}
}

fn extract_partition(
async fn extract_partition<F: Future<Output = ()>>(
pw: &InstallFirmware,
dmgr: &DataMgr,
part: &mut PartReader<File>,
status: &mut impl FnMut(String),
step: &mut impl FnMut(),
part: &mut PartReader<'_, File>,
step: &mut impl FnMut() -> F,
) -> Result<(), PartitionError> {
// Get FS type.
let fs = match part.fs() {
Expand Down Expand Up @@ -392,6 +421,7 @@ fn extract_partition(

// Extract items.
let root = dmgr.partitions().data(dev);
let mut buf = vec![0u8; 0xFFFF];

loop {
// Get next item.
Expand Down Expand Up @@ -429,16 +459,34 @@ fn extract_partition(
// Extract item.
match data {
Some(mut data) => {
status(format!("Extracting {name}..."));
pw.set_status(slint::format!("Extracting {name}..."));

yield_now().await;

// Create only if not exists.
let mut file = match File::create_new(&path) {
Ok(v) => v,
Err(e) => return Err(PartitionError::CreateFile(path, e)),
};

if let Err(e) = std::io::copy(&mut data, &mut file) {
return Err(PartitionError::ExtractFile(name, path, e));
// Copy data.
loop {
let n = match data.read(&mut buf) {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(PartitionError::ExtractFile(name, path, e)),
};

if n == 0 {
break;
}

// Write file.
if let Err(e) = file.write_all(&buf[..n]) {
return Err(PartitionError::ExtractFile(name, path, e));
}

yield_now().await;
}
}
None => {
Expand All @@ -449,11 +497,13 @@ fn extract_partition(
}
}

step();
step().await;
}

// Commit metadata transaction.
status("Committing metadata database...".into());
pw.set_status("Committing metadata database...".into());

yield_now().await;

if let Err(e) = meta.commit() {
return Err(PartitionError::MetaCommit(mp, e));
Expand Down
2 changes: 1 addition & 1 deletion gui/ui/main.slint
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { VerticalBox } from "std-widgets.slint";

export { WaitForDebugger } from "debug.slint";
export { ErrorWindow } from "error.slint";
export { SetupWizard } from "setup.slint";
export { InstallFirmware, SetupWizard } from "setup.slint";

export component MainWindow inherits Window {
in property <[string]> devices;
Expand Down
Loading

0 comments on commit ab581d0

Please sign in to comment.