Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
41ab585a48 | |||
3edefccd0b | |||
713196c9db | |||
5bde3d347a | |||
2304a78db4 | |||
81cf2cf413 | |||
45b06e4478 | |||
77f5b6cd5e | |||
67872bc4d1 | |||
469ba5f574 | |||
8f05a04350 | |||
4ddc54d528 | |||
c4d023ed43 | |||
9b86af282e | |||
2e17e0ae75 | |||
edef5cc6dc |
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,6 +1,39 @@
|
||||
## 0.22.0
|
||||
|
||||
- Added Spanish localization (courtesy of 7x)
|
||||
- Added Korean localization (courtesy of LEveLiQ)
|
||||
|
||||
## 0.21.0
|
||||
|
||||
- Added simplified Chinese localization (courtesy of Chilor)
|
||||
|
||||
## 0.20.1
|
||||
|
||||
- Added Japanese localization (courtesy of SALEC)
|
||||
- Added user-customizable scripts
|
||||
- The launch script runs directly before the game, and is equivalent to adding lines above `start "AM Daemon" ...` in start.bat
|
||||
- The end script runs after the game has closed for any reason, and is equivalent to adding lines below `taskkill /f /im amdaemon.exe ...` in start.bat
|
||||
- This functionality is needed for cursed things such as brokenithm
|
||||
- Fixed the file editor not updating its state properly
|
||||
- Fixed diagnostic exports not exporting paths
|
||||
- Fixed "Install recommended packages" not enabling the segatools hook
|
||||
|
||||
## 0.19.1
|
||||
|
||||
- Fixed the update button enabling the package
|
||||
- Fixed deep URLs with rainycolor.org
|
||||
|
||||
## 0.19.0
|
||||
|
||||
- Added diagnostic exports
|
||||
|
||||
## 0.18.3
|
||||
|
||||
- Updated Rainycolor's domain・真
|
||||
|
||||
## 0.18.2
|
||||
|
||||
- Update Rainycolor's domain
|
||||
- Updated Rainycolor's domain
|
||||
|
||||
## 0.18.1
|
||||
|
||||
|
113
rust/src/cmd.rs
113
rust/src/cmd.rs
@ -1,4 +1,3 @@
|
||||
use ini::Ini;
|
||||
use log;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::path::PathBuf;
|
||||
@ -66,7 +65,7 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
||||
let mut amd_dlls = Vec::new();
|
||||
if let Some(p) = &appd.profile {
|
||||
hash = appd.sum_packages(p);
|
||||
(game_dlls, amd_dlls) = prepare_dlls(p.meta.game, p.mod_pkgs(), &appd.pkgs).map_err(|e| e.to_string())?
|
||||
(game_dlls, amd_dlls) = prepare_dlls(p.meta.game, p.mod_pkgs(), &appd.pkgs);
|
||||
}
|
||||
if let Some(p) = &appd.profile {
|
||||
log::debug!("{}", hash);
|
||||
@ -74,13 +73,12 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
||||
let patches_enabled = appd.patches_enabled(
|
||||
&p.data.sgt.target,
|
||||
&p.data.sgt.target.parent().unwrap().join("amdaemon.exe")
|
||||
).map_err(|e| e.to_string())?;
|
||||
).map_err(|e| format!("Unable to apply patches: {e}"))?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let info = p.prepare_display()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let lineup_res = p.line_up(hash, refresh, patches_enabled).await
|
||||
.map_err(|e| e.to_string());
|
||||
.map_err(|e| format!("Unable to configure displays: {e}"))?;
|
||||
let lineup_res = p.line_up(hash, refresh, patches_enabled).await;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(info) = info {
|
||||
@ -88,11 +86,12 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
|
||||
if lineup_res.is_ok() {
|
||||
Display::wait_for_exit(app.clone(), info);
|
||||
} else {
|
||||
Display::clean_up(&info).map_err(|e| e.to_string())?;
|
||||
Display::clean_up(&info)
|
||||
.map_err(|e| format!("Unable to restore displays: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
lineup_res?;
|
||||
lineup_res.map_err(|e| format!("Init failed: {e}"))?;
|
||||
|
||||
let app_clone = app.clone();
|
||||
let p_clone = p.clone();
|
||||
@ -125,14 +124,15 @@ pub async fn kill() -> Result<(), String> {
|
||||
pub async fn install_package(
|
||||
state: State<'_, tokio::sync::Mutex<AppData>>,
|
||||
key: PkgKey,
|
||||
force: bool
|
||||
force: bool,
|
||||
enable: bool
|
||||
) -> Result<InstallResult, String> {
|
||||
log::debug!("invoke: install_package({})", key);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.pkgs.install_package(&key, force, true)
|
||||
appd.pkgs.install_package(&key, force, true, enable)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| format!("Unable to install {key}: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -142,10 +142,10 @@ pub async fn delete_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
|
||||
let mut appd = state.lock().await;
|
||||
appd.pkgs.delete_package(&key, true)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| format!("Unable to delete {key}: {e}"))?;
|
||||
|
||||
appd.toggle_package(key, ToggleAction::Disable)
|
||||
.map_err(|e| e.to_string())
|
||||
appd.toggle_package(key.clone(), ToggleAction::Disable)
|
||||
.map_err(|e| format!("Unable to disable {key}: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -154,7 +154,7 @@ pub async fn get_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: Pkg
|
||||
|
||||
let appd = state.lock().await;
|
||||
appd.pkgs.get(&key)
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| format!("Unable to load {key}: {e}"))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@ -163,8 +163,8 @@ pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
|
||||
log::debug!("invoke: toggle_package({}, {})", key, enable);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.toggle_package(key, if enable { ToggleAction::EnableRecursive } else { ToggleAction::Disable })
|
||||
.map_err(|e| e.to_string())
|
||||
appd.toggle_package(key.clone(), if enable { ToggleAction::EnableRecursive } else { ToggleAction::Disable })
|
||||
.map_err(|e| format!("Unable to toggle {key}: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -219,9 +219,12 @@ pub async fn create_package(
|
||||
games: Some(games)
|
||||
};
|
||||
|
||||
std::fs::create_dir(&dir).map_err(|e| e.to_string())?;
|
||||
let json = serde_json::to_string_pretty(&manifest).map_err(|e| e.to_string())?;
|
||||
std::fs::write(dir.join("manifest.json"), json).map_err(|e| e.to_string())?;
|
||||
std::fs::create_dir(&dir)
|
||||
.map_err(|e| format!("Unable to create {} directory: {e}", dir.file_name().unwrap_or_default().to_string_lossy()))?;
|
||||
let json = serde_json::to_string_pretty(&manifest)
|
||||
.map_err(|e| format!("Unable to serialize manifest.json: {e}"))?;
|
||||
std::fs::write(dir.join("manifest.json"), json)
|
||||
.map_err(|e| format!("Unable to write manifest.json: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -233,7 +236,7 @@ pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>)
|
||||
let mut appd = state.lock().await;
|
||||
appd.pkgs.reload_all()
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(|e| format!("Unable to reload packages: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -287,17 +290,17 @@ pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), Stri
|
||||
|
||||
// Can be this lazy for now as there are only two short lists
|
||||
let listings1 = PackageStore::fetch_listings(Game::Ongeki).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| format!("Unable to fetch Chunithm listings: {e}"))?;
|
||||
|
||||
let listings2 = PackageStore::fetch_listings(Game::Chunithm).await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| format!("Unable to fetch Ongeki listings: {e}"))?;
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.pkgs.process_fetched_listings(listings1, Game::Ongeki);
|
||||
appd.pkgs.process_fetched_listings(listings2, Game::Chunithm);
|
||||
|
||||
appd.pkgs.save().await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| format!("Unable to save package-list: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -306,7 +309,9 @@ pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), Stri
|
||||
pub async fn list_profiles() -> Result<Vec<ProfileMeta>, String> {
|
||||
log::debug!("invoke: list_profiles");
|
||||
|
||||
let list = crate::profiles::list_profiles().await.map_err(|e| e.to_string())?;
|
||||
let list = crate::profiles::list_profiles()
|
||||
.await
|
||||
.map_err(|e| format!("Unable to list profiles: {e}"))?;
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
@ -320,7 +325,7 @@ pub async fn init_profile(
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
let new_profile = Profile::new(ProfileMeta { game, name })
|
||||
.map_err(|e| format!("Unable to create profile: {}", e))?;
|
||||
.map_err(|e| format!("Unable to create a profile: {}", e))?;
|
||||
|
||||
appd.profile = Some(new_profile);
|
||||
|
||||
@ -332,7 +337,8 @@ pub async fn load_profile(state: State<'_, Mutex<AppData>>, game: Game, name: St
|
||||
log::debug!("invoke: load_profile({} {:?})", game, name);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
|
||||
appd.switch_profile(game, name)
|
||||
.map_err(|e| format!("Unable to switch profile: {e}"))?;
|
||||
appd.fix();
|
||||
Ok(())
|
||||
}
|
||||
@ -439,7 +445,7 @@ pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<()
|
||||
appd.fix();
|
||||
match &mut appd.profile {
|
||||
Some(p) => {
|
||||
p.save().map_err(|e| e.to_string())
|
||||
p.save().map_err(|e| format!("Unable to save profile: {e}"))
|
||||
},
|
||||
None => {
|
||||
Err("no profile to save".to_owned())
|
||||
@ -453,16 +459,7 @@ pub async fn load_segatools_ini(state: State<'_, Mutex<AppData>>, path: PathBuf)
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
if let Some(p) = &mut appd.profile {
|
||||
let str = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
|
||||
// Stupid path escape hack for the ini reader
|
||||
let str = str.replace("\\", "\\\\").replace("\\\\\\\\", "\\\\");
|
||||
let ini = Ini::load_from_str(&str).map_err(|e| e.to_string())?;
|
||||
p.data.sgt.load_from_ini(&ini, p.config_dir()).map_err(|e| e.to_string())?;
|
||||
p.data.network.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||
if let Some(kb) = &mut p.data.keyboard {
|
||||
kb.load_from_ini(&ini).map_err(|e| e.to_string())?;
|
||||
}
|
||||
p.save().map_err(|e| e.to_string())?;
|
||||
p.load_segatools_ini(path).map_err(|e| format!("Unable to load segatools.ini: {e}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -473,21 +470,27 @@ pub async fn create_shortcut(_app: AppHandle, profile_meta: ProfileMeta) -> Resu
|
||||
log::debug!("invoke: create_shortcut({:?})", profile_meta);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return util::create_shortcut(_app, &profile_meta).map_err(|e| e.to_string());
|
||||
return util::create_shortcut(_app, &profile_meta)
|
||||
.map_err(|e| format!("Unable to create a shortcut: {e}"));
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
return Err("unsupported".to_owned());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bool, files: Vec<String>) -> Result<(), String> {
|
||||
pub async fn export_profile(
|
||||
state: State<'_, Mutex<AppData>>,
|
||||
is_diagnostic: bool,
|
||||
export_keychip: bool,
|
||||
files: Vec<String>
|
||||
) -> Result<(), String> {
|
||||
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
|
||||
|
||||
let appd = state.lock().await;
|
||||
match &appd.profile {
|
||||
Some(p) => {
|
||||
p.export(export_keychip, files)
|
||||
.map_err(|e| e.to_string())?;
|
||||
p.export(export_keychip, files, is_diagnostic)
|
||||
.map_err(|e| format!("Unable to export profile: {e}"))?;
|
||||
}
|
||||
None => {
|
||||
let err = "export_profile: no profile".to_owned();
|
||||
@ -503,7 +506,7 @@ pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bo
|
||||
pub async fn import_profile(path: PathBuf) -> Result<(), String> {
|
||||
log::debug!("invoke: import_profile({:?})", path);
|
||||
|
||||
Profile::import(path).map_err(|e| e.to_string())
|
||||
Profile::import(path).map_err(|e| format!("Unable to import profile: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -515,11 +518,13 @@ pub async fn clear_cache(state: State<'_, Mutex<AppData>>) -> Result<(), String>
|
||||
let dir = p.data_dir().join("mu3-mods-cache");
|
||||
let path = dir.join("data_cache.bin");
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).map_err(|e| e.to_string())?;
|
||||
std::fs::remove_file(path)
|
||||
.map_err(|e| format!("Unable to delete data_cache: {e}"))?;
|
||||
}
|
||||
let path = dir.join("data_fumen_analysis_cache.bin");
|
||||
if path.exists() {
|
||||
std::fs::remove_file(path).map_err(|e| e.to_string())?;
|
||||
std::fs::remove_file(path)
|
||||
.map_err(|e| format!("Unable to delete data_fumen_analysis_cache: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -527,14 +532,14 @@ pub async fn clear_cache(state: State<'_, Mutex<AppData>>) -> Result<(), String>
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||
pub fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||
log::debug!("invoke: list_platform_capabilities");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
return Ok(vec!["display".to_owned(), "shortcut".to_owned(), "chunithm".to_owned()]);
|
||||
return Ok(vec!["display".to_owned(), "shortcut".to_owned(), "chunithm".to_owned(), "preload-bat".to_owned()]);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return Ok(vec!["wine".to_owned()]);
|
||||
return Ok(vec!["wine".to_owned(), "preload-sh".to_owned()]);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -559,7 +564,7 @@ pub async fn set_global_config(state: State<'_, Mutex<AppData>>, field: GlobalCo
|
||||
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value,
|
||||
GlobalConfigField::Verbose => appd.cfg.verbose = value,
|
||||
};
|
||||
appd.write().map_err(|e| e.to_string())
|
||||
appd.write().map_err(|e| format!("Unable to write config.json: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -607,7 +612,7 @@ pub async fn file_exists(path: String) -> Result<bool, ()> {
|
||||
// Easier than trying to get the barely-documented tauri permissions system to work
|
||||
#[tauri::command]
|
||||
pub async fn open_file(path: String) -> Result<(), String> {
|
||||
open::that(path).map_err(|e| e.to_string())?;
|
||||
open::that(&path).map_err(|e| format!("Unable to open {path}: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -632,12 +637,14 @@ pub async fn list_com_ports() -> Result<BTreeMap<String, i32>, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: String) -> Result<Vec<Patch>, String> {
|
||||
log::debug!("invoke: list_patches({})", target);
|
||||
pub async fn list_patches(state: State<'_, Mutex<AppData>>, target: PathBuf) -> Result<Vec<Patch>, String> {
|
||||
log::debug!("invoke: list_patches({:?})", target);
|
||||
|
||||
let mut appd = state.lock().await;
|
||||
appd.fix();
|
||||
let list = appd.patch_vec.find_patches(target).map_err(|e| e.to_string())?;
|
||||
let list = appd.patch_vec
|
||||
.find_patches(&target)
|
||||
.map_err(|e| format!("Unable to list patches for {}: {e}", target.file_name().unwrap_or_default().to_string_lossy()))?;
|
||||
|
||||
Ok(list)
|
||||
}
|
@ -17,6 +17,12 @@ pub struct DownloadTick {
|
||||
ratio: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct DownloadEndPayload {
|
||||
pub key: PkgKey,
|
||||
pub enable: bool
|
||||
}
|
||||
|
||||
impl DownloadHandler {
|
||||
pub fn new(app: AppHandle) -> DownloadHandler {
|
||||
DownloadHandler {
|
||||
@ -25,7 +31,7 @@ impl DownloadHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_zip(&mut self, zip_path: &PathBuf, pkg: &Package) -> Result<()> {
|
||||
pub fn download_zip(&mut self, zip_path: &PathBuf, pkg: &Package, enable: bool) -> Result<()> {
|
||||
let rmt = pkg.rmt.as_ref()
|
||||
.ok_or_else(|| anyhow!("Attempted to download a package without remote data"))?
|
||||
.clone();
|
||||
@ -34,12 +40,18 @@ impl DownloadHandler {
|
||||
} else {
|
||||
// TODO clear cache button should clear this
|
||||
self.paths.insert(zip_path.clone());
|
||||
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt));
|
||||
tauri::async_runtime::spawn(Self::download_zip_proc(self.app.clone(), zip_path.clone(), pkg.key(), rmt, enable));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_zip_proc(app: AppHandle, zip_path: PathBuf, pkg_key: PkgKey, rmt: Remote) -> Result<()> {
|
||||
async fn download_zip_proc(
|
||||
app: AppHandle,
|
||||
zip_path: PathBuf,
|
||||
pkg_key: PkgKey,
|
||||
rmt: Remote,
|
||||
enable: bool
|
||||
) -> Result<()> {
|
||||
use futures::StreamExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
@ -66,7 +78,10 @@ impl DownloadHandler {
|
||||
|
||||
log::debug!("downloaded to {:?}", zip_path);
|
||||
|
||||
app.emit("download-end", pkg_key)?;
|
||||
app.emit("download-end", DownloadEndPayload {
|
||||
key: pkg_key,
|
||||
enable
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -102,16 +102,23 @@ pub async fn run(_args: Vec<String>) {
|
||||
});
|
||||
|
||||
app.listen("download-end", closure!(clone apph, |ev| {
|
||||
let raw = ev.payload();
|
||||
log::debug!("download-end triggered: {}", raw);
|
||||
let key = PkgKey(raw[1..raw.len()-1].to_owned());
|
||||
let apph = apph.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mutex = apph.state::<Mutex<AppData>>();
|
||||
let mut appd = mutex.lock().await;
|
||||
let res = appd.pkgs.install_package(&key, true, false).await;
|
||||
log::debug!("download-end install {:?}", res);
|
||||
});
|
||||
let payload = serde_json::from_str::<download_handler::DownloadEndPayload>(ev.payload());
|
||||
match payload {
|
||||
Ok(payload) => {
|
||||
log::debug!("download-end triggered: {:?}", payload);
|
||||
let key = payload.key;
|
||||
let apph = apph.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mutex = apph.state::<Mutex<AppData>>();
|
||||
let mut appd = mutex.lock().await;
|
||||
let res = appd.pkgs.install_package(&key, true, false, payload.enable).await;
|
||||
log::debug!("download-end install {:?}", res);
|
||||
});
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("invalid download payload: {err}");
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
app.listen("launch-end", closure!(clone apph, |_| {
|
||||
@ -134,11 +141,13 @@ pub async fn run(_args: Vec<String>) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mutex = apph.state::<Mutex<AppData>>();
|
||||
let mut appd = mutex.lock().await;
|
||||
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
||||
log::debug!(
|
||||
"install-end-prelude toggle {:?}",
|
||||
res
|
||||
);
|
||||
if payload.enable == true {
|
||||
let res = appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf);
|
||||
log::debug!(
|
||||
"install-end-prelude toggle {:?}",
|
||||
res
|
||||
);
|
||||
}
|
||||
use tauri::Emitter;
|
||||
let res = apph.emit("install-end", payload);
|
||||
log::debug!("install-end {:?}", res);
|
||||
@ -261,7 +270,7 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
||||
log::info!("deep link: {}", url);
|
||||
|
||||
let regex = regex::Regex::new(
|
||||
r"rainycolor://v1/install/rainy\.patafour\.zip/([^/]+)/([^/]+)/[0-9]+\.[0-9]+\.[0-9]+/"
|
||||
r"rainycolor://v1/install/www\.rainycolor\.org/([^/]+)/([^/]+)/[0-9]+\.[0-9]+\.[0-9]+/"
|
||||
).expect("Invalid regex");
|
||||
|
||||
if let Some(caps) = regex.captures(url) {
|
||||
@ -273,11 +282,13 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
|
||||
let mut appd = mutex.lock().await;
|
||||
if appd.pkgs.is_offline() {
|
||||
log::warn!("Deep link installation failed: offline");
|
||||
} else if let Err(e) = appd.pkgs.install_package(&key, true, true).await {
|
||||
} else if let Err(e) = appd.pkgs.install_package(&key, true, true, true).await {
|
||||
log::warn!("Deep link installation failed: {}", e.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log::error!("No caps");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,10 @@ impl Display {
|
||||
}
|
||||
|
||||
display_set.apply().map_err(
|
||||
|_| anyhow!("The selected monitor has been disconnected or doesn't support the chosen display mode")
|
||||
|e| {
|
||||
log::error!("display_set: {e}");
|
||||
anyhow!("The selected monitor has been disconnected or doesn't support the chosen display mode")
|
||||
}
|
||||
)?;
|
||||
displayz::refresh()?;
|
||||
|
||||
|
@ -72,7 +72,7 @@ pub fn prepare_dlls(
|
||||
game: Game,
|
||||
enabled_pkgs: &BTreeSet<PkgKey>,
|
||||
store: &PackageStore,
|
||||
) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
|
||||
) -> (Vec<PathBuf>, Vec<PathBuf>) {
|
||||
let mut res_game = Vec::new();
|
||||
let mut res_amd = Vec::new();
|
||||
for pkg in enabled_pkgs {
|
||||
@ -99,5 +99,5 @@ pub fn prepare_dlls(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((res_game, res_amd))
|
||||
(res_game, res_amd)
|
||||
}
|
@ -43,7 +43,11 @@ impl Segatools {
|
||||
if let Some(aime_path) = s.get("aimePath") {
|
||||
if let Some(game_dir) = self.target.parent() {
|
||||
let target = game_dir.join(aime_path);
|
||||
std::fs::copy(target, config_dir.as_ref().join("aime.txt"))?;
|
||||
if target.exists() {
|
||||
std::fs::copy(target, config_dir.as_ref().join("aime.txt"))?;
|
||||
} else {
|
||||
log::warn!("aime.txt was configured in segatools.ini but it doesn't exist");
|
||||
}
|
||||
} else {
|
||||
log::error!("profile doesn't have a game directory");
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ impl PatchFileVec {
|
||||
}
|
||||
|
||||
pub fn find_patches(&self, target: impl AsRef<Path>) -> Result<Vec<Patch>> {
|
||||
if !target.as_ref().exists() {
|
||||
log::warn!("invalid target path: {:?}", target.as_ref());
|
||||
anyhow::bail!("Unable to open {:?}. Make sure the game path is correct.", target.as_ref());
|
||||
}
|
||||
let checksum = try_digest(target.as_ref())?;
|
||||
|
||||
let mut res_patches = Vec::new();
|
||||
|
@ -109,7 +109,7 @@ impl Package {
|
||||
loc: None,
|
||||
rmt: Some(Remote {
|
||||
package_url: p.package_url,
|
||||
download_url: v.download_url,
|
||||
download_url: v.download_url.replace("https://rainy.patafour.zip/", "https://www.rainycolor.org/"),
|
||||
icon: v.icon,
|
||||
deprecated: p.is_deprecated,
|
||||
nsfw: p.has_nsfw_content,
|
||||
|
@ -23,7 +23,8 @@ pub struct PackageStore {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Payload {
|
||||
pub pkg: PkgKey
|
||||
pub pkg: PkgKey,
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
@ -180,7 +181,13 @@ impl PackageStore {
|
||||
self.offline = false;
|
||||
}
|
||||
|
||||
pub async fn install_package(&mut self, key: &PkgKey, force: bool, install_deps: bool) -> Result<InstallResult> {
|
||||
pub async fn install_package(
|
||||
&mut self,
|
||||
key: &PkgKey,
|
||||
force: bool,
|
||||
install_deps: bool,
|
||||
enable: bool
|
||||
) -> Result<InstallResult> {
|
||||
log::info!("installation request: {}/{}/{}", key, force, install_deps);
|
||||
|
||||
let pkg = self.store.get(key)
|
||||
@ -193,7 +200,8 @@ impl PackageStore {
|
||||
}
|
||||
|
||||
self.app.emit("install-start", Payload {
|
||||
pkg: key.to_owned()
|
||||
pkg: key.to_owned(),
|
||||
enable
|
||||
})?;
|
||||
|
||||
let rmt = pkg.rmt.as_ref()
|
||||
@ -203,7 +211,7 @@ impl PackageStore {
|
||||
let mut set = HashSet::new();
|
||||
self.resolve_deps(rmt.clone(), &mut set)?;
|
||||
for dep in set {
|
||||
Box::pin(self.install_package(&dep, false, false)).await?;
|
||||
Box::pin(self.install_package(&dep, false, false, enable)).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +222,7 @@ impl PackageStore {
|
||||
let part_path = zip_path.join(".part");
|
||||
|
||||
if !zip_path.exists() && !part_path.exists() {
|
||||
self.dlh.download_zip(&zip_path, &pkg)?;
|
||||
self.dlh.download_zip(&zip_path, &pkg, enable)?;
|
||||
log::debug!("deferring {}", key);
|
||||
return Ok(InstallResult::Deferred);
|
||||
}
|
||||
@ -230,7 +238,8 @@ impl PackageStore {
|
||||
self.reload_package(key.to_owned()).await;
|
||||
|
||||
self.app.emit("install-end-prelude", Payload {
|
||||
pkg: key.to_owned()
|
||||
pkg: key.to_owned(),
|
||||
enable
|
||||
})?;
|
||||
|
||||
log::info!("installed {}", key);
|
||||
@ -252,7 +261,8 @@ impl PackageStore {
|
||||
|
||||
if rv.is_ok() {
|
||||
self.app.emit("install-end-prelude", Payload {
|
||||
pkg: key.to_owned()
|
||||
pkg: key.to_owned(),
|
||||
enable: false
|
||||
})?;
|
||||
log::info!("deleted {}", key);
|
||||
}
|
||||
|
@ -255,6 +255,7 @@ impl Profile {
|
||||
|
||||
let mut game_builder;
|
||||
let mut amd_builder;
|
||||
let mut prescript = None;
|
||||
|
||||
let target_path = PathBuf::from(&self.data.sgt.target);
|
||||
let exe_dir = target_path.parent().ok_or_else(|| anyhow!("Invalid target path"))?;
|
||||
@ -274,6 +275,12 @@ impl Profile {
|
||||
amd_builder.arg("cmd.exe");
|
||||
}
|
||||
|
||||
let script_ext = if cfg!(target_os = "windows") { "bat" } else { "sh" };
|
||||
let prescript_path = self.config_dir().join(format!("pre.{script_ext}"));
|
||||
if prescript_path.exists() {
|
||||
prescript = util::spawn_script(prescript_path, &exe_dir, "\"STARTLINER launch script\"");
|
||||
}
|
||||
|
||||
amd_builder.env(
|
||||
"SEGATOOLS_CONFIG_PATH",
|
||||
&ini_path,
|
||||
@ -420,6 +427,23 @@ impl Profile {
|
||||
util::pkill("amdaemon.exe").await;
|
||||
}
|
||||
|
||||
if let Some(mut _child) = prescript {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// child.kill() doesn't work
|
||||
util::pkill_title("STARTLINER launch script").await;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
_child.start_kill()?;
|
||||
}
|
||||
}
|
||||
|
||||
let postscript_path = self.config_dir().join(format!("post.{script_ext}"));
|
||||
if postscript_path.exists() {
|
||||
_ = util::spawn_script(postscript_path, &exe_dir, "\"STARTLINER end script\"");
|
||||
}
|
||||
|
||||
set.join_next().await.expect("No spawn").expect("No result");
|
||||
|
||||
log::debug!("Fin");
|
||||
@ -455,6 +479,21 @@ impl Profile {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_segatools_ini(&mut self, path: impl AsRef<Path>) -> Result<()> {
|
||||
let str = std::fs::read_to_string(path.as_ref())?;
|
||||
// Stupid path escape hack for the ini reader
|
||||
let str = str.replace("\\", "\\\\").replace("\\\\\\\\", "\\\\");
|
||||
let ini = ini::Ini::load_from_str(&str)?;
|
||||
self.data.sgt.load_from_ini(&ini, self.config_dir())?;
|
||||
self.data.network.load_from_ini(&ini)?;
|
||||
if let Some(kb) = &mut self.data.keyboard {
|
||||
kb.load_from_ini(&ini)?;
|
||||
}
|
||||
self.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfilePaths for Profile {
|
||||
|
@ -36,7 +36,7 @@ impl Profile {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>) -> anyhow::Result<()> {
|
||||
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>, is_diagnostic: bool) -> anyhow::Result<()> {
|
||||
let mut prf = self.clone();
|
||||
|
||||
let dir = util::config_dir().join("exports");
|
||||
@ -45,19 +45,21 @@ impl Profile {
|
||||
std::fs::create_dir(&dir)?;
|
||||
}
|
||||
|
||||
let path = dir.join(format!("{}-{}-template.zip", &self.meta.game, &self.meta.name));
|
||||
let path = dir.join(format!("{}-{}-{}.zip", &self.meta.game, &self.meta.name, if is_diagnostic { "diagnostic" } else { "template" } ));
|
||||
|
||||
{
|
||||
let sgt = &mut prf.data.sgt;
|
||||
sgt.target = PathBuf::new();
|
||||
if sgt.amfs.is_absolute() {
|
||||
sgt.amfs = PathBuf::new();
|
||||
}
|
||||
if sgt.option.is_absolute() {
|
||||
sgt.option = PathBuf::new();
|
||||
}
|
||||
if sgt.appdata.is_absolute() {
|
||||
sgt.appdata = PathBuf::new();
|
||||
if !is_diagnostic {
|
||||
sgt.target = PathBuf::new();
|
||||
if sgt.amfs.is_absolute() {
|
||||
sgt.amfs = PathBuf::new();
|
||||
}
|
||||
if sgt.option.is_absolute() {
|
||||
sgt.option = PathBuf::new();
|
||||
}
|
||||
if sgt.appdata.is_absolute() {
|
||||
sgt.appdata = PathBuf::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +68,7 @@ impl Profile {
|
||||
if network.local_path.is_absolute() {
|
||||
network.local_path = PathBuf::new();
|
||||
}
|
||||
if !export_keychip {
|
||||
if !export_keychip || is_diagnostic {
|
||||
network.keychip = String::new();
|
||||
}
|
||||
}
|
||||
@ -83,6 +85,29 @@ impl Profile {
|
||||
zip.write_all(&std::fs::read(self.config_dir().join(file))?)?;
|
||||
}
|
||||
|
||||
if is_diagnostic {
|
||||
let name = "mu3.exe.log";
|
||||
let path = self.data_dir().join(name);
|
||||
if path.exists() {
|
||||
zip.start_file(name, options)?;
|
||||
zip.write_all(&std::fs::read(path)?)?;
|
||||
}
|
||||
|
||||
let name = "chusanApp.exe.log";
|
||||
let path = self.data_dir().join(name);
|
||||
if path.exists() {
|
||||
zip.start_file(name, options)?;
|
||||
zip.write_all(&std::fs::read(path)?)?;
|
||||
}
|
||||
|
||||
let name = "amdaemon.exe.log";
|
||||
let path = self.data_dir().join(name);
|
||||
if path.exists() {
|
||||
zip.start_file(name, options)?;
|
||||
zip.write_all(&std::fs::read(path)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
zip.finish()?;
|
||||
|
||||
Ok(())
|
||||
|
@ -105,6 +105,12 @@ pub async fn pkill(process_name: &str) {
|
||||
.creation_flags(CREATE_NO_WINDOW).output().await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn pkill_title(window_title: &str) {
|
||||
_ = Command::new("taskkill.exe").arg("/fi").raw_arg(format!("\"WindowTitle eq {window_title}\""))
|
||||
.creation_flags(CREATE_NO_WINDOW).output().await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn pkill(process_name: &str) {
|
||||
_ = Command::new("pkill").arg(process_name)
|
||||
@ -214,4 +220,38 @@ pub fn create_shortcut(
|
||||
file.Save(Some(lnk_path.to_str().ok_or_else(|| anyhow!("Illegal shortcut path"))?), true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn spawn_script(path: impl AsRef<Path>, cwd: impl AsRef<Path>, title: &str) -> Option<tokio::process::Child> {
|
||||
// Seems? like this autism is needed to:
|
||||
// 1. pop up a cmd window
|
||||
// 2. launch the batch
|
||||
// 3. die afterwards
|
||||
let mut c;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
c = Command::new("cmd");
|
||||
c.args(["/C", "start"]);
|
||||
c.raw_arg(title);
|
||||
c.args(["cmd", "/C"]);
|
||||
c.arg(path.as_ref());
|
||||
c.current_dir(cwd);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
c = Command::new("sh");
|
||||
c.arg(path.as_ref());
|
||||
c.current_dir(cwd);
|
||||
}
|
||||
|
||||
log::debug!("Script launch: {:?}", c);
|
||||
match c.spawn() {
|
||||
Ok(child) => Some(child),
|
||||
Err(e) => {
|
||||
log::error!("unable to launch {:?}: {e}", path.as_ref());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "STARTLINER",
|
||||
"version": "0.18.2",
|
||||
"version": "0.21.0",
|
||||
"identifier": "zip.patafour.startliner",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
@ -295,7 +295,7 @@ listen<DownloadingStatus>('download-progress', (event) => {
|
||||
pkg.hasAvailableUpdates
|
||||
"
|
||||
icon="pi pi-download"
|
||||
label="UPDATE ALL"
|
||||
:label="t('updateAll')"
|
||||
size="small"
|
||||
class="mr-4 m-2.5"
|
||||
@click="pkg.updateAll()"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
@ -12,6 +12,7 @@ const props = defineProps({
|
||||
filename: String,
|
||||
promptname: String,
|
||||
extension: String,
|
||||
defaultValue: String,
|
||||
});
|
||||
|
||||
const exists = ref(false);
|
||||
@ -35,6 +36,12 @@ const save = async () => {
|
||||
};
|
||||
|
||||
const filePick = async () => {
|
||||
if (props.defaultValue !== undefined) {
|
||||
contents.value = props.defaultValue;
|
||||
exists.value = true;
|
||||
await save();
|
||||
return;
|
||||
}
|
||||
const p = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
@ -54,13 +61,13 @@ const filePick = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
onMounted(async () => {
|
||||
if (props.filename === undefined) {
|
||||
throw new Error('FileEditor without a filename');
|
||||
}
|
||||
target_path.value = await path.join(await prf.configDir, props.filename);
|
||||
await load(target_path.value);
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -12,8 +12,8 @@ invoke('get_changelog').then((s) => (changelog.value = s as string));
|
||||
|
||||
<template>
|
||||
<h1>About</h1>
|
||||
STARTLINER is a launcher, configuration tool and mod manager for
|
||||
O.N.G.E.K.I. and CHUNITHM.
|
||||
STARTLINER is a configuration tool, mod manager and start.bat automation
|
||||
engine for O.N.G.E.K.I. and CHUNITHM.
|
||||
<h1>Changelog</h1>
|
||||
<ScrollPanel style="height: 200px">
|
||||
<div class="markdown">
|
||||
|
@ -53,6 +53,6 @@ const remove = async () => {
|
||||
class="self-center ml-4"
|
||||
style="width: 2rem; height: 2rem"
|
||||
:loading="pkg?.js.downloading"
|
||||
v-on:click="async () => await pkgs.install(pkg)"
|
||||
v-on:click="async () => await pkgs.install(pkg, true)"
|
||||
/>
|
||||
</template>
|
||||
|
@ -71,10 +71,12 @@ const recommendedTooltip = computed(() => {
|
||||
const installRecommended = () => {
|
||||
if (prf.current?.meta.game === 'ongeki') {
|
||||
pkgs.installFromKey('segatools-mu3hook');
|
||||
prf.current.data.sgt.hook = 'segatools-mu3hook';
|
||||
}
|
||||
if (prf.current?.meta.game === 'chunithm') {
|
||||
pkgs.installFromKey('segatools-chusanhook');
|
||||
pkgs.installFromKey('mempatcher-mempatcher');
|
||||
prf.current.data.sgt.hook = 'segatools-chusanhook';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -60,7 +60,7 @@ prf.reload();
|
||||
<AimeOptions />
|
||||
<MiscOptions />
|
||||
<OptionCategory
|
||||
title="Extensions"
|
||||
:title="t('cfg.extensions.title')"
|
||||
v-if="prf.current?.meta.game === 'chunithm'"
|
||||
>
|
||||
<OptionRow :title="t('cfg.extensions.saekawa')">
|
||||
|
@ -29,30 +29,47 @@ const files = new Set<string>();
|
||||
).includes('chunithm');
|
||||
})();
|
||||
|
||||
const fileList = [
|
||||
'aime.txt',
|
||||
'inohara.cfg',
|
||||
'saekawa.toml',
|
||||
'mu3.ini',
|
||||
'segatools-base.ini',
|
||||
'pre.sh',
|
||||
'pre.bat',
|
||||
'post.sh',
|
||||
'post.bat',
|
||||
];
|
||||
|
||||
const diagnosticList = {
|
||||
ongeki: ['mu3.ini', 'segatools-base.ini'],
|
||||
chunithm: ['segatools-base.ini'],
|
||||
};
|
||||
|
||||
const diagnostic = ref(false);
|
||||
|
||||
const exportTemplate = async () => {
|
||||
const fl = [...files.values()];
|
||||
exportVisible.value = false;
|
||||
await invoke('export_profile', {
|
||||
exportKeychip: exportKeychip.value,
|
||||
files: fl,
|
||||
isDiagnostic: diagnostic.value,
|
||||
files:
|
||||
diagnostic.value === true
|
||||
? diagnosticList[prf.current!.meta.game]
|
||||
: fl,
|
||||
});
|
||||
await invoke('open_file', {
|
||||
path: await path.join(await general.configDir, 'exports'),
|
||||
});
|
||||
};
|
||||
|
||||
const fileList = {
|
||||
ongeki: ['aime.txt', 'inohara.cfg', 'mu3.ini', 'segatools-base.ini'],
|
||||
chunithm: ['aime.txt', 'saekawa.toml', 'segatools-base.ini'],
|
||||
};
|
||||
|
||||
const fileListCurrent: Ref<string[]> = ref([]);
|
||||
|
||||
const recalcFileList = async () => {
|
||||
const res: string[] = [];
|
||||
files.clear();
|
||||
for (const idx in fileList[prf.current!.meta.game]) {
|
||||
const f = fileList[prf.current!.meta.game][idx];
|
||||
for (const f of fileList) {
|
||||
const p = await path.join(await prf.configDir, f);
|
||||
if (await invoke('file_exists', { path: p })) {
|
||||
res.push(f);
|
||||
@ -91,16 +108,36 @@ const importPick = async () => {
|
||||
:visible="exportVisible"
|
||||
:closable="false /*this shit doesn't work */"
|
||||
:header="`${t('profile.export')} ${prf.current?.meta.name}`"
|
||||
:style="{ width: '300px', scale: client.scaleValue }"
|
||||
:style="{ width: '330px', scale: client.scaleValue }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col items-center">
|
||||
<SelectButton
|
||||
v-model="diagnostic"
|
||||
:options="[
|
||||
{
|
||||
title: t('profile.standardExport'),
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
title: t('profile.diagnostic'),
|
||||
value: true,
|
||||
},
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
>
|
||||
</SelectButton>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="grow">{{ t('profile.export') }} keychip</div>
|
||||
<ToggleSwitch v-model="exportKeychip" />
|
||||
<ToggleSwitch :disabled="diagnostic" v-model="exportKeychip" />
|
||||
</div>
|
||||
<div class="flex flex-row" v-for="f in fileListCurrent">
|
||||
<div class="grow">{{ t('profile.export') }} {{ f }}</div>
|
||||
<ToggleSwitch
|
||||
:disabled="diagnostic"
|
||||
:model-value="true"
|
||||
@update:model-value="
|
||||
(v) => {
|
||||
@ -178,8 +215,11 @@ const importPick = async () => {
|
||||
style="width: 200px"
|
||||
:options="[
|
||||
{ title: 'English', value: 'en' },
|
||||
// { title: '日本語', value: 'ja' },
|
||||
{ title: 'Español', value: 'es' },
|
||||
{ title: '한국어', value: 'ko' },
|
||||
{ title: '日本語', value: 'ja' },
|
||||
{ title: 'Polski', value: 'pl' },
|
||||
{ title: '简体中文', value: 'zh-Hans' },
|
||||
]"
|
||||
size="small"
|
||||
option-label="title"
|
||||
|
@ -203,7 +203,7 @@ const tryStart = () => {
|
||||
v-tooltip="disabledTooltip"
|
||||
:disabled="disabledTooltip !== null"
|
||||
icon="pi pi-play"
|
||||
label="START"
|
||||
:label="t('start.button.start')"
|
||||
aria-label="start"
|
||||
size="small"
|
||||
class="m-2.5"
|
||||
|
@ -17,6 +17,7 @@ const install = async () => {
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(props.pkg),
|
||||
force: true,
|
||||
enable: false,
|
||||
});
|
||||
} catch (err) {
|
||||
if (props.pkg !== undefined) {
|
||||
|
@ -182,7 +182,6 @@ const canSkipPrimarySwitch = computed(
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
|
@ -1,13 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import FileEditor from '../FileEditor.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { invoke } from '../../invoke';
|
||||
import { usePrfStore } from '../../stores';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const extension = ref('');
|
||||
|
||||
invoke('list_platform_capabilities').then(async (v: unknown) => {
|
||||
if (Array.isArray(v)) {
|
||||
if (v.includes('preload-sh')) {
|
||||
extension.value = 'sh';
|
||||
} else if (v.includes('preload-bat')) {
|
||||
extension.value = 'bat';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const prf = usePrfStore();
|
||||
</script>
|
||||
|
||||
@ -26,5 +40,35 @@ const prf = usePrfStore();
|
||||
<!-- <Button icon="pi pi-refresh" size="small" /> -->
|
||||
<FileEditor filename="segatools-base.ini" />
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
:title="t('cfg.misc.prescript')"
|
||||
:tooltip="t('cfg.misc.prescriptTooltip')"
|
||||
>
|
||||
<FileEditor
|
||||
v-if="extension === 'bat'"
|
||||
filename="pre.bat"
|
||||
:defaultValue="`@echo off\n\nREM This script will launch before (and alongside) the game\n`"
|
||||
/>
|
||||
<FileEditor
|
||||
v-else-if="extension === 'sh'"
|
||||
filename="pre.sh"
|
||||
:defaultValue="`#!/bin/sh\n\n# This script will launch before (and alongside) the game\n`"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
:title="t('cfg.misc.postscript')"
|
||||
:tooltip="t('cfg.misc.postscriptTooltip')"
|
||||
>
|
||||
<FileEditor
|
||||
v-if="extension === 'bat'"
|
||||
filename="post.bat"
|
||||
:defaultValue="`@echo off\n\nREM This script will launch after the game has died\n`"
|
||||
/>
|
||||
<FileEditor
|
||||
v-else-if="extension === 'sh'"
|
||||
filename="post.sh"
|
||||
:defaultValue="`#!/bin/sh\n\n# This script will launch after the game has died\n`"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import en from './i18n/en';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
export type Locale = 'en' | 'ja' | 'pl';
|
||||
export type Locale = 'en' | 'ja' | 'pl' | 'zh-Hans' | 'es' | 'ko';
|
||||
|
||||
const loadLocaleMessages = async (locale: Locale) => {
|
||||
return (await import(`./i18n/${locale}.ts`)).default;
|
||||
@ -13,7 +13,7 @@ const i18n = createI18n({
|
||||
fallbackLocale: 'en',
|
||||
warnHtmlInMessage: false,
|
||||
warnHtmlMessage: false,
|
||||
messages: { en, ja: {}, pl: {} },
|
||||
messages: { en, ja: {}, pl: {}, 'zh-Hans': {}, es: {}, ko: {} },
|
||||
});
|
||||
|
||||
const setLocale = async (locale: Locale) => {
|
||||
|
@ -9,6 +9,7 @@ export default {
|
||||
skip: 'Skip',
|
||||
close: 'Close',
|
||||
by: 'by {namespace}',
|
||||
updateAll: 'UPDATE ALL',
|
||||
start: {
|
||||
failed: 'Start check failed',
|
||||
accept: 'Run anyway',
|
||||
@ -44,8 +45,10 @@ export default {
|
||||
reallyDelete: 'Are you sure you want to delete {profile}?',
|
||||
template: 'STARTLINER template',
|
||||
importTemplate: 'Import template',
|
||||
exportTemplate: 'Export template',
|
||||
exportTemplate: 'Export profile',
|
||||
export: 'Export',
|
||||
standardExport: 'Template',
|
||||
diagnostic: 'Diagnostic',
|
||||
},
|
||||
creator: {
|
||||
header: 'Package creator',
|
||||
@ -163,6 +166,11 @@ export default {
|
||||
other: 'Other segatools options',
|
||||
otherTooltip:
|
||||
'Advanced or situational options not covered by STARTLINER',
|
||||
prescript: 'Launch script',
|
||||
prescriptTooltip: 'Optional script that runs before the game.',
|
||||
postscript: 'End script',
|
||||
postscriptTooltip:
|
||||
'Optional script that runs after the game has ended.',
|
||||
},
|
||||
extensions: {
|
||||
title: 'Extensions',
|
||||
|
283
src/i18n/es.ts
Normal file
283
src/i18n/es.ts
Normal file
@ -0,0 +1,283 @@
|
||||
export default {
|
||||
ok: 'Aceptar',
|
||||
cancel: 'Cancelar',
|
||||
enable: 'Activar',
|
||||
disable: 'Desactivar',
|
||||
default: 'Predeterminado',
|
||||
search: 'Buscar',
|
||||
next: 'Siguiente',
|
||||
skip: 'Omitir',
|
||||
close: 'Cerrar',
|
||||
by: 'por {namespace}',
|
||||
updateAll: 'ACTUALIZAR TODO',
|
||||
start: {
|
||||
failed: 'Verificación inicial fallida',
|
||||
accept: 'Ejecutar de todos modos',
|
||||
error: {
|
||||
package: 'Paquete faltante',
|
||||
dependency: 'Dependencia faltante',
|
||||
tool: 'Herramienta faltante',
|
||||
unknown: 'Error desconocido',
|
||||
},
|
||||
tooltip: {
|
||||
game: 'La ruta del juego debe especificarse',
|
||||
amfs: 'La ruta de amfs debe especificarse',
|
||||
segatools: 'Es necesario un paquete de segatools',
|
||||
},
|
||||
button: {
|
||||
start: 'INICIAR',
|
||||
stop: 'DETENER',
|
||||
unchecked: 'Omitir verificaciones e iniciar',
|
||||
shortcut: 'Crear acceso directo en escritorio',
|
||||
help: 'Ayuda',
|
||||
refresh: 'Reaplicar mods e iniciar',
|
||||
cache: 'Limpiar caché de mods',
|
||||
},
|
||||
},
|
||||
game: {
|
||||
ongeki: 'O.N.G.E.K.I.',
|
||||
chunithm: 'CHUNITHM',
|
||||
},
|
||||
profile: {
|
||||
welcome: '¡Bienvenido a STARTLINER! Comienza creando un perfil.',
|
||||
create: 'Perfil de {game}',
|
||||
delete: 'Eliminar perfil',
|
||||
reallyDelete: '¿Estás seguro de que quieres eliminar {profile}?',
|
||||
template: 'Plantilla de STARTLINER',
|
||||
importTemplate: 'Importar plantilla',
|
||||
exportTemplate: 'Exportar perfil',
|
||||
export: 'Exportar',
|
||||
standardExport: 'Plantilla',
|
||||
diagnostic: 'Diagnósticar',
|
||||
},
|
||||
creator: {
|
||||
header: 'Creador de paquetes',
|
||||
basic: 'Información básica',
|
||||
name: 'Nombre',
|
||||
description: 'Descripción',
|
||||
website: 'Sitio web',
|
||||
type: 'Tipo de paquete',
|
||||
rainy: 'Estándar',
|
||||
segatools: 'Segatools',
|
||||
native: 'Nativo',
|
||||
games: 'Juegos',
|
||||
packageFormat: 'Especificación de formato de paquete',
|
||||
},
|
||||
store: {
|
||||
installRecommended: 'Instalar paquetes recomendados',
|
||||
installed: 'Mostrar instalados',
|
||||
deprecated: 'Mostrar obsoletos',
|
||||
nsfw: 'Mostrar NSFW',
|
||||
incompatible: 'Este paquete actualmente es incompatible con STARTLINER.',
|
||||
|
||||
includeCategories: 'Incluir categorías',
|
||||
excludeCategories: 'Excluir categorías',
|
||||
},
|
||||
pkglist: {
|
||||
missing: 'Faltante',
|
||||
local: 'Paquetes locales',
|
||||
namespace: 'Por espacio de nombres',
|
||||
type: 'Por tipo',
|
||||
category: 'Por categoría',
|
||||
standard: 'Mods estándar',
|
||||
native: 'Mods nativos',
|
||||
segatools: 'segatools',
|
||||
unsupported: 'No compatible',
|
||||
exclusions: 'Exclusiones:',
|
||||
},
|
||||
patch: {
|
||||
loading: 'Cargando...',
|
||||
noneFound:
|
||||
'No se encontraron parches compatibles. Asegúrate de estar usando archivos desempaquetados y sin parches.',
|
||||
forceLoad: 'Forzar carga',
|
||||
'standard-shared-audio': 'Forzar modo de audio compartido, la frecuencia de muestreo del audio del sistema deber ser de 48000Hz',
|
||||
'standard-shared-audio-tooltip': 'Mejora la compatibilidad, pero puede incrementar la latencia',
|
||||
'standard-2ch': 'Forzar salida de audio de 2 canales',
|
||||
'standard-2ch-tooltip': 'Puede causar carga excesiva de bajos',
|
||||
'standard-song-timer': 'Desactivar temporizador de selección de pista',
|
||||
'standard-map-timer': 'Temporizador de seleccion de mapa',
|
||||
'standard-map-timer-tooltip': 'Si se fija en negativo, el temporizador será de 968 + valor (ej: 968 + -1 = 967)',
|
||||
'standard-ticket-timer': 'Temporizador de seleccion de tickets',
|
||||
'standard-ticket-timer-tooltip': 'Si se fija en negativo, el temporizador será de 968 + valor (ej: 968 + -1 = 967)',
|
||||
'standard-course-timer': 'Temporizador de seleccion de curso',
|
||||
'standard-course-timer-tooltip': 'Si se fija en negativo, el temporizador será de 968 + valor (ej: 968 + -1 = 967)',
|
||||
'standard-unlimited-tracks': 'Pistas maximas ilimitadas',
|
||||
'standard-unlimited-tracks-tooltip': 'Debe fijarse para reproducir más de 7 pistas por crédito',
|
||||
'standard-maximum-tracks': 'Pistas maximas',
|
||||
'standard-no-encryption': 'No cifrar',
|
||||
'standard-no-encryption-tooltip': 'Támbien desactivará TLS',
|
||||
'standard-no-tls': 'Sin TLS',
|
||||
'standard-no-tls-tooltip': 'Solucion alternativa para el servidor de títulos',
|
||||
'standard-head-to-head': 'Parche para juego cara a cara',
|
||||
'standard-head-to-head-tooltip': 'Soluciona la sincronización infinita al intentar conectarse al juego cara a cara',
|
||||
'standard-bypass-1080p': 'Omitir verificación de monitor 1080p',
|
||||
'standard-bypass-120hz': 'Omitir verificación de monitor de 120hz',
|
||||
'standard-force-free-play-text': 'Forzar texto de FREE PLAY',
|
||||
'standard-force-free-play-text-tooltip': 'Reemplaza el contador de creditos con FREE PLAY',
|
||||
'standard-custom-free-play-length': 'Longitud personalizada de texto FREE PLAY',
|
||||
'standard-custom-free-play-length-tooltip': 'Cambia la longitud del texto mostrado cuando se activa Forzar texto FREE PLAY',
|
||||
'standard-custom-free-play-text': 'Texto FREE PLAY personalizado',
|
||||
'standard-custom-free-play-text-tooltip': 'Reemplazar el texto FREE PLAY al utilizar Creditos Infinitos',
|
||||
'standard-localhost': 'Permitir 127.0.0.1/localhost como servidor de red',
|
||||
'standard-credit-freeze': 'Congelar créditos',
|
||||
'standard-credit-freeze-tooltip': 'Evita que se usen créditos. Debe haber al menos un crédito disponible para iniciar el juego o comprar boletos premium.',
|
||||
'standard-openssl-fix': 'Solucion a crasheo SHA de OpenSSL',
|
||||
'standard-openssl-fix-tooltip': 'Soluciona crasheos en CPUs Intel de 10ma generacion a mas recientes',
|
||||
},
|
||||
cfg: {
|
||||
afterRestart: 'Aplicado después de reiniciar',
|
||||
hardware: 'Hardware',
|
||||
segatools: {
|
||||
general: 'General',
|
||||
builtIn: 'Emulación integrada de Segatools',
|
||||
targetTooltip:
|
||||
'STARTLINER espera ejecutables desempaquetados colocados en datos de lo contrario limpios.',
|
||||
hooks: 'Hooks',
|
||||
ioModules: 'Módulos IO',
|
||||
ioModulesDesc: 'Esto debe coincidir con tu método de entrada deseado.',
|
||||
ioBuiltIn: 'segatools integrado (teclado)',
|
||||
io4: 'IO4 nativo',
|
||||
installTooltip: '{thing} puede descargarse desde la tienda de paquetes.',
|
||||
},
|
||||
display: {
|
||||
title: 'Pantalla',
|
||||
resolution: 'Resolución del juego',
|
||||
primary: 'Principal',
|
||||
target: 'Pantalla objetivo',
|
||||
mode: 'Modo',
|
||||
rotation: 'Rotación',
|
||||
refreshRate: 'Tasa de refresco',
|
||||
borderlessFullscreen: 'Pantalla completa sin bordes',
|
||||
borderlessFullscreenTooltip:
|
||||
'Coincidir la resolución de la pantalla con el juego.',
|
||||
dontSwitchPrimary: 'Omitir cambio de pantalla principal',
|
||||
dontSwitchPrimaryTooltip:
|
||||
'Solo habilitar esta opción si hay problemas al cambiar a la principal. Los monitores deben tener una tasa de refresco compartida.',
|
||||
index: 'Índice de pantalla',
|
||||
portrait: 'Vertical',
|
||||
landscape: 'Horizontal',
|
||||
flipped: 'invertido',
|
||||
window: 'Ventana',
|
||||
borderless: 'Ventana sin bordes',
|
||||
fullscreen: 'Pantalla completa',
|
||||
},
|
||||
network: {
|
||||
title: 'Red',
|
||||
type: 'Tipo de red',
|
||||
remote: 'Remoto',
|
||||
localArtemis: 'Local (ARTEMiS)',
|
||||
artemisPath: 'Ruta de ARTEMiS',
|
||||
address: 'Dirección del servidor',
|
||||
keychip: 'Keychip',
|
||||
subnet: 'Subred',
|
||||
addrSuffix: 'Sufijo de dirección',
|
||||
},
|
||||
aime: {
|
||||
type: 'Tipo de Aime',
|
||||
modules: 'Módulos Aime',
|
||||
code: 'Código Aime',
|
||||
codeTooltip:
|
||||
'Solo aplicable con la emulación integrada de segatools o con paquetes de terceros compatibles',
|
||||
aimedb: 'Usar AiMeDB para tarjetas físicas',
|
||||
aimedbTooltip:
|
||||
'Ya sea si las tarjetas físicas deben usar AiMeDB para recuperar códigos de acceso. Si el juego está usando una red alojada, habilitar esta opción para cargar los mismos datos de cuenta/perfil que obtendrías en un cabinete físico.',
|
||||
serialPort: 'Puerto serial Aime',
|
||||
serialPortTooltip: `Los puertos pueden verificarse en Dispositivos e Impresoras o en googlechromelabs.github.io/serial-terminal
|
||||
Para AIC Pico, debe seleccionarse el puerto AIME.`,
|
||||
serverName: 'Nombre del servidor',
|
||||
},
|
||||
misc: {
|
||||
title: 'Misceláneo',
|
||||
intel: 'Solución alternativa para el error de OpenSSL en Intel ≥10ma gen',
|
||||
intelTooltip: 'Es recomendable parchear amdaemon en su lugar.',
|
||||
other: 'Otras opciones de segatools',
|
||||
otherTooltip:
|
||||
'Opciones avanzadas o situacionales no cubiertas por STARTLINER',
|
||||
prescript: 'Script de inicio',
|
||||
prescriptTooltip: 'Script opcional que se ejecuta antes del juego.',
|
||||
postscript: 'Script de finalización',
|
||||
postscriptTooltip:
|
||||
'Script opcional que se ejecuta después de que el juego haya terminado.',
|
||||
},
|
||||
extensions: {
|
||||
title: 'Extensiones',
|
||||
bepInExConsole: 'Consola BepInEx',
|
||||
audioMode: 'Modo de audio',
|
||||
audioTooltip:
|
||||
'El modo exclusivo de 2 canales requiere 7EVENDAYSHOLIDAYS-ExclusiveAudio',
|
||||
audioShared: 'Compartido',
|
||||
audio6Ch: 'Exclusivo de 6 canales',
|
||||
audio2Ch: 'Exclusivo de 2 canales',
|
||||
sampleRate: 'Tasa de muestreo',
|
||||
blacklist: 'Lista negra de ID de canciones',
|
||||
blacklistTooltip:
|
||||
'Las puntuaciones en pistas dentro de este rango de ID no se guardarán ni subirán',
|
||||
bonusTracks: 'Desbloquear pistas adicionales',
|
||||
bonusTracksTooltip:
|
||||
'Desactivar esta opción puede ayudar a descongestionar la lista de canciones',
|
||||
saekawa: 'Archivo de configuración de Saekawa',
|
||||
inohara: 'Archivo de configuración de Inohara',
|
||||
},
|
||||
keyboard: {
|
||||
title: 'Teclado',
|
||||
tooltip:
|
||||
'Solo aplicable si el módulo IO está configurado como segatools integrado (teclado) o un módulo de terceros compatible (como mu3io.NET)',
|
||||
leverMode: 'Modo de palanca',
|
||||
mouse: 'Ratón',
|
||||
irTooltip:
|
||||
'Cuando juegues con un teclado real, solo vincula ir1; deja el resto sin vincular',
|
||||
},
|
||||
wine: {
|
||||
prefix: 'Prefijo de Wine',
|
||||
runtime: 'Runtime de Wine',
|
||||
},
|
||||
startliner: {
|
||||
offlineMode: 'Modo sin conexión',
|
||||
offlineModeTooltip: 'Desactiva la tienda de paquetes.',
|
||||
autoUpdate: 'Actualizaciones automáticas',
|
||||
verbose: 'Registros detallados',
|
||||
},
|
||||
},
|
||||
onboarding: {
|
||||
or: 'o',
|
||||
backButton: 'un botón en la parte trasera del controlador',
|
||||
standard: `
|
||||
Es posible que te quedes atascado en la siguiente pantalla:
|
||||
|
||||
{bigblack}Aグループの基準機から設定を取得{endbig}
|
||||
|
||||
En ese caso, debes ir al menú de prueba, y en la configuración del juego {black}ゲーム設定{end} cambiar de "seguir la máquina estándar" {black}基準機に従う{end} a "máquina estándar" {black}基準機{end}.
|
||||
|
||||
Se puede acceder al menú de prueba con %TESTMENU%.
|
||||
`,
|
||||
|
||||
'ongeki-system-processing': `
|
||||
Es posible que te quedes atascado en esta pantalla durante varios minutos. _Esto es normal_. El juego simplemente tarda mucho en cargar datos.
|
||||
|
||||
Si instalas <code>7EVENDAYSHOLIDAYS/LoadBoost</code>, los arranques siguientes serán mucho más rápidos.
|
||||
`,
|
||||
|
||||
'ongeki-lever': `
|
||||
También tienes que calibrar la palanca, o podrías obtener el error 3301.
|
||||
|
||||
Ve a la configuración de palanca ({black}レバー設定{end}), mueve la palanca hacia ambos extremos, luego presiona "finalizar" ({black}終了{end}) y "guardar" ({black}保存する{end}).
|
||||
`,
|
||||
|
||||
'chunithm-server': `
|
||||
Si estás atascado en esta pantalla, reinicia el juego.
|
||||
|
||||
Si el problema persiste, {link}comprueba tu configuración de red{endlink}
|
||||
`,
|
||||
|
||||
finale: `
|
||||
Puedes acceder a esta página en cualquier momento haciendo clic derecho en el botón INICIAR.
|
||||
|
||||
Recursos adicionales:
|
||||
|
||||
- {segaguide}SEGAguide{endlink}
|
||||
- {twotorial}two-torial{endlink}
|
||||
|
||||
## Diviértete
|
||||
`,
|
||||
},
|
||||
};
|
280
src/i18n/ja.ts
280
src/i18n/ja.ts
@ -1,14 +1,294 @@
|
||||
export default {
|
||||
ok: 'OK',
|
||||
cancel: 'キャンセル',
|
||||
enable: '有効にする',
|
||||
disable: '無効にする',
|
||||
default: 'デフォルト',
|
||||
search: '検索',
|
||||
next: '次へ',
|
||||
skip: 'スキップ',
|
||||
close: '閉じる',
|
||||
by: '{namespace}作',
|
||||
updateAll: 'すべてを更新',
|
||||
start: {
|
||||
failed: '起動確認に失敗',
|
||||
accept: 'とにかく実行',
|
||||
error: {
|
||||
package: 'パッケージが見つかりません',
|
||||
dependency: '依存関係が見つかりません',
|
||||
tool: 'ツールが見つかりません',
|
||||
unknown: '不明なエラー',
|
||||
},
|
||||
tooltip: {
|
||||
game: 'ゲームのパスを指定してください',
|
||||
amfs: 'amfsのパスを指定してください',
|
||||
segatools: 'segatoolsフックパッケージが必要です',
|
||||
},
|
||||
button: {
|
||||
start: '開始',
|
||||
stop: '停止',
|
||||
unchecked: 'チェックをスキップして起動',
|
||||
shortcut: 'デスクトップショートカットを作成',
|
||||
help: 'ヘルプ',
|
||||
refresh: 'MODを再適用して起動',
|
||||
cache: 'MODキャッシュのクリア',
|
||||
},
|
||||
},
|
||||
game: {
|
||||
ongeki: 'オンゲキ',
|
||||
chunithm: 'チュウニズム',
|
||||
},
|
||||
profile: {
|
||||
welcome: 'STARTLINERへようこそ! プロフィルの作成から始めよう。',
|
||||
create: '{game}のプロフィル',
|
||||
delete: 'プロフィル削除',
|
||||
reallyDelete: '本当に{profile}を削除しますか?',
|
||||
template: 'STARTLINERのテンプレート',
|
||||
importTemplate: 'テンプレートのインポート',
|
||||
exportTemplate: 'プロフィルのエクスポート',
|
||||
export: 'エクスポート',
|
||||
standardExport: 'テンプレート',
|
||||
diagnostic: '診断',
|
||||
},
|
||||
creator: {
|
||||
header: 'パッケージ製作者',
|
||||
basic: '基本情報',
|
||||
name: '名前',
|
||||
description: '説明',
|
||||
website: 'ウェブサイト',
|
||||
type: 'パッケージタイプ',
|
||||
rainy: 'スタンダード',
|
||||
segatools: 'segatools',
|
||||
native: 'ネイティブ',
|
||||
games: 'ゲーム',
|
||||
packageFormat: 'パッケージフォーマット仕様',
|
||||
},
|
||||
store: {
|
||||
installRecommended: 'おすすめパッケージをインストール',
|
||||
installed: 'インストール済みを表示',
|
||||
deprecated: '非推奨を表示',
|
||||
nsfw: '閲覧注意コンテンツを表示',
|
||||
incompatible: 'このパッケージは現在STARTLINERと互換性がありません。',
|
||||
includeCategories: 'カテゴリーを含める',
|
||||
excludeCategories: 'カテゴリーを除く',
|
||||
},
|
||||
pkglist: {
|
||||
missing: '見つからない',
|
||||
local: 'ローカルパッケージ',
|
||||
namespace: '名前空間順',
|
||||
type: 'タイプ順',
|
||||
category: 'カテゴリ順',
|
||||
standard: 'スタンダードMOD',
|
||||
native: 'ネイティブMOD',
|
||||
segatools: 'segatools',
|
||||
unsupported: '未対応',
|
||||
exclusions: '除外:',
|
||||
},
|
||||
patch: {
|
||||
loading: 'ロード中...',
|
||||
noneFound:
|
||||
'互換性のあるパッチが見つかりません。アンパック済みでパッチが適用されていないファイルを使用していることを確認してください。',
|
||||
forceLoad: '強制ロード',
|
||||
'standard-shared-audio':
|
||||
'共有オーディオモードを強制、システムオーディオのサンプルレートは48000Hzである必要があります',
|
||||
'standard-shared-audio-tooltip':
|
||||
'互換性は向上しますが、遅延が増加する可能性があります',
|
||||
'standard-2ch': '2チャンネルオーディオ出力を強制',
|
||||
'standard-2ch-tooltip': '低音の過負荷を引き起こす可能性があります',
|
||||
'standard-song-timer': '楽曲選択タイマーを無効にする',
|
||||
'standard-map-timer': 'マップ選択タイマー',
|
||||
'standard-map-timer-tooltip':
|
||||
'負の値を設定すると、タイマーは968 + 値となります(例:968 + -1 = 967)',
|
||||
'standard-ticket-timer': 'チケット選択タイマー',
|
||||
'standard-ticket-timer-tooltip':
|
||||
'負の値を設定すると、タイマーは968 + 値となります(例:968 + -1 = 967)',
|
||||
'standard-course-timer': 'コース選択タイマー',
|
||||
'standard-course-timer-tooltip':
|
||||
'負の値を設定すると、タイマーは968 + 値となります(例:968 + -1 = 967)',
|
||||
'standard-unlimited-tracks': '最大トラック数無制限',
|
||||
'standard-unlimited-tracks-tooltip':
|
||||
'1クレジットにつき7曲以上プレイする場合はチェックが必要です',
|
||||
'standard-maximum-tracks': '最大トラック数',
|
||||
'standard-no-encryption': '暗号化無し',
|
||||
'standard-no-encryption-tooltip': 'TLSも無効にします',
|
||||
'standard-no-tls': 'TLS無し',
|
||||
'standard-no-tls-tooltip': 'タイトルサーバーの回避策',
|
||||
'standard-head-to-head': 'ヘッド・トゥ・ヘッドパッチ',
|
||||
'standard-head-to-head-tooltip':
|
||||
'ヘッド・トゥ・ヘッドプレイ接続時の無限同期問題を修正',
|
||||
'standard-bypass-1080p': '1080pモニターチェックをバイパス',
|
||||
'standard-bypass-120hz': '120Hzモニターチェックをバイパス',
|
||||
'standard-force-free-play-text': 'FREE PLAYクレジットテキストを強制',
|
||||
'standard-force-free-play-text-tooltip':
|
||||
'クレジット数をFREE PLAYに置き換える',
|
||||
'standard-custom-free-play-length': 'カスタムFREE PLAYテキスト長',
|
||||
'standard-custom-free-play-length-tooltip':
|
||||
'FREE PLAYクレジット表示を強制が有効な場合に表示されるテキストの長さを変更します。',
|
||||
'standard-custom-free-play-text': 'カスタムFREE PLAYテキスト',
|
||||
'standard-custom-free-play-text-tooltip':
|
||||
'無限クレジット使用時、FREE PLAYのテキストを置き換えます。',
|
||||
'standard-localhost':
|
||||
'ネットワークサーバーとして127.0.0.1/localhostを許可',
|
||||
'standard-credit-freeze': 'クレジット凍結',
|
||||
'standard-credit-freeze-tooltip':
|
||||
'クレジットの使用を防ぎます。ゲームを開始したり、プレミアムチケットを購入したりするには、少なくとも1クレジットが使用可能である必要があります。',
|
||||
'standard-openssl-fix': 'OpenSSL SHAクラッシュのバグ修正',
|
||||
'standard-openssl-fix-tooltip':
|
||||
'第10世代以降のIntel CPUでのクラッシュを修正します',
|
||||
},
|
||||
cfg: {
|
||||
afterRestart: '再起動後に適用',
|
||||
hardware: 'ハードウェア',
|
||||
segatools: {
|
||||
general: '一般',
|
||||
builtIn: 'segatools内蔵エミュレーション',
|
||||
targetTooltip:
|
||||
'STARTLINERは、アンパック済みで改造されていない実行可能ファイルの使用を想定しています。',
|
||||
hooks: 'フック',
|
||||
ioModules: 'IOモジュール',
|
||||
ioModulesDesc: '希望する入力方法と一致させる必要があります。',
|
||||
ioBuiltIn: 'segatools内蔵(キーボード)',
|
||||
io4: 'ネイティブIO4',
|
||||
installTooltip:
|
||||
'{thing}はパッケージストアからダウンロードできます。',
|
||||
},
|
||||
display: {
|
||||
title: 'ディスプレイ',
|
||||
resolution: 'ゲーム解像度',
|
||||
primary: 'メインディスプレイ',
|
||||
target: 'ターゲットディスプレイ',
|
||||
mode: 'モード',
|
||||
rotation: '画面の向き',
|
||||
refreshRate: 'リフレッシュレート',
|
||||
borderlessFullscreen: 'ボーダレスフルスクリーン',
|
||||
borderlessFullscreenTooltip:
|
||||
'ディスプレイの解像度をゲームに合わせる。',
|
||||
dontSwitchPrimary: 'メインディスプレイの切り替えをスキップ',
|
||||
dontSwitchPrimaryTooltip:
|
||||
'メインディスプレイの切り替えで問題が発生する場合にのみ、このオプションを有効にしてください。モニターのリフレッシュレートは一致している必要があります。',
|
||||
index: 'ディスプレイインデックス',
|
||||
portrait: '縦',
|
||||
landscape: '横',
|
||||
flipped: '対称移動',
|
||||
window: 'ウィンドウ',
|
||||
borderless: 'ボーダレスウィンドウ',
|
||||
fullscreen: 'フルスクリーン',
|
||||
},
|
||||
network: {
|
||||
title: 'ネットワーク',
|
||||
type: 'ネットワークタイプ',
|
||||
remote: 'リモート',
|
||||
localArtemis: 'ローカル(ARTEMiS)',
|
||||
artemisPath: 'ARTEMiSパス',
|
||||
address: 'サーバーアドレス',
|
||||
keychip: 'キーチップ',
|
||||
subnet: 'サブネット',
|
||||
addrSuffix: 'アドレスサフィックス',
|
||||
},
|
||||
aime: {
|
||||
type: 'Aimeタイプ',
|
||||
modules: 'Aimeモジュール',
|
||||
code: 'アクセスコード',
|
||||
codeTooltip:
|
||||
'segatools内蔵エミュレーションまたは互換性のあるサードパーティ製パッケージでのみ使用可能。',
|
||||
aimedb: '物理カードにはAiMeDBを使う',
|
||||
aimedbTooltip:
|
||||
'物理カードがアクセスコードを取得するためにAiMeDBを使用するかどうか。ゲームがホストされたネットワークを使用している場合、このオプションを有効にすると、物理筐体で取得するのと同じアカウントデータ/プロフィルがロードされます。',
|
||||
serialPort: 'Aimeシリアルポート',
|
||||
serialPortTooltip: `ポートはデバイスとプリンター、またはgooglechromelabs.github.io/serial-terminalで確認できます
|
||||
AIC Picoの場合は、AIMEポートを選択する。`,
|
||||
serverName: 'サーバー名',
|
||||
},
|
||||
misc: {
|
||||
title: 'その他',
|
||||
intel: '第10世代以降Intel CPU向けOpenSSLバグ回避策',
|
||||
intelTooltip: '代わりにamdaemonにパッチを当てることを推奨します。',
|
||||
other: 'その他のsegatools設定',
|
||||
otherTooltip: 'STARTLINERでカバーされていない上級者向けまたは状況別設定。',
|
||||
prescript: '起動前スクリプト',
|
||||
prescriptTooltip: 'ゲームの起動前に実行されるスクリプト。',
|
||||
postscript: '終了後スクリプト',
|
||||
postscriptTooltip: 'ゲーム終了後に実行されるスクリプト。',
|
||||
},
|
||||
extensions: {
|
||||
title: 'エクステンション',
|
||||
bepInExConsole: 'BepInExコンソール',
|
||||
audioMode: 'オーディオモード',
|
||||
audioTooltip:
|
||||
'排他2チャンネルモードには7EVENDAYSHOLIDAYS-ExclusiveAudioが必要です',
|
||||
audioShared: '共有',
|
||||
audio6Ch: '排他6チャンネル',
|
||||
audio2Ch: '排他2チャンネル',
|
||||
sampleRate: 'サンプルレート',
|
||||
blacklist: '楽曲IDブラックリスト',
|
||||
blacklistTooltip:
|
||||
'このID範囲内の譜面のスコアは保存もアップロードもされません。',
|
||||
bonusTracks: 'ボーナストラックのアンロック',
|
||||
bonusTracksTooltip:
|
||||
'このオプションを無効にすると、楽曲リストを整理するのに役立ちます。',
|
||||
saekawa: 'Saekawa設定ファイル',
|
||||
inohara: 'Inohara設定ファイル',
|
||||
},
|
||||
keyboard: {
|
||||
title: 'キーボード',
|
||||
tooltip:
|
||||
'IOモジュールがsegatools内蔵(キーボード)または互換性のあるサードパーティモジュール(mu3io.NETなど)に設定されている場合のみ適用可能。',
|
||||
leverMode: 'レバーモード',
|
||||
mouse: 'マウス',
|
||||
irTooltip:
|
||||
'実際のキーボードでプレイする場合は、ir1のみをバインドし、残りはバインドしないでください。',
|
||||
},
|
||||
wine: {
|
||||
prefix: 'Wineプレフィックス',
|
||||
runtime: 'Wineランタイム',
|
||||
},
|
||||
startliner: {
|
||||
offlineMode: 'オフラインモード',
|
||||
offlineModeTooltip: 'パッケージストアを無効にする。',
|
||||
autoUpdate: '自動更新',
|
||||
verbose: '詳細ログ',
|
||||
},
|
||||
},
|
||||
onboarding: {
|
||||
or: 'または',
|
||||
backButton: 'コントローラー背面のボタン',
|
||||
standard: `
|
||||
以下のような画面で止まる可能性があります:
|
||||
|
||||
{bigblack}Aグループの基準機から設定を取得{endbig}
|
||||
|
||||
その場合、テストメニューに移動し、{black}ゲーム設定{end}の{black}基準に従う{end}から{black}基準機{end}に切り替える必要があります。
|
||||
|
||||
テストメニューは%TESTMENU%でアクセスできます。
|
||||
`,
|
||||
|
||||
'ongeki-system-processing': `
|
||||
この画面で数分間止まる可能性があります。_これは正常です_。ゲームがデータをロードするのに時間がかかるだけです。
|
||||
|
||||
<code>7EVENDAYSHOLIDAYS/LoadBoost</code>をインストールすれば、それ以降の起動はるかに速くなります。
|
||||
`,
|
||||
|
||||
'ongeki-lever': `
|
||||
レバーのキャリブレーションを行わないと、エラー3301が発生する可能性があります。
|
||||
|
||||
{black}レバー設定{end}に移動し、レバーを両端に動かしてから、{black}終了{end}を押してから{black}保存する{end}を押します。
|
||||
`,
|
||||
|
||||
'chunithm-server': `
|
||||
この画面で止まっている場合は、ゲームを再起動してください。
|
||||
|
||||
問題が解決しない場合は、{link}ネットワーク設定を確認してください{endlink}
|
||||
`,
|
||||
|
||||
finale: `
|
||||
STARTボタンを右クリックすれば、いつでもこのページにアクセスできます。
|
||||
|
||||
その他のリソース:
|
||||
|
||||
- {segaguide}SEGAguide{endlink}
|
||||
- {twotorial}two-torial{endlink}
|
||||
|
||||
## 楽しもう!
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
284
src/i18n/ko.ts
Normal file
284
src/i18n/ko.ts
Normal file
@ -0,0 +1,284 @@
|
||||
export default {
|
||||
ok: '확인',
|
||||
cancel: '취소',
|
||||
enable: '활성화',
|
||||
disable: '비활성화',
|
||||
default: '기본값',
|
||||
search: '검색',
|
||||
next: '다음',
|
||||
skip: '건너뛰기',
|
||||
close: '닫기',
|
||||
by: '제작자 {namespace}',
|
||||
updateAll: '모두 업데이트',
|
||||
start: {
|
||||
failed: '시작 확인 실패',
|
||||
accept: '계속하기',
|
||||
error: {
|
||||
package: '패키지 누락',
|
||||
dependency: '의존성 누락',
|
||||
tool: '도구 없음',
|
||||
unknown: '알 수 없는 오류',
|
||||
},
|
||||
tooltip: {
|
||||
game: '게임 경로를 지정해야 합니다',
|
||||
amfs: 'amfs 경로를 지정해야 합니다',
|
||||
segatools: 'segatools 훅 패키지가 필요합니다',
|
||||
},
|
||||
button: {
|
||||
start: '시작',
|
||||
stop: '중지',
|
||||
unchecked: '확인을 건너뛰고 시작',
|
||||
shortcut: '바탕화면 바로가기 만들기',
|
||||
help: '도움말',
|
||||
refresh: '모드를 다시 적용하고 시작',
|
||||
cache: '모드 캐시 지우기',
|
||||
},
|
||||
},
|
||||
game: {
|
||||
ongeki: 'O.N.G.E.K.I.',
|
||||
chunithm: 'CHUNITHM',
|
||||
},
|
||||
profile: {
|
||||
welcome: 'STARTLINER에 오신 것을 환영합니다! 프로필을 생성하여 시작하세요.',
|
||||
create: '{game} 프로필',
|
||||
delete: '프로필 삭제',
|
||||
reallyDelete: '정말로 {profile}을(를) 삭제하시겠습니까?',
|
||||
template: 'STARTLINER 템플릿',
|
||||
importTemplate: '템플릿 가져오기',
|
||||
exportTemplate: '프로필 내보내기',
|
||||
export: '내보내기',
|
||||
standardExport: '템플릿',
|
||||
diagnostic: '진단',
|
||||
},
|
||||
creator: {
|
||||
header: '패키지 제작자',
|
||||
basic: '기본 정보',
|
||||
name: '이름',
|
||||
description: '설명',
|
||||
website: '웹사이트',
|
||||
type: '패키지 유형',
|
||||
rainy: '기본',
|
||||
segatools: 'Segatools',
|
||||
native: '네이티브',
|
||||
games: '게임',
|
||||
packageFormat: '패키지 형식 사양',
|
||||
},
|
||||
store: {
|
||||
installRecommended: '권장 패키지 설치',
|
||||
installed: '설치된 항목 표시',
|
||||
deprecated: '더 이상 사용되지 않는 항목 표시',
|
||||
nsfw: '부적절한 콘텐츠 표시',
|
||||
incompatible: '이 패키지는 현재 STARTLINER와 호환되지 않습니다.',
|
||||
|
||||
includeCategories: '카테고리 포함',
|
||||
excludeCategories: '카테고리 제외',
|
||||
},
|
||||
pkglist: {
|
||||
missing: '없음',
|
||||
local: '로컬 패키지',
|
||||
namespace: '네임스페이스별',
|
||||
type: '유형별',
|
||||
category: '카테고리별',
|
||||
standard: '표준 모드',
|
||||
native: '네이티브 모드',
|
||||
segatools: 'Segatools',
|
||||
unsupported: '지원되지 않음',
|
||||
exclusions: '제외 항목:',
|
||||
},
|
||||
patch: {
|
||||
loading: '로딩 중...',
|
||||
noneFound:
|
||||
"호환되는 패치가 없습니다. 언팩이 된 패치되지 않은 파일을 사용하고 있는지 확인하세요.",
|
||||
forceLoad: '강제 로드',
|
||||
'standard-shared-audio': '공유 오디오 모드 강제 활성화. 시스템 오디오 샘플 레이트는 48000Hz여야 합니다.',
|
||||
'standard-shared-audio-tooltip': '호환성을 개선하지만 대기 시간이 증가할 수 있습니다.',
|
||||
'standard-2ch': '2채널 오디오 출력 강제 활성화',
|
||||
'standard-2ch-tooltip': '과도한 저음을 유발할 수 있습니다.',
|
||||
'standard-song-timer': '곡 선택 타이머 비활성화',
|
||||
'standard-map-timer': '맵 선택 타이머',
|
||||
'standard-map-timer-tooltip': '음수가 설정되면 타이머는 968 + 값이 됩니다 (예: 968 + -1 = 967)',
|
||||
'standard-ticket-timer': '티켓 선택 타이머',
|
||||
'standard-ticket-timer-tooltip': '음수가 설정되면 타이머는 968 + 값이 됩니다 (예: 968 + -1 = 967)',
|
||||
'standard-course-timer': '코스 선택 타이머',
|
||||
'standard-course-timer-tooltip': '음수가 설정되면 타이머는 968 + 값이 됩니다 (예: 968 + -1 = 967)',
|
||||
'standard-unlimited-tracks': '무제한 최대 트랙',
|
||||
'standard-unlimited-tracks-tooltip': '7개 이상의 트랙을 재생하려면 체크해야 합니다.',
|
||||
'standard-maximum-tracks': '최대 트랙',
|
||||
'standard-no-encryption': '암호화 없음',
|
||||
'standard-no-encryption-tooltip': 'TLS도 비활성화됩니다.',
|
||||
'standard-no-tls': 'TLS 없음',
|
||||
'standard-no-tls-tooltip': '타이틀 서버 우회',
|
||||
'standard-head-to-head': '대결 플레이 패치',
|
||||
'standard-head-to-head-tooltip': '대결 플레이에 연결할 때 무한 동기화 문제를 수정합니다.',
|
||||
'standard-bypass-1080p': '1080p 모니터 확인 우회',
|
||||
'standard-bypass-120hz': '120Hz 모니터 확인 우회',
|
||||
'standard-force-free-play-text': 'FREE PLAY 크레딧 텍스트 강제 표시',
|
||||
'standard-force-free-play-text-tooltip': '크레딧 수를 FREE PLAY로 표시합니다.',
|
||||
'standard-custom-free-play-length': '사용자 지정 FREE PLAY 텍스트 길이',
|
||||
'standard-custom-free-play-length-tooltip': '사용자 지정 FREE PLAY 크레딧 패치가 활성화될 때 표시되는 텍스트의 길이를 변경합니다.',
|
||||
'standard-custom-free-play-text': '사용자 지정 FREE PLAY 텍스트',
|
||||
'standard-custom-free-play-text-tooltip': '무제한 크레딧을 사용할 때 FREE PLAY 텍스트를 대체합니다.',
|
||||
'standard-localhost': '네트워크 서버로 127.0.0.1/localhost 사용 허용',
|
||||
'standard-credit-freeze': '크레딧 잠금',
|
||||
'standard-credit-freeze-tooltip': '크레딧 사용을 방지합니다. 게임 시작 또는 프리미엄 티켓 구매를 위해서는 최소 1크레딧이 있어야 합니다.',
|
||||
'standard-openssl-fix': 'OpenSSL SHA 충돌 버그 수정',
|
||||
'standard-openssl-fix-tooltip': '10세대 이상 Intel CPU에서 발생하는 충돌을 수정합니다.',
|
||||
|
||||
},
|
||||
cfg: {
|
||||
afterRestart: '재시작 후 적용됩니다',
|
||||
hardware: '하드웨어',
|
||||
segatools: {
|
||||
general: '일반',
|
||||
builtIn: 'Segatools 내장 에뮬레이션',
|
||||
targetTooltip:
|
||||
'STARTLINER를 사용하려면, 언팩된 실행 파일을 별도의 수정이 없는 데이터와 함께 사용해야 합니다.',
|
||||
hooks: '훅',
|
||||
ioModules: 'IO 모듈',
|
||||
ioModulesDesc: '원하는 입력 방식과 일치해야 합니다.',
|
||||
ioBuiltIn: 'Segatools 내장 (키보드)',
|
||||
io4: '네이티브 IO4',
|
||||
installTooltip: '패키지 스토어에서 {thing}을(를) 다운로드하실 수 있습니다.',
|
||||
},
|
||||
display: {
|
||||
title: '디스플레이',
|
||||
resolution: '게임 해상도',
|
||||
primary: '주 디스플레이',
|
||||
target: '대상 디스플레이',
|
||||
mode: '모드',
|
||||
rotation: '회전',
|
||||
refreshRate: '주사율',
|
||||
borderlessFullscreen: '테두리 없는 전체 화면',
|
||||
borderlessFullscreenTooltip:
|
||||
'게임과 디스플레이 해상도를 일치시킵니다.',
|
||||
dontSwitchPrimary: '주 디스플레이 전환 건너뛰기',
|
||||
dontSwitchPrimaryTooltip:
|
||||
'주 디스플레이 전환 시 문제가 발생하는 경우에만 이 옵션을 활성화하세요. 모니터의 주사율이 일치해야 합니다.',
|
||||
index: '디스플레이 번호',
|
||||
portrait: '세로',
|
||||
landscape: '가로',
|
||||
flipped: '대칭 이동',
|
||||
window: '창',
|
||||
borderless: '테두리 없는 창',
|
||||
fullscreen: '전체 화면',
|
||||
},
|
||||
network: {
|
||||
title: '네트워크',
|
||||
type: '네트워크 유형',
|
||||
remote: '원격',
|
||||
localArtemis: '로컬 (ARTEMiS)',
|
||||
artemisPath: 'ARTEMiS 경로',
|
||||
address: '서버 주소',
|
||||
keychip: '키칩',
|
||||
subnet: '서브넷',
|
||||
addrSuffix: '주소 접미사',
|
||||
},
|
||||
aime: {
|
||||
type: 'Aime 유형',
|
||||
modules: 'Aime 모듈',
|
||||
code: 'Aime 코드',
|
||||
codeTooltip:
|
||||
'Segatools 내장 에뮬레이션 또는 호환 가능한 패키지에서만 적용됩니다.',
|
||||
aimedb: '실물 카드에 AiMeDB 사용',
|
||||
aimedbTooltip:
|
||||
'실물 카드가 접속 코드를 가져올 때 AiMeDB를 사용할지 여부입니다. 게임이 호스팅된 네트워크를 사용하는 경우, 실제 아케이드 캐비닛에서 얻을 수 있는 것과 동일한 계정 데이터/프로필을 불러오려면 이 옵션을 활성화하세요.',
|
||||
serialPort: 'Aime 리더 포트',
|
||||
serialPortTooltip: `포트는 장치 및 프린터 또는 googlechromelabs.github.io/serial-terminal에서 확인할 수 있습니다.
|
||||
AIC Pico의 경우, AIME 포트를 선택해야 합니다.`,
|
||||
serverName: '서버 이름',
|
||||
},
|
||||
misc: {
|
||||
title: '기타',
|
||||
intel: '인텔 10세대 이상 CPU OpenSSL 버그 우회',
|
||||
intelTooltip: '대신 amdaemon을 패치하는 것이 권장됩니다.',
|
||||
other: '기타 Segatools 옵션',
|
||||
otherTooltip:
|
||||
'STARTLINER에서 다루지 않는 고급 또는 특정 상황용 옵션입니다.',
|
||||
prescript: '시작 스크립트',
|
||||
prescriptTooltip: '게임 실행 전에 실행되는 선택적 스크립트입니다.',
|
||||
postscript: '종료 스크립트',
|
||||
postscriptTooltip:
|
||||
'게임이 종료된 후에 실행되는 선택적 스크립트입니다.',
|
||||
},
|
||||
extensions: {
|
||||
title: '확장',
|
||||
bepInExConsole: 'BepInEx 콘솔',
|
||||
audioMode: '오디오 모드',
|
||||
audioTooltip:
|
||||
'전용 2채널 모드는 7EVENDAYSHOLIDAYS-ExclusiveAudio를 필요로 합니다.',
|
||||
audioShared: '공유',
|
||||
audio6Ch: '전용 6채널',
|
||||
audio2Ch: '전용 2채널',
|
||||
sampleRate: '샘플 레이트',
|
||||
blacklist: '노래 ID 제외 목록',
|
||||
blacklistTooltip:
|
||||
'이 ID 범위 내의 차트 점수는 저장되거나 업로드되지 않습니다.',
|
||||
bonusTracks: '보너스 트랙 잠금 해제',
|
||||
bonusTracksTooltip:
|
||||
'이 옵션을 비활성화하면 곡 목록을 정리하는 데 도움이 될 수 있습니다.',
|
||||
saekawa: 'Saekawa 설정 파일',
|
||||
inohara: 'Inohara 설정 파일',
|
||||
},
|
||||
keyboard: {
|
||||
title: '키보드',
|
||||
tooltip:
|
||||
'IO 모듈이 segatools 내장 (키보드) 또는 호환 가능한 모듈 (예: mu3io.NET)로 설정된 경우에만 적용됩니다.',
|
||||
leverMode: '레버 모드',
|
||||
mouse: '마우스',
|
||||
irTooltip:
|
||||
'실제 키보드로 플레이할 경우, ir1만 할당하고 나머지는 할당하지 마세요.',
|
||||
},
|
||||
wine: {
|
||||
prefix: 'Wine 프리픽스',
|
||||
runtime: 'Wine 런타임',
|
||||
},
|
||||
startliner: {
|
||||
offlineMode: '오프라인 모드',
|
||||
offlineModeTooltip: '패키지 스토어를 비활성화합니다.',
|
||||
autoUpdate: '자동 업데이트',
|
||||
verbose: '상세 로그',
|
||||
},
|
||||
},
|
||||
onboarding: {
|
||||
or: '또는',
|
||||
backButton: '컨트롤러 후면의 버튼',
|
||||
standard: `
|
||||
다음 화면에서 멈출 수 있습니다:
|
||||
|
||||
{bigblack}Aグループの基準機から設定を取得{endbig}
|
||||
|
||||
이 경우, 테스트 메뉴로 이동하여 게임 설정 {black}ゲーム設定{end}에서 "기준기 따르기" {black}基準機に従う{end}를 "기준기" {black}基準機{end}로 변경해야 합니다.
|
||||
|
||||
테스트 메뉴는 %TESTMENU%을 사용하여 접근할 수 있습니다.
|
||||
`,
|
||||
|
||||
'ongeki-system-processing': `
|
||||
이 화면에서 몇 분간 멈출 수 있습니다. _이는 정상입니다_. 게임이 데이터를 로드하는 데 시간이 오래 걸릴 뿐입니다.
|
||||
|
||||
<code>7EVENDAYSHOLIDAYS/LoadBoost</code>를 설치하면 이후 실행 시 훨씬 빨라집니다.
|
||||
`,
|
||||
|
||||
'ongeki-lever': `
|
||||
또한 레버를 보정해야 합니다. 그렇지 않으면 에러 3301이 발생할 수 있습니다.
|
||||
|
||||
레버 설정 ({black}レバー設定{end})으로 이동하여 레버를 양쪽 끝으로 움직인 다음, "종료" ({black}終了{end})를 누르고 "저장" ({black}保存する{end})을 누르세요.
|
||||
`,
|
||||
|
||||
'chunithm-server': `
|
||||
이 화면에서 멈춘 경우, 게임을 다시 시작하세요.
|
||||
|
||||
문제가 계속되면, {link}네트워크 설정을 확인하세요{endlink}
|
||||
`,
|
||||
|
||||
finale: `
|
||||
이 페이지는 언제든지 시작 버튼을 오른쪽 클릭하여 접근할 수 있습니다.
|
||||
|
||||
추가 자료:
|
||||
|
||||
- {segaguide}SEGAguide{endlink}
|
||||
- {twotorial}two-torial{endlink}
|
||||
|
||||
## 즐
|
||||
`,
|
||||
},
|
||||
};
|
@ -9,6 +9,7 @@ export default {
|
||||
skip: 'Pomiń',
|
||||
close: 'Zamknij',
|
||||
by: 'od {namespace}',
|
||||
updateAll: 'ZAKTUALIZUJ WSZYSTKO',
|
||||
start: {
|
||||
failed: 'Uruchomienie nie powiodło się',
|
||||
accept: 'Uruchom mimo to',
|
||||
@ -44,8 +45,10 @@ export default {
|
||||
reallyDelete: 'Czy na pewno chcesz usunąć {profile}?',
|
||||
template: 'Szablon',
|
||||
importTemplate: 'Importuj szablon',
|
||||
exportTemplate: 'Eksportuj szablon',
|
||||
exportTemplate: 'Eksportuj profil',
|
||||
export: 'Eksportuj',
|
||||
standardExport: 'Szablon',
|
||||
diagnostic: 'Diagnostyka',
|
||||
},
|
||||
creator: {
|
||||
header: 'Kreator pakietów',
|
||||
@ -203,6 +206,11 @@ export default {
|
||||
other: 'Inne opcje segatools',
|
||||
otherTooltip:
|
||||
'Zaawansowane lub sytuacyjne opcje, które nie są objęte przez STARTLINERA',
|
||||
prescript: 'Skrypt startowy',
|
||||
prescriptTooltip: 'Opcjonalny skrypt uruchamiany przed grą.',
|
||||
postscript: 'Skrypt końcowy',
|
||||
postscriptTooltip:
|
||||
'Opcjonalny skrypt uruchamiany po zakończeniu gry.',
|
||||
},
|
||||
extensions: {
|
||||
title: 'Rozszerzenia',
|
||||
|
256
src/i18n/zh-Hans.ts
Normal file
256
src/i18n/zh-Hans.ts
Normal file
@ -0,0 +1,256 @@
|
||||
export default {
|
||||
ok: '确定',
|
||||
cancel: '取消',
|
||||
enable: '启用',
|
||||
disable: '禁用',
|
||||
default: '默认',
|
||||
search: '搜索',
|
||||
next: '下一个',
|
||||
skip: '跳过',
|
||||
close: '关闭',
|
||||
by: '来自 {namespace}',
|
||||
updateAll: '更新全部',
|
||||
start: {
|
||||
failed: '启动检查失败',
|
||||
accept: '仍要运行',
|
||||
error: {
|
||||
package: 'Package缺失',
|
||||
dependency: '依赖缺失',
|
||||
tool: 'Tools缺失',
|
||||
unknown: '未知错误',
|
||||
},
|
||||
tooltip: {
|
||||
game: '需要指定游戏路径',
|
||||
amfs: '需要指定amfs路径',
|
||||
segatools: '需要segatools hook package',
|
||||
},
|
||||
button: {
|
||||
start: '启动',
|
||||
stop: '停止',
|
||||
unchecked: '跳过检查并启动',
|
||||
shortcut: '创建桌面快捷方式',
|
||||
help: '帮助',
|
||||
refresh: '重新应用MOD并启动',
|
||||
cache: '清空MOD缓存',
|
||||
},
|
||||
},
|
||||
game: {
|
||||
ongeki: '音击',
|
||||
chunithm: '中二节奏',
|
||||
},
|
||||
profile: {
|
||||
welcome: '欢迎来到STARTLINER! 创建一个配置文件以开始.',
|
||||
create: '{game} 配置文件',
|
||||
delete: '删除配置文件',
|
||||
reallyDelete: '确定删除 {profile}?',
|
||||
template: 'STARTLINER 模板',
|
||||
importTemplate: '导入模板',
|
||||
exportTemplate: '导出配置文件',
|
||||
export: '导出',
|
||||
standardExport: '模板',
|
||||
diagnostic: '诊断',
|
||||
},
|
||||
creator: {
|
||||
header: 'Package作者',
|
||||
basic: '基本信息',
|
||||
name: '名称',
|
||||
description: '介绍',
|
||||
website: '网站',
|
||||
type: 'Package类型',
|
||||
rainy: '标准',
|
||||
segatools: 'Segatools',
|
||||
native: '原生',
|
||||
games: '游戏',
|
||||
packageFormat: 'Package格式规范',
|
||||
},
|
||||
store: {
|
||||
installRecommended: '安装推荐的Packages',
|
||||
installed: '显示已安装',
|
||||
deprecated: '显示已弃用',
|
||||
nsfw: '显示NSFW',
|
||||
incompatible: 'STARTLINER暂不支持此Package.',
|
||||
|
||||
includeCategories: '包含类别',
|
||||
excludeCategories: '排除类别',
|
||||
},
|
||||
pkglist: {
|
||||
missing: '缺失',
|
||||
local: '本地Packages',
|
||||
namespace: '按名称',
|
||||
type: '按类型',
|
||||
category: '按类别',
|
||||
standard: '标准MOD',
|
||||
native: '原生MOD',
|
||||
segatools: 'Segatools',
|
||||
unsupported: '不支持',
|
||||
exclusions: '排除项:',
|
||||
},
|
||||
patch: {
|
||||
loading: '加载中...',
|
||||
noneFound:
|
||||
"未找到兼容的Patch. 请确认你使用的是已解密且未经修补的文件.",
|
||||
forceLoad: '强制加载',
|
||||
// Example patch name override
|
||||
// 'standard-no-encryption': 'No encryption',
|
||||
// 'standard-no-encryption-tooltip': 'Will also disable TLS',
|
||||
// It is also possible to add a tooltip where there normally is none
|
||||
// 'standard-maximum-tracks-tooltip': 'The number of tracks per credit',
|
||||
// For more info check https://gitea.tendokyu.moe/akanyan/STARTLINER/wiki/Translation-%26-Localization
|
||||
},
|
||||
cfg: {
|
||||
afterRestart: '将在重新启动后应用',
|
||||
hardware: '硬件',
|
||||
segatools: {
|
||||
general: '通用',
|
||||
builtIn: 'Segatools内置模拟',
|
||||
targetTooltip:
|
||||
'STARTLINER希望将已解密的可执行文件放入其他干净的数据中.',
|
||||
hooks: 'Hook',
|
||||
ioModules: 'IO模块',
|
||||
ioModulesDesc: '此处应符合您希望使用的输入方式.',
|
||||
ioBuiltIn: 'Segatools内置(键盘)',
|
||||
io4: '原生IO4',
|
||||
installTooltip: '{thing} 可从Package Store下载.',
|
||||
},
|
||||
display: {
|
||||
title: '显示',
|
||||
resolution: '游戏分辨率',
|
||||
primary: '主显示器',
|
||||
target: '目标显示器',
|
||||
mode: '模式',
|
||||
rotation: '旋转',
|
||||
refreshRate: '刷新率',
|
||||
borderlessFullscreen: '无边框全屏',
|
||||
borderlessFullscreenTooltip:
|
||||
'将显示器的分辨率调整为游戏分辨率.',
|
||||
dontSwitchPrimary: '跳过主显示器切换',
|
||||
dontSwitchPrimaryTooltip:
|
||||
'仅在更换到主显示器遇到问题时启用此选项,显示器必须具有匹配的刷新率.',
|
||||
index: '显示器索引',
|
||||
portrait: '纵向',
|
||||
landscape: '横向',
|
||||
flipped: '翻转',
|
||||
window: '窗口化',
|
||||
borderless: '无边框窗口化',
|
||||
fullscreen: '全屏',
|
||||
},
|
||||
network: {
|
||||
title: '网络',
|
||||
type: '服务器类型',
|
||||
remote: '在线',
|
||||
localArtemis: '本地 (ARTEMiS)',
|
||||
artemisPath: 'ARTEMiS路径',
|
||||
address: '服务器地址',
|
||||
keychip: '加密狗号 (Keychip)',
|
||||
subnet: '子网 (Subnet)',
|
||||
addrSuffix: '地址后缀 (Address suffix)',
|
||||
},
|
||||
aime: {
|
||||
type: 'Aime读卡器类型',
|
||||
modules: 'AimeIO',
|
||||
code: 'Aime卡号',
|
||||
codeTooltip:
|
||||
'仅适用于Segatools内置模拟或第三方AimeIO',
|
||||
aimedb: '对实体卡使用AiMeDB',
|
||||
aimedbTooltip:
|
||||
'实体卡需要使用AiMeDB来解析真实卡号,如果您想要获取卡片背面印刷的真实卡号,请启用此选项.',
|
||||
serialPort: '读卡器端口',
|
||||
serialPortTooltip: `端口号可在 设备和打印机 或 设备管理器 中查看
|
||||
对于AIC Pico,应选择AIME Port.`,
|
||||
serverName: '服务器名称',
|
||||
},
|
||||
misc: {
|
||||
title: '杂项',
|
||||
intel: '修复Intel 10代及以上CPU存在的OpenSSL问题',
|
||||
intelTooltip: '推荐使用该选项来代替给amdaemon打补丁.',
|
||||
other: '其他segatools选项',
|
||||
otherTooltip:
|
||||
'未被STARTLINER覆盖的高级选项',
|
||||
prescript: '启动脚本',
|
||||
prescriptTooltip: '在游戏启动前运行的脚本.',
|
||||
postscript: '结束脚本',
|
||||
postscriptTooltip:
|
||||
'在游戏进程结束后运行的脚本.',
|
||||
},
|
||||
extensions: {
|
||||
title: '扩展',
|
||||
bepInExConsole: 'BepInEx控制台',
|
||||
audioMode: '音频模式',
|
||||
audioTooltip:
|
||||
'独占双声道音频需要MOD: 7EVENDAYSHOLIDAYS-ExclusiveAudio',
|
||||
audioShared: '共享',
|
||||
audio6Ch: '独占六声道',
|
||||
audio2Ch: '独占双声道',
|
||||
sampleRate: '采样率',
|
||||
blacklist: '歌曲ID黑名单',
|
||||
blacklistTooltip:
|
||||
'黑名单中的歌曲分数不会被保存和上传',
|
||||
bonusTracks: '解锁Bonus Track',
|
||||
bonusTracksTooltip:
|
||||
'禁用此选项可帮助你整理歌曲列表',
|
||||
saekawa: 'Saekawa配置文件',
|
||||
inohara: 'Inohara配置文件',
|
||||
},
|
||||
keyboard: {
|
||||
title: '键盘',
|
||||
tooltip:
|
||||
'仅适用于Segatools内置(键盘)或兼容的第三方IO(如mu3io.NET)',
|
||||
leverMode: '摇杆模式',
|
||||
mouse: '鼠标',
|
||||
irTooltip:
|
||||
'当使用键盘游玩时,请只绑定ir1;其余的请设置为未绑定',
|
||||
},
|
||||
wine: {
|
||||
prefix: 'Wine前缀',
|
||||
runtime: 'Wine运行时',
|
||||
},
|
||||
startliner: {
|
||||
offlineMode: '离线模式',
|
||||
offlineModeTooltip: '禁用Package Store.',
|
||||
autoUpdate: '自动更新',
|
||||
verbose: '详细日志',
|
||||
},
|
||||
},
|
||||
onboarding: {
|
||||
or: '或',
|
||||
backButton: '控制器背后的按钮',
|
||||
standard: `
|
||||
你可能会卡在这个界面:
|
||||
|
||||
{bigblack}Aグループの基準機から設定を取得{endbig}
|
||||
|
||||
在这种情况下, 你需要跳转到测试模式(Test), 在游戏设定中 {black}ゲーム設定{end} 将 "从机" {black}基準機に従う{end} 切换到 "基准机" {black}基準機{end}.
|
||||
|
||||
测试模式可通过 %TESTMENU% 进入.
|
||||
`,
|
||||
|
||||
'ongeki-system-processing': `
|
||||
你可能会在这个界面上停留一段时间. _这是正常的_. 这个游戏加载数据需要很长时间.
|
||||
|
||||
如果你安装了 <code>7EVENDAYSHOLIDAYS/LoadBoost</code>, 后续启动会变得更快.
|
||||
`,
|
||||
|
||||
'ongeki-lever': `
|
||||
你需要校准你的摇杆, 不然你会看到错误3301.
|
||||
|
||||
前往摇杆设定 ({black}レバー設定{end}), 将摇杆移动至两边, 然后按下"结束" ({black}終了{end}) 和 "保存" ({black}保存する{end}).
|
||||
`,
|
||||
|
||||
'chunithm-server': `
|
||||
如果你卡在这个界面, 请重启游戏.
|
||||
|
||||
如果问题依旧存在, {link}请检查你的网络配置{endlink}
|
||||
`,
|
||||
|
||||
finale: `
|
||||
您随时都可以通过右键点击START按钮重新回到这个页面.
|
||||
|
||||
附录:
|
||||
|
||||
- {segaguide}SEGAguide{endlink}
|
||||
- {twotorial}two-torial{endlink}
|
||||
|
||||
## 玩得愉快
|
||||
`,
|
||||
},
|
||||
};
|
@ -189,7 +189,7 @@ export const usePkgStore = defineStore('pkg', {
|
||||
await this.reloadAll();
|
||||
},
|
||||
|
||||
async install(pkg: Package | undefined) {
|
||||
async install(pkg: Package | undefined, enable: boolean) {
|
||||
if (pkg === undefined) {
|
||||
return;
|
||||
}
|
||||
@ -198,6 +198,7 @@ export const usePkgStore = defineStore('pkg', {
|
||||
await invoke('install_package', {
|
||||
key: pkgKey(pkg),
|
||||
force: true,
|
||||
enable,
|
||||
});
|
||||
} catch (err) {
|
||||
if (pkg !== undefined) {
|
||||
@ -211,6 +212,7 @@ export const usePkgStore = defineStore('pkg', {
|
||||
await invoke('install_package', {
|
||||
key,
|
||||
force: true,
|
||||
enable: false,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -221,7 +223,7 @@ export const usePkgStore = defineStore('pkg', {
|
||||
const list = [];
|
||||
for (const pkg of this.allLocal) {
|
||||
if (pkg.rmt && pkg.rmt.version > pkg.loc!.version) {
|
||||
list.push(this.install(pkg));
|
||||
list.push(this.install(pkg, false));
|
||||
}
|
||||
}
|
||||
await Promise.all(list);
|
||||
@ -322,9 +324,10 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
const generalStore = useGeneralStore();
|
||||
|
||||
const configDir = computed(async () => {
|
||||
const title = `profile-${current.value?.meta.game}-${current.value?.meta.name}`;
|
||||
return path.join(
|
||||
await generalStore.configDir,
|
||||
`profile-${current.value?.meta.game}-${current.value?.meta.name}`
|
||||
title
|
||||
);
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user