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
|
||||
## `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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
# 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
|
||||
|
Loading…
Reference in New Issue
Block a user