Rename to Prospack
No files matched your search
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Packer</title>
|
||||
<title>Prospack</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "ps5-packer",
|
||||
"name": "prospack",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
@@ -21,5 +21,6 @@
|
||||
"@tauri-apps/api": "^2.12.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.1",
|
||||
"@tauri-apps/plugin-opener": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"license": "GPL-3.0-only"
|
||||
}
|
||||
@@ -2685,7 +2685,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ps5-packer"
|
||||
name = "prospack"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
[package]
|
||||
name = "ps5-packer"
|
||||
name = "prospack"
|
||||
version = "0.1.0"
|
||||
description = "Compress, verify and upload PS5 game dumps"
|
||||
description = "Pack, verify and send PS5 dumps as .ffpfsc, from your PC"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/unicxrn/prospack"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "ps5_packer_lib"
|
||||
name = "prospack_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<radialGradient id="g" cx="30%" cy="22%" r="85%"><stop offset="0" stop-color="#2a2340"/><stop offset="0.55" stop-color="#16161a"/><stop offset="1" stop-color="#0e0e10"/></radialGradient>
|
||||
</defs>
|
||||
<rect x="64" y="64" width="896" height="896" rx="208" fill="url(#g)"/>
|
||||
<rect x="64.5" y="64.5" width="895" height="895" rx="207.5" fill="none" stroke="#ffffff" stroke-opacity="0.08" stroke-width="3"/>
|
||||
<rect x="282" y="282" width="460" height="460" rx="104" fill="none" stroke="#ecebe7" stroke-width="52"/>
|
||||
<path d="M400 512h224M512 400v224" stroke="#9b7bff" stroke-width="52" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -264,7 +264,7 @@ impl Ftp {
|
||||
/// Writes a small file atomically: upload to a hidden temp name, then rename over.
|
||||
pub fn put_atomic(&mut self, path: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
let (dir, name) = path.rsplit_once('/').ok_or("bad path")?;
|
||||
let tmp = format!("{dir}/.{name}.packer-tmp");
|
||||
let tmp = format!("{dir}/.{name}.prospack-tmp");
|
||||
// ftpsrv's STOR doesn't truncate before writing, so never write over a leftover temp file.
|
||||
if self.size(&tmp)?.is_some() {
|
||||
self.delete(&tmp)?;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod dump;
|
||||
mod engine;
|
||||
mod ftp;
|
||||
mod paths;
|
||||
mod pipeline;
|
||||
mod ps5;
|
||||
mod settings;
|
||||
@@ -112,6 +113,8 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
paths::init(app.path().app_config_dir()?, app.path().app_data_dir()?);
|
||||
settings::migrate_legacy();
|
||||
let handle = app.handle().clone();
|
||||
let emitter: Emitter = Arc::new(move |event, payload| {
|
||||
let _ = handle.emit(event, payload);
|
||||
@@ -146,5 +149,5 @@ pub fn run() {
|
||||
ps5_status
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Packer");
|
||||
.expect("error while running Prospack");
|
||||
}
|
||||
@@ -5,5 +5,5 @@ fn main() {
|
||||
if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() {
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
ps5_packer_lib::run()
|
||||
prospack_lib::run()
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Where Prospack keeps its config and data. Tauri supplies the platform dirs at startup;
|
||||
//! tests and CLI runs fall back to $PROSPACK_CONFIG_DIR / $PROSPACK_DATA_DIR.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static CONFIG: OnceLock<PathBuf> = OnceLock::new();
|
||||
static DATA: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
pub fn init(config: PathBuf, data: PathBuf) {
|
||||
let _ = CONFIG.set(config);
|
||||
let _ = DATA.set(data);
|
||||
}
|
||||
|
||||
pub fn home() -> PathBuf {
|
||||
std::env::var_os("HOME")
|
||||
.or_else(|| std::env::var_os("USERPROFILE"))
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
std::env::var_os("PROSPACK_CONFIG_DIR")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| CONFIG.get().cloned())
|
||||
.unwrap_or_else(|| home().join(".config/prospack"))
|
||||
}
|
||||
|
||||
pub fn data_dir() -> PathBuf {
|
||||
std::env::var_os("PROSPACK_DATA_DIR")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| DATA.get().cloned())
|
||||
.unwrap_or_else(|| home().join(".local/share/prospack"))
|
||||
}
|
||||
|
||||
/// Where the app lived while it was called Packer (Linux only).
|
||||
pub fn legacy_config_dir() -> PathBuf {
|
||||
home().join(".config/ps5-packer")
|
||||
}
|
||||
|
||||
pub fn legacy_data_dir() -> PathBuf {
|
||||
home().join(".local/share/ps5-packer")
|
||||
}
|
||||
@@ -190,7 +190,7 @@ impl Queue {
|
||||
engine,
|
||||
};
|
||||
let worker = q.clone();
|
||||
std::thread::Builder::new().name("packer-worker".into()).spawn(move || worker.run()).unwrap();
|
||||
std::thread::Builder::new().name("prospack-worker".into()).spawn(move || worker.run()).unwrap();
|
||||
q
|
||||
}
|
||||
|
||||
@@ -595,7 +595,7 @@ impl Queue {
|
||||
let local_size = std::fs::metadata(&image).map_err(|e| format!("Image missing: {e}"))?.len();
|
||||
let dir = s.remote_dir.trim_end_matches('/').to_string();
|
||||
let final_remote = format!("{dir}/{title}.ffpfsc");
|
||||
let tmp_remote = format!("{dir}/.{title}.ffpfsc.packer-part");
|
||||
let tmp_remote = format!("{dir}/.{title}.ffpfsc.prospack-part");
|
||||
|
||||
let mut ftp = ps5::connect(s)?;
|
||||
let others = ps5::competitors(&mut ftp, &dir, &title).unwrap_or_default();
|
||||
@@ -619,7 +619,7 @@ impl Queue {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!(
|
||||
"The PS5 already has a different {title}.ffpfsc ({}). Delete or rename it first; Packer never overwrites.",
|
||||
"The PS5 already has a different {title}.ffpfsc ({}). Delete or rename it first; Prospack never overwrites.",
|
||||
gb(existing)
|
||||
));
|
||||
}
|
||||
@@ -895,9 +895,9 @@ mod e2e {
|
||||
fn e2e_local_fake_ps5() {
|
||||
let scratch = PathBuf::from(std::env::var("PACKER_E2E_SCRATCH").expect("PACKER_E2E_SCRATCH"));
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
std::env::set_var("XDG_CONFIG_HOME", tmp.path().join("config"));
|
||||
std::env::set_var("XDG_DATA_HOME", tmp.path().join("data"));
|
||||
let engine_dir = tmp.path().join("data/ps5-packer/engine");
|
||||
std::env::set_var("PROSPACK_CONFIG_DIR", tmp.path().join("config"));
|
||||
std::env::set_var("PROSPACK_DATA_DIR", tmp.path().join("data"));
|
||||
let engine_dir = tmp.path().join("data/engine");
|
||||
std::fs::create_dir_all(&engine_dir).unwrap();
|
||||
std::os::unix::fs::symlink(scratch.join("mkvenv"), engine_dir.join("venv")).unwrap();
|
||||
|
||||
@@ -950,7 +950,7 @@ mod e2e {
|
||||
let local = std::fs::metadata(j.image_path.as_ref().unwrap()).unwrap().len();
|
||||
let remote = ps5_root.join("data/homebrew/PPSA00001.ffpfsc");
|
||||
assert_eq!(std::fs::metadata(&remote).unwrap().len(), local);
|
||||
assert!(!ps5_root.join("data/homebrew/.PPSA00001.ffpfsc.packer-part").exists());
|
||||
assert!(!ps5_root.join("data/homebrew/.PPSA00001.ffpfsc.prospack-part").exists());
|
||||
let cfg = std::fs::read_to_string(ps5_root.join("data/shadowmount/config.ini")).unwrap();
|
||||
assert!(cfg.contains("image_ro=OTHER.exfat") && cfg.contains("image_ro=PPSA00001.exfat"), "{cfg}");
|
||||
let at = std::fs::read_to_string(ps5_root.join("data/shadowmount/autotune.ini")).unwrap();
|
||||
@@ -1003,7 +1003,7 @@ mod e2e {
|
||||
q.remove(id3).unwrap();
|
||||
let good = std::fs::read(j4.image_path.as_ref().unwrap()).unwrap();
|
||||
std::fs::remove_file(&remote).unwrap();
|
||||
let part = ps5_root.join("data/homebrew/.PPSA00001.ffpfsc.packer-part");
|
||||
let part = ps5_root.join("data/homebrew/.PPSA00001.ffpfsc.prospack-part");
|
||||
std::fs::write(&part, &good[..good.len() / 2]).unwrap();
|
||||
let id5 = q.add(crate::dump::inspect(&dump).unwrap()).unwrap();
|
||||
let j5 = wait_until(&q, id5, 300);
|
||||
|
||||
@@ -160,7 +160,7 @@ pub fn write_hints(ftp: &mut Ftp, title_id: &str, backup_dir: &Path) -> Result<H
|
||||
// Some servers refuse to rename over an existing file.
|
||||
ftp.delete(&path).ok();
|
||||
let (dir, name) = path.rsplit_once('/').unwrap();
|
||||
ftp.rename(&format!("{dir}/.{name}.packer-tmp"), &path)?;
|
||||
ftp.rename(&format!("{dir}/.{name}.prospack-tmp"), &path)?;
|
||||
}
|
||||
res.changed.push(format!("{file}: {line}"));
|
||||
}
|
||||
@@ -200,14 +200,14 @@ pub fn gc_record_json(r: &GcRecord, now: u64) -> String {
|
||||
"aprIndexed": r.apr_indexed,
|
||||
"amprHotSwapOptimized": false,
|
||||
// Any status other than bad-blocks-found*, compression-stats or not-mounted reads as validated.
|
||||
"status": "verified-by-packer",
|
||||
"status": "verified-by-prospack",
|
||||
"nestedName": format!("{}.exfat", r.title_id),
|
||||
"repairedBlocks": 0,
|
||||
"hashMode": "packer-file-compare",
|
||||
"hashMode": "prospack-file-compare",
|
||||
"hashCheckedBlocks": 0,
|
||||
"hashMatchedBlocks": 0,
|
||||
"hashMismatchedBlocks": 0,
|
||||
"verifiedBy": "Packer",
|
||||
"verifiedBy": "Prospack",
|
||||
"verifiedFiles": r.files_verified,
|
||||
"updatedAt": now,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::paths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -24,12 +25,14 @@ pub struct Settings {
|
||||
pub mark_gc_validated: bool,
|
||||
/// Delete the local image once it's confirmed on the PS5.
|
||||
pub delete_local_after_upload: bool,
|
||||
/// The first-run wizard has been completed (or skipped).
|
||||
pub setup_done: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ps5_host: "192.168.0.50".into(),
|
||||
ps5_host: String::new(),
|
||||
ftp_port: 2121,
|
||||
web_port: 8888,
|
||||
remote_dir: "/data/homebrew".into(),
|
||||
@@ -41,30 +44,21 @@ impl Default for Settings {
|
||||
write_smp_hints: true,
|
||||
mark_gc_validated: true,
|
||||
delete_local_after_upload: false,
|
||||
setup_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn home() -> PathBuf {
|
||||
std::env::var_os("HOME").map(PathBuf::from).unwrap_or_else(|| PathBuf::from("/tmp"))
|
||||
}
|
||||
|
||||
fn default_output_dir() -> PathBuf {
|
||||
home().join("PS5 Images")
|
||||
paths::home().join("Prospack Images")
|
||||
}
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
std::env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| home().join(".config"))
|
||||
.join("ps5-packer")
|
||||
paths::config_dir()
|
||||
}
|
||||
|
||||
pub fn data_dir() -> PathBuf {
|
||||
std::env::var_os("XDG_DATA_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| home().join(".local/share"))
|
||||
.join("ps5-packer")
|
||||
paths::data_dir()
|
||||
}
|
||||
|
||||
fn settings_path() -> PathBuf {
|
||||
@@ -85,3 +79,94 @@ pub fn save(s: &Settings) -> Result<(), String> {
|
||||
std::fs::write(&tmp, serde_json::to_vec_pretty(s).unwrap()).map_err(|e| e.to_string())?;
|
||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Carries settings and backups over from the app's earlier name ("Packer", ps5-packer dirs).
|
||||
/// Returns true when settings were written. Never touches the old folders.
|
||||
pub fn migrate_from(old_cfg: &Path, old_data: &Path, new_cfg: &Path, new_data: &Path) -> Result<bool, String> {
|
||||
let new_settings = new_cfg.join("settings.json");
|
||||
let mut wrote = false;
|
||||
if !new_settings.exists() {
|
||||
let old_settings = old_cfg.join("settings.json");
|
||||
let migrated = if let Ok(raw) = std::fs::read_to_string(&old_settings) {
|
||||
// They configured the old app on purpose, so the wizard isn't needed.
|
||||
let mut s: Settings = serde_json::from_str(&raw).map_err(|e| e.to_string())?;
|
||||
s.setup_done = true;
|
||||
Some(s)
|
||||
} else if old_data.is_dir() {
|
||||
// Used the old app with its built-in defaults: keep those, but show the wizard.
|
||||
let old_images = paths::home().join("PS5 Images");
|
||||
Some(Settings {
|
||||
ps5_host: "192.168.0.50".into(),
|
||||
output_dir: if old_images.is_dir() {
|
||||
old_images.to_string_lossy().into_owned()
|
||||
} else {
|
||||
default_output_dir().to_string_lossy().into_owned()
|
||||
},
|
||||
..Settings::default()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(s) = migrated {
|
||||
std::fs::create_dir_all(new_cfg).map_err(|e| e.to_string())?;
|
||||
std::fs::write(&new_settings, serde_json::to_vec_pretty(&s).unwrap()).map_err(|e| e.to_string())?;
|
||||
wrote = true;
|
||||
}
|
||||
}
|
||||
let (old_b, new_b) = (old_data.join("backups"), new_data.join("backups"));
|
||||
if old_b.is_dir() && !new_b.exists() {
|
||||
std::fs::create_dir_all(&new_b).map_err(|e| e.to_string())?;
|
||||
for e in std::fs::read_dir(&old_b).map_err(|e| e.to_string())?.flatten() {
|
||||
let _ = std::fs::copy(e.path(), new_b.join(e.file_name()));
|
||||
}
|
||||
}
|
||||
Ok(wrote)
|
||||
}
|
||||
|
||||
pub fn migrate_legacy() {
|
||||
if let Err(e) = migrate_from(&paths::legacy_config_dir(), &paths::legacy_data_dir(), &config_dir(), &data_dir()) {
|
||||
eprintln!("settings migration failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn migrates_old_ps5_packer_settings_once() {
|
||||
let t = tempfile::tempdir().unwrap();
|
||||
let (old_cfg, old_data) = (t.path().join("old/cfg"), t.path().join("old/data"));
|
||||
std::fs::create_dir_all(old_data.join("backups")).unwrap();
|
||||
std::fs::write(old_data.join("backups/1-config.ini"), "x").unwrap();
|
||||
std::fs::create_dir_all(&old_cfg).unwrap();
|
||||
std::fs::write(old_cfg.join("settings.json"), r#"{"ps5Host":"10.0.0.5","ftpPort":2121}"#).unwrap();
|
||||
let (new_cfg, new_data) = (t.path().join("new/cfg"), t.path().join("new/data"));
|
||||
assert!(migrate_from(&old_cfg, &old_data, &new_cfg, &new_data).unwrap());
|
||||
let s: Settings = serde_json::from_str(&std::fs::read_to_string(new_cfg.join("settings.json")).unwrap()).unwrap();
|
||||
assert_eq!(s.ps5_host, "10.0.0.5");
|
||||
assert!(s.setup_done, "people who configured Packer skip the wizard");
|
||||
assert!(new_data.join("backups/1-config.ini").is_file());
|
||||
assert!(!migrate_from(&old_cfg, &old_data, &new_cfg, &new_data).unwrap(), "second run is a no-op");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_defaults_user_gets_old_host_and_the_wizard() {
|
||||
let t = tempfile::tempdir().unwrap();
|
||||
let old_data = t.path().join("old/data");
|
||||
std::fs::create_dir_all(&old_data).unwrap();
|
||||
let new_cfg = t.path().join("new/cfg");
|
||||
assert!(migrate_from(&t.path().join("old/cfg"), &old_data, &new_cfg, &t.path().join("new/data")).unwrap());
|
||||
let s: Settings = serde_json::from_str(&std::fs::read_to_string(new_cfg.join("settings.json")).unwrap()).unwrap();
|
||||
assert_eq!(s.ps5_host, "192.168.0.50");
|
||||
assert!(!s.setup_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fresh_install_writes_nothing() {
|
||||
let t = tempfile::tempdir().unwrap();
|
||||
let new_cfg = t.path().join("new/cfg");
|
||||
assert!(!migrate_from(&t.path().join("a"), &t.path().join("b"), &new_cfg, &t.path().join("c")).unwrap());
|
||||
assert!(!new_cfg.join("settings.json").exists());
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Packer",
|
||||
"productName": "Prospack",
|
||||
"version": "0.1.0",
|
||||
"identifier": "dev.unicxrn.ps5packer",
|
||||
"identifier": "io.github.unicxrn.prospack",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
@@ -13,7 +13,7 @@
|
||||
"windows": [
|
||||
{
|
||||
"label": "main",
|
||||
"title": "Packer",
|
||||
"title": "Prospack",
|
||||
"width": 1320,
|
||||
"height": 860,
|
||||
"minWidth": 760,
|
||||
@@ -24,12 +24,23 @@
|
||||
"dragDropEnabled": true
|
||||
}
|
||||
],
|
||||
"security": { "csp": null }
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["deb", "appimage"],
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.png"],
|
||||
"targets": [
|
||||
"deb",
|
||||
"appimage"
|
||||
],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.png"
|
||||
],
|
||||
"resources": []
|
||||
}
|
||||
},
|
||||
"mainBinaryName": "prospack"
|
||||
}
|
||||
@@ -171,7 +171,7 @@
|
||||
|
||||
{#if dragging && jobs.length}
|
||||
<div class="dropveil" transition:fade={{ duration: 150 }}>
|
||||
<div><b>Drop to add to the queue</b><span>Packer only reads the dump folder</span></div>
|
||||
<div><b>Drop to add to the queue</b><span>Prospack only reads the dump folder</span></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
<div class="kicker">One-time setup</div>
|
||||
<h2>Install the compression engine</h2>
|
||||
<p>
|
||||
Packer uses <b>MkPFS 1.1.0</b>, the same format Game Compressor builds on. It goes into a private Python
|
||||
Prospack uses <b>MkPFS 1.1.0</b>, the same format Game Compressor builds on. It goes into a private Python
|
||||
environment in <span class="mono">{engine.dir}</span> and doesn't touch your system Python.
|
||||
</p>
|
||||
{#if !engine.python}
|
||||
<p class="err">Python 3 wasn't found. Install it with your package manager, then restart Packer.</p>
|
||||
<p class="err">Python 3 wasn't found. Install it with your package manager, then restart Prospack.</p>
|
||||
{:else}
|
||||
<button class="btn primary" disabled={installing} onclick={onInstall}>
|
||||
{installing ? "Installing…" : "Install MkPFS"}
|
||||
@@ -36,7 +36,7 @@
|
||||
<path d="M20 18v9M15.5 22.5 20 18l4.5 4.5" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<h1>{dragging ? "Let go to add it" : "Drop a game dump here"}</h1>
|
||||
<p>Or click to choose a folder, such as <span class="mono">PPSA01234-app</span>. Packer only reads it.</p>
|
||||
<p>Or click to choose a folder, such as <span class="mono">PPSA01234-app</span>. Prospack only reads it.</p>
|
||||
</button>
|
||||
|
||||
<ol class="steps reveal" style="animation-delay:.16s">
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
{#if job.status === "queued"}
|
||||
<div class="kicker">Queued</div>
|
||||
<h2>{ahead ? `Waiting for ${ahead} ${ahead === 1 ? "game" : "games"} ahead` : "Starting…"}</h2>
|
||||
<p class="desc">{n(job.dump.fileCount)} files, {gb(job.dump.totalBytes)}. Packer handles one game at a time.</p>
|
||||
<p class="desc">{n(job.dump.fileCount)} files, {gb(job.dump.totalBytes)}. Prospack handles one game at a time.</p>
|
||||
<div class="actions"><button class="btn ghost" onclick={onRemove}>Remove from queue</button></div>
|
||||
{:else if job.status === "ready"}
|
||||
<div class="kicker ok">Verified</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
|
||||
async function browse() {
|
||||
const p = await api.pickFolder("Where should Packer write images?");
|
||||
const p = await api.pickFolder("Where should Prospack write images?");
|
||||
if (p) draft.outputDir = p;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<legend class="kicker">Behaviour</legend>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.autoUpload} /><span class="sw"></span>
|
||||
<span><b>Upload when verified</b><small>Otherwise Packer stops after Verify and waits for you.</small></span>
|
||||
<span><b>Upload when verified</b><small>Otherwise Prospack stops after Verify and waits for you.</small></span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.writeSmpHints} /><span class="sw"></span>
|
||||
@@ -104,7 +104,7 @@
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.markGcValidated} /><span class="sw"></span>
|
||||
<span><b>Show as validated in Game Compressor</b><small>Writes Game Compressor's validation record after Packer's own file-by-file check, with the original size and space saved.</small></span>
|
||||
<span><b>Show as validated in Game Compressor</b><small>Writes Game Compressor's validation record after Prospack's own file-by-file check, with the original size and space saved.</small></span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" bind:checked={draft.deleteLocalAfterUpload} /><span class="sw"></span>
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
|
||||
<header class="bar" data-tauri-drag-region>
|
||||
<div class="mark" data-tauri-drag-region>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
||||
<rect x="1.5" y="1.5" width="15" height="15" rx="4" stroke="currentColor" stroke-width="1.4" />
|
||||
<path d="M5.5 6.5h7" stroke="var(--accent)" stroke-width="1.4" stroke-linecap="round" />
|
||||
<path d="M5.5 9.5h7M5.5 12.5h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
||||
<svg width="18" height="18" viewBox="0 0 28 28" fill="none" aria-hidden="true">
|
||||
<rect x="5" y="5" width="18" height="18" rx="4" stroke="currentColor" stroke-width="2" />
|
||||
<path d="M10 14h8M14 10v8" stroke="#9b7bff" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
Packer
|
||||
Prospack
|
||||
</div>
|
||||
|
||||
<button class="conn" class:off={ps5 && !ps5.online} onclick={onSettings} title={ps5?.error ?? ""}>
|
||||
|
||||