Add new tool prepare_steam for managing Steam Library Folders

master
Taeyeon Mori 9 years ago
parent 9e14a02ec9
commit bfedb4506a
  1. 120
      bin/prepare_steam
  2. 38
      etc/prepare_steam.vdf
  3. 174
      lib/python/vdfparser.py

@ -0,0 +1,120 @@
#!/usr/bin/python
# Use Steam with external HDDs
# (c) 2015 Taeyeon Mori CC-BY-SA
import sys
import os
import argparse
import posixpath
import ntpath
import vdfparser
CONFIG = os.path.expanduser("~/.files/etc/prepare_steam.vdf")
def parse_args(argv):
parser = argparse.ArgumentParser(prog=argv[0])
parser.add_argument("-p", "--platform", "--profile", default=sys.platform, help="Platform profile (%(default)s)")
parser.add_argument("--config", default=CONFIG, help="Use alternate config file")
return parser.parse_args(argv[1:])
def take_sorted_list(dct, pred):
keys = list(filter(pred, dct.keys()))
lst = [dct[k] for k in sorted(keys)]
for k in keys:
del dct[k]
return lst
def detect_steam_platformpath(steamroot):
if os.path.exists(os.path.join(steamroot, "Steam.exe")):
return ntpath
else:
return posixpath
def main(argv):
args = parse_args(argv)
vdf = vdfparser.VdfParser()
with open(args.config) as f:
config = vdf.parse(f)
profile = config[args.platform]
steamroot = os.path.expanduser(profile["steamroot"])
# Read steam libraryfolders.vdf
libsvdf = os.path.join(steamroot, "steamapps", "libraryfolders.vdf")
with open(libsvdf) as f:
libs_config = vdf.parse(f)
library_folders = take_sorted_list(libs_config["LibraryFolders"], str.isdigit)
# Read steam config.vdf
steamvdf = os.path.join(steamroot, "config", "config.vdf")
with open(steamvdf) as f:
steam_config = vdf.parse(f)
client_config = steam_config["InstallConfigStore"]["Software"]["Valve"]["Steam"]
take_sorted_list(client_config, lambda k: k.startswith("BaseInstallFolder_"))
# Fix Library Folders
steampath = detect_steam_platformpath(steamroot)
do_normpath = profile.get("SanitizeLibraryPaths", "1") != "0"
if do_normpath:
orig_library_folders = library_folders
library_folders = []
for f in orig_library_folders:
f = steampath.normpath(f)
if f not in library_folders:
library_folders.append(f)
for path, steam_path in profile.get("Libraries", {}).items():
if not steampath:
steam_path = path
if do_normpath:
steam_path = steampath.normpath(steam_path)
if os.path.exists(path):
if steam_path not in library_folders:
library_folders.append(steam_path)
print ("Added Library Folder at %s%s" % (path, (" (%s)" % steam_path) if steam_path != path else ""))
elif steam_path in library_folders:
print ("Removing unavailable Library Folder %s" % steam_path)
library_folders.remove(steam_path)
for path in profile.get("LibraryBlacklist", {}).values():
if path in library_folders:
print ("Removing blacklisted Library Folder %s" % path)
library_folders.remove(path)
for i, path in enumerate(library_folders):
libs_config["LibraryFolders"][str(i + 1)] = path
client_config["BaseInstallFolder_%i" % (i + 1)] = path
print("Available Libraries: %s" % ("\"%s\"" % "\", \"".join(library_folders)) if library_folders else "(none)")
# Save new vdfs
os.rename(libsvdf, libsvdf + ".bak")
with open(libsvdf, "w") as f:
vdf.write(f, libs_config)
os.rename(steamvdf, steamvdf + ".bak")
with open(steamvdf, "w") as f:
vdf.write(f, steam_config)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

@ -0,0 +1,38 @@
// prepare_steam config file
// In Valve VDF format
// Top-level keys are platform profiles (-p/--platform/--profile)
"linux"
{
// Steam Install path
"steamroot" "~/.local/share/Steam/"
// Allow prepare_steam to sanitize library paths?
//"SanitizeLibraryPaths" "1"
// Libraries that prepare_steam should manage
"Libraries"
{
// "Actual Path" "Path to tell Steam"
// The latter may be empty, in which case the former is used
"/media/Data/SteamLibrary" ""
"/media/DumpStore/Steam/Library" ""
}
// Library folders that shouldn't be used by this profile
"LibraryBlacklist"
{
// "whatever" "Path Steam shouldn't use"
// The former value doesn't matter, must however be unique
"0" "/media/DumpStore/Steam/Windows Library"
}
}
"wine32"
{
"steamroot" "~/.local/share/wineprefixes/Steam32/drive_c/Program Files/Steam/"
"Libraries"
{
"/media/Data/SteamLibrary" "E:\\SteamLibrary"
"/media/DumpStore/Steam/Windows Library" "F:\\Steam\\Windows Library"
}
"LibraryBlacklist"
{
"0" "F:\\Steam\\Library"
}
}

@ -0,0 +1,174 @@
#!/usr/bin/python
# Parse Steam/Source VDF Files
# Reference: https://developer.valvesoftware.com/wiki/KeyValues#File_Format
# (c) 2015 Taeyeon Mori; CC-BY-SA
from __future__ import unicode_literals
import io
class VdfParser:
"""
Simple Steam/Source VDF parser
"""
# Special Characters
quote_char = "\""
escape_char = "\\"
begin_char = "{"
end_char = "}"
whitespace_chars = " \t\n"
comment_char = "/"
newline_char = "\n"
def __init__(self, *, encoding=False, factory=dict, strict=True):
"""
@brief Construct a VdfParser instance
@param encoding Encoding for bytes operations. Pass None to use unicode strings
@param factory A factory function creating a mapping type from an iterable of key/value tuples.
"""
self.encoding = encoding
if encoding:
self.empty_string = self.empty_string.encode(encoding)
self.quote_char = self.quote_char.encode(encoding)
self.escape_char = self.escape_char.encode(encoding)
self.begin_char = self.begin_char.encode(encoding)
self.end_char = self.end_char.encode(encoding)
self.whitespace_chars = self.whitespace_chars.encode(encoding)
self.comment_char = self.comment_char.encode(encoding)
self.newline_char = self.newline_char.encode(encoding)
self.factory = factory
self.strict = strict
def _make_map(self, tokens):
return self.factory(zip(tokens[::2], tokens[1::2]))
def _parse_map(self, fd, inner=False):
tokens = []
current = []
escape = False
quoted = False
comment = False
if self.encoding:
make_string = b"".join
else:
make_string = "".join
def finish(override=False):
if current or override:
tokens.append(make_string(current))
current.clear()
while True:
c = fd.read(1)
if not c:
finish()
if len(tokens) / 2 != len(tokens) // 2:
raise ValueError("Unexpected EOF: Last pair incomplete")
elif self.strict and (escape or quoted or inner):
raise ValueError("Unexpected EOF: EOF encountered while not processing outermost mapping")
return self._make_map(tokens)
if escape:
current.append(c)
escape = False
elif quoted:
if c == self.escape_char:
escape = True
elif c == self.quote_char:
quoted = False
finish(override=True)
else:
current.append(c)
elif comment:
if c == self.newline_char:
comment = False
else:
if c == self.escape_char:
escape = True
elif c == self.begin_char:
finish()
if len(tokens) / 2 == len(tokens) // 2 and (self.strict or self.factory == dict):
raise ValueError("Sub-dictionary cannot be a key")
tokens.append(self._parse_map(fd, True))
elif c == self.end_char:
finish()
if len(tokens) / 2 != len(tokens) // 2:
raise ValueError("Unexpected close: Missing last value (Unbalanced tokens)")
return self._make_map(tokens)
elif c in self.whitespace_chars:
finish()
elif c == self.quote_char:
finish()
quoted = True
elif c == self.comment_char and current and current[-1] == self.comment_char:
del current[-1]
finish()
comment = True
else:
current.append(c)
def parse(self, fd):
"""
Parse a VDF file into a python dictionary
"""
return self._parse_map(fd)
def parse_string(self, content):
"""
Parse the content of a VDF file
"""
if self.encoding:
return self.parse(io.BytesIO(content))
else:
return self.parse(io.StringIO(content))
def _make_literal(self, lit):
# TODO
return "\"%s\"" % (str(lit).replace("\\", "\\\\").replace("\"", "\\\""))
def _write_map(self, fd, dictionary, indent):
if indent is None:
def write(str=None, i=False, d=False, nl=False):
if str:
fd.write(str)
if delim:
fd.write(" ")
else:
def write(str=None, i=False, d=False, nl=False):
if not str and nl:
fd.write("\n")
else:
if i:
fd.write("\t" * indent)
if str:
fd.write(str)
if nl:
fd.write("\n")
elif d:
fd.write("\t\t")
for k, v in dictionary.items():
if isinstance(v, dict):
write(self._make_literal(k), i=1, d=1, nl=1)
write("{", i=1, nl=1)
self._write_map(fd, v, indent + 1 if indent is not None else None)
write("}", i=1)
else:
write(self._make_literal(k), i=1, d=1)
write(self._make_literal(v))
write(d=1, nl=1)
def write(self, fd, dictionary, *, pretty=True):
"""
Write a dictionary to a file in VDF format
"""
if self.encoding:
raise NotImplementedError("Writing in binary mode is not implemented yet.") # TODO (maybe)
self._write_map(fd, dictionary, 0 if pretty else None)
Loading…
Cancel
Save