Skip to content

Commit

Permalink
[Tech] Type-check Legendary commands (#2918)
Browse files Browse the repository at this point in the history
  • Loading branch information
CommandMC authored and flavioislima committed Oct 26, 2023
1 parent 6b788e2 commit e111423
Show file tree
Hide file tree
Showing 26 changed files with 488 additions and 200 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,9 @@
"stream": "^0.0.2",
"systeminformation": "^5.21.7",
"ts-prune": "^0.10.3",
"tslib": "^2.4.0",
"yauzl": "^2.10.0",
"zod": "^3.22.3"
"zod": "^3.22.3",
"tslib": "^2.5.0"
},
"scripts": {
"setup": "yarn install && yarn allow-scripts",
Expand Down
18 changes: 10 additions & 8 deletions src/backend/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import { trackPidPlaytime } from './metrics/metrics'
import { closeOverlay, openOverlay } from 'backend/hyperplay-overlay'
import * as VDF from '@node-steam/vdf'
import { readFileSync } from 'fs'
import { LegendaryCommand } from './storeManagers/legendary/commands'
import { commandToArgsArray } from './storeManagers/legendary/library'

async function prepareLaunch(
gameSettings: GameSettings,
Expand Down Expand Up @@ -951,26 +953,28 @@ async function callRunner(

/**
* Generates a formatted, safe command that can be logged
* @param commandParts The runner command that's executed, e. g. install, list, etc.
* @param command The runner command that's executed, e.g. install, list, etc.
* Note that this will be modified, so pass a copy of your actual command parts
* @param env Enviroment variables to use
* @param wrappers Wrappers to use (gamemode, steam runtime, etc.)
* @param runnerPath The full path to the runner executable
* @returns
*/
function getRunnerCallWithoutCredentials(
commandParts: string[],
command: string[] | LegendaryCommand,
env: Record<string, string> | NodeJS.ProcessEnv = {},
runnerPath: string
): string {
const modifiedCommandParts = [...commandParts]
if (!Array.isArray(command)) command = commandToArgsArray(command)

const modifiedCommand = [...command]
// Redact sensitive arguments (Authorization Code for Legendary, token for GOGDL)
for (const sensitiveArg of ['--code', '--token']) {
const sensitiveArgIndex = modifiedCommandParts.indexOf(sensitiveArg)
const sensitiveArgIndex = modifiedCommand.indexOf(sensitiveArg)
if (sensitiveArgIndex === -1) {
continue
}
modifiedCommandParts[sensitiveArgIndex + 1] = '<redacted>'
modifiedCommand[sensitiveArgIndex + 1] = '<redacted>'
}

const formattedEnvVars: string[] = []
Expand All @@ -984,12 +988,10 @@ function getRunnerCallWithoutCredentials(
formattedEnvVars.push(`${key}=${quoteIfNecessary(value ?? '')}`)
}

commandParts = commandParts.filter(Boolean)

return [
...formattedEnvVars,
quoteIfNecessary(runnerPath),
...modifiedCommandParts.map(quoteIfNecessary)
...modifiedCommand.map(quoteIfNecessary)
].join(' ')
}

Expand Down
15 changes: 8 additions & 7 deletions src/backend/save_sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { legendaryConfigPath } from './constants'
import { join } from 'path'
import { gameManagerMap, libraryManagerMap } from 'backend/storeManagers'
import { LegendaryAppName } from './storeManagers/legendary/commands/base'

async function getDefaultSavePath(
appName: string,
Expand Down Expand Up @@ -78,13 +79,13 @@ async function getDefaultLegendarySavePath(appName: string): Promise<string> {
logInfo(['Computing default save path for', appName], LogPrefix.Legendary)
const abortControllerName = appName + '-savePath'
await runLegendaryCommand(
[
'sync-saves',
appName,
'--skip-upload',
'--skip-download',
'--accept-path'
],
{
subcommand: 'sync-saves',
appName: LegendaryAppName.parse(appName),
'--skip-upload': true,
'--skip-download': true,
'--accept-path': true
},
createAbortController(abortControllerName),
{
logMessagePrefix: 'Getting default save path',
Expand Down
4 changes: 2 additions & 2 deletions src/backend/storeManagers/gog/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ import { showDialogBoxModalAuto } from '../../dialog/dialog'
import { sendFrontendMessage } from '../../main_window'
import { RemoveArgs } from 'common/types/game_manager'
import { logFileLocation } from 'backend/storeManagers/storeManagerCommon/games'
import { getWineFlags } from 'backend/utils/compatibility_layers'
import { getWineFlagsArray } from 'backend/utils/compatibility_layers'
import axios, { AxiosError } from 'axios'
import { isOnline, runOnceWhenOnline } from 'backend/online_monitor'

Expand Down Expand Up @@ -513,7 +513,7 @@ export async function launch(
? wineExec.replaceAll("'", '')
: wineExec

wineFlag = [...getWineFlags(wineBin, wineType, shlex.join(wrappers))]
wineFlag = getWineFlagsArray(wineBin, wineType, shlex.join(wrappers))
}

const commandParts = [
Expand Down
13 changes: 13 additions & 0 deletions src/backend/storeManagers/legendary/commands/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NonEmptyString } from './base'

interface AuthCommand {
subcommand: 'auth'
'--import'?: true
'--code'?: NonEmptyString
'--token'?: NonEmptyString
'--sid'?: NonEmptyString
'--delete'?: true
'--disable-webview'?: true
}

export default AuthCommand
37 changes: 37 additions & 0 deletions src/backend/storeManagers/legendary/commands/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { z } from 'zod'
import path from 'path'
import { hasGame } from '../library'

export const LegendaryAppName = z
.string()
.refine((val) => hasGame(val), {
message: 'AppName was not found on account'
})
.brand('LegendaryAppName')
export type LegendaryAppName = z.infer<typeof LegendaryAppName>

export const LegendaryPlatform = z.enum(['Win32', 'Windows', 'Mac'] as const)
export type LegendaryPlatform = z.infer<typeof LegendaryPlatform>

export const NonEmptyString = z.string().min(1).brand('NonEmptyString')
export type NonEmptyString = z.infer<typeof NonEmptyString>

export const Path = z
.string()
.refine((val) => path.parse(val).root, 'Path is not valid')
.brand('Path')
export type Path = z.infer<typeof Path>

export const PositiveInteger = z
.number()
.int()
.positive()
.brand('PositiveInteger')
export type PositiveInteger = z.infer<typeof PositiveInteger>

export const URL = z.string().url().brand('URL')
export type URL = z.infer<typeof URL>

// FIXME: This doesn't feel right
export const URI = z.union([Path, URL])
export type URI = z.infer<typeof URI>
6 changes: 6 additions & 0 deletions src/backend/storeManagers/legendary/commands/cleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface CleanupCommand {
subcommand: 'cleanup'
'--keep-manifests'?: true
}

export default CleanupCommand
23 changes: 23 additions & 0 deletions src/backend/storeManagers/legendary/commands/eos_overlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from 'zod'
import { LegendaryAppName, NonEmptyString, Path } from './base'

const EosOverlayAction = z.enum([
'install',
'update',
'remove',
'enable',
'disable',
'info'
] as const)
type EosOverlayAction = z.infer<typeof EosOverlayAction>

interface EosOverlayCommand {
subcommand: 'eos-overlay'
action: EosOverlayAction
'--path'?: Path
'--prefix'?: Path
'--app'?: LegendaryAppName
'--bottle'?: NonEmptyString
}

export default EosOverlayCommand
12 changes: 12 additions & 0 deletions src/backend/storeManagers/legendary/commands/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LegendaryAppName, LegendaryPlatform, Path } from './base'

interface ImportCommand {
subcommand: 'import'
appName: LegendaryAppName
installationDirectory: Path
'--disable-check'?: true
'--with-dlcs'?: true
'--platform'?: LegendaryPlatform
}

export default ImportCommand
2 changes: 1 addition & 1 deletion src/backend/storeManagers/legendary/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { PositiveInteger } from './base'

import InstallCommand from './install'
import LaunchCommand from './launch'
Expand All @@ -12,6 +11,7 @@ import UninstallCommand from './uninstall'
import ImportCommand from './import'
import CleanupCommand from './cleanup'
import AuthCommand from './auth'
import { PositiveInteger } from './base'
import EglSyncCommand from './egl_sync'

interface BaseLegendaryCommand {
Expand Down
11 changes: 11 additions & 0 deletions src/backend/storeManagers/legendary/commands/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { LegendaryAppName, LegendaryPlatform } from './base'

interface InfoCommand {
subcommand: 'info'
appName: LegendaryAppName
'--offline'?: true
'--json'?: true
'--platform'?: LegendaryPlatform
}

export default InfoCommand
48 changes: 48 additions & 0 deletions src/backend/storeManagers/legendary/commands/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
PositiveInteger,
LegendaryAppName,
NonEmptyString,
Path,
URL,
URI,
LegendaryPlatform
} from './base'

interface InstallCommand {
subcommand: 'install' | 'download' | 'update' | 'repair'
appName: LegendaryAppName
sdlList?: NonEmptyString[]
'--base-path'?: Path
'--game-folder'?: NonEmptyString
'--max-shared-memory'?: PositiveInteger
'--max-workers'?: PositiveInteger
'--manifest'?: URI
'--old-manifest'?: URI
'--delta-manifest'?: URI
'--base-url'?: URL
'--force'?: true
'--disable-patching'?: true
'--download-only'?: true
'--no-install'?: true
'--update-only'?: true
'--dlm-debug'?: true
'--platform'?: LegendaryPlatform
'--prefix'?: NonEmptyString
'--exclude'?: NonEmptyString
'--enable-reordering'?: true
'--dl-timeout'?: PositiveInteger
'--save-path'?: Path
'--repair'?: true
'--repair-and-update'?: true
'--ignore-free-space'?: true
'--disable-delta-manifests'?: true
'--reset-sdl'?: true
'--skip-sdl'?: true
'--disable-sdl'?: true
'--preferred-cdn'?: NonEmptyString
'--no-https'?: true
'--with-dlcs'?: true
'--skip-dlcs'?: true
}

export default InstallCommand
26 changes: 26 additions & 0 deletions src/backend/storeManagers/legendary/commands/launch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { LegendaryAppName, NonEmptyString, Path } from './base'

interface LaunchCommand {
subcommand: 'launch'
appName: LegendaryAppName
extraArguments?: string
'--offline'?: true
'--skip-version-check'?: true
'--override-username'?: NonEmptyString
'--dry-run'?: true
'--language'?: NonEmptyString
'--wrapper'?: NonEmptyString
'--set-defaults'?: true
'--reset-defaults'?: true
'--override-exe'?: Path
'--origin'?: true
'--json'?: true
'--wine'?: Path
'--wine-prefix'?: Path
'--no-wine'?: true
'--crossover'?: true
'--crossover-app'?: Path
'--crossover-bottle'?: NonEmptyString
}

export default LaunchCommand
16 changes: 16 additions & 0 deletions src/backend/storeManagers/legendary/commands/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LegendaryPlatform } from './base'

interface ListCommand {
subcommand: 'list'
'--platform'?: LegendaryPlatform
'--include-ue'?: true
'-T'?: true
'--third-party'?: true
'--include-non-installable'?: true
'--csv'?: true
'--tsv'?: true
'--json'?: true
'--force-refresh'?: true
}

export default ListCommand
10 changes: 10 additions & 0 deletions src/backend/storeManagers/legendary/commands/move.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { LegendaryAppName, Path } from './base'

interface MoveCommand {
subcommand: 'move'
appName: LegendaryAppName
newBasePath: Path
'--skip-move'?: true
}

export default MoveCommand
7 changes: 7 additions & 0 deletions src/backend/storeManagers/legendary/commands/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
interface StatusCommand {
subcommand: 'status'
'--offline'?: true
'--json'?: true
}

export default StatusCommand
15 changes: 15 additions & 0 deletions src/backend/storeManagers/legendary/commands/sync_saves.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LegendaryAppName, Path } from './base'

interface SyncSavesCommand {
subcommand: 'sync-saves'
appName: LegendaryAppName
'--skip-upload'?: true
'--skip-download'?: true
'--force-upload'?: true
'--force-download'?: true
'--save-path'?: Path
'--disable-filters'?: true
'--accept-path'?: true
}

export default SyncSavesCommand
9 changes: 9 additions & 0 deletions src/backend/storeManagers/legendary/commands/uninstall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LegendaryAppName } from './base'

interface UninstallCommand {
subcommand: 'uninstall'
appName: LegendaryAppName
'--keep-files'?: true
}

export default UninstallCommand
Loading

0 comments on commit e111423

Please sign in to comment.