Compare commits
4 Commits
RELEASE-v1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 109157b19f | |||
| a94f5f8c58 | |||
| 3cdd1a50e8 | |||
| 60737f5669 |
@@ -1,188 +1,246 @@
|
||||
# Legacy Cross Play
|
||||
## Any questions, need help? [Join the server!](https://discord.gg/cb9rs9yfDm)
|
||||
## CURRENTLY, THE ORIGINAL LEGACY CLIENT IS BROKEN, USE [THIS ONE](https://github.com/DeveloperExotic/LegacyCrossPlay/releases)
|
||||
|
||||
**This project allows Legacy Console Edition clients to join a vanilla 1.8 Java Edition server!**
|
||||

|
||||

|
||||
|
||||
# How to Install
|
||||
|
||||
**Code modifications, or server plugins ARE NOT required!**
|
||||
|
||||
- Install [NodeJS](https://nodejs.org/en/download), you may need to restart your computer after
|
||||
- Start a localhost Java Edition server running on port 25564, and in offline mode (you can edit these in `server.properties`)
|
||||
- Run the file `install_dependencies.bat`, - you only need to run this file one time.
|
||||
- Run `start.bat`, the Java Edition server will appear in the join tab on Legacy Console Edition! You can configure the server IP / PORT in `constants.js`.
|
||||
|
||||
# Known issues, will be fixed
|
||||
|
||||
- Chunk lighting is always based on sky light (no lighting data)
|
||||
- You're unable to accept villager trades
|
||||
## Any questions or need help?
|
||||
Join the server: https://discord.gg/cb9rs9yfDm
|
||||
|
||||
---
|
||||
|
||||
# TODO
|
||||
**This project allows Legacy Console Edition clients to join a vanilla 1.8 Java Edition server.**
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
## How to Install
|
||||
|
||||
**Client modifications or server plugins are not required.**
|
||||
|
||||
- Install Node.js
|
||||
https://nodejs.org/en/download
|
||||
(you may need to restart your computer)
|
||||
|
||||
- Run `install_dependencies.bat`
|
||||
(only required once)
|
||||
|
||||
- Run `start.bat`
|
||||
|
||||
The proxy will appear in the join tab on Legacy Console Edition.
|
||||
|
||||
---
|
||||
|
||||
## How to Use
|
||||
|
||||
Use `/lce` commands to manage your connection.
|
||||
|
||||
- `/lce connect <ip>`
|
||||
Connect to a Java Edition server.
|
||||
|
||||
- `/lce disconnect`
|
||||
Disconnect from the server.
|
||||
|
||||
- `/lce link`
|
||||
Link your Java Edition account.
|
||||
|
||||
- `/lce unlink`
|
||||
Unlink your account.
|
||||
|
||||
- `/lce uselink <true/false>`
|
||||
Enable or disable the linked account when connecting.
|
||||
|
||||
---
|
||||
|
||||
## TODO
|
||||
|
||||
### Known Issues
|
||||
|
||||
- Crafting is buggy / not fully implemented
|
||||
- Villager trades cannot be accepted
|
||||
- Lighting has minor dimension related issues
|
||||
|
||||
---
|
||||
|
||||
## Chunks
|
||||
|
||||
- [x] Java → LCE chunks
|
||||
- [x] Blocks
|
||||
- [x] Metadata
|
||||
- [x] Lighting
|
||||
- [x] Biomes
|
||||
- [x] Java -> LCE chunks
|
||||
- [x] Blocks
|
||||
- [x] Metadata
|
||||
- [x] Lighting
|
||||
- [x] Biomes
|
||||
|
||||
## Inventory Syncing
|
||||
---
|
||||
|
||||
- [x] Held item
|
||||
- [x] Item dropping
|
||||
- [x] Creative items
|
||||
- [x] Creative clear
|
||||
- [x] Moved items
|
||||
## Inventory Sync
|
||||
|
||||
## Sync Gamemode / XP / Health / Hunger
|
||||
- [x] Held item
|
||||
- [x] Item dropping
|
||||
- [x] Creative items
|
||||
- [x] Inventory movement
|
||||
|
||||
- [x] Damage knockback / sound
|
||||
---
|
||||
|
||||
## Game State
|
||||
|
||||
- [x] Gamemode / XP / Health / Hunger
|
||||
- [x] Damage knockback and sounds
|
||||
|
||||
---
|
||||
|
||||
## Respawn
|
||||
|
||||
- [x] Death animation
|
||||
- [x] Respawn handling
|
||||
- [x] Death animation
|
||||
- [x] Respawn handling
|
||||
|
||||
---
|
||||
|
||||
## Environment
|
||||
|
||||
### Time
|
||||
|
||||
- [x] Sync Time
|
||||
- [x] DoDaylightCycle fix
|
||||
- [x] Time synchronization
|
||||
- [x] Daylight cycle fixes
|
||||
|
||||
### Weather
|
||||
|
||||
- [x] Sync Weather
|
||||
- [x] Weather synchronization
|
||||
|
||||
---
|
||||
|
||||
## Sound
|
||||
|
||||
- [x] Sound effects
|
||||
- [x] Sound effects
|
||||
|
||||
---
|
||||
|
||||
# Entities
|
||||
## Entities
|
||||
|
||||
## Base Entities
|
||||
### Base Entities
|
||||
|
||||
- [x] Java edition players
|
||||
- [x] Items
|
||||
- [x] Mobs
|
||||
- [x] Java edition players
|
||||
- [x] Items
|
||||
- [x] Mobs
|
||||
|
||||
---
|
||||
|
||||
### Mob Features
|
||||
|
||||
- [x] Ageable entities
|
||||
- [x] Entity metadata (e.g. size, type)
|
||||
- [x] Nametags
|
||||
- [x] Mob inventory
|
||||
- [x] Animations (e.g. bats)
|
||||
- [x] Ageable entities
|
||||
- [x] Metadata (size, type)
|
||||
- [x] Nametags
|
||||
- [x] Mob inventory
|
||||
- [x] Animations
|
||||
|
||||
## Entity Attachments
|
||||
---
|
||||
|
||||
- [x] Attach to entities (e.g. riding pig, minecart, boat)
|
||||
### Entity Attachments
|
||||
|
||||
- [x] Riding (boats, minecarts, etc.)
|
||||
|
||||
---
|
||||
|
||||
### Damage Translation
|
||||
|
||||
#### LCE Damage → Java Entity
|
||||
- [x] LCE -> Java
|
||||
- [x] Entities
|
||||
- [x] Players
|
||||
|
||||
- [x] Entity
|
||||
- [x] Player
|
||||
|
||||
#### Java Damage → LCE Entity
|
||||
|
||||
- [x] Mobs
|
||||
- [x] Players
|
||||
- [x] Hurt sound
|
||||
- [x] Entity velocity knockback
|
||||
|
||||
### Critical Hits
|
||||
|
||||
- [x] Normal critical
|
||||
- [x] Magical critical
|
||||
|
||||
### Other Entity Systems
|
||||
|
||||
- [x] Fix minecart metadata (e.g. tnt minecart)
|
||||
- [x] Arrows
|
||||
- [x] Fix arrow bug when colliding with player / low bow pull
|
||||
- [x] Falling block entities (e.g. sand, gravel, anvil)
|
||||
- [x] Fireball
|
||||
- [x] Custom Java Edition skins
|
||||
- [x] Java -> LCE
|
||||
- [x] Mobs
|
||||
- [x] Players
|
||||
- [x] Knockback
|
||||
- [x] Hurt sounds
|
||||
|
||||
---
|
||||
|
||||
# Server
|
||||
### Combat
|
||||
|
||||
## Chat
|
||||
|
||||
- [x] Receive server chat
|
||||
- [x] Finter server colors
|
||||
- [x] Send server chat
|
||||
- [x] Fix chat commands (e.g. /say, /tellraw)
|
||||
- [x] Java edition english chat translations
|
||||
|
||||
## Disconnections
|
||||
|
||||
- [x] Our LCE client disconnects
|
||||
- [x] Our java player disconnects
|
||||
- [x] Critical hits (normal and magical)
|
||||
|
||||
---
|
||||
|
||||
# Player Animations
|
||||
### Misc Entities
|
||||
|
||||
## LCE → Java
|
||||
|
||||
- [x] Crouch
|
||||
- [x] Sprint
|
||||
- [x] Attack (swing)
|
||||
- [x] Riding
|
||||
- [x] Sword blocking
|
||||
|
||||
## Java → LCE
|
||||
|
||||
- [x] Crouch
|
||||
- [x] Sprint
|
||||
- [x] Attack
|
||||
- [x] Entity
|
||||
- [x] Player
|
||||
- [x] Eating
|
||||
- [x] Sleeping
|
||||
- [x] Minecart fixes
|
||||
- [x] Arrows
|
||||
- [x] Collision and behavior fixes
|
||||
- [x] Falling blocks (sand, gravel, anvils)
|
||||
- [x] Fireballs
|
||||
- [x] Custom Java skins
|
||||
|
||||
---
|
||||
|
||||
# Block Updates
|
||||
## Server
|
||||
|
||||
## Java → LCE
|
||||
### Chat
|
||||
|
||||
- [x] Creative
|
||||
- [x] Survival
|
||||
- [x] Explosions
|
||||
|
||||
## LCE → Java
|
||||
|
||||
- [x] Creative
|
||||
- [x] Survival
|
||||
- [x] Receive and send chat
|
||||
- [x] Color handling
|
||||
- [x] Command support (/say, /tellraw)
|
||||
- [x] Translation support
|
||||
|
||||
---
|
||||
|
||||
# World
|
||||
### Disconnections
|
||||
|
||||
- [x] Particles
|
||||
- [x] Nether and End dimensions
|
||||
- [x] LCE client disconnect handling
|
||||
- [x] Java player disconnect handling
|
||||
|
||||
---
|
||||
|
||||
# UI
|
||||
## Player Animations
|
||||
|
||||
- [x] Crafting
|
||||
- [x] Brewing stand
|
||||
- [x] Furnace
|
||||
- [x] Villager trading
|
||||
- [ ] Accept trade _(Hard to implement, coming soon)_
|
||||
- [x] Anvils
|
||||
- [x] Chests
|
||||
### LCE -> Java
|
||||
|
||||
- [x] Crouch
|
||||
- [x] Sprint
|
||||
- [x] Attack
|
||||
- [x] Riding
|
||||
- [x] Blocking
|
||||
|
||||
---
|
||||
|
||||
### Java -> LCE
|
||||
|
||||
- [x] Crouch
|
||||
- [x] Sprint
|
||||
- [x] Attack
|
||||
- [x] Player
|
||||
- [x] Entity
|
||||
- [x] Eating
|
||||
- [x] Sleeping
|
||||
|
||||
---
|
||||
|
||||
## Block Updates
|
||||
|
||||
### Java -> LCE
|
||||
|
||||
- [x] Creative
|
||||
- [x] Survival
|
||||
- [x] Explosions
|
||||
|
||||
---
|
||||
|
||||
### LCE -> Java
|
||||
|
||||
- [x] Creative
|
||||
- [x] Survival
|
||||
|
||||
---
|
||||
|
||||
## World
|
||||
|
||||
- [x] Particles
|
||||
- [x] Nether and End support
|
||||
|
||||
---
|
||||
|
||||
## UI
|
||||
|
||||
- [x] Crafting
|
||||
- [x] Brewing stand
|
||||
- [x] Furnace
|
||||
- [x] Villager trading
|
||||
- [ ] Accepting trades
|
||||
- [x] Anvils
|
||||
- [x] Chests
|
||||
+4
-10
@@ -1,14 +1,10 @@
|
||||
/* --------------------------------------------------------------- */
|
||||
/* constants.js */
|
||||
/* --------------------------------------------------------------- */
|
||||
const USE_LEGACY_USERNAME = false;
|
||||
const CUSTOM_USERNAME = "LCE_Player";
|
||||
const LOBBY_NAME = "Legacy Cross Play";
|
||||
|
||||
/* EVERYTHING IS NOW HANDLED IN GAME, JOIN THE PROXY DIRECTLY INSTEAD OF CONFIGURING THIS FILE! */
|
||||
|
||||
const SERVERS = [
|
||||
{ server: "us-1.creepernation.net:25568", cracked: true },
|
||||
{ server: "hypixel.net", cracked: false },
|
||||
{ server: "localhost:25564", cracked: true },
|
||||
];
|
||||
|
||||
/* DONT TOUCH THESE BELOW THINGS UNLESS YOU KNOW WHAT YOURE DOING! */
|
||||
|
||||
@@ -20,8 +16,6 @@ module.exports = {
|
||||
GAME_PORT,
|
||||
WIN64_LAN_DISCOVERY_PORT,
|
||||
MINECRAFT_NET_VERSION,
|
||||
USE_LEGACY_USERNAME,
|
||||
CUSTOM_USERNAME,
|
||||
SERVERS,
|
||||
LOBBY_NAME,
|
||||
};
|
||||
/* --------------------------------------------------------------- */
|
||||
|
||||
+45
-51
@@ -6,6 +6,7 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const AUTH_CACHE_DIR = path.join(__dirname, "..", ".auth_cache");
|
||||
const USER_IDENTIFIER = "lce-proxy-user";
|
||||
|
||||
class MicrosoftAuth {
|
||||
constructor() {
|
||||
@@ -22,72 +23,65 @@ class MicrosoftAuth {
|
||||
}
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
async authenticate(onDeviceCode) {
|
||||
this.ensureCacheDir();
|
||||
|
||||
if (!this.authflow) {
|
||||
const userIdentifier = "lce-proxy-user";
|
||||
this.authflow = new Authflow(userIdentifier, AUTH_CACHE_DIR);
|
||||
this.authflow = new Authflow(USER_IDENTIFIER, AUTH_CACHE_DIR);
|
||||
|
||||
if (onDeviceCode) {
|
||||
this.authflow.codeCallback = onDeviceCode;
|
||||
}
|
||||
|
||||
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",
|
||||
);
|
||||
const result = await this.authflow.getMinecraftJavaToken({
|
||||
fetchProfile: true,
|
||||
});
|
||||
|
||||
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 */
|
||||
}
|
||||
};
|
||||
if (!result.profile) {
|
||||
throw new Error(
|
||||
"Couldn't log into Java Edition account! Do you own Minecraft Java Edition?",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.authflow.getMinecraftJavaToken({
|
||||
fetchProfile: true,
|
||||
});
|
||||
this.username = result.profile.name;
|
||||
this.uuid = result.profile.id;
|
||||
this.accessToken = result.token;
|
||||
this.authenticated = 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;
|
||||
}
|
||||
return {
|
||||
username: this.username,
|
||||
uuid: this.uuid,
|
||||
accessToken: this.accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
async getAuthData() {
|
||||
return await this.authenticate();
|
||||
unlink() {
|
||||
try {
|
||||
const files = fs.readdirSync(AUTH_CACHE_DIR);
|
||||
for (const file of files) {
|
||||
fs.unlinkSync(path.join(AUTH_CACHE_DIR, file));
|
||||
}
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
this.authflow = null;
|
||||
this.authenticated = false;
|
||||
this.username = null;
|
||||
this.uuid = null;
|
||||
this.accessToken = null;
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
return this.authenticated;
|
||||
}
|
||||
|
||||
hasCachedAccount() {
|
||||
try {
|
||||
const files = fs.readdirSync(AUTH_CACHE_DIR);
|
||||
return files.length > 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const authInstance = new MicrosoftAuth();
|
||||
|
||||
+112
-81
@@ -5,10 +5,7 @@
|
||||
//beware that there are a lot of magic numbers, need to do a refractor soon.
|
||||
|
||||
const mc = require("minecraft-protocol");
|
||||
const {
|
||||
USE_LEGACY_USERNAME,
|
||||
CUSTOM_USERNAME,
|
||||
} = require("../constants");
|
||||
|
||||
const msAuth = require("./auth");
|
||||
const PacketWriter = require("./packetwriter");
|
||||
const { mapJavaItemToLCE, mapJavaBlockToLCE } = require("./mappings");
|
||||
@@ -29,9 +26,7 @@ async function connectToJavaServer(proxy, client) {
|
||||
let authConfig = {
|
||||
host: serverConfig.host,
|
||||
port: serverConfig.port,
|
||||
username: !USE_LEGACY_USERNAME
|
||||
? CUSTOM_USERNAME
|
||||
: client.username || "undefined",
|
||||
username: client.username || "Player",
|
||||
auth: "offline",
|
||||
version: "1.8.9",
|
||||
};
|
||||
@@ -53,7 +48,6 @@ async function connectToJavaServer(proxy, client) {
|
||||
version: "1.8.9",
|
||||
};
|
||||
} else {
|
||||
console.log(`Server ${serverConfig.name} requires authentication. Authenticating...`);
|
||||
await msAuth.authenticate();
|
||||
authConfig = {
|
||||
host: serverConfig.host,
|
||||
@@ -83,72 +77,61 @@ async function connectToJavaServer(proxy, client) {
|
||||
client.javaClient = javaClient;
|
||||
client._lastJavaPackets = [];
|
||||
|
||||
const returnToLobby = (message) => {
|
||||
if (client.socket && client.socket.destroyed) return;
|
||||
const old = client.javaClient;
|
||||
client.javaClient = null;
|
||||
client.inLobby = true;
|
||||
if (old) {
|
||||
old.removeAllListeners();
|
||||
old.on("error", () => {
|
||||
/* do nothing */
|
||||
});
|
||||
try {
|
||||
old.end();
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
proxy.enterLobby(client, message);
|
||||
};
|
||||
|
||||
javaClient.on("error", (err) => {
|
||||
console.log(`DISCONNECTED: ${err}`);
|
||||
if (!client._removing) {
|
||||
client._removing = true;
|
||||
const index = proxy.clients.indexOf(client);
|
||||
if (index > -1) {
|
||||
proxy.clients.splice(index, 1);
|
||||
}
|
||||
if (client.smallId) {
|
||||
proxy.usedSmallIds.delete(client.smallId);
|
||||
}
|
||||
client.javaClient = null;
|
||||
if (client.socket && !client.socket.destroyed) {
|
||||
try {
|
||||
const writer = new PacketWriter();
|
||||
writer.writeInt(20);
|
||||
proxy.sendPacket(client, 0xff, writer.toBuffer());
|
||||
client.socket.destroy();
|
||||
} catch (err) {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
returnToLobby(`Connection error: ${err.message || err}`);
|
||||
});
|
||||
|
||||
javaClient.on("disconnect", (packet) => {
|
||||
console.log(`DISCONNECTED: ${packet.reason}`);
|
||||
if (!client._removing) {
|
||||
proxy.disconnectClient(client, 2);
|
||||
const handleDisconnectReason = (raw, prefix) => {
|
||||
let text = "";
|
||||
try {
|
||||
text = typeof raw === "string" ? raw : JSON.parse(raw)?.text || raw;
|
||||
} catch (e) {
|
||||
text = String(raw);
|
||||
}
|
||||
});
|
||||
|
||||
javaClient.on("kick_disconnect", (packet) => {
|
||||
console.log(`DISCONNECTED: ${packet.reason}`);
|
||||
if (client.state === "play") {
|
||||
let reason = 10;
|
||||
if (packet.reason) {
|
||||
const reasonText =
|
||||
typeof packet.reason === "string"
|
||||
? packet.reason
|
||||
: JSON.stringify(packet.reason);
|
||||
const lowerReason = reasonText.toLowerCase();
|
||||
if (lowerReason.includes("ban")) {
|
||||
reason = 10;
|
||||
} else if (lowerReason.includes("kick")) {
|
||||
reason = 10;
|
||||
} else if (
|
||||
lowerReason.includes("timeout") ||
|
||||
lowerReason.includes("timed out")
|
||||
) {
|
||||
reason = 20;
|
||||
} else if (lowerReason.includes("full")) {
|
||||
reason = 23;
|
||||
} else {
|
||||
reason = 10;
|
||||
}
|
||||
if (text.toLowerCase().includes("failed to verify username")) {
|
||||
if (msAuth.hasCachedAccount() && client.useLinkedAccount === false) {
|
||||
returnToLobby(
|
||||
'This server requires a Java Edition account, execute "/lce uselink true"',
|
||||
);
|
||||
} else {
|
||||
returnToLobby(
|
||||
"This server requires a Java Edition account, use /lce link",
|
||||
);
|
||||
}
|
||||
proxy.disconnectClient(client, reason);
|
||||
} else {
|
||||
proxy.disconnectClient(client, 2);
|
||||
returnToLobby(`${prefix}: ${text}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
javaClient.on("disconnect", (packet) =>
|
||||
handleDisconnectReason(packet.reason, "Disconnected"),
|
||||
);
|
||||
javaClient.on("kick_disconnect", (packet) =>
|
||||
handleDisconnectReason(packet.reason, "Kicked"),
|
||||
);
|
||||
|
||||
javaClient.on("end", () => {
|
||||
if (proxy.clients.indexOf(client) > -1 && !client._removing) {
|
||||
proxy.disconnectClient(client, 2);
|
||||
if (client.javaClient) {
|
||||
returnToLobby("Connection to server closed");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1426,18 +1409,6 @@ async function connectToJavaServer(proxy, client) {
|
||||
let tileY = packet.y / 32;
|
||||
let tileZ = packet.z / 32;
|
||||
|
||||
// if (lceEntityType === 71 && itemFrameDirection !== null) {
|
||||
// if (itemFrameDirection === 0) {
|
||||
// tileZ--;
|
||||
// } else if (itemFrameDirection === 1) {
|
||||
// tileX++;
|
||||
// } else if (itemFrameDirection === 2) {
|
||||
// tileZ++;
|
||||
// } else if (itemFrameDirection === 3) {
|
||||
// tileX -= 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
const entityInfo = {
|
||||
entityId: lceEntityId,
|
||||
javaEntityId: packet.entityId,
|
||||
@@ -1691,7 +1662,7 @@ async function connectToJavaServer(proxy, client) {
|
||||
|
||||
const sleepWriter = new PacketWriter();
|
||||
sleepWriter.writeInt(player.entityId);
|
||||
sleepWriter.writeByte(0); //0=start sleep
|
||||
sleepWriter.writeByte(0);
|
||||
sleepWriter.writeInt(packet.location.x);
|
||||
sleepWriter.writeByte(packet.location.y & 0xff);
|
||||
sleepWriter.writeInt(packet.location.z);
|
||||
@@ -2614,9 +2585,68 @@ function convertAndSendJavaChunk(proxy, client, javaChunk) {
|
||||
}
|
||||
}
|
||||
|
||||
const hasSky = client.javaDimension === 0;
|
||||
const javaBlitBase = sectionCount * 8192;
|
||||
const javaSlitBase = hasSky ? javaBlitBase + sectionCount * 2048 : -1;
|
||||
|
||||
const BLOCK_LIGHT_OFF = blockCount + halfBlockCount;
|
||||
const SKY_LIGHT_OFF = blockCount + halfBlockCount + halfBlockCount;
|
||||
|
||||
rawData.fill(0xff, SKY_LIGHT_OFF, SKY_LIGHT_OFF + halfBlockCount);
|
||||
|
||||
let litSectionIdx = 0;
|
||||
for (let sectionY = 0; sectionY < 16; sectionY++) {
|
||||
if ((bitMap & (1 << sectionY)) === 0) continue;
|
||||
|
||||
const baseY = sectionY * 16;
|
||||
const jBlit = javaBlitBase + litSectionIdx * 2048;
|
||||
const jSlit =
|
||||
javaSlitBase >= 0 ? javaSlitBase + litSectionIdx * 2048 : -1;
|
||||
|
||||
for (let sx = 0; sx < 16; sx++) {
|
||||
const jLow = (sx & 1) === 0;
|
||||
for (let sz = 0; sz < 16; sz++) {
|
||||
for (let yp = 0; yp < 8; yp++) {
|
||||
const y0 = yp * 2;
|
||||
const totalY = baseY + y0;
|
||||
if (totalY >= chunkHeight) continue;
|
||||
|
||||
const jb0 = (y0 * 256 + sz * 16 + sx) >> 1;
|
||||
const jb1 = jb0 + 128;
|
||||
|
||||
let cIdx;
|
||||
if (totalY < 128) {
|
||||
cIdx = (sx * 16 + sz) * 64 + (totalY >> 1);
|
||||
} else {
|
||||
cIdx = 16384 + (sx * 16 + sz) * 64 + ((totalY - 128) >> 1);
|
||||
}
|
||||
|
||||
//block light
|
||||
if (jBlit + jb1 < javaData.length) {
|
||||
const b0 = javaData[jBlit + jb0];
|
||||
const b1 = javaData[jBlit + jb1];
|
||||
const n0 = jLow ? b0 & 0x0f : (b0 >> 4) & 0x0f;
|
||||
const n1 = jLow ? b1 & 0x0f : (b1 >> 4) & 0x0f;
|
||||
rawData[BLOCK_LIGHT_OFF + cIdx] = n0 | (n1 << 4);
|
||||
}
|
||||
|
||||
//sky light
|
||||
if (jSlit >= 0 && jSlit + jb1 < javaData.length) {
|
||||
const s0 = javaData[jSlit + jb0];
|
||||
const s1 = javaData[jSlit + jb1];
|
||||
const n0 = jLow ? s0 & 0x0f : (s0 >> 4) & 0x0f;
|
||||
const n1 = jLow ? s1 & 0x0f : (s1 >> 4) & 0x0f;
|
||||
rawData[SKY_LIGHT_OFF + cIdx] = n0 | (n1 << 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
litSectionIdx++;
|
||||
}
|
||||
|
||||
//biome fix!
|
||||
const biomeOffset = blockCount + 3 * halfBlockCount;
|
||||
const hasSky = client.javaDimension === 0;
|
||||
const javaBiomeOffset = sectionCount * (hasSky ? 12288 : 10240);
|
||||
|
||||
if (javaChunk.x === 0 && javaChunk.z === 0) {
|
||||
@@ -2645,8 +2675,9 @@ function convertAndSendJavaChunk(proxy, client, javaChunk) {
|
||||
}
|
||||
}
|
||||
|
||||
const skyLightOffset = blockCount + halfBlockCount + halfBlockCount;
|
||||
if (skyLightOffset >= 0 && skyLightOffset + halfBlockCount <= rawSize) {
|
||||
const hasSkyFallback = client.javaDimension === 0;
|
||||
if (!hasSkyFallback) {
|
||||
const skyLightOffset = blockCount + halfBlockCount + halfBlockCount;
|
||||
for (let i = 0; i < halfBlockCount; i++) {
|
||||
rawData[skyLightOffset + i] = 0xff;
|
||||
}
|
||||
|
||||
+5
-1
@@ -16,13 +16,17 @@ class LANBroadcast {
|
||||
this.buffer.writeUInt16LE(char, 8 + i * 2);
|
||||
}
|
||||
|
||||
this.buffer.writeUInt8(1, 72); //player count including host
|
||||
this.buffer.writeUInt8(1, 72); //player count (updated dynamically)
|
||||
this.buffer.writeUInt8(8, 73); //max players
|
||||
this.buffer.writeUInt32LE(0, 74); //host settings
|
||||
this.buffer.writeUInt32LE(0, 78); //texture pack ID
|
||||
this.buffer.writeUInt8(0, 82); //sub texture pack ID
|
||||
this.buffer.writeUInt8(1, 83); //is joinable
|
||||
}
|
||||
|
||||
setPlayerCount(count) {
|
||||
this.buffer.writeUInt8(Math.max(1, count), 72);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LANBroadcast;
|
||||
|
||||
+466
-93
@@ -11,9 +11,7 @@ const {
|
||||
GAME_PORT,
|
||||
WIN64_LAN_DISCOVERY_PORT,
|
||||
MINECRAFT_NET_VERSION,
|
||||
USE_LEGACY_USERNAME,
|
||||
CUSTOM_USERNAME,
|
||||
SERVERS,
|
||||
LOBBY_NAME,
|
||||
} = require("../constants");
|
||||
|
||||
const PacketWriter = require("./packetwriter");
|
||||
@@ -70,7 +68,7 @@ class LCEProxy {
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x3c, 0x3d, 0x3e, 0x46, 0x64,
|
||||
0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x82, 0x83, 0x85,
|
||||
0x96, 0x97, 0x98, 0x9a, 0x9d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
|
||||
0xcf, 0xd0, 0xd1, 0xfa, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
0xcf, 0xd0, 0xd1, 0xfa, 0xfc, 0xfd, 0xfe, 0xff, 0xaa, 0xac,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -185,13 +183,6 @@ class LCEProxy {
|
||||
this.startTCPServer();
|
||||
|
||||
console.log("\nProxy is now running!");
|
||||
console.log(
|
||||
`Broadcasting ${SERVERS.length} server(s) as LAN games on Minecraft Legacy Edition.`,
|
||||
);
|
||||
SERVERS.forEach((server, index) => {
|
||||
const serverStr = typeof server === 'string' ? server : server.server;
|
||||
console.log(` - ${serverStr} on port ${GAME_PORT + index}`);
|
||||
});
|
||||
console.log("Press Ctrl+C to stop.\n");
|
||||
}
|
||||
|
||||
@@ -201,96 +192,80 @@ class LCEProxy {
|
||||
this.udpSocket.setBroadcast(true);
|
||||
});
|
||||
|
||||
this.broadcasts = SERVERS.map((server, index) => {
|
||||
const gamePort = GAME_PORT + index;
|
||||
const serverStr = typeof server === 'string' ? server : server.server;
|
||||
const parts = serverStr.split(':');
|
||||
this.serverConfigs[index] = {
|
||||
host: parts[0],
|
||||
port: parts[1] ? parseInt(parts[1]) : 25565,
|
||||
name: serverStr,
|
||||
cracked: typeof server === 'object' ? server.cracked : false,
|
||||
gamePort: gamePort,
|
||||
};
|
||||
return new LANBroadcast(serverStr, gamePort);
|
||||
});
|
||||
this.lobbyBroadcast = new LANBroadcast(LOBBY_NAME, GAME_PORT);
|
||||
|
||||
this.broadcastInterval = setInterval(() => {
|
||||
this.broadcasts.forEach((broadcast) => {
|
||||
this.udpSocket.send(
|
||||
broadcast.buffer,
|
||||
0,
|
||||
broadcast.buffer.length,
|
||||
WIN64_LAN_DISCOVERY_PORT,
|
||||
"255.255.255.255",
|
||||
);
|
||||
});
|
||||
this.lobbyBroadcast.setPlayerCount(Math.max(1, this.clients.length));
|
||||
this.udpSocket.send(
|
||||
this.lobbyBroadcast.buffer,
|
||||
0,
|
||||
this.lobbyBroadcast.buffer.length,
|
||||
WIN64_LAN_DISCOVERY_PORT,
|
||||
"255.255.255.255",
|
||||
);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
startTCPServer() {
|
||||
this.serverConfigs.forEach((serverConfig, index) => {
|
||||
const tcpServer = net.createServer((socket) => {
|
||||
socket.setNoDelay(true);
|
||||
const tcpServer = net.createServer((socket) => {
|
||||
socket.setNoDelay(true);
|
||||
|
||||
let smallId = 1;
|
||||
while (this.usedSmallIds.has(smallId) && smallId <= 8) {
|
||||
smallId++;
|
||||
}
|
||||
let smallId = 0;
|
||||
while (this.usedSmallIds.has(smallId) && smallId <= 8) {
|
||||
smallId++;
|
||||
}
|
||||
|
||||
if (smallId > 8) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (smallId > 8) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.usedSmallIds.add(smallId);
|
||||
this.usedSmallIds.add(smallId);
|
||||
|
||||
const client = {
|
||||
socket: socket,
|
||||
smallId: smallId,
|
||||
buffer: Buffer.alloc(0),
|
||||
state: "prelogin",
|
||||
javaClient: null,
|
||||
breakingBlock: null,
|
||||
javaPlayers: new Map(),
|
||||
nextLceEntityId: 100,
|
||||
javaToLceEntityId: new Map(),
|
||||
javaEntities: new Map(),
|
||||
serverConfig: serverConfig,
|
||||
};
|
||||
const client = {
|
||||
socket: socket,
|
||||
smallId: smallId,
|
||||
buffer: Buffer.alloc(0),
|
||||
state: "prelogin",
|
||||
javaClient: null,
|
||||
breakingBlock: null,
|
||||
javaPlayers: new Map(),
|
||||
nextLceEntityId: 100,
|
||||
javaToLceEntityId: new Map(),
|
||||
javaEntities: new Map(),
|
||||
serverConfig: null,
|
||||
inLobby: true,
|
||||
};
|
||||
|
||||
this.clients.push(client);
|
||||
this.clients.push(client);
|
||||
|
||||
const smallIdBuffer = Buffer.from([smallId]);
|
||||
socket.write(smallIdBuffer);
|
||||
const smallIdBuffer = Buffer.from([smallId]);
|
||||
socket.write(smallIdBuffer);
|
||||
|
||||
socket.on("data", (data) => {
|
||||
this.handleClientData(client, data);
|
||||
});
|
||||
|
||||
socket.on("end", () => {
|
||||
this.removeClient(client);
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
this.removeClient(client);
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
if (this.clients.indexOf(client) > -1) {
|
||||
this.removeClient(client);
|
||||
}
|
||||
});
|
||||
socket.on("data", (data) => {
|
||||
this.handleClientData(client, data);
|
||||
});
|
||||
|
||||
tcpServer.listen(serverConfig.gamePort, () => {
|
||||
console.log(
|
||||
`Server ${serverConfig.name} should now be visible on LCE.`,
|
||||
);
|
||||
socket.on("end", () => {
|
||||
this.removeClient(client);
|
||||
});
|
||||
|
||||
this.tcpServers.push(tcpServer);
|
||||
socket.on("error", (err) => {
|
||||
this.removeClient(client);
|
||||
});
|
||||
|
||||
socket.on("close", () => {
|
||||
if (this.clients.indexOf(client) > -1) {
|
||||
this.removeClient(client);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
tcpServer.listen(GAME_PORT, () => {
|
||||
/* do nothing */
|
||||
});
|
||||
|
||||
this.tcpServers.push(tcpServer);
|
||||
}
|
||||
|
||||
getPacketDataSize(packetId, data, offset) {
|
||||
@@ -499,6 +474,11 @@ class LCEProxy {
|
||||
case 0x02: //PreLoginPacket
|
||||
this.handlePreLogin(client, packetData);
|
||||
break;
|
||||
case 0xab: //AuthResponsePacket (171)
|
||||
if (client.state === "login") {
|
||||
this.handleAuthResponse(client, packetData);
|
||||
}
|
||||
break;
|
||||
case 0x01: //LoginPacket
|
||||
this.handleLogin(client, packetData);
|
||||
break;
|
||||
@@ -625,6 +605,7 @@ class LCEProxy {
|
||||
const loginKey = reader.readString();
|
||||
|
||||
client.state = "login";
|
||||
client._netcodeVersion = netcodeVersion;
|
||||
|
||||
const writer = new PacketWriter();
|
||||
writer.writeShort(MINECRAFT_NET_VERSION);
|
||||
@@ -677,20 +658,135 @@ class LCEProxy {
|
||||
writer.writeInt(0); //texturePackId
|
||||
|
||||
this.sendPacket(client, 0x02, writer.toBuffer());
|
||||
if (client._netcodeVersion >= 561) {
|
||||
client.authState = "waitingResponse";
|
||||
setImmediate(() => this._sendAuthSchemePacket(client));
|
||||
} else {
|
||||
client.authState = "done";
|
||||
}
|
||||
}
|
||||
|
||||
_sendAuthSchemePacket(client) {
|
||||
const chars = "0123456789abcdef";
|
||||
let serverId = "";
|
||||
for (let i = 0; i < 20; i++)
|
||||
serverId += chars[Math.floor(Math.random() * 16)];
|
||||
client._authServerId = serverId;
|
||||
|
||||
const schemes = ["offline"];
|
||||
client._authSchemes = schemes;
|
||||
|
||||
const writer = new PacketWriter();
|
||||
writer.writeShort(schemes.length);
|
||||
for (const s of schemes) writer.writeString(s);
|
||||
writer.writeString(serverId);
|
||||
this.sendPacket(client, 0xaa, writer.toBuffer());
|
||||
}
|
||||
|
||||
handleAuthResponse(client, data) {
|
||||
if (client.authState !== "waitingResponse") return;
|
||||
|
||||
const reader = new PacketReader(data.slice(1));
|
||||
const chosenScheme = reader.readString();
|
||||
const mojangUuid = reader.readString();
|
||||
const username = reader.readString();
|
||||
|
||||
const validSchemes = client._authSchemes || ["mojang", "offline"];
|
||||
if (!validSchemes.includes(chosenScheme)) {
|
||||
this._sendAuthResult(client, false, "", "", "Invalid auth scheme");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosenScheme === "offline") {
|
||||
const crypto = require("crypto");
|
||||
const hash = crypto
|
||||
.createHash("md5")
|
||||
.update("OfflinePlayer:" + username)
|
||||
.digest();
|
||||
hash[6] = (hash[6] & 0x0f) | 0x30;
|
||||
hash[8] = (hash[8] & 0x3f) | 0x80;
|
||||
const h = hash.toString("hex");
|
||||
const offlineUuid =
|
||||
h.slice(0, 8) +
|
||||
"-" +
|
||||
h.slice(8, 12) +
|
||||
"-" +
|
||||
h.slice(12, 16) +
|
||||
"-" +
|
||||
h.slice(16, 20) +
|
||||
"-" +
|
||||
h.slice(20, 32);
|
||||
client.authUuid = offlineUuid;
|
||||
client.authUsername = username || "Player";
|
||||
client.authState = "done";
|
||||
this._sendAuthResult(client, true, offlineUuid, client.authUsername, "");
|
||||
} else {
|
||||
const msAuth = require("./auth");
|
||||
const finish = () => {
|
||||
const raw = msAuth.uuid || "";
|
||||
const uuid = raw.includes("-")
|
||||
? raw
|
||||
: raw.slice(0, 8) +
|
||||
"-" +
|
||||
raw.slice(8, 12) +
|
||||
"-" +
|
||||
raw.slice(12, 16) +
|
||||
"-" +
|
||||
raw.slice(16, 20) +
|
||||
"-" +
|
||||
raw.slice(20, 32);
|
||||
client.authUuid = uuid;
|
||||
client.authUsername = msAuth.username;
|
||||
client.authState = "done";
|
||||
this._sendAuthResult(client, true, uuid, msAuth.username, "");
|
||||
};
|
||||
if (msAuth.isAuthenticated()) {
|
||||
finish();
|
||||
} else {
|
||||
msAuth
|
||||
.authenticate()
|
||||
.then(finish)
|
||||
.catch(() => {
|
||||
this._sendAuthResult(
|
||||
client,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
"Authentication failed",
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sendAuthResult(client, success, uuid, username, errorMessage) {
|
||||
const writer = new PacketWriter();
|
||||
writer.writeBoolean(success);
|
||||
writer.writeString(uuid);
|
||||
writer.writeString(username);
|
||||
writer.writeString(errorMessage);
|
||||
writer.writeString("");
|
||||
writer.writeInt(0);
|
||||
this.sendPacket(client, 0xac, writer.toBuffer());
|
||||
if (!success) setTimeout(() => this.disconnectClient(client, 2), 200);
|
||||
}
|
||||
|
||||
handleLogin(client, data) {
|
||||
if (client.authState !== "done") {
|
||||
this.disconnectClient(client, 2);
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new PacketReader(data.slice(1));
|
||||
const clientVersion = reader.readInt();
|
||||
const username = reader.readString();
|
||||
|
||||
client.state = "login";
|
||||
client.username = !USE_LEGACY_USERNAME ? CUSTOM_USERNAME : username;
|
||||
client.username = username;
|
||||
|
||||
if (client.javaClient) {
|
||||
const oldJavaClient = client.javaClient;
|
||||
client.javaClient = null;
|
||||
|
||||
oldJavaClient.removeAllListeners();
|
||||
oldJavaClient.on("error", () => {
|
||||
/* do nothing */
|
||||
@@ -702,7 +798,109 @@ class LCEProxy {
|
||||
}
|
||||
}
|
||||
|
||||
this.connectToJavaServer(client);
|
||||
this.enterLobby(client);
|
||||
}
|
||||
|
||||
sendChatToClient(client, text) {
|
||||
const writer = new PacketWriter();
|
||||
writer.writeShort(0);
|
||||
writer.writeShort(0x10);
|
||||
writer.writeString(text);
|
||||
this.sendPacket(client, 0x03, writer.toBuffer());
|
||||
}
|
||||
|
||||
enterLobby(client, message) {
|
||||
client.inLobby = true;
|
||||
client.javaGameMode = 2;
|
||||
client.javaDimension = 0;
|
||||
client.javaSpawnX = 0.5;
|
||||
client.javaSpawnY = 65.0;
|
||||
client.javaSpawnZ = 0.5;
|
||||
client.javaYaw = 0;
|
||||
client.javaPitch = 0;
|
||||
client.javaPlayers = new Map();
|
||||
client.javaEntities = new Map();
|
||||
client.javaToLceEntityId = new Map();
|
||||
client.nextLceEntityId = 100;
|
||||
client.loadedChunks = new Set();
|
||||
client.heldSlot = 0;
|
||||
client.inventory = {};
|
||||
client._windowInventories = {};
|
||||
client._currentChest = null;
|
||||
client._deadEntities = new Set();
|
||||
client._destroyedEntities = new Set();
|
||||
client._dyingPlayers = new Map();
|
||||
client._lastJavaPackets = [];
|
||||
client._bookGUI = null;
|
||||
client._craftingInProgress = false;
|
||||
client._pendingAttacks = new Set();
|
||||
client._lastAttacks = new Map();
|
||||
client._itemUseState = {};
|
||||
client.openWindowId = null;
|
||||
client.openWindowType = null;
|
||||
client.isRiding = false;
|
||||
client.abilities = {};
|
||||
client.serverConfig = null;
|
||||
|
||||
client.state = "play";
|
||||
this.sendLCELoginResponse(client);
|
||||
|
||||
for (const other of this.clients) {
|
||||
if (other === client || other.state !== "play" || other.inLobby) continue;
|
||||
this.sendAddPlayerPacket(client, {
|
||||
entityId: other.smallId,
|
||||
name: other.username || "Player",
|
||||
x: other.playerX || 0,
|
||||
y: other.playerY || 65,
|
||||
z: other.playerZ || 0,
|
||||
yaw: other.playerYaw || 0,
|
||||
pitch: other.playerPitch || 0,
|
||||
});
|
||||
}
|
||||
|
||||
for (const other of this.clients) {
|
||||
if (other === client || other.state !== "play" || other.inLobby) continue;
|
||||
this.sendAddPlayerPacket(other, {
|
||||
entityId: client.smallId,
|
||||
name: client.username || "Player",
|
||||
x: 0.5,
|
||||
y: 65,
|
||||
z: 0.5,
|
||||
yaw: 0,
|
||||
pitch: 0,
|
||||
});
|
||||
}
|
||||
|
||||
for (let cx = -2; cx <= 2; cx++) {
|
||||
for (let cz = -2; cz <= 2; cz++) {
|
||||
this.sendFlatChunk(client, cx, cz);
|
||||
}
|
||||
}
|
||||
|
||||
const welcomeDelay = message ? 200 : 500;
|
||||
if (message) {
|
||||
setTimeout(() => {
|
||||
this.sendChatToClient(client, message);
|
||||
}, 200);
|
||||
}
|
||||
setTimeout(
|
||||
() => {
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
"Welcome to LegacyCrossPlay - developed by @DeveloperExotic!",
|
||||
);
|
||||
},
|
||||
message ? 600 : 500,
|
||||
);
|
||||
setTimeout(
|
||||
() => {
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
"Type /lce connect <ip> to join a Java Edition server",
|
||||
);
|
||||
},
|
||||
message ? 900 : 800,
|
||||
);
|
||||
}
|
||||
|
||||
sendLCELoginResponse(client) {
|
||||
@@ -727,7 +925,7 @@ class LCEProxy {
|
||||
writer.writeInt(0); //ugcPlayersVersion
|
||||
writer.writeByte(1); //difficulty (hardcoded to Easy)
|
||||
writer.writeInt(0); //multiplayerInstanceId
|
||||
writer.writeByte(0); //playerIndex
|
||||
writer.writeByte(client.smallId); //playerIndex
|
||||
writer.writeInt(0); //playerSkinId
|
||||
writer.writeInt(0); //playerCapeId
|
||||
writer.writeBoolean(false); //isGuest
|
||||
@@ -2007,6 +2205,22 @@ class LCEProxy {
|
||||
if (stringCount > 0) {
|
||||
const message = reader.readString();
|
||||
|
||||
if (message && message.startsWith("/lce ")) {
|
||||
this.handleCommand(client, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message && message.startsWith("/")) {
|
||||
if (client.javaClient && client.javaClient.write) {
|
||||
try {
|
||||
client.javaClient.write("chat", { message });
|
||||
} catch (err) {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
client.javaClient &&
|
||||
client.javaClient.write &&
|
||||
@@ -2014,9 +2228,7 @@ class LCEProxy {
|
||||
message.length > 0
|
||||
) {
|
||||
try {
|
||||
client.javaClient.write("chat", {
|
||||
message: message,
|
||||
});
|
||||
client.javaClient.write("chat", { message });
|
||||
} catch (err) {
|
||||
/* do nothing */
|
||||
}
|
||||
@@ -2024,6 +2236,168 @@ class LCEProxy {
|
||||
}
|
||||
}
|
||||
|
||||
handleCommand(client, message) {
|
||||
const parts = message.trim().split(/\s+/);
|
||||
const cmd = parts[1]?.toLowerCase();
|
||||
|
||||
if (cmd === "connect") {
|
||||
if (parts.length !== 3) {
|
||||
this.sendChatToClient(client, "Command not valid");
|
||||
return;
|
||||
}
|
||||
|
||||
const arg = parts[2];
|
||||
const colonCount = (arg.match(/:/g) || []).length;
|
||||
if (colonCount > 1) {
|
||||
this.sendChatToClient(client, "Command not valid");
|
||||
return;
|
||||
}
|
||||
|
||||
let host, port;
|
||||
if (colonCount === 1) {
|
||||
const split = arg.split(":");
|
||||
host = split[0];
|
||||
port = parseInt(split[1], 10);
|
||||
if (!host || isNaN(port) || port < 1 || port > 65535) {
|
||||
this.sendChatToClient(client, "Command not valid");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
host = arg;
|
||||
port = 25565;
|
||||
}
|
||||
|
||||
const displayAddr = colonCount === 1 ? arg : `${host}:${port}`;
|
||||
this.sendChatToClient(client, `Connecting to ${displayAddr}`);
|
||||
|
||||
if (client.javaClient) {
|
||||
const old = client.javaClient;
|
||||
client.javaClient = null;
|
||||
old.removeAllListeners();
|
||||
old.on("error", () => {
|
||||
/* do nothing */
|
||||
});
|
||||
try {
|
||||
old.end();
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
|
||||
client.inLobby = false;
|
||||
client.state = "login";
|
||||
client.serverConfig = {
|
||||
host,
|
||||
port,
|
||||
name: displayAddr,
|
||||
cracked: !(
|
||||
client.useLinkedAccount !== false &&
|
||||
require("./auth").hasCachedAccount()
|
||||
),
|
||||
};
|
||||
|
||||
this.connectToJavaServer(client);
|
||||
} else if (cmd === "disconnect") {
|
||||
if (parts.length !== 2) {
|
||||
this.sendChatToClient(client, "Command not valid");
|
||||
return;
|
||||
}
|
||||
if (client.inLobby || !client.javaClient) {
|
||||
this.sendChatToClient(client, "Command not valid");
|
||||
return;
|
||||
}
|
||||
const old = client.javaClient;
|
||||
client.javaClient = null;
|
||||
old.removeAllListeners();
|
||||
old.on("error", () => {
|
||||
/* do nothing */
|
||||
});
|
||||
try {
|
||||
old.end();
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
this.enterLobby(client, "Disconnected from server");
|
||||
} else if (cmd === "link") {
|
||||
if (parts.length !== 2) {
|
||||
this.sendChatToClient(client, "Usage: /lce link");
|
||||
return;
|
||||
}
|
||||
const msAuth = require("./auth");
|
||||
if (msAuth.hasCachedAccount()) {
|
||||
this.sendChatToClient(client, "You already have an account linked!");
|
||||
return;
|
||||
}
|
||||
this.sendChatToClient(client, "Starting Microsoft authentication...");
|
||||
let linkInterval = null;
|
||||
msAuth
|
||||
.authenticate((data) => {
|
||||
const code =
|
||||
data.userCode ||
|
||||
data.user_code ||
|
||||
(data.message && data.message.match(/[A-Z0-9]{8,}/)?.[0]) ||
|
||||
"???";
|
||||
const msg = `To link your account, head to https://www.microsoft.com/link and enter code ${code}`;
|
||||
this.sendChatToClient(client, msg);
|
||||
linkInterval = setInterval(
|
||||
() => this.sendChatToClient(client, msg),
|
||||
1000,
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
if (linkInterval) {
|
||||
clearInterval(linkInterval);
|
||||
linkInterval = null;
|
||||
}
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
`Account successfully linked as ${msAuth.username}, you may now reconnect to the server`,
|
||||
);
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
"Use /lce unlink to unlink your account",
|
||||
);
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
"Or use /lce uselink <true/false> to toggle using your linked account",
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (linkInterval) {
|
||||
clearInterval(linkInterval);
|
||||
linkInterval = null;
|
||||
}
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
`Failed to link account: ${err.message || err}`,
|
||||
);
|
||||
});
|
||||
} else if (cmd === "unlink") {
|
||||
if (parts.length !== 2) {
|
||||
this.sendChatToClient(client, "Usage: /lce unlink");
|
||||
return;
|
||||
}
|
||||
const msAuth = require("./auth");
|
||||
msAuth.unlink();
|
||||
this.sendChatToClient(client, "Java Edition account unlinked.");
|
||||
} else if (cmd === "uselink") {
|
||||
if (parts.length !== 3 || (parts[2] !== "true" && parts[2] !== "false")) {
|
||||
this.sendChatToClient(client, "Usage: /lce uselink <true/false>");
|
||||
return;
|
||||
}
|
||||
client.useLinkedAccount = parts[2] === "true";
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
`Using linked account: ${client.useLinkedAccount}`,
|
||||
);
|
||||
} else {
|
||||
this.sendChatToClient(
|
||||
client,
|
||||
"Command not valid. Use /lce connect <ip> or /lce disconnect",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handlePlayerAction(client, data, isBundled = false) {
|
||||
const reader = new PacketReader(data.slice(1));
|
||||
const action = reader.readByte();
|
||||
@@ -3725,7 +4099,6 @@ class LCEProxy {
|
||||
23: "Server full",
|
||||
};
|
||||
const reasonText = reasonMap[reason] || `Unknown (${reason})`;
|
||||
console.log(`DISCONNECTED: ${reasonText}`);
|
||||
|
||||
if (client.socket && !client.socket.destroyed) {
|
||||
try {
|
||||
@@ -3769,7 +4142,7 @@ class LCEProxy {
|
||||
this.clients.splice(index, 1);
|
||||
}
|
||||
|
||||
if (client.smallId) {
|
||||
if (client.smallId !== undefined) {
|
||||
this.usedSmallIds.delete(client.smallId);
|
||||
}
|
||||
|
||||
@@ -3801,7 +4174,7 @@ class LCEProxy {
|
||||
try {
|
||||
return await connectToJavaServerFunc(this, client);
|
||||
} catch (err) {
|
||||
console.log(`DISCONNECTED: ${err}`);
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user