baltica 2.0.11 → 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.
@@ -19,6 +19,9 @@ class BridgePlayer extends utils_1.Emitter {
19
19
  address: this.bridge.options.destination.address,
20
20
  port: this.bridge.options.destination.port,
21
21
  offline: this.bridge.options.offline,
22
+ proxy: this.bridge.options.proxy,
23
+ email: this.bridge.options.email,
24
+ password: this.bridge.options.password,
22
25
  });
23
26
  this.client.stopPastLogin = true;
24
27
  this.client.once("PlayStatusPacket", (packet) => {
@@ -1,3 +1,4 @@
1
+ import type { Socks5Options } from "@baltica/raknet";
1
2
  import { type ServerOptions } from "../../server";
2
3
  export type BridgeOptions = ServerOptions & {
3
4
  destination: {
@@ -5,5 +6,8 @@ export type BridgeOptions = ServerOptions & {
5
6
  port: number;
6
7
  };
7
8
  offline: boolean;
9
+ proxy?: Socks5Options;
10
+ email?: string;
11
+ password?: string;
8
12
  };
9
13
  export declare const defaultBridgeOptions: BridgeOptions;
@@ -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<void>;
18
+ connect(): Promise<Client>;
19
19
  private registerHandshakeHandlers;
20
20
  private onNetworkSettings;
21
21
  private onServerHandshake;
@@ -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 = protocol_1.ResourcePackResponse.HaveAllPacks;
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 = packet.texturePacks.map((p) => new protocol_1.RequestedResourcePack(p.uuid, p.version));
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
@@ -1,3 +1,4 @@
1
1
  export * from "./client";
2
2
  export * from "./server";
3
3
  export * from "./bridge";
4
+ export * from "@baltica/utils";
package/dist/index.js CHANGED
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./client"), exports);
18
18
  __exportStar(require("./server"), exports);
19
19
  __exportStar(require("./bridge"), exports);
20
+ __exportStar(require("@baltica/utils"), exports);
@@ -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
+ }
@@ -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 = JSON.parse(Buffer.from(jwt.split(".")[1], "base64").toString());
108
- if (payload?.extraData?.displayName) {
109
- displayName = payload.extraData.displayName;
110
- xuid = payload.extraData.XUID ?? "";
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?.identityPublicKey) {
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 = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
125
- if (payload?.cpk)
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?.xname && !displayName)
163
+ if (typeof payload.xname === "string" && !displayName)
128
164
  displayName = payload.xname;
129
- if (payload?.xid && !xuid)
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.11",
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.19",
16
+ "@serenityjs/protocol": "^0.8.20",
17
17
  "jose": "^6.1.3"
18
18
  },
19
19
  "files": [