feat: add SpecialK injection support #187

Manually merged
CastroFidel merged 2 commits from scripts-specialk into main 2026-06-03 17:32:40 +00:00
6 changed files with 263 additions and 6 deletions

No files matched your search

@@ -2403,7 +2403,8 @@ pw_optiscaler_sync_files () {
done
if [[ "${PW_USE_OPTISCALER}" != "1" ]] ; then
if [[ -f "$PATH_TO_GAME/dlssg_to_fsr3_amd_is_better.dll" ]] ; then
if [[ -f "$PATH_TO_GAME/optiscaler_version" ]]
then
for dll in $OPTISCALER_FILES ; do rm -fr "$PATH_TO_GAME/$dll"; done
for dll in $ORIGINAL_GAME_DLLS ; do [[ -e "$PATH_TO_GAME/${dll}.b" ]] && mv -f "$PATH_TO_GAME/${dll}.b" "$PATH_TO_GAME/${dll}" 2>/dev/null; done
try_remove_file "$PATH_TO_GAME/optiscaler_version"
@@ -2420,10 +2421,19 @@ pw_optiscaler_sync_files () {
check_variables OPTISCALER_FG_TYPE "Nukems"
sed -i "s|FGType =.*|FGType = $OPTISCALER_FG_TYPE|g" "$OPTISCALER_PATH/OptiScaler.ini"
echo "" > "$PATH_TO_GAME/dxvk.conf"
if [[ -f "$PATH_TO_GAME/dxgi.dll" ]] \
&& [[ -f "$PATH_TO_GAME/specialk_version" ]]
then
create_new_dir "$PATH_TO_GAME/plugins"
mv -f "$PATH_TO_GAME/dxgi.dll" "$PATH_TO_GAME/plugins/dxgi.dll"
mv -f "$PATH_TO_GAME/specialk_version" "$PATH_TO_GAME/plugins/specialk_version"
fi
if [[ ! -f "$PATH_TO_GAME/dlssg_to_fsr3_amd_is_better.dll" && -f "$PATH_TO_GAME/dxgi.dll" ]] ; then
fatal 'dxgi.dll is already present in the game folder!\nThis script uses dxgi.dll to load required files.\nRemove the mod using dxgi.dll.'
fi
for dll in $ORIGINAL_GAME_DLLS ; do [[ -e "$PATH_TO_GAME/$dll" && ! -e "$PATH_TO_GAME/${dll}.b" ]] && mv -f "$PATH_TO_GAME/$dll" "$PATH_TO_GAME/${dll}.b" 2>/dev/null; done
if [[ ! -f "$PATH_TO_GAME/optiscaler_version" ]] ; then
for dll in $ORIGINAL_GAME_DLLS ; do [[ -e "$PATH_TO_GAME/$dll" && ! -e "$PATH_TO_GAME/${dll}.b" ]] && mv -f "$PATH_TO_GAME/$dll" "$PATH_TO_GAME/${dll}.b" 2>/dev/null; done
fi
if [[ ! -f "$PATH_TO_GAME/optiscaler_version" ]] || ! grep -q "$PW_OPTISCALER_VER" "$PATH_TO_GAME/optiscaler_version" ; then
for dll in $OPTISCALER_FILES ; do cp -fr "$OPTISCALER_PATH/$dll" "$PATH_TO_GAME/$dll" || print_error "$OPTISCALER_PATH/$dll not found for copy!"; done
echo "$PW_OPTISCALER_VER" > "$PATH_TO_GAME/optiscaler_version"
@@ -2432,6 +2442,113 @@ pw_optiscaler_sync_files () {
set_to_dxvk_conf nvidia_new
}
pw_specialk_set_ini_value () {
local ini_file section key value
ini_file="$1"
section="$2"
key="$3"
value="$4"
[[ -f "$ini_file" ]] && sed -i '/./,$!d' "$ini_file"
if grep -q "^\[$section\]" "$ini_file" ; then
if sed -n "/^\[$section\]/,/^\[/{p}" "$ini_file" | grep -q "^$key=" ; then
sed -i "/^\[$section\]/,/^\[/{s/^$key=.*/$key=$value/}" "$ini_file"
else
sed -i "/^\[$section\]/a $key=$value" "$ini_file"
fi
else
[[ -s "$ini_file" ]] && printf '\n' >> "$ini_file"
printf '[%s]\n%s=%s\n' "$section" "$key" "$value" >> "$ini_file"
fi
}
pw_specialk_sync_files () {
local SPECIALK_PATH SPECIALK_DLLS dll PW_SK_DLL_NAME PW_SK_ARCH_DLL PW_SK_INI_FILE PW_SK_VERSION_FILE
SPECIALK_PATH="${PW_PLUGINS_PATH}/specialk/specialk-${PW_SPECIALK_VER}"
SPECIALK_DLLS="d3d9.dll d3d11.dll dinput8.dll dxgi.dll opengl32.dll"
PW_SK_VERSION_FILE="$PATH_TO_GAME/specialk_version"
if [[ "${PW_USE_SPECIALK}" != "1" ]] ; then
if [[ -f "$PW_SK_VERSION_FILE" ]] ; then
for dll in $SPECIALK_DLLS ; do rm -fr "$PATH_TO_GAME/$dll"; done
for dll in $SPECIALK_DLLS ; do [[ -e "$PATH_TO_GAME/${dll}.b" ]] && mv -f "$PATH_TO_GAME/${dll}.b" "$PATH_TO_GAME/${dll}" 2>/dev/null; done
for dll in $SPECIALK_DLLS ; do try_remove_file "$PATH_TO_GAME/${dll%.*}.ini"; done
for dll in $SPECIALK_DLLS ; do try_remove_file "$PATH_TO_GAME/custom_${dll%.*}.ini"; done
try_remove_file "$PW_SK_VERSION_FILE"
fi
if [[ -f "$PATH_TO_GAME/plugins/specialk_version" ]] ; then
try_remove_file "$PATH_TO_GAME/plugins/dxgi.dll"
try_remove_file "$PATH_TO_GAME/plugins/specialk_version"
try_remove_file "$PATH_TO_GAME/dxgi.ini"
try_remove_file "$PATH_TO_GAME/custom_dxgi.ini"
fi
return 0
fi
if [[ ! -d "$SPECIALK_PATH" ]] && try_download "github.com/SpecialKO/SpecialK/releases/download/${PW_SPECIALK_VER}/SpecialK.7z" "${PORT_DATA_PATH}/data/tmp/SpecialK.7z" ; then
create_new_dir "$SPECIALK_PATH"
if [[ -z "$pw_7z" ]] ; then
if command -v 7z &>/dev/null ; then pw_7z="7z"; else pw_7z="${PW_PLUGINS_PATH}/portable/lib/p7zip/7z"; fi
fi
$pw_7z x "${PORT_DATA_PATH}/data/tmp/SpecialK.7z" -o"$SPECIALK_PATH" >/dev/null
try_remove_file "${PORT_DATA_PATH}/data/tmp/SpecialK.7z"
fi
if [[ -d "$SPECIALK_PATH" ]] ; then
if file "$PW_EXE_FILE" | grep -q "x86-64" ; then
PW_SK_ARCH_DLL="SpecialK64.dll"
else
PW_SK_ARCH_DLL="SpecialK32.dll"
fi
if [[ "${PW_USE_OPTISCALER}" == "1" ]] && [[ -f "$SPECIALK_PATH/$PW_SK_ARCH_DLL" ]] ; then
create_new_dir "$PATH_TO_GAME/plugins"
if [[ ! -f "$PATH_TO_GAME/plugins/specialk_version" ]] || ! grep -q "$PW_SPECIALK_VER" "$PATH_TO_GAME/plugins/specialk_version" ; then
cp -f "$SPECIALK_PATH/$PW_SK_ARCH_DLL" "$PATH_TO_GAME/plugins/dxgi.dll"
echo "$PW_SPECIALK_VER" > "$PATH_TO_GAME/plugins/specialk_version"
fi
PW_SK_INI_FILE="$PATH_TO_GAME/custom_dxgi.ini"
pw_specialk_set_ini_value "$PW_SK_INI_FILE" "Compatibility.General" "UsingWINE" "true"
pw_specialk_set_ini_value "$PW_SK_INI_FILE" "Steam.Log" "Silent" "true"
return 0
fi
local DETECT_OUT HIGHEST_DX USES_OGL
DETECT_OUT=$(python_module graphics_detector "$PW_EXE_FILE")
HIGHEST_DX=$(echo "$DETECT_OUT" | grep "^HIGHEST_DX=" | cut -d'=' -f2)
USES_OGL=$(echo "$DETECT_OUT" | grep "^USES_OGL=" | cut -d'=' -f2)
PW_SK_DLL_NAME="dxgi.dll"
case "$HIGHEST_DX" in
"DirectX 12") PW_SK_DLL_NAME="dxgi.dll" ;;
"DirectX 11") PW_SK_DLL_NAME="d3d11.dll" ;;
"DirectX 9") PW_SK_DLL_NAME="d3d9.dll" ;;
"DirectX 8"|"DirectDraw") return 0 ;;
*) [[ "$USES_OGL" == "true" ]] && PW_SK_DLL_NAME="opengl32.dll" || PW_SK_DLL_NAME="dxgi.dll" ;;
esac
if [[ -f "$SPECIALK_PATH/$PW_SK_ARCH_DLL" ]] ; then
PW_SK_INI_FILE="$PATH_TO_GAME/custom_${PW_SK_DLL_NAME%.*}.ini"
if [[ ! -f "$PW_SK_VERSION_FILE" ]] \
&& [[ -f "$PATH_TO_GAME/$PW_SK_DLL_NAME" ]] \
&& [[ ! -f "$PATH_TO_GAME/${PW_SK_DLL_NAME}.b" ]]
then
mv -f "$PATH_TO_GAME/$PW_SK_DLL_NAME" "$PATH_TO_GAME/${PW_SK_DLL_NAME}.b"
fi
if [[ ! -f "$PW_SK_VERSION_FILE" ]] || ! grep -q "$PW_SPECIALK_VER" "$PW_SK_VERSION_FILE" ; then
cp -f "$SPECIALK_PATH/$PW_SK_ARCH_DLL" "$PATH_TO_GAME/$PW_SK_DLL_NAME"
echo "$PW_SPECIALK_VER" > "$PW_SK_VERSION_FILE"
fi
var_winedlloverride_update "${PW_SK_DLL_NAME%.*}=n,b"
pw_specialk_set_ini_value "$PW_SK_INI_FILE" "Compatibility.General" "UsingWINE" "true"
pw_specialk_set_ini_value "$PW_SK_INI_FILE" "Steam.Log" "Silent" "true"
fi
fi
}
pw_prepare_gamescope_launch () {
local gpu_vendor
local gamescope_available="0"
@@ -2855,6 +2972,7 @@ start_portproton () {
fi
pw_optiscaler_sync_files
pw_specialk_sync_files
if [[ "${PW_USE_RAY_TRACING}" == "1" ]] ; then
var_vkd3d_config_update dxr
@@ -3154,7 +3272,7 @@ start_portproton () {
fi
# DGVOODOO2 enable:
if [[ "${PW_DGVOODOO2}" == "1" ]] ; then
if [[ "${PW_DGVOODOO2}" == "1" ]] && [[ "${PW_USE_SPECIALK}" != "1" ]] ; then
if [[ ! -f "${PW_VULKAN_DIR}/dgvoodoo2-${DGV2_VER}/dgVoodoo.conf" ]] ; then
print_info "Download and install DGVOODOO2 v.${DGV2_VER}"
if try_download "https://github.com/Castro-Fidel/vulkan/releases/download/dgvoodoo2-${DGV2_VER}/dgvoodoo2-${DGV2_VER}.tar.xz" \
@@ -3878,6 +3996,7 @@ steamplay_launch () {
fi
pw_optiscaler_sync_files
pw_specialk_sync_files
PORT_WINE_PREFIX="$STEAM_COMPAT_DATA_PATH/pfx"
@@ -4122,7 +4241,7 @@ export -f gui_proton_downloader
gui_edit_db () {
KEY_EDIT_DB_GUI=$RANDOM
DISABLE_EDIT_DB_LIST=""
PW_EDIT_DB_LIST=(PW_MANGOHUD PW_MANGOHUD_USER_CONF PW_VKBASALT PW_VKBASALT_USER_CONF PW_DGVOODOO2
PW_EDIT_DB_LIST=(PW_MANGOHUD PW_MANGOHUD_USER_CONF PW_VKBASALT PW_VKBASALT_USER_CONF PW_DGVOODOO2 PW_USE_SPECIALK
PW_USE_ESYNC PW_USE_FSYNC PW_USE_NTSYNC PW_USE_RAY_TRACING PW_USE_NVAPI_AND_DLSS PW_USE_OPTISCALER
PW_USE_LS_FRAME_GEN PW_WINE_FULLSCREEN_FSR PW_HIDE_NVIDIA_GPU PW_VIRTUAL_DESKTOP PW_USE_TERMINAL
PW_USE_GAMEMODE PW_USE_INHIBIT_SLEEP PW_USE_D3D_EXTRAS PW_FIX_VIDEO_IN_GAME PW_REDUCE_PULSE_LATENCY
@@ -4155,6 +4274,7 @@ gui_edit_db () {
if [[ "$PW_VULKAN_USE" == "0" ]] ; then
DISABLE_EDIT_DB_LIST+=" PW_VKBASALT PW_VKBASALT_USER_CONF PW_USE_RAY_TRACING PW_USE_OBS_VKCAPTURE PW_USE_GALLIUM_ZINK PW_USE_WINED3D_VULKAN"
fi
[[ "${PW_USE_SPECIALK}" == "1" ]] && DISABLE_EDIT_DB_LIST+=" PW_DGVOODOO2"
[[ ! -e "/dev/ntsync" ]] && DISABLE_EDIT_DB_LIST+=" PW_USE_NTSYNC"
+2
View File
@@ -14,6 +14,7 @@ export VKD3D_OLD_VER="2.14.1"
export VKD3D_SAREK_VER="sarek-2.6.0"
export PW_LSFG_VK_VER="2.0.0-dev25-p11"
export PW_OPTISCALER_VER="v03-0.9.0-pre8"
export PW_SPECIALK_VER="SK_26_5_15"
export PW_GALLIUM_NINE_VER="0.10"
export DOTPFX_VER="48v9"
export DEFPFX_VER="v1"
@@ -29,6 +30,7 @@ export PW_RT_MOUNT_RW=("/media" "/mnt" "/run/media/${USER}" "${PORT_DATA_PATH}")
check_variables PW_WINE_USE "PROTON_LG"
check_variables PROTON_USE_XALIA "0"
check_variables PW_USE_RUNTIME "1"
check_variables PW_USE_SPECIALK "0"
check_variables PW_USE_ESYNC "0"
check_variables PW_USE_FSYNC "0"
check_variables PW_USE_NTSYNC "0"
+2 -2
View File
@@ -33,7 +33,7 @@ bindir = prefix / get_option('bindir')
# Use custom purelibdir if specified, otherwise use default
purelibdir_opt = get_option('python_purelibdir')
pythondir = purelibdir_opt != '' ? purelibdir_opt : python3.get_install_dir(pure: true)
pythondir = purelibdir_opt != '' ? purelibdir_opt: python3.get_install_dir(pure: true)
pkgdatadir = pythondir / meson.project_name()
# C compiler for vk_gpu_info
@@ -133,7 +133,7 @@ install_subdir(
# Install udev rules
udev_rulesdir_opt = get_option('udev_rulesdir')
udevdir = udev_rulesdir_opt != '' ? udev_rulesdir_opt : prefix / 'lib' / 'udev' / 'rules.d'
udevdir = udev_rulesdir_opt != '' ? udev_rulesdir_opt: prefix / 'lib' / 'udev' / 'rules.d'
install_data(
'build-aux/lib/udev/rules.d/60-portprotonqt.rules',
+1
View File
@@ -123,6 +123,7 @@ install_data(
['scripts_utils/__init__.py',
'scripts_utils/dbus_tools.py',
'scripts_utils/easyterm.py',
'scripts_utils/graphics_detector.py',
'scripts_utils/idle_inhibit.py',
'scripts_utils/mount_points.py',
'scripts_utils/prefix_backup.py',
@@ -0,0 +1,133 @@
import sys
from pathlib import Path
from typing import Any
DIRECTX_IMPORTS = {
"ddraw.dll": "DirectDraw",
"d3d8.dll": "DirectX 8",
"d3d9.dll": "DirectX 9",
"d3d10.dll": "DirectX 10",
"d3d10_1.dll": "DirectX 10",
"d3d11.dll": "DirectX 11",
"d3d12.dll": "DirectX 12",
}
DIRECTX_SIGNATURES = {
"DirectDraw": (b"ddraw.dll", b"DirectDrawCreate"),
"DirectX 8": (b"d3d8.dll", b"Direct3DCreate8"),
"DirectX 9": (b"d3d9.dll", b"Direct3DCreate9"),
"DirectX 10": (b"d3d10.dll", b"D3D10CreateDevice"),
"DirectX 11": (b"d3d11.dll", b"D3D11CreateDevice"),
"DirectX 12": (b"d3d12.dll", b"D3D12CreateDevice", b"D3D12Core.dll"),
}
DIRECTX_ORDER = {
"DirectDraw": 7,
"DirectX 8": 8,
"DirectX 9": 9,
"DirectX 10": 10,
"DirectX 11": 11,
"DirectX 12": 12,
}
OPENGL_IMPORTS = {
"opengl32.dll": "OpenGL",
"glu32.dll": "OpenGL Utility Library",
}
OPENGL_SIGNATURES = {
"OpenGL": (
b"opengl32.dll",
b"wglCreateContext",
b"wglGetProcAddress",
b"glGetString",
b"OpenGL context",
b"OpenGL Error",
),
"OpenGL Utility Library": (b"glu32.dll", b"gluPerspective", b"gluLookAt"),
}
def analyze_executable(file_path: str) -> dict[str, Any]:
"""Detect graphics API usage in a Windows executable."""
path = Path(file_path).expanduser().resolve()
if not path.is_file():
raise FileNotFoundError(f"File not found: {path}")
directx = set()
opengl = set()
if path.suffix.lower() in {".exe", ".dll"}:
_add_pe_imports(path, directx, opengl)
_add_file_signatures(path, directx, opengl)
return {
"highest_directx": _highest_directx(list(directx)),
"uses_opengl": bool(opengl),
}
def _add_pe_imports(path: Path, directx: set[str], opengl: set[str]) -> None:
try:
import pefile
except ImportError:
return
try:
pe = pefile.PE(str(path), fast_load=True)
pe.parse_data_directories(
directories=[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"],
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT"],
]
)
for attr_name in ["DIRECTORY_ENTRY_IMPORT", "DIRECTORY_ENTRY_DELAY_IMPORT"]:
for entry in getattr(pe, attr_name, []):
dll = entry.dll.decode("utf-8", errors="ignore").lower()
if dll in DIRECTX_IMPORTS:
directx.add(DIRECTX_IMPORTS[dll])
elif dll in OPENGL_IMPORTS:
opengl.add(OPENGL_IMPORTS[dll])
except (pefile.PEFormatError, ValueError):
return
def _add_file_signatures(path: Path, directx: set[str], opengl: set[str]) -> None:
try:
with path.open("rb") as f:
data = f.read(67108864).lower()
except OSError:
return
for name, patterns in DIRECTX_SIGNATURES.items():
if any(p.lower() in data for p in patterns):
directx.add(name)
for name, patterns in OPENGL_SIGNATURES.items():
if any(p.lower() in data for p in patterns):
opengl.add(name)
def _highest_directx(directx: list[str]) -> str:
if not directx:
return "None"
return max(directx, key=lambda n: DIRECTX_ORDER[n])
def _main(argv: list[str]) -> int:
if len(argv) != 2:
print("Usage: python3 -m portprotonqt.graphics_detector FILE")
return 2
try:
result = analyze_executable(argv[1])
print(f"HIGHEST_DX={result['highest_directx']}")
print(f"USES_OGL={'true' if result['uses_opengl'] else 'false'}")
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(_main(sys.argv))
+1
View File
@@ -89,6 +89,7 @@ def get_toggle_settings():
'PW_VKBASALT': _("Enable vkBasalt by default to improve graphics in games running on Vulkan. (The HOME hotkey disables vkbasalt)"),
'PW_VKBASALT_USER_CONF': _("Forced use of VKBASALT system settings (GOverlay, etc.)"),
'PW_DGVOODOO2': _("Enable dgVoodoo2. Forced use all dgVoodoo2 libs (Glide 2.11-3.1, DirectDraw 1-7, Direct3D 2-9) on all 3D API."),
'PW_USE_SPECIALK': _("Enable SpecialK (injection library for fixing graphics, latency and more)"),
# Upscalers and Frame Generation
'PW_USE_OPTISCALER': _("Enable OptiScaler (replacement upscaler / frame generator)"),
'PW_USE_LS_FRAME_GEN': _("Enable Lossless Scaling frame generation (experimental)"),