baltica 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -16
- package/dist/client/client.js +2 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/skin-loader.d.ts +38 -0
- package/dist/client/skin-loader.js +156 -0
- package/dist/client/types/client-data.js +19 -1
- package/dist/client/types/client-options.d.ts +2 -0
- package/dist/client/types/client-options.js +2 -0
- package/dist/client/types/login-data.d.ts +7 -0
- package/dist/client/types/payload.d.ts +1 -0
- package/dist/client/worker/worker-client.js +1 -1
- package/dist/shared/auth/authentication.d.ts +27 -5
- package/dist/shared/auth/authentication.js +277 -41
- package/dist/shared/auth.js +168 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -51,29 +51,57 @@ const client = new Client({
|
|
|
51
51
|
port: 19132,
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// Connect and get server info
|
|
55
54
|
await client.connect();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Email/Password Authentication
|
|
58
|
+
|
|
59
|
+
You can authenticate directly with your Microsoft account using email and password:
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
client
|
|
59
|
-
|
|
61
|
+
```typescript
|
|
62
|
+
const client = new Client({
|
|
63
|
+
address: "play.server.com",
|
|
64
|
+
port: 19132,
|
|
65
|
+
email: "your-email@outlook.com",
|
|
66
|
+
password: "your-password",
|
|
60
67
|
});
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
await client.connect();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Important Notes:**
|
|
73
|
+
- This method only works with accounts that have 2FA (Two-Factor Authentication) **disabled**
|
|
74
|
+
- The account must have an Xbox profile and own Minecraft Bedrock Edition or launched minecraft at least once.
|
|
75
|
+
- User tokens are cached for ~14 days to minimize login requests (BETA)
|
|
76
|
+
- Tokens are stored in the `tokens` folder by default
|
|
77
|
+
|
|
78
|
+
### Using a Proxy
|
|
79
|
+
|
|
80
|
+
Baltica supports SOCKS5 proxies for client connections:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const client = new Client({
|
|
84
|
+
address: "play.server.com",
|
|
85
|
+
port: 19132,
|
|
86
|
+
email: "your-email@outlook.com",
|
|
87
|
+
password: "your-password",
|
|
88
|
+
proxy: {
|
|
89
|
+
host: "proxy.example.com",
|
|
90
|
+
port: 1080,
|
|
91
|
+
userId: "proxy-username", // Optional
|
|
92
|
+
password: "proxy-password", // Optional
|
|
93
|
+
},
|
|
94
|
+
skinFile: `./skins/skin.png`, // If you want to load a skin from a png file
|
|
74
95
|
});
|
|
96
|
+
|
|
97
|
+
await client.connect();
|
|
75
98
|
```
|
|
76
99
|
|
|
100
|
+
This is useful for:
|
|
101
|
+
- Bypassing IP restrictions
|
|
102
|
+
- Testing from different geographic locations
|
|
103
|
+
- Managing multiple bot connections
|
|
104
|
+
|
|
77
105
|
### Server Usage
|
|
78
106
|
|
|
79
107
|
Want to create your own Minecraft server? We got you:
|
package/dist/client/client.js
CHANGED
|
@@ -57,6 +57,8 @@ class Client extends shared_1.Emitter {
|
|
|
57
57
|
this.packetCompressor = new shared_1.PacketCompressor(this);
|
|
58
58
|
this.handleGamePackets();
|
|
59
59
|
this.raknet.on("encapsulated", this.handleEncapsulated.bind(this));
|
|
60
|
+
// Wait for authentication to complete before sending network settings request
|
|
61
|
+
await this.waitForSessionReady();
|
|
60
62
|
const request = new protocol_1.RequestNetworkSettingsPacket();
|
|
61
63
|
request.protocol = types_1.ProtocolList[types_1.CurrentVersionConst];
|
|
62
64
|
this.send(request);
|
package/dist/client/index.d.ts
CHANGED
package/dist/client/index.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SkinData } from "./types/payload";
|
|
2
|
+
/**
|
|
3
|
+
* Load a Minecraft Bedrock skin from a PNG file
|
|
4
|
+
* @param pngPath - Path to the PNG skin file (64x64 or 64x32)
|
|
5
|
+
* @returns SkinData object ready to use in client options
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Client, loadSkinFromPNG } from 'baltica';
|
|
10
|
+
*
|
|
11
|
+
* const client = new Client({
|
|
12
|
+
* address: "play.example.com",
|
|
13
|
+
* port: 19132,
|
|
14
|
+
* skinData: loadSkinFromPNG('./my-skin.png')
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadSkinFromPNG(pngPath: string): SkinData;
|
|
19
|
+
/**
|
|
20
|
+
* Load a Minecraft Bedrock skin from raw RGBA buffer
|
|
21
|
+
* @param buffer - Raw RGBA pixel data buffer
|
|
22
|
+
* @param width - Skin width (typically 64)
|
|
23
|
+
* @param height - Skin height (typically 64 or 32)
|
|
24
|
+
* @returns SkinData object ready to use in client options
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { Client, loadSkinFromBuffer } from 'baltica';
|
|
29
|
+
*
|
|
30
|
+
* const rgbaBuffer = Buffer.from([...]); // Your RGBA data
|
|
31
|
+
* const client = new Client({
|
|
32
|
+
* address: "play.example.com",
|
|
33
|
+
* port: 19132,
|
|
34
|
+
* skinData: loadSkinFromBuffer(rgbaBuffer, 64, 64)
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadSkinFromBuffer(buffer: Buffer, width: number, height: number): SkinData;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadSkinFromPNG = loadSkinFromPNG;
|
|
37
|
+
exports.loadSkinFromBuffer = loadSkinFromBuffer;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const pngjs_1 = require("pngjs");
|
|
40
|
+
/**
|
|
41
|
+
* Load a Minecraft Bedrock skin from a PNG file
|
|
42
|
+
* @param pngPath - Path to the PNG skin file (64x64 or 64x32)
|
|
43
|
+
* @returns SkinData object ready to use in client options
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* import { Client, loadSkinFromPNG } from 'baltica';
|
|
48
|
+
*
|
|
49
|
+
* const client = new Client({
|
|
50
|
+
* address: "play.example.com",
|
|
51
|
+
* port: 19132,
|
|
52
|
+
* skinData: loadSkinFromPNG('./my-skin.png')
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function loadSkinFromPNG(pngPath) {
|
|
57
|
+
// Read and parse PNG file
|
|
58
|
+
const pngData = fs.readFileSync(pngPath);
|
|
59
|
+
const png = pngjs_1.PNG.sync.read(pngData);
|
|
60
|
+
// Validate dimensions
|
|
61
|
+
if (png.width !== 64 || (png.height !== 64 && png.height !== 32)) {
|
|
62
|
+
throw new Error(`Invalid skin dimensions: ${png.width}x${png.height}. Expected 64x64 or 64x32`);
|
|
63
|
+
}
|
|
64
|
+
// Convert RGBA pixel data to base64
|
|
65
|
+
const base64Skin = png.data.toString("base64");
|
|
66
|
+
// Create skin resource patch for standard humanoid model
|
|
67
|
+
const resourcePatch = {
|
|
68
|
+
geometry: {
|
|
69
|
+
default: "geometry.humanoid.custom",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
AnimatedImageData: [],
|
|
74
|
+
ArmSize: "wide",
|
|
75
|
+
CapeData: "",
|
|
76
|
+
CapeId: "",
|
|
77
|
+
CapeImageHeight: 0,
|
|
78
|
+
CapeImageWidth: 0,
|
|
79
|
+
CapeOnClassicSkin: false,
|
|
80
|
+
PersonaPieces: [],
|
|
81
|
+
PersonaSkin: false,
|
|
82
|
+
PieceTintColors: [],
|
|
83
|
+
PremiumSkin: false,
|
|
84
|
+
SkinAnimationData: "",
|
|
85
|
+
SkinColor: "#0",
|
|
86
|
+
SkinData: base64Skin,
|
|
87
|
+
SkinGeometryData: "",
|
|
88
|
+
SkinGeometryDataEngineVersion: "",
|
|
89
|
+
SkinId: `custom_skin_${Date.now()}`,
|
|
90
|
+
SkinImageHeight: png.height,
|
|
91
|
+
SkinImageWidth: png.width,
|
|
92
|
+
SkinResourcePatch: Buffer.from(JSON.stringify(resourcePatch)).toString("base64"),
|
|
93
|
+
TrustedSkin: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Load a Minecraft Bedrock skin from raw RGBA buffer
|
|
98
|
+
* @param buffer - Raw RGBA pixel data buffer
|
|
99
|
+
* @param width - Skin width (typically 64)
|
|
100
|
+
* @param height - Skin height (typically 64 or 32)
|
|
101
|
+
* @returns SkinData object ready to use in client options
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* import { Client, loadSkinFromBuffer } from 'baltica';
|
|
106
|
+
*
|
|
107
|
+
* const rgbaBuffer = Buffer.from([...]); // Your RGBA data
|
|
108
|
+
* const client = new Client({
|
|
109
|
+
* address: "play.example.com",
|
|
110
|
+
* port: 19132,
|
|
111
|
+
* skinData: loadSkinFromBuffer(rgbaBuffer, 64, 64)
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function loadSkinFromBuffer(buffer, width, height) {
|
|
116
|
+
// Validate dimensions
|
|
117
|
+
if (width !== 64 || (height !== 64 && height !== 32)) {
|
|
118
|
+
throw new Error(`Invalid skin dimensions: ${width}x${height}. Expected 64x64 or 64x32`);
|
|
119
|
+
}
|
|
120
|
+
// Validate buffer size (RGBA = 4 bytes per pixel)
|
|
121
|
+
const expectedSize = width * height * 4;
|
|
122
|
+
if (buffer.length !== expectedSize) {
|
|
123
|
+
throw new Error(`Invalid buffer size: ${buffer.length} bytes. Expected ${expectedSize} bytes for ${width}x${height} RGBA`);
|
|
124
|
+
}
|
|
125
|
+
// Convert to base64
|
|
126
|
+
const base64Skin = buffer.toString("base64");
|
|
127
|
+
// Create skin resource patch
|
|
128
|
+
const resourcePatch = {
|
|
129
|
+
geometry: {
|
|
130
|
+
default: "geometry.humanoid.custom",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
AnimatedImageData: [],
|
|
135
|
+
ArmSize: "wide",
|
|
136
|
+
CapeData: "",
|
|
137
|
+
CapeId: "",
|
|
138
|
+
CapeImageHeight: 0,
|
|
139
|
+
CapeImageWidth: 0,
|
|
140
|
+
CapeOnClassicSkin: false,
|
|
141
|
+
PersonaPieces: [],
|
|
142
|
+
PersonaSkin: false,
|
|
143
|
+
PieceTintColors: [],
|
|
144
|
+
PremiumSkin: false,
|
|
145
|
+
SkinAnimationData: "",
|
|
146
|
+
SkinColor: "#0",
|
|
147
|
+
SkinData: base64Skin,
|
|
148
|
+
SkinGeometryData: "",
|
|
149
|
+
SkinGeometryDataEngineVersion: "",
|
|
150
|
+
SkinId: `custom_skin_${Date.now()}`,
|
|
151
|
+
SkinImageHeight: height,
|
|
152
|
+
SkinImageWidth: width,
|
|
153
|
+
SkinResourcePatch: Buffer.from(JSON.stringify(resourcePatch)).toString("base64"),
|
|
154
|
+
TrustedSkin: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -42,6 +42,7 @@ const uuid_1345_1 = require("uuid-1345");
|
|
|
42
42
|
const __1 = require("../");
|
|
43
43
|
const types_1 = require("../../shared/types");
|
|
44
44
|
const login_data_1 = require("./login-data");
|
|
45
|
+
const skin_loader_1 = require("../skin-loader");
|
|
45
46
|
const PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
|
|
46
47
|
const algorithm = "ES384";
|
|
47
48
|
class ClientData {
|
|
@@ -52,6 +53,10 @@ class ClientData {
|
|
|
52
53
|
this.client = client;
|
|
53
54
|
this.payload = (0, __1.createDefaultPayload)(client);
|
|
54
55
|
this.loginData = (0, login_data_1.prepareLoginData)();
|
|
56
|
+
// Load skin from file if skinFile is provided
|
|
57
|
+
if (client.options.skinFile && !client.options.skinData) {
|
|
58
|
+
client.options.skinData = (0, skin_loader_1.loadSkinFromPNG)(client.options.skinFile);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
createLoginPacket() {
|
|
57
62
|
const loginPacket = new protocol_1.LoginPacket();
|
|
@@ -68,7 +73,7 @@ class ClientData {
|
|
|
68
73
|
return loginPacket;
|
|
69
74
|
}
|
|
70
75
|
async createClientChain(mojangKey, offline) {
|
|
71
|
-
const { clientX509, ecdhKeyPair } = this.loginData;
|
|
76
|
+
const { clientX509, ecdhKeyPair, sessionTokenData } = this.loginData;
|
|
72
77
|
let payload;
|
|
73
78
|
let header;
|
|
74
79
|
if (offline) {
|
|
@@ -96,6 +101,19 @@ class ClientData {
|
|
|
96
101
|
identityPublicKey: mojangKey || PUBLIC_KEY,
|
|
97
102
|
certificateAuthority: true,
|
|
98
103
|
};
|
|
104
|
+
// Add session token data for PocketMine 1.21.100+ compatibility
|
|
105
|
+
if (sessionTokenData) {
|
|
106
|
+
payload.ipt = sessionTokenData.ipt;
|
|
107
|
+
payload.tid = sessionTokenData.tid;
|
|
108
|
+
payload.mid = sessionTokenData.mid;
|
|
109
|
+
payload.xid = sessionTokenData.xid;
|
|
110
|
+
payload.cpk = sessionTokenData.cpk;
|
|
111
|
+
payload.xname = this.client.profile.name;
|
|
112
|
+
}
|
|
113
|
+
// Add pfcd if available (PlayFab ID)
|
|
114
|
+
if (this.payload.pfcd) {
|
|
115
|
+
payload.pfcd = this.payload.pfcd;
|
|
116
|
+
}
|
|
99
117
|
header = {
|
|
100
118
|
alg: algorithm,
|
|
101
119
|
x5u: clientX509,
|
|
@@ -33,6 +33,8 @@ export type ClientOptions = {
|
|
|
33
33
|
tokensFolder: string;
|
|
34
34
|
/** Skin Data for custom skins (By default we parse it from json)*/
|
|
35
35
|
skinData: SkinData | undefined;
|
|
36
|
+
/** Path to PNG skin file (alternative to skinData) */
|
|
37
|
+
skinFile: string | undefined;
|
|
36
38
|
/** LoginPacket data Customization */
|
|
37
39
|
loginOptions: LoginPacketOptions;
|
|
38
40
|
/** The View Distance of the client. */
|
|
@@ -18,6 +18,8 @@ exports.defaultClientOptions = {
|
|
|
18
18
|
tokensFolder: "tokens",
|
|
19
19
|
/** Default Value: undefined */
|
|
20
20
|
skinData: undefined,
|
|
21
|
+
/** Default Value: undefined */
|
|
22
|
+
skinFile: undefined,
|
|
21
23
|
loginOptions: {
|
|
22
24
|
/** Default Value: Unknown */
|
|
23
25
|
currentInputMode: protocol_1.InputMode.Unknown,
|
|
@@ -6,6 +6,13 @@ type LoginData = {
|
|
|
6
6
|
clientX509: string;
|
|
7
7
|
clientIdentityChain: string;
|
|
8
8
|
clientUserChain: string;
|
|
9
|
+
sessionTokenData?: {
|
|
10
|
+
ipt: string;
|
|
11
|
+
tid: string;
|
|
12
|
+
mid: string;
|
|
13
|
+
xid: string;
|
|
14
|
+
cpk: string;
|
|
15
|
+
};
|
|
9
16
|
};
|
|
10
17
|
export declare const prepareLoginData: () => LoginData;
|
|
11
18
|
export type { LoginData };
|
|
@@ -6,7 +6,7 @@ const worker_1 = require("./worker");
|
|
|
6
6
|
class WorkerClient extends raknet_1.EventEmitter {
|
|
7
7
|
constructor(options) {
|
|
8
8
|
super();
|
|
9
|
-
this._options = { ...raknet_1.
|
|
9
|
+
this._options = { ...(0, raknet_1.createDefaultClientOptions)(), ...options };
|
|
10
10
|
this._worker = (0, worker_1.connect)(this._options);
|
|
11
11
|
this.handleEvents();
|
|
12
12
|
}
|
|
@@ -1,21 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Xbox Live Authentication Module
|
|
3
|
+
*
|
|
4
|
+
* Based on the authentication flow from @xboxreplay/xboxlive-auth
|
|
5
|
+
* https://github.com/XboxReplay/xboxlive-auth
|
|
6
|
+
*
|
|
7
|
+
* Modified to support SOCKS5 proxies for all HTTP requests and Minecraft Authentication
|
|
8
|
+
*/
|
|
3
9
|
export interface BedrockTokens {
|
|
4
10
|
chains: string[];
|
|
5
11
|
xuid: string;
|
|
6
12
|
gamertag: string;
|
|
7
13
|
userHash: string;
|
|
14
|
+
xstsToken: string;
|
|
15
|
+
playfabXstsToken: string;
|
|
16
|
+
playfabUserHash: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ProxyOptions {
|
|
19
|
+
host: string;
|
|
20
|
+
port: number;
|
|
21
|
+
userId?: string;
|
|
22
|
+
password?: string;
|
|
8
23
|
}
|
|
9
24
|
export interface AuthOptions {
|
|
10
25
|
email: string;
|
|
11
26
|
password: string;
|
|
12
27
|
clientPublicKey: string;
|
|
13
28
|
cacheDir?: string;
|
|
29
|
+
proxy?: ProxyOptions;
|
|
14
30
|
}
|
|
15
31
|
/**
|
|
16
32
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
17
|
-
*
|
|
33
|
+
* Supports SOCKS5 proxy for all authentication requests
|
|
18
34
|
*/
|
|
19
35
|
export declare function authenticateWithCredentials(options: AuthOptions): Promise<BedrockTokens>;
|
|
20
|
-
export
|
|
21
|
-
export
|
|
36
|
+
export type Email = string;
|
|
37
|
+
export interface AuthenticateResponse {
|
|
38
|
+
access_token: string;
|
|
39
|
+
token_type: string;
|
|
40
|
+
expires_in: number;
|
|
41
|
+
scope: string;
|
|
42
|
+
user_id: string;
|
|
43
|
+
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Xbox Live Authentication Module
|
|
4
|
+
*
|
|
5
|
+
* Based on the authentication flow from @xboxreplay/xboxlive-auth
|
|
6
|
+
* https://github.com/XboxReplay/xboxlive-auth
|
|
7
|
+
*
|
|
8
|
+
* Modified to support SOCKS5 proxies for all HTTP requests and Minecraft Authentication
|
|
9
|
+
*/
|
|
2
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
11
|
if (k2 === undefined) k2 = k;
|
|
4
12
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -33,15 +41,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
41
|
};
|
|
34
42
|
})();
|
|
35
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.xnet = exports.live = void 0;
|
|
37
44
|
exports.authenticateWithCredentials = authenticateWithCredentials;
|
|
38
|
-
const xboxlive_auth_1 = require("@xboxreplay/xboxlive-auth");
|
|
39
|
-
Object.defineProperty(exports, "live", { enumerable: true, get: function () { return xboxlive_auth_1.live; } });
|
|
40
|
-
Object.defineProperty(exports, "xnet", { enumerable: true, get: function () { return xboxlive_auth_1.xnet; } });
|
|
41
45
|
const fs = __importStar(require("node:fs"));
|
|
42
46
|
const path = __importStar(require("node:path"));
|
|
43
47
|
const raknet_1 = require("@sanctumterra/raknet");
|
|
48
|
+
const fetch_socks_1 = require("fetch-socks");
|
|
49
|
+
const undici_1 = require("undici");
|
|
44
50
|
const MINECRAFT_BEDROCK_RELYING_PARTY = "https://multiplayer.minecraft.net/";
|
|
51
|
+
const PLAYFAB_RELYING_PARTY = "https://b980a380.minecraft.playfabapi.com/";
|
|
52
|
+
const XBOX_AUTH_CLIENT_ID = "00000000441cc96b";
|
|
45
53
|
function hashString(str) {
|
|
46
54
|
let hash = 0;
|
|
47
55
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -63,7 +71,6 @@ function loadCache(cacheFile) {
|
|
|
63
71
|
try {
|
|
64
72
|
if (fs.existsSync(cacheFile)) {
|
|
65
73
|
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
66
|
-
// Check if token is still valid (with 1 hour buffer)
|
|
67
74
|
const expiresAt = new Date(cached.notAfter).getTime();
|
|
68
75
|
if (Date.now() < expiresAt - 3600000) {
|
|
69
76
|
return cached;
|
|
@@ -91,16 +98,47 @@ function saveCache(cacheFile, userToken, userHash, notAfter) {
|
|
|
91
98
|
/* ignore */
|
|
92
99
|
}
|
|
93
100
|
}
|
|
101
|
+
function createProxiedFetch(proxy) {
|
|
102
|
+
if (!proxy) {
|
|
103
|
+
return fetch;
|
|
104
|
+
}
|
|
105
|
+
const dispatcher = (0, fetch_socks_1.socksDispatcher)({
|
|
106
|
+
type: 5,
|
|
107
|
+
host: proxy.host,
|
|
108
|
+
port: proxy.port,
|
|
109
|
+
userId: proxy.userId,
|
|
110
|
+
password: proxy.password,
|
|
111
|
+
});
|
|
112
|
+
return async (input, init) => {
|
|
113
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
114
|
+
const response = await (0, undici_1.fetch)(url, {
|
|
115
|
+
...init,
|
|
116
|
+
dispatcher,
|
|
117
|
+
});
|
|
118
|
+
return response;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
94
121
|
/**
|
|
95
122
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
96
|
-
*
|
|
123
|
+
* Supports SOCKS5 proxy for all authentication requests
|
|
97
124
|
*/
|
|
98
125
|
async function authenticateWithCredentials(options) {
|
|
99
|
-
const { email, password, clientPublicKey, cacheDir } = options;
|
|
126
|
+
const { email, password, clientPublicKey, cacheDir, proxy } = options;
|
|
100
127
|
const cacheFile = cacheDir ? getCacheFile(cacheDir, email) : null;
|
|
128
|
+
const proxiedFetch = createProxiedFetch(proxy);
|
|
129
|
+
// Verify proxy is working by checking our IP
|
|
130
|
+
if (proxy) {
|
|
131
|
+
try {
|
|
132
|
+
const ipResp = await proxiedFetch("https://api.ipify.org?format=json");
|
|
133
|
+
const ipData = (await ipResp.json());
|
|
134
|
+
raknet_1.Logger.info(`Proxy IP verified: ${ipData.ip}`);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
raknet_1.Logger.warn(`Could not verify proxy IP: ${e instanceof Error ? e.message : String(e)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
101
140
|
let userToken;
|
|
102
141
|
let userHash;
|
|
103
|
-
// Try to use cached user token first
|
|
104
142
|
const cached = cacheFile ? loadCache(cacheFile) : null;
|
|
105
143
|
if (cached) {
|
|
106
144
|
raknet_1.Logger.info("Using cached Xbox user token...");
|
|
@@ -108,49 +146,250 @@ async function authenticateWithCredentials(options) {
|
|
|
108
146
|
userHash = cached.userHash;
|
|
109
147
|
}
|
|
110
148
|
else {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
// Exchange for Xbox user token (valid ~14 days)
|
|
115
|
-
const userTokenResp = await xboxlive_auth_1.xnet.exchangeRpsTicketForUserToken(accessToken, "t");
|
|
149
|
+
raknet_1.Logger.info(`Authenticating with Xbox Live...${proxy ? ` (via proxy ${proxy.host}:${proxy.port})` : ""}`);
|
|
150
|
+
const accessToken = await getMicrosoftAccessToken(email, password, proxiedFetch);
|
|
151
|
+
const userTokenResp = await exchangeRpsTicketForUserToken(accessToken, proxiedFetch);
|
|
116
152
|
userToken = userTokenResp.Token;
|
|
117
153
|
userHash = userTokenResp.DisplayClaims.xui[0].uhs;
|
|
118
|
-
// Cache the user token
|
|
119
154
|
if (cacheFile) {
|
|
120
155
|
saveCache(cacheFile, userToken, userHash, userTokenResp.NotAfter);
|
|
121
156
|
}
|
|
122
157
|
}
|
|
123
|
-
// Get XSTS token for Minecraft Bedrock
|
|
124
|
-
const xstsResp = await
|
|
125
|
-
XSTSRelyingParty: MINECRAFT_BEDROCK_RELYING_PARTY,
|
|
126
|
-
sandboxId: "RETAIL",
|
|
127
|
-
});
|
|
158
|
+
// Get XSTS token for Minecraft Bedrock
|
|
159
|
+
const xstsResp = await exchangeTokenForXSTSToken(userToken, MINECRAFT_BEDROCK_RELYING_PARTY, proxiedFetch);
|
|
128
160
|
const xuid = xstsResp.DisplayClaims.xui[0].xid || "";
|
|
129
|
-
// Get
|
|
130
|
-
const
|
|
161
|
+
// Get XSTS token for Playfab (needed for session token)
|
|
162
|
+
const playfabXstsResp = await exchangeTokenForXSTSToken(userToken, PLAYFAB_RELYING_PARTY, proxiedFetch);
|
|
163
|
+
const chains = await getMinecraftBedrockChains(xstsResp.Token, userHash, clientPublicKey, proxiedFetch);
|
|
131
164
|
const gamertag = extractGamertagFromChains(chains);
|
|
132
165
|
raknet_1.Logger.info(`Authenticated as: ${gamertag} (${xuid})`);
|
|
133
|
-
return {
|
|
166
|
+
return {
|
|
167
|
+
chains,
|
|
168
|
+
xuid,
|
|
169
|
+
gamertag,
|
|
170
|
+
userHash,
|
|
171
|
+
xstsToken: xstsResp.Token,
|
|
172
|
+
playfabXstsToken: playfabXstsResp.Token,
|
|
173
|
+
playfabUserHash: playfabXstsResp.DisplayClaims.xui[0].uhs,
|
|
174
|
+
};
|
|
134
175
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Get Microsoft access token using email/password via OAuth flow
|
|
178
|
+
* This implements the full browser-like login flow
|
|
179
|
+
*/
|
|
180
|
+
async function getMicrosoftAccessToken(email, password, proxiedFetch) {
|
|
181
|
+
const authUrl = "https://login.live.com/oauth20_authorize.srf";
|
|
182
|
+
const params = new URLSearchParams({
|
|
183
|
+
client_id: XBOX_AUTH_CLIENT_ID,
|
|
184
|
+
redirect_uri: "https://login.live.com/oauth20_desktop.srf",
|
|
185
|
+
response_type: "token",
|
|
186
|
+
scope: "service::user.auth.xboxlive.com::MBI_SSL",
|
|
187
|
+
display: "touch",
|
|
188
|
+
locale: "en",
|
|
189
|
+
});
|
|
190
|
+
const preAuthResp = await proxiedFetch(`${authUrl}?${params}`, {
|
|
191
|
+
headers: {
|
|
192
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
193
|
+
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
194
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
if (!preAuthResp.ok) {
|
|
198
|
+
throw new Error(`Pre-auth request failed: ${preAuthResp.status}`);
|
|
199
|
+
}
|
|
200
|
+
const preAuthHtml = await preAuthResp.text();
|
|
201
|
+
const cookies = extractCookies(preAuthResp.headers);
|
|
202
|
+
const { ppft, urlPost } = extractLoginParams(preAuthHtml);
|
|
203
|
+
const loginBody = new URLSearchParams({
|
|
204
|
+
login: email,
|
|
205
|
+
loginfmt: email,
|
|
206
|
+
passwd: password,
|
|
207
|
+
PPFT: ppft,
|
|
208
|
+
PPSX: "Passpor",
|
|
209
|
+
NewUser: "1",
|
|
210
|
+
FoundMSAs: "",
|
|
211
|
+
fspost: "0",
|
|
212
|
+
i21: "0",
|
|
213
|
+
CookieDisclosure: "0",
|
|
214
|
+
IsFidoSupported: "1",
|
|
215
|
+
isSignupPost: "0",
|
|
216
|
+
isRecoveryAttemptPost: "0",
|
|
217
|
+
i13: "0",
|
|
218
|
+
i19: Math.floor(Math.random() * 100000).toString(),
|
|
219
|
+
});
|
|
220
|
+
const loginResp = await proxiedFetch(urlPost, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: {
|
|
223
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
224
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
225
|
+
Cookie: cookies,
|
|
226
|
+
Referer: `${authUrl}?${params}`,
|
|
227
|
+
Origin: "https://login.live.com",
|
|
228
|
+
},
|
|
229
|
+
body: loginBody.toString(),
|
|
230
|
+
redirect: "manual",
|
|
231
|
+
});
|
|
232
|
+
// Check for access token in redirect
|
|
233
|
+
let location = loginResp.headers.get("location") || "";
|
|
234
|
+
// Follow redirects manually to find the access token
|
|
235
|
+
let attempts = 0;
|
|
236
|
+
while (attempts < 5 && !location.includes("access_token=")) {
|
|
237
|
+
if (!location) {
|
|
238
|
+
// Check if we got an error page
|
|
239
|
+
const responseText = await loginResp.text();
|
|
240
|
+
if (responseText.includes("sErrTxt") ||
|
|
241
|
+
responseText.includes("Your account or password is incorrect")) {
|
|
242
|
+
throw new Error("Invalid credentials");
|
|
243
|
+
}
|
|
244
|
+
if (responseText.includes("Sign in a different way") ||
|
|
245
|
+
responseText.includes("idA_PWD_SwitchToCredPicker")) {
|
|
246
|
+
throw new Error("2FA is enabled on this account. Direct login requires 2FA to be disabled.");
|
|
247
|
+
}
|
|
248
|
+
// Try to extract access token from response body (some flows embed it)
|
|
249
|
+
const tokenMatch = responseText.match(/access_token=([^&"']+)/);
|
|
250
|
+
if (tokenMatch) {
|
|
251
|
+
return decodeURIComponent(tokenMatch[1]);
|
|
252
|
+
}
|
|
253
|
+
throw new Error("Failed to get redirect URL from login response");
|
|
254
|
+
}
|
|
255
|
+
if (location.includes("access_token=")) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const redirectResp = await proxiedFetch(location, {
|
|
259
|
+
headers: {
|
|
260
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
261
|
+
Cookie: cookies,
|
|
262
|
+
},
|
|
263
|
+
redirect: "manual",
|
|
140
264
|
});
|
|
141
|
-
|
|
265
|
+
location = redirectResp.headers.get("location") || "";
|
|
266
|
+
attempts++;
|
|
142
267
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
268
|
+
// Extract access token from URL fragment
|
|
269
|
+
const tokenMatch = location.match(/access_token=([^&]+)/);
|
|
270
|
+
if (tokenMatch) {
|
|
271
|
+
return decodeURIComponent(tokenMatch[1]);
|
|
272
|
+
}
|
|
273
|
+
throw new Error("Failed to obtain access token from Microsoft");
|
|
274
|
+
}
|
|
275
|
+
function extractCookies(headers) {
|
|
276
|
+
const setCookies = headers.get("set-cookie");
|
|
277
|
+
if (!setCookies)
|
|
278
|
+
return "";
|
|
279
|
+
// Parse and combine cookies
|
|
280
|
+
const cookies = [];
|
|
281
|
+
const cookieStrings = setCookies.split(/,(?=[^;]*=)/);
|
|
282
|
+
for (const cookieStr of cookieStrings) {
|
|
283
|
+
const match = cookieStr.match(/^([^=]+)=([^;]*)/);
|
|
284
|
+
if (match) {
|
|
285
|
+
cookies.push(`${match[1].trim()}=${match[2]}`);
|
|
148
286
|
}
|
|
149
|
-
throw error;
|
|
150
287
|
}
|
|
288
|
+
return cookies.join("; ");
|
|
151
289
|
}
|
|
152
|
-
|
|
153
|
-
|
|
290
|
+
function extractLoginParams(html) {
|
|
291
|
+
let ppft = null;
|
|
292
|
+
// Pattern for sFTTag with escaped quotes (JSON format)
|
|
293
|
+
const sFTTagMatch = html.match(/sFTTag":"<input[^>]*value=\\"([^"\\]+)\\"/);
|
|
294
|
+
if (sFTTagMatch) {
|
|
295
|
+
ppft = sFTTagMatch[1];
|
|
296
|
+
}
|
|
297
|
+
// Fallback patterns
|
|
298
|
+
if (!ppft) {
|
|
299
|
+
const ppftPatterns = [
|
|
300
|
+
/sFTTag:'[^']*value="([^"]+)"/,
|
|
301
|
+
/name="PPFT"[^>]*value="([^"]+)"/,
|
|
302
|
+
/value="([^"]+)"[^>]*name="PPFT"/,
|
|
303
|
+
/<input[^>]*name="PPFT"[^>]*value="([^"]+)"/,
|
|
304
|
+
/"sFT"\s*:\s*"([^"]+)"/,
|
|
305
|
+
/sFT:'([^']+)'/,
|
|
306
|
+
/"sFT":"([^"]+)"/,
|
|
307
|
+
];
|
|
308
|
+
for (const pattern of ppftPatterns) {
|
|
309
|
+
const match = html.match(pattern);
|
|
310
|
+
if (match) {
|
|
311
|
+
ppft = match[1];
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!ppft) {
|
|
317
|
+
throw new Error("Failed to extract PPFT token from login page");
|
|
318
|
+
}
|
|
319
|
+
// Extract urlPost
|
|
320
|
+
const urlPostPatterns = [
|
|
321
|
+
/urlPost:\s*'([^']+)'/,
|
|
322
|
+
/urlPost:\s*"([^"]+)"/,
|
|
323
|
+
/"urlPost"\s*:\s*"([^"]+)"/,
|
|
324
|
+
];
|
|
325
|
+
let urlPost = "https://login.live.com/ppsecure/post.srf";
|
|
326
|
+
for (const pattern of urlPostPatterns) {
|
|
327
|
+
const match = html.match(pattern);
|
|
328
|
+
if (match) {
|
|
329
|
+
urlPost = match[1];
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { ppft, urlPost };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Exchange Microsoft access token for Xbox User Token
|
|
337
|
+
*/
|
|
338
|
+
async function exchangeRpsTicketForUserToken(accessToken, proxiedFetch) {
|
|
339
|
+
const response = await proxiedFetch("https://user.auth.xboxlive.com/user/authenticate", {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: {
|
|
342
|
+
"Content-Type": "application/json",
|
|
343
|
+
Accept: "application/json",
|
|
344
|
+
"x-xbl-contract-version": "1",
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify({
|
|
347
|
+
RelyingParty: "http://auth.xboxlive.com",
|
|
348
|
+
TokenType: "JWT",
|
|
349
|
+
Properties: {
|
|
350
|
+
AuthMethod: "RPS",
|
|
351
|
+
SiteName: "user.auth.xboxlive.com",
|
|
352
|
+
RpsTicket: `t=${accessToken}`,
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
const text = await response.text();
|
|
358
|
+
throw new Error(`Xbox user token exchange failed: ${response.status} - ${text}`);
|
|
359
|
+
}
|
|
360
|
+
return response.json();
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Exchange Xbox User Token for XSTS Token
|
|
364
|
+
*/
|
|
365
|
+
async function exchangeTokenForXSTSToken(userToken, relyingParty, proxiedFetch) {
|
|
366
|
+
const response = await proxiedFetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: {
|
|
369
|
+
"Content-Type": "application/json",
|
|
370
|
+
Accept: "application/json",
|
|
371
|
+
"x-xbl-contract-version": "1",
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({
|
|
374
|
+
RelyingParty: relyingParty,
|
|
375
|
+
TokenType: "JWT",
|
|
376
|
+
Properties: {
|
|
377
|
+
SandboxId: "RETAIL",
|
|
378
|
+
UserTokens: [userToken],
|
|
379
|
+
},
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
if (!response.ok) {
|
|
383
|
+
const text = await response.text();
|
|
384
|
+
throw new Error(`Xbox XSTS token exchange failed: ${response.status} - ${text}`);
|
|
385
|
+
}
|
|
386
|
+
return response.json();
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get Minecraft Bedrock authentication chains
|
|
390
|
+
*/
|
|
391
|
+
async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey, proxiedFetch) {
|
|
392
|
+
const response = await proxiedFetch("https://multiplayer.minecraft.net/authentication", {
|
|
154
393
|
method: "POST",
|
|
155
394
|
headers: {
|
|
156
395
|
"Content-Type": "application/json",
|
|
@@ -162,11 +401,8 @@ async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey) {
|
|
|
162
401
|
if (!response.ok) {
|
|
163
402
|
const text = await response.text();
|
|
164
403
|
if (response.status === 401) {
|
|
165
|
-
throw new Error("Minecraft Bedrock authentication failed (401
|
|
166
|
-
"
|
|
167
|
-
" 1. The account does not have an Xbox profile (create one at xbox.com)\n" +
|
|
168
|
-
" 2. The account does not own Minecraft Bedrock Edition\n" +
|
|
169
|
-
" 3. The account needs to accept Xbox/Minecraft terms of service");
|
|
404
|
+
throw new Error("Minecraft Bedrock authentication failed (401).\n" +
|
|
405
|
+
"The account may not have an Xbox profile or Minecraft Bedrock Edition.");
|
|
170
406
|
}
|
|
171
407
|
throw new Error(`Minecraft Bedrock auth failed: ${response.status} - ${text}`);
|
|
172
408
|
}
|
package/dist/shared/auth.js
CHANGED
|
@@ -107,12 +107,42 @@ async function authenticateWithEmailPassword(client) {
|
|
|
107
107
|
password: client.options.password,
|
|
108
108
|
clientPublicKey: client.data.loginData.clientX509,
|
|
109
109
|
cacheDir: client.options.tokensFolder,
|
|
110
|
+
proxy: client.options.proxy,
|
|
110
111
|
});
|
|
111
112
|
const profile = {
|
|
112
113
|
name: tokens.gamertag,
|
|
113
114
|
uuid: generateUUID(tokens.gamertag),
|
|
114
115
|
xuid: Number(tokens.xuid) || 0,
|
|
115
116
|
};
|
|
117
|
+
// Get Playfab session ticket
|
|
118
|
+
const playfabData = await getPlayfabSessionTicket(tokens.playfabUserHash, tokens.playfabXstsToken);
|
|
119
|
+
// Get the multiplayer session token
|
|
120
|
+
// First get the MC services token (mcToken)
|
|
121
|
+
const mcToken = await getMinecraftServicesTokenFromPlayfab(playfabData.sessionTicket);
|
|
122
|
+
// Then use mcToken to get the multiplayer session token
|
|
123
|
+
const sessionToken = await getMultiplayerSessionTokenFromMcToken(mcToken, client.data.loginData.clientX509);
|
|
124
|
+
client.data.loginToken = sessionToken;
|
|
125
|
+
// Extract pfcd from session token and store it
|
|
126
|
+
try {
|
|
127
|
+
const tokenParts = sessionToken.split(".");
|
|
128
|
+
if (tokenParts.length >= 2) {
|
|
129
|
+
const payload = JSON.parse(Buffer.from(tokenParts[1], "base64").toString());
|
|
130
|
+
if (payload.pfcd) {
|
|
131
|
+
client.data.payload.pfcd = payload.pfcd;
|
|
132
|
+
}
|
|
133
|
+
// Store session token data for client chain
|
|
134
|
+
client.data.loginData.sessionTokenData = {
|
|
135
|
+
ipt: payload.ipt,
|
|
136
|
+
tid: payload.prop ? JSON.parse(payload.prop).tid : undefined,
|
|
137
|
+
mid: payload.prop ? JSON.parse(payload.prop).mid : undefined,
|
|
138
|
+
xid: payload.xid,
|
|
139
|
+
cpk: payload.cpk,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
raknet_1.Logger.warn(`Failed to extract pfcd from session token: ${e instanceof Error ? e.message : String(e)}`);
|
|
145
|
+
}
|
|
116
146
|
const endTime = Date.now();
|
|
117
147
|
raknet_1.Logger.info(`Authentication with Xbox (email/password) took ${(endTime - startTime) / 1000}s.`);
|
|
118
148
|
setupClientProfile(client, profile, tokens.chains);
|
|
@@ -154,6 +184,144 @@ async function getMultiplayerSessionToken(authflow, client) {
|
|
|
154
184
|
throw error;
|
|
155
185
|
}
|
|
156
186
|
}
|
|
187
|
+
async function getPlayfabSessionTicket(playfabUserHash, playfabXstsToken) {
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch("https://20ca2.playfabapi.com/Client/LoginWithXbox", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: { "Content-Type": "application/json" },
|
|
192
|
+
body: JSON.stringify({
|
|
193
|
+
CreateAccount: true,
|
|
194
|
+
EncryptedRequest: null,
|
|
195
|
+
InfoRequestParameters: {
|
|
196
|
+
GetCharacterInventories: false,
|
|
197
|
+
GetCharacterList: false,
|
|
198
|
+
GetPlayerProfile: true,
|
|
199
|
+
GetPlayerStatistics: false,
|
|
200
|
+
GetTitleData: false,
|
|
201
|
+
GetUserAccountInfo: true,
|
|
202
|
+
GetUserData: false,
|
|
203
|
+
GetUserInventory: false,
|
|
204
|
+
GetUserReadOnlyData: false,
|
|
205
|
+
GetUserVirtualCurrency: false,
|
|
206
|
+
PlayerStatisticNames: null,
|
|
207
|
+
ProfileConstraints: null,
|
|
208
|
+
TitleDataKeys: null,
|
|
209
|
+
UserDataKeys: null,
|
|
210
|
+
UserReadOnlyDataKeys: null,
|
|
211
|
+
},
|
|
212
|
+
PlayerSecret: null,
|
|
213
|
+
TitleId: "20CA2",
|
|
214
|
+
XboxToken: `XBL3.0 x=${playfabUserHash};${playfabXstsToken}`,
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
const text = await response.text();
|
|
219
|
+
throw new Error(`Playfab login failed: ${response.status} ${response.statusText} - ${text}`);
|
|
220
|
+
}
|
|
221
|
+
const json = (await response.json());
|
|
222
|
+
return {
|
|
223
|
+
sessionTicket: json.data.SessionTicket,
|
|
224
|
+
playFabId: json.data.PlayFabId,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
raknet_1.Logger.error(`Error while getting Playfab session ticket: ${error instanceof Error ? error.message : String(error)}`);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function getMinecraftServicesTokenFromPlayfab(sessionTicket) {
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch("https://authorization.franchise.minecraft-services.net/api/v1.0/session/start", {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: { "Content-Type": "application/json" },
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
device: {
|
|
239
|
+
applicationType: "MinecraftPE",
|
|
240
|
+
gameVersion: "1.21.130",
|
|
241
|
+
id: "c1681ad3-415e-30cd-abd3-3b8f51e771d1",
|
|
242
|
+
memory: String(8 * (1024 * 1024 * 1024)),
|
|
243
|
+
platform: "Windows10",
|
|
244
|
+
playFabTitleId: "20CA2",
|
|
245
|
+
storePlatform: "uwp.store",
|
|
246
|
+
type: "Windows10",
|
|
247
|
+
},
|
|
248
|
+
user: {
|
|
249
|
+
token: sessionTicket,
|
|
250
|
+
tokenType: "PlayFab",
|
|
251
|
+
},
|
|
252
|
+
}),
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
const text = await response.text();
|
|
256
|
+
throw new Error(`MC services token failed: ${response.status} ${response.statusText} - ${text}`);
|
|
257
|
+
}
|
|
258
|
+
const json = (await response.json());
|
|
259
|
+
return json.result.authorizationHeader;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
raknet_1.Logger.error(`Error while getting MC services token: ${error instanceof Error ? error.message : String(error)}`);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function getMultiplayerSessionTokenFromMcToken(mcToken, publicKey) {
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch("https://authorization.franchise.minecraft-services.net/api/v1.0/multiplayer/session/start", {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: {
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
Authorization: mcToken,
|
|
273
|
+
"Accept-Encoding": "identity",
|
|
274
|
+
},
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
publicKey: publicKey,
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
const text = await response.text();
|
|
281
|
+
throw new Error(`Multiplayer session start failed: ${response.status} ${response.statusText} - ${text}`);
|
|
282
|
+
}
|
|
283
|
+
const json = (await response.json());
|
|
284
|
+
return json.result.signedToken;
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
raknet_1.Logger.error(`Error while getting Multiplayer Session Token: ${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function getMultiplayerSessionTokenFromXsts(sessionTicket, publicKey) {
|
|
292
|
+
try {
|
|
293
|
+
const response = await fetch("https://authorization.franchise.minecraft-services.net/api/v1.0/session/start", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: { "Content-Type": "application/json" },
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
device: {
|
|
298
|
+
applicationType: "MinecraftPE",
|
|
299
|
+
gameVersion: "1.21.130",
|
|
300
|
+
id: "c1681ad3-415e-30cd-abd3-3b8f51e771d1",
|
|
301
|
+
memory: String(8 * (1024 * 1024 * 1024)),
|
|
302
|
+
platform: "Windows10",
|
|
303
|
+
playFabTitleId: "20CA2",
|
|
304
|
+
storePlatform: "uwp.store",
|
|
305
|
+
type: "Windows10",
|
|
306
|
+
},
|
|
307
|
+
user: {
|
|
308
|
+
token: sessionTicket,
|
|
309
|
+
tokenType: "PlayFab",
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const text = await response.text();
|
|
315
|
+
throw new Error(`Multiplayer session start failed: ${response.status} ${response.statusText} - ${text}`);
|
|
316
|
+
}
|
|
317
|
+
const json = (await response.json());
|
|
318
|
+
return json.result.authorizationHeader;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
raknet_1.Logger.error(`Error while getting Multiplayer Session Token: ${error instanceof Error ? error.message : String(error)}`);
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
157
325
|
function extractProfile(jwt) {
|
|
158
326
|
if (!jwt) {
|
|
159
327
|
raknet_1.Logger.error("JWT is undefined or empty");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baltica",
|
|
3
3
|
"description": "Library for Minecraft Bedrock Edition community developers.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.23",
|
|
5
5
|
"minecraft": "1.21.130",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,18 +21,21 @@
|
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@sanctumterra/raknet": "^1.4.
|
|
24
|
+
"@sanctumterra/raknet": "^1.4.11",
|
|
25
25
|
"@serenityjs/binarystream": "^3.0.10",
|
|
26
26
|
"@serenityjs/protocol": "^0.8.17",
|
|
27
|
-
"
|
|
27
|
+
"fetch-socks": "^1.3.2",
|
|
28
28
|
"jose": "^5.10.0",
|
|
29
29
|
"prismarine-auth": "^2.7.0",
|
|
30
|
+
"undici": "^7.18.2",
|
|
30
31
|
"uuid-1345": "^1.0.2"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@biomejs/biome": "1.9.4",
|
|
34
|
-
"@types/node": "^22.19.
|
|
35
|
+
"@types/node": "^22.19.7",
|
|
36
|
+
"@types/pngjs": "^6.0.5",
|
|
35
37
|
"@types/uuid-1345": "^0.99.25",
|
|
38
|
+
"pngjs": "^7.0.0",
|
|
36
39
|
"typescript": "^5.9.3"
|
|
37
40
|
}
|
|
38
|
-
}
|
|
41
|
+
}
|