[feature] we can now create launch shortcut in steam library

This commit is contained in:
MathieuG-P
2025-01-06 17:24:36 +01:00
parent 774c3a2954
commit 2a99c76d09
15 changed files with 210 additions and 182 deletions
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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に接続する",
+10 -7
View File
@@ -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에 연결하기",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
+5 -2
View File
@@ -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",
@@ -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<SteamShortcut>{
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<boolean>{
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;
});
+10 -125
View File
@@ -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<SteamShortcut[]> {
// 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<SteamShortcut[]> {
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<void> {
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<void> {
public async createShortcut(shortcutData: SteamShortcutData, userId?: number): Promise<void> {
const userIds = userId ? [userId] : await (async (): Promise<number[]> => {
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);
}
@@ -103,10 +103,10 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
</div>
</div>
{isSteamVersion && (
<Tippy placement="right" theme="default" content={"Si activé, au lieu de créer un raccourci sur le bureau, celui-ci sera créé dans Steam."}>
<Tippy placement="right" theme="default" content={t("modals.create-launch-shortcut.steam-shortcut-tippy")}>
<div className="h-full flex items-center gap-1.5 mt-3 mb-4 w-fit pr-1">
<BsmCheckbox className="h-5 aspect-square relative z-[1]" checked={steamShortcut} onChange={e => setSteamShortcut(() => e)} />
<span>Créer un raccourci Steam</span>
<span>{t("modals.create-launch-shortcut.create-steam-shortcut")}</span>
</div>
</Tippy>
)}
@@ -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({
+132 -4
View File
@@ -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<BaseShortcut> {
// Mandatory fields
export interface SteamShortcutData extends Partial<BaseShortcutData> {
// 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;
}
}