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:
parent
8832024604
commit
d5ec915416
26
apps/Lutris/preloader.sh
Executable file
26
apps/Lutris/preloader.sh
Executable 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
|
||||||
|
|
@ -1,12 +1,13 @@
|
|||||||
# leagueoflinux scripts
|
# leagueoflinux scripts
|
||||||
## `sulaunchhelper2.sh`
|
## `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
|
### 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
|
```sh
|
||||||
curl -OL https://gitlab.com/tretrauit/scripts/-/raw/main/games/LoL/linux/sulaunchhelper2.py
|
curl -OL https://raw.githubusercontent.com/CakeTheLiar/launchhelper/master/sulaunchhelper2.py
|
||||||
curl -OL https://gitlab.com/tretrauit/scripts/-/raw/main/games/LoL/linux/syscall_check.sh
|
chmod +x sulaunchhelper2.py
|
||||||
chmod +x sulaunchhelper2.py syscall_check.sh
|
|
||||||
```
|
```
|
||||||
+ Then to download `sulaunchhelper2.sh` itself:
|
+ Then to download `sulaunchhelper2.sh` itself:
|
||||||
```sh
|
```sh
|
||||||
@ -22,12 +23,13 @@ chmod +x sulaunchhelper2.sh
|
|||||||
## `garena_wrapper.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 ;)
|
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
|
### 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.
|
> 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 `lol.py` and `syscall_check.sh` present, if not you can execute these to quickly download them (to current directory):
|
|
||||||
|
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
|
```sh
|
||||||
curl -OL https://raw.githubusercontent.com/nhubaotruong/league-of-legends-linux-garena-script/main/lol.py
|
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
|
||||||
chmod +x sulaunchhelper2.py syscall_check.sh
|
|
||||||
```
|
```
|
||||||
+ Then to download `garena_wrapper.sh` itself:
|
+ Then to download `garena_wrapper.sh` itself:
|
||||||
```sh
|
```sh
|
||||||
|
@ -3,21 +3,9 @@
|
|||||||
# It automatically execute lol.py to start LoL lutris game from Garena.
|
# 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.
|
# You need lol.py and syscall_check.sh present in game prefix root directory.
|
||||||
|
|
||||||
SCC_SH='syscall_check.sh'
|
|
||||||
LOL_PY='lol.py'
|
LOL_PY='lol.py'
|
||||||
|
|
||||||
dialog() {
|
|
||||||
zenity "$@" --icon-name='lutris' --width="400" --title="Garena LoL to LoL lutris wrapper"
|
|
||||||
}
|
|
||||||
|
|
||||||
own_dir="$(realpath .)"
|
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..."
|
echo "Waiting for Garena to start..."
|
||||||
until _=$(pidof Garena.exe)
|
until _=$(pidof Garena.exe)
|
||||||
do
|
do
|
||||||
|
@ -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()
|
|
@ -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')
|
|
@ -3,7 +3,6 @@
|
|||||||
# Because this is for Lutris pre-launch script, you're required to have both sulaunchhelper2.py and syscall_check.sh
|
# 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
|
# present in the game' wineprefix
|
||||||
|
|
||||||
SCC_SH='syscall_check.sh'
|
|
||||||
SULH_PY='sulaunchhelper2.py'
|
SULH_PY='sulaunchhelper2.py'
|
||||||
|
|
||||||
dialog() {
|
dialog() {
|
||||||
@ -11,12 +10,6 @@ dialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
own_dir="$(realpath .)"
|
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..."
|
echo "Trying to check for Frida kernel argument..."
|
||||||
if [ "$(cat /proc/sys/kernel/yama/ptrace_scope)" -ne 0 ]; then
|
if [ "$(cat /proc/sys/kernel/yama/ptrace_scope)" -ne 0 ]; then
|
||||||
|
Loading…
Reference in New Issue
Block a user