Microslop authentication support

This commit is contained in:
unknown
2026-03-09 02:24:59 -04:00
parent 4094fab5d2
commit 8a01bbf230
12 changed files with 1083 additions and 265 deletions

BIN
.assets/crossplay2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

4
.gitignore vendored
View File

@@ -1 +1,3 @@
node_modules/
node_modules/
.auth_cache/
*.cache

View File

@@ -1,20 +1,21 @@
/* --------------------------------------------------------------- */
/* constants.js */
/* --------------------------------------------------------------- */
const GAME_PORT = 25565; //
const WIN64_LAN_DISCOVERY_PORT = 25566; //
const MINECRAFT_NET_VERSION = 560; //
const JAVA_SERVER_HOST = "localhost"; // The java edition server host that the proxy will connect to.
const JAVA_SERVER_PORT = 25564; // The java edition server port that the proxy will connect to (default is 25565).
const PROXY_NAME = JAVA_SERVER_HOST + ":" + JAVA_SERVER_PORT; //
module.exports = { //
GAME_PORT, //
WIN64_LAN_DISCOVERY_PORT, //
MINECRAFT_NET_VERSION, //
USE_LEGACY_USERNAME: false, // You can use your legacy edition username, or a custom username below.
CUSTOM_USERNAME: "LCE_Player", // The custom username used if USE_LEGACY_USERNAME is false. Ensure it's UNDER 16 characters.
JAVA_SERVER_HOST, //
JAVA_SERVER_PORT, //
PROXY_NAME, //
}; //
/* --------------------------------------------------------------- */
const GAME_PORT = 25565;
const WIN64_LAN_DISCOVERY_PORT = 25566;
const MINECRAFT_NET_VERSION = 560;
const JAVA_SERVER_HOST = "hypixel.net"; // The java edition server host that the proxy will connect to.
const JAVA_SERVER_PORT = 25565; // The java edition server port that the proxy will connect to.
const PROXY_NAME = JAVA_SERVER_HOST + ":" + JAVA_SERVER_PORT;
module.exports = {
GAME_PORT,
WIN64_LAN_DISCOVERY_PORT,
MINECRAFT_NET_VERSION,
USE_JAVA_ACCOUNT: true, // Allows use of a microsoft java account for online servers. (Disable this if you don't own java - you'll only be able to connect to cracked/offline servers.)
USE_LEGACY_USERNAME: false, // You can use your legacy edition username, or a custom username below. (offline servers only)
CUSTOM_USERNAME: "LCE_Player", // The custom username used if USE_LEGACY_USERNAME is false. Ensure it's UNDER 16 characters. (offline servers only)
JAVA_SERVER_HOST,
JAVA_SERVER_PORT,
PROXY_NAME,
};
/* --------------------------------------------------------------- */

View File

@@ -10,7 +10,10 @@ process.on("warning", (warning) => {
const LCEProxy = require("./src/lceproxy");
const proxy = new LCEProxy();
proxy.start();
(async () => {
await proxy.start();
})();
process.on("SIGINT", () => {
proxy.stop();

3
package-lock.json generated
View File

@@ -9,7 +9,8 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"minecraft-protocol": "^1.64.0"
"minecraft-protocol": "^1.64.0",
"prismarine-auth": "^2.7.0"
}
},
"node_modules/@azure/msal-common": {

View File

@@ -15,6 +15,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"minecraft-protocol": "^1.64.0"
"minecraft-protocol": "^1.64.0",
"prismarine-auth": "^2.7.0"
}
}

92
src/auth.js Normal file
View File

@@ -0,0 +1,92 @@
const { Authflow, Titles } = require("prismarine-auth");
const fs = require("fs");
const path = require("path");
const AUTH_CACHE_DIR = path.join(__dirname, "..", ".auth_cache");
class MicrosoftAuth {
constructor() {
this.authflow = null;
this.authenticated = false;
this.username = null;
this.uuid = null;
this.accessToken = null;
}
ensureCacheDir() {
if (!fs.existsSync(AUTH_CACHE_DIR)) {
fs.mkdirSync(AUTH_CACHE_DIR, { recursive: true });
}
}
async authenticate() {
this.ensureCacheDir();
if (!this.authflow) {
const userIdentifier = "lce-proxy-user";
this.authflow = new Authflow(userIdentifier, AUTH_CACHE_DIR);
}
if (!this.authflow.onMsaCode) {
this.authflow.onMsaCode = (data) => {
console.log("\nYou have not yet logged into your Java account!");
const url = `https://microsoft.com/link?otc=${data.userCode}`;
console.log(`enter the code ${data.userCode} at ${url}`);
console.log(
"If you enabled this by accident and would like to only join offline servers, set USE_JAVA_ACCOUNT to false in constants.js\n",
);
try {
const { exec } = require("child_process");
const platform = process.platform;
const command =
platform === "win32"
? `start ${url}`
: platform === "darwin"
? `open ${url}`
: `xdg-open ${url}`;
exec(command);
} catch (err) {
/* do nothing */
}
};
}
try {
const result = await this.authflow.getMinecraftJavaToken({
fetchProfile: true,
});
if (!result.profile) {
throw new Error(
"Error logging into Java Edition account! Do you own Minecraft Java Edition?\n",
);
}
this.username = result.profile.name;
this.uuid = result.profile.id;
this.accessToken = result.token;
this.authenticated = true;
return {
username: this.username,
uuid: this.uuid,
accessToken: this.accessToken,
};
} catch (err) {
throw err;
}
}
async getAuthData() {
return await this.authenticate();
}
isAuthenticated() {
return this.authenticated;
}
}
const authInstance = new MicrosoftAuth();
module.exports = authInstance;

View File

@@ -5,9 +5,11 @@ const mc = require("minecraft-protocol");
const {
JAVA_SERVER_HOST,
JAVA_SERVER_PORT,
USE_JAVA_ACCOUNT,
USE_LEGACY_USERNAME,
CUSTOM_USERNAME,
} = require("../constants");
const msAuth = require("./auth");
const PacketWriter = require("./packetwriter");
const { mapJavaItemToLCE, mapJavaBlockToLCE } = require("./mappings");
const { mapJavaSoundToLCE } = require("./mappings/soundmapping");
@@ -15,8 +17,8 @@ const { parseChatComponent } = require("./utils/chat");
const { countSetBits, compressRLE } = require("./utils/chunk");
const zlib = require("zlib");
function connectToJavaServer(proxy, client) {
const javaClient = mc.createClient({
async function connectToJavaServer(proxy, client) {
let authConfig = {
host: JAVA_SERVER_HOST,
port: JAVA_SERVER_PORT,
username: !USE_LEGACY_USERNAME
@@ -24,7 +26,30 @@ function connectToJavaServer(proxy, client) {
: client.username || "undefined",
auth: "offline",
version: "1.8.9",
});
};
if (USE_JAVA_ACCOUNT) {
if (msAuth.isAuthenticated()) {
authConfig = {
host: JAVA_SERVER_HOST,
port: JAVA_SERVER_PORT,
username: msAuth.username,
session: {
accessToken: msAuth.accessToken,
selectedProfile: {
id: msAuth.uuid,
name: msAuth.username,
},
},
auth: "microsoft",
version: "1.8.9",
};
} else {
console.error("Not authenticated! This should not happen.");
}
}
const javaClient = mc.createClient(authConfig);
client.javaClient = javaClient;
client._lastJavaPackets = [];
@@ -1000,138 +1025,38 @@ function connectToJavaServer(proxy, client) {
});
javaClient.on("spawn_entity", (packet) => {
if (client.state === "play") {
let lceEntityType = packet.type;
if (packet.type === 10) {
lceEntityType = 10;
const minecartSubtype = packet.intField || 0;
const minecartTypes = [
"Rideable",
"Chest",
"Furnace",
"TNT",
"Spawner",
"Hopper",
];
const minecartType = minecartTypes[minecartSubtype] || "Unknown";
}
let fallingBlockData = null;
if (packet.type === 70) {
const javaBlockId = packet.intField & 0xfff;
const metadata = (packet.intField >> 12) & 0xf;
const lceBlockId = proxy.mapJavaBlockToLCE(javaBlockId);
fallingBlockData = lceBlockId | (metadata << 16);
}
if (lceEntityType < 0 || lceEntityType > 200) {
return;
}
const lceEntityId = proxy.mapJavaEntityIdToLce(client, packet.entityId);
let velocityX = 0,
velocityY = 0,
velocityZ = 0;
if (packet.objectData) {
velocityX = packet.objectData.velocityX || 0;
velocityY = packet.objectData.velocityY || 0;
velocityZ = packet.objectData.velocityZ || 0;
}
let arrowData = -1;
if (lceEntityType === 60) {
arrowData = lceEntityId;
const javaYaw = (packet.yaw / 256) * 360;
const javaPitch = (packet.pitch / 256) * 360;
}
let fishingHookData = -1;
if (lceEntityType === 90) {
const javaOwnerId = packet.intField || packet.entityId;
if (javaOwnerId === client.javaPlayerEntityId) {
fishingHookData = client.lcePlayerEntityId || 1;
} else {
fishingHookData =
proxy.mapJavaEntityIdToLce(client, javaOwnerId) || lceEntityId;
try {
if (client.state === "play") {
if (
!packet ||
packet.entityId === undefined ||
packet.type === undefined
) {
return;
}
}
const entityInfo = {
entityId: lceEntityId,
javaEntityId: packet.entityId,
type: lceEntityType,
x: packet.x / 32,
y: packet.y / 32,
z: packet.z / 32,
yaw: lceEntityType === 60 ? 0 : (packet.yaw / 256) * 360,
pitch: lceEntityType === 60 ? 0 : (packet.pitch / 256) * 360,
data:
lceEntityType === 2
? 1
: lceEntityType === 60
? arrowData
: lceEntityType === 90
? fishingHookData
: lceEntityType === 63 || lceEntityType === 64
? 0
: lceEntityType === 70
? fallingBlockData
: lceEntityType === 10
? packet.intField || 0
: packet.objectData?.intField || -1,
velocityX: velocityX,
velocityY: velocityY,
velocityZ: velocityZ,
};
entityInfo.spawnTime = Date.now();
client.javaEntities.set(packet.entityId, entityInfo);
try {
proxy.sendAddEntityPacket(client, entityInfo);
} catch (err) {
return;
}
if (lceEntityType === 2) {
entityInfo.waitingForItemData = true;
}
if (lceEntityType === 10) {
const minecartMetadata = [];
const minecartSubtype = packet.intField || 0;
let displayBlockId = 0;
switch (minecartSubtype) {
case 1:
displayBlockId = 54;
break; //chest
case 2:
displayBlockId = 61;
break; //furnace
case 3:
displayBlockId = 46;
break; //tNT
case 5:
displayBlockId = 154;
break; //hopper
case 4:
displayBlockId = 52;
break; //spawner
default:
displayBlockId = 0;
break; //no display
if (
!Number.isFinite(packet.x) ||
!Number.isFinite(packet.y) ||
!Number.isFinite(packet.z)
) {
return;
}
if (displayBlockId > 0) {
minecartMetadata.push({ key: 20, type: 2, value: displayBlockId });
minecartMetadata.push({ key: 21, type: 2, value: 6 });
minecartMetadata.push({ key: 22, type: 0, value: 1 });
proxy.sendEntityMetadataPacket(client, entityInfo, minecartMetadata);
if (!Number.isFinite(packet.yaw) || !Number.isFinite(packet.pitch)) {
return;
}
let lceEntityType = packet.type;
const unsupportedEntityTypes = [78];
if (unsupportedEntityTypes.includes(lceEntityType)) {
return;
}
if (packet.type === 10) {
lceEntityType = 10;
const minecartSubtype = packet.intField || 0;
const minecartTypes = [
"Rideable",
"Chest",
@@ -1140,9 +1065,142 @@ function connectToJavaServer(proxy, client) {
"Spawner",
"Hopper",
];
const cartType = minecartTypes[minecartSubtype] || "Unknown";
const minecartType = minecartTypes[minecartSubtype] || "Unknown";
}
let fallingBlockData = null;
if (packet.type === 70) {
const javaBlockId = packet.intField & 0xfff;
const metadata = (packet.intField >> 12) & 0xf;
const lceBlockId = proxy.mapJavaBlockToLCE(javaBlockId);
fallingBlockData = lceBlockId | (metadata << 16);
}
if (lceEntityType < 0 || lceEntityType > 200) {
return;
}
const lceEntityId = proxy.mapJavaEntityIdToLce(client, packet.entityId);
let velocityX = 0,
velocityY = 0,
velocityZ = 0;
if (packet.objectData) {
velocityX = packet.objectData.velocityX || 0;
velocityY = packet.objectData.velocityY || 0;
velocityZ = packet.objectData.velocityZ || 0;
}
let arrowData = -1;
if (lceEntityType === 60) {
arrowData = lceEntityId;
const javaYaw = (packet.yaw / 256) * 360;
const javaPitch = (packet.pitch / 256) * 360;
}
let fishingHookData = -1;
if (lceEntityType === 90) {
const javaOwnerId = packet.intField || packet.entityId;
if (javaOwnerId === client.javaPlayerEntityId) {
fishingHookData = client.lcePlayerEntityId || 1;
} else {
fishingHookData =
proxy.mapJavaEntityIdToLce(client, javaOwnerId) || lceEntityId;
}
}
const entityInfo = {
entityId: lceEntityId,
javaEntityId: packet.entityId,
type: lceEntityType,
x: packet.x / 32,
y: packet.y / 32,
z: packet.z / 32,
yaw: lceEntityType === 60 ? 0 : (packet.yaw / 256) * 360,
pitch: lceEntityType === 60 ? 0 : (packet.pitch / 256) * 360,
data:
lceEntityType === 2
? 1
: lceEntityType === 60
? arrowData
: lceEntityType === 90
? fishingHookData
: lceEntityType === 63 || lceEntityType === 64
? 0
: lceEntityType === 70
? fallingBlockData
: lceEntityType === 10
? packet.intField || 0
: packet.objectData?.intField || -1,
velocityX: velocityX,
velocityY: velocityY,
velocityZ: velocityZ,
};
entityInfo.spawnTime = Date.now();
client.javaEntities.set(packet.entityId, entityInfo);
try {
proxy.sendAddEntityPacket(client, entityInfo);
} catch (err) {
return;
}
if (lceEntityType === 2) {
entityInfo.waitingForItemData = true;
}
if (lceEntityType === 10) {
const minecartMetadata = [];
const minecartSubtype = packet.intField || 0;
let displayBlockId = 0;
switch (minecartSubtype) {
case 1:
displayBlockId = 54;
break;
case 2:
displayBlockId = 61;
break;
case 3:
displayBlockId = 46;
break;
case 5:
displayBlockId = 154;
break;
case 4:
displayBlockId = 52;
break;
default:
displayBlockId = 0;
break; //no display
}
if (displayBlockId > 0) {
minecartMetadata.push({ key: 20, type: 2, value: displayBlockId });
minecartMetadata.push({ key: 21, type: 2, value: 6 });
minecartMetadata.push({ key: 22, type: 0, value: 1 });
proxy.sendEntityMetadataPacket(
client,
entityInfo,
minecartMetadata,
);
const minecartTypes = [
"Rideable",
"Chest",
"Furnace",
"TNT",
"Spawner",
"Hopper",
];
const cartType = minecartTypes[minecartSubtype] || "Unknown";
}
}
}
} catch (err) {
/* do nothing */
}
});
@@ -1514,114 +1572,123 @@ function connectToJavaServer(proxy, client) {
});
javaClient.on("entity_metadata", (packet) => {
if (client.state === "play") {
let isPlayer = false;
for (const [uuid, player] of client.javaPlayers) {
if (player.javaEntityId === packet.entityId && player.spawned) {
//0x01=on fire, 0x02=sneaking, 0x04=unused, 0x08=sprinting, 0x10=eating, 0x20=invisible
if (packet.metadata && packet.metadata.length > 0) {
const sharedFlags = packet.metadata.find(
(m) => m.key === 0 && m.type === 0,
);
if (sharedFlags !== undefined) {
const flags = sharedFlags.value & 0xff;
const metaWriter = new PacketWriter();
metaWriter.writeInt(player.entityId);
metaWriter.writeByte((0 << 5) | 0);
metaWriter.writeByte(flags);
metaWriter.writeByte(0x7f);
proxy.sendPacket(client, 0x28, metaWriter.toBuffer());
}
}
isPlayer = true;
break;
}
}
if (!isPlayer && client.javaEntities.has(packet.entityId)) {
const entity = client.javaEntities.get(packet.entityId);
entity.metadata = packet.metadata || [];
if (entity.type === 90 && packet.metadata) {
const hookedEntityMeta = packet.metadata.find((m) => m.key === 6);
if (hookedEntityMeta) {
if (hookedEntityMeta.value > 0) {
const hookedJavaEntityId = hookedEntityMeta.value;
const hookedLceEntityId = proxy.mapJavaEntityIdToLce(
client,
hookedJavaEntityId,
try {
if (client.state === "play") {
let isPlayer = false;
for (const [uuid, player] of client.javaPlayers) {
if (player.javaEntityId === packet.entityId && player.spawned) {
//0x01=on fire, 0x02=sneaking, 0x04=unused, 0x08=sprinting, 0x10=eating, 0x20=invisible
if (packet.metadata && packet.metadata.length > 0) {
const sharedFlags = packet.metadata.find(
(m) => m.key === 0 && m.type === 0,
);
entity.hookedEntityId = hookedLceEntityId;
if (sharedFlags !== undefined) {
const flags = sharedFlags.value & 0xff;
const metaWriter = new PacketWriter();
metaWriter.writeInt(player.entityId);
metaWriter.writeByte((0 << 5) | 0);
metaWriter.writeByte(flags);
metaWriter.writeByte(0x7f);
proxy.sendPacket(client, 0x28, metaWriter.toBuffer());
}
}
isPlayer = true;
break;
}
}
//if (entity.type === 10) {
// console.log('minecart', entity.type, entity.entityId, JSON.stringify(packet.metadata));
//}
if (!isPlayer && client.javaEntities.has(packet.entityId)) {
const entity = client.javaEntities.get(packet.entityId);
entity.metadata = packet.metadata || [];
if (
entity.type === 2 &&
entity.waitingForItemData &&
packet.metadata &&
packet.metadata.length > 0
) {
const itemMetadata = packet.metadata.find(
(m) => m.key === 10 && m.type === 5,
);
if (itemMetadata && itemMetadata.value) {
proxy.sendSetItemDataPacket(client, entity, itemMetadata.value);
entity.waitingForItemData = false;
}
}
const customNameMeta = packet.metadata.find(
(m) => m.key === 2 && m.type === 4,
);
const customNameVisibleMeta = packet.metadata.find(
(m) => m.key === 3 && m.type === 0,
);
const lceMetadata = [];
if (customNameMeta && customNameMeta.value) {
lceMetadata.push({
key: 10,
type: 4,
value: customNameMeta.value,
});
}
if (customNameVisibleMeta !== undefined) {
lceMetadata.push({
key: 11,
type: 0,
value: customNameVisibleMeta.value,
});
}
const relevantMetadata = packet.metadata.filter((m) => {
if (entity.type === 10) {
if ([20, 21, 22].includes(m.key)) {
return false;
if (entity.type === 90 && packet.metadata) {
const hookedEntityMeta = packet.metadata.find((m) => m.key === 6);
if (hookedEntityMeta) {
if (hookedEntityMeta.value > 0) {
const hookedJavaEntityId = hookedEntityMeta.value;
const hookedLceEntityId = proxy.mapJavaEntityIdToLce(
client,
hookedJavaEntityId,
);
entity.hookedEntityId = hookedLceEntityId;
}
}
}
//0=shared flags (e.g. on fire, sneaking, sprinting)
//12=age
//13=skeleton type
//16=villager profession or slime/magma size
//17=batman hanging flag - 0x01 = hanging
return [0, 12, 13, 16, 17].includes(m.key) && m.type !== 5;
});
//if (entity.type === 10) {
// console.log('minecart', entity.type, entity.entityId, JSON.stringify(packet.metadata));
//}
const allMetadata = [...lceMetadata, ...relevantMetadata];
if (
entity.type === 2 &&
entity.waitingForItemData &&
packet.metadata &&
packet.metadata.length > 0
) {
const itemMetadata = packet.metadata.find(
(m) => m.key === 10 && m.type === 5,
);
if (itemMetadata && itemMetadata.value) {
proxy.sendSetItemDataPacket(client, entity, itemMetadata.value);
entity.waitingForItemData = false;
}
}
if (allMetadata.length > 0 && entity.type !== 2) {
proxy.sendEntityMetadataPacket(client, entity, allMetadata);
const customNameMeta = packet.metadata
? packet.metadata.find((m) => m && m.key === 2 && m.type === 4)
: undefined;
const customNameVisibleMeta = packet.metadata
? packet.metadata.find((m) => m && m.key === 3 && m.type === 0)
: undefined;
const lceMetadata = [];
if (customNameMeta && customNameMeta.value) {
lceMetadata.push({
key: 10,
type: 4,
value: customNameMeta.value,
});
}
if (customNameVisibleMeta !== undefined) {
lceMetadata.push({
key: 11,
type: 0,
value: customNameVisibleMeta.value,
});
}
const relevantMetadata = packet.metadata
? packet.metadata.filter((m) => {
if (!m || m.key === undefined || m.type === undefined) {
return false;
}
if (entity.type === 10) {
if ([20, 21, 22].includes(m.key)) {
return false;
}
}
if (m.type === 5) {
return false;
}
return [0, 12, 13, 16, 17].includes(m.key);
})
: [];
const allMetadata = [...lceMetadata, ...relevantMetadata];
if (allMetadata.length > 0 && entity.type !== 2) {
proxy.sendEntityMetadataPacket(client, entity, allMetadata);
}
}
}
} catch (err) {
/* do nothing */
}
});

View File

@@ -8,13 +8,16 @@ const {
GAME_PORT,
WIN64_LAN_DISCOVERY_PORT,
MINECRAFT_NET_VERSION,
USE_JAVA_ACCOUNT,
USE_LEGACY_USERNAME,
CUSTOM_USERNAME,
PROXY_NAME,
} = require("../constants");
const msAuth = require("./auth");
const PacketWriter = require("./packetwriter");
const PacketReader = require("./packetreader");
const LANBroadcast = require("./lanbroadcast");
const recipeData = require("./mappings/recipemapping");
const {
createEntityTypeMapping,
createItemMapping,
@@ -54,6 +57,16 @@ class LCEProxy {
this.javaBlockMapping = createBlockMapping();
this.javaItemMapping = createItemMapping();
this.javaEntityTypeMapping = createEntityTypeMapping();
this.validLCEPackets = new Set([
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x3e, 0x46, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x82, 0x83, 0xc8, 0xc9, 0xca, 0xcb,
0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xfa, 0xfc, 0xfd, 0xfe, 0xff,
]);
}
mapJavaBlockToLCE(javaBlockId) {
@@ -163,7 +176,21 @@ class LCEProxy {
return null;
}
start() {
async start() {
if (USE_JAVA_ACCOUNT) {
try {
await msAuth.authenticate();
console.log("Logged in as: " + msAuth.username);
} catch (err) {
console.log("\Failed to log into java account! (", err.message, ")");
console.log(
"If you enabled this by accident and would like to only join offline servers for free,",
);
console.log("set USE_JAVA_ACCOUNT to false in constants.js\n");
process.exit(1);
}
}
this.startBroadcasting();
this.startTCPServer();
@@ -510,10 +537,11 @@ class LCEProxy {
this.handleContainerAck(client, packetData);
}
break;
//maybe we can use a custom JavaCraftingPacket (0x9E) packet with
//crafting grid data because java edition doesn't craft with recipe ids
//case 0x96:
// break;
case 0x96:
if (client.state === "play") {
this.handleCraftItem(client, packetData);
}
break;
case 0x97: //TradeItemPacket - 151
if (client.state === "play") {
this.handleTradeItem(client, packetData);
@@ -2185,10 +2213,25 @@ class LCEProxy {
}, totalDelay + 100);
}
//LCE uses recipe ids to craft, whilst java edition requires grid data
//we should write a server plugin that sends the recipe data when crafting because lazy
handleCraftItem(client, data) {
/* do nothing */
const reader = new PacketReader(data.slice(1));
const uid = reader.readShort();
const recipeId = reader.readInt();
const grid = recipeData[recipeId] || [
"none",
"none",
"none",
"none",
"none",
"none",
"none",
"none",
"none",
];
//console.log(`recipe id: ${recipeId}`);
//console.log(`${grid[0]}, ${grid[1]}, ${grid[2]}, ${grid[3]}, ${grid[4]}, ${grid[5]}, ${grid[6]}, ${grid[7]}, ${grid[8]}`);
}
handleJavaCrafting(client, data) {
/* do nothing */
@@ -2526,13 +2569,35 @@ class LCEProxy {
}
sendPacket(client, packetId, payload) {
const packetData = Buffer.concat([Buffer.from([packetId]), payload]);
const lengthPrefix = Buffer.allocUnsafe(4);
lengthPrefix.writeUInt32BE(packetData.length, 0);
if (!this.validLCEPackets.has(packetId)) {
console.warn(
`[WARNING] Attempted to send invalid packet ID: 0x${packetId.toString(16).toUpperCase().padStart(2, "0")}`,
);
return;
}
const fullPacket = Buffer.concat([lengthPrefix, packetData]);
if (!payload || !Buffer.isBuffer(payload)) {
console.warn(
`[WARNING] Invalid payload for packet 0x${packetId.toString(16).toUpperCase().padStart(2, "0")}`,
);
return;
}
const success = client.socket.write(fullPacket);
if (client.socket.destroyed) {
return;
}
try {
const packetData = Buffer.concat([Buffer.from([packetId]), payload]);
const lengthPrefix = Buffer.allocUnsafe(4);
lengthPrefix.writeUInt32BE(packetData.length, 0);
const fullPacket = Buffer.concat([lengthPrefix, packetData]);
client.socket.write(fullPacket);
} catch (err) {
/* do nothing */
}
}
disconnectClient(client, reason = 0) {
@@ -2602,8 +2667,8 @@ class LCEProxy {
}
}
connectToJavaServer(client) {
return connectToJavaServerFunc(this, client);
async connectToJavaServer(client) {
return await connectToJavaServerFunc(this, client);
}
stop() {

View File

@@ -0,0 +1,586 @@
//all of the crafting recipes that are sent from the client converted into a java recipe grid.
//this was absolute fucking torture to write, thanks mojang for not using recipe ids in java edition crafting until 1.12!
//unfinised as of 3/9/2026 (MM/DD/YY)
module.exports = {
//page 1
0: [
"oak_log", "none", "none",
"none", "none", "none",
"none", "none", "none"
],
1: [
"birch_log", "none", "none",
"none", "none", "none",
"none", "none", "none"
],
2: [
"spruce_log", "none", "none",
"none", "none", "none",
"none", "none", "none"
],
3: [
"jungle_log", "none", "none",
"none", "none", "none",
"none", "none", "none"
],
4: [
"any_plank", "none", "none",
"any_plank", "none", "none",
"none", "none", "none"
],
40: [
"sand", "sand", "none",
"sand", "sand", "none",
"none", "none", "none"
],
41: [
"sandstone_slab", "sandstone_slab", "none",
"sandstone_slab", "sandstone_slab", "none",
"none", "none", "none"
],
42: [
"quartz_slab", "none", "none",
"quartz_slab", "none", "none",
"none", "none", "none"
],
43: [
"quartz_block", "none", "none",
"quartz_block", "none", "none",
"none", "none", "none"
],
49: [
"stone", "stone", "none",
"stone", "stone", "none",
"none", "none", "none"
],
51: [
"nether_brick", "nether_brick", "none",
"nether_brick", "nether_brick", "none",
"none", "none", "none"
],
156: [
"snowball", "snowball", "none",
"snowball", "snowball", "none",
"none", "none", "none"
],
157: [
"snowball", "snowball", "none",
"snowball", "snowball", "none",
"none", "none", "none"
],
158: [
"clay", "clay", "none",
"clay", "clay", "none",
"none", "none", "none"
],
159: [
"brick", "brick", "none",
"brick", "brick", "none",
"none", "none", "none"
],
203: [
"nether_quartz", "nether_quartz", "none",
"nether_quartz", "nether_quartz", "none",
"none", "none", "none"
],
44: [
"any_plank", "any_plank", "none",
"any_plank", "any_plank", "none",
"none", "none", "none"
],
45: [
"cobblestone", "cobblestone", "cobblestone",
"cobblestone", "none", "cobblestone",
"cobblestone", "cobblestone", "cobblestone"
],
55: [
"any_plank", "book", "none",
"diamond", "obsidian", "diamond",
"obsidian", "obsidian", "obsidian"
],
56: [
"iron_block", "iron_block", "iron_block",
"none", "iron_ingot", "none",
"iron_ingot", "iron_ingot", "iron_ingot"
],
227: [
"none", "none", "none",
"none", "blaze_rod", "none",
"cobblestone", "cobblestone", "cobblestone"
],
46: [
"any_plank", "any_plank", "any_plank",
"any_plank", "none", "any_plank",
"any_plank", "any_plank", "any_plank"
],
47: [
"chest", "tripwire_hook", "none",
"none", "none", "none",
"none", "none", "none"
],
48: [
"obsidian", "obsidian", "obsidian",
"obsidian", "ender_eye", "obsidian",
"obsidian", "obsidian", "obsidian"
],
54: [
"none", "none", "none",
"any_wool", "any_wool", "any_wool",
"any_plank", "any_plank", "any_plank"
],
57: [
"stick", "none", "stick",
"stick", "stick", "stick",
"stick", "none", "stick"
],
58: [
"stick", "any_plank", "stick",
"stick", "any_plank", "stick",
"none", "none", "none"
],
59: [
"stick", "stick", "stick",
"stick", "stick", "stick",
"none", "none", "none"
],
60: [
"nether_brick", "nether_brick", "nether_brick",
"nether_brick", "nether_brick", "nether_brick",
"none", "none", "none"
],
61: [
"iron_ingot", "iron_ingot", "iron_ingot",
"iron_ingot", "iron_ingot", "iron_ingot",
"none", "none", "none"
],
62: [
"cobblestone", "cobblestone", "cobblestone",
"cobblestone", "cobblestone", "cobblestone",
"none", "none", "none"
],
63: [
"mossy_cobblestone", "mossy_cobblestone", "mossy_cobblestone",
"mossy_cobblestone", "mossy_cobblestone", "mossy_cobblestone",
"none", "none", "none"
],
64: [
"any_plank", "any_plank", "none",
"any_plank", "any_plank", "none",
"any_plank", "any_plank", "none"
],
65: [
"iron_ingot", "iron_ingot", "none",
"iron_ingot", "iron_ingot", "none",
"iron_ingot", "iron_ingot", "none"
],
67: [
"any_plank", "any_plank", "any_plank",
"any_plank", "any_plank", "any_plank",
"none", "none", "none"
],
66: [
"oak_plank", "none", "none",
"oak_plank", "oak_plank", "none",
"oak_plank", "oak_plank", "oak_plank"
],
68: [
"cobblestone", "none", "none",
"cobblestone", "cobblestone", "none",
"cobblestone", "cobblestone", "cobblestone"
],
69: [
"brick_block", "none", "none",
"brick_block", "brick_block", "none",
"brick_block", "brick_block", "brick_block"
],
70: [
"stonebrick", "none", "none",
"stonebrick", "stonebrick", "none",
"stonebrick", "stonebrick", "stonebrick"
],
71: [
"nether_brick", "none", "none",
"nether_brick", "nether_brick", "none",
"nether_brick", "nether_brick", "nether_brick"
],
72: [
"sandstone", "none", "none",
"sandstone", "sandstone", "none",
"sandstone", "sandstone", "sandstone"
],
73: [
"birch_planks", "none", "none",
"birch_planks", "birch_planks", "none",
"birch_planks", "birch_planks", "birch_planks"
],
74: [
"spruce_planks", "none", "none",
"spruce_planks", "spruce_planks", "none",
"spruce_planks", "spruce_planks", "spruce_planks"
],
75: [
"jungle_planks", "none", "none",
"jungle_planks", "jungle_planks", "none",
"jungle_planks", "jungle_planks", "jungle_planks"
],
76: [
"quartz_block", "none", "none",
"quartz_block", "quartz_block", "none",
"quartz_block", "quartz_block", "quartz_block"
],
//next: slabs
//page 2
5: [
"any_plank", "any_plank", "any_plank",
"none", "stick", "none",
"none", "stick", "none"
],
9: [
"cobblestone", "cobblestone", "cobblestone",
"none", "stick", "none",
"none", "stick", "none"
],
13: [
"iron_ingot", "iron_ingot", "iron_ingot",
"none", "stick", "none",
"none", "stick", "none"
],
17: [
"diamond", "diamond", "diamond",
"none", "stick", "none",
"none", "stick", "none"
],
21: [
"gold_ingot", "gold_ingot", "gold_ingot",
"none", "stick", "none",
"none", "stick", "none"
],
6: [
"none", "any_plank", "none",
"none", "stick", "none",
"none", "stick", "none"
],
10: [
"none", "cobblestone", "none",
"none", "stick", "none",
"none", "stick", "none"
],
14: [
"none", "iron_ingot", "none",
"none", "stick", "none",
"none", "stick", "none"
],
18: [
"none", "diamond", "none",
"none", "stick", "none",
"none", "stick", "none"
],
22: [
"none", "gold_ingot", "none",
"none", "stick", "none",
"none", "stick", "none"
],
7: [
"any_plank", "any_plank", "none",
"any_plank", "stick", "none",
"none", "stick", "none"
],
11: [
"cobblestone", "cobblestone", "none",
"cobblestone", "stick", "none",
"none", "stick", "none"
],
15: [
"iron_ingot", "iron_ingot", "none",
"iron_ingot", "stick", "none",
"none", "stick", "none"
],
19: [
"diamond", "diamond", "none",
"diamond", "stick", "none",
"none", "stick", "none"
],
23: [
"gold_ingot", "gold_ingot", "none",
"gold_ingot", "stick", "none",
"none", "stick", "none"
],
8: [
"any_plank", "any_plank", "none",
"none", "stick", "none",
"none", "stick", "none"
],
12: [
"cobblestone", "cobblestone", "none",
"none", "stick", "none",
"none", "stick", "none"
],
16: [
"iron_ingot", "iron_ingot", "none",
"none", "stick", "none",
"none", "stick", "none"
],
20: [
"diamond", "diamond", "none",
"none", "stick", "none",
"none", "stick", "none"
],
24: [
"gold_ingot", "gold_ingot", "none",
"none", "stick", "none",
"none", "stick", "none"
],
25: [
"iron_ingot", "none", "none",
"none", "iron_ingot", "none",
"none", "none", "none"
],
187: [
"flint", "none", "none",
"none", "iron_ingot", "none",
"none", "none", "none"
],
161: [
"gunpowder", "sand", "gunpowder",
"sand", "gunpowder", "sand",
"gunpowder", "sand", "gunpowder"
],
185: [
"none", "none", "stick",
"none", "stick", "string",
"stick", "none", "string"
],
186: [
"fishing_rod", "none", "none",
"none", "carrot", "none",
"none", "none", "none"
],
189: [
"none", "stick", "string",
"stick", "none", "string",
"none", "stick", "string"
],
190: [
"flint", "none", "none",
"stick", "none", "none",
"feather", "none", "none"
],
191: [
"none", "any_plank", "none",
"none", "any_plank", "none",
"none", "stick", "none"
],
192: [
"none", "cobblestone", "none",
"none", "cobblestone", "none",
"none", "stick", "none"
],
193: [
"none", "iron_ingot", "none",
"none", "iron_ingot", "none",
"none", "stick", "none"
],
194: [
"none", "diamond", "none",
"none", "diamond", "none",
"none", "stick", "none"
],
195: [
"none", "gold_ingot", "none",
"none", "gold_ingot", "none",
"none", "stick", "none"
],
196: [
"none", "none", "none",
"iron_ingot", "none", "iron_ingot",
"none", "iron_ingot", "none"
],
197: [
"any_plank", "none", "any_plank",
"none", "any_plank", "none",
"none", "none", "none"
],
198: [
"glass", "none", "glass",
"none", "glass", "none",
"none", "none", "none"
],
226: [
"iront_ingot", "none", "iront_ingot",
"iront_ingot", "none", "iront_ingot",
"iront_ingot", "iront_ingot", "iront_ingot"
],
200: [
"charcoal", "none", "none",
"stick", "none", "none",
"none", "none", "none"
],
201: [
"coal", "none", "none",
"stick", "none", "none",
"none", "none", "none"
],
202: [
"glowstone", "glowstone", "none",
"glowstone", "glowstone", "none",
"none", "none", "none"
],
213: [
"gunpowder", "blaze_powder", "none",
"charcoal", "none", "none",
"none", "none", "none"
],
214: [
"gunpowder", "blaze_powder", "none",
"coal", "none", "none",
"none", "none", "none"
],
228: [
"pumpkin", "none", "none",
"torch", "none", "none",
"none", "none", "none"
],
211: [
"none", "gold_ingot", "none",
"gold_ingot", "redstone", "gold_ingot",
"none", "gold_ingot", "none"
],
212: [
"ender_pearl", "blaze_powder", "none",
"none", "none", "none",
"none", "none", "none"
],
215: [
"string", "string", "none",
"string", "slime_ball", "none",
"none", "none", "string"
],
216: [
"none", "iron_ingot", "none",
"iron_ingot", "redstone", "iron_ingot",
"none", "iron_ingot", "none"
],
217: [
"paper", "paper", "paper",
"paper", "compass", "paper",
"paper", "paper", "paper"
],
//page 3
26: [
"gold_ingot", "gold_ingot", "gold_ingot",
"gold_ingot", "apple", "gold_ingot",
"gold_ingot", "gold_ingot", "gold_ingot"
],
27: [
"gold_block", "gold_block", "gold_block",
"gold_block", "apple", "gold_block",
"gold_block", "gold_block", "gold_block"
],
28: [
"gold_nugget", "gold_nugget", "gold_nugget",
"gold_nugget", "melon", "gold_nugget",
"gold_nugget", "gold_nugget", "gold_nugget"
],
35: [
"gold_nugget", "gold_nugget", "gold_nugget",
"gold_nugget", "carrot", "gold_nugget",
"gold_nugget", "gold_nugget", "gold_nugget"
],
29: [
"brown_mushroom", "red_mushroom", "none",
"bowl", "none", "none",
"none", "none", "none"
],
30: [
"wheat", "cocoa_beans", "wheat",
"none", "none", "none",
"none", "none", "none"
],
31: [
"melon", "melon", "melon",
"melon", "melon", "melon",
"melon", "melon", "melon"
],
};

View File

@@ -3,7 +3,7 @@
/* --------------------------------------------------------------- */
const playerPackets = require("./playerpackets");
const entityPackets = require("./entitypackets");
const worldPackets = require("./worldPackets");
const worldPackets = require("./worldpackets");
const containerPackets = require("./containerpackets");
module.exports = {