Some minor changes to support arch distros.
This commit is contained in:
@@ -121,6 +121,9 @@ ptlinux_resolve_dependency_packages()
|
|||||||
arch:openssl)
|
arch:openssl)
|
||||||
candidates=(openssl)
|
candidates=(openssl)
|
||||||
;;
|
;;
|
||||||
|
arch:umu)
|
||||||
|
candidates=(umu-launcher)
|
||||||
|
;;
|
||||||
suse:python)
|
suse:python)
|
||||||
candidates=(python3)
|
candidates=(python3)
|
||||||
;;
|
;;
|
||||||
@@ -210,6 +213,7 @@ ptlinux_has_wine_stack()
|
|||||||
ptlinux_detect_missing_dependencies()
|
ptlinux_detect_missing_dependencies()
|
||||||
{
|
{
|
||||||
local python_bin="${PYTHON_BIN:-python3}"
|
local python_bin="${PYTHON_BIN:-python3}"
|
||||||
|
local family="${1:-}"
|
||||||
PTLINUX_MISSING_DEPENDENCIES=()
|
PTLINUX_MISSING_DEPENDENCIES=()
|
||||||
|
|
||||||
if ! command -v "$python_bin" >/dev/null 2>&1
|
if ! command -v "$python_bin" >/dev/null 2>&1
|
||||||
@@ -227,10 +231,6 @@ ptlinux_detect_missing_dependencies()
|
|||||||
then
|
then
|
||||||
PTLINUX_MISSING_DEPENDENCIES+=(winetricks)
|
PTLINUX_MISSING_DEPENDENCIES+=(winetricks)
|
||||||
fi
|
fi
|
||||||
if ! command -v gamescope >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
PTLINUX_MISSING_DEPENDENCIES+=(gamescope)
|
|
||||||
fi
|
|
||||||
if ! command -v cabextract >/dev/null 2>&1
|
if ! command -v cabextract >/dev/null 2>&1
|
||||||
then
|
then
|
||||||
PTLINUX_MISSING_DEPENDENCIES+=(cabextract)
|
PTLINUX_MISSING_DEPENDENCIES+=(cabextract)
|
||||||
@@ -239,6 +239,10 @@ ptlinux_detect_missing_dependencies()
|
|||||||
then
|
then
|
||||||
PTLINUX_MISSING_DEPENDENCIES+=(openssl)
|
PTLINUX_MISSING_DEPENDENCIES+=(openssl)
|
||||||
fi
|
fi
|
||||||
|
if [[ "$family" == "arch" ]] && ! command -v umu-run >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
PTLINUX_MISSING_DEPENDENCIES+=(umu)
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ptlinux_select_privilege_command()
|
ptlinux_select_privilege_command()
|
||||||
@@ -344,15 +348,14 @@ ptlinux_install_host_dependencies()
|
|||||||
local package_line
|
local package_line
|
||||||
local -a packages=()
|
local -a packages=()
|
||||||
|
|
||||||
ptlinux_detect_missing_dependencies
|
ptlinux_read_distribution
|
||||||
|
family="$(ptlinux_dependency_family "$PTLINUX_DISTRIBUTION_ID" "$PTLINUX_DISTRIBUTION_LIKE")"
|
||||||
|
ptlinux_detect_missing_dependencies "$family"
|
||||||
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} == 0 ))
|
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} == 0 ))
|
||||||
then
|
then
|
||||||
printf '%s\n' "All required host dependencies are already available."
|
printf '%s\n' "All required host dependencies are already available."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ptlinux_read_distribution
|
|
||||||
family="$(ptlinux_dependency_family "$PTLINUX_DISTRIBUTION_ID" "$PTLINUX_DISTRIBUTION_LIKE")"
|
|
||||||
if [[ "$family" == "unknown" ]]
|
if [[ "$family" == "unknown" ]]
|
||||||
then
|
then
|
||||||
printf 'Unsupported distribution: %s. Re-run with --skip-deps after installing: %s\n' \
|
printf 'Unsupported distribution: %s. Re-run with --skip-deps after installing: %s\n' \
|
||||||
@@ -374,7 +377,7 @@ ptlinux_install_host_dependencies()
|
|||||||
printf '%s\n' "Installing missing host dependencies (authentication may be requested)."
|
printf '%s\n' "Installing missing host dependencies (authentication may be requested)."
|
||||||
ptlinux_install_resolved_packages "$family" "${packages[@]}"
|
ptlinux_install_resolved_packages "$family" "${packages[@]}"
|
||||||
hash -r
|
hash -r
|
||||||
ptlinux_detect_missing_dependencies
|
ptlinux_detect_missing_dependencies "$family"
|
||||||
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} != 0 ))
|
if (( ${#PTLINUX_MISSING_DEPENDENCIES[@]} != 0 ))
|
||||||
then
|
then
|
||||||
printf 'Dependencies remain unavailable after package installation: %s\n' \
|
printf 'Dependencies remain unavailable after package installation: %s\n' \
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ from .paths import AppPaths
|
|||||||
|
|
||||||
__all__ = ["AppPaths", "__version__"]
|
__all__ = ["AppPaths", "__version__"]
|
||||||
|
|
||||||
__version__ = "0.5.3"
|
__version__ = "0.5.8"
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ def inspect_host(environment: Mapping[str, str] | None = None) -> DoctorReport:
|
|||||||
ToolStatus(
|
ToolStatus(
|
||||||
"gamescope",
|
"gamescope",
|
||||||
shutil.which("gamescope"),
|
shutil.which("gamescope"),
|
||||||
True,
|
False,
|
||||||
"borderless X11/Wayland game container",
|
"optional manual game-container diagnostics",
|
||||||
),
|
),
|
||||||
ToolStatus(
|
ToolStatus(
|
||||||
"cabextract",
|
"cabextract",
|
||||||
@@ -147,22 +147,22 @@ def _python_tk_path() -> str | None:
|
|||||||
def _package_hint(distribution: str) -> str | None:
|
def _package_hint(distribution: str) -> str | None:
|
||||||
if distribution in {"ubuntu", "debian", "linuxmint", "pop"}:
|
if distribution in {"ubuntu", "debian", "linuxmint", "pop"}:
|
||||||
return (
|
return (
|
||||||
"Install Python/Tk, Wine (64- and 32-bit), Winetricks, Gamescope, "
|
"Install Python/Tk, Wine (64- and 32-bit), Winetricks, "
|
||||||
"Cabextract, OpenSSL, and both 64- and 32-bit Vulkan drivers with APT."
|
"Cabextract, OpenSSL, and both 64- and 32-bit Vulkan drivers with APT."
|
||||||
)
|
)
|
||||||
if distribution in {"fedora", "nobara"}:
|
if distribution in {"fedora", "nobara"}:
|
||||||
return (
|
return (
|
||||||
"Install python3-tkinter, wine, winetricks, gamescope, cabextract, openssl, "
|
"Install python3-tkinter, wine, winetricks, cabextract, openssl, "
|
||||||
"and the matching 32-bit Vulkan driver with DNF."
|
"and the matching 32-bit Vulkan driver with DNF."
|
||||||
)
|
)
|
||||||
if distribution in {"arch", "cachyos", "endeavouros", "manjaro"}:
|
if distribution in {"arch", "cachyos", "endeavouros", "manjaro"}:
|
||||||
return (
|
return (
|
||||||
"Install python, tk, wine, winetricks, gamescope, cabextract, openssl, and "
|
"Install python, tk, wine, winetricks, umu-launcher, cabextract, "
|
||||||
"the matching lib32 Vulkan driver with Pacman."
|
"openssl, and the matching lib32 Vulkan driver with Pacman."
|
||||||
)
|
)
|
||||||
if distribution in {"opensuse-tumbleweed", "opensuse-leap", "suse"}:
|
if distribution in {"opensuse-tumbleweed", "opensuse-leap", "suse"}:
|
||||||
return (
|
return (
|
||||||
"Install Python/Tk, Wine, Winetricks, Gamescope, Cabextract, OpenSSL, and "
|
"Install Python/Tk, Wine, Winetricks, Cabextract, OpenSSL, and "
|
||||||
"32-bit Vulkan support with Zypper."
|
"32-bit Vulkan support with Zypper."
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ def _validate(settings: Settings) -> None:
|
|||||||
if settings.refresh_rate < 24 or settings.refresh_rate > 1000:
|
if settings.refresh_rate < 24 or settings.refresh_rate > 1000:
|
||||||
raise ValueError("refresh_rate must be between 24 and 1000")
|
raise ValueError("refresh_rate must be between 24 and 1000")
|
||||||
if settings.scaler not in {"auto", "integer", "fit", "fill", "stretch"}:
|
if settings.scaler not in {"auto", "integer", "fit", "fill", "stretch"}:
|
||||||
raise ValueError(f"unsupported Gamescope scaler: {settings.scaler}")
|
raise ValueError(f"unsupported legacy scaler setting: {settings.scaler}")
|
||||||
if settings.filter not in {"linear", "nearest", "fsr", "nis", "pixel"}:
|
if settings.filter not in {"linear", "nearest", "fsr", "nis", "pixel"}:
|
||||||
raise ValueError(f"unsupported Gamescope filter: {settings.filter}")
|
raise ValueError(f"unsupported legacy filter setting: {settings.filter}")
|
||||||
if settings.runtime not in {"auto", "proton7", "umu"}:
|
if settings.runtime not in {"auto", "proton7", "proton7-container", "umu"}:
|
||||||
raise ValueError(f"unsupported runtime selection: {settings.runtime}")
|
raise ValueError(f"unsupported runtime selection: {settings.runtime}")
|
||||||
if settings.proton_sync not in {"auto", "fsync", "esync"}:
|
if settings.proton_sync not in {"auto", "fsync", "esync"}:
|
||||||
raise ValueError(f"unsupported Proton synchronization mode: {settings.proton_sync}")
|
raise ValueError(f"unsupported Proton synchronization mode: {settings.proton_sync}")
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
"""Build the verified V21 texture-reclaim runtime DLL from a local game DLL."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
import hashlib
|
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
|
||||||
import struct
|
|
||||||
|
|
||||||
|
|
||||||
SOURCE_SHA256 = "4b4e5c28e2bfdaa8d36c8341fad27becfd03faf63aa8ea874f5d43c5b1961868"
|
|
||||||
PATCHED_SHA256 = "0be11b06ccf9be747e32b195c8d5980062f446d7d3a0a662ebfe0e602a01c538"
|
|
||||||
TEXT_VA = 0x10001000
|
|
||||||
TEXT_FILE_OFFSET = 0x400
|
|
||||||
ENTRY_VA = 0x107C0B40
|
|
||||||
ENTRY_FILE_OFFSET = TEXT_FILE_OFFSET + (ENTRY_VA - TEXT_VA)
|
|
||||||
ENTRY_ORIGINAL = bytes.fromhex("55 8b ec 6a ff")
|
|
||||||
CAVE_VA = 0x11195CE0
|
|
||||||
CAVE_FILE_OFFSET = TEXT_FILE_OFFSET + (CAVE_VA - TEXT_VA)
|
|
||||||
TEXT_RAW_SIZE = 0x1194E00
|
|
||||||
MAX_HOOK_SIZE = TEXT_FILE_OFFSET + TEXT_RAW_SIZE - CAVE_FILE_OFFSET
|
|
||||||
HOOK = bytes.fromhex(
|
|
||||||
"9c60e8490136ff85c0744a89c38d70088dbe505d010039fe743b80be58030000"
|
|
||||||
"00742a8b068b4c242885c974083b81d41f000074188b4c242c85c974083b81d4"
|
|
||||||
"1f000074085089d9e8a32036ff81c650040000ebc1619d5589e56affe904ae62"
|
|
||||||
"ff"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TextureReclaimPatchError(RuntimeError):
|
|
||||||
"""Raised when the exact V21 binary profile cannot be patched safely."""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class TextureReclaimPatchResult:
|
|
||||||
source: str
|
|
||||||
destination: str
|
|
||||||
source_sha256: str
|
|
||||||
destination_sha256: str
|
|
||||||
entry_va: str
|
|
||||||
cave_va: str
|
|
||||||
hook_size: int
|
|
||||||
old_text_virtual_size: int
|
|
||||||
new_text_virtual_size: int
|
|
||||||
commands: tuple[tuple[str, ...], ...]
|
|
||||||
created_at: str
|
|
||||||
|
|
||||||
|
|
||||||
def build_texture_reclaim_patch(
|
|
||||||
source: Path,
|
|
||||||
destination: Path,
|
|
||||||
) -> TextureReclaimPatchResult:
|
|
||||||
"""Create the V21 patch in a separate runtime cache, never in the game tree."""
|
|
||||||
source = source.expanduser().resolve(strict=True)
|
|
||||||
if _sha256(source) != SOURCE_SHA256:
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
"source game.dll does not match the verified V21 profile"
|
|
||||||
)
|
|
||||||
|
|
||||||
hook = HOOK
|
|
||||||
commands: list[tuple[str, ...]] = []
|
|
||||||
if not hook or len(hook) > MAX_HOOK_SIZE:
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
f"compiled hook size {len(hook)} exceeds the {MAX_HOOK_SIZE}-byte code cave"
|
|
||||||
)
|
|
||||||
|
|
||||||
image = bytearray(source.read_bytes())
|
|
||||||
if bytes(image[ENTRY_FILE_OFFSET : ENTRY_FILE_OFFSET + len(ENTRY_ORIGINAL)]) != ENTRY_ORIGINAL:
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
"V21 map-loader entry bytes do not match the verified profile"
|
|
||||||
)
|
|
||||||
if any(image[CAVE_FILE_OFFSET : CAVE_FILE_OFFSET + len(hook)]):
|
|
||||||
raise TextureReclaimPatchError("V21 code cave is not empty")
|
|
||||||
|
|
||||||
text_header = _find_pe_section(image, b".text")
|
|
||||||
old_virtual_size, text_rva, raw_size, raw_offset = struct.unpack_from(
|
|
||||||
"<IIII", image, text_header + 8
|
|
||||||
)
|
|
||||||
if (text_rva, raw_size, raw_offset) != (
|
|
||||||
TEXT_VA - 0x10000000,
|
|
||||||
TEXT_RAW_SIZE,
|
|
||||||
TEXT_FILE_OFFSET,
|
|
||||||
):
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
"V21 executable section layout does not match the verified profile"
|
|
||||||
)
|
|
||||||
new_virtual_size = CAVE_VA - TEXT_VA + len(hook)
|
|
||||||
if new_virtual_size > TEXT_RAW_SIZE:
|
|
||||||
raise TextureReclaimPatchError("compiled hook extends beyond the executable section")
|
|
||||||
if old_virtual_size > new_virtual_size:
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
"verified executable section is already larger than the patch target"
|
|
||||||
)
|
|
||||||
|
|
||||||
entry_displacement = CAVE_VA - (ENTRY_VA + 5)
|
|
||||||
image[ENTRY_FILE_OFFSET : ENTRY_FILE_OFFSET + 5] = b"\xe9" + struct.pack(
|
|
||||||
"<i", entry_displacement
|
|
||||||
)
|
|
||||||
image[CAVE_FILE_OFFSET : CAVE_FILE_OFFSET + len(hook)] = hook
|
|
||||||
struct.pack_into("<I", image, text_header + 8, new_virtual_size)
|
|
||||||
|
|
||||||
destination = destination.expanduser().absolute()
|
|
||||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
temporary = destination.with_name(f".{destination.name}.tmp")
|
|
||||||
try:
|
|
||||||
temporary.write_bytes(image)
|
|
||||||
if _sha256(temporary) != PATCHED_SHA256:
|
|
||||||
raise TextureReclaimPatchError(
|
|
||||||
"built runtime patch does not match the verified V21 profile"
|
|
||||||
)
|
|
||||||
temporary.replace(destination)
|
|
||||||
shutil.copystat(source, destination)
|
|
||||||
except BaseException:
|
|
||||||
temporary.unlink(missing_ok=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
return TextureReclaimPatchResult(
|
|
||||||
source=str(source),
|
|
||||||
destination=str(destination),
|
|
||||||
source_sha256=SOURCE_SHA256,
|
|
||||||
destination_sha256=PATCHED_SHA256,
|
|
||||||
entry_va=f"0x{ENTRY_VA:08x}",
|
|
||||||
cave_va=f"0x{CAVE_VA:08x}",
|
|
||||||
hook_size=len(hook),
|
|
||||||
old_text_virtual_size=old_virtual_size,
|
|
||||||
new_text_virtual_size=new_virtual_size,
|
|
||||||
commands=tuple(commands),
|
|
||||||
created_at=datetime.now().astimezone().isoformat(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _find_pe_section(image: bytes | bytearray, section_name: bytes) -> int:
|
|
||||||
if len(image) < 0x40:
|
|
||||||
raise TextureReclaimPatchError("source is too small to be a PE image")
|
|
||||||
pe_offset = struct.unpack_from("<I", image, 0x3C)[0]
|
|
||||||
if image[pe_offset : pe_offset + 4] != b"PE\0\0":
|
|
||||||
raise TextureReclaimPatchError("source does not have a valid PE signature")
|
|
||||||
number_of_sections = struct.unpack_from("<H", image, pe_offset + 6)[0]
|
|
||||||
optional_header_size = struct.unpack_from("<H", image, pe_offset + 20)[0]
|
|
||||||
section_table = pe_offset + 24 + optional_header_size
|
|
||||||
for index in range(number_of_sections):
|
|
||||||
header = section_table + index * 40
|
|
||||||
name = bytes(image[header : header + 8]).rstrip(b"\0")
|
|
||||||
if name == section_name:
|
|
||||||
return header
|
|
||||||
raise TextureReclaimPatchError(f"PE section is missing: {section_name!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def _sha256(path: Path) -> str:
|
|
||||||
digest = hashlib.sha256()
|
|
||||||
with path.open("rb") as handle:
|
|
||||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
||||||
digest.update(chunk)
|
|
||||||
return digest.hexdigest()
|
|
||||||
Reference in New Issue
Block a user