Support Microsoft authentication from debug mode
This commit is contained in:
parent
b980606088
commit
db3c6d2848
7 changed files with 172 additions and 99 deletions
|
@ -50,6 +50,7 @@ dependencies {
|
|||
implementation("io.github.spair:imgui-java-natives-windows-ft:$imguiVersion")
|
||||
|
||||
implementation(files("vendor/libs/lwjgl-util.jar"))
|
||||
implementation(files("vendor/libs/minecraft_authenticator-2.0.1.jar"))
|
||||
|
||||
runtimeOnly("org.joml:joml:1.10.2")
|
||||
runtimeOnly("org.anarres:jcpp:1.4.14")
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
package site.hackery.unknit;
|
||||
|
||||
|
||||
import com.mojang.authlib.Agent;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
|
||||
import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
|
||||
import com.mojang.util.UUIDTypeAdapter;
|
||||
import net.minecraft.client.main.Main;
|
||||
import net.minecraft.client.util.Session;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(Main.class)
|
||||
public abstract class MixinMinecraftMain {
|
||||
private static void replace(List<String> arguments, String tag, String value) {
|
||||
int index = arguments.indexOf(tag);
|
||||
if (index != -1) {
|
||||
arguments.set(index + 1, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceVersion(List<String> arguments) {
|
||||
replace(arguments, "--version", "1.18.1");
|
||||
replace(arguments, "--versionType", "release");
|
||||
}
|
||||
|
||||
private static void replaceSession(List<String> arguments, Session session) {
|
||||
replace(arguments, "--accessToken", session.getAccessToken());
|
||||
|
||||
arguments.add("--username");
|
||||
arguments.add(session.getUsername());
|
||||
|
||||
arguments.add("--uuid");
|
||||
arguments.add(session.getUuid());
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "main", at = @At("HEAD"), argsOnly = true, remap = false)
|
||||
private static String[] setArgs(String[] args) {
|
||||
List<String> arguments = new ArrayList<>(Arrays.asList(args));
|
||||
replaceVersion(arguments);
|
||||
|
||||
try {
|
||||
String authFile = System.getenv("LOGIN_FILE");
|
||||
authFile = authFile == null ? "lastlogin.txt" : authFile;
|
||||
|
||||
List<String> loginDetails = Files.readAllLines(Paths.get(authFile));
|
||||
if (loginDetails.size() >= 2) {
|
||||
Session session = getAuthenticatedSession(loginDetails.get(0), loginDetails.get(1));
|
||||
replaceSession(arguments, session);
|
||||
}
|
||||
} catch (AuthenticationException | IOException ignored) {
|
||||
}
|
||||
|
||||
return arguments.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static Session getAuthenticatedSession(String username, String password) throws AuthenticationException {
|
||||
YggdrasilUserAuthentication yggdrasilUserAuthentication = constructUserAuthentication(username, password);
|
||||
yggdrasilUserAuthentication.logIn();
|
||||
|
||||
GameProfile selectedProfile = yggdrasilUserAuthentication.getSelectedProfile();
|
||||
|
||||
return new Session(
|
||||
selectedProfile.getName(),
|
||||
UUIDTypeAdapter.fromUUID(selectedProfile.getId()),
|
||||
yggdrasilUserAuthentication.getAuthenticatedToken(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Session.AccountType.LEGACY // Why legacy?
|
||||
);
|
||||
}
|
||||
|
||||
private static YggdrasilUserAuthentication constructUserAuthentication(String username, String password) {
|
||||
YggdrasilUserAuthentication yggdrasilUserAuthentication = (YggdrasilUserAuthentication) new YggdrasilAuthenticationService(Proxy.NO_PROXY, "my epic client token").createUserAuthentication(Agent.MINECRAFT);
|
||||
yggdrasilUserAuthentication.setUsername(username);
|
||||
yggdrasilUserAuthentication.setPassword(password);
|
||||
|
||||
return yggdrasilUserAuthentication;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package site.hackery.unknit.auth;
|
||||
|
||||
import net.hycrafthd.minecraft_authenticator.login.AuthenticationException;
|
||||
import net.hycrafthd.minecraft_authenticator.login.Authenticator;
|
||||
import net.hycrafthd.minecraft_authenticator.login.User;
|
||||
import net.hycrafthd.minecraft_authenticator.login.file.AuthenticationFile;
|
||||
import net.minecraft.client.util.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
public class GameAuthenticationFlow {
|
||||
/**
|
||||
* Creates an authenticated Minecraft session from given environment variables.
|
||||
* AUTH_TYPE may be "mojang", "microsoft", or "refresh" (default).
|
||||
* Under "mojang" authentication, you must supply the "AUTH_USERNAME" and "AUTH_PASSWORD" variables.
|
||||
* Under "microsoft" authentication, you must supply the "AUTH_TOKEN" variable.
|
||||
*
|
||||
* @return a valid Session, or null
|
||||
*/
|
||||
public static Session createSessionFromEnvironment() {
|
||||
try {
|
||||
Authenticator.Builder readyAuth = createAuthenticator();
|
||||
if (readyAuth != null) {
|
||||
Authenticator authenticator = readyAuth.run();
|
||||
Optional<User> userOpt = authenticator.getUser();
|
||||
if (userOpt.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
saveAuthData(authenticator.getResultFile());
|
||||
|
||||
User user = userOpt.get();
|
||||
return new Session(
|
||||
user.getName(),
|
||||
user.getUuid(),
|
||||
user.getAccessToken(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Session.AccountType.byName(user.getType())
|
||||
);
|
||||
}
|
||||
} catch (AuthenticationException | IOException ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Path getAuthDataFile() {
|
||||
String authFilePath = System.getenv("AUTH_DATA_FILE");
|
||||
authFilePath = authFilePath == null ? "mc_auth.dat" : authFilePath;
|
||||
return Paths.get(authFilePath);
|
||||
}
|
||||
|
||||
private static Authenticator.Builder createAuthenticator() throws IOException {
|
||||
String authType = System.getenv("AUTH_TYPE");
|
||||
authType = authType == null ? "refresh" : authType;
|
||||
|
||||
switch (authType) {
|
||||
case "mojang":
|
||||
return createMojangAuthenticator();
|
||||
case "microsoft":
|
||||
return createMicrosoftAuthenticator();
|
||||
case "refresh":
|
||||
return createRefreshAuthenticator();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Authenticator.Builder createMojangAuthenticator() {
|
||||
String username = System.getenv("AUTH_USERNAME");
|
||||
String password = System.getenv("AUTH_PASSWORD");
|
||||
|
||||
if (username == null || password == null) {
|
||||
throw new RuntimeException("Attempted to perform Mojang authentication without AUTH_USERNAME or AUTH_PASSWORD set");
|
||||
}
|
||||
|
||||
return Authenticator.ofYggdrasil("-", username, password).shouldAuthenticate();
|
||||
}
|
||||
|
||||
private static Authenticator.Builder createMicrosoftAuthenticator() {
|
||||
String token = System.getenv("AUTH_TOKEN");
|
||||
if (token == null) {
|
||||
throw new RuntimeException("Attempted to perform Microsoft authentication without AUTH_TOKEN set");
|
||||
}
|
||||
|
||||
return Authenticator.ofMicrosoft(token).shouldAuthenticate();
|
||||
}
|
||||
|
||||
private static Authenticator.Builder createRefreshAuthenticator() throws IOException {
|
||||
Path authData = getAuthDataFile();
|
||||
if (!Files.exists(authData)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = Files.newInputStream(authData)) {
|
||||
AuthenticationFile file = AuthenticationFile.read(inputStream);
|
||||
return Authenticator.of(file).shouldAuthenticate();
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveAuthData(AuthenticationFile file) throws IOException {
|
||||
try (OutputStream stream = Files.newOutputStream(getAuthDataFile())) {
|
||||
file.write(stream);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package site.hackery.unknit;
|
||||
package site.hackery.unknit.mixin;
|
||||
|
||||
import net.minecraft.client.*;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.*;
|
||||
import net.minecraft.client.ClientBrandRetriever;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = ClientBrandRetriever.class, remap = false)
|
||||
public abstract class MixinClientBrandRetriever {
|
|
@ -1,4 +1,4 @@
|
|||
package site.hackery.unknit;
|
||||
package site.hackery.unknit.mixin;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.util.ModStatus;
|
|
@ -0,0 +1,51 @@
|
|||
package site.hackery.unknit.mixin;
|
||||
|
||||
|
||||
import net.minecraft.client.main.Main;
|
||||
import net.minecraft.client.util.Session;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
import site.hackery.unknit.auth.GameAuthenticationFlow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Main.class)
|
||||
public abstract class MixinMinecraftMain {
|
||||
private static void replace(List<String> arguments, String tag, String value) {
|
||||
int index = arguments.indexOf(tag);
|
||||
if (index != -1) {
|
||||
arguments.set(index + 1, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceVersion(List<String> arguments) {
|
||||
replace(arguments, "--version", "1.18.1");
|
||||
replace(arguments, "--versionType", "release");
|
||||
}
|
||||
|
||||
private static void replaceSession(List<String> arguments, Session session) {
|
||||
replace(arguments, "--accessToken", session.getAccessToken());
|
||||
|
||||
arguments.add("--username");
|
||||
arguments.add(session.getUsername());
|
||||
|
||||
arguments.add("--uuid");
|
||||
arguments.add(session.getUuid());
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "main", at = @At("HEAD"), argsOnly = true, remap = false)
|
||||
private static String[] setArgs(String[] args) {
|
||||
List<String> arguments = new ArrayList<>(Arrays.asList(args));
|
||||
replaceVersion(arguments);
|
||||
|
||||
Session session = GameAuthenticationFlow.createSessionFromEnvironment();
|
||||
if (session != null) {
|
||||
replaceSession(arguments, session);
|
||||
}
|
||||
|
||||
return arguments.toArray(new String[0]);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "site.hackery.unknit",
|
||||
"package": "site.hackery.unknit.mixin",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
Loading…
Reference in a new issue