feat: Added more ways to detect duplicates
feat: Added resolution and widescreen settings feat: Added Xenia and Xemu integration
This commit is contained in:
parent
764691fc86
commit
05fafced07
25 changed files with 407 additions and 49 deletions
|
|
@ -201,31 +201,68 @@ export default new Elysia()
|
|||
.groupBy(schema.games.id)
|
||||
.where(and(...where));
|
||||
|
||||
localGamesSet = new Set(localGames.filter(g => !!g.source_id && !!g.source).map(g => `${g.source}@${g.source_id}`));
|
||||
localGamesSet = new Set(
|
||||
localGames.filter(g => !!g.source_id && !!g.source).map(g => `${g.source}@${g.source_id}`)
|
||||
.concat(localGames.filter(g => !!g.igdb_id).map(g => `igdb@${g.igdb_id}`))
|
||||
);
|
||||
|
||||
if (!query.collection_id)
|
||||
function localGameExistsPredicate (game: { id: FrontEndId, igdb_id?: number | null, ra_id?: number | null; })
|
||||
{
|
||||
if (localGamesSet?.has(`${game.id.source}@${game.id.id}`)) return true;
|
||||
if (game.igdb_id && localGamesSet?.has(`igdb@${game.igdb_id}`)) return true;
|
||||
if (game.ra_id && localGamesSet?.has(`ra@${game.ra_id}`)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (query.collection_id)
|
||||
{
|
||||
// Collections are just a remote thing for now.
|
||||
const remoteGames: FrontEndGameTypeWithIds[] = [];
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.map(g =>
|
||||
{
|
||||
if (localGameExistsPredicate(g))
|
||||
{
|
||||
return convertLocalToFrontend(localGames.find(g => localGameExistsPredicate({ id: { id: g.source_id ?? '', source: g.source ?? '' }, igdb_id: g.igdb_id, ra_id: g.ra_id }))!);
|
||||
}
|
||||
else
|
||||
{
|
||||
return g;
|
||||
}
|
||||
}));
|
||||
|
||||
} else
|
||||
{
|
||||
games.push(...localGames.slice(query.offset, query.limit ? query.offset ?? 0 + query.limit : undefined).map(g =>
|
||||
{
|
||||
return convertLocalToFrontend(g);
|
||||
}));
|
||||
|
||||
const remoteGames: FrontEndGameType[] = [];
|
||||
const remoteGames: FrontEndGameTypeWithIds[] = [];
|
||||
const remoteGameSet = new Set<string>();
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.filter(g => !localGamesSet?.has(`${g.id.source}@${g.id.id}`)));
|
||||
} else
|
||||
{
|
||||
const remoteGames: FrontEndGameType[] = [];
|
||||
await plugins.hooks.games.fetchGames.promise({ query, games: remoteGames }).catch(e => console.error(e));
|
||||
games.push(...remoteGames.map(g =>
|
||||
games.push(...remoteGames.filter(g =>
|
||||
{
|
||||
if (localGamesSet?.has(`${g.id.source}@${g.id.id}`))
|
||||
if (localGameExistsPredicate(g))
|
||||
{
|
||||
return convertLocalToFrontend(localGames.find(l => l.source === g.id.source && l.source_id === g.id.id)!);
|
||||
} else
|
||||
{
|
||||
return g;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g.igdb_id)
|
||||
{
|
||||
const igdbId = `igdb@${g.igdb_id}`;
|
||||
if (remoteGameSet.has(igdbId)) return false;
|
||||
remoteGameSet.add(igdbId);
|
||||
}
|
||||
|
||||
if (g.ra_id)
|
||||
{
|
||||
const raId = `ra@${g.ra_id}`;
|
||||
if (remoteGameSet.has(raId)) return false;
|
||||
remoteGameSet.add(raId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export class GameHooks
|
|||
*/
|
||||
fetchGames = new AsyncSeriesHook<[ctx: {
|
||||
query: GameListFilterType;
|
||||
games: FrontEndGameType[];
|
||||
games: FrontEndGameTypeWithIds[];
|
||||
}]>(['ctx']);
|
||||
fetchGame = new AsyncSeriesBailHook<[ctx: {
|
||||
source: string;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import z from "zod";
|
|||
import { checkFiles } from "../games/services/utils";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import { path7za } from "7zip-bin";
|
||||
import slugify from 'slugify';
|
||||
|
||||
interface JobConfig
|
||||
{
|
||||
|
|
@ -70,8 +71,8 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
name: game.title,
|
||||
summary: game.description,
|
||||
system_slug: gameId.system,
|
||||
path_fs: path.join('roms', gameId.system, game.title),
|
||||
extract_path: path.join('roms', gameId.system, game.title),
|
||||
path_fs: path.join('roms', gameId.system, slugify(game.title)),
|
||||
extract_path: '.',
|
||||
};
|
||||
|
||||
break;
|
||||
|
|
@ -104,13 +105,17 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
});
|
||||
|
||||
const downloadedFiles = await downloader.start();
|
||||
if (!downloadedFiles)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (info.extract_path && downloadedFiles)
|
||||
{
|
||||
let progress = 0;
|
||||
const progressDelta = 1 / downloadedFiles.length;
|
||||
for (const filePath of downloadedFiles)
|
||||
{
|
||||
const extractPath = path.join(config.get('downloadPath'), info.extract_path);
|
||||
const extractPath = path.join(config.get('downloadPath'), info.path_fs ?? '', info.extract_path);
|
||||
await new Promise((resolve, reject) =>
|
||||
{
|
||||
const seven = Seven.extractFull(filePath, extractPath, { $bin: process.env.ZIP7_PATH ?? path7za, $progress: true });
|
||||
|
|
@ -119,7 +124,10 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
cx.setProgress(progress + p.percent * progressDelta, "extract");
|
||||
});
|
||||
|
||||
seven.on('error', e => reject(e));
|
||||
seven.on('error', e =>
|
||||
{
|
||||
reject(e);
|
||||
});
|
||||
seven.on('end', async () =>
|
||||
{
|
||||
await fs.rm(filePath);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import desc from './package.json';
|
|||
import path from 'node:path';
|
||||
import { config } from "@/bun/api/app";
|
||||
|
||||
export default class DOLPHINIntegration implements PluginType
|
||||
export default class CEMUIntegration implements PluginType
|
||||
{
|
||||
emulator = 'CEMU';
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ export default class DOLPHINIntegration implements PluginType
|
|||
{
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulator }, (ctx) =>
|
||||
{
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "config", "fullscreen", "resolution", "saves", "states"] };
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "fullscreen", "saves", "states"] };
|
||||
});
|
||||
|
||||
ctx.hooks.games.emulatorLaunch.tapPromise({ name: desc.name, emulator: this.emulator }, async (ctx) =>
|
||||
|
|
@ -20,7 +20,7 @@ export default class DOLPHINIntegration implements PluginType
|
|||
|
||||
args.push(`--fullscreen=${config.get('launchInFullscreen') ? "True" : "False"}`);
|
||||
|
||||
const savesPath = path.join(config.get('downloadPath'), "saves", 'DOLPHIN');
|
||||
const savesPath = path.join(config.get('downloadPath'), "saves", this.emulator);
|
||||
|
||||
args.push(`--mlc=${savesPath}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default class DOLPHINIntegration implements PluginType
|
|||
{
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulator }, (ctx) =>
|
||||
{
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "config", "fullscreen", "resolution", "saves", "states"] };
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "config", "resolution", "fullscreen", "states"] };
|
||||
});
|
||||
|
||||
ctx.hooks.emulators.emulatorPostInstall.tapPromise({ name: desc.name, emulator: this.emulator }, async (ctx) =>
|
||||
|
|
@ -35,6 +35,17 @@ export default class DOLPHINIntegration implements PluginType
|
|||
args.push(`--config=Dolphin.Interface.SkipNKitWarning=True`);
|
||||
args.push(`--config=Dolphin.Analytics.PermissionAsked=True`);
|
||||
|
||||
const resolution = config.get('emulatorResolution');
|
||||
const resolutionMapping = {
|
||||
"720p": 2,
|
||||
"1080p": 3,
|
||||
"1440p": 4,
|
||||
"4k": 6
|
||||
};
|
||||
args.push(`--config=GFX.Settings.InternalResolution=${resolutionMapping[resolution] ?? 1}`);
|
||||
args.push(`--config=GFX.Settings.wideScreenHack=${config.get('emulatorWidescreen') ? "True" : "False"}`);
|
||||
args.push(`--config=GFX.Settings.AspectRatio=${config.get('emulatorWidescreen') ? "1" : "0"}`);
|
||||
|
||||
const savesPath = path.join(config.get('downloadPath'), "saves", this.emulator);
|
||||
|
||||
args.push(`--config=Dolphin.General.WiiSDCardPath=${path.join(savesPath, 'WiiSD.raw')}`);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ CdvdShareWrite = false
|
|||
EnablePatches = true
|
||||
EnableCheats = false
|
||||
EnablePINE = false
|
||||
EnableWideScreenPatches = false
|
||||
EnableWideScreenPatches = {{ENABLE_WIDESCREEN}}
|
||||
EnableNoInterlacingPatches = false
|
||||
EnableRecordingTools = true
|
||||
EnableGameFixes = true
|
||||
|
|
@ -92,7 +92,7 @@ VsyncEnable = 0
|
|||
FramerateNTSC = 59.94
|
||||
FrameratePAL = 50
|
||||
SyncToHostRefreshRate = false
|
||||
AspectRatio = Auto 4:3/3:2
|
||||
AspectRatio = {{ASPECT_RATIO}}
|
||||
FMVAspectRatioSwitch = Off
|
||||
ScreenshotSize = 0
|
||||
ScreenshotFormat = 0
|
||||
|
|
@ -168,7 +168,7 @@ linear_present_mode = 1
|
|||
deinterlace_mode = 0
|
||||
OsdScale = 100
|
||||
Renderer = 14
|
||||
upscale_multiplier = 1
|
||||
upscale_multiplier = {{UPSCALE_MULTIPLIER}}
|
||||
mipmap_hw = -1
|
||||
accurate_blending_unit = 1
|
||||
crc_hack_level = -1
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default class PCSX2Integration implements PluginType
|
|||
return {
|
||||
id: desc.name,
|
||||
supportLevel: "full",
|
||||
capabilities: [...baseCapabilities, "resolution", "config"]
|
||||
capabilities: [...baseCapabilities, "config", "resolution"]
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -52,6 +52,12 @@ export default class PCSX2Integration implements PluginType
|
|||
const biosFolder = path.join(config.get('downloadPath'), "bios", this.emulator);
|
||||
const storageFolder = path.join(config.get('downloadPath'), "storage", this.emulator);
|
||||
const savesFolder = path.join(config.get('downloadPath'), "saves", this.emulator);
|
||||
const resolutionMapping = {
|
||||
"720p": 2,
|
||||
"1080p": 3,
|
||||
"1440p": 4,
|
||||
"4k": 6,
|
||||
};
|
||||
|
||||
const view = {
|
||||
BIOS_PATH: biosFolder,
|
||||
|
|
@ -62,6 +68,9 @@ export default class PCSX2Integration implements PluginType
|
|||
COVERS_PATH: path.join(storageFolder, 'covers'),
|
||||
TEXTURES_PATH: path.join(storageFolder, 'textures'),
|
||||
RECURSIVE_PATHS: path.join(config.get('downloadPath'), 'roms', 'PS2'),
|
||||
ENABLE_WIDESCREEN: config.get('emulatorWidescreen'),
|
||||
ASPECT_RATIO: config.get('emulatorWidescreen') ? "16:9" : "Auto 4:3/3:2",
|
||||
UPSCALE_MULTIPLIER: resolutionMapping[config.get('emulatorResolution')] ?? 1
|
||||
};
|
||||
|
||||
await Promise.all(Object.values(view).map(p => ensureDir(p)));
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ HardwareTransform = True
|
|||
SoftwareSkinning = True
|
||||
TextureFiltering = 1
|
||||
BufferFiltering = 1
|
||||
InternalResolution = 3
|
||||
InternalResolution = {{RESOLUTION}}
|
||||
AndroidHwScale = 1
|
||||
HighQualityDepth = 1
|
||||
FrameSkip = 0
|
||||
|
|
@ -109,7 +109,7 @@ AnisotropyLevel = 4
|
|||
VertexDecCache = False
|
||||
TextureBackoffCache = False
|
||||
TextureSecondaryCache = False
|
||||
FullScreen = True
|
||||
FullScreen = {{FULLSCREEN}}
|
||||
FullScreenMulti = False
|
||||
SmallDisplayZoomType = 2
|
||||
SmallDisplayOffsetX = 0.500000
|
||||
|
|
|
|||
|
|
@ -10,12 +10,21 @@ import Mustache from "mustache";
|
|||
import { ensureDir } from "fs-extra";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
export default class PCSX2Integration implements PluginType
|
||||
export default class PPSSPPIntegration implements PluginType
|
||||
{
|
||||
emulator = "PPSSPP";
|
||||
|
||||
load (ctx: PluginContextType)
|
||||
{
|
||||
ctx.hooks.emulators.emulatorPostInstall.tapPromise({ name: desc.name, emulator: this.emulator }, async (ctx) =>
|
||||
{
|
||||
await Bun.write(path.join(ctx.path, "portable.txt"), "");
|
||||
if (process.platform === 'win32')
|
||||
{
|
||||
await Bun.write(path.join(ctx.path, "installed.txt"), path.join(config.get('downloadPath'), 'saves', this.emulator));
|
||||
}
|
||||
});
|
||||
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulator }, (ctx) =>
|
||||
{
|
||||
const baseCapabilities: EmulatorCapabilities[] = ["batch", "fullscreen", "saves", "states"];
|
||||
|
|
@ -25,7 +34,7 @@ export default class PCSX2Integration implements PluginType
|
|||
return {
|
||||
id: desc.name,
|
||||
supportLevel: "full",
|
||||
capabilities: [...baseCapabilities, "resolution", "config"]
|
||||
capabilities: [...baseCapabilities, "config", "resolution"]
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -68,7 +77,7 @@ export default class PCSX2Integration implements PluginType
|
|||
let ppssppPath = '';
|
||||
if (process.platform === 'win32')
|
||||
{
|
||||
ppssppPath = path.join(ctx.autoValidCommand.metadata.emulatorDir, 'memstick', 'PSP', 'SYSTEM');
|
||||
ppssppPath = path.join(config.get('downloadPath'), 'saves', this.emulator, 'PSP', 'SYSTEM');
|
||||
} else
|
||||
{
|
||||
//TODO: Use way to set custom memstick path when they support it
|
||||
|
|
@ -80,8 +89,17 @@ export default class PCSX2Integration implements PluginType
|
|||
|
||||
if (confPath)
|
||||
{
|
||||
const resolutionMapping = {
|
||||
"720p": "2",
|
||||
"1080p": "4",
|
||||
"1440p": "6",
|
||||
"4k": "8"
|
||||
};
|
||||
const configFileContents = await Bun.file(confPath).text();
|
||||
await Bun.write(path.join(ppssppPath, 'ppsspp.ini'), Mustache.render(configFileContents, {}));
|
||||
await Bun.write(path.join(ppssppPath, 'ppsspp.ini'), Mustache.render(configFileContents, {
|
||||
RESOLUTION: resolutionMapping[config.get('emulatorResolution')] ?? 0,
|
||||
FULLSCREEN: config.get('launchInFullscreen') ? "True" : "False"
|
||||
}));
|
||||
}
|
||||
|
||||
if (controlsPath)
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ HardwareTransform = True
|
|||
SoftwareSkinning = True
|
||||
TextureFiltering = 1
|
||||
BufferFiltering = 1
|
||||
InternalResolution = 3
|
||||
InternalResolution = {{RESOLUTION}}
|
||||
AndroidHwScale = 1
|
||||
HighQualityDepth = 1
|
||||
FrameSkip = 0
|
||||
|
|
@ -109,7 +109,7 @@ AnisotropyLevel = 4
|
|||
VertexDecCache = False
|
||||
TextureBackoffCache = False
|
||||
TextureSecondaryCache = False
|
||||
FullScreen = True
|
||||
FullScreen = {{FULLSCREEN}}
|
||||
FullScreenMulti = False
|
||||
SmallDisplayZoomType = 2
|
||||
SmallDisplayOffsetX = 0.500000
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "com.simeonradivoev.gameflow.xemu",
|
||||
"displayName": "XEMU Integration",
|
||||
"version": "0.0.1",
|
||||
"description": "XEMU Emulator Integration",
|
||||
"main": "./xemu.ts",
|
||||
"icon": "https://upload.wikimedia.org/wikipedia/commons/8/8e/Xemu_logo_green.svg",
|
||||
"keywords": [
|
||||
"integration",
|
||||
"emulator",
|
||||
"xbox",
|
||||
"xemu"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
||||
import desc from './package.json';
|
||||
import { GameflowHooks } from "@/bun/api/hooks/app";
|
||||
import { config } from "@/bun/api/app";
|
||||
import path from "node:path";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import toml, { TomlTable } from 'smol-toml';
|
||||
import fs from 'node:fs/promises';
|
||||
import bin from './eeprom.bin' with { type: 'file' };
|
||||
|
||||
export default class XEMUIntegration implements PluginType
|
||||
{
|
||||
emulator = 'XEMU';
|
||||
|
||||
load (ctx: PluginContextType)
|
||||
{
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulator }, (ctx) =>
|
||||
{
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "fullscreen", "saves", "states"] };
|
||||
});
|
||||
|
||||
ctx.hooks.games.emulatorLaunch.tapPromise({ name: desc.name, emulator: this.emulator }, async (ctx) =>
|
||||
{
|
||||
const args: string[] = [];
|
||||
|
||||
if (config.get('launchInFullscreen'))
|
||||
{
|
||||
args.push("-full-screen");
|
||||
}
|
||||
|
||||
if (ctx.autoValidCommand.metadata.romPath)
|
||||
{
|
||||
args.push("-dvd_path");
|
||||
args.push(ctx.autoValidCommand.metadata.romPath);
|
||||
}
|
||||
|
||||
const configPath = path.join(config.get('downloadPath'), 'storage', this.emulator, 'xemu.toml');
|
||||
let configFile: { general: TomlTable & { misc: TomlTable; }, sys: TomlTable & { files: TomlTable; }; } = { general: { misc: {} }, sys: { files: {} } };
|
||||
if (await Bun.file(configPath).exists())
|
||||
{
|
||||
configFile = toml.parse(await Bun.file(configPath).text()) as any;
|
||||
}
|
||||
|
||||
configFile.general.misc ??= {};
|
||||
configFile.general.misc.skip_boot_anim = true;
|
||||
configFile.general.show_welcome = false;
|
||||
configFile.general.games_dir = path.join(config.get('downloadPath'), 'roms', 'xbox');
|
||||
configFile.sys.mem_limit = '128';
|
||||
const biosFolder = path.join(config.get('downloadPath'), "bios", this.emulator);
|
||||
if (await fs.exists(biosFolder))
|
||||
{
|
||||
const biosPaths = (await fs.readdir(biosFolder));
|
||||
const flash = biosPaths.find(f => f.endsWith('.bin') && !f.includes('mcpx'));
|
||||
const bootrom = biosPaths.find(f => f.endsWith('.bin') && f.includes('mcpx'));
|
||||
const hardDrive = biosPaths.find(f => f.endsWith('qcow2'));
|
||||
if (flash) configFile.sys.files.flashrom_path = path.join(biosFolder, flash);
|
||||
if (bootrom) configFile.sys.files.bootrom_path = path.join(biosFolder, bootrom);
|
||||
if (hardDrive) configFile.sys.files.hdd_path = path.join(biosFolder, hardDrive);
|
||||
}
|
||||
|
||||
if (!ctx.dryRun)
|
||||
{
|
||||
const eepromPath = path.join(config.get('downloadPath'), "storage", this.emulator, 'eeprom.bin');
|
||||
await Bun.write(eepromPath, await Bun.file(bin).arrayBuffer());
|
||||
configFile.sys.files.eeprom_path = eepromPath;
|
||||
|
||||
await Bun.write(configPath, toml.stringify(configFile));
|
||||
args.push("-config_path");
|
||||
args.push(configPath);
|
||||
}
|
||||
|
||||
|
||||
return args;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "com.simeonradivoev.gameflow.xenia",
|
||||
"displayName": "XENIA Integration",
|
||||
"version": "0.0.1",
|
||||
"description": "XENIA Emulator Integration",
|
||||
"main": "./xenia.ts",
|
||||
"icon": "https://xenia.jp/images/logo-256x256.png",
|
||||
"keywords": [
|
||||
"integration",
|
||||
"emulator",
|
||||
"xbox360",
|
||||
"xenia",
|
||||
"xenia-edge"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
||||
import desc from './package.json';
|
||||
import { GameflowHooks } from "@/bun/api/hooks/app";
|
||||
import { config } from "@/bun/api/app";
|
||||
import path from "node:path";
|
||||
import { ensureDir } from "fs-extra";
|
||||
import toml, { TomlTable } from 'smol-toml';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
export default class XENIAIntegration implements PluginType
|
||||
{
|
||||
emulator = 'XENIA';
|
||||
emulatorEdge = 'XENIA-EDGE';
|
||||
|
||||
async handlePostInstall (ctx: Parameters<typeof GameflowHooks.prototype.emulators.emulatorPostInstall.callAsync>['0'])
|
||||
{
|
||||
await Bun.write(path.join(ctx.path, "portable.txt"), "");
|
||||
}
|
||||
|
||||
async handleLaunch (ctx: Parameters<typeof GameflowHooks.prototype.games.emulatorLaunch.callAsync>['0'])
|
||||
{
|
||||
const args: string[] = [];
|
||||
|
||||
if (ctx.autoValidCommand.metadata.romPath)
|
||||
{
|
||||
args.push(ctx.autoValidCommand.metadata.romPath);
|
||||
}
|
||||
|
||||
const configPath = path.join(config.get('downloadPath'), 'storage', ctx.autoValidCommand.emulator!, `${ctx.autoValidCommand.emulator}.toml`);
|
||||
|
||||
if (!ctx.dryRun)
|
||||
{
|
||||
await ensureDir(path.join(config.get('downloadPath'), 'storage', ctx.autoValidCommand.emulator!));
|
||||
let configFile: TomlTable & { Storage: TomlTable, GPU: TomlTable, Display: TomlTable; } = { Storage: {}, GPU: {}, Display: {} };
|
||||
if (await fs.exists(configPath))
|
||||
{
|
||||
configFile = toml.parse(await Bun.file(configPath).text()) as any;
|
||||
}
|
||||
|
||||
const resolutionMapping = {
|
||||
"720p": 1,
|
||||
"1080p": 2,
|
||||
"1440p": 3,
|
||||
"4k": 3
|
||||
};
|
||||
|
||||
configFile.Display.fullscreen = config.get('launchInFullscreen');
|
||||
configFile.GPU.draw_resolution_scale_x = resolutionMapping[config.get('emulatorResolution')] ?? 1;
|
||||
configFile.GPU.draw_resolution_scale_y = resolutionMapping[config.get('emulatorResolution')] ?? 1;
|
||||
await ensureDir(path.join(config.get('downloadPath'), 'saves', ctx.autoValidCommand.emulator!));
|
||||
configFile.Storage.content_root = path.join(config.get('downloadPath'), 'saves', ctx.autoValidCommand.emulator!);
|
||||
configFile.Storage.storage_root = path.join(config.get('downloadPath'), 'storage', ctx.autoValidCommand.emulator!, 'config');
|
||||
configFile.Storage.cache_root = path.join(config.get('downloadPath'), 'storage', ctx.autoValidCommand.emulator!, 'cache');
|
||||
|
||||
await Bun.write(configPath, toml.stringify(configFile));
|
||||
};
|
||||
|
||||
args.push(`--config`, configPath);
|
||||
|
||||
if (config.get('launchInFullscreen'))
|
||||
{
|
||||
args.push(`--fullscreen`);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
handleEmulatorLaunchSupport (ctx: Parameters<typeof GameflowHooks.prototype.games.emulatorLaunchSupport.callAsync>['0']):
|
||||
ReturnType<typeof GameflowHooks.prototype.games.emulatorLaunchSupport.call>
|
||||
{
|
||||
return { id: desc.name, supportLevel: "full", capabilities: ["batch", "fullscreen", "saves", "states"] };
|
||||
}
|
||||
|
||||
load (ctx: PluginContextType)
|
||||
{
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulator }, this.handleEmulatorLaunchSupport);
|
||||
ctx.hooks.games.emulatorLaunchSupport.tap({ name: desc.name, emulator: this.emulatorEdge }, this.handleEmulatorLaunchSupport);
|
||||
|
||||
ctx.hooks.games.emulatorLaunch.tapPromise({ name: desc.name, emulator: this.emulator }, this.handleLaunch);
|
||||
ctx.hooks.games.emulatorLaunch.tapPromise({ name: desc.name, emulator: this.emulatorEdge }, this.handleLaunch);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { PluginContextType, PluginType } from "@/bun/types/typesc.schema";
|
||||
import desc from './package.json';
|
||||
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomsApiRomsGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
||||
import { DetailedRomSchema, getCollectionApiCollectionsIdGet, getCollectionsApiCollectionsGet, getCurrentUserApiUsersMeGet, getPlatformApiPlatformsIdGet, getPlatformFirmwareApiFirmwareGet, getPlatformsApiPlatformsGet, getRomApiRomsIdGet, getRomContentApiRomsIdContentFileNameGet, getRomsApiRomsGet, SimpleRomSchema, updateRomUserApiRomsIdPropsPut } from "@/clients/romm";
|
||||
import { config } from "@/bun/api/app";
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
|
@ -138,7 +138,9 @@ export default class RommIntegration implements PluginType
|
|||
});
|
||||
games.push(...rommGames.data.items.map(g =>
|
||||
{
|
||||
return this.convertRomToFrontend(g);
|
||||
const game: FrontEndGameType & { igdb_id?: number; } = this.convertRomToFrontend(g);
|
||||
game.igdb_id = g.igdb_id ?? undefined;
|
||||
return game;
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
@ -181,8 +183,9 @@ export default class RommIntegration implements PluginType
|
|||
|
||||
const files = await Promise.all(rom.files.map(async f =>
|
||||
{
|
||||
getRomContentApiRomsIdContentFileNameGet;
|
||||
const file: DownloadFileEntry = {
|
||||
url: new URL(`${config.get('rommAddress')}/api/romsfiles/${f.id}/content/${f.file_name}`),
|
||||
url: new URL(`${config.get('rommAddress')}/api/roms/${f.id}/files/content/${f.file_name}`),
|
||||
file_name: f.file_name,
|
||||
file_path: f.file_path,
|
||||
size: f.file_size_bytes,
|
||||
|
|
@ -198,8 +201,8 @@ export default class RommIntegration implements PluginType
|
|||
const name = files[0].file_name.toLocaleLowerCase();
|
||||
if (name.endsWith('.zip') || name.endsWith('.7z') || name.endsWith('.rar'))
|
||||
{
|
||||
extract_path = rom.name ?? path.parse(name).name;
|
||||
path_fs = path.join(rom.fs_path, extract_path);
|
||||
extract_path = '.';
|
||||
path_fs = path.join(rom.fs_path, rom.slug ?? rom.fs_name_no_ext);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ import { PluginManager } from "./plugin-manager";
|
|||
import pcsx2 from './builtin/emulators/com.simeonradivoev.gameflow.pcsx2/package.json';
|
||||
import ppsspp from './builtin/emulators/com.simeonradivoev.gameflow.ppsspp/package.json';
|
||||
import dolphin from './builtin/emulators/com.simeonradivoev.gameflow.dolphin/package.json';
|
||||
import cemu from './builtin/emulators/com.simeonradivoev.gameflow.cemu/package.json';
|
||||
import xenia from './builtin/emulators/com.simeonradivoev.gameflow.xenia/package.json';
|
||||
import xemu from './builtin/emulators/com.simeonradivoev.gameflow.xemu/package.json';
|
||||
import romm from './builtin/sources/com.simeonradivoev.gameflow.romm/package.json';
|
||||
import { PluginDescriptionSchema, PluginDescriptionType, PluginSchema } from "@/bun/types/typesc.schema";
|
||||
|
||||
|
|
@ -13,6 +16,9 @@ export default async function register (pluginManager: PluginManager)
|
|||
{ ...pcsx2, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.pcsx2/pcsx2') },
|
||||
{ ...ppsspp, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.ppsspp/ppsspp') },
|
||||
{ ...dolphin, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.dolphin/dolphin') },
|
||||
{ ...cemu, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.cemu/cemu') },
|
||||
{ ...xenia, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.xenia/xenia') },
|
||||
{ ...xemu, load: () => import('./builtin/emulators/com.simeonradivoev.gameflow.xemu/xemu') },
|
||||
{ ...romm, load: () => import('./builtin/sources/com.simeonradivoev.gameflow.romm/romm') },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export class TaskQueue
|
|||
{
|
||||
this.queue = [];
|
||||
this.activeQueue.forEach(c => c.abort());
|
||||
return Promise.all(this.activeQueue.map(c => c.promise.promise));
|
||||
return Promise.all(this.activeQueue.map(c => c.promise.promise.catch(e => console.error("Error During Task Queue Closing"))));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,10 +212,15 @@ export class JobContext<T extends IJob<TData, TState>, TData, TState extends str
|
|||
{
|
||||
this.events.emit('started', { id: this.m_id, job: this });
|
||||
await this.m_job.start(this);
|
||||
this.completed = true;
|
||||
this.events.emit('completed', { id: this.m_id, job: this });
|
||||
this.m_promise.resolve(this.m_job.exposeData?.());
|
||||
|
||||
if (!this.abortSignal.aborted)
|
||||
{
|
||||
this.completed = true;
|
||||
this.events.emit('completed', { id: this.m_id, job: this });
|
||||
this.m_promise.resolve(this.m_job.exposeData?.());
|
||||
} else
|
||||
{
|
||||
this.m_promise.resolve(undefined);
|
||||
}
|
||||
} catch (error)
|
||||
{
|
||||
if (error !== 'cancel')
|
||||
|
|
@ -225,7 +230,7 @@ export class JobContext<T extends IJob<TData, TState>, TData, TState extends str
|
|||
|
||||
this.events.emit('error', { id: this.m_id, job: this, error });
|
||||
this.error = error;
|
||||
this.m_promise.reject(error);
|
||||
this.m_promise.resolve(undefined);
|
||||
} finally
|
||||
{
|
||||
this.running = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue