Files
Steam-Local-Game-Importer/loadSteamInfo
2021-09-14 21:21:42 +00:00

499 lines
18 KiB
Python

#!/usr/bin/env python
from steamctl.clients import CachingSteamClient
import sys
import json
import requests
from pathlib import Path
from argparse import Namespace
from io import StringIO
steamClient = False
class App:
appId = 0
name = ""
executable = ""
start = ""
icon = ""
shortcut = ""
options = ""
hidden = False
desktopConfig = False
overlay = False
vr = False
devkit = False
devkitId = ""
playTime = 0
tags = "null"
def __init__(self, appId):
self.appId = appId
def __repr__(self):
return "App %s (%s at %s)" % (self.appId, self.name, self.executable)
def __hash__(self):
return self.appId
def __eq__(self, other):
if self.appId != other.appId:
return False
if self.name != other.name:
return False
if self.executable != other.executable:
return False
if self.start != other.start:
return False
if self.icon != other.icon:
return False
if self.shortcut != other.shortcut:
return False
if self.options != other.options:
return False
if self.hidden != other.hidden:
return False
if self.desktopConfig != other.desktopConfig:
return False
if self.overlay != other.overlay:
return False
if self.vr != other.vr:
return False
if self.devkit != other.devkit:
return False
if self.devkitId != other.devkitId:
return False
if self.playTime != other.playTime:
return False
if self.tags != other.tags:
return False
return True
def parseShortcuts(shortcutFile):
data = []
with open(shortcutFile, 'rb') as shortcutData:
rawData = shortcutData.read()
i = 0
buffer = bytearray()
app = None
while i < len(rawData):
buffer.append(rawData[i])
try:
buffCont = buffer.decode()
except UnicodeDecodeError:
#print(rawData[:i])
#print("Clearing buffer %s after %d" % (buffer[:-1].decode(), buffer[-1]))
buffer.clear()
buffCont = ""
found = True
if buffCont.endswith("shortcuts"):
buffer.clear()
i += 2
while rawData[i] != 2:
buffer.append(rawData[i])
i += 1
data.append({
"shortcuts": buffer[:-1].decode()
})
if buffCont.endswith("appid"):
buffer.clear()
i += 2
# Some app names do have a 1 for one of their bits
while rawData[i + 1:i + 8] != bytearray("AppName", "utf-8"):
buffer.append(rawData[i])
i += 1
app = App(int.from_bytes(buffer, "little"))
data.append(app)
elif buffCont.endswith("AppName"):
buffer.clear()
i += 2
while rawData[i + 1:i + 4] != bytearray("Exe", "utf-8"):
buffer.append(rawData[i])
i += 1
app.name = buffer[:-1].decode()
elif buffCont.endswith("Exe"):
buffer.clear()
i += 2
while rawData[i + 1:i + 9] != bytearray("StartDir", "utf-8"):
buffer.append(rawData[i])
i += 1
app.executable = buffer[:-1].decode()
elif buffCont.endswith("StartDir"):
buffer.clear()
i += 2
while rawData[i] != 0:
buffer.append(rawData[i])
i += 1
app.start = buffer.decode()
elif buffCont.endswith("icon"):
buffer.clear()
i += 2
while rawData[i + 1:i + 13] != bytearray("ShortcutPath", "utf-8"):
buffer.append(rawData[i])
i += 1
app.icon = buffer[:-1].decode()
elif buffCont.endswith("ShortcutPath"):
buffer.clear()
i += 2
while rawData[i + 1:i + 14] != bytearray("LaunchOptions", "utf-8"):
buffer.append(rawData[i])
i += 1
app.shortcut = buffer[:-1].decode()
elif buffCont.endswith("LaunchOptions"):
buffer.clear()
i += 2
while rawData[i + 1:i + 9] != bytearray("IsHidden", "utf-8"):
buffer.append(rawData[i])
i += 1
app.options = buffer[:-1].decode()
elif buffCont.endswith("IsHidden"):
buffer.clear()
i += 2
while len(buffer) < 3:
buffer.append(rawData[i])
i += 1
app.hidden = int.from_bytes(buffer, "little")
elif buffCont.endswith("AllowDesktopConfig"):
buffer.clear()
i += 2
while len(buffer) < 3:
buffer.append(rawData[i])
i += 1
app.desktopConfig = int.from_bytes(buffer, "little")
elif buffCont.endswith("AllowOverlay"):
buffer.clear()
i += 2
while len(buffer) < 3:
buffer.append(rawData[i])
i += 1
app.overlay = int.from_bytes(buffer, "little")
elif buffCont.endswith("OpenVR"):
buffer.clear()
i += 2
while len(buffer) < 3:
buffer.append(rawData[i])
i += 1
app.vr = int.from_bytes(buffer, "little")
elif buffCont.endswith("Devkit") and type(app.devkit) == bool:
buffer.clear()
i += 2
while len(buffer) < 3:
buffer.append(rawData[i])
i += 1
app.devkit = int.from_bytes(buffer, "little")
elif buffCont.endswith("DevkitGameID"):
buffer.clear()
i += 2
while rawData[i + 1:i + 13] != bytearray("LastPlayTime", "utf-8"):
buffer.append(rawData[i])
i += 1
app.devkitId = buffer[:-1].decode()
elif buffCont.endswith("LastPlayTime"):
buffer.clear()
i += 2
while len(buffer) < 4:
buffer.append(rawData[i])
i += 1
app.playTime = int.from_bytes(buffer, "little")
elif buffCont.endswith("tags"):
buffer.clear()
i += 2
while i < len(rawData) and rawData[i] != 2:
buffer.append(rawData[i])
i += 1
if i < len(rawData) and rawData[i] == 2:
buffer = buffer[:-1]
app.tags = buffer.decode()
else:
found = False
#print('\r' + buffCont, end='')
if found:
#try:
#print(": %s" % buffer.decode())
#except UnicodeDecodeError:
#print(": %s" % int.from_bytes(buffer, "little"))
buffer.clear()
i += 1
return data
def writeShortcuts(apps, shortcutFile):
encoding = "utf-8"
data = bytearray([0x00])
for app in apps:
if type(app) == dict:
data += bytearray("shortcuts", encoding)
data.append(0x00)
data += bytearray(apps[0]["shortcuts"], encoding)
else:
data.append(0x00)
data.append(0x02)
data += bytearray("appid", encoding)
data.append(0x00)
data += int(app.appId).to_bytes(4, "little")
data.append(0x01)
data += bytearray("AppName", encoding)
data.append(0x00)
data += bytearray(app.name, encoding)
data.append(0x00)
data.append(0x01)
data += bytearray("Exe", encoding)
data.append(0x00)
data += bytearray(app.executable, encoding)
data.append(0x00)
data.append(0x01)
data += bytearray("StartDir", encoding)
data.append(0x00)
data += bytearray(app.start, encoding)
data.append(0x00)
data.append(0x01)
data += bytearray("icon", encoding)
data.append(0x00)
data += bytearray(app.icon, encoding)
data.append(0x00)
data.append(0x01)
data += bytearray("ShortcutPath", encoding)
data.append(0x00)
data += bytearray(app.shortcut, encoding)
data.append(0x00)
data.append(0x01)
data += bytearray("LaunchOptions", encoding)
data.append(0x00)
data += bytearray(app.options, encoding)
data.append(0x00)
data.append(0x02)
data += bytearray("IsHidden", encoding)
data.append(0x00)
data += int(app.hidden).to_bytes(3, "little")
data.append(0x00)
data.append(0x02)
data += bytearray("AllowDesktopConfig", encoding)
data.append(0x00)
data += int(app.desktopConfig).to_bytes(3, "little")
data.append(0x00)
data.append(0x02)
data += bytearray("AllowOverlay", encoding)
data.append(0x00)
data += int(app.overlay).to_bytes(3, "little")
data.append(0x00)
data.append(0x02)
data += bytearray("OpenVR", encoding)
data.append(0x00)
data += int(app.vr).to_bytes(3, "little")
data.append(0x00)
data.append(0x02)
data += bytearray("Devkit", encoding)
data.append(0x00)
data += int(app.devkit).to_bytes(3, "little")
data.append(0x00)
data.append(0x01)
data += bytearray("DevkitGameID", encoding)
data.append(0x00)
data += bytearray(app.devkitId, encoding)
data.append(0x00)
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 += bytearray(app.tags, encoding)
with open(shortcutFile, 'wb') as stream:
stream.write(data)
def getSteamArgs():
global steamClient
if not steamClient:
args = Namespace()
args.user = None
args.anonymous = True
args.skip_licenses = True
steamClient = CachingSteamClient()
print("Logging into steam")
steamClient.login_from_args(args)
return steamClient
def getHeader(code):
return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/header.jpg" % code
def getLibraryTall(code):
return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/library_600x900.jpg" % code
def getBackground(code):
return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/library_hero.jpg" % code
def getLogo(code):
return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/logo.png" % code
def getGameInfo(code):
fromContent = requests.get(fromURL, allow_redirects=True)
return "https://cdn.cloudflare.steamstatic.com/steam/apps/%s/logo.png" % code
def copyContent(name, content, byte=True):
userdata = Path(Path.home(), ".steam", "root", "userdata")
for user in userdata.iterdir():
toFile = Path(user, name)
print("Writing to %s" % (toFile))
mode = 'w'
if byte:
mode += 'b'
with open(toFile, mode) as stream:
stream.write(content)
def copyFile(fromURL, toCode):
fromURLComp = fromURL.split('/')
fromName = fromURLComp[6]
fromCode = fromURLComp[5]
userdata = Path(Path.home(), ".steam", "root", "userdata")
# Gets the final file name
if fromName == "library_hero.jpg":
toName = "_hero.jpg"
elif fromName == "library_600x900.jpg":
toName = "p.jpg"
elif fromName == "header.jpg":
toName = ".jpg"
else:
toName = "_" + fromName
toName = toCode + toName
print("Copying %s (%s) to %s" % (fromName, fromCode, toName))
fromContent = requests.get(fromURL, allow_redirects=True)
copyContent(Path("config", "grid", toName), fromContent.content)
def copyContents(ids):
client = getSteamArgs()
fromCodes = []
toCodes = []
reverse = {}
for id in ids:
fromCode = id[0]
toCode = id[1]
reverse[toCode] = fromCode
fromCodes.append(fromCode)
toCodes.append(toCode)
copyFile(getHeader(str(fromCode)), str(toCode))
copyFile(getLibraryTall(str(fromCode)), str(toCode))
copyFile(getBackground(str(fromCode)), str(toCode))
copyFile(getLogo(str(fromCode)), str(toCode))
data = client.get_product_info(apps=fromCodes)
i = 0
for i in range(len(ids)):
logo = data["apps"][ids[i][0]]["common"]["library_assets"]["logo_position"]
logo2 = {
"nversion": 1,
"logoPosition": {
"pinnedPosition": logo["pinned_position"],
"nWidthPct": logo["width_pct"],
"nHeightPct": logo["height_pct"]
}
}
copyContent(Path("config", "grid", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False)
logo2 = [[
"achievements", {
"version":2,
"data": {
"vecHighlight": [],
"vecUnachieved": [],
"vecAchievedHidden": [],
"nTotal": 0,
"nAchieved": 0
}
}
], [
"customimage", {
"version": 1,
"data": logo2
}
]
]
copyContent(Path("config", "librarycache", str(ids[i][1]) + ".json"), json.dumps(logo2), byte=False)
userdata = Path(Path.home(), ".steam", "root", "userdata")
for user in userdata.iterdir():
shortcutData = parseShortcuts(Path(user, "config", "shortcuts.vdf"))
for app in shortcutData:
if type(app) != dict:
if app.appId in toCodes:
print("Setting shortcut data for %s", app.name)
app.name = data["apps"][reverse[app.appId]]["common"]["name"]
app.icon = str(Path(user, "config", "grid", str(app.appId) + ".jpg"))
print("Setting %s to %s" % (app.name, app.icon))
writeShortcuts(shortcutData, Path(user, "config", "shortcuts.vdf"))
if __name__ == "__main__":
keys = []
ids = []
ops = []
for term in sys.argv[1:]:
if not term.startswith('-'):
keys.append(term)
if term.isnumeric():
if len(ids) > 0 and type(ids[-1]) == int:
ids[-1] = (ids[-1], int(term))
else:
ids.append(int(term))
else:
ids = None
else:
if term[1] == '-':
ops.append(term[2:])
else:
for op in term[1:]:
ops.append(op)
if len(keys) < 2 and not "a" in ops:
print("%s [OPTIONS] (<fromId> <toId>)..." % __file__)
print("%s [OPTIONS] <term>..." % __file__)
exit(1)
if ids:
if type(ids[-1]) == int:
print("Must have an even number of fromID - toId pairs")
exit(2)
copyContents(ids)
else:
tempApps = {}
apps = []
userdata = Path(Path.home(), ".steam", "root", "userdata")
for user in userdata.iterdir():
shortcutData = parseShortcuts(Path(user, "config", "shortcuts.vdf"))
for app in shortcutData:
if type(app) != dict:
if "a" in ops:
apps.append(app)
else:
for term in keys:
if term.lower() in app.name.lower():
if not app in tempApps:
tempApps[app] = 1
else:
tempApps[app] += 1
if not "a" in ops:
for app in tempApps:
if tempApps[app] / len(keys) >= 0.7:
apps.append(app)
if len(apps) == 0:
if "v" in ops:
print("No matches found")
exit(0)
elif len(apps) == 1 and not "v" in ops and not "l" in ops:
print(apps[0].appId)
else:
for app in apps:
print("%d - %s" % (app.appId, app.name))
if "l" in ops:
print("\tExecutable: %s" % app.executable)
print("\tRuntime Directory: %s" % app.start)
print("\tIcon: %s" % app.icon)
print("\tShortcut: %s" % app.shortcut)
print("\tRuntime Options: %s" % app.options)
print("\tIs Hidden: %s" % bool(app.hidden))
print("\tDesktop Configuration: %s" % bool(app.desktopConfig))
print("\tOverlay Enabled: %s" % bool(app.overlay))
print("\tIs VR: %s" % bool(app.vr))
print("\tDevkit: %d" % app.devkit)
print("\tDevkit ID: %s" % app.devkitId)
print("\tDate of last Play: %s" % app.playTime)
print("\tTags: %s" % app.tags)