diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 5f662c4e..db477c9b 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "Verknüpfung erstellt", - "msg": "Die Verknüpfung wurde auf dem Desktop erstellt." + "msg": "Die Verknüpfung wurde auf dem Desktop erstellt.", + "msg-steam": "Die Verknüpfung wurde in der Steam-Bibliothek erstellt." }, "error": { "msg": "Beim Erstellen der Verknüpfung ist ein Fehler aufgetreten." @@ -843,7 +844,9 @@ "desc": "Das Erstellen einer Verknüpfung ermöglicht es dir, Beat Saber mit den ausgewählten Optionen zu starten, ohne durch BSManager zu gehen.", "launch-options": "Startoptionen", "advanced-launch": "Erweiterter Start", - "valid-btn": "Verknüpfung erstellen" + "valid-btn": "Verknüpfung erstellen", + "create-steam-shortcut": "Steam-Verknüpfung erstellen", + "steam-shortcut-tippy": "Wenn aktiviert, wird anstelle einer Verknüpfung auf dem Desktop eine Verknüpfung in Steam erstellt." }, "connect-to-meta": { "title": "Mit Meta verbinden", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 9deaba07..6e7c296d 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "Shortcut created", - "msg": "The shortcut has been created on the desktop." + "msg": "The shortcut has been created on the desktop.", + "msg-steam": "The shortcut has been created in the Steam library." }, "error": { "msg": "An error occurred while creating the shortcut." @@ -838,7 +839,9 @@ "desc": "Creating a shortcut will allow you to start Beat Saber with the chosen options without going through BSManager.", "launch-options": "Launch options", "advanced-launch": "Advanced launch", - "valid-btn": "Create the shortcut" + "valid-btn": "Create the shortcut", + "create-steam-shortcut": "Create a Steam shortcut", + "steam-shortcut-tippy": "If enabled, instead of creating a shortcut on the desktop, it will be created in Steam." }, "connect-to-meta": { "title": "Connect to Meta", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 528e4939..5b489edc 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "Acceso directo creado", - "msg": "El acceso directo se ha creado en el escritorio." + "msg": "El acceso directo se ha creado en el escritorio.", + "msg-steam": "El acceso directo se ha creado en la biblioteca de Steam." }, "error": { "msg": "Se produjo un error al crear el acceso directo." @@ -843,7 +844,9 @@ "desc": "Crear un atajo te permitirá iniciar Beat Saber con las opciones seleccionadas sin pasar por BSManager.", "launch-options": "Opciones de lanzamiento", "advanced-launch": "Lanzamiento avanzado", - "valid-btn": "Crear el atajo" + "valid-btn": "Crear el atajo", + "create-steam-shortcut": "Crear un atajo directo de Steam", + "steam-shortcut-tippy": "Si está activado, en lugar de crear un atajo directo en el escritorio, se creará en Steam." }, "connect-to-meta": { "title": "Conexión a Meta", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index e0e3c769..06929d43 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -637,7 +637,8 @@ "create-launch-shortcut": { "success": { "title": "Raccourci créé", - "msg": "Le raccourci a été créé sur le bureau." + "msg": "Le raccourci a été créé sur le bureau.", + "msg-steam": "Le raccourci a été créé dans la bibliothèque Steam." }, "error": { "msg": "Une erreur s'est produite lors de la création du raccourci." @@ -844,7 +845,9 @@ "desc": "Créer un raccourci te permettra de démarrer Beat Saber avec les options choisies sans passer par BSManager.", "launch-options": "Options de lancement", "advanced-launch": "Lancement avancé", - "valid-btn": "Créer le raccourci" + "valid-btn": "Créer le raccourci", + "create-steam-shortcut": "Créer un raccourci Steam", + "steam-shortcut-tippy": "Si activé, au lieu de créer un raccourci sur le bureau, celui-ci sera créé dans Steam." }, "connect-to-meta": { "title": "Connexion à Meta", diff --git a/assets/jsons/translations/it.json b/assets/jsons/translations/it.json index 7de34c0c..2ad7ca1f 100644 --- a/assets/jsons/translations/it.json +++ b/assets/jsons/translations/it.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "Scorciatoia creata", - "msg": "La scorciatoia è già stat creata sul desktop." + "msg": "La scorciatoia è già stat creata sul desktop.", + "msg-steam": "Il collegamento è stato creato nella libreria di Steam." }, "error": { "msg": "C'è stato un errore durante la creazione della scorciatoia." @@ -838,7 +839,9 @@ "desc": "Creare una scorciatoia ti permetterà di avviare Beat Saber con le opzioni scelte senza passare per BSManager.", "launch-options": "Opzioni di lancio", "advanced-launch": "Lancio avanzato", - "valid-btn": "Crea la scorciatoia" + "valid-btn": "Crea la scorciatoia", + "create-steam-shortcut": "Crea un scorciatoia Steam", + "steam-shortcut-tippy": "Se abilitato, invece di creare un scorciatoia sul desktop, verrà creato in Steam." }, "connect-to-meta": { "title": "Connettiti a Meta", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 61ffe3a7..c97bac6d 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "ショートカットを作成しました", - "msg": "デスクトップにショートカットを作成されました。" + "msg": "デスクトップにショートカットを作成されました。", + "msg-steam": "ショートカットがSteamライブラリに作成されました。" }, "error": { "msg": "ショートカットの作成中にエラーが発生しました。" @@ -843,7 +844,9 @@ "desc": "ショートカットを作成すると、BSManagerを起動せずに、直接Beat Saberを起動できます。", "launch-options": "起動オプション", "advanced-launch": "高度な起動", - "valid-btn": "ショートカットを作成" + "valid-btn": "ショートカットを作成", + "create-steam-shortcut": "Steamショートカットを作成", + "steam-shortcut-tippy": "有効にすると、デスクトップにショートカットを作成する代わりに、Steam内に作成されます。" }, "connect-to-meta": { "title": "Metaに接続する", diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index e296c2af..c48a5857 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "바로 가기 생성 완료", - "msg": "바탕 화면에 바로 가기가 생성되었습니다." + "msg": "바탕 화면에 바로 가기가 생성되었습니다.", + "msg-steam": "바로 가기가 Steam 라이브러리에 생성되었습니다." }, "error": { "msg": "바로 가기 생성 중 오류가 발생했습니다." @@ -838,12 +839,14 @@ "title": "공유 폴더 추가", "description": "「{folder}」폴더를 연결할 때 문제가 발생할 수 있습니다. 정말 추가하시겠습니까?" }, - "create-launcher-shortcut": { - "title": "런처 바로가기 만들기", - "description": "바탕화면에 실행 파일의 바로가기를 만들어 BSManager를 손쉽게 실행하세요.", - "buttons": { - "submit": "바로가기 만들기" - } + "create-launch-shortcut": { + "title": "바로가기를 생성", + "desc": "바로가기를 생성하면 BSManager를 거치지 않고 선택한 옵션으로 Beat Saber를 시작할 수 있습니다.", + "launch-options": "실행 옵션", + "advanced-launch": "고급 실행", + "valid-btn": "바로가기 생성", + "create-steam-shortcut": "Steam 바로가기를 생성", + "steam-shortcut-tippy": "활성화하면 바탕 화면에 바로가기를 생성하는 대신 Steam에 생성됩니다." }, "connect-to-meta": { "title": "Meta에 연결하기", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 651e11a9..5fb1a70d 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "Ярлык создан", - "msg": "Ярлык находится на рабочем столе." + "msg": "Ярлык находится на рабочем столе.", + "msg-steam": "Ярлык был создан в библиотеке Steam." }, "error": { "msg": "Ошибка создания ярлыка." @@ -843,7 +844,9 @@ "desc": "Ярлык позволит вам запустить Beat Saber без помощи BSManager.", "launch-options": "Параметры запуска", "advanced-launch": "Расширенный запуск", - "valid-btn": "Создать ярлык" + "valid-btn": "Создать ярлык", + "create-steam-shortcut": "Создать ярлык для Steam", + "steam-shortcut-tippy": "Если включено, вместо создания ярлыка на рабочем столе, он будет создан в Steam." }, "connect-to-meta": { "title": "Подключение к Meta", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 668517a5..614af414 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "捷徑已創建", - "msg": "捷徑已在桌面上創建" + "msg": "捷徑已在桌面上創建", + "msg-steam": "捷徑已在 Steam 庫中建立。" }, "error": { "msg": "創建捷徑時發生錯誤" @@ -843,7 +844,9 @@ "desc": "創建捷徑使得你可以在不通過 BSManager 的情況下以特定選項啟動 BeatSaber", "launch-options": "啟動選項", "advanced-launch": "高級啟動", - "valid-btn": "創建捷徑" + "valid-btn": "創建捷徑", + "create-steam-shortcut": "建立 Steam 快捷方式", + "steam-shortcut-tippy": "如果啟用,將不會在桌面建立捷徑,而是在 Steam 中建立。" }, "connect-to-meta": { "title": "連接到 Meta", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ace849b7..0d229eae 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -636,7 +636,8 @@ "create-launch-shortcut": { "success": { "title": "快捷方式已创建", - "msg": "快捷方式已在桌面上创建" + "msg": "快捷方式已在桌面上创建", + "msg-steam": "快捷方式已在 Steam 库中创建。" }, "error": { "msg": "创建快捷方式时发生错误" @@ -843,7 +844,9 @@ "desc": "创建快捷方式使得你可以在不通过 BSManager 的情况下以特定选项启动 BeatSaber", "launch-options": "启动选项", "advanced-launch": "高级启动", - "valid-btn": "创建快捷方式" + "valid-btn": "创建快捷方式", + "create-steam-shortcut": "创建 Steam 快捷方式", + "steam-shortcut-tippy": "如果启用,将不会在桌面创建快捷方式,而是在 Steam 中创建。" }, "connect-to-meta": { "title": "连接到 Meta", diff --git a/src/main/services/bs-launcher/bs-launcher.service.ts b/src/main/services/bs-launcher/bs-launcher.service.ts index 11916a7c..6ebe7ac7 100644 --- a/src/main/services/bs-launcher/bs-launcher.service.ts +++ b/src/main/services/bs-launcher/bs-launcher.service.ts @@ -24,7 +24,6 @@ import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.int import { StaticConfigurationService } from "../static-configuration.service"; import { SteamService } from "../steam.service"; import { tryit } from "shared/helpers/error.helpers"; -import { SteamShortcut } from "shared/models/steam/shortcut.model"; export class BSLauncherService { private static instance: BSLauncherService; @@ -200,27 +199,6 @@ export class BSLauncherService { return this.bsmProtocolService.buildLink("launch", shortcutParams).toString(); } - private async getSteamShortcutData(launchOptions: LaunchOption): Promise{ - const shortcutName = ["Beat Saber", launchOptions.version.BSVersion, launchOptions.version.name].join(" "); - const shortcutIconColor = new Color(launchOptions.version.color, "hex"); - - const exePath = app.getPath("exe"); - - return { - appid: "\u0000\u0000\u0000", - AppName: shortcutName, - Exe: exePath, - StartDir: path.dirname(exePath), - LaunchOptions: this.createLaunchLink(launchOptions), - icon: await this.createShortcutPng(shortcutIconColor), - tags: [ - "BSManager", - "Beat Saber", - "VR", - ] - } as SteamShortcut; - } - public async createLaunchShortcut(launchOptions: LaunchOption, steamShortcut?: boolean): Promise{ const shortcutUrl = this.createLaunchLink(launchOptions); @@ -229,13 +207,20 @@ export class BSLauncherService { if(steamShortcut){ const userId = await tryit(() => this.steam.getActiveUser()); - return this.steam.createShortcut(await this.getSteamShortcutData(launchOptions), userId.result).then(() => true).catch(e => { + const exePath = app.getPath("exe"); + return this.steam.createShortcut({ + AppName: shortcutName, + Exe: exePath, + StartDir: path.dirname(exePath), + LaunchOptions: this.createLaunchLink(launchOptions), + icon: await this.createShortcutPng(shortcutIconColor), + OpenVR: "\u0001" + }, userId.result).then(() => true).catch(e => { log.error(e); return false; }); } - return execOnOs({ win32: async () => ( shell.writeShortcutLink(path.join(app.getPath("desktop"), `${shortcutName}.lnk`), { @@ -262,7 +247,7 @@ export class BSLauncherService { const bsPath: string = await (async () => { const bsPath = await this.localVersionService.getInstalledVersionPath(launchOption.version); return bsPath ?? this.localVersionService.getVersionPath(launchOption.version); - })().catch(e => { + })().catch((e): null => { log.error(e); return null; }); diff --git a/src/main/services/steam.service.ts b/src/main/services/steam.service.ts index 257c270c..68cc012d 100644 --- a/src/main/services/steam.service.ts +++ b/src/main/services/steam.service.ts @@ -8,7 +8,7 @@ import { getProcessId, isProcessRunning } from "main/helpers/os.helpers"; import { isElevated } from "query-process"; import { execOnOs } from "../helpers/env.helpers"; import { pathExists, pathExistsSync, readdir, writeFile } from "fs-extra"; -import { SteamShortcut, SteamShortcutKey } from "../../shared/models/steam/shortcut.model"; +import { SteamShortcut, SteamShortcutData } from "../../shared/models/steam/shortcut.model"; const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs"); @@ -174,9 +174,9 @@ export class SteamService { acc.push(path.join(configPath, entry.name)); } return acc; - }, []); + }, [] as string[]); }) - .catch(err => { + .catch((err): string[] => { log.error("Error while reading steam user data folders", err); return []; }); @@ -188,136 +188,20 @@ export class SteamService { return path.join(await this.getSteamPath(), "userdata", userId.toString(), "config", "shortcuts.vdf"); } - private async readShortcutsFile(shortcutsPath: string): Promise { - // Code taken from: https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts#Reading_the_shortcuts.vdf - - const rawData: string = await readFile(shortcutsPath, "utf-8"); - const startIndex = rawData.indexOf("\u0000shortcuts\u0000"); - if (startIndex < 0) { - console.error("Could not find shortcuts in shortcuts.vdf"); - return []; - } - const start = startIndex + "\u0000shortcuts\u0000".length; - const end = rawData.lastIndexOf("\u0008\u0008"); - if (end < 0 || end <= start) { - console.error("Could not find end of shortcuts in shortcuts.vdf"); - return []; - } - - const shortcutsString = rawData.substring(start, end - start); - - const result: SteamShortcut[] = []; - let currentShortcut: SteamShortcut | null = null; - let word = ""; - let key = ""; - let readingTags = false; - let tagId = -1; - - const shortcutKeysRegex = new RegExp(`(\u0001|\u0002)(${Object.values(SteamShortcutKey).join("|")})`, "i"); - - for (const c of shortcutsString) { - - if (c === "\u0000") { - if (word.endsWith(`\u0001${SteamShortcutKey.AppName}`)) { - if (currentShortcut) { - result.push(currentShortcut); - } - currentShortcut = { - AppName: "", - Exe: "", - StartDir: "", - icon: "", - LaunchOptions: "", - IsHidden: null, - tags: [], - }; - key = `\u0001${SteamShortcutKey.AppName}`; - } else if (shortcutKeysRegex.test(word)) { - key = word; - } else if (word === SteamShortcutKey.tags) { - readingTags = true; - } else if (key !== "") { - const currentKey = shortcutKeysRegex.exec(key).pop().replaceAll("\u0001", "").replaceAll("\u0002", "") as SteamShortcutKey; - if (currentShortcut && currentKey && currentKey !== SteamShortcutKey.tags) { - currentShortcut[currentKey] = (word as string & string[]); - } - key = ""; - } else if (readingTags) { - if (word.startsWith("\u0001")) { - tagId = parseInt(word.substring(1), 10); - } else if (tagId >= 0 && currentShortcut) { - currentShortcut.tags.push(word); - tagId = -1; - } else { - readingTags = false; - } - } - word = ""; - } else { - word += c; - } - } - - if (currentShortcut) { - result.push(currentShortcut); - } - - return result; - } - private async getShortcuts(userId: number): Promise { const shortcutsPath = await this.getShortcutsPath(userId); - return this.readShortcutsFile(shortcutsPath); - } + const shortcutsString = await readFile(shortcutsPath, { encoding: "utf-8" }); - private buildShortcutTagsString(tags: string[]): string { - let tagString = "\u0000tags\u0000"; - for (let i = 0; i < tags.length; i++) { - tagString += `\u0001${i}\u0000${tags[i]}\u0000`; - } - tagString += "\u0008"; - return tagString; - } - - private buildShortcutString(shortcut: SteamShortcut): string { - const getSeparator = (key: SteamShortcutKey): string => { - return key === SteamShortcutKey.IsHidden || key === SteamShortcutKey.appid ? "\u0002" : "\u0001"; - }; - - const getQuote = (key: SteamShortcutKey): string => { - return key === SteamShortcutKey.Exe || key === SteamShortcutKey.StartDir || key === SteamShortcutKey.icon ? "\"" : ""; - } - - let shortcutString = ""; - for (const key of Object.keys(shortcut)) { - console.log("KEY", key); - if(key === SteamShortcutKey.tags) { - continue; - } - - const value = shortcut[key as SteamShortcutKey]; - shortcutString += `${getSeparator(key as SteamShortcutKey)}${key}\u0000\"${value}\"\u0000`; - } - - shortcutString += `\u0000\u0000${this.buildShortcutTagsString(shortcut.tags)}`; - return shortcutString; + return SteamShortcut.parseShortcutsRawData(shortcutsString); } private async writeShortcuts(shortcuts: SteamShortcut[], userId: number): Promise { - let shortcutsString = "\u0000shortcuts\u0000"; - for (let i = 0; i < shortcuts.length; i++) { - shortcutsString += `\u0000${i}\u0000`; - shortcutsString += this.buildShortcutString(shortcuts[i]); - shortcutsString += "\u0008"; - } - shortcutsString += "\u0008\u0008"; - const shortcutsPath = await this.getShortcutsPath(userId); - await writeFile(shortcutsPath, shortcutsString, { encoding: "utf-8" }); - + const stringData = SteamShortcut.getShortcutsString(shortcuts); + await writeFile(shortcutsPath, stringData, { encoding: "utf-8" }); } - public async createShortcut(shortcutData: SteamShortcut, userId?: number): Promise { + public async createShortcut(shortcutData: SteamShortcutData, userId?: number): Promise { const userIds = userId ? [userId] : await (async (): Promise => { const folders = await this.getUserDataFolders(); return folders.map(folder => parseInt(path.basename(folder))); @@ -328,7 +212,8 @@ export class SteamService { log.warn("Error while reading shortcuts", e); return []; }); - shortcuts.push(shortcutData); + shortcuts.push(new SteamShortcut(shortcutData)); + await this.writeShortcuts(shortcuts, userId); } diff --git a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx index c8831752..0ec7966e 100644 --- a/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/create-launch-shortcut-modal.component.tsx @@ -103,10 +103,10 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean, {isSteamVersion && ( - +
setSteamShortcut(() => e)} /> - Créer un raccourci Steam + {t("modals.create-launch-shortcut.create-steam-shortcut")}
)} diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx index d54f0fa2..d42e184e 100644 --- a/src/renderer/pages/version-viewer.component.tsx +++ b/src/renderer/pages/version-viewer.component.tsx @@ -123,7 +123,7 @@ export function VersionViewer() { lastValueFrom(bsLauncher.createLaunchShortcut(data.launchOption, data.steamShortcut)).then(() => { notification.notifySuccess({ title: "notifications.create-launch-shortcut.success.title", - desc: "notifications.create-launch-shortcut.success.msg" + desc: `notifications.create-launch-shortcut.success.${data.steamShortcut ? "msg-steam" : "msg"}` }); }).catch(() => { notification.notifyError({ diff --git a/src/shared/models/steam/shortcut.model.ts b/src/shared/models/steam/shortcut.model.ts index c3561d26..e8930aea 100644 --- a/src/shared/models/steam/shortcut.model.ts +++ b/src/shared/models/steam/shortcut.model.ts @@ -1,4 +1,4 @@ -export enum SteamShortcutKey { +enum SteamShortcutKey { AppName = "AppName", Exe = "Exe", StartDir = "StartDir", @@ -16,13 +16,141 @@ export enum SteamShortcutKey { tags = "tags" } -type BaseShortcut = { +type BaseShortcutData = { [K in SteamShortcutKey]: K extends "tags" ? string[] : string }; -export interface SteamShortcut extends Partial { - // Mandatory fields +export interface SteamShortcutData extends Partial { + // Mandatory and specific values AppName: string; Exe: string; StartDir: string; + OpenVR?: "\x01" | "\x00"; +} + +export class SteamShortcut { + + public static parseShortcutsRawData(rawData: string): SteamShortcut[] { + // Code taken from: https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts#Reading_the_shortcuts.vdf + + const startIndex = rawData.indexOf("\u0000shortcuts\u0000"); + if (startIndex < 0) { + return []; + } + const start = startIndex + "\u0000shortcuts\u0000".length; + const end = rawData.lastIndexOf("\u0008\u0008"); + if (end < 0 || end <= start) { + return []; + } + + const shortcutsString = rawData.substring(start, end - start); + + const result: SteamShortcutData[] = []; + let currentShortcut: SteamShortcutData | null = null; + let word = ""; + let key = ""; + let readingTags = false; + let tagId = -1; + + const shortcutKeysRegex = new RegExp(`[\u0001\u0002](${Object.values(SteamShortcutKey).join("|")})`, "i"); + + for (const c of shortcutsString) { + + if (c === "\u0000") { + if (word.endsWith(`\u0001${SteamShortcutKey.AppName}`)) { + if (currentShortcut) { + result.push(currentShortcut); + } + currentShortcut = { + AppName: "", + Exe: "", + StartDir: "", + icon: "", + LaunchOptions: "", + IsHidden: "\x00", + }; + key = `\u0001${SteamShortcutKey.AppName}`; + } else if (shortcutKeysRegex.test(word)) { + key = word; + } else if (word === SteamShortcutKey.tags) { + readingTags = true; + } else if (key !== "") { + const currentKey = shortcutKeysRegex.exec(key).pop().replaceAll("\u0001", "").replaceAll("\u0002", "") as SteamShortcutKey; + if (currentShortcut && currentKey && currentKey !== SteamShortcutKey.tags) { + currentShortcut[currentKey] = word.replaceAll("\"", "") as string & ("\x01" | "\x00") // Make TS happy + } + key = ""; + } else if (readingTags) { + if (word.startsWith("\u0001")) { + tagId = parseInt(word.substring(1), 10); + } else if (tagId >= 0 && currentShortcut) { + currentShortcut.tags.push(word); + tagId = -1; + } else { + readingTags = false; + } + } + word = ""; + } else { + word += c; + } + } + + if (currentShortcut) { + result.push(currentShortcut); + } + + return result.map(shortcutData => new SteamShortcut(shortcutData)); + } + + public static getShortcutsString(shortcuts: SteamShortcut[]): string { + let shortcutsString = "\u0000shortcuts\u0000"; + for (let i = 0; i < shortcuts.length; i++) { + const shortcut = shortcuts[i]; + if(!shortcut.data?.AppName || !shortcut.data?.Exe || !shortcut.data?.StartDir) { + continue; + } + shortcutsString += `\u0000${i}\u0000`; + shortcutsString += shortcut.getStringBytes(); + } + shortcutsString += "\u0008\u0008"; + return shortcutsString; + } + + private data: SteamShortcutData; + + public constructor(shortcutData: SteamShortcutData) { + this.data = shortcutData; + } + + public getStringBytes(): string { + + const isHidden = this.data?.IsHidden === "\x01"; + const allowDesktopConfig = this.data?.AllowDesktopConfig === "\x01"; + const allowOverlay = this.data?.AllowDesktopConfig === "\x01"; + const openVR = this.data?.OpenVR === "\x01"; + const devkit = this.data?.Devkit === "\x01"; + + let strShortcut = ""; + + strShortcut += `\x02appid\x00${this.data?.appid || "\x00\x00\x00"}\x00`; + strShortcut += `\x01AppName\x00${this.data?.AppName ?? ""}\x00`; + strShortcut += `\x01Exe\x00\"${this.data?.Exe ?? ""}\"\x00`; + strShortcut += `\x01StartDir\x00\"${this.data?.StartDir ?? ""}\"\x00`; + strShortcut += `\x01icon\x00${this.data?.icon ?? ""}\x00`; + strShortcut += `\x01ShortcutPath\x00\x00`; + strShortcut += `\x01LaunchOptions\x00${this.data?.LaunchOptions ?? ""}\x00`; + strShortcut += `\x02IsHidden\x00${isHidden ? "\x01" : "\x00"}\x00\x00\x00`; + strShortcut += `\x02AllowDesktopConfig\x00${allowDesktopConfig ? "\x01" : "\x00"}\x00\x00\x00`; + strShortcut += `\x02AllowOverlay\x00${allowOverlay ? "\x01" : "\x00"}\x00\x00\x00`; + strShortcut += `\x02OpenVR\x00${openVR ? "\x01" : "\x00"}\x00\x00\x00`; + strShortcut += `\x02Devkit\x00${devkit ? "\x01" : "\x00"}\x00\x00\x00`; + strShortcut += `\x01DevkitGameID\x00${this.data?.DevkitGameID ?? ""}\x00`; + strShortcut += `\x02DevkitOverrideAppID\x00\x00\x00\x00\x00`; + strShortcut += `\x02LastPlayTime\x00\x00\x00\x00\x00`; + strShortcut += `\x00tags\x00${""}\x08\x08`; + + return strShortcut; + } + }