chore: rewrite directory name & feat: add lol sulaunchhelper2.sh

This commit is contained in:
tretrauit 2022-05-30 20:12:39 +07:00
parent a5abc5f35c
commit ec2fa3d16d
No known key found for this signature in database
GPG Key ID: 862760FF1903319E
18 changed files with 327 additions and 0 deletions

16
games/LoL/linux/README.md Normal file
View File

@ -0,0 +1,16 @@
# leagueoflinux scripts
## `sulaunchhelper2.sh`
This script is a wrapper for `sulaunchhelper2.py` and `syscall_check.sh`, for use with Lutris pre-game launch script.
+ To install you must have `sulaunchhelper2.py` and `syscall_check.sh` present, if not you can execute these to quickly download them (to current directory):
```sh
curl -OL sulaunchhelper2.py
curl -OL syscall_check.sh
chmod +x sulaunchhelper2.py syscall_check.sh
```
+ Then to download `sulaunchhelper2.sh` itself:
```sh
curl -OL sulaunchhelper2.sh
chmod +x sulaunchhelper2.sh
```
+ After that, copy all these files to the game prefix you want to use, then set pre-launch script in Lutris to where `sulaunchhelper2.sh` is located.
+ Enjoy your LoL experience :P

View File

@ -0,0 +1,199 @@
#!/usr/bin/env python3
import frida
import socket
import ssl
import psutil
import time
import subprocess
import sys
"""
requires psutil, frida (pip install psutil frida)
suLaunchhelper2 will try to lauch the League of Legends client by instrumenting frida (https://frida.re/)
This version requires sudo or `sysctl kernel.yama.ptrace_scope=0`.
It will launch frida directly from outside of wine.
"""
WINEDUMP = 'winedump' # path to winedump, does not necessarily need to be the same version as wine
SELECT_SIGNATURE = "'long', ['long', 'pointer', 'pointer', 'pointer'], 'stdcall'" # return type, [arg types...], abi
sleep_time = 1
timeout = 0 # 0 to disable
hide_backtrace = True
class FridaWrapper():
def __init__(self, process, module=None, export=None, position=None, signature=None):
self.session = None
self.process = process
self.module = module
self.export = export
self.position = position
self.signature = signature
def get_location(self):
if self.module and self.export:
return f"Process.getModuleByName('{self.module}').getExportByName('{self.export}')"
if self.position and self.signature:
return f"new NativeFunction(ptr({hex(self.position)}), {self.signature})"
def attach(self):
if self.session: return
self.session = frida.attach(self.process)
script = self.session.create_script("""
Interceptor.attach(
""" + self.get_location() + """, {
onEnter: function(args) {
args[4].writeInt(0x0);
}
}
);
""")
script.load()
def detach(self):
if not self.session: return
script = self.session.create_script("Interceptor.detachAll();")
script.load()
self.session.detach()
self.session = None
def attached(self):
return self.session is not None
class TimeoutException(Exception):
pass
class Process:
def __init__(self, name, internal=None):
self.name = name
self.internal = internal or name
self.process = None
def find(self):
if p := find_process_by_name(self.internal):
self.process = p
return True
return False
def wait_for(self, tsleep=1, timeout=0):
start = time.time()
while not self.find():
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
raise TimeoutException(f'Timeout while waiting for {self.name}')
def __repr__(self):
return self.name
class Symbol:
def __init__(self, offset, position, name):
self.offset = offset if type(offset) == int else int(offset, 16)
self.position = int(position)
self.name = name
def __repr__(self):
return f'Symbol(offset={hex(self.offset)}, position={self.position}, name=\'{self.name}\')'
def check_ssl_connect(host, port, verify=True):
ctx = ssl.create_default_context()
if not verify:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
with socket.create_connection((host, port)) as sock:
with ctx.wrap_socket(sock) as ssock:
return True
except:
return False
def find_process_by_name(name):
for p in psutil.process_iter(attrs=['pid', 'name']):
if p.info['name'] == name:
return p
def find_section(proc, name):
for m in proc.memory_maps(grouped=False):
if m.path.lower().endswith(name.lower()):
return int(m.addr.split('-')[0], 16), m
def get_dll_exports(dll):
exports = subprocess.run(['winedump', '-j', 'export', dll], stdout=subprocess.PIPE).stdout
exports = str(exports, 'utf-8')
exports = exports.split('\n')
exports = exports[19:-3] # everything before this is just the pretext
exports = [Symbol(sym[0], sym[1], sym[2]) for sym in [e.strip().split() for e in exports]]
return exports
def find_dll_export(dll, export):
exports = get_dll_exports(dll)
for e in exports:
if e.name == export:
return e
if __name__ == '__main__':
# hide backtrace
if hide_backtrace:
sys.tracebacklimit = None
rclient = Process('RiotClientServices.exe', 'RiotClientServi')
lclient = Process('LeagueClient.exe')
lclientux = Process('LeagueClientUx.exe')
# Wait for RiotClientServices.exe
print(f'Waiting for {rclient}')
rclient.wait_for(tsleep=sleep_time, timeout=timeout)
print(f'Found {rclient}: pid {rclient.process.pid}')
base, module = find_section(rclient.process, 'ws2_32.dll')
exp_select = find_dll_export(module.path, 'select')
# Wait for LeagueClient.exe
print(f'Waiting for {lclient}')
lclient.wait_for(tsleep=sleep_time, timeout=timeout)
print(f'Found {lclient}: pid {lclient.process.pid}')
f = FridaWrapper(rclient.process.pid, position = base + exp_select.offset, signature = SELECT_SIGNATURE)
# Wait for LeagueClientUx.exe
print(f'Waiting for {lclientux}')
start = time.time()
while not lclientux.find():
if not f.attached():
print('Attaching...')
f.attach()
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
f.detach()
raise TimeoutException(f'Timeout while waiting for {lclientux}')
print(f'Found {lclientux}: pid {lclientux.process.pid}')
# Find app-port
port_xarg = next(x for x in lclientux.process.cmdline() if '--app-port=' in x)
port = port_xarg.split('=')[1]
# Wait for SSL response on app-port
print(f'Waiting for port {port}')
start = time.time()
while not check_ssl_connect('127.0.0.1', port, verify=False):
if not f.attached():
print('Attaching...')
f.attach()
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
f.detach()
raise TimeoutException(f'Timeout while waiting for SSL response')
if f.attached():
print('Detaching...')
f.detach()
else:
print('Nothing to do')
print('Done')

View File

@ -0,0 +1,54 @@
#!/bin/bash
# This script is a wrapper for sulaunchhelper2.py and somewhat based on syscall_check.sh for kernel.yama.ptrace_scope wrapper
# Because this is for Lutris pre-launch script, you're required to have both sulaunchhelper2.py and syscall_check.sh
# present in the game' wineprefix
SCC_SH='syscall_check.sh'
SULH_PY='sulaunchhelper2.py'
dialog() {
zenity "$@" --icon-name='lutris' --width="400" --title="League of Legends launch helper (root version)"
}
own_dir="$(realpath .)"
# try to call syscall_check.sh
if ! [ -x "${own_dir}/${SCC_SH}" ]; then
dialog "Please place this script into the same directory as '${SCC_SH}'!"
else
sh "${own_dir}/${SCC_SH}"
fi
if [ "$(cat /proc/sys/kernel/yama/ptrace_scope)" -ne 0 ]; then
once="Change setting until next reboot"
persist="Change setting and persist after reboot"
manual="Show me the commands; I'll handle it myself"
if dialog --question --text="League of Legends' client UI launch much faster with some modification to your system. but as far as this script can detect, the setting has not been changed yet.\n\nWould you like to change the setting now?"
then
# I tried to embed the command in the dialog and run the output, but
# parsing variables with embedded quotes is an excercise in frustration.
RESULT=$(dialog --list --radiolist --height="200" \
--column="" --column="Command" \
"TRUE" "$once" \
"FALSE" "$persist" \
"FALSE" "$manual")
case "$RESULT" in
"$once")
pkexec sysctl -w kernel.yama.ptrace_scope=0
;;
"$persist")
pkexec sh -c 'echo "kernel.yama.ptrace_scope=0" >> /etc/sysctl.conf && sysctl -p'
;;
"$manual")
dialog --info --no-wrap --text="To change the setting (a kernel parameter) until next boot, run:\n\nsudo sh -c 'sysctl -w kernel.yama.ptrace_scope=0'\n\nTo persist the setting between reboots, run:\n\nsudo sh -c 'echo \"kernel.yama.ptrace_scope=0\" >> /etc/sysctl.conf && sysctl -p'\nChange the setting then press OK or script will NOT work."
;;
*)
echo "Dialog canceled or unknown option selected: $RESULT"
;;
esac
fi
fi
python3 "${own_dir}/${SULH_PY}"

View File

@ -0,0 +1,58 @@
#!/usr/bin/env sh
# If abi.vsyscall32=0 is already set, no need to do anything
if [ "$(cat /proc/sys/abi/vsyscall32)" -eq 0 ]; then
exit 0
fi
dialog() {
zenity "$@" --icon-name='lutris' --width="400" --title="League of Legends anticheat compatibility check"
}
final_check() {
if [ "$(cat /proc/sys/abi/vsyscall32)" -ne 0 ]; then
dialog --warning --text="As far as this script can detect, your system is not configured to work with League's anticheat. Please verify that you can get into the practice too before playing a multiplayer game."
fi
}
trap final_check EXIT
if grep -E -x -q "abi.vsyscall32( )?=( )?0" /etc/sysctl.conf; then
if dialog --question --text="It looks like you already configured your system to work with League anticheat, and saved the setting to persist across reboots. However, for some reason the persistence part did not work.\n\nFor now, would you like to enable the setting again until the next reboot?"
then
pkexec sh -c 'sysctl -w abi.vsyscall32=0'
fi
exit 0
fi
once="Change setting until next reboot"
persist="Change setting and persist after reboot"
manual="Show me the commands; I'll handle it myself"
if dialog --question --text="League of Legends' anticheat requires using a modified version of wine and changing a system setting. Otherwise, the game will crash after champion select. Wine-lol comes with the Lutris installer, but as far as this script can detect, the setting has not been changed yet.\nNote: The setting (abi.vsyscall32=0) may reduce the performance of some 32 bit applications.\n\nWould you like to change the setting now?"
then
# I tried to embed the command in the dialog and run the output, but
# parsing variables with embedded quotes is an excercise in frustration.
RESULT=$(dialog --list --radiolist --height="200" \
--column="" --column="Command" \
"TRUE" "$once" \
"FALSE" "$persist" \
"FALSE" "$manual")
case "$RESULT" in
"$once")
pkexec sh -c 'sysctl -w abi.vsyscall32=0'
;;
"$persist")
pkexec sh -c 'echo "abi.vsyscall32 = 0" >> /etc/sysctl.conf && sysctl -p'
;;
"$manual")
dialog --info --no-wrap --text="To change the setting (a kernel parameter) until next boot, run:\n\nsudo sh -c 'sysctl -w abi.vsyscall32=0'\n\nTo persist the setting between reboots, run:\n\nsudo sh -c 'echo \"abi.vsyscall32 = 0\" >> /etc/sysctl.conf && sysctl -p'"
# Anyone who wants to do it manually doesn't need another warning
trap - EXIT
;;
*)
echo "Dialog canceled or unknown option selected: $RESULT"
;;
esac
fi