Skip to content

Instantly share code, notes, and snippets.

@thurask
Last active December 7, 2018 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thurask/0da26d6733923807f204262c9d952860 to your computer and use it in GitHub Desktop.
Save thurask/0da26d6733923807f204262c9d952860 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""Check some popular TS4 mods for updates."""
import argparse
import io
import os
import platform
import re
import string
import sys
import tempfile
import zipfile
import bs4 # pip install bs4
import requests # pip install requests
import uncompyle6 # pip install uncompyle6
try:
import unpyc3 # https://github.com/andrew-tavera/unpyc37
except (ImportError, AttributeError):
UNPYC = False
else:
UNPYC = True
GISTURL = "https://gist.github.com/thurask/0da26d6733923807f204262c9d952860"
BMDURL = "https://basementalcc.com/download/"
MCURL = "https://deaderpool-mccc.com/main.bundle.js"
WWURL = "https://wickedwhimsmod.com/download/"
WPURL = "https://www.loverslab.com/files/file/5002-nisa%E2%80%99s-wicked-perversions/"
PATCHURL = "https://help.ea.com/en-us/help/the-sims/the-sims-4/the-sims-4-updates/"
TS4FOLDER = os.path.join(os.path.expanduser("~"), "Documents", "Electronic Arts", "The Sims 4")
def get_mod_packages(filtername):
"""Get mod package paths."""
rootmod = os.path.join(TS4FOLDER, "Mods")
results = []
for root, _, files in os.walk(rootmod):
for filex in files:
if filex.endswith(filtername):
results.append(os.path.join(root, filex))
return results
def decomp_function(infile, dummyio, unpyc=False):
"""Decompiler function of choice."""
if unpyc:
decomp = unpyc3.decompile(infile)
print(decomp, file=dummyio)
dfinal = dummyio.getvalue()
else:
decomp = uncompyle6.decompile_file(infile, dummyio)[0]
dfinal = decomp.text
return dfinal
def zip_opener(zipf, pyoname, tmppath):
"""Handle change in file extensions."""
with zipf.open(pyoname) as gvp:
with open(tmppath, "wb") as gtfile:
gtfile.write(gvp.read())
def generic_package_cracker(packname, pyoname, unpyc=False):
"""Open and decompile a mod package."""
gpacks = get_mod_packages(packname)
if not gpacks:
return None
if len(gpacks) > 1:
raise SystemExit("MORE THAN ONE COPY OF {0} FOUND!".format(packname))
else:
with tempfile.TemporaryDirectory() as tmpdirname:
tmppath = os.path.join(tmpdirname, "tempfile.pyc")
zipf = zipfile.ZipFile(gpacks[0])
try:
zip_opener(zipf, pyoname, tmppath)
except KeyError:
pycname = pyoname.replace(".pyo", ".pyc")
zip_opener(zipf, pycname, tmppath)
with io.StringIO() as dummy:
decomp = decomp_function(tmppath, dummy, unpyc)
return decomp
def get_local_mccc(unpyc=False):
"""Get local MCCC version."""
mcscript = "mc_cmd_center.ts4script"
mcpyo = "mc_cmd_version.pyo"
dtext = generic_package_cracker(mcscript, mcpyo, unpyc)
if dtext is None:
mcversion = "Not installed"
else:
mcbase = [txt for txt in dtext.split("\n") if "VERSION = " in txt][0]
mcversion = mcbase.split(" = '")[-1].replace("'", "")
return mcversion
def get_local_basemental(unpyc=False):
"""Get local Basemental Drugs version."""
bmscript = "basementaldrugs.ts4script"
bmpyo = "bmdversion.pyo"
dtext = generic_package_cracker(bmscript, bmpyo, unpyc)
if dtext is None:
bmversion = "Not installed"
else:
bmbase = [txt for txt in dtext.split("\n") if "Current version" in txt][0]
bmraw = bmbase.split(": v")[-1]
if bmraw == bmbase:
bmversion = bmraw.split("Basemental Drugs ")[-1].replace("')", "")
else:
bmversion = bmraw.split("'")[0]
return bmversion
def get_local_wwhims(unpyc=False):
"""Get local Wicked Whims version."""
wwscript = "TURBODRIVER_WickedWhims_Scripts.ts4script"
wwpyo = "wickedwhims/version_registry.pyo"
dtext = generic_package_cracker(wwscript, wwpyo, unpyc)
if dtext is None:
wwversion = "Not installed"
else:
dct = dtext.split("\n")[0:5]
wwbase = ".".join([field.split(" = ")[-1] for field in dct[0:4]])
wwhotfix = string.ascii_lowercase[int(dct[4].split(" = ")[-1]) - 1]
wwversion = "{0}{1}".format(wwbase, wwhotfix)
return wwversion
def get_local_wperv(unpyc=False):
"""Get local Wicked Perversions version."""
wpscript = "NisaK_Wicked_Perversions.ts4script"
wppyo = "NisaK/utilities/hazard_detector.pyo"
dtext = generic_package_cracker(wpscript, wppyo, unpyc)
if dtext is None:
wpversion = "Not installed"
else:
wpbase = [txt for txt in dtext.split("\n") if "NISA_MOD_VER" in txt][0]
wpalmost = wpbase.split(" = ")[-1].replace("'", "")
wpversion = ".".join(wpalmost.split(".")[1:])
return wpversion
def get_local_game_version():
"""Get local game version."""
gvt = os.path.join(TS4FOLDER, "GameVersion.txt")
with open(gvt, "r") as afile:
gvtraw = afile.read()
gvtclean = gvtraw.split("\x00")[-1]
return gvtclean
def clean_latest_version(inver):
"""Extract latest game version from raw text."""
inver2 = inver.split("-")[1].strip()
stripvers = [ver.strip() for ver in inver2.split("/")]
splitvers = [sver.split(" ") for sver in stripvers]
pcver = splitvers[0][1]
macver = splitvers[1][1]
return pcver, macver
def get_latest_versions():
"""Scrape for latest game version."""
req = requests.get(PATCHURL)
soup = bs4.BeautifulSoup(req.text, "html.parser")
h4s = soup.find_all("h4")
latest = h4s[0].text
pcver, macver = clean_latest_version(latest)
verdict = {"Windows": pcver, "Mac": macver}
return verdict
def get_platform_version():
"""Get platform."""
platsys = platform.system()
fplat = "Mac" if platsys == "Darwin" else "Windows"
return fplat
def get_latest_basemental():
"""Scrape for latest Basemental Drugs version."""
req = requests.get(BMDURL)
soup = bs4.BeautifulSoup(req.text, "html.parser")
alist = [atag for atag in soup.find_all("a") if "DOWNLOAD " in atag.text]
latest = alist[0].text.replace("DOWNLOAD BASEMENTAL DRUGS ", "").replace("!", "")
return latest
def get_latest_mccc():
"""Scrape for latest MCCC version."""
req = requests.get(MCURL)
soup = bs4.BeautifulSoup(req.text, "html.parser")
latesta = soup.find_all("a", string=re.compile("^MC Command Center"), attrs={"href": True})[0]
finalver = latesta.text.replace("MC Command Center ", "")
return finalver
def get_latest_wwhims():
"""Scrape for latest Wicked Whims version."""
req = requests.get(WWURL)
soup = bs4.BeautifulSoup(req.text, "html.parser")
attrdict = {"style": "text-align: center;"}
htwoes = soup.find_all("h2", attrs=attrdict, string=re.compile("^WickedWhims"))
latesth = htwoes[0].text
finalver = latesth.replace("WickedWhims ", "")
return finalver
def get_latest_wperv():
"""Scrape for latest Wicked Perversions version."""
req = requests.get(WPURL)
soup = bs4.BeautifulSoup(req.text, "html.parser")
latests = soup.find_all("section", {"data-controller": "downloads.front.view.changeLog"})[0]
finalver = latests.text.split("\n")[1].split("LL.")[-1]
return finalver
def current_versions(unpyc=False):
"""Pack local versions into a dict."""
vcurrent = get_local_game_version()
mcurrent = get_local_mccc(unpyc)
bcurrent = get_local_basemental(unpyc)
wcurrent = get_local_wwhims(unpyc)
wpcurrent = get_local_wperv(unpyc)
curdict = {}
curdict["The Sims 4"] = vcurrent
curdict["MC Command Center"] = mcurrent
curdict["Basemental Drugs"] = bcurrent
curdict["Wicked Whims"] = wcurrent
curdict["Wicked Perversions"] = wpcurrent
return curdict
def latest_versions_remote(pg13):
"""Pack remote versions into a dict."""
vlatest = get_latest_versions()
mlatest = get_latest_mccc()
if pg13:
blatest = None
wlatest = None
wplatest = None
else:
blatest = get_latest_basemental()
wlatest = get_latest_wwhims()
wplatest = get_latest_wperv()
platsys = get_platform_version()
latdict = {}
latdict["The Sims 4"] = vlatest[platsys]
latdict["MC Command Center"] = mlatest
latdict["Basemental Drugs"] = blatest
latdict["Wicked Whims"] = wlatest
latdict["Wicked Perversions"] = wplatest
return latdict
def main(llatest=False, unpyc=False, pg13=False):
"""Print latest versions with optional compare."""
latdict = latest_versions_remote(pg13)
if llatest:
curdict = current_versions(unpyc)
print("LATEST PUBLIC VERSIONS")
print("~"*32)
for key, val in latdict.items():
print("{0}: {1}".format(key, val))
if llatest:
print("\tYour version: {0}".format(curdict[key]))
def handle_args():
"""Take arguments from command line."""
parser = argparse.ArgumentParser(epilog=GISTURL)
parser.add_argument("-u",
"--update-check",
dest="llatest",
help="check if local game/mods are up to date",
action="store_true",
default=False)
parser.add_argument("--pg13",
dest="pg13",
help="don't check adult mods for updates",
action="store_true",
default=False)
if UNPYC:
parser.add_argument("--unpyc3",
dest="unpyc3",
help="use unpyc3 instead of uncompyle6",
action="store_true",
default=False)
parser.set_defaults()
args = parser.parse_args(sys.argv[1:])
if not UNPYC:
args.unpyc3 = False
main(args.llatest, args.unpyc3, args.pg13)
if __name__ == "__main__":
handle_args()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment