Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ flate2 = "1.1.2"
fs4 = { version = "0.13.1", default-features = false }
futures = { version = "0.3.31", default-features = false }
futures-util = "0.3.31"
hashlink = "0.10.0"
heck = "0.5.0"
hex = "0.4.3"
hickory-resolver = "0.25.2"
Expand Down
20 changes: 4 additions & 16 deletions apps/app/src/api/oauth_utils/auth_code_reply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@
//! [RFC 8252]: https://datatracker.ietf.org/doc/html/rfc8252

use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
net::SocketAddr,
sync::{LazyLock, Mutex},
time::Duration,
};

use hyper::body::Incoming;
use hyper_util::rt::{TokioIo, TokioTimer};
use theseus::ErrorKind;
use tokio::{
net::TcpListener,
sync::{broadcast, oneshot},
};
use theseus::prelude::tcp_listen_any_loopback;
use tokio::sync::{broadcast, oneshot};

static SERVER_SHUTDOWN: LazyLock<broadcast::Sender<()>> =
LazyLock::new(|| broadcast::channel(1024).0);
Expand All @@ -35,17 +33,7 @@ static SERVER_SHUTDOWN: LazyLock<broadcast::Sender<()>> =
pub async fn listen(
listen_socket_tx: oneshot::Sender<Result<SocketAddr, theseus::Error>>,
) -> Result<Option<String>, theseus::Error> {
// IPv4 is tried first for the best compatibility and performance with most systems.
// IPv6 is also tried in case IPv4 is not available. Resolving "localhost" is avoided
// to prevent failures deriving from improper name resolution setup. Any available
// ephemeral port is used to prevent conflicts with other services. This is all as per
// RFC 8252's recommendations
const ANY_LOOPBACK_SOCKET: &[SocketAddr] = &[
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0),
];

let listener = match TcpListener::bind(ANY_LOOPBACK_SOCKET).await {
let listener = match tcp_listen_any_loopback().await {
Ok(listener) => {
listen_socket_tx
.send(listener.local_addr().map_err(|e| {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ quick-xml = { workspace = true, features = ["async-tokio"] }
enumset.workspace = true
chardetng.workspace = true
encoding_rs.workspace = true
hashlink.workspace = true
png.workspace = true
bytemuck.workspace = true
rgb.workspace = true
phf.workspace = true
itertools.workspace = true

chrono = { workspace = true, features = ["serde"] }
daedalus.workspace = true
Expand Down
1 change: 0 additions & 1 deletion packages/app-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ fn build_java_jars() {
.arg("build")
.arg("--no-daemon")
.arg("--console=rich")
.arg("--info")
.current_dir(dunce::canonicalize("java").unwrap())
.status()
.expect("Failed to wait on Gradle build");
Expand Down
56 changes: 56 additions & 0 deletions packages/app-lib/java/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction
import com.github.jengelman.gradle.plugins.shadow.transformers.CacheableTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.apache.tools.zip.ZipEntry
import org.apache.tools.zip.ZipOutputStream
import java.io.IOException
import java.util.jar.JarFile
import java.util.jar.Attributes as JarAttributes
import java.util.jar.Manifest as JarManifest

plugins {
java
id("com.diffplug.spotless") version "7.0.4"
Expand All @@ -11,6 +22,7 @@ repositories {
dependencies {
implementation("org.ow2.asm:asm:9.8")
implementation("org.ow2.asm:asm-tree:9.8")
implementation("com.google.code.gson:gson:2.13.1")

testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand Down Expand Up @@ -46,6 +58,50 @@ tasks.shadowJar {

enableRelocation = true
relocationPrefix = "com.modrinth.theseus.shadow"

// Adapted from ManifestResourceTransformer to do one thing: remove Multi-Release.
// Multi-Release gets added by shadow because gson has Multi-Release set to true, however
// shadow strips the actual versions directory, as gson only has a module-info.class in there.
// However, older versions of SecureJarHandler crash if Multi-Release is set to true but the
// versions directory is missing.
transform(@CacheableTransformer object : ResourceTransformer {
private var manifestDiscovered = false
private var manifest: JarManifest? = null

override fun canTransformResource(element: FileTreeElement): Boolean {
return JarFile.MANIFEST_NAME.equals(element.path, ignoreCase = true)
}

override fun transform(context: TransformerContext) {
if (!manifestDiscovered) {
try {
manifest = JarManifest(context.inputStream)
manifestDiscovered = true
} catch (e: IOException) {
logger.warn("Failed to read MANIFEST.MF", e)
}
}
}

override fun hasTransformedResource(): Boolean = true

override fun modifyOutputStream(
os: ZipOutputStream,
preserveFileTimestamps: Boolean
) {
// If we didn't find a manifest, then let's create one.
if (manifest == null) {
manifest = JarManifest()
}

manifest!!.mainAttributes.remove(JarAttributes.Name.MULTI_RELEASE)

os.putNextEntry(ZipEntry(JarFile.MANIFEST_NAME).apply {
time = ShadowCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES
})
manifest!!.write(os)
}
})
}

tasks.named<Test>("test") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,31 @@
package com.modrinth.theseus;

import java.io.ByteArrayOutputStream;
import com.modrinth.theseus.rpc.RpcHandlers;
import com.modrinth.theseus.rpc.TheseusRpc;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

public final class MinecraftLaunch {
public static void main(String[] args) throws IOException, ReflectiveOperationException {
final String mainClass = args[0];
final String[] gameArgs = Arrays.copyOfRange(args, 1, args.length);

System.setProperty("modrinth.process.args", String.join("\u001f", gameArgs));
parseInput();

relaunch(mainClass, gameArgs);
}

private static void parseInput() throws IOException {
final ByteArrayOutputStream line = new ByteArrayOutputStream();
while (true) {
final int b = System.in.read();
if (b < 0) {
throw new IllegalStateException("Stdin terminated while parsing");
}
if (b != '\n') {
line.write(b);
continue;
}
if (handleLine(line.toString("UTF-8"))) {
break;
}
line.reset();
}
}

private static boolean handleLine(String line) {
final String[] parts = line.split("\t", 2);
switch (parts[0]) {
case "property": {
final String[] keyValue = parts[1].split("\t", 2);
System.setProperty(keyValue[0], keyValue[1]);
return false;
}
case "launch":
return true;
}
final CompletableFuture<Void> waitForLaunch = new CompletableFuture<>();
TheseusRpc.connectAndStart(
System.getProperty("modrinth.internal.ipc.host"),
Integer.getInteger("modrinth.internal.ipc.port"),
new RpcHandlers()
.handler("set_system_property", String.class, String.class, System::setProperty)
.handler("launch", () -> waitForLaunch.complete(null)));

System.err.println("Unknown input line " + line);
return false;
waitForLaunch.join();
relaunch(mainClass, gameArgs);
}

private static void relaunch(String mainClassName, String[] args) throws ReflectiveOperationException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.modrinth.theseus.rpc;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;

public class RpcHandlers {
private final Map<String, Function<JsonElement[], JsonElement>> handlers = new HashMap<>();
private boolean frozen;

public RpcHandlers handler(String functionName, Runnable handler) {
return addHandler(functionName, args -> {
handler.run();
return JsonNull.INSTANCE;
});
}

public <A, B> RpcHandlers handler(
String functionName, Class<A> arg1Type, Class<B> arg2Type, BiConsumer<A, B> handler) {
return addHandler(functionName, args -> {
if (args.length != 2) {
throw new IllegalArgumentException(functionName + " expected 2 arguments");
}
final A arg1 = TheseusRpc.GSON.fromJson(args[0], arg1Type);
final B arg2 = TheseusRpc.GSON.fromJson(args[1], arg2Type);
handler.accept(arg1, arg2);
return JsonNull.INSTANCE;
});
}

private RpcHandlers addHandler(String functionName, Function<JsonElement[], JsonElement> handler) {
if (frozen) {
throw new IllegalStateException("Cannot add handler to frozen RpcHandlers instance");
}
handlers.put(functionName, handler);
return this;
}

Map<String, Function<JsonElement[], JsonElement>> build() {
frozen = true;
return handlers;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.modrinth.theseus.rpc;

public class RpcMethodException extends RuntimeException {
private static final long serialVersionUID = 1922360184188807964L;

public RpcMethodException(String message) {
super(message);
}
}
Loading