Initial commit with nexus updates and streamlined UI

This commit is contained in:
jschantli committed 2026-09-21 19:01:19 -07:00
commit f780daecad
41 files changed
+4668

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
import io
import json
import unittest
import zipfile
from pathlib import Path
from mhwmm.core import loader, nexus, steam
from tests.helpers import TmpCase, make_zip
VDF = '''"libraryfolders"
{
\t"0"
\t{
\t\t"path"\t\t"/tmp/a b"
\t\t"apps" { "2246340" "1" }
\t}
\t"1" { "path" "/mnt/games" }
}
'''
class VDFTests(unittest.TestCase):
def test_parse_and_case_insensitive_get(self):
d = steam.parse_vdf(VDF)
self.assertEqual(steam.vdf_get(d, "LibraryFolders", "1", "PATH"), "/mnt/games")
def test_roundtrip_dump(self):
d = steam.parse_vdf(VDF)
self.assertEqual(steam.parse_vdf(steam.dump_vdf(d)), d)
def test_escapes_and_comments(self):
d = steam.parse_vdf('// hi\n"a" "x\\"y\\\\z"\n')
self.assertEqual(d["a"], 'x"y\\z')
def test_errors(self):
for bad in ('"a" {', '}', '{ "a" "b" }'):
with self.assertRaises(steam.VDFError):
steam.parse_vdf(bad)
def test_real_files_on_this_machine(self):
root = Path.home() / ".steam/steam/steamapps"
if not (root / "appmanifest_2246340.acf").is_file():
self.skipTest("Monster Hunter Wilds not installed here")
acf = steam.vdf_get(steam.read_vdf(root / "appmanifest_2246340.acf"), "AppState")
self.assertEqual(acf["appid"], "2246340")
self.assertTrue(acf["installdir"])
self.assertTrue(steam.library_folders(root.parent))
found = steam.find_app()
self.assertIsNotNone(found)
self.assertTrue((found.game_dir / "MonsterHunterWilds.exe").exists())
def test_launch_options(self):
req = steam.REQUIRED_LAUNCH_OPTIONS
self.assertTrue(steam.launch_options_ok(req))
self.assertFalse(steam.launch_options_ok(""))
self.assertFalse(steam.launch_options_ok("mangohud %command%"))
self.assertEqual(steam.suggest_launch_options(""), req)
self.assertEqual(steam.suggest_launch_options("mangohud %command%"),
'WINEDLLOVERRIDES="dinput8.dll=n,b" mangohud %command%')
self.assertEqual(steam.suggest_launch_options('WINEDLLOVERRIDES="winmm=n" %command%'),
'WINEDLLOVERRIDES="winmm=n;dinput8.dll=n,b" %command%')
self.assertEqual(steam.suggest_launch_options(req), req)
class LocalConfigTests(TmpCase):
def test_read_and_write_launch_options(self):
lc = self.tmp / "localconfig.vdf"
lc.write_text('"UserLocalConfigStore"\n{\n\t"Software"\n\t{\n\t\t"Valve"\n\t\t{\n\t\t\t"Steam"\n\t\t\t{\n'
'\t\t\t\t"apps"\n\t\t\t\t{\n\t\t\t\t\t"2246340"\n\t\t\t\t\t{\n\t\t\t\t\t\t"LaunchOptions"\t\t"-x"\n'
'\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n')
self.assertEqual(steam.read_launch_options(lc), "-x")
orig = steam.steam_running
try:
steam.steam_running = lambda: True
with self.assertRaises(steam.SteamRunningError):
steam.write_launch_options(lc, "y")
self.assertEqual(steam.read_launch_options(lc), "-x")
steam.steam_running = lambda: False
bak = steam.write_launch_options(lc, steam.REQUIRED_LAUNCH_OPTIONS)
finally:
steam.steam_running = orig
self.assertTrue(bak.exists())
self.assertEqual(steam.read_launch_options(lc), steam.REQUIRED_LAUNCH_OPTIONS)
class LoaderTests(TmpCase):
def test_config_patch_preserves_other_keys(self):
cfg = self.tmp / loader.CONFIG_NAME
cfg.write_text("Foo=1\nlooseFileLoader_enabled=false\nBar=2\n")
loader.enable_loose_loader(self.tmp)
self.assertEqual(cfg.read_text(), "Foo=1\nlooseFileLoader_enabled=true\nBar=2\n")
def test_config_created_when_absent(self):
loader.enable_loose_loader(self.tmp)
self.assertTrue(loader.loose_loader_enabled(self.tmp))
def test_status_checks(self):
st = loader.status(self.tmp, None)
self.assertEqual([c.ok for c in st.checks], [False, False, False])
(self.tmp / "dinput8.dll").write_bytes(b"x")
loader.enable_loose_loader(self.tmp)
st = loader.status(self.tmp, steam.REQUIRED_LAUNCH_OPTIONS)
self.assertTrue(st.all_ok)
def test_install_from_release(self):
blob = io.BytesIO()
with zipfile.ZipFile(blob, "w") as zf:
zf.writestr("dinput8.dll", b"MZfake")
zf.writestr("reframework/x", b"ignored")
rel = json.dumps({"tag_name": "nightly-1", "assets": [
{"name": "MHWILDS.zip", "browser_download_url": "https://example/MHWILDS.zip"}]}).encode()
fetch = lambda url: rel if url == loader.RELEASES_API else blob.getvalue()
self.assertEqual(loader.install_reframework(self.tmp, fetch), "nightly-1")
self.assertEqual((self.tmp / "dinput8.dll").read_bytes(), b"MZfake")
self.assertFalse((self.tmp / "reframework").exists())
class NexusTests(TmpCase):
def test_parse_nxm_with_key_and_expires(self):
l = nexus.parse_nxm("nxm://monsterhunterwilds/mods/123/files/456?key=AbC_-9&expires=1700000000&user_id=1")
self.assertEqual((l.game, l.mod_id, l.file_id, l.key, l.expires),
("monsterhunterwilds", 123, 456, "AbC_-9", "1700000000"))
def test_parse_rejects_garbage(self):
for bad in ("https://x/mods/1/files/2", "nxm://g/mods/x/files/2", "nxm:///mods/1/files/2", "nxm://g/mods/1"):
with self.assertRaises(nexus.NexusError, msg=bad):
nexus.parse_nxm(bad)
def test_client_passes_key_and_records_limits(self):
seen = {}
class Resp:
headers = {"x-rl-daily-remaining": "99", "x-rl-daily-limit": "100",
"x-rl-hourly-remaining": "9", "x-rl-hourly-limit": "10"}
def read(self): return b'[{"URI": "https://cdn/x.zip"}]'
def opener(req):
seen["url"], seen["headers"] = req.full_url, dict(req.header_items())
return Resp()
c = nexus.NexusClient("SECRET", opener)
out = c.download_links(nexus.parse_nxm("nxm://monsterhunterwilds/mods/1/files/2?key=K&expires=5"))
self.assertEqual(out[0]["URI"], "https://cdn/x.zip")
self.assertIn("/v1/games/monsterhunterwilds/mods/1/files/2/download_link.json?key=K&expires=5", seen["url"])
self.assertEqual(seen["headers"]["Apikey"], "SECRET")
self.assertIn("9/10 hourly", c.limits.summary())
def test_no_key_is_a_clear_error(self):
with self.assertRaises(nexus.NexusError):
nexus.NexusClient("").mod_info(nexus.parse_nxm("nxm://monsterhunterwilds/mods/1/files/2"))
def test_keystore_file_fallback_is_0600(self):
ks = nexus.KeyStore(self.tmp / "secrets.json")
nexus._secret_mod = lambda: None
ks.set("abc")
self.assertEqual(ks.get(), "abc")
self.assertEqual((self.tmp / "secrets.json").stat().st_mode & 0o777, 0o600)
def test_download_refuses_http(self):
with self.assertRaises(nexus.NexusError):
nexus.download("http://x/y.zip", self.tmp)
if __name__ == "__main__":
unittest.main()