baltica 2.0.12 → 2.0.13
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/dist/client/client.d.ts +1 -1
- package/dist/client/client.js +7 -3
- package/dist/client/types/login/login-data.d.ts +1 -0
- package/dist/client/types/login/login-data.js +20 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/login-payload.d.ts +6 -0
- package/dist/server/login-payload.js +160 -0
- package/dist/server/player.js +45 -9
- package/package.json +2 -2
package/dist/client/client.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
15
15
|
startGameData: StartGamePacket;
|
|
16
16
|
private isDisconnected;
|
|
17
17
|
constructor(options: Partial<ClientOptions>);
|
|
18
|
-
connect(): Promise<
|
|
18
|
+
connect(): Promise<Client>;
|
|
19
19
|
private registerHandshakeHandlers;
|
|
20
20
|
private onNetworkSettings;
|
|
21
21
|
private onServerHandshake;
|
package/dist/client/client.js
CHANGED
|
@@ -39,7 +39,7 @@ class Client extends utils_1.Emitter {
|
|
|
39
39
|
this.requestNetworkSettings();
|
|
40
40
|
return new Promise((resolve, reject) => {
|
|
41
41
|
this.once("SetLocalPlayerAsInitializedPacket", () => {
|
|
42
|
-
resolve();
|
|
42
|
+
resolve(this);
|
|
43
43
|
this.emit("connect");
|
|
44
44
|
});
|
|
45
45
|
this.once("disconnect", (reason) => {
|
|
@@ -92,14 +92,17 @@ class Client extends utils_1.Emitter {
|
|
|
92
92
|
onResourcePacksInfo(packet) {
|
|
93
93
|
const response = new protocol_1.ResourcePackClientResponsePacket();
|
|
94
94
|
response.packs = packet.packs.map((p) => new protocol_1.RequestedResourcePack(p.uuid, p.version));
|
|
95
|
-
response.response =
|
|
95
|
+
response.response = response.packs.length > 0
|
|
96
|
+
// TODO: Maybe add packet downloading later if specified in options
|
|
97
|
+
? protocol_1.ResourcePackResponse.HaveAllPacks
|
|
98
|
+
: protocol_1.ResourcePackResponse.HaveAllPacks;
|
|
96
99
|
const cacheStatus = new protocol_1.ClientCacheStatusPacket();
|
|
97
100
|
cacheStatus.enabled = false;
|
|
98
101
|
this.send([response.serialize(), cacheStatus.serialize()]);
|
|
99
102
|
}
|
|
100
103
|
onResourcePackStack(packet) {
|
|
101
104
|
const response = new protocol_1.ResourcePackClientResponsePacket();
|
|
102
|
-
response.packs =
|
|
105
|
+
response.packs = [];
|
|
103
106
|
response.response = protocol_1.ResourcePackResponse.Completed;
|
|
104
107
|
this.send(response.serialize());
|
|
105
108
|
}
|
|
@@ -176,6 +179,7 @@ class Client extends utils_1.Emitter {
|
|
|
176
179
|
};
|
|
177
180
|
this.loginData = types_1.LoginData.prepare(this.options, this.profile);
|
|
178
181
|
this.loginData.clientIdentityChain = await this.loginData.createClientChain(this.profile, null, true);
|
|
182
|
+
this.loginData.loginToken = await this.loginData.createOfflineToken(this.profile);
|
|
179
183
|
this.loginData.clientUserChain = await this.loginData.createClientUserChain(this.loginData.privateKey, this.profile, this.options);
|
|
180
184
|
}
|
|
181
185
|
authenticateOnline() {
|
|
@@ -77,6 +77,7 @@ export declare class LoginData {
|
|
|
77
77
|
payload: Payload;
|
|
78
78
|
static prepare(options: ClientOptions, profile: PlayerProfile): LoginData;
|
|
79
79
|
createLoginPacket(options: ClientOptions): LoginPacket;
|
|
80
|
+
createOfflineToken(profile: PlayerProfile): Promise<string>;
|
|
80
81
|
createClientChain(profile: PlayerProfile, mojangKey: string | null, offline: boolean): Promise<string>;
|
|
81
82
|
createClientUserChain(privateKey: KeyObject, profile: PlayerProfile, options: ClientOptions): Promise<string>;
|
|
82
83
|
static createSharedSecret(privateKey: KeyObject, publicKey: KeyObject): Buffer;
|
|
@@ -130,7 +130,7 @@ class LoginData {
|
|
|
130
130
|
}
|
|
131
131
|
createLoginPacket(options) {
|
|
132
132
|
const loginPacket = new protocol_1.LoginPacket();
|
|
133
|
-
const chain = [this.clientIdentityChain, ...this.accessToken];
|
|
133
|
+
const chain = [this.clientIdentityChain, ...this.accessToken].filter((value) => typeof value === "string" && value.length > 0);
|
|
134
134
|
const certificate = JSON.stringify({ chain });
|
|
135
135
|
const identityObj = {
|
|
136
136
|
AuthenticationType: options.offline ? 2 : 0,
|
|
@@ -143,6 +143,25 @@ class LoginData {
|
|
|
143
143
|
loginPacket.tokens = new protocol_1.LoginTokens(this.clientUserChain, JSON.stringify(identityObj));
|
|
144
144
|
return loginPacket;
|
|
145
145
|
}
|
|
146
|
+
async createOfflineToken(profile) {
|
|
147
|
+
const josePrivateKey = await jose.importPKCS8(this.privateKey.export({ format: "pem", type: "pkcs8" }), ALGORITHM);
|
|
148
|
+
const payload = {
|
|
149
|
+
aud: "api://auth-minecraft-services/multiplayer",
|
|
150
|
+
cpk: this.clientX509,
|
|
151
|
+
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365,
|
|
152
|
+
leguuid: profile.uuid,
|
|
153
|
+
mid: LoginData.nextUUID(profile.name).replace(/-/g, "").slice(0, 16).toUpperCase(),
|
|
154
|
+
nid: "",
|
|
155
|
+
nname: "",
|
|
156
|
+
pid: "",
|
|
157
|
+
pname: "",
|
|
158
|
+
xid: profile.xuid || "",
|
|
159
|
+
xname: profile.name,
|
|
160
|
+
};
|
|
161
|
+
return new jose.SignJWT(payload)
|
|
162
|
+
.setProtectedHeader({ alg: ALGORITHM, x5u: this.clientX509 })
|
|
163
|
+
.sign(josePrivateKey);
|
|
164
|
+
}
|
|
146
165
|
async createClientChain(profile, mojangKey, offline) {
|
|
147
166
|
let payload;
|
|
148
167
|
let header;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type LoginPayloadObject = Record<string, unknown>;
|
|
2
|
+
export declare function decodeJwtPayload(jwt: string): LoginPayloadObject | null;
|
|
3
|
+
export declare function summarizeUnknownIdentityPayloadFields(payload: LoginPayloadObject): string[];
|
|
4
|
+
export declare function summarizeUnknownClientPayloadFields(payload: LoginPayloadObject): string[];
|
|
5
|
+
export declare function summarizeUnknownTokenPayloadFields(payload: LoginPayloadObject): string[];
|
|
6
|
+
export declare function summarizeClientPayloadForLog(payload: LoginPayloadObject): LoginPayloadObject;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeJwtPayload = decodeJwtPayload;
|
|
4
|
+
exports.summarizeUnknownIdentityPayloadFields = summarizeUnknownIdentityPayloadFields;
|
|
5
|
+
exports.summarizeUnknownClientPayloadFields = summarizeUnknownClientPayloadFields;
|
|
6
|
+
exports.summarizeUnknownTokenPayloadFields = summarizeUnknownTokenPayloadFields;
|
|
7
|
+
exports.summarizeClientPayloadForLog = summarizeClientPayloadForLog;
|
|
8
|
+
const KNOWN_IDENTITY_KEYS = new Set([
|
|
9
|
+
"certificateAuthority",
|
|
10
|
+
"cpk",
|
|
11
|
+
"exp",
|
|
12
|
+
"extraData",
|
|
13
|
+
"iat",
|
|
14
|
+
"identityPublicKey",
|
|
15
|
+
"iss",
|
|
16
|
+
"mid",
|
|
17
|
+
"nbf",
|
|
18
|
+
"pfcd",
|
|
19
|
+
"randomNonce",
|
|
20
|
+
"tid",
|
|
21
|
+
"xid",
|
|
22
|
+
"xname",
|
|
23
|
+
]);
|
|
24
|
+
const KNOWN_IDENTITY_EXTRA_DATA_KEYS = new Set([
|
|
25
|
+
"XUID",
|
|
26
|
+
"displayName",
|
|
27
|
+
"identity",
|
|
28
|
+
"sandboxId",
|
|
29
|
+
"titleId",
|
|
30
|
+
]);
|
|
31
|
+
const KNOWN_CLIENT_DATA_KEYS = new Set([
|
|
32
|
+
"AnimatedImageData",
|
|
33
|
+
"ArmSize",
|
|
34
|
+
"CapeData",
|
|
35
|
+
"CapeId",
|
|
36
|
+
"CapeImageHeight",
|
|
37
|
+
"CapeImageWidth",
|
|
38
|
+
"CapeOnClassicSkin",
|
|
39
|
+
"ClientRandomId",
|
|
40
|
+
"CompatibleWithClientSideChunkGen",
|
|
41
|
+
"CurrentInputMode",
|
|
42
|
+
"DefaultInputMode",
|
|
43
|
+
"DeviceId",
|
|
44
|
+
"DeviceModel",
|
|
45
|
+
"DeviceOS",
|
|
46
|
+
"GameVersion",
|
|
47
|
+
"GraphicsMode",
|
|
48
|
+
"GuiScale",
|
|
49
|
+
"IsEditorMode",
|
|
50
|
+
"LanguageCode",
|
|
51
|
+
"MaxViewDistance",
|
|
52
|
+
"MemoryTier",
|
|
53
|
+
"OverrideSkin",
|
|
54
|
+
"PartyId",
|
|
55
|
+
"PersonaPieces",
|
|
56
|
+
"PersonaSkin",
|
|
57
|
+
"PieceTintColors",
|
|
58
|
+
"PlatformOfflineId",
|
|
59
|
+
"PlatformOnlineId",
|
|
60
|
+
"PlatformType",
|
|
61
|
+
"PlayFabId",
|
|
62
|
+
"PlayformType",
|
|
63
|
+
"PremiumSkin",
|
|
64
|
+
"SelfSignedId",
|
|
65
|
+
"ServerAddress",
|
|
66
|
+
"SkinAnimationData",
|
|
67
|
+
"SkinColor",
|
|
68
|
+
"SkinData",
|
|
69
|
+
"SkinGeometryData",
|
|
70
|
+
"SkinGeometryDataEngineVersion",
|
|
71
|
+
"SkinId",
|
|
72
|
+
"SkinImageHeight",
|
|
73
|
+
"SkinImageWidth",
|
|
74
|
+
"SkinResourcePatch",
|
|
75
|
+
"ThirdPartyName",
|
|
76
|
+
"ThirdPartyNameOnly",
|
|
77
|
+
"TrustedSkin",
|
|
78
|
+
"UIProfile",
|
|
79
|
+
"pfcd",
|
|
80
|
+
]);
|
|
81
|
+
const KNOWN_TOKEN_KEYS = new Set([
|
|
82
|
+
"aud",
|
|
83
|
+
"cpk",
|
|
84
|
+
"exp",
|
|
85
|
+
"iat",
|
|
86
|
+
"ipt",
|
|
87
|
+
"iss",
|
|
88
|
+
"leguuid",
|
|
89
|
+
"mid",
|
|
90
|
+
"nbf",
|
|
91
|
+
"nid",
|
|
92
|
+
"nname",
|
|
93
|
+
"pfcd",
|
|
94
|
+
"pid",
|
|
95
|
+
"pname",
|
|
96
|
+
"sub",
|
|
97
|
+
"tid",
|
|
98
|
+
"xid",
|
|
99
|
+
"xname",
|
|
100
|
+
]);
|
|
101
|
+
function decodeJwtPayload(jwt) {
|
|
102
|
+
const payload = jwt.split(".")[1];
|
|
103
|
+
if (!payload)
|
|
104
|
+
return null;
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function unknownKeys(payload, knownKeys) {
|
|
118
|
+
return Object.keys(payload)
|
|
119
|
+
.filter((key) => !knownKeys.has(key))
|
|
120
|
+
.sort();
|
|
121
|
+
}
|
|
122
|
+
function summarizeUnknownIdentityPayloadFields(payload) {
|
|
123
|
+
const unknown = unknownKeys(payload, KNOWN_IDENTITY_KEYS);
|
|
124
|
+
const extraData = payload.extraData;
|
|
125
|
+
if (extraData && typeof extraData === "object" && !Array.isArray(extraData)) {
|
|
126
|
+
const nestedUnknown = unknownKeys(extraData, KNOWN_IDENTITY_EXTRA_DATA_KEYS).map((key) => `extraData.${key}`);
|
|
127
|
+
unknown.push(...nestedUnknown);
|
|
128
|
+
}
|
|
129
|
+
return unknown.sort();
|
|
130
|
+
}
|
|
131
|
+
function summarizeUnknownClientPayloadFields(payload) {
|
|
132
|
+
return unknownKeys(payload, KNOWN_CLIENT_DATA_KEYS);
|
|
133
|
+
}
|
|
134
|
+
function summarizeUnknownTokenPayloadFields(payload) {
|
|
135
|
+
return unknownKeys(payload, KNOWN_TOKEN_KEYS);
|
|
136
|
+
}
|
|
137
|
+
function summarizeClientPayloadForLog(payload) {
|
|
138
|
+
const summary = {};
|
|
139
|
+
const copyIfPresent = (key) => {
|
|
140
|
+
if (payload[key] !== undefined) {
|
|
141
|
+
summary[key] = payload[key];
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
copyIfPresent("DeviceId");
|
|
145
|
+
copyIfPresent("DeviceOS");
|
|
146
|
+
copyIfPresent("DeviceModel");
|
|
147
|
+
copyIfPresent("GameVersion");
|
|
148
|
+
copyIfPresent("SkinId");
|
|
149
|
+
copyIfPresent("PartyId");
|
|
150
|
+
copyIfPresent("PlayFabId");
|
|
151
|
+
copyIfPresent("PlatformOnlineId");
|
|
152
|
+
copyIfPresent("SelfSignedId");
|
|
153
|
+
if (typeof payload.SkinData === "string") {
|
|
154
|
+
summary.SkinDataLength = payload.SkinData.length;
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(payload.PersonaPieces)) {
|
|
157
|
+
summary.PersonaPiecesCount = payload.PersonaPieces.length;
|
|
158
|
+
}
|
|
159
|
+
return summary;
|
|
160
|
+
}
|
package/dist/server/player.js
CHANGED
|
@@ -40,10 +40,16 @@ const protocol_1 = require("@serenityjs/protocol");
|
|
|
40
40
|
const jose = __importStar(require("jose"));
|
|
41
41
|
const node_crypto_1 = require("node:crypto");
|
|
42
42
|
const shared_1 = require("../shared");
|
|
43
|
+
const login_payload_1 = require("./login-payload");
|
|
43
44
|
const SALT = "\u{1F9C2}";
|
|
44
45
|
const SALT_BUFFER = Buffer.from(SALT);
|
|
45
46
|
const CURVE = "secp384r1";
|
|
46
47
|
const ALGORITHM = "ES384";
|
|
48
|
+
function asRecord(value) {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50
|
+
return null;
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
47
53
|
class Player extends utils_1.Emitter {
|
|
48
54
|
packetCompressor;
|
|
49
55
|
packetEncryptor = null;
|
|
@@ -99,17 +105,38 @@ class Player extends utils_1.Emitter {
|
|
|
99
105
|
const identity = JSON.parse(identityRaw);
|
|
100
106
|
const certData = JSON.parse(identity.Certificate ?? identity.certificate ?? "{}");
|
|
101
107
|
const chain = certData.chain ?? [];
|
|
108
|
+
// console.log(packet);
|
|
102
109
|
let displayName = "";
|
|
103
110
|
let xuid = "";
|
|
104
111
|
let identityPublicKey = "";
|
|
112
|
+
const clientPayload = (0, login_payload_1.decodeJwtPayload)(packet.tokens.client);
|
|
113
|
+
if (clientPayload) {
|
|
114
|
+
// console.log("Login client data payload:", clientPayload);
|
|
115
|
+
// Logger.debug("Login client data payload:", clientPayload);
|
|
116
|
+
const unknownFields = (0, login_payload_1.summarizeUnknownClientPayloadFields)(clientPayload);
|
|
117
|
+
if (unknownFields.length > 0) {
|
|
118
|
+
utils_1.Logger.warn(`Unparsed client login payload fields: ${unknownFields.join(", ")}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
utils_1.Logger.warn("Failed to decode client data login payload");
|
|
123
|
+
}
|
|
105
124
|
for (const jwt of chain) {
|
|
106
125
|
try {
|
|
107
|
-
const payload =
|
|
108
|
-
if (payload
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
const payload = (0, login_payload_1.decodeJwtPayload)(jwt);
|
|
127
|
+
if (!payload)
|
|
128
|
+
continue;
|
|
129
|
+
utils_1.Logger.debug("Login identity payload:", payload);
|
|
130
|
+
const unknownFields = (0, login_payload_1.summarizeUnknownIdentityPayloadFields)(payload);
|
|
131
|
+
if (unknownFields.length > 0) {
|
|
132
|
+
utils_1.Logger.warn(`Unparsed identity login payload fields: ${unknownFields.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
const extraData = asRecord(payload.extraData);
|
|
135
|
+
if (typeof extraData?.displayName === "string") {
|
|
136
|
+
displayName = extraData.displayName;
|
|
137
|
+
xuid = typeof extraData.XUID === "string" ? extraData.XUID : "";
|
|
111
138
|
}
|
|
112
|
-
if (payload
|
|
139
|
+
if (typeof payload.identityPublicKey === "string") {
|
|
113
140
|
identityPublicKey = payload.identityPublicKey;
|
|
114
141
|
}
|
|
115
142
|
}
|
|
@@ -121,12 +148,21 @@ class Player extends utils_1.Emitter {
|
|
|
121
148
|
const token = identity.Token ?? identity.token;
|
|
122
149
|
if (token) {
|
|
123
150
|
try {
|
|
124
|
-
const payload =
|
|
125
|
-
if (payload
|
|
151
|
+
const payload = (0, login_payload_1.decodeJwtPayload)(token);
|
|
152
|
+
if (!payload) {
|
|
153
|
+
utils_1.Logger.warn("Failed to decode client login payload");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
utils_1.Logger.debug("Login client payload:", payload);
|
|
157
|
+
const unknownFields = (0, login_payload_1.summarizeUnknownTokenPayloadFields)(payload);
|
|
158
|
+
if (unknownFields.length > 0) {
|
|
159
|
+
utils_1.Logger.warn(`Unparsed login token payload fields: ${unknownFields.join(", ")}`);
|
|
160
|
+
}
|
|
161
|
+
if (typeof payload.cpk === "string")
|
|
126
162
|
identityPublicKey = payload.cpk;
|
|
127
|
-
if (payload
|
|
163
|
+
if (typeof payload.xname === "string" && !displayName)
|
|
128
164
|
displayName = payload.xname;
|
|
129
|
-
if (payload
|
|
165
|
+
if (typeof payload.xid === "string" && !xuid)
|
|
130
166
|
xuid = payload.xid;
|
|
131
167
|
}
|
|
132
168
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baltica",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
4
4
|
"description": "Core baltica package",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@baltica/auth": "*",
|
|
14
14
|
"@baltica/raknet": "*",
|
|
15
15
|
"@baltica/utils": "*",
|
|
16
|
-
"@serenityjs/protocol": "^0.8.
|
|
16
|
+
"@serenityjs/protocol": "^0.8.20",
|
|
17
17
|
"jose": "^6.1.3"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|