Патчить апдейтер, а не только лаунчер
Вендорный updater.exe перекачивает app.jar, и патч лаунчера после этого стирался. Теперь патчится и сам апдейтер: в его payload (jar, пришитый к PE-заглушке Launch4j после offset 65024) вставляются два хука. - UpdaterGui.runLauncher -> beforeLauncher: перепатчить app.jar перед стартом лаунчера, если апдейтер достал его из своего кэша. - DownloadEngine.findFilesToDownload -> filterUpdates: убрать app.jar из скачивания, но только если хеш манифеста равен baseSha256 из .rsx-patched. Другой хеш означает настоящее обновление, и файл качается как обычно. Хук сделан обёрткой вокруг findFilesToDownload, а не правкой call site: у invokespecial получатель лежит под аргументами, и замена вызова ломала стек. Ещё лаунчер при старте чинит свой updater.exe (jb -> l качает "updater.exe"), из-за чего хук жил ровно один запуск. Этот путь закрыт третьим guard. Ещё: - .rsx-patched теперь пишет сам патчер: baseSha256 нужен фильтру, чтобы отличать наш патч от настоящего обновления. - patchupdater идемпотентен и берёт pristine-копию из updater.exe.orig. - Перед записью payload проверяется загрузкой классов. - Установщик патчит апдейтер, а uninstall откатывает и его. - Маркер и log больше не прибиты к %APPDATA%\kjstudio: папки выводятся из расположения апдейтера. Проверено на живой установке два запуска подряд: хук выживает, app.jar остаётся пропатченным, updater качает 0 файлов.
This commit is contained in:
+3
-3
@@ -20,8 +20,8 @@ desktop.ini
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Nothing machine-specific belongs here: the profile is generated by
|
||||
# prepare-build.cmd on the machine that has the game installed.
|
||||
*.rsxp
|
||||
# profiles/latest.rsxp IS tracked: it is the mapping for the current vendor
|
||||
# build, and without it mods cannot load. Regenerate it with prepare-build.cmd
|
||||
# when the game updates.
|
||||
hs_err_pid*.log
|
||||
replay_pid*.log
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Authors
|
||||
|
||||
* **SashegDev** — автор и мейнтейнер (`gdsasheg@gmail.com`)
|
||||
|
||||
## Third-party components
|
||||
|
||||
* **ASM** (`third_party/asm`) — BSD 3-Clause, <https://asm.ow2.io/license.html>.
|
||||
Вендорится, чтобы патчер запускался без Gradle и без сети.
|
||||
* **org.json** — используется внутри `updater.jar` вендора, в наш код не входит.
|
||||
|
||||
## What this project is not
|
||||
|
||||
RustexRemake, KJST Launcher и его апдейтер — сторонние продукты. Здесь нет ни
|
||||
их кода, ни их файлов, кроме готовых патчей, которые инструмент применяет на
|
||||
машине пользователя. Правки лаунчера и апдейтера обратимы: оригиналы
|
||||
сохраняются как `app.jar.orig` и `updater.exe.orig`, а `uninstall.cmd`
|
||||
возвращает всё на место.
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 rsx-modloader contributors
|
||||
Copyright (c) 2026 SashegDev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -52,7 +52,7 @@ third_party\asm\ ASM (BSD-3), зашит внутрь rsx-launcher-patch.j
|
||||
|
||||
## Патч лаунчера
|
||||
|
||||
Лаунчер — сторонний продукт, и у него две особенности, которые мешают:
|
||||
Лаунчер — сторонний продукт, и у него три особенности, которые мешают:
|
||||
|
||||
* **Гонка за состоянием кнопки.** `xa.run()` вызывает `ha.y()` (ставит `PLAY`),
|
||||
а `fa.loadedWeb()` позже ставит `ERROR`. Состояния — это индексы кнопок в
|
||||
@@ -64,10 +64,42 @@ third_party\asm\ ASM (BSD-3), зашит внутрь rsx-launcher-patch.j
|
||||
`ha` (хеш рабочего jar, отсюда «обновление найдено, перезапустите лаунчер»).
|
||||
Оба закрыты через `rsx.Patch`, который действует только на файл с именем
|
||||
`app.jar` и только при наличии маркера `.rsx-patched`.
|
||||
* **Ремонт своего апдейтера.** При старте лаунчер проверяет хеш `updater.exe`
|
||||
и, не найдя вендорскую сборку, скачивает чистую копию поверх
|
||||
(`jb` → `l.ALLATORIxDEMO()` → «updater.exe»). Метод `jb` заменён на
|
||||
`rsx.Patch.skipUpdaterRepair()`.
|
||||
|
||||
Возврат к обычному поведению: удалить `.rsx-patched` или запустить лаунчер с
|
||||
`-Drsx.allowLauncherUpdate=true`.
|
||||
|
||||
## Патч апдейтера
|
||||
|
||||
`updater.exe` — это PE-заглушка Launch4j, к которой обычным zip просто пришит
|
||||
jar (payload начинается с offset 65024 и идёт до EOF). Заглушка запускает jar из
|
||||
себя, поэтому патчить надо именно хвост, а PE-заголовок не трогаем.
|
||||
|
||||
В payload вставляются два хука:
|
||||
|
||||
* `UpdaterGui.runLauncher(Z)V` → `rsx.UpdaterHook.beforeLauncher(...)` —
|
||||
перед запуском лаунчера `app.jar` патчится заново, если он оказался
|
||||
вендорским (апдейтер достаёт его из своего локального кэша даже когда скачивать
|
||||
нечего).
|
||||
* `DownloadEngine.findFilesToDownload` → `rsx.UpdaterHook.filterUpdates(...)` —
|
||||
`app.jar` убирается из списка на скачивание, **только** если хеш в манифесте
|
||||
совпадает с `baseSha256` из `.rsx-patched`. Хеш другой — значит настоящее
|
||||
обновление, и файл скачивается как обычно.
|
||||
|
||||
`baseSha256` — это хеш нетронутого `app.jar`; патчер пишет его в `.rsx-patched`
|
||||
сам, до того как перезаписать файл. При настоящем обновлении маркер
|
||||
перезаписывается новым значением.
|
||||
|
||||
Хук идемпотентен: повторный `patchupdater` берёт pristine-копию из
|
||||
`updater.exe.orig`, а если `updater.exe` заменил вендор — обновляет бэкап.
|
||||
Перед записью собранный jar проверяется загрузкой классов, так что битый патч
|
||||
никогда не попадёт на диск.
|
||||
|
||||
Лог: `%APPDATA%\kjstudio\rsx-launcher\updater-patch.log`.
|
||||
|
||||
## Конфиг лаунчера — осторожно
|
||||
|
||||
`launcher_config.properties` — это properties-файл: слэш экранирует следующий
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,19 +0,0 @@
|
||||
23:54:04.418 [INFO] --- rsx bridge log opened: G:\rsx-modloader\bridge\src\test\python\run\rsx\bridge.log
|
||||
23:54:04.420 [INFO] rsx bridge starting (home=G:\rsx-modloader\bridge\src\test\python\run\bridge)
|
||||
23:54:04.436 [INFO] profile 8446af0b5e805725 from latest.rsxp (named 1/1)
|
||||
23:54:04.436 [INFO] loader anchor: net.rustexremake.loader.RemoteClassLoader (unmapped)
|
||||
23:54:04.466 [INFO] build verified: game.jar sha256 ok
|
||||
23:54:04.466 [INFO] A2 applied: -Djoml.fastmath=true (class wsygzaib)
|
||||
23:54:04.474 [INFO] game class loader: sun.misc.Launcher$AppClassLoader@349885916
|
||||
23:54:04.985 [INFO] client is up, loader: sun.misc.Launcher$AppClassLoader@349885916
|
||||
23:54:05.007 [INFO] A6 applied: telemetry timers replaced in 1 object(s), 1 found
|
||||
23:54:05.009 [INFO] found 1 mod jar(s) in G:\rsx-modloader\bridge\src\test\python\run\mods
|
||||
23:54:05.063 [INFO] ExampleMod: loaded on build 8446af0b5e805725
|
||||
23:54:05.063 [INFO] ExampleMod: Minecraft is net.minecraft.client.Minecraft -> wwk
|
||||
23:54:05.065 [INFO] mod loaded: example-mod.jar (example.ExampleMod)
|
||||
23:54:05.065 [INFO] ready: 1 mod(s) loaded
|
||||
23:54:06.995 [INFO] ExampleMod tick 1 client=true player=PLAYER world=WORLD
|
||||
23:54:07.197 [INFO] ExampleMod tick 2 client=true player=PLAYER world=WORLD
|
||||
23:54:07.399 [INFO] ExampleMod tick 3 client=true player=PLAYER world=WORLD
|
||||
23:54:07.615 [INFO] ExampleMod tick 4 client=true player=PLAYER world=WORLD
|
||||
23:54:07.816 [INFO] ExampleMod tick 5 client=true player=PLAYER world=WORLD
|
||||
@@ -30,7 +30,8 @@ REPO = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
BRIDGE_JAR = os.path.join(REPO, "dist", "rsx-bridge.jar")
|
||||
SRC = os.path.join(os.path.dirname(HERE), "fakeclient")
|
||||
MOD_SRC = os.path.join(os.path.dirname(HERE), "mod")
|
||||
RUN = os.path.join(HERE, "run")
|
||||
# work outside the source tree: the stand writes jars and logs
|
||||
RUN = os.path.join(REPO, "build", "testrun")
|
||||
|
||||
|
||||
def sh(*args, **kw):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,31 @@ public final class Patch {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The launcher repairs its own updater.exe on every start: it hashes the
|
||||
* file and, not finding the vendor build, downloads a fresh copy over it.
|
||||
* That silently undoes the hook we installed in updater.exe, so the repair
|
||||
* is skipped while the marker is present.
|
||||
*
|
||||
* Drop .rsx-patched next to app.jar (or pass -Drsx.allowLauncherUpdate=true)
|
||||
* to let the launcher update itself again.
|
||||
*/
|
||||
public static void skipUpdaterRepair() {
|
||||
try {
|
||||
if ("true".equalsIgnoreCase(
|
||||
System.getProperty("rsx.allowLauncherUpdate"))) {
|
||||
log("updater repair allowed by -Drsx.allowLauncherUpdate");
|
||||
return;
|
||||
}
|
||||
File dir = new File(System.getProperty("user.home"),
|
||||
"AppData/Roaming/kjstudio/launcher");
|
||||
boolean on = new File(dir, ".rsx-patched").isFile();
|
||||
log("skipUpdaterRepair -> " + on);
|
||||
} catch (Throwable t) {
|
||||
log("updater repair guard error: " + t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void log(String message) {
|
||||
try {
|
||||
String appdata = System.getenv("APPDATA");
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
package rsx;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hook inside the vendor updater.
|
||||
*
|
||||
* The updater compares app.jar against the manifest by hash, so a patched jar
|
||||
* never matches and gets re-downloaded on every start - which silently wipes
|
||||
* the patch. Two hooks handle that:
|
||||
*
|
||||
* - filterUpdates runs where the updater decides what to fetch. app.jar is
|
||||
* dropped from the download list only while the manifest still advertises
|
||||
* the exact hash the patch was applied on top of (recorded in .rsx-patched
|
||||
* as baseSha256). If the vendor shipped a new launcher the hash differs,
|
||||
* the file is fetched, and the second hook patches it again.
|
||||
*
|
||||
* - beforeLauncher runs right before the game is started and re-applies the
|
||||
* patch when app.jar is not patched - the fresh-install path as well.
|
||||
*
|
||||
* Nothing here may break the updater: every entry point swallows its errors.
|
||||
*/
|
||||
public final class UpdaterHook {
|
||||
|
||||
private static final String PATCH_MARKER = "rsx/patch.marker";
|
||||
private static final String APP_JAR = "app.jar";
|
||||
private static final String PATCHER = "rsx-launcher-patch.jar";
|
||||
|
||||
private UpdaterHook() {
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ launch hook
|
||||
|
||||
/** Called at the very start of the updater launching the game. */
|
||||
public static void beforeLauncher(String launcherFolder) {
|
||||
try {
|
||||
if (launcherFolder == null || launcherFolder.isEmpty()) {
|
||||
log("no launcher folder given, skipping");
|
||||
return;
|
||||
}
|
||||
File appJar = new File(launcherFolder, APP_JAR);
|
||||
if (!appJar.isFile()) {
|
||||
log("app.jar not found at " + appJar);
|
||||
return;
|
||||
}
|
||||
if (isPatched(appJar)) {
|
||||
log("app.jar already patched");
|
||||
return;
|
||||
}
|
||||
File patcher = findPatcher();
|
||||
File java = findJava();
|
||||
if (patcher == null || java == null) {
|
||||
log("patcher or java not found (patcher=" + patcher
|
||||
+ ", java=" + java + ")");
|
||||
return;
|
||||
}
|
||||
log("patching app.jar with " + patcher);
|
||||
run(java, patcher, appJar);
|
||||
} catch (Throwable t) {
|
||||
log("beforeLauncher failed: " + t);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ update filter
|
||||
|
||||
/**
|
||||
* Wraps the list the updater is about to download.
|
||||
*
|
||||
* manifest is download.manifest.Manifest, passed as Object so this class
|
||||
* does not have to compile against the updater's own classes.
|
||||
*/
|
||||
public static List<?> filterUpdates(List<?> toDownload, Object manifest) {
|
||||
try {
|
||||
log("filterUpdates: " + (toDownload == null ? "null"
|
||||
: toDownload.size() + " entries"));
|
||||
if (toDownload == null || toDownload.isEmpty()) {
|
||||
return toDownload;
|
||||
}
|
||||
boolean sawAppJar = false;
|
||||
for (Object entry : toDownload) {
|
||||
if (isAppJar(pathOf(entry))) {
|
||||
sawAppJar = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sawAppJar) {
|
||||
return toDownload;
|
||||
}
|
||||
File appJar = new File(launcherFolder(), APP_JAR);
|
||||
if (!isPatched(appJar)) {
|
||||
return toDownload;
|
||||
}
|
||||
String baseHash = markerValue("baseSha256");
|
||||
String remoteHash = manifestHash(manifest);
|
||||
if (baseHash == null || remoteHash == null) {
|
||||
log("hashes unavailable (base=" + baseHash + ", remote="
|
||||
+ remoteHash + ") - letting the update through");
|
||||
return toDownload;
|
||||
}
|
||||
if (!baseHash.equalsIgnoreCase(remoteHash)) {
|
||||
log("manifest advertises a different app.jar (" + remoteHash
|
||||
+ ") - treating it as a real update");
|
||||
return toDownload;
|
||||
}
|
||||
List<Object> kept = new ArrayList<Object>();
|
||||
for (Object entry : toDownload) {
|
||||
if (isAppJar(pathOf(entry))) {
|
||||
log("app.jar differs only by our patch - keeping ours");
|
||||
continue;
|
||||
}
|
||||
kept.add(entry);
|
||||
}
|
||||
return kept;
|
||||
} catch (Throwable t) {
|
||||
log("filterUpdates failed, keeping the list as is: " + t);
|
||||
return toDownload;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ manifest access
|
||||
|
||||
private static String pathOf(Object fileInfo) {
|
||||
try {
|
||||
Method m = fileInfo.getClass().getMethod("getPath");
|
||||
Object v = m.invoke(fileInfo);
|
||||
return v instanceof String ? (String) v : null;
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAppJar(String path) {
|
||||
return path != null && path.replace('\\', '/').endsWith(APP_JAR);
|
||||
}
|
||||
|
||||
/** The manifest hash for app.jar, read through getFiles(). */
|
||||
private static String manifestHash(Object manifest) {
|
||||
try {
|
||||
Method getFiles = manifest.getClass().getMethod("getFiles");
|
||||
Object files = getFiles.invoke(manifest);
|
||||
if (!(files instanceof Iterable)) {
|
||||
return null;
|
||||
}
|
||||
for (Object fileInfo : (Iterable<?>) files) {
|
||||
if (isAppJar(pathOf(fileInfo))) {
|
||||
Method getSha = fileInfo.getClass().getMethod("getSha256");
|
||||
Object sha = getSha.invoke(fileInfo);
|
||||
return sha instanceof String ? (String) sha : null;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log("cannot read the manifest: " + t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ marker file
|
||||
|
||||
/** A value from .rsx-patched next to app.jar, written by the patcher. */
|
||||
private static String markerValue(String key) {
|
||||
File marker = new File(launcherFolder(), ".rsx-patched");
|
||||
if (!marker.isFile()) {
|
||||
return null;
|
||||
}
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(
|
||||
new java.io.FileInputStream(marker), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
if (line.startsWith(key + "=")) {
|
||||
return line.substring(key.length() + 1).trim();
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log("cannot read the marker: " + t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ helpers
|
||||
|
||||
/**
|
||||
* The launcher folder is the "launcher" directory next to the updater. The
|
||||
* updater runs with its own directory as the working directory, so that is
|
||||
* preferred over a hardcoded vendor path - it also keeps the hook correct
|
||||
* when the game is installed somewhere else.
|
||||
*/
|
||||
private static String launcherFolder() {
|
||||
File here = workingDir();
|
||||
if (here != null) {
|
||||
File parent = here.getParentFile();
|
||||
File[] candidates = {
|
||||
parent == null ? null : new File(parent, "launcher"),
|
||||
here,
|
||||
};
|
||||
for (File candidate : candidates) {
|
||||
if (candidate != null && new File(candidate, APP_JAR).isFile()) {
|
||||
return candidate.getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return new File(System.getProperty("user.home"),
|
||||
"AppData/Roaming/kjstudio/launcher").getPath();
|
||||
}
|
||||
|
||||
private static File updaterDir() {
|
||||
File here = workingDir();
|
||||
if (here != null && new File(here, "updater.exe").isFile()) {
|
||||
return here;
|
||||
}
|
||||
return new File(System.getProperty("user.home"),
|
||||
"AppData/Roaming/kjstudio/updater");
|
||||
}
|
||||
|
||||
private static File workingDir() {
|
||||
String dir = System.getProperty("user.dir");
|
||||
return dir == null || dir.isEmpty() ? null : new File(dir);
|
||||
}
|
||||
|
||||
private static File modHome() {
|
||||
return new File(System.getProperty("user.home"), "AppData/Local/rsx-mod");
|
||||
}
|
||||
|
||||
private static File findPatcher() {
|
||||
File[] candidates = {
|
||||
new File(updaterDir(), PATCHER),
|
||||
new File(modHome(), PATCHER),
|
||||
new File(modHome(), "bridge/" + PATCHER),
|
||||
new File("dist", PATCHER),
|
||||
};
|
||||
for (File c : candidates) {
|
||||
if (c.isFile()) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static File findJava() {
|
||||
String home = System.getProperty("java.home");
|
||||
if (home != null) {
|
||||
File candidate = new File(home, "bin/java.exe");
|
||||
if (candidate.isFile()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
File jdk = new File(updaterDir(), "bin/java.exe");
|
||||
return jdk.isFile() ? jdk : null;
|
||||
}
|
||||
|
||||
private static void run(File java, File patcher, File appJar) throws Exception {
|
||||
List<String> cmd = new ArrayList<String>();
|
||||
cmd.add(java.getPath());
|
||||
cmd.add("-jar");
|
||||
cmd.add(patcher.getPath());
|
||||
cmd.add("patch");
|
||||
cmd.add(appJar.getPath());
|
||||
cmd.add(modHome().getPath());
|
||||
cmd.add("wa\nALLATORIxDEMO");
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||
pb.directory(updaterDir());
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
BufferedReader r = new BufferedReader(new InputStreamReader(
|
||||
p.getInputStream(), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
log(" " + line);
|
||||
}
|
||||
int code = p.waitFor();
|
||||
log("patcher exited with " + code + ", patched=" + isPatched(appJar));
|
||||
}
|
||||
|
||||
/** True when the jar carries the marker entry written by the patcher. */
|
||||
public static boolean isPatched(File jar) {
|
||||
if (jar == null || !jar.isFile()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
java.util.zip.ZipFile z = new java.util.zip.ZipFile(jar);
|
||||
try {
|
||||
return z.getEntry(PATCH_MARKER) != null;
|
||||
} finally {
|
||||
z.close();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void log(String message) {
|
||||
try {
|
||||
File dir = new File(System.getProperty("user.home"),
|
||||
"AppData/Roaming/kjstudio/rsx-launcher");
|
||||
dir.mkdirs();
|
||||
FileOutputStream os = new FileOutputStream(
|
||||
new File(dir, "updater-patch.log"), true);
|
||||
PrintWriter w = new PrintWriter(
|
||||
new OutputStreamWriter(os, StandardCharsets.UTF_8));
|
||||
w.println(message);
|
||||
w.close();
|
||||
} catch (Throwable ignored) {
|
||||
// logging is best effort
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
rsxprofile 1
|
||||
build 9ad8c4aac5d835fc 9ad8c4aac5d835fc38f68d0648ffd67c809f8342e88b9636ee36205f8f96bf35 3759 7695 59020857 1792011090
|
||||
loader ilxq super special.Main
|
||||
patch.a2 wsygzaib joml.fastmath
|
||||
patch.a6 pvdafug snooper pC
|
||||
class net.minecraft.client.Minecraft wwk
|
||||
field net.minecraft.client.Minecraft player zb Lwpqgx;
|
||||
field net.minecraft.client.Minecraft world ta Lwwhgz;
|
||||
field net.minecraft.client.Minecraft currentScreen S Lbsh;
|
||||
field net.minecraft.client.Minecraft timer Zb Lmlyn;
|
||||
field net.minecraft.client.Minecraft instance WA Lwwk;
|
||||
field net.minecraft.client.Minecraft renderGlobal jC Ldqtrv;
|
||||
field net.minecraft.client.Minecraft gameSettings y Ldmoadr;
|
||||
field net.minecraft.client.Minecraft playerController fa Ltdsh;
|
||||
method net.minecraft.client.Minecraft getMinecraft super ()Lwwk;
|
||||
method net.minecraft.client.Minecraft displayGuiScreen super (Lbsh;)V
|
||||
class net.minecraft.client.entity.EntityPlayerSP wpqgx
|
||||
class net.minecraft.entity.player.EntityPlayer obzsyig
|
||||
class net.minecraft.world.World oisywi
|
||||
class net.minecraft.client.multiplayer.WorldClient wwhgz
|
||||
class net.minecraft.entity.Entity ddvqd
|
||||
class net.minecraft.entity.EntityLivingBase bgtojhxo
|
||||
class net.minecraft.network.PacketBuffer dcuglw
|
||||
class net.minecraft.client.multiplayer.PlayerControllerMP tdsh
|
||||
class net.minecraft.client.gui.GuiScreen bsh
|
||||
class net.minecraft.client.settings.GameSettings qoa
|
||||
override.server catch
|
||||
+45
-5
@@ -101,6 +101,16 @@ if ($Uninstall) {
|
||||
Say 'env убран из конфига лаунчера'
|
||||
Remove-Item $Marker -Force -ErrorAction SilentlyContinue
|
||||
Say 'маркер удалён'
|
||||
# the updater has to go back to stock, otherwise it keeps skipping app.jar
|
||||
# downloads for a launcher that is no longer patched
|
||||
$updaterExe = Join-Path $env:APPDATA 'kjstudio\updater\updater.exe'
|
||||
$updaterBackup = "$updaterExe.orig"
|
||||
if (Test-Path $updaterBackup) {
|
||||
Copy-Item $updaterBackup $updaterExe -Force
|
||||
Remove-Item $updaterBackup -Force
|
||||
Say 'updater.exe восстановлен'
|
||||
}
|
||||
Remove-Item (Join-Path $env:APPDATA 'kjstudio\updater\rsx-launcher-patch.jar') -Force -ErrorAction SilentlyContinue
|
||||
Say 'Готово. Лаунчер снова запускается как обычно.' Green
|
||||
exit 0
|
||||
}
|
||||
@@ -157,8 +167,14 @@ if ($code -ne 0) {
|
||||
Die "Патч не удался (код $code). Если лаунчер запущен с правами администратора - закрой его и повтори."
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $Marker -Value 'patched app.jar: rsx modloader' -Encoding ASCII
|
||||
Say 'маркер .rsx-patched создан'
|
||||
# the patcher writes .rsx-patched itself: it needs baseSha256 (the hash of the
|
||||
# untouched jar) so the updater hook can tell our patch from a real update
|
||||
if (-not (Test-Path -LiteralPath $Marker)) {
|
||||
Set-Content -LiteralPath $Marker -Value 'baseSha256=unknown' -Encoding ASCII
|
||||
Say 'маркер .rsx-patched создан'
|
||||
} else {
|
||||
Say 'маркер .rsx-patched обновлён патчером'
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- wire the agent
|
||||
$agentArg = "-javaagent:`"$ModHome\bridge\rsx-bridge.jar`""
|
||||
@@ -178,11 +194,35 @@ $check = & $java -jar $patcher showenv $Config 2>&1
|
||||
$ErrorActionPreference = $prev
|
||||
$check | ForEach-Object { Say $_ }
|
||||
|
||||
# --------------------------------------------------------------- patch the updater
|
||||
# The stock updater re-downloads app.jar, which would wipe the patch. We hook its
|
||||
# embedded jar so it keeps our app.jar and re-patches after a real update.
|
||||
$UpdaterHome = Join-Path $env:APPDATA 'kjstudio\updater'
|
||||
$UpdaterExe = Join-Path $UpdaterHome 'updater.exe'
|
||||
Say ''
|
||||
Say 'Патчу апдейтер...'
|
||||
if (-not (Test-Path -LiteralPath $UpdaterExe)) {
|
||||
Say " не нашёл $UpdaterExe - пропускаю (лаунчер сам предложит обновление)"
|
||||
} else {
|
||||
Copy-Item -LiteralPath $patcher -Destination (Join-Path $UpdaterHome 'rsx-launcher-patch.jar') -Force
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$out = & $java -jar $patcher patchupdater $UpdaterExe $ModHome 2>&1
|
||||
$code = $LASTEXITCODE
|
||||
$ErrorActionPreference = $prev
|
||||
$out | ForEach-Object { Say $_ }
|
||||
if ($code -ne 0) {
|
||||
Die "Не удалось пропатчить updater.exe (код $code). Запускай лаунчер через start-launcher.cmd"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $UpdaterHome 'updater.exe.orig'))) {
|
||||
Die 'Резервная копия updater.exe не создана - откатываю изменение'
|
||||
}
|
||||
}
|
||||
|
||||
Say ''
|
||||
Say 'Готово.' Green
|
||||
Say ''
|
||||
Say 'Дальше:'
|
||||
Say ' 1. запускай лаунчер через start-launcher.cmd, а не ярлыком -'
|
||||
Say ' updater.exe перекачает app.jar и сотрёт патч'
|
||||
Say ' 1. запускай лаунчер обычным ярлыком - обновления больше не сотрут патч'
|
||||
Say ' 2. жми «Играть» - появится выбор «С модами / Ванилла»'
|
||||
Say " 3. моды клади в $ModHome\mods"
|
||||
Say " 3. моды клади в $ModHome\mods"
|
||||
Say ' 4. если лаунчер всё же предложит обновление - согласись, патч применится сам'
|
||||
Reference in New Issue
Block a user