mirror of
https://github.com/open-goal/opengoal-vscode.git
synced 2024-10-19 20:47:37 -04:00
decomp: pre-populated list of possible types (#181)
This commit is contained in:
parent
08851637e2
commit
040e941857
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ out/
|
|||
*.exe
|
||||
*.bin
|
||||
lsp-metadata.json
|
||||
*-types.json
|
||||
|
|
|
@ -222,6 +222,14 @@
|
|||
"default": null,
|
||||
"description": "File path to the decompiler executable"
|
||||
},
|
||||
"opengoal.typeSearcherPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"default": null,
|
||||
"description": "File path to the type searcher executable"
|
||||
},
|
||||
"opengoal.decompilerJak1Config": {
|
||||
"type": [
|
||||
"string",
|
||||
|
|
|
@ -14,6 +14,7 @@ export function getConfig() {
|
|||
eeManPagePath: configOptions.get<string>("eeManPagePath"),
|
||||
vuManPagePath: configOptions.get<string>("vuManPagePath"),
|
||||
decompilerPath: configOptions.get<string>("decompilerPath"),
|
||||
typeSearcherPath: configOptions.get<string>("typeSearcherPath"),
|
||||
jak1DecompConfig: configOptions.get<string>("decompilerJak1Config"),
|
||||
jak2DecompConfig: configOptions.get<string>("decompilerJak2Config"),
|
||||
decompilerJak1ConfigDirectory: configOptions.get<string>(
|
||||
|
@ -75,6 +76,15 @@ export async function updateDecompilerPath(path: string) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function updateTypeSearcherPath(path: string) {
|
||||
const userConfig = vscode.workspace.getConfiguration();
|
||||
await userConfig.update(
|
||||
"opengoal.typeSearcherPath",
|
||||
path,
|
||||
vscode.ConfigurationTarget.Global
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateJak1DecompConfig(config: string) {
|
||||
const userConfig = vscode.workspace.getConfiguration();
|
||||
await userConfig.update(
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
import * as vscode from "vscode";
|
||||
import { RecentFiles } from "./RecentFiles";
|
||||
import { getWorkspaceFolderByName } from "./utils/workspace";
|
||||
|
||||
const channel = vscode.window.createOutputChannel("OpenGOAL");
|
||||
let extensionContext: vscode.ExtensionContext;
|
||||
let recentFiles: RecentFiles;
|
||||
let projectRoot: vscode.Uri | undefined = undefined;
|
||||
|
||||
export function initContext(extContext: vscode.ExtensionContext) {
|
||||
extensionContext = extContext;
|
||||
|
@ -27,3 +29,17 @@ export function getExtensionContext() {
|
|||
export function getMainChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
export function getProjectRoot(): vscode.Uri {
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
// if it's still undefined, throw an error
|
||||
if (projectRoot === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"OpenGOAL - Unable to locate 'jak-project' workspace folder"
|
||||
);
|
||||
throw new Error("unable to locate 'jak-project' workspace folder");
|
||||
}
|
||||
}
|
||||
return projectRoot;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { exec, execFile } from "child_process";
|
||||
import { existsSync, promises as fs } from "fs";
|
||||
import * as vscode from "vscode";
|
||||
import { determineGameFromPath, GameName, openFile } from "../utils/file-utils";
|
||||
import { determineGameFromPath, GameName } from "../utils/file-utils";
|
||||
import { open_in_pdf } from "./man-page";
|
||||
import * as util from "util";
|
||||
import {
|
||||
|
@ -12,22 +12,19 @@ import {
|
|||
} from "../config/config";
|
||||
import * as path from "path";
|
||||
import * as glob from "glob";
|
||||
import { getExtensionContext, getRecentFiles } from "../context";
|
||||
import { getExtensionContext, getProjectRoot } from "../context";
|
||||
import {
|
||||
getFileNamesFromUris,
|
||||
getUrisFromTabs,
|
||||
getWorkspaceFolderByName,
|
||||
truncateFileNameEndings,
|
||||
} from "../utils/workspace";
|
||||
import { activateDecompTypeSearcher } from "./type-searcher/type-searcher";
|
||||
import { updateTypeCastSuggestions } from "./type-caster";
|
||||
|
||||
const globAsync = util.promisify(glob);
|
||||
const execFileAsync = util.promisify(execFile);
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
// Put some of this stuff into the context
|
||||
let projectRoot: vscode.Uri | undefined = undefined;
|
||||
|
||||
let channel: vscode.OutputChannel;
|
||||
let fsWatcher: vscode.FileSystemWatcher | undefined;
|
||||
|
||||
|
@ -102,27 +99,19 @@ async function promptUserToSelectConfig(
|
|||
async function getDecompilerConfig(
|
||||
gameName: GameName
|
||||
): Promise<string | undefined> {
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
if (projectRoot === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"OpenGOAL - Unable to locate 'jak-project' workspace folder"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
if (gameName == GameName.Jak1) {
|
||||
const decompConfig = config.jak1DecompConfig;
|
||||
if (
|
||||
decompConfig === undefined ||
|
||||
!existsSync(
|
||||
vscode.Uri.joinPath(projectRoot, `decompiler/config/${decompConfig}`)
|
||||
.fsPath
|
||||
vscode.Uri.joinPath(
|
||||
getProjectRoot(),
|
||||
`decompiler/config/${decompConfig}`
|
||||
).fsPath
|
||||
)
|
||||
) {
|
||||
const config = await promptUserToSelectConfig(projectRoot);
|
||||
const config = await promptUserToSelectConfig(getProjectRoot());
|
||||
if (config === undefined) {
|
||||
return;
|
||||
} else {
|
||||
|
@ -137,11 +126,13 @@ async function getDecompilerConfig(
|
|||
if (
|
||||
decompConfig === undefined ||
|
||||
!existsSync(
|
||||
vscode.Uri.joinPath(projectRoot, `decompiler/config/${decompConfig}`)
|
||||
.fsPath
|
||||
vscode.Uri.joinPath(
|
||||
getProjectRoot(),
|
||||
`decompiler/config/${decompConfig}`
|
||||
).fsPath
|
||||
)
|
||||
) {
|
||||
const config = await promptUserToSelectConfig(projectRoot);
|
||||
const config = await promptUserToSelectConfig(getProjectRoot());
|
||||
if (config === undefined) {
|
||||
return;
|
||||
} else {
|
||||
|
@ -156,16 +147,6 @@ async function getDecompilerConfig(
|
|||
}
|
||||
|
||||
async function checkDecompilerPath(): Promise<string | undefined> {
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
if (projectRoot === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"OpenGOAL - Unable to locate 'jak-project' workspace folder"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let decompilerPath = getConfig().decompilerPath;
|
||||
|
||||
// Look for the decompiler if the path isn't set or the file is now missing
|
||||
|
@ -173,7 +154,10 @@ async function checkDecompilerPath(): Promise<string | undefined> {
|
|||
return decompilerPath;
|
||||
}
|
||||
|
||||
const potentialPath = vscode.Uri.joinPath(projectRoot, defaultDecompPath());
|
||||
const potentialPath = vscode.Uri.joinPath(
|
||||
getProjectRoot(),
|
||||
defaultDecompPath()
|
||||
);
|
||||
if (existsSync(potentialPath.fsPath)) {
|
||||
decompilerPath = potentialPath.fsPath;
|
||||
} else {
|
||||
|
@ -221,7 +205,7 @@ async function decompFiles(decompConfig: string, fileNames: string[]) {
|
|||
],
|
||||
{
|
||||
encoding: "utf8",
|
||||
cwd: projectRoot?.fsPath,
|
||||
cwd: getProjectRoot()?.fsPath,
|
||||
timeout: 20000,
|
||||
}
|
||||
);
|
||||
|
@ -239,16 +223,9 @@ async function decompFiles(decompConfig: string, fileNames: string[]) {
|
|||
}
|
||||
|
||||
async function getValidObjectNames(gameName: string) {
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
if (projectRoot === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the `all_objs.json` file
|
||||
const objsPath = path.join(
|
||||
projectRoot.fsPath,
|
||||
getProjectRoot().fsPath,
|
||||
"goal_src",
|
||||
gameName,
|
||||
"build",
|
||||
|
@ -415,7 +392,14 @@ function toggleAutoDecompilation() {
|
|||
fsWatcher = vscode.workspace.createFileSystemWatcher(
|
||||
"**/decompiler/config/**/*.{jsonc,json,gc}"
|
||||
);
|
||||
fsWatcher.onDidChange(() => decompAllActiveFiles());
|
||||
fsWatcher.onDidChange((uri: vscode.Uri) => {
|
||||
decompAllActiveFiles();
|
||||
// Also update list of types for that game
|
||||
const gameName = determineGameFromPath(uri);
|
||||
if (gameName !== undefined) {
|
||||
updateTypeCastSuggestions(gameName);
|
||||
}
|
||||
});
|
||||
fsWatcher.onDidCreate(() => decompAllActiveFiles());
|
||||
fsWatcher.onDidDelete(() => decompAllActiveFiles());
|
||||
} else {
|
||||
|
@ -434,16 +418,6 @@ async function updateSourceFile() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
if (projectRoot === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"OpenGOAL - Unable to locate 'jak-project' workspace folder"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = path.basename(editor.document.fileName);
|
||||
let disasmFilePath = "";
|
||||
if (fileName.match(/.*_ir2\.asm/)) {
|
||||
|
@ -468,7 +442,7 @@ async function updateSourceFile() {
|
|||
`python ./scripts/gsrc/update-from-decomp.py --game ${gameName} --file ${fileName}`,
|
||||
{
|
||||
encoding: "utf8",
|
||||
cwd: projectRoot?.fsPath,
|
||||
cwd: getProjectRoot()?.fsPath,
|
||||
timeout: 20000,
|
||||
}
|
||||
);
|
||||
|
@ -486,16 +460,6 @@ async function updateReferenceTest() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (projectRoot === undefined) {
|
||||
projectRoot = getWorkspaceFolderByName("jak-project");
|
||||
if (projectRoot === undefined) {
|
||||
vscode.window.showErrorMessage(
|
||||
"OpenGOAL - Unable to locate 'jak-project' workspace folder"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - duplication with above
|
||||
|
||||
let fileName = path.basename(editor.document.fileName);
|
||||
|
@ -518,7 +482,7 @@ async function updateReferenceTest() {
|
|||
gameName = "jak2";
|
||||
}
|
||||
const folderToSearch = vscode.Uri.joinPath(
|
||||
projectRoot,
|
||||
getProjectRoot(),
|
||||
`goal_src/${gameName}`
|
||||
);
|
||||
const files = await globAsync(`**/${fileName}.gc`, {
|
||||
|
@ -530,7 +494,7 @@ async function updateReferenceTest() {
|
|||
}
|
||||
|
||||
const refTestPath = vscode.Uri.joinPath(
|
||||
projectRoot,
|
||||
getProjectRoot(),
|
||||
`test/decompiler/reference/${gameName}/${files[0].replace(
|
||||
".gc",
|
||||
"_REF.gc"
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { getExtensionContext } from "../context";
|
||||
import { getExtensionContext, getProjectRoot } from "../context";
|
||||
import * as vscode from "vscode";
|
||||
import { basename, join } from "path";
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { fstat, readFileSync, writeFileSync } from "fs";
|
||||
import { parse, stringify } from "comment-json";
|
||||
import { getFuncNameFromSelection } from "../languages/ir2/ir2-utils";
|
||||
import { getDecompilerConfigDirectory } from "./utils";
|
||||
import { determineGameFromPath, GameName } from "../utils/file-utils";
|
||||
import { getConfig, updateTypeSearcherPath } from "../config/config";
|
||||
import { existsSync } from "fs";
|
||||
import * as util from "util";
|
||||
import { execFile } from "child_process";
|
||||
const execFileAsync = util.promisify(execFile);
|
||||
|
||||
enum CastKind {
|
||||
Label,
|
||||
|
@ -33,6 +39,81 @@ class CastContext {
|
|||
}
|
||||
}
|
||||
|
||||
const typeCastSuggestions = new Map<GameName, string[]>();
|
||||
const recentLabelCasts = new Map<GameName, string[]>();
|
||||
const recentTypeCasts = new Map<GameName, string[]>();
|
||||
const recentStackCasts = new Map<GameName, string[]>();
|
||||
|
||||
function defaultTypeSearcherPath() {
|
||||
const platform = process.platform;
|
||||
if (platform == "win32") {
|
||||
return "out/build/Release/bin/type_searcher.exe";
|
||||
} else {
|
||||
return "build/tools/type_searcher";
|
||||
}
|
||||
}
|
||||
|
||||
async function checkTypeSearcherPath(): Promise<string | undefined> {
|
||||
let typeSearcherPath = getConfig().typeSearcherPath;
|
||||
|
||||
// Look for the decompiler if the path isn't set or the file is now missing
|
||||
if (typeSearcherPath !== undefined && existsSync(typeSearcherPath)) {
|
||||
return typeSearcherPath;
|
||||
}
|
||||
|
||||
const potentialPath = vscode.Uri.joinPath(
|
||||
getProjectRoot(),
|
||||
defaultTypeSearcherPath()
|
||||
);
|
||||
if (existsSync(potentialPath.fsPath)) {
|
||||
typeSearcherPath = potentialPath.fsPath;
|
||||
} else {
|
||||
// Ask the user to find it cause we have no idea
|
||||
const path = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: "Select Type Searcher",
|
||||
title: "Provide the type searcher executable's path",
|
||||
});
|
||||
if (path === undefined || path.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
typeSearcherPath = path[0].fsPath;
|
||||
}
|
||||
updateTypeSearcherPath(typeSearcherPath);
|
||||
return typeSearcherPath;
|
||||
}
|
||||
|
||||
export async function updateTypeCastSuggestions(gameName: GameName) {
|
||||
const typeSearcherPath = await checkTypeSearcherPath();
|
||||
if (!typeSearcherPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonPath = vscode.Uri.joinPath(
|
||||
getExtensionContext().extensionUri,
|
||||
`${gameName.toString()}-types.json`
|
||||
).fsPath;
|
||||
await execFileAsync(
|
||||
typeSearcherPath,
|
||||
[`--game`, gameName.toString(), `--output-path`, jsonPath, `--all`],
|
||||
{
|
||||
encoding: "utf8",
|
||||
cwd: getProjectRoot().fsPath,
|
||||
timeout: 20000,
|
||||
}
|
||||
);
|
||||
if (existsSync(jsonPath)) {
|
||||
const result = readFileSync(jsonPath, { encoding: "utf-8" });
|
||||
typeCastSuggestions.set(gameName, JSON.parse(result));
|
||||
}
|
||||
} catch (error: any) {
|
||||
vscode.window.showErrorMessage(
|
||||
"Couldn't get a list of all types to use for casting suggestions"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getOpNumber(line: string): Promise<number | undefined> {
|
||||
const matches = [...line.matchAll(opNumRegex)];
|
||||
if (matches.length == 1) {
|
||||
|
@ -109,6 +190,51 @@ async function validActiveFile(editor: vscode.TextEditor): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
function generateCastSelectionItems(
|
||||
fullList: string[] | undefined,
|
||||
recentList: string[] | undefined
|
||||
): vscode.QuickPickItem[] {
|
||||
const items: vscode.QuickPickItem[] = [];
|
||||
if (recentList !== undefined && recentList.length > 0) {
|
||||
items.push({
|
||||
label: "Recent Casts",
|
||||
kind: vscode.QuickPickItemKind.Separator,
|
||||
});
|
||||
for (const name of recentList) {
|
||||
items.push({
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (fullList !== undefined && fullList.length > 0) {
|
||||
items.push({
|
||||
label: "All Types",
|
||||
kind: vscode.QuickPickItemKind.Separator,
|
||||
});
|
||||
for (const name of fullList) {
|
||||
items.push({
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function initTypeCastSuggestions(gameName: GameName | undefined) {
|
||||
if (gameName !== undefined && typeCastSuggestions.size === 0) {
|
||||
await updateTypeCastSuggestions(gameName);
|
||||
if (recentLabelCasts.get(gameName) === undefined) {
|
||||
recentLabelCasts.set(gameName, []);
|
||||
}
|
||||
if (recentTypeCasts.get(gameName) === undefined) {
|
||||
recentTypeCasts.set(gameName, []);
|
||||
}
|
||||
if (recentStackCasts.get(gameName) === undefined) {
|
||||
recentStackCasts.set(gameName, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function labelCastSelection() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor === undefined || !validActiveFile(editor)) {
|
||||
|
@ -126,14 +252,34 @@ async function labelCastSelection() {
|
|||
}
|
||||
|
||||
// Get what we should cast to
|
||||
const castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
const gameName = determineGameFromPath(editor.document.uri);
|
||||
await initTypeCastSuggestions(gameName);
|
||||
if (gameName === undefined) {
|
||||
await vscode.window.showErrorMessage("Couldn't determine game version");
|
||||
return;
|
||||
}
|
||||
|
||||
const items = generateCastSelectionItems(
|
||||
typeCastSuggestions.get(gameName),
|
||||
recentLabelCasts.get(gameName)
|
||||
);
|
||||
let castToType;
|
||||
if (items.length > 0) {
|
||||
castToType = (
|
||||
await vscode.window.showQuickPick(items, {
|
||||
title: "Cast to Type?",
|
||||
})
|
||||
)?.label;
|
||||
} else {
|
||||
castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
}
|
||||
|
||||
if (castToType === undefined || castToType.trim() === "") {
|
||||
await vscode.window.showErrorMessage("Can't cast if no type is provided");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the label is a pointer, ask for a size
|
||||
let pointerSize = undefined;
|
||||
if (castToType.includes("pointer")) {
|
||||
|
@ -159,6 +305,7 @@ async function labelCastSelection() {
|
|||
lastCastKind = CastKind.Label;
|
||||
lastLabelCastType = castToType;
|
||||
lastLabelCastSize = pointerSize;
|
||||
recentLabelCasts.get(gameName)?.unshift(castToType);
|
||||
}
|
||||
|
||||
async function getStackOffset(line: string): Promise<number | undefined> {
|
||||
|
@ -217,9 +364,30 @@ async function stackCastSelection() {
|
|||
}
|
||||
|
||||
// Get what we should cast to
|
||||
const castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
const gameName = determineGameFromPath(editor.document.uri);
|
||||
await initTypeCastSuggestions(gameName);
|
||||
if (gameName === undefined) {
|
||||
await vscode.window.showErrorMessage("Couldn't determine game version");
|
||||
return;
|
||||
}
|
||||
|
||||
const items = generateCastSelectionItems(
|
||||
typeCastSuggestions.get(gameName),
|
||||
recentStackCasts.get(gameName)
|
||||
);
|
||||
let castToType;
|
||||
if (items.length > 0) {
|
||||
castToType = (
|
||||
await vscode.window.showQuickPick(items, {
|
||||
title: "Cast to Type?",
|
||||
})
|
||||
)?.label;
|
||||
} else {
|
||||
castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
}
|
||||
|
||||
if (castToType === undefined || castToType.trim() === "") {
|
||||
await vscode.window.showErrorMessage("Can't cast if no type is provided");
|
||||
return;
|
||||
|
@ -230,6 +398,7 @@ async function stackCastSelection() {
|
|||
|
||||
lastCastKind = CastKind.Stack;
|
||||
lastStackCastType = castToType;
|
||||
recentStackCasts.get(gameName)?.unshift(castToType);
|
||||
}
|
||||
|
||||
function getRegisters(
|
||||
|
@ -341,9 +510,29 @@ async function typeCastSelection() {
|
|||
}
|
||||
|
||||
// Get what we should cast to
|
||||
const castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
const gameName = determineGameFromPath(editor.document.uri);
|
||||
await initTypeCastSuggestions(gameName);
|
||||
if (gameName === undefined) {
|
||||
await vscode.window.showErrorMessage("Couldn't determine game version");
|
||||
return;
|
||||
}
|
||||
|
||||
const items = generateCastSelectionItems(
|
||||
typeCastSuggestions.get(gameName),
|
||||
recentTypeCasts.get(gameName)
|
||||
);
|
||||
let castToType;
|
||||
if (items.length > 0) {
|
||||
castToType = (
|
||||
await vscode.window.showQuickPick(items, {
|
||||
title: "Cast to Type?",
|
||||
})
|
||||
)?.label;
|
||||
} else {
|
||||
castToType = await vscode.window.showInputBox({
|
||||
title: "Cast to Type?",
|
||||
});
|
||||
}
|
||||
if (castToType === undefined || castToType.trim() === "") {
|
||||
await vscode.window.showErrorMessage("Can't cast if no type is provided");
|
||||
return;
|
||||
|
@ -361,6 +550,7 @@ async function typeCastSelection() {
|
|||
lastCastKind = CastKind.TypeCast;
|
||||
lastTypeCastRegister = registerSelection;
|
||||
lastTypeCastType = castToType;
|
||||
recentTypeCasts.get(gameName)?.unshift(castToType);
|
||||
}
|
||||
|
||||
// Execute the same cast as last time (same type, same register) just on a different selection
|
||||
|
|
|
@ -4,8 +4,8 @@ import { promises as fs } from "fs";
|
|||
import { getRecentFiles } from "../context";
|
||||
|
||||
export enum GameName {
|
||||
Jak1,
|
||||
Jak2,
|
||||
Jak1 = "jak1",
|
||||
Jak2 = "jak2",
|
||||
}
|
||||
|
||||
const fileSwitchingAssoc = {
|
||||
|
|
Loading…
Reference in a new issue