Skip to content

Commit 5919c12

Browse files
committed
Updated AltManager
1 parent 9551dd8 commit 5919c12

5 files changed

Lines changed: 331 additions & 36 deletions

File tree

src/main/java/net/wurstclient/altmanager/AltsFile.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,12 @@ private static Alt loadAlt(String nameOrEmail, JsonObject jsonAlt)
155155
String refreshToken =
156156
JsonUtils.getAsString(jsonAlt.get("refresh_token"), "");
157157
String name = JsonUtils.getAsString(jsonAlt.get("name"), "");
158+
String clientId =
159+
JsonUtils.getAsString(jsonAlt.get("client_id"), "");
158160

159161
if(!token.isEmpty() || !refreshToken.isEmpty())
160-
return new TokenAlt(token, refreshToken, name, starred);
162+
return new TokenAlt(token, refreshToken, name, starred,
163+
clientId);
161164

162165
return new CrackedAlt(nameOrEmail, starred);
163166
}

src/main/java/net/wurstclient/altmanager/MicrosoftLoginManager.java

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,23 @@ public static void loginWithToken(String token) throws LoginException
127127
public static void loginWithRefreshToken(String refreshToken)
128128
throws LoginException
129129
{
130-
System.out.println("Logging in with refresh token...");
130+
loginWithRefreshToken(refreshToken, null);
131+
}
132+
133+
public static void loginWithRefreshToken(String refreshToken,
134+
String clientId) throws LoginException
135+
{
136+
boolean hasCustomClient = clientId != null && !clientId.isBlank()
137+
&& !clientId.equals(CLIENT_ID);
138+
System.out.println("Logging in with refresh token" + (hasCustomClient
139+
? " (custom client_id: " + clientId + ")" : " (default client_id)")
140+
+ "...");
131141
long startTime = System.nanoTime();
132142

133143
try
134144
{
135145
MinecraftProfile mcProfile =
136-
authenticateRefreshTokenWithoutSession(refreshToken);
146+
authenticateRefreshTokenWithoutSession(refreshToken, clientId);
137147
System.out.println("Refresh-token auth resolved profile: "
138148
+ mcProfile.getName() + " (" + mcProfile.getUUID() + ")");
139149
setSession(mcProfile);
@@ -145,6 +155,8 @@ public static void loginWithRefreshToken(String refreshToken)
145155
{
146156
System.out.println("Refresh-token login failed after "
147157
+ (System.nanoTime() - startTime) / 1e6D + " ms");
158+
System.out.println("Exception: " + e.getMessage());
159+
e.printStackTrace();
148160
throw e;
149161
}
150162
}
@@ -171,22 +183,36 @@ public static MinecraftProfile authenticateTokenWithoutSession(String token)
171183

172184
public static MinecraftProfile authenticateRefreshTokenWithoutSession(
173185
String refreshToken) throws LoginException
186+
{
187+
return authenticateRefreshTokenWithoutSession(refreshToken, null);
188+
}
189+
190+
public static MinecraftProfile authenticateRefreshTokenWithoutSession(
191+
String refreshToken, String clientId) throws LoginException
174192
{
175193
if(refreshToken == null || refreshToken.isBlank())
176194
throw new LoginException("Refresh token cannot be empty.");
177195

178-
String msftAccessToken =
179-
getMicrosoftAccessTokenFromRefreshToken(refreshToken.trim());
196+
String msftAccessToken = getMicrosoftAccessTokenFromRefreshToken(
197+
refreshToken.trim(), clientId);
180198
return getAccountFromMicrosoftAccessToken(msftAccessToken);
181199
}
182200

183201
public static MinecraftProfile authenticateTokenAltWithoutSession(
184202
String token, String refreshToken) throws LoginException
203+
{
204+
return authenticateTokenAltWithoutSession(token, refreshToken, null);
205+
}
206+
207+
public static MinecraftProfile authenticateTokenAltWithoutSession(
208+
String token, String refreshToken, String clientId)
209+
throws LoginException
185210
{
186211
String trimmedRefresh = refreshToken == null ? "" : refreshToken.trim();
187212

188213
if(!trimmedRefresh.isEmpty())
189-
return authenticateRefreshTokenWithoutSession(trimmedRefresh);
214+
return authenticateRefreshTokenWithoutSession(trimmedRefresh,
215+
clientId);
190216

191217
return authenticateTokenWithoutSession(token);
192218
}
@@ -394,13 +420,33 @@ private static String getMicrosoftAccessToken(String authCode)
394420
private static String getMicrosoftAccessTokenFromRefreshToken(
395421
String refreshToken) throws LoginException
396422
{
423+
return getMicrosoftAccessTokenFromRefreshToken(refreshToken, null);
424+
}
425+
426+
private static String getMicrosoftAccessTokenFromRefreshToken(
427+
String refreshToken, String clientIdOverride) throws LoginException
428+
{
429+
String effectiveClientId =
430+
clientIdOverride != null && !clientIdOverride.isBlank()
431+
? clientIdOverride.trim() : CLIENT_ID;
432+
433+
boolean isNonDefaultClient = !effectiveClientId.equals(CLIENT_ID);
434+
435+
System.out.println("Using client_id: " + effectiveClientId);
436+
397437
Map<String, String> postData = new HashMap<>();
398-
postData.put("client_id", CLIENT_ID);
438+
postData.put("client_id", effectiveClientId);
399439
postData.put("refresh_token", refreshToken);
400440
postData.put("grant_type", "refresh_token");
401-
postData.put("redirect_uri",
402-
"https://login.live.com/oauth20_desktop.srf");
403-
postData.put("scope", SCOPE_UNENCODED);
441+
442+
// When using a non-default client, omit scope and redirect_uri
443+
// to avoid mismatches with how the token was originally obtained.
444+
if(!isNonDefaultClient)
445+
{
446+
postData.put("redirect_uri",
447+
"https://login.live.com/oauth20_desktop.srf");
448+
postData.put("scope", SCOPE_UNENCODED);
449+
}
404450

405451
byte[] encodedDataBytes =
406452
urlEncodeMap(postData).getBytes(StandardCharsets.UTF_8);
@@ -422,6 +468,16 @@ private static String getMicrosoftAccessTokenFromRefreshToken(
422468
out.write(encodedDataBytes);
423469
}
424470

471+
int responseCode = connection.getResponseCode();
472+
if(responseCode != 200)
473+
{
474+
String errorBody = readErrorBody(connection);
475+
System.out.println("Microsoft token refresh returned HTTP "
476+
+ responseCode + ": " + errorBody);
477+
throw new LoginException("Microsoft returned HTTP "
478+
+ responseCode + ": " + errorBody);
479+
}
480+
425481
WsonObject json = JsonUtils.parseConnectionToObject(connection);
426482
return json.getString("access_token");
427483

@@ -435,13 +491,36 @@ private static String getMicrosoftAccessTokenFromRefreshToken(
435491
}
436492
}
437493

494+
private static String readErrorBody(HttpURLConnection connection)
495+
{
496+
try(InputStream errorStream = connection.getErrorStream())
497+
{
498+
if(errorStream == null)
499+
return connection.getResponseMessage();
500+
501+
try(BufferedReader reader = new BufferedReader(
502+
new InputStreamReader(errorStream, StandardCharsets.UTF_8)))
503+
{
504+
return reader.lines().collect(Collectors.joining());
505+
}
506+
}catch(IOException e)
507+
{
508+
return "(could not read error body)";
509+
}
510+
}
511+
438512
private static XBoxLiveToken getXBLToken(String msftAccessToken)
439513
throws LoginException
440514
{
441515
JsonObject properties = new JsonObject();
442516
properties.addProperty("AuthMethod", "RPS");
443517
properties.addProperty("SiteName", "user.auth.xboxlive.com");
444-
properties.addProperty("RpsTicket", msftAccessToken);
518+
519+
// The RpsTicket must be the access token prefixed with "d="
520+
// to indicate it's a delegated token from Microsoft account.
521+
String rpsTicket = msftAccessToken.startsWith("d=") ? msftAccessToken
522+
: "d=" + msftAccessToken;
523+
properties.addProperty("RpsTicket", rpsTicket);
445524

446525
JsonObject postData = new JsonObject();
447526
postData.addProperty("RelyingParty", "http://auth.xboxlive.com");
@@ -452,7 +531,8 @@ private static XBoxLiveToken getXBLToken(String msftAccessToken)
452531

453532
try
454533
{
455-
URLConnection connection = XBL_TOKEN_URL.openConnection();
534+
HttpURLConnection connection =
535+
(HttpURLConnection)XBL_TOKEN_URL.openConnection();
456536

457537
connection.setRequestProperty("Content-Type", "application/json");
458538
connection.setRequestProperty("Accept", "application/json");
@@ -466,6 +546,16 @@ private static XBoxLiveToken getXBLToken(String msftAccessToken)
466546
out.write(request.getBytes(StandardCharsets.US_ASCII));
467547
}
468548

549+
int responseCode = connection.getResponseCode();
550+
if(responseCode != 200)
551+
{
552+
String errorBody = readErrorBody(connection);
553+
System.out.println("XBL authentication returned HTTP "
554+
+ responseCode + ": " + errorBody);
555+
throw new LoginException("XBL auth returned HTTP "
556+
+ responseCode + ": " + errorBody);
557+
}
558+
469559
WsonObject json = JsonUtils.parseConnectionToObject(connection);
470560

471561
String token = json.getString("Token");

src/main/java/net/wurstclient/altmanager/TokenAlt.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ public final class TokenAlt extends Alt
1616
{
1717
private final String token;
1818
private final String refreshToken;
19+
private final String clientId;
1920
private String name;
2021

2122
public TokenAlt(String token, String refreshToken, String name,
2223
boolean favorite)
24+
{
25+
this(token, refreshToken, name, favorite, "");
26+
}
27+
28+
public TokenAlt(String token, String refreshToken, String name,
29+
boolean favorite, String clientId)
2330
{
2431
super(favorite);
2532

@@ -32,15 +39,28 @@ public TokenAlt(String token, String refreshToken, String name,
3239

3340
this.token = normalizedToken;
3441
this.refreshToken = normalizedRefresh;
42+
this.clientId = clientId == null ? "" : clientId.trim();
3543
this.name = name == null ? "" : name;
3644
}
3745

3846
@Override
3947
public void login() throws LoginException
4048
{
4149
if(!refreshToken.isEmpty())
42-
MicrosoftLoginManager.loginWithRefreshToken(refreshToken);
43-
else
50+
{
51+
if(!clientId.isEmpty())
52+
{
53+
System.out.println(
54+
"*** TOKENALT using client_id: " + clientId + " ***");
55+
MicrosoftLoginManager.loginWithRefreshToken(refreshToken,
56+
clientId);
57+
}else
58+
{
59+
System.out.println(
60+
"*** TOKENALT using DEFAULT client_id (old path) ***");
61+
MicrosoftLoginManager.loginWithRefreshToken(refreshToken);
62+
}
63+
}else
4464
MicrosoftLoginManager.loginWithToken(token);
4565

4666
name = getNameFromSession();
@@ -67,6 +87,8 @@ public void exportAsJson(JsonObject json)
6787
jsonAlt.addProperty("refresh_token", refreshToken);
6888
jsonAlt.addProperty("name", name);
6989
jsonAlt.addProperty("starred", isFavorite());
90+
if(!clientId.isEmpty())
91+
jsonAlt.addProperty("client_id", clientId);
7092

7193
String key = "token_"
7294
+ Integer.toHexString(Objects.hash(token, refreshToken, name));
@@ -76,7 +98,8 @@ public void exportAsJson(JsonObject json)
7698
@Override
7799
public String exportAsTXT()
78100
{
79-
return "token:" + token + ":" + refreshToken + ":" + name;
101+
return "token:" + token + ":" + refreshToken + ":" + name
102+
+ (clientId.isEmpty() ? "" : ":" + clientId);
80103
}
81104

82105
@Override
@@ -101,6 +124,11 @@ public String getRefreshToken()
101124
return refreshToken;
102125
}
103126

127+
public String getClientId()
128+
{
129+
return clientId;
130+
}
131+
104132
void setName(String name)
105133
{
106134
this.name = name == null ? "" : name;
@@ -109,7 +137,7 @@ void setName(String name)
109137
@Override
110138
public int hashCode()
111139
{
112-
return Objects.hash(token, refreshToken);
140+
return Objects.hash(token, refreshToken, clientId);
113141
}
114142

115143
@Override
@@ -123,6 +151,7 @@ public boolean equals(Object obj)
123151

124152
TokenAlt other = (TokenAlt)obj;
125153
return Objects.equals(token, other.token)
126-
&& Objects.equals(refreshToken, other.refreshToken);
154+
&& Objects.equals(refreshToken, other.refreshToken)
155+
&& Objects.equals(clientId, other.clientId);
127156
}
128157
}

0 commit comments

Comments
 (0)