diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ec1ab466..1ea23a16b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. -## 2.4.28 +## 2.4.29 +### Changed +* Don't show artwork only apps when doing excludes (this didn't work anyway, since nothing is being added) +## 2.4.28 ### Added * Parsing of executable arguments in executable mode for GOG, Amazon, Epic, EA Desktop, and Legendary (resolves [issue 263](https://github.com/SteamGridDB/steam-rom-manager/issues/263)). Previously these were being ignored. As far as I can tell UPlay, UWP, and Itch.io never pass commandline arguments to executables. diff --git a/README.md b/README.md index 93a6a8314a..22dad1da5c 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,11 @@ In addition to flexible importing of ROMS, SRM now has several *platform parsers We are open to suggestions and pull requests if you would like a platform parser added! ## Artwork Only Parsers -Artwork only parsers allow you to change the artwork for existing non-SRM added games. Put it simply they just change artwork, they don't add shortcuts. +Artwork only parsers allow you to change the artwork for existing non-SRM games. Put it simply they just change artwork, they don't add shortcuts. |Parser|Windows|Mac OS|Linux| |---|---|---|---| |Steam|✅|✅|✅| - -In the future we plan to add an artwork only parser for non Steam games (either added manually or through some tool other than SRM). - - +|Non-SRM Shortcuts|✅|✅|✅| # For developers diff --git a/src/lang/en-US/langStrings.json b/src/lang/en-US/langStrings.json index 2d538879b1..8377336155 100644 --- a/src/lang/en-US/langStrings.json +++ b/src/lang/en-US/langStrings.json @@ -93,6 +93,12 @@ "fatalError__i": "> Steam parser failed with fatal error:\n ${error}" } }, + "nonSRMShortcutsParser": { + "errors": { + "noSteamAccounts": "> Steam directory specified has no accounts.", + "fatalError__i": "> Non-SRM Shortcuts parser failed with fatal error:\n ${error}" + } + }, "manualParser": { "manifestsInputTitle": "Manifests Directory", "manifestsInputPlaceholder": "/path/to/your/manifests/", @@ -515,7 +521,7 @@ "incorrectParser": "Incorrect parser!" }, "romDir__md": "> ROMs directory is invalid!", - "userAccounts__md": "> Steam parser requires `User Accounts` field", + "userAccounts__md": "> Artwork Only parsers require `User Accounts` field", "steamDir__md": "> Steam directory is invalid!", "startInDir__md": "> \"Start In\" directory is invalid!", "executable__md": "> Executable is invalid!", diff --git a/src/lang/en-US/markdown/non-srm-shortcuts-parser.md b/src/lang/en-US/markdown/non-srm-shortcuts-parser.md new file mode 100644 index 0000000000..4f429bff35 --- /dev/null +++ b/src/lang/en-US/markdown/non-srm-shortcuts-parser.md @@ -0,0 +1,26 @@ +# Non-SRM Shortcut Parser + +This parser imports non SRM steam shortcuts into SRM so their artowrk can be managed. It does not add shortcuts, and as such is an `Artwork Only` parser. This parser requires the `User Accounts` field to be set. + +## User accounts (required) + +Used to limit configuration to specific user accounts. In order to set user accounts, the following syntax must be used: +``` +${...} +``` +You **must** use the username you use to **log in** into Steam **if** [use account credentials](#what-does-use-account-credentials-do) is enabled: + +![Account example](../../../assets/images/user-account-example.png) {.fitImage .center} + +For example, this is how you specify account for "Banana" and "Apple": + +``` +${Banana}${Apple} +``` + +You can also limit accounts by specifying their ids directly. For example: + +``` +${56489124}${21987424} +``` +Would limit the search to `steam/userdata/56489124` and `steam/userdata/21987424`. diff --git a/src/lang/en-US/markdown/steam-parser.md b/src/lang/en-US/markdown/steam-parser.md index 9ecd1c430c..e385e6ae9a 100644 --- a/src/lang/en-US/markdown/steam-parser.md +++ b/src/lang/en-US/markdown/steam-parser.md @@ -1,13 +1,13 @@ # Steam parser -This parser imports steam games into SRM. It does not add shortcuts, but it allows you to set the artwork for your steam games. By default the parser will get games from all user accounts in the steam directory specified — if you would prefer to only get the games for a subset of the accounts then specify them in the `User accounts` field. +This parser imports steam games into SRM so you can manage their artwork. It does not add shortcuts, and as such is an `Artwork Only` parser. This parser requires the `User Accounts` field to be set. ## Limitations Unfortunately for the time being this parser only works for steam games **that are in at least one category**. The reason for this is that Steam only stores your full list of games locally if they are categorized. Sometimes, for unknown reasons, games will be stored locally regardless and the parser will work, but to be safe the easiest thing to do is just **create a Steam Category** that has all of your Steam games in it. -## User accounts (Optional) +## User accounts (required) -Can be used to limit configuration to specific user accounts. In order to set user accounts, the following syntax must be used: +Used to limit configuration to specific user accounts. In order to set user accounts, the following syntax must be used: ``` ${...} ``` @@ -21,11 +21,12 @@ For example, this is how you specify account for "Banana" and "Apple": ${Banana}${Apple} ``` -In case the [use account credentials](#what-does-use-account-credentials-do) is disabled, you can still limit accounts by specifying their ids directly: +You can also limit accounts by specifying their ids directly. For example: ``` ${56489124}${21987424} ``` +Would limit the search to `steam/userdata/56489124` and `steam/userdata/21987424`. ## What does "Skip found accounts with missing data directories" do? diff --git a/src/lang/index.ts b/src/lang/index.ts index 85c896ab5e..a70a2ed9f9 100644 --- a/src/lang/index.ts +++ b/src/lang/index.ts @@ -112,13 +112,22 @@ function getMarkdown(langPath: string) { steamParser: { docs__md: { self: [ - require(`${langPath}/steam-parser.md`) + require(`${langPath}/steam-parser.md`), + require(`${langPath}/steam-parser-input.md`) ], input: [ require(`${langPath}/steam-parser-input.md`) ] } }, + nonSRMShortcutsParser: { + docs__md: { + self: [ + require(`${langPath}/non-srm-shortcuts-parser.md`), + ], + input: [] as string[] + } + }, manualParser: { docs__md: { self: [ diff --git a/src/lib/helpers/steam/generate-app-id.ts b/src/lib/helpers/steam/generate-app-id.ts index c353f0809b..daa86b434c 100644 --- a/src/lib/helpers/steam/generate-app-id.ts +++ b/src/lib/helpers/steam/generate-app-id.ts @@ -15,12 +15,6 @@ export function generateAppId(exe: string, appname: string) { export function generateShortAppId(exe: string, appname: string) { return shortenAppId(generateAppId(exe, appname)); } - -// Used as appid in shortcuts.vdf -export function generateShortcutId(exe: string, appname: string) { - return Number((generatePreliminaryId(exe, appname) >> BigInt(32)) - BigInt(0x100000000)); -} - // Convert from AppId to ShortAppId export function shortenAppId(longId: string) { return String(BigInt(longId) >> BigInt(32)); @@ -31,7 +25,17 @@ export function lengthenAppId(shortId: string) { return String(BigInt(shortId) << BigInt(32) | BigInt(0x02000000)); } +// Used as appid in shortcuts.vdf +export function generateShortcutId(exe: string, appname: string) { + return Number((generatePreliminaryId(exe, appname) >> BigInt(32)) - BigInt(0x100000000)); +} + // Convert from AppId to ShortcutAppId export function shortcutifyAppId(longId: string) { return Number(shortenAppId(longId)) >> 32 } + +// Convert from ShortcutAppId to AppId +export function appifyShortcutId(shortcutId: number) { + return lengthenAppId(String(BigInt(shortcutId) + BigInt(0x100000000))) +} diff --git a/src/lib/parsers/all-parsers.ts b/src/lib/parsers/all-parsers.ts index c7ade34653..8a9ab822e9 100644 --- a/src/lib/parsers/all-parsers.ts +++ b/src/lib/parsers/all-parsers.ts @@ -1,6 +1,7 @@ export * from './glob.parser'; export * from './glob-regex.parser'; export * from './steam.parser'; +export * from './non-srm-shortcuts.parser'; export * from './epic.parser'; export * from './gog-galaxy.parser'; export * from './steam.parser'; diff --git a/src/lib/parsers/available-parsers.ts b/src/lib/parsers/available-parsers.ts index abe221cddb..eeb875ac5a 100644 --- a/src/lib/parsers/available-parsers.ts +++ b/src/lib/parsers/available-parsers.ts @@ -16,14 +16,15 @@ export const availableParserInputs: Record = { 'UPlay': ['uplayDir','uplayLauncherMode'], 'UWP': ['UWPDir', 'UWPLauncherMode'], 'EA Desktop': ['eaGamesDir','eaLauncherMode'], - 'Battle.net': ['battleExeOverride'] + 'Battle.net': ['battleExeOverride'], + 'Non-SRM Shortcuts': [] }; export const availableParsers: ParserType[] = Object.keys(availableParserInputs) as ParserType[]; export const superTypes: Record = { 'Manual': ['Manual'], - 'ArtworkOnly': ['Steam'], + 'ArtworkOnly': ['Steam', 'Non-SRM Shortcuts'], 'ROM': [ 'Glob', 'Glob-regex' diff --git a/src/lib/parsers/gog-galaxy.parser.ts b/src/lib/parsers/gog-galaxy.parser.ts index aa000faf74..da4e63379b 100644 --- a/src/lib/parsers/gog-galaxy.parser.ts +++ b/src/lib/parsers/gog-galaxy.parser.ts @@ -58,7 +58,6 @@ export class GOGParser implements GenericParser { let parsedData: ParsedData = {success: [], failed:[]}; parsedData.executableLocation = galaxyExePath; for(let task of playtasks) { - console.log("playtask", task) if(task.params.executablePath) { const productID = task.productId.toString(); parsedData.success.push({ diff --git a/src/lib/parsers/itch-io.parser.ts b/src/lib/parsers/itch-io.parser.ts index 8ad03e0c77..48185616f3 100644 --- a/src/lib/parsers/itch-io.parser.ts +++ b/src/lib/parsers/itch-io.parser.ts @@ -71,7 +71,6 @@ export class ItchIoParser implements GenericParser { sqliteWrapper.callWorker() .then((games: {[k: string]: any}[]) => { const success = games.map(({ title, verdict }: { [key:string]:string }) => { - console.log("verd",JSON.parse(verdict)) const { basePath, candidates } = JSON.parse(verdict); if (!candidates) { return null; diff --git a/src/lib/parsers/non-srm-shortcuts.parser.ts b/src/lib/parsers/non-srm-shortcuts.parser.ts new file mode 100644 index 0000000000..1f91d90376 --- /dev/null +++ b/src/lib/parsers/non-srm-shortcuts.parser.ts @@ -0,0 +1,51 @@ +import { ParserInfo, GenericParser, ParsedData, VDF_ListData } from '../../models'; +import { APP } from '../../variables'; +import * as path from 'path'; +import * as steam from '../helpers/steam' +const shortcutsParser = require('steam-shortcut-editor'); + +import * as fs from 'fs'; + +export class NonSRMShortcutsParser implements GenericParser { + + private get lang() { + return APP.lang.nonSRMShortcutsParser; + } + getParserInfo(): ParserInfo { + return { + title: 'Non-SRM Shortcuts', + info: this.lang.docs__md.self.join(''), + inputs: {} + }; + } + + execute(directories: string[], inputs: { [key: string]: any }, cache?: { [key: string]: any }) { + return new Promise(async (resolve, reject)=>{ + if(!directories || directories.length==0){ + return reject(this.lang.errors.noSteamAccounts); + } + try { + const parsedData: ParsedData = { success: [], failed: [] } + for(let userdir of directories) { + const shortcutsPath = path.join(userdir,'config','shortcuts.vdf') + const addedItemsPath = path.join(userdir,'config','addedItemsV2.json') + const addedItems = fs.existsSync(addedItemsPath) ? JSON.parse(fs.readFileSync(addedItemsPath,'utf8')) : {}; + const addedAppIds = Object.keys(addedItems.addedApps).filter(appId=>!addedItems.addedApps[appId].artworkOnly) + if(fs.existsSync(shortcutsPath)) { + const {shortcuts} = shortcutsParser.parseBuffer(fs.readFileSync(shortcutsPath)); + const mappedApps = shortcuts.filter((shortcut: any) => { + return !addedAppIds.includes(steam.appifyShortcutId(shortcut.appid)) + }).map((shortcut: any) => { return { + extractedTitle: shortcut.appname, + extractedAppId: steam.shortenAppId(steam.appifyShortcutId(shortcut.appid)) + }}) + parsedData.success=[...parsedData.success, ...mappedApps] + } + } + resolve(parsedData) + } catch(e) { + reject(this.lang.errors.fatalError__i.interpolate({error: e})) + } + }) + } +} \ No newline at end of file diff --git a/src/lib/parsers/steam.parser.ts b/src/lib/parsers/steam.parser.ts index 49ba34d106..4671bf66f2 100644 --- a/src/lib/parsers/steam.parser.ts +++ b/src/lib/parsers/steam.parser.ts @@ -7,7 +7,6 @@ import * as path from "path"; import * as bvdf from "binary-vdf-2"; import { glob } from "glob"; import * as json from "../helpers/json"; -import * as steam from "../helpers/steam"; export class SteamParser implements GenericParser { diff --git a/src/lib/vdf-shortcuts-file.ts b/src/lib/vdf-shortcuts-file.ts index 535a6b5a28..4ed2609b37 100644 --- a/src/lib/vdf-shortcuts-file.ts +++ b/src/lib/vdf-shortcuts-file.ts @@ -9,7 +9,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; const shortcutsParser = require('steam-shortcut-editor'); -import * as genericParser from '@node-steam/vdf'; export class VDF_ShortcutsFile { diff --git a/src/models/language.model.ts b/src/models/language.model.ts index ffd7fa8e51..0d139f6b14 100644 --- a/src/models/language.model.ts +++ b/src/models/language.model.ts @@ -105,6 +105,16 @@ export interface languageStruct { fatalError__i: string } }, + nonSRMShortcutsParser: { + docs__md: { + self: string[], + input: string[] + }, + errors: { + noSteamAccounts: string, + fatalError__i: string + } + } battleNetParser: { battleExeOverrideTitle: string, battleExeOverridePlaceholder: string, diff --git a/src/models/parser.model.ts b/src/models/parser.model.ts index bb7be6647d..3e80dbdcba 100644 --- a/src/models/parser.model.ts +++ b/src/models/parser.model.ts @@ -116,7 +116,7 @@ const steamInputEnabled = StringLiteralArray(['0','1','2']) // 0 disabled, 1 glo export type SteamInputEnabled = (typeof steamInputEnabled)[number]; -export type ParserType = 'Glob' | 'Glob-regex' | 'Manual' | 'Amazon Games' | 'Epic' | 'Legendary' | 'GOG Galaxy' | 'itch.io' | 'Steam' | 'UPlay' | 'UWP' | 'EA Desktop'| 'Battle.net'; +export type ParserType = 'Glob' | 'Glob-regex' | 'Manual' | 'Amazon Games' | 'Epic' | 'Legendary' | 'GOG Galaxy' | 'itch.io' | 'Steam' | 'Non-SRM Shortcuts' | 'UPlay' | 'UWP' | 'EA Desktop'| 'Battle.net'; export type SuperType = 'Manual'|'ArtworkOnly'|'ROM'|'Platform'; export interface ParserInfo { diff --git a/src/renderer/components/parsers.component.ts b/src/renderer/components/parsers.component.ts index 04f94c79e6..4f572e636e 100644 --- a/src/renderer/components/parsers.component.ts +++ b/src/renderer/components/parsers.component.ts @@ -267,7 +267,7 @@ export class ParsersComponent implements AfterViewInit, OnDestroy { })); }, onValidate: (self, path) => { - if (parsers[i]!=='Steam' && this.userForm.get('parserType').value === parsers[i]) + if (parserInfo.superTypesMap[parsers[i]] !== parserInfo.ArtworkOnlyType && this.userForm.get('parserType').value === parsers[i]) return this.parsersService.validate(path[0] as keyof UserConfiguration, { parser: parsers[i], input: inputFieldName, inputData: self.value }); else return null; @@ -895,7 +895,7 @@ export class ParsersComponent implements AfterViewInit, OnDestroy { const executableLocation = data.files[i].modifiedExecutableLocation; const title = data.files[i].finalTitle; let shortAppId; let appId; let exceptionKey; - if(config.parserType !== 'Steam') { + if(parserInfo.superTypesMap[config.parserType] !== parserInfo.ArtworkOnlyType) { shortAppId = steam.generateShortAppId(executableLocation, title); appId = steam.lengthenAppId(shortAppId); exceptionKey = steam.generateShortAppId(executableLocation, data.files[i].extractedTitle); diff --git a/src/renderer/components/preview.component.ts b/src/renderer/components/preview.component.ts index 2d69a34c2a..07d0b564b9 100644 --- a/src/renderer/components/preview.component.ts +++ b/src/renderer/components/preview.component.ts @@ -365,7 +365,7 @@ export class PreviewComponent implements OnDestroy { if(this.detailsApp && this.matchFix) { const {steamDirectory, userId, appId, app} = this.detailsApp; this.previewData[steamDirectory][userId].apps[appId].title = this.matchFixDict[this.matchFix].name; - if(app.parserType !== 'Steam') { + if(superTypesMap[app.parserType] !== 'ArtworkOnly') { const changedId = steam.generateAppId(app.executableLocation, this.matchFixDict[this.matchFix].name); this.previewData[steamDirectory][userId].apps[appId].changedId = changedId; } @@ -493,7 +493,6 @@ export class PreviewComponent implements OnDestroy { return `${app.extractedTitle} \$\{id:${exceptionId}\}` }); exceptionKeys = exceptionKeys.concat(newKeys) - this.previewData[steamDirectory][userId].apps = _.pickBy(this.previewData[steamDirectory][userId].apps, (value: PreviewDataApp, key: string) => { return !this.excludedAppIds[steamDirectory][userId][key] }) diff --git a/src/renderer/services/parsers.service.ts b/src/renderer/services/parsers.service.ts index ce525da5e4..90e7083068 100644 --- a/src/renderer/services/parsers.service.ts +++ b/src/renderer/services/parsers.service.ts @@ -204,7 +204,7 @@ export class ParsersService { return; else userConfigurations[index].current.parserId = userConfigurations[index].saved.parserId; - if(userConfigurations[index].current.parserType==='Steam') { + if(parserInfo.superTypesMap[userConfigurations[index].current.parserType]===parserInfo.ArtworkOnlyType) { userConfigurations[index].current.titleFromVariable.tryToMatchTitle=false; } userConfigurations[index] = { saved: userConfigurations[index].current, current: null }; @@ -307,7 +307,7 @@ export class ParsersService { return (data == null || data.length === 0 || this.validateEnvironmentPath(data || '', true)) ? null : this.lang.validationErrors.startInDir__md; case 'userAccounts': { - if(options && options.parserType=='Steam') { + if(options && parserInfo.superTypesMap[options.parserType as ParserType]==parserInfo.ArtworkOnlyType) { return data && data.specifiedAccounts ? this.validateVariableParserString(data.specifiedAccounts||'') : this.lang.validationErrors.userAccounts__md; } else{ return this.validateVariableParserString((data||{}).specifiedAccounts || ''); diff --git a/src/renderer/services/preview.service.ts b/src/renderer/services/preview.service.ts index ee08d4d765..5f3fc418ef 100644 --- a/src/renderer/services/preview.service.ts +++ b/src/renderer/services/preview.service.ts @@ -39,6 +39,7 @@ import * as url from "../../lib/helpers/url"; import * as appImage from "../../lib/helpers/app-image"; import * as ids from '../../lib/helpers/steam'; import { artworkTypes, defaultArtworkType, artworkIdDict, invertedArtworkIdDict } from '../../lib/artwork-types'; +import { superTypesMap } from '../../lib/parsers/available-parsers'; import * as _ from "lodash"; import * as fs from "fs-extra"; import * as path from "path"; @@ -545,7 +546,7 @@ export class PreviewService { let executableLocation = file.modifiedExecutableLocation; let title = file.finalTitle; let appID: string = ''; - if(config.parserType !== 'Steam') { + if(superTypesMap[config.parserType] !== 'ArtworkOnly') { appID = steam.generateAppId(executableLocation, title); } else { appID = steam.lengthenAppId(executableLocation.replace(/\"/g,"")); diff --git a/src/renderer/templates/view.component.html b/src/renderer/templates/view.component.html index 5556f7c550..c11ac2756a 100644 --- a/src/renderer/templates/view.component.html +++ b/src/renderer/templates/view.component.html @@ -18,7 +18,7 @@ {{steamUser}}
- +