Compare commits

..

No commits in common. "5c07a77b63f8c21452f17ce4f6c2455b7ba554cd" and "bbaec35a5f21af56612224bf96df9bcf7c36691d" have entirely different histories.

8 changed files with 21 additions and 113 deletions

View File

@ -49,21 +49,9 @@ class Shortcuts:
def find_appname_by_unsigned_id(self, appid: int) -> str: def find_appname_by_unsigned_id(self, appid: int) -> str:
# NOTE: bitmask signed int back to 32-bit CRC # NOTE: bitmask signed int back to 32-bit CRC
# varying client versions treat case sensitivity differently
try:
for key in self.shortcuts["shortcuts"].keys(): for key in self.shortcuts["shortcuts"].keys():
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid: if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
try:
return str(self.shortcuts["shortcuts"][key]["appname"]) return str(self.shortcuts["shortcuts"][key]["appname"])
except Exception as e:
logger.debug(e)
try:
return str(self.shortcuts["shortcuts"][key]["AppName"])
except Exception as e:
logger.debug(e)
return unknown
except Exception as e:
logger.debug(e)
return unknown return unknown
def get_shortcuts(self) -> Any: def get_shortcuts(self) -> Any:
@ -131,18 +119,22 @@ class Shortcuts:
https://developer.valvesoftware.com/wiki/Add_Non-Steam_Game https://developer.valvesoftware.com/wiki/Add_Non-Steam_Game
https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts https://developer.valvesoftware.com/wiki/Steam_Library_Shortcuts
Key case is not internally consistent and varies between client versions Wiki variously lists keys with title case and lowercase, but keys actually do
Most keys use Pascal case, but some do not not seem to be case-sensitive. Some entries generated by Steam do not match the wiki
Keys are entered into the dictionary in a linear insertion order
# TODO: try to replicate key case sensitivity as it is created by Steam with generic shortcut
appid: signed int CRC appid: signed int CRC
Exe: absolute path to the executable, must be wrapped in literal quotes exe: absolute path to the executable, must be wrapped in literal quotes
StartDir: directory the executable starts in, generally the parent StartDir: directory the executable starts in, generally the parent
""" """
NEW_ENTRY: dict[str, Any] = {} NEW_ENTRY: dict[str, Any] = {}
NEW_ENTRY["appid"] = meta.appid NEW_ENTRY["appid"] = meta.appid
NEW_ENTRY["AppName"] = meta.appname NEW_ENTRY["appname"] = meta.appname
NEW_ENTRY["Exe"] = f'"{meta.exe_path}"' NEW_ENTRY["exe"] = f'"{meta.exe_path}"'
NEW_ENTRY["StartDir"] = f"{meta.start_dir}" NEW_ENTRY["StartDir"] = f"{meta.start_dir}"
NEW_ENTRY["icon"] = meta.icon NEW_ENTRY["icon"] = meta.icon
NEW_ENTRY["ShortcutPath"] = "" NEW_ENTRY["ShortcutPath"] = ""
@ -150,7 +142,7 @@ class Shortcuts:
NEW_ENTRY["IsHidden"] = 0 NEW_ENTRY["IsHidden"] = 0
NEW_ENTRY["AllowDesktopConfig"] = 1 NEW_ENTRY["AllowDesktopConfig"] = 1
NEW_ENTRY["AllowOverlay"] = 1 NEW_ENTRY["AllowOverlay"] = 1
NEW_ENTRY["OpenVR"] = 0 NEW_ENTRY["openvr"] = 0
NEW_ENTRY["Devkit"] = 0 NEW_ENTRY["Devkit"] = 0
NEW_ENTRY["DevkitGameID"] = "" NEW_ENTRY["DevkitGameID"] = ""
NEW_ENTRY["DevkitOverrideAppID"] = 0 NEW_ENTRY["DevkitOverrideAppID"] = 0

View File

@ -205,28 +205,21 @@ def launch_offline(
proc = subprocess.run([*client_args, *params]) proc = subprocess.run([*client_args, *params])
return proc.returncode return proc.returncode
def find_loginusers(path: Path) -> Path:
return path / "config" / "loginusers.vdf"
def find_user_id(path: Path) -> str | None: def find_user_id(path: Path) -> str | None:
resolved_path = find_loginusers(path) resolved_path = path / "config" / "loginusers.vdf"
try: try:
with open(resolved_path, "r") as f: with open(resolved_path, "r") as f:
v = vdf.load(f) v = vdf.load(f)
# NOTE: beta client
# /package/beta
if len(v["users"]) == 1:
return str(list(v["users"].keys())[0])
for user in v["users"]: for user in v["users"]:
if v["users"][user]["MostRecent"] == "1": if v["users"][user]["MostRecent"] == "1":
return str(user) return str(user)
return None return None
except Exception as e: except Exception as e:
logger.debug(e) logger.warn(e)
return None return None
def find_user_id_32(path: Path) -> int: def find_user_id_32(path: Path) -> int:
uid = find_user_id(path) uid = find_user_id(path)
if uid is None: if uid is None:

View File

@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b18" version = "7.0.0b17"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"

View File

@ -1,13 +0,0 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"Timestamp" "0"
}
}

View File

@ -1,14 +0,0 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"Timestamp" "0"
}
}

View File

@ -1,25 +0,0 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "0"
"Timestamp" "0"
}
"1"
{
"AccountName" "STEAMUSER2"
"PersonaName" "STEAMUSER2"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"Timestamp" "0"
}
}

View File

@ -1,25 +0,0 @@
import pytest
from pathlib import Path
from dzgui.api.steam import find_user_id
from tests.fixtures import fixture_path
pytestmark = pytest.mark.apitest
@pytest.mark.parametrize(
"fixture, expect",
[
("api/loginusers_legacy_client.vdf", 0),
("api/loginusers_legacy_client_multiple.vdf", 1),
("api/loginusers_beta_client.vdf", 0),
],
)
def test_loginusers(monkeypatch, fixture: str, expect: int) -> None:
def mock_loginusers(path: Path) -> Path:
return fixture_path(fixture)
monkeypatch.setattr("dzgui.api.steam.find_loginusers", mock_loginusers)
uid = find_user_id(Path(""))
assert int(uid) == expect

View File

@ -30,9 +30,9 @@ def test_no_shortcuts(monkeypatch) -> None:
@pytest.fixture @pytest.fixture
def dummy_app() -> None: def dummy_app() -> None:
d = { d = {
"AppName": "TEST APP", "appname": "TEST APP",
"StartDir": "TEST_DIR", "StartDir": "TEST_DIR",
"Exe": "TEST_DIR/TEST_EXE.EXE", "exe": "TEST_DIR/TEST_EXE.EXE",
"icon": "IMAGES_DIR/TEST_IMAGE.PNG", "icon": "IMAGES_DIR/TEST_IMAGE.PNG",
} }
return d return d
@ -41,8 +41,8 @@ def dummy_app() -> None:
def test_wrap_exe(dummy_app) -> None: def test_wrap_exe(dummy_app) -> None:
s = Shortcuts(Path("")) s = Shortcuts(Path(""))
s.add_shortcut(*dummy_app.values()) s.add_shortcut(*dummy_app.values())
assert s.shortcuts["shortcuts"]["0"]["Exe"][0] == '"' assert s.shortcuts["shortcuts"]["0"]["exe"][0] == '"'
assert s.shortcuts["shortcuts"]["0"]["Exe"][-1] == '"' assert s.shortcuts["shortcuts"]["0"]["exe"][-1] == '"'
def test_add_shortcut(dummy_app) -> None: def test_add_shortcut(dummy_app) -> None:
@ -51,7 +51,7 @@ def test_add_shortcut(dummy_app) -> None:
new = s.shortcuts["shortcuts"] new = s.shortcuts["shortcuts"]
ind = str(len(new) - 1) ind = str(len(new) - 1)
for k, v in dummy_app.items(): for k, v in dummy_app.items():
if k == "Exe": if k == "exe":
v = f'"{v}"' v = f'"{v}"'
assert new[ind][k] == v assert new[ind][k] == v