baltica 0.1.22 → 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 +1 -0
- 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/shared/auth/authentication.d.ts +3 -0
- package/dist/shared/auth/authentication.js +13 -1
- package/dist/shared/auth.js +167 -0
- package/package.json +3 -1
package/README.md
CHANGED
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 };
|
|
@@ -48,6 +48,7 @@ const raknet_1 = require("@sanctumterra/raknet");
|
|
|
48
48
|
const fetch_socks_1 = require("fetch-socks");
|
|
49
49
|
const undici_1 = require("undici");
|
|
50
50
|
const MINECRAFT_BEDROCK_RELYING_PARTY = "https://multiplayer.minecraft.net/";
|
|
51
|
+
const PLAYFAB_RELYING_PARTY = "https://b980a380.minecraft.playfabapi.com/";
|
|
51
52
|
const XBOX_AUTH_CLIENT_ID = "00000000441cc96b";
|
|
52
53
|
function hashString(str) {
|
|
53
54
|
let hash = 0;
|
|
@@ -154,12 +155,23 @@ async function authenticateWithCredentials(options) {
|
|
|
154
155
|
saveCache(cacheFile, userToken, userHash, userTokenResp.NotAfter);
|
|
155
156
|
}
|
|
156
157
|
}
|
|
158
|
+
// Get XSTS token for Minecraft Bedrock
|
|
157
159
|
const xstsResp = await exchangeTokenForXSTSToken(userToken, MINECRAFT_BEDROCK_RELYING_PARTY, proxiedFetch);
|
|
158
160
|
const xuid = xstsResp.DisplayClaims.xui[0].xid || "";
|
|
161
|
+
// Get XSTS token for Playfab (needed for session token)
|
|
162
|
+
const playfabXstsResp = await exchangeTokenForXSTSToken(userToken, PLAYFAB_RELYING_PARTY, proxiedFetch);
|
|
159
163
|
const chains = await getMinecraftBedrockChains(xstsResp.Token, userHash, clientPublicKey, proxiedFetch);
|
|
160
164
|
const gamertag = extractGamertagFromChains(chains);
|
|
161
165
|
raknet_1.Logger.info(`Authenticated as: ${gamertag} (${xuid})`);
|
|
162
|
-
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
|
+
};
|
|
163
175
|
}
|
|
164
176
|
/**
|
|
165
177
|
* Get Microsoft access token using email/password via OAuth flow
|
package/dist/shared/auth.js
CHANGED
|
@@ -114,6 +114,35 @@ async function authenticateWithEmailPassword(client) {
|
|
|
114
114
|
uuid: generateUUID(tokens.gamertag),
|
|
115
115
|
xuid: Number(tokens.xuid) || 0,
|
|
116
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
|
+
}
|
|
117
146
|
const endTime = Date.now();
|
|
118
147
|
raknet_1.Logger.info(`Authentication with Xbox (email/password) took ${(endTime - startTime) / 1000}s.`);
|
|
119
148
|
setupClientProfile(client, profile, tokens.chains);
|
|
@@ -155,6 +184,144 @@ async function getMultiplayerSessionToken(authflow, client) {
|
|
|
155
184
|
throw error;
|
|
156
185
|
}
|
|
157
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
|
+
}
|
|
158
325
|
function extractProfile(jwt) {
|
|
159
326
|
if (!jwt) {
|
|
160
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",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@biomejs/biome": "1.9.4",
|
|
35
35
|
"@types/node": "^22.19.7",
|
|
36
|
+
"@types/pngjs": "^6.0.5",
|
|
36
37
|
"@types/uuid-1345": "^0.99.25",
|
|
38
|
+
"pngjs": "^7.0.0",
|
|
37
39
|
"typescript": "^5.9.3"
|
|
38
40
|
}
|
|
39
41
|
}
|