Compare commits

..
22 Commits
Author SHA1 Message Date
aclist 5c07a77b63 Merge pull request #409 from aclist/fix/beta-client-vdf
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
fix: handle alternate client versions when parsing vdf
2026-07-15 11:29:54 +09:00
aclist 91773c933c chore: bump version 2026-07-15 11:26:01 +09:00
aclist 6a30ed44fc fix: handle alternate client versions when parsing vdf 2026-07-15 11:25:47 +09:00
aclist bbaec35a5f Merge pull request #408 from aclist/fix/conditional-warning
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
fix: make warning conditional when mods > 0
2026-07-14 17:40:09 +09:00
aclist c46477f0a7 fix: make warning conditional when mods > 0 2026-07-14 17:28:46 +09:00
aclist f47446a2e1 Merge pull request #407 from aclist/fix/parse-nsg-ids
fix: parse NSG ids
2026-07-14 17:18:22 +09:00
aclist cdb311cb1f chore: clear typehinting errors 2026-07-14 17:14:11 +09:00
aclist 11cdcb62b2 chore: drop unused import 2026-07-14 17:08:11 +09:00
aclist 1913f69e7b chore: drop unused import 2026-07-14 17:07:56 +09:00
aclist 8c2cea0fdc chore: bump version 2026-07-14 17:07:47 +09:00
aclist 8c29b9b35a fix: parse out-of-band appids as NSGs 2026-07-14 17:06:40 +09:00
aclist d8239f1c34 Merge pull request #405 from aclist/chore/update-licenses
Mirror to Codeberg / mirror-to-codeberg (push) Has been cancelled
chore: list missing library
2026-07-12 12:34:32 +09:00
aclist 8c3ec4512a chore: list missing library 2026-07-12 12:34:00 +09:00
aclist 1a18c8ff02 Merge pull request #404 from aclist/fix/app-path-tests
chore: update tests to reflect get_app_path() changes
2026-07-12 12:23:05 +09:00
aclist 92c23ec609 chore: update tests to reflect get_app_path() changes 2026-07-12 12:22:12 +09:00
aclist e9d4054e76 Merge pull request #403 from aclist/prerelease/7.0.0b16
Mirror to Codeberg / mirror-to-codeberg (push) Waiting to run
chore: bump version
2026-07-12 10:35:11 +09:00
aclist 1c9e026f70 chore: bump version 2026-07-12 10:34:24 +09:00
aclist eb076b7dcd Merge pull request #402 from aclist/fix/clean-acf
chore: drop comment
2026-07-12 10:32:27 +09:00
aclist 8629e02670 chore: drop comment 2026-07-12 10:30:06 +09:00
aclist 3ba38f30ad Merge pull request #401 from aclist/feat/collapsible-changelog
feat: collapsible changelog
2026-07-12 10:28:18 +09:00
aclist 7c37fd8b6e Merge pull request #400 from aclist/feat/shortcuts
feat: generate Steam shortcut
2026-07-12 10:27:27 +09:00
aclist 27d444ea6f feat: parse changelog chunks 2026-07-11 22:59:28 +09:00
19 changed files with 249 additions and 73 deletions

No files matched your search

+29 -28
View File
@@ -1,6 +1,7 @@
# Changelog # Changelog
## Added ## [7.0.0] Unreleased
### Added
- Setup wizard - Setup wizard
- Changelog text wrapping and formatting - Changelog text wrapping and formatting
- Changelog ships with source - Changelog ships with source
@@ -44,7 +45,7 @@
- Generate Steam shortcuts and cover art - Generate Steam shortcuts and cover art
- Warn user if background downloads are disabled - Warn user if background downloads are disabled
## Changed ### Changed
- Conform to PEP 440 versioning for beta versions - Conform to PEP 440 versioning for beta versions
- Reduce padding on keys button - Reduce padding on keys button
- Boldface breadcrumbs - Boldface breadcrumbs
@@ -61,58 +62,58 @@
- Enable LAN page Empty/Full filters on first run of app - Enable LAN page Empty/Full filters on first run of app
- Propagate subscribed mods to Steam client - Propagate subscribed mods to Steam client
## Dropped ### Dropped
- Debug mode - Debug mode
- Branch switching - Branch switching
- Manual mod install mode (describe rationale) - Manual mod install mode (describe rationale)
- Force update mods - Force update mods
## Fixed ### Fixed
- Longstanding issue with left clicks not registering as tree selection changes after spamming keyboard input - Longstanding issue with left clicks not registering as tree selection changes after spamming keyboard input
- Center server title text on server dialogs - Center server title text on server dialogs
- Rare segfaults when changing maps (threading) - Rare segfaults when changing maps (threading)
- Moved dialogs out of threads - Moved dialogs out of threads
## Unreleased ### Unreleased
- Load offline mods - Load offline mods
- Local documentation - Local documentation
- Raw debug command in context menu - Raw debug command in context menu
## Developer-facing ### Developer-facing
- Add pyproject.toml file - Add pyproject.toml file
- Show deprecation warnings - Show deprecation warnings
- Options -> Dev page - Options -> Dev page
## [6.0.5] 2026-05-17 ## [6.0.5] 2026-05-17
## Changed ### Changed
- Update symlink hash logic to align with DZGUI 7 beta - Update symlink hash logic to align with DZGUI 7 beta
- Update A2S helper to latest commit - Update A2S helper to latest commit
## [6.0.4] 2026-04-13 ## [6.0.4] 2026-04-13
## Fixed ### Fixed
- Update A2S module checksums to support servers with missing description field - Update A2S module checksums to support servers with missing description field
## [6.0.3] 2026-04-12 ## [6.0.3] 2026-04-12
## Fixed ### Fixed
- Use GLibUnix namespace where available when setting up SIGINT callback - Use GLibUnix namespace where available when setting up SIGINT callback
## [6.0.2] 2026-01-26 ## [6.0.2] 2026-01-26
## Fixed ### Fixed
- Explicitly use Python 3.13 when calling subprocesses - Explicitly use Python 3.13 when calling subprocesses
- Raise error correctly from module - Raise error correctly from module
- Do not spawn dialogs from inside of thread when fetching prereqs - Do not spawn dialogs from inside of thread when fetching prereqs
## Changed ### Changed
- Optimize time complexity of startup checks - Optimize time complexity of startup checks
- Optimize coordinate calculation by using local records first - Optimize coordinate calculation by using local records first
- Rename some internal functions - Rename some internal functions
- Wrap entire startup process in dialog - Wrap entire startup process in dialog
## [6.0.1] 2026-01-16 ## [6.0.1] 2026-01-16
## Fixed ### Fixed
- Explicitly use Python 3.13 when calling subprocesses - Explicitly use Python 3.13 when calling subprocesses
## [6.0.0] 2026-01-14 ## [6.0.0] 2026-01-14
## Added ### Added
- Support DayZ Experimental - Support DayZ Experimental
- Ping display on server tables - Ping display on server tables
- Save descriptive text notes on a per-server basis - Save descriptive text notes on a per-server basis
@@ -138,7 +139,7 @@
- Commandline usage help text (GaryBlackbourne) - Commandline usage help text (GaryBlackbourne)
- More descriptive help dialogs when initial dependencies are missing - More descriptive help dialogs when initial dependencies are missing
## Fixed ### Fixed
- Script failing to start when remote endpoints are unavailable (GaryBlackbourne) - Script failing to start when remote endpoints are unavailable (GaryBlackbourne)
- Key stickiness when quickly navigating through entries in tables - Key stickiness when quickly navigating through entries in tables
- Servers returning malformed A2S_INFO blocking server browser from loading - Servers returning malformed A2S_INFO blocking server browser from loading
@@ -177,7 +178,7 @@
- Path discovery during first-time setup when parsing filepaths with whitespaces - Path discovery during first-time setup when parsing filepaths with whitespaces
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it - First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
## Changed ### Changed
- Require Python 3.13 - Require Python 3.13
- Reduced global API cooldown from 60s to 30s - Reduced global API cooldown from 60s to 30s
- Clarify dialog messages when DayZ path could not be found - Clarify dialog messages when DayZ path could not be found
@@ -189,50 +190,50 @@
- Rewrote distance calculation module (GaryBlackbourne) - Rewrote distance calculation module (GaryBlackbourne)
- Changed preferred client setting from radio toggle to combobox - Changed preferred client setting from radio toggle to combobox
## Dropped ### Dropped
- Ping readout in statusbar - Ping readout in statusbar
- Extraneous information from right statusbar - Extraneous information from right statusbar
## [5.8.3] 2026-01-04 ## [5.8.3] 2026-01-04
## Fixed ### Fixed
- Normalize checksum numbers and dates - Normalize checksum numbers and dates
## [5.8.2] 2026-01-04 ## [5.8.2] 2026-01-04
## Fixed ### Fixed
- Fix regression in symlink routine introduced by previous hotfix - Fix regression in symlink routine introduced by previous hotfix
## [5.8.1] 2025-12-27 ## [5.8.1] 2025-12-27
## Fixed ### Fixed
- Path discovery during first-time setup when parsing filepaths with whitespaces - Path discovery during first-time setup when parsing filepaths with whitespaces
- First-time setup dialog continuously triggering when DayZ install path had whitespaces in it - First-time setup dialog continuously triggering when DayZ install path had whitespaces in it
## [5.8.0] 2025-07-06 ## [5.8.0] 2025-07-06
## Added ### Added
- Filter servers by official/unofficial status - Filter servers by official/unofficial status
- Automatically fetch geolocation records - Automatically fetch geolocation records
## Changed ### Changed
- Updated internal versioning of helper files - Updated internal versioning of helper files
## Fixed ### Fixed
- Corrected erroneous 2024 date in prior changelog entries - Corrected erroneous 2024 date in prior changelog entries
## [5.7.0] 2025-04-17 ## [5.7.0] 2025-04-17
## Added ### Added
- Save application dimensions when quitting and restore on subsequent boot - Save application dimensions when quitting and restore on subsequent boot
## Fixed ### Fixed
- Issues with the window exceeding the bounds of the screen when displaying some table contexts - Issues with the window exceeding the bounds of the screen when displaying some table contexts
## Changed ### Changed
- More direct server ping query method that should return marginally more accurate times - More direct server ping query method that should return marginally more accurate times
## [5.6.7] 2025-04-04 ## [5.6.7] 2025-04-04
## Dropped ### Dropped
- Removed extraneous pre-boot API checks that could cause error messages to be printed if the user had not set up an API key yet - Removed extraneous pre-boot API checks that could cause error messages to be printed if the user had not set up an API key yet
## [5.6.6] 2025-03-16 ## [5.6.6] 2025-03-16
## Changed ### Changed
- Update IP database records for 2025-03 - Update IP database records for 2025-03
## [5.6.5] 2025-03-04 ## [5.6.5] 2025-03-04
## Fixed ### Fixed
- Livonia server results being dropped from batch queries - Livonia server results being dropped from batch queries
## [5.6.4] 2025-02-10 ## [5.6.4] 2025-02-10
-1
View File
@@ -86,7 +86,6 @@ class WorkshopACF(ACF):
def __init__(self, file: Path) -> None: def __init__(self, file: Path) -> None:
super().__init__(file) super().__init__(file)
# TODO: break into workshop parser and generic acf parser
def unpack(self, d: dict, lines: list[Any] = []) -> str: def unpack(self, d: dict, lines: list[Any] = []) -> str:
t1 = "AppWorkshop" t1 = "AppWorkshop"
t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails") t2 = ("WorkshopItemsInstalled", "WorkshopItemDetails")
+26 -10
View File
@@ -12,6 +12,7 @@ from typing import Any
from dzgui.api.steam import find_user_id_32 from dzgui.api.steam import find_user_id_32
from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH from dzgui.const.constants import APP_NAME, APP_NAME_LOWER, IMAGES_PATH
from dzgui.util.dirs import copy_dzgui_to_xdg_data from dzgui.util.dirs import copy_dzgui_to_xdg_data
from dzgui.util.strings import unknown
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
@@ -46,6 +47,25 @@ class Shortcuts:
wrapped_exe = f'"{exe}"' wrapped_exe = f'"{exe}"'
return appname + wrapped_exe return appname + wrapped_exe
def find_appname_by_unsigned_id(self, appid: int) -> str:
# NOTE: bitmask signed int back to 32-bit CRC
# varying client versions treat case sensitivity differently
try:
for key in self.shortcuts["shortcuts"].keys():
if self.shortcuts["shortcuts"][key]["appid"] & 0xFFFFFFFF == appid:
try:
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
def get_shortcuts(self) -> Any: def get_shortcuts(self) -> Any:
return self.shortcuts return self.shortcuts
@@ -111,22 +131,18 @@ 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
Wiki variously lists keys with title case and lowercase, but keys actually do Key case is not internally consistent and varies between client versions
not seem to be case-sensitive. Some entries generated by Steam do not match the wiki Most keys use Pascal case, but some do not
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"] = ""
@@ -134,7 +150,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
+21 -9
View File
@@ -32,23 +32,25 @@ from dzgui.const.endpoints import (
) )
from dzgui.strings import wizard from dzgui.strings import wizard
from dzgui.util.bash import concat_bash_args from dzgui.util.bash import concat_bash_args
from dzgui.util.strings import unknown
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
class AppNotInstalledError(Exception): class AppNotInstalledError(Exception):
"""App not present in user's libraryfolders""" """App not present in user's libraryfolders"""
pass pass
class AppMovedError(Exception): class AppMovedError(Exception):
"""VDF points to a nonexistent location on disk""" """VDF points to a nonexistent location on disk"""
pass pass
class VDFLoadError(Exception): class VDFLoadError(Exception):
"""Malformed VDF or JSON conversion""" """Malformed VDF or JSON conversion"""
pass pass
@@ -203,21 +205,28 @@ 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 = path / "config" / "loginusers.vdf" resolved_path = find_loginusers(path)
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.warn(e) logger.debug(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:
@@ -322,9 +331,12 @@ def get_running_app() -> int | None:
def get_app_allows_downloads(path: Path, appid: int) -> bool: def get_app_allows_downloads(path: Path, appid: int) -> bool:
root_path = get_app_path(path, appid) try:
acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf") root_path = get_app_path(path, appid)
flag = ACF(acf).get_allows_downloads() acf = root_path.joinpath(f"steamapps/appmanifest_{appid}.acf")
flag = ACF(acf).get_allows_downloads()
except AppNotInstalledError:
flag = 0
match flag: match flag:
# NOTE: adheres to global client setting # NOTE: adheres to global client setting
case 0: case 0:
@@ -403,13 +415,13 @@ def get_app_path(folders_path: Path, appid: int) -> Path:
return Path(app_path) return Path(app_path)
def get_app_name(appid: int) -> str: def get_app_name(appid: int) -> str | None:
payload = {"appids": [appid]} payload = {"appids": [appid]}
res = requests.get(APP_DETAILS, params=payload) res = requests.get(APP_DETAILS, params=payload)
if res.status_code != 200: if res.status_code != 200:
return unknown return None
try: try:
return str(res.json()[str(appid)]["data"]["name"]) return str(res.json()[str(appid)]["data"]["name"])
except Exception as e: except Exception as e:
logger.debug(e) logger.debug(e)
return unknown return None
+7
View File
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING
import dzgui.api.pefile as PeFile import dzgui.api.pefile as PeFile
import dzgui.api.servers as Servers import dzgui.api.servers as Servers
from dzgui.api.shortcuts import Shortcuts
from dzgui.api.steam import ( from dzgui.api.steam import (
connect, connect,
@@ -175,6 +176,12 @@ class ConnectionManager:
if running_app is not None: if running_app is not None:
allows_dl = get_app_allows_downloads(steam_path, running_app) allows_dl = get_app_allows_downloads(steam_path, running_app)
running_app_name = get_app_name(running_app) running_app_name = get_app_name(running_app)
# NOTE: for out-of-range appid, assume NSG.
# dzgui.api.steam.get_app_name() will return None
if running_app_name is None:
s = Shortcuts(steam_path)
# NOTE: returns "Unknown" if unparseable
running_app_name = s.find_appname_by_unsigned_id(running_app)
allows_downloads = (allows_dl, running_app_name) allows_downloads = (allows_dl, running_app_name)
else: else:
allows_downloads = (True, "") allows_downloads = (True, "")
+54 -5
View File
@@ -1,4 +1,5 @@
import logging import logging
import re
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from importlib import resources from importlib import resources
@@ -32,15 +33,63 @@ class Changelog(HelpMenuMixin, ScrollableMixin, Gtk.ScrolledWindow): # type: ig
# TODO: should long text be wrapped? # TODO: should long text be wrapped?
self.controller = controller self.controller = controller
formatted = format_pango(changelog) self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5, margin_top=10)
self.changelog_label = Gtk.Label(valign=Gtk.Align.START, margin=15)
self.changelog_label.set_markup(formatted)
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.box.add(self.changelog_label)
self.add(self.box) self.add(self.box)
self.connect("key-press-event", self._on_keypress) self.connect("key-press-event", self._on_keypress)
self.connect("key-press-event", self._on_esc_keypress) self.connect("key-press-event", self._on_esc_keypress)
changes = self._parse(changelog)
self._generate_nodes(changes)
self.show_all()
def grab_content_area(self) -> None: def grab_content_area(self) -> None:
self.grab_focus() self.grab_focus()
def _parse_version(self, version: str) -> tuple[str, str]:
pattern = r"(^##\s\[)(.+)(\] )(.+$)"
match = re.match(pattern, version)
if match:
version = match.group(2)
date = match.group(4)
return version, date
raise ValueError("Changelog parse error")
def _generate_nodes(self, releases: list[tuple[str, list[str]]]) -> None:
for header, changes in releases:
for ind in (0, -1):
if changes[ind] == "":
del changes[ind]
text = "\n".join(changes)
formatted = format_pango(text)
label = Gtk.Label(valign=Gtk.Align.START, margin=15, halign=Gtk.Align.START)
label.set_markup(formatted)
version, date = self._parse_version(header)
button_text = f"{version} ({date})"
button = Gtk.Button(label=button_text, margin_start=5)
expander = Gtk.Expander()
expander.set_label_widget(button)
expander.add(label)
self.box.add(expander)
def _parse(self, changelog: str) -> list[tuple[str, list[str]]]:
release = ""
releases: list[tuple[str, list[str]]] = []
release_notes: list[str] = []
for line in changelog.splitlines():
if line.startswith("# "):
continue
if line.startswith("## "):
if release != "":
releases.append((release, release_notes))
release_notes = []
release = ""
release = line
continue
release_notes.append(line.rstrip())
return releases
+1 -1
View File
@@ -273,7 +273,7 @@ class PreConnectionAssistant(Gtk.Box):
) )
allows_dl, running_app = prereqs.allows_downloads allows_dl, running_app = prereqs.allows_downloads
if allows_dl is False: if len(prereqs.mods) > 0 and allows_dl is False:
msg = ( msg = (
f"The game '{running_app}' is currently running in Steam, but background downloads are not enabled.\n" f"The game '{running_app}' is currently running in Steam, but background downloads are not enabled.\n"
"Either stop the game first, or update your global Steam settings or the game's local settings.\n" "Either stop the game first, or update your global Steam settings or the game's local settings.\n"
+2 -2
View File
@@ -4,7 +4,7 @@ description = "DayZ server browser and mod manager for Linux"
authors = [ authors = [
{name = "aclist"} {name = "aclist"}
] ]
version = "7.0.0b15" version = "7.0.0b18"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
license-files = ["LICENSE"] license-files = ["LICENSE"]
readme = "README.md" readme = "README.md"
@@ -76,7 +76,7 @@ build-backend = "setuptools.build_meta"
line-length = 88 line-length = 88
indent-width = 4 indent-width = 4
extend-exclude = ["*lib/a2s"] extend-exclude = ["*lib/a2s"]
lint.unfixable = ["F401"] lint.unfixable = ["F401", "I001"]
[tool.ruff.format] [tool.ruff.format]
quote-style = "double" quote-style = "double"
+1
View File
@@ -12,6 +12,7 @@ This file includes the combined licenses to the following components:
- psutil - psutil
- PyGObject - PyGObject
- python-a2s - python-a2s
- vdf
When shipped as a binary installer, these components are collectively bundled When shipped as a binary installer, these components are collectively bundled
as a turnkey application and used to run the DZGUI utility.\n\n as a turnkey application and used to run the DZGUI utility.\n\n
File renamed without changes.
+13
View File
@@ -0,0 +1,13 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AutoLogin" "1"
"Timestamp" "0"
}
}
+14
View File
@@ -0,0 +1,14 @@
"users"
{
"0"
{
"AccountName" "STEAMUSER"
"PersonaName" "STEAMUSER"
"RememberPassword" "0"
"WantsOfflineMode" "0"
"SkipOfflineModeWarning" "0"
"AllowAutoLogin" "1"
"MostRecent" "1"
"Timestamp" "0"
}
}
@@ -0,0 +1,25 @@
"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"
}
}
File renamed without changes.
File renamed without changes.
+25
View File
@@ -0,0 +1,25 @@
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
+26 -12
View File
@@ -1,8 +1,14 @@
import pytest import pytest
from pathlib import Path from pathlib import Path
import dzgui.api.pefile as PeFile import dzgui.api.pefile as PeFile
from dzgui.api.steam import VDFLoadError, AppNotInstalledError, AppMovedError, get_app_path from dzgui.api.steam import (
VDFLoadError,
AppNotInstalledError,
AppMovedError,
get_app_path
)
from dzgui.config.query import get_config from dzgui.config.query import get_config
from dzgui.config.xdg import get_xdg_paths, parse_filepaths from dzgui.config.xdg import get_xdg_paths, parse_filepaths
@@ -10,12 +16,7 @@ from dzgui.const.constants import APPID_DAYZ
from tests.fixtures import fixture_path from tests.fixtures import fixture_path
pytestmark = pytest.mark.pefile pytestmark = pytest.mark.apitest
@pytest.fixture
def second_drive():
return fixture_path("in_library_on_second_drive.vdf")
@pytest.fixture @pytest.fixture
@@ -26,6 +27,8 @@ def default_steam_path():
return conf["default_steam_path"] return conf["default_steam_path"]
# TODO: split into separate test file
# FIXME: use fixtures
def test_pefile_length(default_steam_path): def test_pefile_length(default_steam_path):
try: try:
pe_file_path = PeFile.get_pefile_path(Path(default_steam_path), APPID_DAYZ) pe_file_path = PeFile.get_pefile_path(Path(default_steam_path), APPID_DAYZ)
@@ -52,15 +55,26 @@ def test_invalid_pefile_path():
("malformed.vdf", VDFLoadError), ("malformed.vdf", VDFLoadError),
], ],
) )
def test_not_in_library(fixture, exception): def test_not_in_library(monkeypatch, fixture, exception):
fixture = fixture_path(fixture) import dzgui.api.steam as steam
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", fixture)
folder_path = Path(fixture_path("api"))
with pytest.raises(exception): with pytest.raises(exception):
try: try:
get_app_path(fixture, APPID_DAYZ) get_app_path(folder_path, APPID_DAYZ)
except Exception as e: except Exception as e:
raise e raise e
def test_on_second_drive(second_drive): @pytest.fixture
path = get_app_path(second_drive, APPID_DAYZ) def second_drive():
return "in_library_on_second_drive.vdf"
def test_on_second_drive(monkeypatch, second_drive):
import dzgui.api.steam as steam
monkeypatch.setattr(steam, "LIBRARYFOLDERS_PATH", second_drive)
folder_path = Path(fixture_path("api"))
path = get_app_path(folder_path, APPID_DAYZ)
assert path == Path("/tmp") assert path == Path("/tmp")
+5 -5
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