Tekken 3 update script added.

This commit is contained in:
Storm Dragon committed 2026-10-17 03:37:12 -04:00
1 parent 4886578fc5
commit d5b688314c
5 files changed
+169 -16

No files matched your search

+1 -1
View File
@@ -86,7 +86,7 @@ prepare_prism_bridge() {
prepare_tekken3_prism_bridge() { prepare_tekken3_prism_bridge() {
local gameDir="$1" local gameDir="$1"
local prismHash local prismHash
# Access 1.0 ships this exact Prism 0.18.3 DLL. Refuse an unverified update. # Access 1.0 and 1.0.1 ship this exact Prism 0.18.3 DLL.
prismHash="$(sha256sum "${gameDir}/T3Access/prism.dll")" || return 1 prismHash="$(sha256sum "${gameDir}/T3Access/prism.dll")" || return 1
if [[ "${prismHash%% *}" != "7c7d09c8c7306e8e1a0c46d603ccb2a391c194c77843400c11b746e7dd56a467" ]]; then if [[ "${prismHash%% *}" != "7c7d09c8c7306e8e1a0c46d603ccb2a391c194c77843400c11b746e7dd56a467" ]]; then
echo "Tekken 3 Access has an unrecognized Prism DLL; update its AGM integration before launching." >&2 echo "Tekken 3 Access has an unrecognized Prism DLL; update its AGM integration before launching." >&2
+2 -2
View File
@@ -26,10 +26,10 @@ fi
# Keep the base executable paired with the accessibility mod tested against it. # Keep the base executable paired with the accessibility mod tested against it.
tekkenPortCommit="b6e941378ad03dbd834fef340bd8d90bae6e430b" tekkenPortCommit="b6e941378ad03dbd834fef340bd8d90bae6e430b"
download "https://github.com/Muqaddas12/Tekken3-PC/archive/${tekkenPortCommit}.zip" \ download "https://github.com/Muqaddas12/Tekken3-PC/archive/${tekkenPortCommit}.zip" \
"https://github.com/BlindChallengerX/Tekken3-Access/releases/download/v1.0/Tekken3Access-1.0.zip" "https://github.com/BlindChallengerX/Tekken3-Access/releases/download/v1.0.1/Tekken3Access-1.0.1.zip"
install_wine_bottle install_wine_bottle
if ! tekkenSetupOutput="$(python3 "${scriptDir}/game-scripts/tekken3_setup.py" install \ if ! tekkenSetupOutput="$(python3 "${scriptDir}/game-scripts/tekken3_setup.py" install \
"$tekkenDiscDir" "${cache}/${tekkenPortCommit}.zip" "${cache}/Tekken3Access-1.0.zip" \ "$tekkenDiscDir" "${cache}/${tekkenPortCommit}.zip" "${cache}/Tekken3Access-1.0.1.zip" \
"${WINEPREFIX}/drive_c/Program Files/Tekken 3 Access" 2>&1)"; then "${WINEPREFIX}/drive_c/Program Files/Tekken 3 Access" 2>&1)"; then
alert "Tekken 3 installation" "$tekkenSetupOutput" alert "Tekken 3 installation" "$tekkenSetupOutput"
exit 1 exit 1
+22 -1
View File
@@ -85,7 +85,7 @@ downloads. The disc files are read without modification, outside AGM's download
cache. After installation, the extracted game data is sufficient for play. cache. After installation, the extracted game data is sufficient for play.
The installer downloads the tested PC-port commit The installer downloads the tested PC-port commit
`b6e941378ad03dbd834fef340bd8d90bae6e430b` and Access 1.0, verifies the mod `b6e941378ad03dbd834fef340bd8d90bae6e430b` and Access 1.0.1, verifies the mod
archive and the port executable/extractor, and uses native Linux Python 3 to archive and the port executable/extractor, and uses native Linux Python 3 to
extract game data. Allow approximately 1 GB for installed files, in addition to extract game data. Allow approximately 1 GB for installed files, in addition to
your original dump. Python 3 is an additional installer dependency. your original dump. Python 3 is an additional installer dependency.
@@ -108,6 +108,23 @@ controller; the mod's function keys still work. To play with the keyboard, set
Enter for Start, arrows to move, and Z/A/X/S for Square/Triangle/Cross/Circle. Enter for Start, arrows to move, and Z/A/X/S for Square/Triangle/Cross/Circle.
The documentation button opens the bundled manual, including the mod keys. The documentation button opens the bundled manual, including the mod keys.
To update an existing AGM installation, close Tekken 3 and run from this checkout:
```bash
bash game-scripts/tekken3-update.sh
```
This installs the verified Access 1.0.1 release without the original disc dump
or another game-data extraction. It checks the installed port executable,
preserves settings, controller selection, memory cards, logs and mod preferences,
and refreshes the manual. Replaced files are backed up beside the game folder;
the completion message gives the backup path. Future releases need a compatibility
check before the pinned update is changed.
In 1.0.1, Shift+F1 raises the distance hum volume and Control+F1 lowers it, with
ten steps and zero for off. The chosen level is remembered. Fight sounds go
quiet while the pause menu or command list is open and resume with the fight.
Before submitting changes, test installation with missing or incorrect disc Before submitting changes, test installation with missing or incorrect disc
files and with a valid USA dump. In the installed game, check the loaded files and with a valid USA dump. In the installed game, check the loaded
announcement, menu and character selection, F1 distance/F2 health, fight sounds, announcement, menu and character selection, F1 distance/F2 health, fight sounds,
@@ -115,6 +132,10 @@ pause/resume, controller input if available, and saving/relaunching. Also check
Arcadelux speech if modifying the shared Prism helper. Logs are Arcadelux speech if modifying the shared Prism helper. Logs are
`T3Access/T3Access.log` and `T3Access/launch.log` in the game folder; copy them `T3Access/T3Access.log` and `T3Access/launch.log` in the game folder; copy them
before another launch overwrites them. before another launch overwrites them.
After updating, check both hum-volume shortcuts, level zero, and persistence
after restarting. Check sound suppression and resumption in a normal fight,
Tekken Force and Tekken Ball, including the pause command list. Confirm your
controller/keyboard selection and existing memory-card saves still work.
## AI-assisted contributions ## AI-assisted contributions
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# shellcheck disable=SC1091 # AGM includes are resolved relative to this script.
scriptDir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.."
# shellcheck source=.includes/dialog-interface.sh
source "${scriptDir}/.includes/dialog-interface.sh"
# shellcheck source=.includes/functions.sh
source "${scriptDir}/.includes/functions.sh"
export game="Tekken 3"
cache="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager"
gamePath="$HOME/.local/wine64/drive_c/Program Files/Tekken 3 Access"
if ! command -v python3 >/dev/null; then
alert "Tekken 3 Update" "Install native Linux Python 3, then run this updater again."
exit 1
fi
if ! updateOutput="$(python3 "${scriptDir}/game-scripts/tekken3_setup.py" validate-installation "$gamePath" 2>&1)"; then
alert "Tekken 3 Update" "$updateOutput"
exit 1
fi
if ! mkdir -p "$cache"; then
alert "Tekken 3 Update" "Could not create the download cache: ${cache}"
exit 1
fi
if ! download "https://github.com/BlindChallengerX/Tekken3-Access/releases/download/v1.0.1/Tekken3Access-1.0.1.zip"; then
alert "Tekken 3 Update" "Could not download Tekken 3 Access 1.0.1."
exit 1
fi
if ! updateOutput="$(python3 "${scriptDir}/game-scripts/tekken3_setup.py" update \
"${cache}/Tekken3Access-1.0.1.zip" "$gamePath" 2>&1)"; then
alert "Tekken 3 Update" "$updateOutput"
exit 1
fi
agm_msgbox "Tekken 3 Update" "Tekken 3 Update" "$updateOutput"
+109 -12
View File
@@ -3,6 +3,7 @@
import argparse import argparse
import hashlib import hashlib
import os
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
import re import re
import shutil import shutil
@@ -16,7 +17,8 @@ from zipfile import BadZipFile, ZipFile
PORT_COMMIT = "b6e941378ad03dbd834fef340bd8d90bae6e430b" PORT_COMMIT = "b6e941378ad03dbd834fef340bd8d90bae6e430b"
PORT_EXE_HASH = "25d038f55d7a1c02a0e5bbcc97fe9b1738c8292198e7fa0d1b171b57129d18c0" PORT_EXE_HASH = "25d038f55d7a1c02a0e5bbcc97fe9b1738c8292198e7fa0d1b171b57129d18c0"
SETUP_HASH = "c1e0430498b2f4212d7e04187e3ba822d3ac273de487cd35d93aa2ef823ac5aa" SETUP_HASH = "c1e0430498b2f4212d7e04187e3ba822d3ac273de487cd35d93aa2ef823ac5aa"
MOD_ARCHIVE_HASH = "bacaf33cda0ba615bffe4082a9b230cdb9d9f63327f78baf76dec9098381a1d6" MOD_ARCHIVE_HASH = "7aacee1ce376dff83823a6fc9c6717ad31e94cd37fa85551a3a8e8048e709646"
PRISM_HASH = "7c7d09c8c7306e8e1a0c46d603ccb2a391c194c77843400c11b746e7dd56a467"
USA_EXE_HASH = "fbda8b68e5799dbef4af39a161783bc670c15b0aa0e87dce65e210717da19b8c" USA_EXE_HASH = "fbda8b68e5799dbef4af39a161783bc670c15b0aa0e87dce65e210717da19b8c"
TRACK_SIZES = (632532768, 27701856, 28042896) TRACK_SIZES = (632532768, 27701856, 28042896)
TRACK_NAMES = tuple(f"Tekken 3 (USA) (Track {trackNumber}).bin" for trackNumber in range(1, 4)) TRACK_NAMES = tuple(f"Tekken 3 (USA) (Track {trackNumber}).bin" for trackNumber in range(1, 4))
@@ -74,6 +76,102 @@ def verify_hash(path, expectedHash):
raise ValueError(f"{path.name} does not match the tested version. Remove the cached download and retry.") raise ValueError(f"{path.name} does not match the tested version. Remove the cached download and retry.")
def write_manual(gameRoot):
manualText = (
"Tekken 3 Access under Wine\n\n"
"Keyboard: Enter starts; arrows move; Z/A/X/S are Square/Triangle/Cross/Circle.\n"
'Controller mode is enabled by default (p1_device = "auto"); connect your controller before launch.\n'
'To play with the keyboard, set p1_device = "keyboard" in settings.toml and restart.\n'
"The mod function keys work in either mode.\n"
"Alt+F4 exits. Settings, memory cards and logs are in this game folder.\n"
"AGM uses Prism's native Speech Dispatcher bridge for speech.\n"
"The upstream manual follows.\n\n"
)
(gameRoot / "manual.txt").write_text(manualText + (gameRoot / "T3Access/README.txt").read_text())
def check_game_stopped():
for processPath in Path("/proc").iterdir():
if not processPath.name.isdigit():
continue
try:
if processPath.stat().st_uid != os.getuid():
continue
arguments = (processPath / "cmdline").read_bytes().split(b"\0")
except (FileNotFoundError, ProcessLookupError, PermissionError):
continue
if any(argument.replace(b"\\", b"/").rsplit(b"/", 1)[-1].lower()
in (b"tekken3.exe", b"play tekken 3 access.exe") for argument in arguments):
raise ValueError("Close Tekken 3 and its access launcher before updating.")
def validate_installation(destination):
check_game_stopped()
if destination.is_symlink():
raise ValueError("The game directory must not be a symbolic link.")
verify_hash(destination / "Tekken3.exe", PORT_EXE_HASH)
for name in ("Play Tekken 3 Access.exe", "T3Access/T3Access.dll", "game_data/disc.cue", "settings.toml"):
if not (destination / name).is_file():
raise ValueError(f"The existing installation is missing {name}. Install Tekken 3 through AGM first.")
def update_game(modArchive, destination):
validate_installation(destination)
verify_hash(modArchive, MOD_ARCHIVE_HASH)
with tempfile.TemporaryDirectory(prefix=".tekken3-update-", dir=destination.parent) as temporaryDir:
gameRoot = Path(temporaryDir) / "mod"
gameRoot.mkdir()
extract_archive(modArchive, gameRoot)
for name in ("Play Tekken 3 Access.exe", "T3Access/T3Access.dll", "T3Access/README.txt"):
if not (gameRoot / name).is_file():
raise ValueError(f"The update is missing {name}.")
verify_hash(gameRoot / "T3Access/prism.dll", PRISM_HASH)
write_manual(gameRoot)
relativeFiles = sorted(path.relative_to(gameRoot) for path in gameRoot.rglob("*") if path.is_file())
for relativePath in relativeFiles:
targetPath = destination / relativePath
for path in (targetPath, *targetPath.parents):
if path == destination.parent:
break
if path.is_symlink():
raise ValueError(f"Update target must not be a symbolic link: {path}")
if targetPath.exists() and not targetPath.is_file():
raise ValueError(f"Update target is not a file: {targetPath}")
for parentPath in targetPath.parents:
if parentPath == destination:
break
if parentPath.exists() and not parentPath.is_dir():
raise ValueError(f"Update target parent is not a directory: {parentPath}")
backupRoot = Path(tempfile.mkdtemp(prefix=".tekken3-access-backup-", dir=destination.parent))
for relativePath in relativeFiles:
sourcePath = destination / relativePath
if sourcePath.exists():
backupPath = backupRoot / relativePath
backupPath.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(sourcePath, backupPath)
check_game_stopped()
appliedFiles = []
try:
for relativePath in relativeFiles:
targetPath = destination / relativePath
targetPath.parent.mkdir(parents=True, exist_ok=True)
(gameRoot / relativePath).replace(targetPath)
appliedFiles.append(relativePath)
except OSError as error:
try:
for relativePath in reversed(appliedFiles):
targetPath = destination / relativePath
backupPath = backupRoot / relativePath
if backupPath.exists():
shutil.copy2(backupPath, targetPath)
else:
targetPath.unlink()
except OSError as restoreError:
raise OSError(f"Update and restoration failed. Restore files from {backupRoot}. {restoreError}") from error
raise OSError(f"Update failed; replaced files restored. Backup: {backupRoot}. {error}") from error
print(f"Updated Tekken 3 Access to 1.0.1. Backup of replaced files: {backupRoot}")
def install_game(discDir, portArchive, modArchive, destination): def install_game(discDir, portArchive, modArchive, destination):
validate_disc(discDir) validate_disc(discDir)
verify_hash(modArchive, MOD_ARCHIVE_HASH) verify_hash(modArchive, MOD_ARCHIVE_HASH)
@@ -94,17 +192,7 @@ def install_game(discDir, portArchive, modArchive, destination):
settingsPath = gameRoot / "settings.toml" settingsPath = gameRoot / "settings.toml"
settingsText = settingsPath.read_text() settingsText = settingsPath.read_text()
settingsPath.write_text(settingsText.replace("fullscreen = 1", "fullscreen = 0").replace("supersampling = 3", "supersampling = 1").replace("window_width = 1920", "window_width = 960").replace('p1_device = "keyboard"', 'p1_device = "auto"')) settingsPath.write_text(settingsText.replace("fullscreen = 1", "fullscreen = 0").replace("supersampling = 3", "supersampling = 1").replace("window_width = 1920", "window_width = 960").replace('p1_device = "keyboard"', 'p1_device = "auto"'))
manualText = ( write_manual(gameRoot)
"Tekken 3 Access under Wine\n\n"
"Keyboard: Enter starts; arrows move; Z/A/X/S are Square/Triangle/Cross/Circle.\n"
'Controller mode is enabled (p1_device = "auto"); connect your controller before launch.\n'
'To play with the keyboard, set p1_device = "keyboard" in settings.toml and restart.\n'
"The mod function keys work in either mode.\n"
"Alt+F4 exits. Settings, memory cards and logs are in this game folder.\n"
"AGM uses Prism's native Speech Dispatcher bridge for speech.\n"
"The upstream manual follows.\n\n"
)
(gameRoot / "manual.txt").write_text(manualText + (gameRoot / "T3Access/README.txt").read_text())
if destination.exists(): if destination.exists():
destination.rmdir() # Only an empty destination is accepted above. destination.rmdir() # Only an empty destination is accepted above.
gameRoot.rename(destination) gameRoot.rename(destination)
@@ -116,6 +204,11 @@ def main():
subparsers = parser.add_subparsers(dest="command", required=True) subparsers = parser.add_subparsers(dest="command", required=True)
validateParser = subparsers.add_parser("validate-disc") validateParser = subparsers.add_parser("validate-disc")
validateParser.add_argument("discDir", type=Path) validateParser.add_argument("discDir", type=Path)
installedParser = subparsers.add_parser("validate-installation")
installedParser.add_argument("destination", type=Path)
updateParser = subparsers.add_parser("update")
updateParser.add_argument("modArchive", type=Path)
updateParser.add_argument("destination", type=Path)
installParser = subparsers.add_parser("install") installParser = subparsers.add_parser("install")
for name in ("discDir", "portArchive", "modArchive", "destination"): for name in ("discDir", "portArchive", "modArchive", "destination"):
installParser.add_argument(name, type=Path) installParser.add_argument(name, type=Path)
@@ -124,6 +217,10 @@ def main():
if args.command == "validate-disc": if args.command == "validate-disc":
validate_disc(args.discDir) validate_disc(args.discDir)
print("Tekken 3 (USA) disc files validated.") print("Tekken 3 (USA) disc files validated.")
elif args.command == "validate-installation":
validate_installation(args.destination)
elif args.command == "update":
update_game(args.modArchive, args.destination)
else: else:
install_game(args.discDir, args.portArchive, args.modArchive, args.destination) install_game(args.discDir, args.portArchive, args.modArchive, args.destination)
except (OSError, ValueError, BadZipFile, subprocess.CalledProcessError) as error: except (OSError, ValueError, BadZipFile, subprocess.CalledProcessError) as error: