feat: add preloader.sh for Lutris

feat: remove syscall_check.sh wrapper in sulaunchhelper2.sh and garena_wrapper.sh
chore: remove lol.py and sulaunchhelper2.py (use directly their owner repo)
This commit is contained in:
tretrauit 2022-06-01 17:48:59 +07:00
parent 8832024604
commit d5ec915416
No known key found for this signature in database
GPG Key ID: 862760FF1903319E
6 changed files with 37 additions and 348 deletions

26
apps/Lutris/preloader.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
# Lutris pre-loader script, for allowing loading multiple pre-load scripts.
# Enabling debug will enable the script output.
DEBUG=1
execute_file () {
# Just in case...
chmod +x "$1"
if [[ $DEBUG -eq 0 ]]; then
nohup "$1" >/dev/null 2>&1 &
else
# No this is not as smart as you think...
filename=$(echo "$1" | cut -d "/" -f3)
nohup "$1" > ./logs/preloader_"$filename".log 2>&1 &
fi
}
echo "Checking directory..."
mkdir -p ./preloader/ ./logs/
for file in ./preloader/*.*; do
[ -e "$file" ] || continue
echo "Found $file, loading..."
execute_file "$file"
done

View File

@ -1,12 +1,13 @@
# leagueoflinux scripts
## `sulaunchhelper2.sh`
This script is a wrapper for `sulaunchhelper2.py` and `syscall_check.sh`, for use with Lutris pre-game launch script.
This script is a wrapper for `sulaunchhelper2.py` for use with Lutris pre-game launch script.
### Installation
+ 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):
> This script no longer wrap `syscall_check.sh`, if you need to execute that script alongside this one, I recommend you to take a look at [preloader.sh](./README.md)
+ To install you must have `sulaunchhelper2.py` present, if not you can execute these to quickly download them (to current directory):
```sh
curl -OL https://gitlab.com/tretrauit/scripts/-/raw/main/games/LoL/linux/sulaunchhelper2.py
curl -OL https://gitlab.com/tretrauit/scripts/-/raw/main/games/LoL/linux/syscall_check.sh
chmod +x sulaunchhelper2.py syscall_check.sh
curl -OL https://raw.githubusercontent.com/CakeTheLiar/launchhelper/master/sulaunchhelper2.py
chmod +x sulaunchhelper2.py
```
+ Then to download `sulaunchhelper2.sh` itself:
```sh
@ -22,12 +23,13 @@ chmod +x sulaunchhelper2.sh
## `garena_wrapper.sh`
This script automates the launching of [lol.py](https://github.com/nhubaotruong/league-of-legends-linux-garena-script) (LoL in Garena client) so you don't have to manually do it ;)
### Installation
Because this script is used in Lutris pre-game launch script, it also wrap `syscall_check.sh`. You also need to follow steps in `lol.py` repository to properly config your LoL prefix.
+ To install you must have `lol.py` and `syscall_check.sh` present, if not you can execute these to quickly download them (to current directory):
> This script no longer wrap `syscall_check.sh`, if you need to execute that script alongside this one, I recommend you to take a look at [preloader.sh](./README.md)
You need to follow steps in `lol.py` repository to properly config your LoL prefix.
+ To install you must have `lol.py` present, if not you can execute these to quickly download them (to current directory):
```sh
curl -OL https://raw.githubusercontent.com/nhubaotruong/league-of-legends-linux-garena-script/main/lol.py
curl -OL https://gitlab.com/tretrauit/scripts/-/raw/main/games/LoL/linux/syscall_check.sh
chmod +x sulaunchhelper2.py syscall_check.sh
chmod +x sulaunchhelper2.py
```
+ Then to download `garena_wrapper.sh` itself:
```sh

View File

@ -3,21 +3,9 @@
# It automatically execute lol.py to start LoL lutris game from Garena.
# You need lol.py and syscall_check.sh present in game prefix root directory.
SCC_SH='syscall_check.sh'
LOL_PY='lol.py'
dialog() {
zenity "$@" --icon-name='lutris' --width="400" --title="Garena LoL to LoL lutris wrapper"
}
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
echo "Waiting for Garena to start..."
until _=$(pidof Garena.exe)
do

View File

@ -1,121 +0,0 @@
#!/usr/bin/env python3
import psutil
import os
import yaml
import sqlite3
import re
import time
def find_process_id_by_name(processName):
"""
Get a list of all the PIDs of a all the running process whose name contains
the given string processName
"""
# Iterate over the all the running process
while True:
time.sleep(1)
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=["cmdline", "name", "pid"])
# Check if process name contains the given name string.
if processName.lower() in pinfo["name"].lower():
return pinfo
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
def kill_old_league_and_riot_process():
RIOT_AND_LEAGUE_PROCESS_REGEX = re.compile("(league|riot).*\.exe", flags=re.I)
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=["name", "pid"])
# Check if process name contains the given name string.
if RIOT_AND_LEAGUE_PROCESS_REGEX.search(pinfo.get("name")):
print(
f"Found old process still running: {pinfo.get('name')}, pid: {pinfo.get('pid')}"
)
p = psutil.Process(pinfo.get("pid"))
p.kill()
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
def main():
riot_service_process_name = "RiotClientServices.exe"
print(
f"The script will wait for a {riot_service_process_name} process to show up to get it's token. So go to the Garena client and press Play"
)
kill_old_league_and_riot_process()
# Get RiotClientServices.exe process
riot_process = find_process_id_by_name(riot_service_process_name)
# Programmatically get RiotClientServices.exe arguments
try:
riot_argument = " ".join(riot_process.get("cmdline", [])[1:]).strip()
except Exception:
print("Cannot get Garena Token")
# Exit if no riot_argument is found
if not riot_argument:
quit()
print(f"Garena token got from {riot_service_process_name}: {riot_argument}")
# Kill the current RiotClientServices.exe
p = psutil.Process(riot_process.get("pid"))
p.kill()
# Start game using lutris
HOME = os.getenv("HOME")
LUTRIS_DB_PATH = HOME + "/.local/share/lutris"
LUTRIS_DB_NAME = "pga.db"
conn = sqlite3.connect(LUTRIS_DB_PATH + "/" + LUTRIS_DB_NAME)
c = conn.cursor()
c.execute("SELECT id,configpath FROM games WHERE slug='league-of-legends'")
try:
game_id, configpath = c.fetchone()
conn.close()
except Exception:
print("No league of legends install from lutris found")
conn.close()
quit()
LUTRIS_CONFIG_FOLDER = HOME + "/.config/lutris/games/"
LUTRIS_LOL_CONFIG_FILE = LUTRIS_CONFIG_FOLDER + configpath + ".yml"
with open(LUTRIS_LOL_CONFIG_FILE, "r") as f:
try:
game_config = yaml.load(f, Loader=yaml.FullLoader)
except yaml.YAMLError as exc:
print(exc)
quit()
with open(LUTRIS_LOL_CONFIG_FILE, "w") as f:
game_config["game"]["args"] = riot_argument
try:
yaml.dump(game_config, f, default_flow_style=False)
except yaml.YAMLError as exc:
print(exc)
quit()
print("Starting game with current config:")
print(f"- Wine version: {game_config.get('wine',{}).get('version')}")
print(f"- Executable: {game_config.get('game',{}).get('exe')}")
print(f"- Wineprefix: {game_config.get('game',{}).get('prefix')}")
print(
"If this is your first time running LOL since reboot, a pop up will appear, chose the first or second option then enter your password\n"
)
print("It's a workaround by the lutris community\n")
print("Ignore the lines below, they are not actually error\n")
launch_command = f"lutris lutris:rungameid/{game_id}"
os.system(launch_command)
if __name__ == "__main__":
main()

View File

@ -1,199 +0,0 @@
#!/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

@ -3,7 +3,6 @@
# 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() {
@ -11,12 +10,6 @@ dialog() {
}
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
echo "Trying to check for Frida kernel argument..."
if [ "$(cat /proc/sys/kernel/yama/ptrace_scope)" -ne 0 ]; then