Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be7891f066 | |||
| 6a508476b9 | |||
| 9ef0eff2f5 |
@@ -1,6 +1,6 @@
|
||||
# PristonTale EU for Linux
|
||||
|
||||
Version 1.0.5 installs, updates, repairs, and launches PristonTale EU through a
|
||||
Version 1.0.8 installs, updates, repairs, and launches PristonTale EU through a
|
||||
native Linux dashboard. Game files come directly from the publisher and are not
|
||||
included in this archive.
|
||||
|
||||
@@ -15,11 +15,11 @@ Then run:
|
||||
```text
|
||||
openssl pkey -pubin -in launcher-update-public.pem -outform DER | sha256sum
|
||||
openssl dgst -sha256 -verify launcher-update-public.pem \
|
||||
-signature pristontale-eu-linux-installer-v1-0-5.tar.gz.sig \
|
||||
pristontale-eu-linux-installer-v1-0-5.tar.gz
|
||||
sha256sum -c pristontale-eu-linux-installer-v1-0-5.tar.gz.sha256
|
||||
tar -xzf pristontale-eu-linux-installer-v1-0-5.tar.gz
|
||||
cd pristontale-eu-linux-installer-v1-0-5
|
||||
-signature pristontale-eu-linux-installer-v1-0-8.tar.gz.sig \
|
||||
pristontale-eu-linux-installer-v1-0-8.tar.gz
|
||||
sha256sum -c pristontale-eu-linux-installer-v1-0-8.tar.gz.sha256
|
||||
tar -xzf pristontale-eu-linux-installer-v1-0-8.tar.gz
|
||||
cd pristontale-eu-linux-installer-v1-0-8
|
||||
./install.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "PTEU-Linux-Launcher"
|
||||
version = "1.0.5"
|
||||
version = "1.0.8"
|
||||
description = "PristonTale EU Linux installer, clean launcher, and diagnostic harness"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
@@ -4,4 +4,4 @@ from .paths import AppPaths
|
||||
|
||||
__all__ = ["AppPaths", "__version__"]
|
||||
|
||||
__version__ = "1.0.5"
|
||||
__version__ = "1.0.8"
|
||||
|
||||
@@ -394,6 +394,7 @@ def play_locked(
|
||||
if recommended_comparison is not None:
|
||||
runtime_issues["recommended_one_variable_comparison"] = recommended_comparison
|
||||
clean_exit = outcome.returncode == 0 and current_game_crash is None
|
||||
session_issue = game_log_issue_reason(current_game_log)
|
||||
launch_evidence = game_readiness_signal
|
||||
if launch_evidence is None and current_game_log is not None:
|
||||
launch_evidence = "game_log"
|
||||
@@ -402,6 +403,8 @@ def play_locked(
|
||||
launch_succeeded = launch_evidence is not None
|
||||
report = {
|
||||
"ok": clean_exit,
|
||||
"degraded": session_issue is not None,
|
||||
"session_issue": session_issue,
|
||||
"launch_succeeded": launch_succeeded,
|
||||
"dry_run": dry_run,
|
||||
"runtime": asdict(selection),
|
||||
@@ -430,6 +433,7 @@ def play_locked(
|
||||
**window_launch_metadata,
|
||||
"runtime_seconds": round(game_runtime_seconds),
|
||||
"clean_exit": clean_exit,
|
||||
"degraded": session_issue is not None,
|
||||
"timed_out": outcome.timed_out,
|
||||
"launch_succeeded": launch_succeeded,
|
||||
"game_window_ready": game_window_ready,
|
||||
@@ -440,7 +444,7 @@ def play_locked(
|
||||
"realm": start_option.name,
|
||||
**api._game_exit_metadata(outcome.returncode, selection.kind),
|
||||
}
|
||||
if not clean_exit:
|
||||
if not clean_exit or session_issue is not None:
|
||||
common_game_metadata["failure_reason"] = game_failure_reason(
|
||||
outcome,
|
||||
current_game_log,
|
||||
@@ -516,6 +520,10 @@ def play_locked(
|
||||
exit_message = game_crash_message(current_game_log)
|
||||
elif outcome.timed_out:
|
||||
exit_message = "Game session timed out"
|
||||
elif session_issue == "direct3d_resource_exhaustion":
|
||||
exit_message = (
|
||||
"Game client exited after logging Direct3D resource exhaustion"
|
||||
)
|
||||
elif common_game_metadata.get("signal_inferred") is True:
|
||||
signal_wrapper = (
|
||||
"Proton" if selection.kind == "proton7" else "UMU"
|
||||
@@ -537,12 +545,14 @@ def play_locked(
|
||||
severity=(
|
||||
"error"
|
||||
if current_game_crash is not None
|
||||
else "info" if clean_exit else "warning"
|
||||
else "warning"
|
||||
if not clean_exit or session_issue is not None
|
||||
else "info"
|
||||
),
|
||||
message=exit_message,
|
||||
metadata=(
|
||||
common_game_metadata
|
||||
if clean_exit
|
||||
if clean_exit and session_issue is None
|
||||
else {
|
||||
**common_game_metadata,
|
||||
**failure_evidence,
|
||||
@@ -646,7 +656,7 @@ def game_failure_reason(
|
||||
current_game_crash: dict[str, object] | None,
|
||||
exit_metadata: dict[str, object],
|
||||
) -> str:
|
||||
"""Classify the strongest evidence recorded for a failed game session."""
|
||||
"""Classify the strongest evidence recorded for a failed or degraded session."""
|
||||
|
||||
if current_game_log and current_game_log.get("runtime_out_of_memory") is True:
|
||||
return "direct3d_resource_exhaustion"
|
||||
@@ -659,6 +669,16 @@ def game_failure_reason(
|
||||
return "nonzero_exit"
|
||||
|
||||
|
||||
def game_log_issue_reason(
|
||||
current_game_log: dict[str, object] | None,
|
||||
) -> str | None:
|
||||
"""Classify native-log evidence that degraded an otherwise clean process exit."""
|
||||
|
||||
if current_game_log and current_game_log.get("runtime_out_of_memory") is True:
|
||||
return "direct3d_resource_exhaustion"
|
||||
return None
|
||||
|
||||
|
||||
def game_crash_message(current_game_log: dict[str, object] | None) -> str:
|
||||
if (
|
||||
current_game_log is not None
|
||||
@@ -846,7 +866,32 @@ def game_log_telemetry_summary(report: dict[str, object]) -> dict[str, object]:
|
||||
"missing_image_reference_count",
|
||||
"missing_pack_image_reference_count",
|
||||
)
|
||||
return {key: report[key] for key in keys if key in report}
|
||||
summary = {key: report[key] for key in keys if key in report}
|
||||
asset_references = report.get("asset_references")
|
||||
if isinstance(asset_references, dict):
|
||||
asset_summary = {
|
||||
key: asset_references[key]
|
||||
for key in (
|
||||
"total_events",
|
||||
"unique_valid_references",
|
||||
"invalid_reference_events",
|
||||
"catalog_available",
|
||||
"managed_integrity_failed",
|
||||
)
|
||||
if key in asset_references
|
||||
}
|
||||
for source_key, output_key in (
|
||||
("managed_present_paths", "managed_present_count"),
|
||||
("managed_missing_paths", "managed_missing_count"),
|
||||
("publisher_unmanaged_paths", "publisher_unmanaged_count"),
|
||||
("unclassified_paths", "unclassified_count"),
|
||||
):
|
||||
paths = asset_references.get(source_key)
|
||||
if isinstance(paths, (list, tuple)):
|
||||
asset_summary[output_key] = len(paths)
|
||||
if asset_summary:
|
||||
summary["asset_reference_summary"] = asset_summary
|
||||
return summary
|
||||
|
||||
|
||||
def crash_dump_telemetry_summary(report: dict[str, object]) -> dict[str, object]:
|
||||
@@ -971,6 +1016,16 @@ def print_game_outcome(
|
||||
)
|
||||
elif outcome.timed_out:
|
||||
print("Game client timed out and was stopped.", file=sys.stderr)
|
||||
elif (
|
||||
outcome.returncode == 0
|
||||
and game_log_issue_reason(current_game_log)
|
||||
== "direct3d_resource_exhaustion"
|
||||
):
|
||||
print(
|
||||
"Game client exited with status 0 after logging Direct3D resource "
|
||||
"exhaustion.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
elif outcome.returncode == 0:
|
||||
print("Game client exited normally.")
|
||||
elif outcome.returncode is None:
|
||||
@@ -1039,8 +1094,8 @@ def print_interrupted_game_log(report: dict[str, object]) -> None:
|
||||
print(
|
||||
"Detected Direct3D resource exhaustion in the 32-bit game process"
|
||||
+ effect_text
|
||||
+ ". Restarting the client can recover temporarily; depot repair will not "
|
||||
"fix this failure.",
|
||||
+ ". Restarting clears the process address space, but this condition can "
|
||||
"recur; depot repair will not fix it.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
elif report["managed_asset_integrity_failed"]:
|
||||
|
||||
@@ -134,6 +134,11 @@ def _inspect_minidump_bytes(data: bytes) -> dict[str, object] | None:
|
||||
exception_code = _read_u32(data, exception_rva + 8)
|
||||
exception_flags = _read_u32(data, exception_rva + 12)
|
||||
exception_address = _read_u64(data, exception_rva + 24)
|
||||
exception_parameters = _read_exception_parameters(
|
||||
data,
|
||||
exception_rva,
|
||||
_exception_size,
|
||||
)
|
||||
modules = _read_modules(data, streams.get(_MODULE_LIST_STREAM_TYPE))
|
||||
exception_module = _module_for_address(modules, exception_address)
|
||||
architecture = _read_architecture(data, streams.get(_SYSTEM_INFO_STREAM_TYPE))
|
||||
@@ -154,14 +159,25 @@ def _inspect_minidump_bytes(data: bytes) -> dict[str, object] | None:
|
||||
),
|
||||
}
|
||||
)
|
||||
stack_pointer = _read_exception_stack_pointer(
|
||||
data,
|
||||
exception_stream,
|
||||
architecture,
|
||||
)
|
||||
stack = _read_exception_stack(
|
||||
data,
|
||||
streams.get(_THREAD_LIST_STREAM_TYPE),
|
||||
thread_id,
|
||||
stack_pointer=stack_pointer,
|
||||
)
|
||||
if stack is not None:
|
||||
pointer_size = 8 if architecture in {"x86_64", "arm64", "ia64"} else 4
|
||||
signatures = _application_stack_signatures(stack, pointer_size, modules)
|
||||
signatures = _application_stack_signatures(
|
||||
stack,
|
||||
pointer_size,
|
||||
modules,
|
||||
excluded_addresses=frozenset(exception_parameters),
|
||||
)
|
||||
if signatures:
|
||||
result["application_stack"] = signatures
|
||||
return result
|
||||
@@ -211,6 +227,8 @@ def _read_exception_stack(
|
||||
data: bytes,
|
||||
stream: tuple[int, int] | None,
|
||||
thread_id: int,
|
||||
*,
|
||||
stack_pointer: int | None = None,
|
||||
) -> bytes | None:
|
||||
if stream is None or stream[1] < 4:
|
||||
return None
|
||||
@@ -222,10 +240,55 @@ def _read_exception_stack(
|
||||
offset = rva + 4 + index * _THREAD_ENTRY_SIZE
|
||||
if _read_u32(data, offset) != thread_id:
|
||||
continue
|
||||
stack_size = min(_read_u32(data, offset + 32), _MAX_STACK_BYTES)
|
||||
stack_start = _read_u64(data, offset + 24)
|
||||
reported_stack_size = _read_u32(data, offset + 32)
|
||||
stack_rva = _read_u32(data, offset + 36)
|
||||
_require_range(data, stack_rva, stack_size)
|
||||
return data[stack_rva : stack_rva + stack_size]
|
||||
stack_offset = 0
|
||||
if (
|
||||
stack_pointer is not None
|
||||
and stack_start <= stack_pointer < stack_start + reported_stack_size
|
||||
):
|
||||
stack_offset = stack_pointer - stack_start
|
||||
stack_size = min(
|
||||
reported_stack_size - stack_offset,
|
||||
_MAX_STACK_BYTES,
|
||||
)
|
||||
_require_range(data, stack_rva + stack_offset, stack_size)
|
||||
return data[stack_rva + stack_offset : stack_rva + stack_offset + stack_size]
|
||||
return None
|
||||
|
||||
|
||||
def _read_exception_parameters(
|
||||
data: bytes,
|
||||
exception_rva: int,
|
||||
exception_size: int,
|
||||
) -> tuple[int, ...]:
|
||||
parameter_count = _read_u32(data, exception_rva + 32)
|
||||
if parameter_count > 15 or 40 + parameter_count * 8 > exception_size:
|
||||
raise ValueError("invalid minidump exception parameters")
|
||||
return tuple(
|
||||
_read_u64(data, exception_rva + 40 + index * 8)
|
||||
for index in range(parameter_count)
|
||||
)
|
||||
|
||||
|
||||
def _read_exception_stack_pointer(
|
||||
data: bytes,
|
||||
stream: tuple[int, int],
|
||||
architecture: str,
|
||||
) -> int | None:
|
||||
rva, size = stream
|
||||
if size < 168:
|
||||
return None
|
||||
context_size = _read_u32(data, rva + 160)
|
||||
context_rva = _read_u32(data, rva + 164)
|
||||
if context_size == 0 or context_rva == 0:
|
||||
return None
|
||||
_require_range(data, context_rva, context_size)
|
||||
if architecture == "x86" and context_size >= 200:
|
||||
return _read_u32(data, context_rva + 196)
|
||||
if architecture == "x86_64" and context_size >= 160:
|
||||
return _read_u64(data, context_rva + 152)
|
||||
return None
|
||||
|
||||
|
||||
@@ -233,12 +296,16 @@ def _application_stack_signatures(
|
||||
stack: bytes,
|
||||
pointer_size: int,
|
||||
modules: tuple[_Module, ...],
|
||||
*,
|
||||
excluded_addresses: frozenset[int] = frozenset(),
|
||||
) -> list[dict[str, str]]:
|
||||
result: list[dict[str, str]] = []
|
||||
seen: set[tuple[str, int]] = set()
|
||||
format_code = "<Q" if pointer_size == 8 else "<I"
|
||||
for offset in range(0, len(stack) - pointer_size + 1, pointer_size):
|
||||
address = struct.unpack_from(format_code, stack, offset)[0]
|
||||
if address in excluded_addresses:
|
||||
continue
|
||||
module = _module_for_address(modules, address)
|
||||
if module is None or module.system:
|
||||
continue
|
||||
|
||||
@@ -476,6 +476,10 @@ def run_game(
|
||||
*game_arguments,
|
||||
]
|
||||
environment = _umu_environment(paths, selection)
|
||||
# Build 22.80.90 can consume an uninitialized heap value as a vector
|
||||
# length under current Wine. Proton's scoped heap-zero mode prevents the
|
||||
# resulting std::length_error without changing publisher files.
|
||||
environment["PROTON_HEAP_ZERO_MEMORY"] = "1"
|
||||
elif selection.kind == "proton7-container":
|
||||
if not selection.proton_path:
|
||||
raise RuntimeErrorPT("Proton script path is missing")
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
"application": "PristonTale-EU",
|
||||
"archive": {
|
||||
"layout": "flat",
|
||||
"name": "PristonTale-EU-1.0.5.tar.gz",
|
||||
"sha256": "31414eefc52325a532ded83166d083c4d8d4a6699bfa92d619197b879e6d6578",
|
||||
"size": 1544157,
|
||||
"url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v1.0.5/PristonTale-EU-1.0.5.tar.gz?sha256=31414eefc52325a532ded83166d083c4d8d4a6699bfa92d619197b879e6d6578"
|
||||
"name": "PristonTale-EU-1.0.8.tar.gz",
|
||||
"sha256": "f9e48505b17475c4b5de6d4ebc6835f3dfcb816aeeac3ae0b4c42cce546177a4",
|
||||
"size": 1544812,
|
||||
"url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v1.0.8/PristonTale-EU-1.0.8.tar.gz?sha256=f9e48505b17475c4b5de6d4ebc6835f3dfcb816aeeac3ae0b4c42cce546177a4"
|
||||
},
|
||||
"archive_sha256": "31414eefc52325a532ded83166d083c4d8d4a6699bfa92d619197b879e6d6578",
|
||||
"archive_size": 1544157,
|
||||
"archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v1.0.5/PristonTale-EU-1.0.5.tar.gz?sha256=31414eefc52325a532ded83166d083c4d8d4a6699bfa92d619197b879e6d6578",
|
||||
"archive_sha256": "f9e48505b17475c4b5de6d4ebc6835f3dfcb816aeeac3ae0b4c42cce546177a4",
|
||||
"archive_size": 1544812,
|
||||
"archive_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/download/v1.0.8/PristonTale-EU-1.0.8.tar.gz?sha256=f9e48505b17475c4b5de6d4ebc6835f3dfcb816aeeac3ae0b4c42cce546177a4",
|
||||
"channel": "stable",
|
||||
"commit": "67d89b40d54f1bf6d1886cd2cdbd35b0fa8b4324",
|
||||
"commit": "6a508476b90955705cdec106ba2151bd53d1f824",
|
||||
"compatibility": {
|
||||
"config_schema": 1,
|
||||
"manifest_schema": 1,
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/__init__.py",
|
||||
"sha256": "012bb02392f8b6441d2f7f53b71b5736d70495409ee01d52ff1677dd68931cea",
|
||||
"sha256": "f8dcaf56174e23070242e4df9a06d6eb0e7a9b8d221ffaa6465db96227bb2976",
|
||||
"size": 151
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/application/commands/play.py",
|
||||
"sha256": "7591103e801972398c2d1bd683518b9149c0903b3f6bd3de79c185f66c0b42f6",
|
||||
"size": 34941
|
||||
"sha256": "d4160bad360c7ba4299930b1040565468458ec4f88320220da0e2d29cf64b233",
|
||||
"size": 36758
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/application/commands/settings.py",
|
||||
@@ -126,8 +126,8 @@
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/diagnostics/crash_dumps.py",
|
||||
"sha256": "fba6ce4110128d2e1d551e2ad720319e9e5f2b90740707b553a293c00c1d1ff6",
|
||||
"size": 8611
|
||||
"sha256": "b26e3805a1ef7f1ba464dec9f7ec5c453ecc450ea650183001c7703e6773d24a",
|
||||
"size": 10351
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/diagnostics/native_logs.py",
|
||||
@@ -206,8 +206,8 @@
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/game/runtime.py",
|
||||
"sha256": "461c2c5d1774fd3d6f998dfc981ba7b6df59a95414dd66f26e1a74b67f5af8b0",
|
||||
"size": 32162
|
||||
"sha256": "cdeba4551aab6c54f2704d2aabc964a61b54a2cf9006567cf94a0a6aaf281061",
|
||||
"size": 32421
|
||||
},
|
||||
{
|
||||
"path": "ptlinux/game/runtime_view.py",
|
||||
@@ -696,21 +696,21 @@
|
||||
},
|
||||
{
|
||||
"path": "release.json",
|
||||
"sha256": "3cf9be35e4a0db075d79956b11f8d21c9ae4c8be5169690300bae335df6e7d01",
|
||||
"sha256": "37426179c48b341bb457d376adaa415af3b28a009df574b0c0bb014a4e21d5fe",
|
||||
"size": 218
|
||||
}
|
||||
],
|
||||
"minimum_updater_version": "0.9.9",
|
||||
"notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v1.0.5",
|
||||
"published_at": "2026-09-15T19:15:39Z",
|
||||
"notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v1.0.8",
|
||||
"published_at": "2026-09-18T19:36:12Z",
|
||||
"release": {
|
||||
"notes_markdown": "# PristonTale EU Launcher 1.0.5\n\nAdds opt-in, bounded game crash-dump attachments for telemetry, improves game-exit diagnostics and Proton 7 crash classification, and packages the current launcher, runtime compatibility, and native helper updates.\n\n## Changes\n\n- Updated telemetry to support game dumps. (`67d89b4`)\n- Released version 1.0.2 (`c278efa`)\n- Reworked some game window and gamescope methods for Cinnamon. (`f3059c3`)\n- Added more folders and restructured classes inside them. (`2cbb931`)\n- Reworked the project structure. (`be7a478`)\n- Delete RELEASE_PIPELINE_WORKPLAN.md (`15741f5`)\n- Add channel-aware release tooling and updater recovery (`0a89323`)\n- Publish signed launcher release history (`cc48bf4`)\n- Upload files to \"updates\" (`84f8de5`)\n- Delete updates/stable.json.sig (`1329485`)\n- Delete updates/stable.json (`8f1655b`)\n- Fix v0.9.11 archive download cache (`be71d31`)\n- Upload files to \"updates\" (`46e11ae`)\n- Delete updates/stable.json.sig (`eeb993e`)\n- Delete updates/stable.json (`a90bd19`)\n\n---\n\nVersion: `1.0.5` \nChannel: `stable` \nTag: `v1.0.5` \nSource commit: `67d89b40d54f1bf6d1886cd2cdbd35b0fa8b4324`\n",
|
||||
"release_notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v1.0.5",
|
||||
"summary": "Adds opt-in, bounded game crash-dump attachments for telemetry, improves game-exit diagnostics and Proton 7 crash classification, and packages the current launcher, runtime compatibility, and native helper updates.",
|
||||
"title": "PristonTale EU Launcher 1.0.5"
|
||||
"notes_markdown": "# PristonTale EU Launcher 1.0.8\n\nPrevents the Build 22.80.90 startup crash under UMU-Proton 10 and packages the\ncurrent launcher, diagnostics, telemetry, and native compatibility updates.\n\n## Changes\n\n- Enable Proton's heap-zero compatibility mode for UMU game launches to prevent\n the reproduced `std::length_error(\"vector too long\")` startup failure.\n- Improve crash-dump stack analysis and privacy-safe asset-failure telemetry.\n- Improve Direct3D resource-exhaustion handling and current runtime packaging.\n\n---\n\nVersion: `1.0.8` \nChannel: `stable` \nTag: `v1.0.8` \nSource commit: `6a508476b90955705cdec106ba2151bd53d1f824`\n",
|
||||
"release_notes_url": "https://git.m4ko.net/m4ko/pristontale-eu-linux/releases/tag/v1.0.8",
|
||||
"summary": "Prevents the Build 22.80.90 startup crash under UMU-Proton 10 and packages the",
|
||||
"title": "PristonTale EU Launcher 1.0.8"
|
||||
},
|
||||
"schema": 1,
|
||||
"status": "active",
|
||||
"tag": "v1.0.5",
|
||||
"version": "1.0.5"
|
||||
"tag": "v1.0.8",
|
||||
"version": "1.0.8"
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user