forked from akanyan/STARTLINER
feat: initial chunithm support
This commit is contained in:
@ -16,6 +16,7 @@ import StartButton from './StartButton.vue';
|
||||
import { invoke } from '../invoke';
|
||||
import { useGeneralStore, usePkgStore, usePrfStore } from '../stores';
|
||||
import { Dirs } from '../types';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
const pkg = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
@ -27,6 +28,7 @@ const currentTab: Ref<string | number> = ref(3);
|
||||
const pkgSearchTerm = ref('');
|
||||
|
||||
const isProfileDisabled = computed(() => prf.current === null);
|
||||
const isRunning = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
invoke('list_directories').then((d) => {
|
||||
@ -47,8 +49,23 @@ onMounted(async () => {
|
||||
key: 'segatools-mu3hook',
|
||||
force: false,
|
||||
});
|
||||
await invoke('install_package', {
|
||||
key: 'segatools-chusanhook',
|
||||
force: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
listen('launch-start', () => {
|
||||
isRunning.value = true;
|
||||
currentTab.value = 5;
|
||||
});
|
||||
|
||||
listen('launch-end', () => {
|
||||
isRunning.value = false;
|
||||
currentTab.value = 0;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -61,20 +78,27 @@ onMounted(async () => {
|
||||
>
|
||||
<div class="fixed w-full flex z-100">
|
||||
<TabList class="grow">
|
||||
<Tab :disabled="isProfileDisabled" :value="0"
|
||||
><div class="pi pi-list-check"></div
|
||||
<Tab :value="3" :disabled="isRunning"
|
||||
><div class="pi pi-question-circle"></div
|
||||
></Tab>
|
||||
<Tab :disabled="isProfileDisabled || isRunning" :value="0"
|
||||
><div class="pi pi-box"></div
|
||||
></Tab>
|
||||
<Tab v-if="prf.current?.meta.game === 'chunithm'" :disabled="isRunning" :value="4"
|
||||
><div class="pi pi-ticket"></div
|
||||
></Tab>
|
||||
<Tab
|
||||
v-if="pkg.networkStatus === 'online'"
|
||||
:disabled="isProfileDisabled"
|
||||
:disabled="isProfileDisabled || isRunning"
|
||||
:value="1"
|
||||
><div class="pi pi-download"></div
|
||||
></Tab>
|
||||
<Tab :disabled="isProfileDisabled" :value="2"
|
||||
<Tab :disabled="isProfileDisabled || isRunning" :value="2"
|
||||
><div class="pi pi-cog"></div
|
||||
></Tab>
|
||||
<Tab :value="3"
|
||||
><div class="pi pi-question-circle"></div
|
||||
|
||||
<Tab :value="5" v-if="isRunning"
|
||||
><div class="pi pi-sparkles"></div
|
||||
></Tab>
|
||||
<div class="grow"></div>
|
||||
<div class="flex gap-4">
|
||||
@ -131,16 +155,6 @@ onMounted(async () => {
|
||||
missing.<br />Existing features are expected to break
|
||||
sometimes.
|
||||
<ProfileList />
|
||||
<img
|
||||
v-if="prf.current?.game === 'ongeki'"
|
||||
src="/sticker-ongeki.svg"
|
||||
class="fixed bottom-0 right-0 z-999"
|
||||
/>
|
||||
<img
|
||||
v-else-if="prf.current?.game === 'chunithm'"
|
||||
src="/sticker-chunithm.svg"
|
||||
class="fixed bottom-0 right-0 z-999"
|
||||
/>
|
||||
<br /><br /><br />
|
||||
<footer>
|
||||
<Button
|
||||
@ -151,7 +165,24 @@ onMounted(async () => {
|
||||
/>
|
||||
</footer>
|
||||
</TabPanel>
|
||||
<TabPanel :value="4">
|
||||
CHUNITHM patches are not yet implemented.<br />Use
|
||||
<a href=https://patcher.two-torial.xyz/ target="_blank" style="text-decoration: underline;">patcher.two-torial.xyz</a>
|
||||
</TabPanel>
|
||||
<TabPanel :value="5">Running!</TabPanel>
|
||||
</TabPanels>
|
||||
<div v-if="currentTab === 5 || currentTab === 3">
|
||||
<img
|
||||
v-if="prf.current?.meta.game === 'ongeki'"
|
||||
src="/sticker-ongeki.svg"
|
||||
class="fixed bottom-0 right-0 z-999"
|
||||
/>
|
||||
<img
|
||||
v-else-if="prf.current?.meta.game === 'chunithm'"
|
||||
src="/sticker-chunithm.svg"
|
||||
class="fixed bottom-0 right-0 z-999"
|
||||
/>
|
||||
</div>
|
||||
</Tabs>
|
||||
</main>
|
||||
</template>
|
||||
|
@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { Ref, computed, ref } from 'vue';
|
||||
import Button from 'primevue/button';
|
||||
import Fieldset from 'primevue/fieldset';
|
||||
import ModListEntry from './ModListEntry.vue';
|
||||
import ModTitlecard from './ModTitlecard.vue';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePkgStore, usePrfStore } from '../stores';
|
||||
import { Package } from '../types';
|
||||
import { pkgKey } from '../util';
|
||||
|
||||
const props = defineProps({
|
||||
search: String,
|
||||
@ -14,12 +16,20 @@ const props = defineProps({
|
||||
const pkgs = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
const empty = ref(true);
|
||||
const gameSublist: Ref<string[]> = ref([]);
|
||||
|
||||
const group = () => {
|
||||
const a = Object.assign(
|
||||
invoke('get_game_packages', {
|
||||
game: prf.current?.meta.game,
|
||||
}).then((list) => {
|
||||
gameSublist.value = list as string[];
|
||||
});
|
||||
|
||||
const group = computed(() => {
|
||||
const res = Object.assign(
|
||||
{},
|
||||
Object.groupBy(
|
||||
pkgs.allLocal
|
||||
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||
.filter(
|
||||
(p) =>
|
||||
props.search === undefined ||
|
||||
@ -35,12 +45,12 @@ const group = () => {
|
||||
({ namespace }) => namespace
|
||||
)
|
||||
);
|
||||
empty.value = Object.keys(a).length === 0;
|
||||
return a;
|
||||
};
|
||||
empty.value = Object.keys(res).length === 0;
|
||||
return res;
|
||||
});
|
||||
|
||||
const missing = computed(() => {
|
||||
return prf.current?.mods.filter((m) => !pkgs.hasLocal(m)) ?? [];
|
||||
return prf.current?.data.mods.filter((m) => !pkgs.hasLocal(m)) ?? [];
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -68,7 +78,7 @@ const missing = computed(() => {
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>
|
||||
<Fieldset v-for="(namespace, key) in group()" :legend="key.toString()">
|
||||
<Fieldset v-for="(namespace, key) in group" :legend="key.toString()">
|
||||
<ModListEntry v-for="p in namespace" :pkg="p" />
|
||||
</Fieldset>
|
||||
<div v-if="empty" class="text-3xl">∅</div>
|
||||
|
@ -32,7 +32,6 @@ const model = computed({
|
||||
<div class="flex items-center">
|
||||
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
|
||||
<UpdateButton :pkg="pkg" />
|
||||
<!-- @vue-expect-error Can't 'as any' because it breaks VSCode -->
|
||||
<ToggleSwitch
|
||||
v-if="hasFeature(pkg, Feature.Mod)"
|
||||
class="scale-[1.33] shrink-0"
|
||||
|
@ -1,20 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Ref, ref } from 'vue';
|
||||
import Divider from 'primevue/divider';
|
||||
import MultiSelect from 'primevue/multiselect';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import ModStoreEntry from './ModStoreEntry.vue';
|
||||
import { usePkgStore } from '../stores';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePkgStore, usePrfStore } from '../stores';
|
||||
import { pkgKey } from '../util';
|
||||
|
||||
const pkgs = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
const empty = ref(true);
|
||||
|
||||
const props = defineProps({
|
||||
search: String,
|
||||
});
|
||||
|
||||
const gameSublist: Ref<string[]> = ref([]);
|
||||
|
||||
invoke('get_game_packages', {
|
||||
game: prf.current?.meta.game,
|
||||
}).then((list) => {
|
||||
gameSublist.value = list as string[];
|
||||
});
|
||||
|
||||
const list = () => {
|
||||
const res = pkgs.allRemote
|
||||
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||
.filter(
|
||||
(p) =>
|
||||
props.search === undefined ||
|
||||
|
@ -53,13 +53,16 @@ const iconSrc = computed(() => {
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="hasFeature(pkg, Feature.Hook)"
|
||||
v-if="
|
||||
hasFeature(pkg, Feature.ChusanHook) ||
|
||||
hasFeature(pkg, Feature.Mu3Hook)
|
||||
"
|
||||
v-tooltip="'Hook'"
|
||||
class="pi pi-wrench ml-1 text-blue-400"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="hasFeature(pkg, Feature.GameIO)"
|
||||
v-if="hasFeature(pkg, Feature.Mu3IO)"
|
||||
v-tooltip="'IO'"
|
||||
class="pi pi-wrench ml-1 text-green-400"
|
||||
>
|
||||
|
@ -1,399 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, ref } from 'vue';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Select from 'primevue/select';
|
||||
import SelectButton from 'primevue/selectbutton';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import FileEditor from './FileEditor.vue';
|
||||
import FilePicker from './FilePicker.vue';
|
||||
import OptionCategory from './OptionCategory.vue';
|
||||
import OptionRow from './OptionRow.vue';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePkgStore, usePrfStore } from '../stores';
|
||||
import { Feature } from '../types';
|
||||
import { hasFeature, pkgKey } from '../util';
|
||||
import AimeOptions from './options/Aime.vue';
|
||||
import DisplayOptions from './options/Display.vue';
|
||||
import MiscOptions from './options/Misc.vue';
|
||||
import NetworkOptions from './options/Network.vue';
|
||||
import SegatoolsOptions from './options/Segatools.vue';
|
||||
import { usePrfStore } from '../stores';
|
||||
|
||||
const pkgs = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
|
||||
const aimeCode = ref('');
|
||||
const capabilities: Ref<string[]> = ref([]);
|
||||
|
||||
const displayList: Ref<{ title: string; value: string }[]> = ref([
|
||||
{
|
||||
title: 'Primary',
|
||||
value: 'default',
|
||||
},
|
||||
]);
|
||||
|
||||
const loadDisplays = () => {
|
||||
const newList = [
|
||||
{
|
||||
title: 'Primary',
|
||||
value: 'default',
|
||||
},
|
||||
];
|
||||
invoke('list_platform_capabilities')
|
||||
.then(async (v: unknown) => {
|
||||
let different = false;
|
||||
if (Array.isArray(v)) {
|
||||
capabilities.value.push(...v);
|
||||
}
|
||||
if (capabilities.value.includes('display')) {
|
||||
for (const [devName, devString] of (await invoke(
|
||||
'list_displays'
|
||||
)) as Array<[string, string]>) {
|
||||
newList.push({
|
||||
title: `${devName.replace('\\\\.\\', '')} (${devString})`,
|
||||
value: devName,
|
||||
});
|
||||
if (
|
||||
displayList.value.find(
|
||||
(item) => item.value === devName
|
||||
) === undefined
|
||||
) {
|
||||
different = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (displayList.value.length !== newList.length) {
|
||||
different = true;
|
||||
}
|
||||
if (different) {
|
||||
displayList.value = newList;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
loadDisplays();
|
||||
prf.reload();
|
||||
|
||||
const aimeCodeModel = computed({
|
||||
get() {
|
||||
return aimeCode.value;
|
||||
},
|
||||
async set(value: string) {
|
||||
aimeCode.value = value;
|
||||
if (value.match(/^[0-9]{20}$/) || value.length === 0) {
|
||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||
await writeTextFile(aime_path, aimeCode.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const extraDisplayOptionsDisabled = computed(() => {
|
||||
return prf.current?.display.target === 'default';
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||
aimeCode.value = await readTextFile(aime_path).catch(() => '');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="General">
|
||||
<OptionRow title="mu3.exe">
|
||||
<FilePicker
|
||||
:directory="false"
|
||||
promptname="mu3.exe"
|
||||
extension="exe"
|
||||
:value="prf.current!.sgt.target"
|
||||
:callback="(value: string) => (prf.current!.sgt.target = value)"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
|
||||
<OptionRow title="amfs">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
placeholder="amfs"
|
||||
:value="prf.current!.sgt.amfs"
|
||||
:callback="(value: string) => (prf.current!.sgt.amfs = value)"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="option">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
placeholder="option"
|
||||
:value="prf.current!.sgt.option"
|
||||
:callback="(value: string) => (prf.current!.sgt.option = value)"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="appdata">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
:value="prf.current!.sgt.appdata"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.sgt.appdata = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="mu3hook">
|
||||
<Select
|
||||
v-model="prf.current!.sgt.hook"
|
||||
:options="
|
||||
pkgs.hooks.map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
})
|
||||
"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow title="mu3io">
|
||||
<Select
|
||||
v-model="prf.current!.sgt.io"
|
||||
placeholder="segatools built-in"
|
||||
:options="[
|
||||
{ title: 'segatools built-in', value: null },
|
||||
...pkgs.gameIOs.map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
}),
|
||||
]"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Display">
|
||||
<OptionRow
|
||||
v-if="capabilities.includes('display')"
|
||||
title="Target display"
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.display.target"
|
||||
:options="displayList"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
placeholder="(Disconnected)"
|
||||
@show="loadDisplays"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow class="number-input" title="Game resolution">
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="480"
|
||||
:max="9999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.display.rez[0]"
|
||||
/>
|
||||
x
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="640"
|
||||
:max="9999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.display.rez[1]"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Display mode">
|
||||
<SelectButton
|
||||
v-model="prf.current!.display.mode"
|
||||
:options="[
|
||||
{ title: 'Window', value: 'Window' },
|
||||
{ title: 'Borderless window', value: 'Borderless' },
|
||||
{ title: 'Fullscreen', value: 'Fullscreen' },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Display rotation"
|
||||
v-if="capabilities.includes('display')"
|
||||
>
|
||||
<SelectButton
|
||||
v-model="prf.current!.display.rotation"
|
||||
:options="[
|
||||
{ title: 'Unchanged', value: 0 },
|
||||
{ title: 'Portrait', value: 90 },
|
||||
{ title: 'Portrait (flipped)', value: 270 },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
v-if="capabilities.includes('display')"
|
||||
class="number-input"
|
||||
title="Refresh Rate"
|
||||
>
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="60"
|
||||
:max="999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.display.frequency"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Borderless fullscreen"
|
||||
v-if="capabilities.includes('display')"
|
||||
tooltip="Match display resolution with the game."
|
||||
>
|
||||
<ToggleSwitch
|
||||
:disabled="
|
||||
extraDisplayOptionsDisabled ||
|
||||
prf.current?.display.mode !== 'Borderless'
|
||||
"
|
||||
v-model="prf.current!.display.borderless_fullscreen"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Network">
|
||||
<OptionRow title="Network type">
|
||||
<SelectButton
|
||||
v-model="prf.current!.network.network_type"
|
||||
:options="[
|
||||
{ title: 'Remote', value: 'Remote' },
|
||||
{ title: 'Local (ARTEMiS)', value: 'Artemis' },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
v-if="prf.current!.network.network_type == 'Artemis'"
|
||||
title="ARTEMiS path"
|
||||
>
|
||||
<FilePicker
|
||||
:directory="false"
|
||||
promptname="index.py"
|
||||
extension="py"
|
||||
:value="prf.current!.network.local_path"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.network.local_path = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<!-- <OptionRow
|
||||
v-if="prf.current!.network.network_type == 'Artemis'"
|
||||
title="ARTEMiS console"
|
||||
>
|
||||
<ToggleSwitch v-model="prf.current!.network.local_console" />
|
||||
</OptionRow> -->
|
||||
<OptionRow
|
||||
v-if="prf.current!.network.network_type == 'Remote'"
|
||||
title="Server address"
|
||||
>
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="40"
|
||||
placeholder="192.168.1.234"
|
||||
v-model="prf.current!.network.remote_address"
|
||||
/> </OptionRow
|
||||
><OptionRow
|
||||
v-if="prf.current!.network.network_type == 'Remote'"
|
||||
title="Keychip"
|
||||
>
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="16"
|
||||
placeholder="A123-01234567890"
|
||||
v-model="prf.current!.network.keychip"
|
||||
/> </OptionRow
|
||||
><OptionRow title="Subnet">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="15"
|
||||
placeholder="192.168.1.0"
|
||||
v-model="prf.current!.network.subnet"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Address suffix">
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="3"
|
||||
:min="0"
|
||||
:max="255"
|
||||
placeholder="12"
|
||||
v-model="prf.current!.network.suffix"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Aime">
|
||||
<OptionRow title="Aime emulation">
|
||||
<Select
|
||||
v-model="prf.current!.sgt.aime"
|
||||
:options="[
|
||||
{ title: 'none', value: 'Disabled' },
|
||||
{ title: 'segatools built-in', value: 'BuiltIn' },
|
||||
...pkgs.aimes.map((p) => {
|
||||
return {
|
||||
title: pkgKey(p),
|
||||
value: hasFeature(p, Feature.AMNet)
|
||||
? { AMNet: pkgKey(p) }
|
||||
: { Other: pkgKey(p) },
|
||||
};
|
||||
}),
|
||||
]"
|
||||
placeholder="none"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow title="Aime code">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:disabled="prf.current!.sgt.aime === 'Disabled'"
|
||||
:maxlength="20"
|
||||
placeholder="00000000000000000000"
|
||||
v-model="aimeCodeModel"
|
||||
/>
|
||||
</OptionRow>
|
||||
<div v-if="prf.current!.sgt.aime?.hasOwnProperty('AMNet')">
|
||||
<OptionRow title="Server name">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
placeholder="CHUNI-PENGUIN"
|
||||
:maxlength="50"
|
||||
v-model="prf.current!.sgt.amnet.name"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Server address">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
placeholder="http://+:6070"
|
||||
:maxlength="50"
|
||||
v-model="prf.current!.sgt.amnet.addr"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Use AiMeDB for physical cards"
|
||||
tooltip="Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab."
|
||||
>
|
||||
<ToggleSwitch v-model="prf.current!.sgt.amnet.physical" />
|
||||
</OptionRow>
|
||||
</div>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Misc">
|
||||
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
|
||||
<ToggleSwitch v-model="prf.current!.sgt.intel" />
|
||||
</OptionRow>
|
||||
<OptionRow title="More segatools options">
|
||||
<FileEditor filename="segatools-base.ini" />
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
<OptionCategory title="Extensions">
|
||||
<SegatoolsOptions />
|
||||
<DisplayOptions v-if="prf.current!.meta.game === 'ongeki'" />
|
||||
<NetworkOptions />
|
||||
<AimeOptions />
|
||||
<MiscOptions />
|
||||
<OptionCategory
|
||||
title="Extensions"
|
||||
v-if="prf.current!.meta.game === 'ongeki'"
|
||||
>
|
||||
<OptionRow title="Inohara config">
|
||||
<FileEditor
|
||||
filename="inohara.cfg"
|
||||
@ -402,7 +33,8 @@ const extraDisplayOptionsDisabled = computed(() => {
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="BepInEx console">
|
||||
<ToggleSwitch v-model="prf.current!.bepinex.console" />
|
||||
<!-- @vue-expect-error -->
|
||||
<ToggleSwitch v-model="prf.current!.data.bepinex.console" />
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
||||
|
@ -19,7 +19,6 @@ const prf = usePrfStore();
|
||||
icon="pi pi-plus"
|
||||
class="chunithm-button profile-button"
|
||||
@click="() => prf.create('chunithm')"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-12 flex flex-col flex-wrap align-middle gap-4">
|
||||
|
@ -61,7 +61,8 @@ const deleteProfile = async () => {
|
||||
<div class="flex flex-row flex-wrap align-middle gap-2">
|
||||
<Button
|
||||
:disabled="
|
||||
prf.current?.game === p!.game && prf.current?.name === p!.name
|
||||
prf.current?.meta.game === p!.game &&
|
||||
prf.current?.meta.name === p!.name
|
||||
"
|
||||
:class="
|
||||
(p!.game === 'chunithm' ? 'chunithm-button' : 'ongeki-button') +
|
||||
|
@ -5,6 +5,7 @@ import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import ScrollPanel from 'primevue/scrollpanel';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { invoke } from '../invoke';
|
||||
import { usePrfStore } from '../stores';
|
||||
|
||||
@ -54,17 +55,19 @@ const startline = async (force: boolean) => {
|
||||
|
||||
const kill = async () => {
|
||||
await invoke('kill');
|
||||
startStatus.value = 'ready';
|
||||
};
|
||||
|
||||
const disabledTooltip = computed(() => {
|
||||
if (prf.current?.sgt.target.length === 0) {
|
||||
if (prf.current?.data.sgt.target.length === 0) {
|
||||
return 'The game path must be specified';
|
||||
}
|
||||
if (prf.current?.sgt.amfs.length === 0) {
|
||||
if (prf.current?.data.sgt.amfs.length === 0) {
|
||||
return 'The amfs path must be specified';
|
||||
}
|
||||
if (prf.current?.sgt.hook === null || prf.current?.sgt.hook === undefined) {
|
||||
if (
|
||||
prf.current?.data.sgt.hook === null ||
|
||||
prf.current?.data.sgt.hook === undefined
|
||||
) {
|
||||
return 'A segatools hook package is necessary';
|
||||
}
|
||||
return null;
|
||||
@ -72,10 +75,13 @@ const disabledTooltip = computed(() => {
|
||||
|
||||
listen('launch-start', () => {
|
||||
startStatus.value = 'running';
|
||||
getCurrentWindow().minimize();
|
||||
});
|
||||
|
||||
listen('launch-end', () => {
|
||||
startStatus.value = 'ready';
|
||||
getCurrentWindow().unminimize();
|
||||
getCurrentWindow().setFocus();
|
||||
});
|
||||
|
||||
const messageSplit = (message: any) => {
|
||||
|
99
src/components/options/Aime.vue
Normal file
99
src/components/options/Aime.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Select from 'primevue/select';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import * as path from '@tauri-apps/api/path';
|
||||
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { usePkgStore, usePrfStore } from '../../stores';
|
||||
import { Feature } from '../../types';
|
||||
import { hasFeature, pkgKey } from '../../util';
|
||||
|
||||
const pkgs = usePkgStore();
|
||||
const prf = usePrfStore();
|
||||
|
||||
const aimeCode = ref('');
|
||||
|
||||
prf.reload();
|
||||
|
||||
const aimeCodeModel = computed({
|
||||
get() {
|
||||
return aimeCode.value;
|
||||
},
|
||||
async set(value: string) {
|
||||
aimeCode.value = value;
|
||||
if (value.match(/^[0-9]{20}$/) || value.length === 0) {
|
||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||
await writeTextFile(aime_path, aimeCode.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const aime_path = await path.join(await prf.configDir, 'aime.txt');
|
||||
aimeCode.value = await readTextFile(aime_path).catch(() => '');
|
||||
})();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Aime">
|
||||
<OptionRow title="Aime emulation">
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.aime"
|
||||
:options="[
|
||||
{ title: 'none', value: 'Disabled' },
|
||||
{ title: 'segatools built-in', value: 'BuiltIn' },
|
||||
...pkgs.byFeature(Feature.Aime).map((p) => {
|
||||
return {
|
||||
title: pkgKey(p),
|
||||
value: hasFeature(p, Feature.AMNet)
|
||||
? { AMNet: pkgKey(p) }
|
||||
: { Other: pkgKey(p) },
|
||||
};
|
||||
}),
|
||||
]"
|
||||
placeholder="none"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow title="Aime code">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:disabled="prf.current!.data.sgt.aime === 'Disabled'"
|
||||
:maxlength="20"
|
||||
placeholder="00000000000000000000"
|
||||
v-model="aimeCodeModel"
|
||||
/>
|
||||
</OptionRow>
|
||||
<div v-if="prf.current!.data.sgt.aime?.hasOwnProperty('AMNet')">
|
||||
<OptionRow title="Server name">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
placeholder="CHUNI-PENGUIN"
|
||||
:maxlength="50"
|
||||
v-model="prf.current!.data.sgt.amnet.name"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Server address">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
placeholder="http://+:6070"
|
||||
:maxlength="50"
|
||||
v-model="prf.current!.data.sgt.amnet.addr"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Use AiMeDB for physical cards"
|
||||
tooltip="Whether physical cards should use AiMeDB to retrieve access codes. If the game is using a hosted network, enable this option to load the same account data/profile as you would get on a physical cab."
|
||||
>
|
||||
<ToggleSwitch v-model="prf.current!.data.sgt.amnet.physical" />
|
||||
</OptionRow>
|
||||
</div>
|
||||
</OptionCategory>
|
||||
</template>
|
161
src/components/options/Display.vue
Normal file
161
src/components/options/Display.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, ref } from 'vue';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Select from 'primevue/select';
|
||||
import SelectButton from 'primevue/selectbutton';
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { invoke } from '../../invoke';
|
||||
import { usePrfStore } from '../../stores';
|
||||
|
||||
const capabilities: Ref<string[]> = ref([]);
|
||||
const displayList: Ref<{ title: string; value: string }[]> = ref([
|
||||
{
|
||||
title: 'Primary',
|
||||
value: 'default',
|
||||
},
|
||||
]);
|
||||
const prf = usePrfStore();
|
||||
|
||||
const extraDisplayOptionsDisabled = computed(() => {
|
||||
return prf.current?.data.display.target === 'default';
|
||||
});
|
||||
|
||||
const loadDisplays = () => {
|
||||
const newList = [
|
||||
{
|
||||
title: 'Primary',
|
||||
value: 'default',
|
||||
},
|
||||
];
|
||||
invoke('list_platform_capabilities')
|
||||
.then(async (v: unknown) => {
|
||||
let different = false;
|
||||
if (Array.isArray(v)) {
|
||||
capabilities.value.push(...v);
|
||||
}
|
||||
if (capabilities.value.includes('display')) {
|
||||
for (const [devName, devString] of (await invoke(
|
||||
'list_displays'
|
||||
)) as Array<[string, string]>) {
|
||||
newList.push({
|
||||
title: `${devName.replace('\\\\.\\', '')} (${devString})`,
|
||||
value: devName,
|
||||
});
|
||||
if (
|
||||
displayList.value.find(
|
||||
(item) => item.value === devName
|
||||
) === undefined
|
||||
) {
|
||||
different = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (displayList.value.length !== newList.length) {
|
||||
different = true;
|
||||
}
|
||||
if (different) {
|
||||
displayList.value = newList;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
loadDisplays();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Display">
|
||||
<OptionRow
|
||||
v-if="capabilities.includes('display')"
|
||||
title="Target display"
|
||||
>
|
||||
<Select
|
||||
v-model="prf.current!.data.display.target"
|
||||
:options="displayList"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
placeholder="(Disconnected)"
|
||||
@show="loadDisplays"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow class="number-input" title="Game resolution">
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="480"
|
||||
:max="9999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.data.display.rez[0]"
|
||||
/>
|
||||
x
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="640"
|
||||
:max="9999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.data.display.rez[1]"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Display mode">
|
||||
<SelectButton
|
||||
v-model="prf.current!.data.display.mode"
|
||||
:options="[
|
||||
{ title: 'Window', value: 'Window' },
|
||||
{ title: 'Borderless window', value: 'Borderless' },
|
||||
{ title: 'Fullscreen', value: 'Fullscreen' },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Display rotation"
|
||||
v-if="capabilities.includes('display')"
|
||||
>
|
||||
<SelectButton
|
||||
v-model="prf.current!.data.display.rotation"
|
||||
:options="[
|
||||
{ title: 'Unchanged', value: 0 },
|
||||
{ title: 'Portrait', value: 90 },
|
||||
{ title: 'Portrait (flipped)', value: 270 },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
v-if="capabilities.includes('display')"
|
||||
class="number-input"
|
||||
title="Refresh Rate"
|
||||
>
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:min="60"
|
||||
:max="999"
|
||||
:use-grouping="false"
|
||||
v-model="prf.current!.data.display.frequency"
|
||||
:disabled="extraDisplayOptionsDisabled"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
title="Borderless fullscreen"
|
||||
v-if="capabilities.includes('display')"
|
||||
tooltip="Match display resolution with the game."
|
||||
>
|
||||
<ToggleSwitch
|
||||
:disabled="
|
||||
extraDisplayOptionsDisabled ||
|
||||
prf.current?.data.display.mode !== 'Borderless'
|
||||
"
|
||||
v-model="prf.current!.data.display.borderless_fullscreen"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
20
src/components/options/Misc.vue
Normal file
20
src/components/options/Misc.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import ToggleSwitch from 'primevue/toggleswitch';
|
||||
import FileEditor from '../FileEditor.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { usePrfStore } from '../../stores';
|
||||
|
||||
const prf = usePrfStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Misc">
|
||||
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
|
||||
<ToggleSwitch v-model="prf.current!.data.sgt.intel" />
|
||||
</OptionRow>
|
||||
<OptionRow title="More segatools options">
|
||||
<FileEditor filename="segatools-base.ini" />
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
91
src/components/options/Network.vue
Normal file
91
src/components/options/Network.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import SelectButton from 'primevue/selectbutton';
|
||||
import FilePicker from '../FilePicker.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { usePrfStore } from '../../stores';
|
||||
|
||||
const prf = usePrfStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="Network">
|
||||
<OptionRow title="Network type">
|
||||
<SelectButton
|
||||
v-model="prf.current!.data.network.network_type"
|
||||
:options="[
|
||||
{ title: 'Remote', value: 'Remote' },
|
||||
{ title: 'Local (ARTEMiS)', value: 'Artemis' },
|
||||
]"
|
||||
:allow-empty="false"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow
|
||||
v-if="prf.current!.data.network.network_type == 'Artemis'"
|
||||
title="ARTEMiS path"
|
||||
>
|
||||
<FilePicker
|
||||
:directory="false"
|
||||
promptname="index.py"
|
||||
extension="py"
|
||||
:value="prf.current!.data.network.local_path"
|
||||
:callback="
|
||||
(value: string) =>
|
||||
(prf.current!.data.network.local_path = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<!-- <OptionRow
|
||||
v-if="prf.current!.data.network.network_type == 'Artemis'"
|
||||
title="ARTEMiS console"
|
||||
>
|
||||
<ToggleSwitch v-model="prf.current!.data.network.local_console" />
|
||||
</OptionRow> -->
|
||||
<OptionRow
|
||||
v-if="prf.current!.data.network.network_type == 'Remote'"
|
||||
title="Server address"
|
||||
>
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="40"
|
||||
placeholder="192.168.1.234"
|
||||
v-model="prf.current!.data.network.remote_address"
|
||||
/> </OptionRow
|
||||
><OptionRow
|
||||
v-if="prf.current!.data.network.network_type == 'Remote'"
|
||||
title="Keychip"
|
||||
>
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="16"
|
||||
placeholder="A123-01234567890"
|
||||
v-model="prf.current!.data.network.keychip"
|
||||
/> </OptionRow
|
||||
><OptionRow title="Subnet">
|
||||
<InputText
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="15"
|
||||
placeholder="192.168.1.0"
|
||||
v-model="prf.current!.data.network.subnet"
|
||||
/>
|
||||
</OptionRow>
|
||||
<OptionRow title="Address suffix">
|
||||
<InputNumber
|
||||
class="shrink"
|
||||
size="small"
|
||||
:maxlength="3"
|
||||
:min="0"
|
||||
:max="255"
|
||||
placeholder="12"
|
||||
v-model="prf.current!.data.network.suffix"
|
||||
/>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
112
src/components/options/Segatools.vue
Normal file
112
src/components/options/Segatools.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import Select from 'primevue/select';
|
||||
import FilePicker from '../FilePicker.vue';
|
||||
import OptionCategory from '../OptionCategory.vue';
|
||||
import OptionRow from '../OptionRow.vue';
|
||||
import { usePkgStore, usePrfStore } from '../../stores';
|
||||
import { Feature } from '../../types';
|
||||
import { pkgKey } from '../../util';
|
||||
|
||||
const prf = usePrfStore();
|
||||
const pkgs = usePkgStore();
|
||||
|
||||
const names = computed(() => {
|
||||
switch (prf.current?.meta.game) {
|
||||
case 'ongeki': {
|
||||
return {
|
||||
exe: 'mu3.exe',
|
||||
hook: 'mu3hook',
|
||||
io: 'mu3io',
|
||||
};
|
||||
}
|
||||
case 'chunithm': {
|
||||
return {
|
||||
exe: 'chusanApp.exe',
|
||||
hook: 'chusanhook',
|
||||
io: 'chuniio',
|
||||
};
|
||||
}
|
||||
case undefined:
|
||||
throw new Error('Option tab without a profile');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OptionCategory title="General">
|
||||
<OptionRow :title="names.exe">
|
||||
<FilePicker
|
||||
:directory="false"
|
||||
:promptname="names.exe"
|
||||
extension="exe"
|
||||
:value="prf.current!.data.sgt.target"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.data.sgt.target = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
|
||||
<OptionRow title="amfs">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
placeholder="amfs"
|
||||
:value="prf.current!.data.sgt.amfs"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.data.sgt.amfs = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="option">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
placeholder="option"
|
||||
:value="prf.current!.data.sgt.option"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.data.sgt.option = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow title="appdata">
|
||||
<FilePicker
|
||||
:directory="true"
|
||||
:value="prf.current!.data.sgt.appdata"
|
||||
:callback="
|
||||
(value: string) => (prf.current!.data.sgt.appdata = value)
|
||||
"
|
||||
></FilePicker>
|
||||
</OptionRow>
|
||||
<OptionRow :title="names.hook">
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.hook"
|
||||
:options="
|
||||
pkgs
|
||||
.byFeature(
|
||||
prf.current?.meta.game === 'ongeki'
|
||||
? Feature.Mu3Hook
|
||||
: Feature.ChusanHook
|
||||
)
|
||||
.map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
})
|
||||
"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
<OptionRow :title="names.io" v-if="prf.current?.meta.game === 'ongeki'">
|
||||
<Select
|
||||
v-model="prf.current!.data.sgt.io"
|
||||
placeholder="segatools built-in"
|
||||
:options="[
|
||||
{ title: 'segatools built-in', value: null },
|
||||
...pkgs.byFeature(Feature.Mu3IO).map((p) => {
|
||||
return { title: pkgKey(p), value: pkgKey(p) };
|
||||
}),
|
||||
]"
|
||||
option-label="title"
|
||||
option-value="value"
|
||||
></Select>
|
||||
</OptionRow>
|
||||
</OptionCategory>
|
||||
</template>
|
@ -100,14 +100,8 @@ export const usePkgStore = defineStore('pkg', {
|
||||
(c) => !state.excludeCategories.includes(c)
|
||||
))
|
||||
),
|
||||
hooks: (state) =>
|
||||
Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Hook)),
|
||||
gameIOs: (state) =>
|
||||
Object.values(state.pkg).filter((p) =>
|
||||
hasFeature(p, Feature.GameIO)
|
||||
),
|
||||
aimes: (state) =>
|
||||
Object.values(state.pkg).filter((p) => hasFeature(p, Feature.Aime)),
|
||||
byFeature: (state) => (feature: Feature) =>
|
||||
Object.values(state.pkg).filter((p) => hasFeature(p, feature)),
|
||||
},
|
||||
actions: {
|
||||
setupListeners() {
|
||||
@ -189,18 +183,14 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
() =>
|
||||
pkg !== undefined &&
|
||||
current.value !== null &&
|
||||
current.value?.mods.includes(pkgKey(pkg))
|
||||
current.value?.data.mods.includes(pkgKey(pkg))
|
||||
);
|
||||
|
||||
const reload = async () => {
|
||||
const p = (await invoke('get_current_profile')) as any;
|
||||
if (p != null && 'OngekiProfile' in p) {
|
||||
current.value = { ...p.OngekiProfile, game: 'ongeki' };
|
||||
} else {
|
||||
current.value = null;
|
||||
}
|
||||
const p = (await invoke('get_current_profile')) as Profile;
|
||||
current.value = p;
|
||||
if (current.value !== null) {
|
||||
changePrimaryColor(current.value.game);
|
||||
changePrimaryColor(current.value.meta.game);
|
||||
} else {
|
||||
changePrimaryColor(null);
|
||||
}
|
||||
@ -231,10 +221,10 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
});
|
||||
|
||||
if (
|
||||
current.value?.game === profile.game &&
|
||||
current.value.name === profile.name
|
||||
current.value?.meta.game === profile.game &&
|
||||
current.value.meta.name === profile.name
|
||||
) {
|
||||
current.value.name = name;
|
||||
current.value.meta.name = name;
|
||||
}
|
||||
|
||||
await reloadList();
|
||||
@ -272,7 +262,7 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
const configDir = computed(async () => {
|
||||
return await path.join(
|
||||
generalStore.configDir,
|
||||
`profile-${current.value?.game}-${current.value?.name}`
|
||||
`profile-${current.value?.meta.game}-${current.value?.meta.name}`
|
||||
);
|
||||
});
|
||||
|
||||
@ -283,7 +273,7 @@ export const usePrfStore = defineStore('prf', () => {
|
||||
watchEffect(async () => {
|
||||
if (current.value !== null) {
|
||||
await invoke('sync_current_profile', {
|
||||
profile: { OngekiProfile: current.value },
|
||||
data: current.value.data,
|
||||
});
|
||||
if (timeout !== null) {
|
||||
clearTimeout(timeout);
|
||||
|
28
src/types.ts
28
src/types.ts
@ -24,11 +24,12 @@ export interface Package {
|
||||
}
|
||||
|
||||
export enum Feature {
|
||||
Mod = 0b00001,
|
||||
Hook = 0b00010,
|
||||
GameIO = 0b00100,
|
||||
Aime = 0b01000,
|
||||
AMNet = 0b10000,
|
||||
Mod = 1 << 0,
|
||||
Aime = 1 << 1,
|
||||
AMNet = 1 << 2,
|
||||
Mu3Hook = 1 << 3,
|
||||
Mu3IO = 1 << 4,
|
||||
ChusanHook = 1 << 5,
|
||||
}
|
||||
|
||||
export type Status =
|
||||
@ -45,6 +46,14 @@ export interface ProfileMeta {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ProfileData {
|
||||
mods: string[];
|
||||
sgt: SegatoolsConfig;
|
||||
display: DisplayConfig;
|
||||
network: NetworkConfig;
|
||||
bepinex: BepInExConfig;
|
||||
}
|
||||
|
||||
export interface SegatoolsConfig {
|
||||
target: string;
|
||||
hook: string | null;
|
||||
@ -83,12 +92,9 @@ export interface BepInExConfig {
|
||||
console: boolean;
|
||||
}
|
||||
|
||||
export interface Profile extends ProfileMeta {
|
||||
mods: string[];
|
||||
sgt: SegatoolsConfig;
|
||||
display: DisplayConfig;
|
||||
network: NetworkConfig;
|
||||
bepinex: BepInExConfig;
|
||||
export interface Profile {
|
||||
meta: ProfileMeta;
|
||||
data: ProfileData;
|
||||
}
|
||||
|
||||
export type Module = 'sgt' | 'display' | 'network';
|
||||
|
Reference in New Issue
Block a user