Lets apps be added here

By specifying some flags after the "toCode," one can build a shortcuts file from scratch.

Great for automated tools.
This commit is contained in:
William Hubbard
2022-02-06 01:54:18 +00:00
committed by GitHub
parent 5f6246d226
commit 7b98c599e5

View File

@@ -43,25 +43,26 @@ class App:
devkit : bool devkit : bool
devkitId: str devkitId: str
playTime: long playTime: long
The date that the application was last launched. The date that the application was last launched, in seconds since January 1, 1970 Universal Time
tags: str
""" """
appId = 0 appIndx = 0
name = "" appId = 0 # appid
executable = "" name = "" # AppName
start = "" executable = "" # Exe
icon = "" start = "" # StartDir
shortcut = "" icon = "" # icon
options = "" shortcut = "" # ShortcutPath
hidden = False options = "" # LaunchOptions
desktopConfig = False hidden = False # IsHidden
overlay = False desktopConfig = True # AllowDesktopConfig
vr = False overlay = True # AllowOverlay
devkit = False vr = False # OpenVR
devkitId = "" devkit = False # Devkit
playTime = 0 devkitId = "" # DevkitGameID
tags = "null" devkitOverride = False # DevkitOverrideAppID
playTime = 0 # LastPlayTime
#tags = ""
def __init__(self, appId): def __init__(self, appId):
self.appId = appId self.appId = appId
@@ -99,9 +100,9 @@ class App:
return False return False
if self.devkitId != other.devkitId: if self.devkitId != other.devkitId:
return False return False
if self.playTime != other.playTime: if self.devkitOverride != other.devkitOverride:
return False return False
if self.tags != other.tags: if self.playTime != other.playTime:
return False return False
return True return True
@@ -128,144 +129,163 @@ def parseShortcuts(shortcutFile):
rawData = shortcutData.read() rawData = shortcutData.read()
i = 0 i = 0
buffer = bytearray() buffer = bytearray()
appIndx = 0
app = None app = None
while i < len(rawData): while i < len(rawData):
buffer.append(rawData[i]) buffer.append(rawData[i])
try: if rawData[i] == 0 and len(buffer) > 1 or rawData[i] == 8:
buffCont = buffer.decode() try:
except UnicodeDecodeError: buffCont = buffer.decode()
#print(rawData[:i]) buffCont = buffCont[:-1]
#print("Clearing buffer %s after %d" % (buffer[:-1].decode(), buffer[-1])) except UnicodeDecodeError:
buffer.clear() #print(rawData[:i])
buffCont = "" #print("Clearing buffer %s after %d" % (buffer[:-1].decode(), buffer[-1]))
found = True buffer.clear()
if buffCont.endswith("shortcuts"): buffCont = ""
buffer.clear() found = True
i += 2 try:
while rawData[i] != 2: if buffCont.endswith("shortcuts") or rawData[i] == 8:
buffer.append(rawData[i]) buffer.clear()
i += 1 i += 2
data.append({ if rawData[i] != 8:
"shortcuts": buffer[:-1].decode() # We are done here if it is 8
}) while rawData[i] != 2:
if buffCont.endswith("appid"): buffer.append(rawData[i])
buffer.clear() i += 1
i += 2 appIndx = int.from_bytes(buffer, "little")
# Some app names do have a 1 for one of their bits elif buffCont.endswith("appid"):
while rawData[i + 1:i + 8] != bytearray("AppName", "utf-8"): buffer.clear()
buffer.append(rawData[i]) i += 1
i += 1 # Some app names do have a 1 for one of their bits
app = App(int.from_bytes(buffer, "little")) while rawData[i] != 1:
data.append(app) buffer.append(rawData[i])
elif buffCont.endswith("AppName"): i += 1
buffer.clear() app = App(int.from_bytes(buffer, "little"))
i += 2 app.appIndx = appIndx
while rawData[i + 1:i + 4] != bytearray("Exe", "utf-8"): appIndx = 0
buffer.append(rawData[i]) data.append(app)
i += 1 elif buffCont.endswith("AppName"):
app.name = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("Exe"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i + 1:i + 9] != bytearray("StartDir", "utf-8"): i += 1
buffer.append(rawData[i]) app.name = buffer[:-1].decode()
i += 1 elif buffCont.endswith("Exe"):
app.executable = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("StartDir"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i] != 0: i += 1
buffer.append(rawData[i]) app.executable = buffer[:-1].decode()
i += 1 elif buffCont.endswith("StartDir"):
app.start = buffer.decode() buffer.clear()
elif buffCont.endswith("icon"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i + 1:i + 13] != bytearray("ShortcutPath", "utf-8"): i += 1
buffer.append(rawData[i]) app.start = buffer[:-1].decode()
i += 1 elif buffCont.endswith("icon"):
app.icon = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("ShortcutPath"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i + 1:i + 14] != bytearray("LaunchOptions", "utf-8"): i += 1
buffer.append(rawData[i]) app.icon = buffer[:-1].decode()
i += 1 elif buffCont.endswith("ShortcutPath"):
app.shortcut = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("LaunchOptions"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i + 1:i + 9] != bytearray("IsHidden", "utf-8"): i += 1
buffer.append(rawData[i]) app.shortcut = buffer[:-1].decode()
i += 1 elif buffCont.endswith("LaunchOptions"):
app.options = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("IsHidden"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 3: i += 1
buffer.append(rawData[i]) app.options = buffer[:-1].decode()
i += 1 elif buffCont.endswith("IsHidden"):
app.hidden = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("AllowDesktopConfig"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 3: i += 1
buffer.append(rawData[i]) app.hidden = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("AllowDesktopConfig"):
app.desktopConfig = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("AllowOverlay"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 3: i += 1
buffer.append(rawData[i]) app.desktopConfig = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("AllowOverlay"):
app.overlay = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("OpenVR"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 3: i += 1
buffer.append(rawData[i]) app.overlay = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("OpenVR"):
app.vr = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("Devkit") and type(app.devkit) == bool: i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 3: i += 1
buffer.append(rawData[i]) app.vr = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("Devkit"):
app.devkit = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("DevkitGameID"): i += 1
buffer.clear() while rawData[i] != 1:
i += 2 buffer.append(rawData[i])
while rawData[i + 1:i + 13] != bytearray("LastPlayTime", "utf-8"): i += 1
buffer.append(rawData[i]) app.devkit = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("DevkitGameID"):
app.devkitId = buffer[:-1].decode() buffer.clear()
elif buffCont.endswith("LastPlayTime"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while len(buffer) < 4: i += 1
buffer.append(rawData[i]) app.devkitId = buffer[:-1].decode()
i += 1 elif buffCont.endswith("DevkitOverrideAppID"):
app.playTime = int.from_bytes(buffer, "little") buffer.clear()
elif buffCont.endswith("tags"): i += 1
buffer.clear() while rawData[i] != 2:
i += 2 buffer.append(rawData[i])
while i < len(rawData) and rawData[i] != 2: i += 1
buffer.append(rawData[i]) app.devkitOverride = bool.from_bytes(buffer, "little")
i += 1 elif buffCont.endswith("LastPlayTime"):
if i < len(rawData) and rawData[i] == 2: buffer.clear()
buffer = buffer[:-1] i += 1
app.tags = buffer.decode() while rawData[i] != 116: # The TAGS thing seems to delimit this
else: buffer.append(rawData[i])
found = False i += 1
app.playTime = int.from_bytes(buffer, "little")
i -= 1
elif buffCont.endswith("tags"):
buffer.clear()
i += 1
while rawData[i] != 8:
buffer.append(rawData[i])
i += 1
#app.tags = buffer.decode()
else:
found = False
buffer.clear()
except:
print("Exception when parsing!", file=sys.stderr)
print("Buffer data: %s + %d" % (buffCont, len(buffer)), file=sys.stderr)
print("Index %d / %d" % (i, len(rawData)), file=sys.stderr)
raise
#print('\r' + buffCont, end='') #print('\r' + buffCont, end='')
if found: if found:
#try: #try:
#print(": %s" % buffer.decode()) #print(": %s" % buffer.decode())
#except UnicodeDecodeError: #except UnicodeDecodeError:
#print(": %s" % int.from_bytes(buffer, "little")) #print(": %s" % int.from_bytes(buffer, "little"))
buffer.clear() buffer.clear()
i += 1 i += 1
return data return data
@@ -286,85 +306,103 @@ def writeShortcuts(apps, shortcutFile):
encoding = "utf-8" encoding = "utf-8"
data = bytearray([0x00]) data = bytearray([0x00])
data += bytearray("shortcuts", encoding)
data.append(0x00);
i = 1
for app in apps: for app in apps:
if type(app) == dict: data.append(0x00)
data += bytearray("shortcuts", encoding) data.append(app.appIndx or i)
data.append(0x00) i += 1
data += bytearray(apps[0]["shortcuts"], encoding) data += bytearray([0x00, 0x02])
else: data += bytearray("appid", encoding)
data.append(0x00) data.append(0x00)
data.append(0x02) data += int(app.appId).to_bytes(4, "little")
data += bytearray("appid", encoding) data.append(0x01)
data.append(0x00) data += bytearray("AppName", encoding)
data += int(app.appId).to_bytes(4, "little") data.append(0x00)
data.append(0x01) data += bytearray(app.name, encoding)
data += bytearray("AppName", encoding) data.append(0x00)
data.append(0x00) data.append(0x01)
data += bytearray(app.name, encoding) data += bytearray("Exe", encoding)
data.append(0x00) data.append(0x00)
data.append(0x01) path = app.executable
data += bytearray("Exe", encoding) if not path is str:
data.append(0x00) path = str(path)
data += bytearray(app.executable, encoding) if path[0] != '"':
data.append(0x00) # Wrap the path in quotes as needed
data.append(0x01) data += bytearray("\"", encoding)
data += bytearray("StartDir", encoding) data += bytearray(path, encoding)
data.append(0x00) if path[-1] != '"':
data += bytearray(app.start, encoding) data += bytearray("\"", encoding)
data.append(0x00) data.append(0x00)
data.append(0x01) data.append(0x01)
data += bytearray("icon", encoding) data += bytearray("StartDir", encoding)
data.append(0x00) data.append(0x00)
data += bytearray(app.icon, encoding) path = app.start
data.append(0x00) if not path is str:
data.append(0x01) path = str(path)
data += bytearray("ShortcutPath", encoding) if path[0] != '"':
data.append(0x00) data += bytearray("\"", encoding)
data += bytearray(app.shortcut, encoding) data += bytearray(app.start, encoding)
data.append(0x00) if path[-1] != '"':
data.append(0x01) data += bytearray("\"", encoding)
data += bytearray("LaunchOptions", encoding) data.append(0x00)
data.append(0x00) data.append(0x01)
data += bytearray(app.options, encoding) data += bytearray("icon", encoding)
data.append(0x00) data.append(0x00)
data.append(0x02) data += bytearray(app.icon, encoding)
data += bytearray("IsHidden", encoding) data.append(0x00)
data.append(0x00) data.append(0x01)
data += int(app.hidden).to_bytes(3, "little") data += bytearray("ShortcutPath", encoding)
data.append(0x00) data.append(0x00)
data.append(0x02) data += bytearray(app.shortcut, encoding)
data += bytearray("AllowDesktopConfig", encoding) data.append(0x00)
data.append(0x00) data.append(0x01)
data += int(app.desktopConfig).to_bytes(3, "little") data += bytearray("LaunchOptions", encoding)
data.append(0x00) data.append(0x00)
data.append(0x02) data += bytearray(app.options, encoding)
data += bytearray("AllowOverlay", encoding) data.append(0x00)
data.append(0x00) data.append(0x02)
data += int(app.overlay).to_bytes(3, "little") data += bytearray("IsHidden", encoding)
data.append(0x00) data.append(0x00)
data.append(0x02) data += bool(app.hidden).to_bytes(4, "little")
data += bytearray("OpenVR", encoding) data.append(0x02)
data.append(0x00) data += bytearray("AllowDesktopConfig", encoding)
data += int(app.vr).to_bytes(3, "little") data.append(0x00)
data.append(0x00) data += bool(app.desktopConfig).to_bytes(4, "little")
data.append(0x02) data.append(0x02)
data += bytearray("Devkit", encoding) data += bytearray("AllowOverlay", encoding)
data.append(0x00) data.append(0x00)
data += int(app.devkit).to_bytes(3, "little") data += bool(app.overlay).to_bytes(4, "little")
data.append(0x00) data.append(0x02)
data.append(0x01) data += bytearray("OpenVR", encoding)
data += bytearray("DevkitGameID", encoding) data.append(0x00)
data.append(0x00) data += bool(app.vr).to_bytes(4, "little")
data += bytearray(app.devkitId, encoding) data.append(0x02)
data.append(0x00) data += bytearray("Devkit", encoding)
data.append(0x02) data.append(0x00)
data += bytearray("LastPlayTime", encoding) data += bool(app.devkit).to_bytes(4, "little")
data.append(0x00) data.append(0x01)
data += int(app.playTime).to_bytes(4, "little") data += bytearray("DevkitGameID", encoding)
data.append(0x00) data.append(0x00)
data += bytearray("tags", encoding) data += bytearray(app.devkitId, encoding)
data.append(0x00) data.append(0x00)
data += bytearray(app.tags, encoding) data.append(0x02)
data += bytearray("DevkitOverrideAppID", encoding)
data.append(0x00)
data += bool(app.devkitOverride).to_bytes(4, "little")
data.append(0x02)
data += bytearray("LastPlayTime", encoding)
data.append(0x00)
data += int(app.playTime).to_bytes(4, "little")
data.append(0x00)
data += bytearray("tags", encoding)
data.append(0x00)
data.append(0x08)
data.append(0x08)
#data += bytearray(app.tags, encoding)
data.append(0x08)
data.append(0x08)
with open(shortcutFile, 'wb') as stream: with open(shortcutFile, 'wb') as stream:
stream.write(data) stream.write(data)
@@ -528,20 +566,25 @@ def copyContents(ids):
ids : list ids : list
A two-dimensional list of numbers, where the first index of each A two-dimensional list of numbers, where the first index of each
sub-list contains the app to download information from, and the second sub-list contains the app to download information from, and the second
is the ID of the app to store that information under. is the ID of the app to store that information under, or App objects
""" """
client = getSteamArgs() client = getSteamArgs()
fromCodes = [] fromCodes = []
toCodes = [] toCodes = []
reverse = {} reverse = {}
apps = {}
for id in ids: for id in ids:
# Get the mappings # Get the mappings
fromCode = id[0] fromCode = id[0]
toCode = id[1] toCode = id[1]
if isinstance(toCode, App):
apps[toCode.appId] = toCode
toCode = toCode.appId
reverse[toCode] = fromCode reverse[toCode] = fromCode
fromCodes.append(fromCode) fromCodes.append(fromCode)
toCodes.append(toCode) toCodes.append(toCode)
print("Copying %s to %s" % (fromCode, toCode))
# Copy the appropriate images from online # Copy the appropriate images from online
copyFile(getHeader(str(fromCode)), str(toCode)) copyFile(getHeader(str(fromCode)), str(toCode))
copyFile(getLibraryTall(str(fromCode)), str(toCode)) copyFile(getLibraryTall(str(fromCode)), str(toCode))
@@ -562,8 +605,11 @@ def copyContents(ids):
"nHeightPct": logo["height_pct"] "nHeightPct": logo["height_pct"]
} }
} }
toCode = ids[i][1]
if isinstance(toCode, App):
toCode = toCode.appId
# Stores the logo positioning to file. # Stores the logo positioning to file.
copyContent(Path("config", "grid", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False) copyContent(Path("config", "grid", str(toCode) + ".json"), json.dumps(logo2), byte=False)
# Generate the data for other stuff. # Generate the data for other stuff.
logo2 = [[ logo2 = [[
@@ -584,19 +630,38 @@ def copyContents(ids):
} }
] ]
] ]
copyContent(Path("config", "librarycache", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False) copyContent(Path("config", "librarycache", str(toCode) + ".json"), json.dumps(logo2), byte=False)
# Write the actual shortcut data. # Write the actual shortcut data.
for user in userdata.iterdir(): for user in userdata.iterdir():
shortcutData = parseShortcuts(Path(user, "config", "shortcuts.vdf")) shortcutPath = Path(user, "config", "shortcuts.vdf")
shortcutData = parseShortcuts(shortcutPath)
toWrite = []
for app in apps:
toWrite.append(apps[app])
for app in shortcutData: for app in shortcutData:
if type(app) != dict: # Add existing apps back to the queue
if app.appId in toCodes: if app.appId in apps:
print("Setting shortcut data for %s", app.name) eApp = apps[app.appId]
app.name = data["apps"][reverse[app.appId]]["common"]["name"] eApp.appIndx = eApp.appIndx or app.appIndx
app.icon = str(Path(user, "config", "grid", str(app.appId) + ".jpg")) eApp.name = eApp.name.strip() or app.name.strip()
print("Setting %s to %s" % (app.name, app.icon)) eApp.executable = eApp.executable.strip() or app.executable.strip()
writeShortcuts(shortcutData, Path(user, "config", "shortcuts.vdf")) eApp.start = eApp.start.strip() or app.start.strip()
eApp.icon = eApp.icon.strip() or str(Path(user, "config", "grid", str(eApp.appId) + ".jpg")) or app.icon.strip()
eApp.shortcut = eApp.shortcut.strip() or app.shortcut.strip()
eApp.options = eApp.options.strip() or app.options.strip()
eApp.hidden = eApp.hidden or app.hidden
# The default is true. This sets it to whichever is false, if any
eApp.desktopConfig = not (not eApp.desktopConfig or not app.desktopConfig)
eApp.overlay = not (not eApp.overlay or not app.overlay)
eApp.vr = eApp.vr or app.vr
eApp.devKit = eApp.devkit or app.devkit
eApp.devKitId = eApp.devkitId or app.devkitId
eApp.devKitOverride = eApp.devkitOverride or app.devkitOverride
eApp.playTime = eApp.playTime or app.playTime
else:
toWrite.append(app)
writeShortcuts(toWrite, shortcutPath)
def printHelp(): def printHelp():
print("""%s [OPTIONS] (<fromId> <toId>)... print("""%s [OPTIONS] (<fromId> <toId>)...
@@ -620,6 +685,16 @@ term
OPTIONS OPTIONS
-h, --help -h, --help
Print this message Print this message
--name
The name a new app will be assigned.
--executable
The link to the application executable.
--start
The path the application will start in.
--options
Command-line options for when launching the executable.
--vr
Whether or not the app is VR enabled.
-a -a
Prints information on all installed apps. Prints information on all installed apps.
-l -l
@@ -662,6 +737,41 @@ if __name__ == "__main__":
opArgs[ops[-1]] = opArgs[ops[-1]][1:-1] opArgs[ops[-1]] = opArgs[ops[-1]][1:-1]
else: else:
ops.append(term[2:]) ops.append(term[2:])
if ops[-1] == "name" or ops[-1] == "executable" or ops[-1] == "icon" or ops[-1] == "start" or ops[-1] == "shortcut" or ops[-1] == "options" or ops[-1] == "hidden" or ops[-1] == "desktopConfig" or ops[-1] == "overlay" or ops[-1] == "vr" or ops[-1] == "devkit" or ops[-1] == "devkitId" or ops[-1] == "playTime" or ops[-1] == "tags":
if ids and len(ids) > 0 and type(ids[-1]) == tuple and (isinstance(ids[-1][1], App) or str(ids[-1][1]) == keys[-1]):
# If the previous key was the toCode for an app...
if type(ids[-1][1]) == int:
# Convert it to an app
ids[-1] = (ids[-1][0], App(ids[-1][1]))
if ops[-1] == "name":
ids[-1][1].name = opArgs[ops[-1]]
elif ops[-1] == "executable":
ids[-1][1].executable = "\"" + str(Path(opArgs[ops[-1]]).resolve()) + "\""
elif ops[-1] == "start":
ids[-1][1].start = "\"" + str(Path(opArgs[ops[-1]]).resolve()) + "\""
elif ops[-1] == "icon":
ids[-1][1].icon = opArgs[ops[-1]]
elif ops[-1] == "shortcut":
ids[-1][1].shortcut = opArgs[ops[-1]]
elif ops[-1] == "options":
ids[-1][1].options = opArgs[ops[-1]]
elif ops[-1] == "hidden":
ids[-1][1].hidden = bool(opArgs[ops[-1]])
elif ops[-1] == "desktopConfig":
ids[-1][1].desktopConfig = bool(opArgs[ops[-1]])
elif ops[-1] == "overlay":
ids[-1][1].overlay = bool(opArgs[ops[-1]])
elif ops[-1] == "vr":
ids[-1][1].vr = bool(opArgs[ops[-1]])
elif ops[-1] == "devkit":
ids[-1][1].devkit = bool(opArgs[ops[-1]])
elif ops[-1] == "devkitId":
ids[-1][1].devkitId = opArgs[ops[-1]]
elif ops[-1] == "playTime":
ids[-1][1].playTime = int(opArgs[ops[-1]])
else:
print("App definition options must follow a toCode (%s followed %s, last id was %s)" % (term, keys[-1], ids[-1]), file=sys.stderr)
exit(1)
else: else:
for op in term[1:]: for op in term[1:]:
ops.append(op) ops.append(op)
@@ -677,6 +787,13 @@ if __name__ == "__main__":
else: else:
print("\t%s" % op) print("\t%s" % op)
print() print()
print("IDs:")
for appId in ids:
if type(appId) == int:
print("\t%d" % (appId))
else:
print("\t%s" % (appId,))
print()
if "h" in ops or "help" in ops: if "h" in ops or "help" in ops:
printHelp() printHelp()
@@ -757,11 +874,11 @@ if __name__ == "__main__":
print("\tIcon: %s" % app.icon) print("\tIcon: %s" % app.icon)
print("\tShortcut: %s" % app.shortcut) print("\tShortcut: %s" % app.shortcut)
print("\tRuntime Options: %s" % app.options) print("\tRuntime Options: %s" % app.options)
print("\tIs Hidden: %s" % bool(app.hidden)) print("\tIs Hidden: %s" % app.hidden)
print("\tDesktop Configuration: %s" % bool(app.desktopConfig)) print("\tDesktop Configuration: %s" % app.desktopConfig)
print("\tOverlay Enabled: %s" % bool(app.overlay)) print("\tOverlay Enabled: %s" % app.overlay)
print("\tIs VR: %s" % bool(app.vr)) print("\tIs VR: %s" % app.vr)
print("\tDevkit: %d" % app.devkit) print("\tDevkit: %s" % app.devkit)
print("\tDevkit ID: %s" % app.devkitId) print("\tDevkit ID: %s" % app.devkitId)
print("\tDevkit Override App ID: %s" % app.devkitOverride)
print("\tDate of last Play: %s" % app.playTime) print("\tDate of last Play: %s" % app.playTime)
print("\tTags: %s" % app.tags)