Skip to content

Commit

Permalink
non-srm shortcuts parser finished
Browse files Browse the repository at this point in the history
  • Loading branch information
cbartondock committed May 7, 2024
1 parent ce0c244 commit b618e0a
Show file tree
Hide file tree
Showing 21 changed files with 138 additions and 33 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 7 additions & 1 deletion src/lang/en-US/langStrings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down Expand Up @@ -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!",
Expand Down
26 changes: 26 additions & 0 deletions src/lang/en-US/markdown/non-srm-shortcuts-parser.md
Original file line number Diff line number Diff line change
@@ -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`.
9 changes: 5 additions & 4 deletions src/lang/en-US/markdown/steam-parser.md
Original file line number Diff line number Diff line change
@@ -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:
```
${...}
```
Expand All @@ -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?

Expand Down
11 changes: 10 additions & 1 deletion src/lang/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
16 changes: 10 additions & 6 deletions src/lib/helpers/steam/generate-app-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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)))
}
1 change: 1 addition & 0 deletions src/lib/parsers/all-parsers.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
5 changes: 3 additions & 2 deletions src/lib/parsers/available-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ export const availableParserInputs: Record<ParserType, string[]> = {
'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<SuperType, ParserType[]> = {
'Manual': ['Manual'],
'ArtworkOnly': ['Steam'],
'ArtworkOnly': ['Steam', 'Non-SRM Shortcuts'],
'ROM': [
'Glob',
'Glob-regex'
Expand Down
1 change: 0 additions & 1 deletion src/lib/parsers/gog-galaxy.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
1 change: 0 additions & 1 deletion src/lib/parsers/itch-io.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
51 changes: 51 additions & 0 deletions src/lib/parsers/non-srm-shortcuts.parser.ts
Original file line number Diff line number Diff line change
@@ -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<ParsedData>(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}))
}
})
}
}
1 change: 0 additions & 1 deletion src/lib/parsers/steam.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
1 change: 0 additions & 1 deletion src/lib/vdf-shortcuts-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions src/models/language.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/models/parser.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/parsers.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/components/preview.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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]
})
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/services/parsers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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 || '');
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/services/preview.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,""));
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/templates/view.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<span>{{steamUser}}</span>
</div>
<div class="apps">
<ng-container *ngFor="let shortcut of sortedShortcuts(viewService.vdfData[steamDir][steamUser].shortcuts.fileData.shortcuts)">
<ng-container *ngFor="let shortcut of sortedShortcuts(viewService.vdfData[steamDir][steamUser].shortcuts.data)">
<div class="app" (click)="setCurrentShortcut(steamDir, steamUser, shortcut)" [class.currentShortcut]="shortcut==currentShortcut"
*ngIf="(shortcut.appname | fuzzyTest: filterValue)"
>
Expand Down

0 comments on commit b618e0a

Please sign in to comment.