3 Commits

Author SHA1 Message Date
469ba5f574 feat: add prelaunch scripts 2025-05-09 16:07:58 +00:00
8f05a04350 fix: file editor not updating state properly 2025-05-09 16:07:13 +00:00
4ddc54d528 feat: add japanese localization 2025-05-09 16:06:04 +00:00
12 changed files with 373 additions and 9 deletions

View File

@ -1,3 +1,9 @@
## 0.20.0
- Added user-customizable pre-launch scripts
- Added japanese localization
- Fixed the file editor not updating state properly
## 0.19.1
- Fixed the update button enabling the package

View File

@ -533,14 +533,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]

View File

@ -255,6 +255,7 @@ impl Profile {
let mut game_builder;
let mut amd_builder;
let mut prelaunch = None;
let target_path = PathBuf::from(&self.data.sgt.target);
let exe_dir = target_path.parent().ok_or_else(|| anyhow!("Invalid target path"))?;
@ -264,6 +265,17 @@ impl Profile {
{
game_builder = Command::new(sgt_dir.join(self.meta.game.inject_exe()));
amd_builder = Command::new("cmd.exe");
let prelaunch_path = self.config_dir().join("prelaunch.bat");
if prelaunch_path.exists() {
let mut c = Command::new("cmd");
c.args(["/C", "start"]);
c.raw_arg("\"STARTLINER Prelaunch\"");
c.args(["cmd", "/C"]);
c.arg(prelaunch_path);
log::debug!("Prelaunch: {:?}", c);
prelaunch = Some(c.spawn()?);
}
}
#[cfg(target_os = "linux")]
{
@ -272,6 +284,14 @@ impl Profile {
game_builder.arg(sgt_dir.join(self.meta.game.inject_exe()));
amd_builder.arg("cmd.exe");
let prelaunch_path = self.config_dir().join("prelaunch.sh");
if prelaunch_path.exists() {
prelaunch_builder = Some(Command::new("sh"));
c.arg(prelaunch_path);
log::debug!("Prelaunch: {:?}", c);
prelaunch = Some(c.spawn()?);
}
}
amd_builder.env(
@ -420,6 +440,18 @@ impl Profile {
util::pkill("amdaemon.exe").await;
}
if let Some(mut _child) = prelaunch {
#[cfg(target_os = "windows")]
{
// child.kill() doesn't work
util::pkill_title("STARTLINER Prelaunch").await;
}
#[cfg(target_os = "linux")]
{
_child.start_kill()?;
}
}
set.join_next().await.expect("No spawn").expect("No result");
log::debug!("Fin");

View File

@ -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)

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER",
"version": "0.19.1",
"version": "0.20.0",
"identifier": "zip.patafour.startliner",
"build": {
"beforeDevCommand": "bun run dev",

View File

@ -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>

View File

@ -209,7 +209,7 @@ const importPick = async () => {
style="width: 200px"
:options="[
{ title: 'English', value: 'en' },
// { title: '日本語', value: 'ja' },
{ title: '日本語', value: 'ja' },
{ title: 'Polski', value: 'pl' },
]"
size="small"

View File

@ -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,20 @@ const prf = usePrfStore();
<!-- <Button icon="pi pi-refresh" size="small" /> -->
<FileEditor filename="segatools-base.ini" />
</OptionRow>
<OptionRow
:title="t('cfg.misc.prelaunch')"
:tooltip="t('cfg.misc.prelaunchTooltip')"
>
<FileEditor
v-if="extension === 'bat'"
filename="prelaunch.bat"
:defaultValue="`@echo off\n\nREM This script will be launched alongside the game\n`"
/>
<FileEditor
v-else-if="extension === 'sh'"
filename="prelaunch.sh"
:defaultValue="`#!/bin/sh\n\n# This script will be launched alongside the game\n`"
/>
</OptionRow>
</OptionCategory>
</template>

View File

@ -166,6 +166,8 @@ export default {
other: 'Other segatools options',
otherTooltip:
'Advanced or situational options not covered by STARTLINER',
prelaunch: 'Prelaunch script',
prelaunchTooltip: 'Optional script that runs before the game.',
},
extensions: {
title: 'Extensions',

View File

@ -1,14 +1,293 @@
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: '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-1967',
'standard-ticket-timer': 'チケット選択タイマー',
'standard-ticket-timer-tooltip':
'負に設定すると、タイマーは968値となる968-1967',
'standard-course-timer': 'コース選択タイマー',
'standard-course-timer-tooltip':
'負に設定すると、タイマーは968値となる968-1967',
'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世代以降のインテル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世代以降インテル向けOpenSSLバグの回避策',
intelTooltip: '代わりにamdaemonにパッチを当てることを推奨する。',
other: 'その他segatools設定',
otherTooltip: 'STARTLINERに含まれない上級者向けまたは状況別設定',
prelaunch: 'スタート前スクリプト',
prelaunchTooltip: 'ゲームの前に実行されるスクリプト',
},
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}
## 楽しもう!
`,
},
};

View File

@ -206,6 +206,8 @@ export default {
other: 'Inne opcje segatools',
otherTooltip:
'Zaawansowane lub sytuacyjne opcje, które nie są objęte przez STARTLINERA',
prelaunch: 'Skrypt przedstartowy',
prelaunchTooltip: 'Opcjonalny skrypt uruchamiany przed grą.',
},
extensions: {
title: 'Rozszerzenia',

View File

@ -324,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
);
});