setup: check requirements in the frontend

This commit is contained in:
Tyler Wilding 2022-04-17 20:07:01 -04:00
parent efd7a5daad
commit 95eb86cfb2
No known key found for this signature in database
GPG key ID: A89403EB356ED106
3 changed files with 174 additions and 54 deletions

View file

@ -159,6 +159,7 @@ body {
height: 100vh;
width: 90vw;
max-width: 90vw;
overflow-y: scroll;
}
.flex-center {
@ -271,3 +272,21 @@ body {
details > summary:hover {
cursor: pointer;
}
.progress-row a {
text-decoration: none;
color: #ffa500;
}
.progress-row a:hover {
color: #ffbd42;
}
.orange-text {
color: #ffa500;
}
ul.no-decoration {
list-style-type: none;
padding-left: 2em;
}

View file

@ -1,5 +1,7 @@
import { Command } from "@tauri-apps/api/shell";
import { resourceDir } from "@tauri-apps/api/path";
import { os } from "@tauri-apps/api";
import { getHighestSimd } from "/src/lib/commands";
export class InstallationStatus {
static Pending = Symbol("pending");
@ -8,6 +10,13 @@ export class InstallationStatus {
static Success = Symbol("success");
}
export class RequirementStatus {
static Unknown = Symbol("unknown");
static Met = Symbol("met");
static Failed = Symbol("failed");
static Checking = Symbol("checking");
}
// TODO - is this set to `production` properly in release mode?
function isInDebugMode() {
return process.env.NODE_ENV === "development";
@ -21,6 +30,35 @@ if (isInDebugMode()) {
debugPath += "\\launcher\\bundle-test\\data";
}
export async function isAVXSupported() {
let highestSIMD = await getHighestSimd();
if (highestSIMD === undefined) {
return RequirementStatus.Unknown;
}
if (highestSIMD.toLowerCase().startsWith("avx")) {
return RequirementStatus.Met
}
return RequirementStatus.Failed;
}
export async function isOpenGLVersionSupported(version) {
// TODO - glewinfo not pre-compiled to work on linux yet!
if (await os.platform() === "linux" || await os.platform() === "darwin") {
return RequirementStatus.Unknown;
}
// Otherwise, query for the version
let command = Command.sidecar("bin/glewinfo", ["-version", version], { cwd: "bin" });
try {
let output = await command.execute();
if (output.code === 0) {
return RequirementStatus.Met;
}
return RequirementStatus.Failed;
} catch {
return RequirementStatus.Failed;
}
}
/**
* @param {String} filePath
* @returns {Promise<ChildProcess>}

View file

@ -1,4 +1,5 @@
<script>
import { onMount } from "svelte";
import { Link, navigate } from "svelte-routing";
import { filePrompt } from "/src/lib/utils/file";
import {
@ -6,9 +7,11 @@
decompileGameData,
extractISO,
validateGameData,
isAVXSupported,
isOpenGLVersionSupported,
} from "/src/lib/setup";
import { SupportedGame, setInstallStatus } from "/src/lib/config";
import { InstallationStatus } from "/src/lib/setup";
import { InstallationStatus, RequirementStatus } from "/src/lib/setup";
import {
appendToInstallLog,
appendToInstallErrorLog,
@ -18,6 +21,19 @@
let setupInProgress = false;
let isoPath = undefined;
let requirementChecks = [
{
status: RequirementStatus.Checking,
text: `CPU Supports&nbsp;<a href="https://en.wikipedia.org/wiki/Advanced_Vector_Extensions"><strong>AVX or AVX2</strong></a>`,
check: async () => await isAVXSupported(),
},
{
status: RequirementStatus.Checking,
text: `GPU Supports&nbsp;OpenGL&nbsp;<span class="orange-text"><strong>4.3</strong></span>`,
check: async () => await isOpenGLVersionSupported("4.3"),
},
];
let currStep = 0;
let installSteps = [
{
@ -46,18 +62,42 @@
},
];
// TODO - move this to the enum
function statusIndicator(status) {
if (status === InstallationStatus.InProgress) {
return `spinner`;
} else if (status === InstallationStatus.Success) {
if (
status === InstallationStatus.InProgress ||
status === RequirementStatus.Checking
) {
return `<div class="loader" />`;
} else if (
status === InstallationStatus.Success ||
status === RequirementStatus.Met
) {
return "✅";
} else if (status === InstallationStatus.Failed) {
} else if (
status === InstallationStatus.Failed ||
status === RequirementStatus.Failed
) {
return "❌";
} else if (status === RequirementStatus.Unknown) {
return "⚠️";
} else {
return "⏳";
}
}
function areRequirementsMet(checks) {
for (let i = 0; i < checks.length; i++) {
if (
checks[i].status !== RequirementStatus.Met &&
checks[i].status !== RequirementStatus.Unknown
) {
return false;
}
}
return true;
}
function finishStep(output) {
appendLogs(output);
installSteps[currStep].status =
@ -72,7 +112,10 @@
async function appendLogs(output) {
const separator = `----${installSteps[currStep].text}----\n`;
await appendToInstallLog(SupportedGame.Jak1, "\n" + separator + output.stdout);
await appendToInstallLog(
SupportedGame.Jak1,
"\n" + separator + output.stdout
);
await appendToInstallErrorLog(
SupportedGame.Jak1,
"\n" + separator + output.stderr
@ -111,6 +154,12 @@
}
// Events
onMount(async () => {
for (let i = 0; i < requirementChecks.length; i++) {
requirementChecks[i].status = await requirementChecks[i].check();
}
});
async function onClickBrowse() {
isoPath = await filePrompt();
installProcess();
@ -119,54 +168,68 @@
<div class="content">
<h1>Setup Process</h1>
<p>Browse for your ISO - Obtained by dumping your own legitimate copy</p>
<div>
<button class="btn" on:click={onClickBrowse}>Browse for ISO</button>
{#if isoPath}
{isoPath}
{/if}
<span id="filePathLabel" />
</div>
{#if !setupInProgress}
<div class="row">
<Link to="/jak1">
<button class="btn">Cancel</button>
</Link>
</div>
{:else}
<div>
<h2>Progress</h2>
<ul>
{#each installSteps as step}
<li>
<span class="progress-row">
{#if step.status === InstallationStatus.InProgress}
<div class="loader" />
{:else}
{statusIndicator(step.status)}
{/if}
{step.text}
</span>
</li>
{/each}
</ul>
</div>
<div class="row">
<details>
<summary>Installation Logs</summary>
<div class="logContainer">
{#each installSteps as step}
{#if step.logs !== ""}
{step.logs}
{/if}
{#if step.errorLogs !== ""}
<div class="errorLogs">
{step.errorLogs}
</div>
{/if}
{/each}
<h2>Minimum Requirements</h2>
<ul class="no-decoration">
{#each requirementChecks as check}
<li>
<span class="progress-row">
{@html statusIndicator(check.status)}
{@html check.text}
</span>
{#if check.status === RequirementStatus.Unknown}
<ul class="no-decoration">
<li>Unable to determine this requirement</li>
</ul>
{/if}
</li>
{/each}
</ul>
{#if areRequirementsMet(requirementChecks)}
<p>Browse for your ISO - Obtained by dumping your own legitimate copy</p>
<div>
<button class="btn" on:click={onClickBrowse}>Browse for ISO</button>
{#if isoPath}
{isoPath}
{/if}
<span id="filePathLabel" />
</div>
{#if !setupInProgress}
<div class="row">
<Link to="/jak1">
<button class="btn">Cancel</button>
</Link>
</div>
</details>
</div>
{:else}
<div>
<h2>Progress</h2>
<ul class="no-decoration">
{#each installSteps as step}
<li>
<span class="progress-row">
{@html statusIndicator(step.status)}
{step.text}
</span>
</li>
{/each}
</ul>
</div>
<div class="row">
<details>
<summary>Installation Logs</summary>
<div class="logContainer">
{#each installSteps as step}
{#if step.logs !== ""}
{step.logs}
{/if}
{#if step.errorLogs !== ""}
<div class="errorLogs">
{step.errorLogs}
</div>
{/if}
{/each}
</div>
</details>
</div>
{/if}
{/if}
</div>