implement multiplayer direct connect, implement network manager, implement handshake and login packets, implement ByteBuf, implement RSA and AES encryption, bump version to 1.1.0

This commit is contained in:
LabyStudio
2022-06-17 06:34:09 +02:00
parent 22872f55d6
commit 410346427f
35 changed files with 2513 additions and 10 deletions
@@ -14,6 +14,7 @@ export default class GameSettings {
this.sensitivity = 100;
this.viewDistance = 4;
this.debugOverlay = false;
this.serverAddress = 'server.labystudio.de';
}
load() {
+19 -2
View File
@@ -19,11 +19,20 @@ import ParticleRenderer from "./render/particle/ParticleRenderer.js";
import GuiChat from "./gui/screens/GuiChat.js";
import CommandHandler from "./command/CommandHandler.js";
import GuiContainerCreative from "./gui/screens/container/GuiContainerCreative.js";
import GameProfile from "../util/GameProfile.js";
import UUID from "../util/UUID.js";
export default class Minecraft {
static VERSION = "1.0.4"
static VERSION = "1.1.0"
static URL_GITHUB = "https://github.com/labystudio/js-minecraft";
static PROTOCOL_VERSION = 758;
// TODO Add to settings
static PROXY = {
"address": "localhost",
"port": 30023
};
/**
* Create Minecraft instance and render it on a canvas
@@ -38,6 +47,9 @@ export default class Minecraft {
this.fps = 0;
let username = "Player" + Math.floor(Math.random() * 100);
this.profile = new GameProfile(username, UUID.randomUUID());
// Tick timer
this.timer = new Timer(20);
@@ -114,7 +126,7 @@ export default class Minecraft {
// Create player
this.player = new PlayerEntity(this, this.world);
this.player.username = "Player" + Math.floor(Math.random() * 100);
this.player.username = this.profile.username;
this.world.addEntity(this.player);
// Load spawn chunks and respawn player
@@ -200,6 +212,11 @@ export default class Minecraft {
return;
}
// Fallback screen
if (screen === null && !this.isInGame()) {
screen = new GuiMainMenu();
}
// Close previous screen
if (this.currentScreen !== null) {
this.currentScreen.onClose();
@@ -0,0 +1,73 @@
import GuiScreen from "../GuiScreen.js";
import GuiButton from "../widgets/GuiButton.js";
import NetworkManager from "../../network/NetworkManager.js";
import HandshakePacket from "../../network/packet/handshake/client/HandshakePacket.js";
import ProtocolState from "../../network/ProtocolState.js";
import NetworkLoginHandler from "../../network/handler/NetworkLoginHandler.js";
import Minecraft from "../../Minecraft.js";
import LoginStartPacket from "../../network/packet/login/client/LoginStartPacket.js";
export default class GuiConnecting extends GuiScreen {
constructor(previousScreen, address) {
super();
this.previousScreen = previousScreen;
this.connecting = false;
this.networkManager = null;
// Split up address into host and port
if (address.includes(":")) {
let parts = address.split(":");
this.address = parts[0];
this.port = parseInt(parts[1]);
} else {
this.address = address;
this.port = 25565;
}
}
connect(address, port) {
this.networkManager = new NetworkManager(this.minecraft);
this.networkManager.setNetworkHandler(new NetworkLoginHandler(this.networkManager));
this.networkManager.connect(address, port, Minecraft.PROXY);
// Send Minecraft protocol handshake
this.networkManager.sendPacket(new HandshakePacket(Minecraft.PROTOCOL_VERSION, ProtocolState.LOGIN));
this.networkManager.sendPacket(new LoginStartPacket(this.minecraft.profile.username));
}
init() {
super.init();
let y = this.height / 2 - 50;
this.buttonList.push(new GuiButton("Cancel", this.width / 2 - 100, y + 130, 200, 20, () => {
this.minecraft.displayScreen(this.previousScreen);
}));
// Connect on first initialization
if (!this.connecting) {
this.connecting = true;
this.connect(this.address, this.port);
}
}
drawScreen(stack, mouseX, mouseY, partialTicks) {
// Render dirt background
this.drawBackground(stack, this.textureBackground, this.width, this.height);
// Render title
this.drawCenteredString(stack, "Connecting to server...", this.width / 2, this.height / 2 - 20);
super.drawScreen(stack, mouseX, mouseY, partialTicks);
}
onClose() {
super.onClose();
if (this.networkManager !== null) {
this.networkManager.close();
}
}
}
@@ -0,0 +1,52 @@
import GuiScreen from "../GuiScreen.js";
import GuiButton from "../widgets/GuiButton.js";
import GuiTextField from "../widgets/GuiTextField.js";
import GuiConnecting from "./GuiConnecting.js";
export default class GuiDirectConnect extends GuiScreen {
constructor(previousScreen) {
super();
this.previousScreen = previousScreen;
}
init() {
super.init();
let y = this.height / 2 - 50;
this.fieldAddress = new GuiTextField(this.width / 2 - 100, y + 30, 200, 20)
this.fieldAddress.maxLength = 30;
this.fieldAddress.text = this.minecraft.settings.serverAddress;
this.buttonList.push(this.fieldAddress);
this.buttonList.push(new GuiButton("Connect", this.width / 2 - 155, y + 110, 150, 20, () => {
this.minecraft.displayScreen(new GuiConnecting(this, this.fieldAddress.text));
}));
this.buttonList.push(new GuiButton("Cancel", this.width / 2 + 5, y + 110, 150, 20, () => {
this.minecraft.displayScreen(this.previousScreen);
}));
}
drawScreen(stack, mouseX, mouseY, partialTicks) {
// Background
this.drawDefaultBackground(stack);
// Title
this.drawCenteredString(stack, "Connect to a server", this.width / 2, 50);
let y = this.height / 2 - 50;
// Seed
this.drawString(stack, "Server Address", this.width / 2 - 100, y + 17, -6250336);
super.drawScreen(stack, mouseX, mouseY, partialTicks);
}
onClose() {
this.minecraft.settings.serverAddress = this.fieldAddress.text;
this.minecraft.settings.save();
}
}
@@ -0,0 +1,38 @@
import GuiScreen from "../GuiScreen.js";
import GuiButton from "../widgets/GuiButton.js";
import FontRenderer from "../../render/gui/FontRenderer.js";
export default class GuiDisconnected extends GuiScreen {
constructor(message) {
super();
this.message = message;
}
init() {
super.init();
this.multilineMessage =this.minecraft.fontRenderer.listFormattedStringToWidth(this.message, this.width - 50);
let y = this.height / 2 - 50;
this.buttonList.push(new GuiButton("Done", this.width / 2 - 100, y + 130, 200, 20, () => {
this.minecraft.displayScreen(null);
}));
}
drawScreen(stack, mouseX, mouseY, partialTicks) {
// Render dirt background
this.drawBackground(stack, this.textureBackground, this.width, this.height);
// Render title
this.drawCenteredString(stack, "Disconnected from server:", this.width / 2, this.height / 2 - 20, 0x80FFFFFF);
for(let i = 0; i < this.multilineMessage.length; i++) {
this.drawCenteredString(stack, this.multilineMessage[i], this.width / 2, this.height / 2 + i * FontRenderer.FONT_HEIGHT);
}
super.drawScreen(stack, mouseX, mouseY, partialTicks);
}
}
@@ -6,6 +6,7 @@ import {BackSide} from "../../../../../../../libraries/three.module.js";
import MathHelper from "../../../util/MathHelper.js";
import Minecraft from "../../Minecraft.js";
import GuiCreateWorld from "./GuiCreateWorld.js";
import GuiDirectConnect from "./GuiDirectConnect.js";
export default class GuiMainMenu extends GuiScreen {
@@ -26,8 +27,8 @@ export default class GuiMainMenu extends GuiScreen {
this.minecraft.displayScreen(new GuiCreateWorld(this));
}));
this.buttonList.push(new GuiButton("Multiplayer", this.width / 2 - 100, y + 24, 200, 20, () => {
}).setEnabled(false));
this.minecraft.displayScreen(new GuiDirectConnect(this));
}));
this.buttonList.push(new GuiButton("Minecraft Realms", this.width / 2 - 100, y + 24 * 2, 200, 20, () => {
}).setEnabled(false));
@@ -0,0 +1,169 @@
import ByteBuf from "./util/ByteBuf.js";
import PacketRegistry from "./PacketRegistry.js";
import ProtocolState from "./ProtocolState.js";
import {aesjs} from "../../../../../../libraries/aes.js";
export default class NetworkManager {
constructor(minecraft) {
this.minecraft = minecraft;
this.socket = null;
this.connected = false;
this.networkHandler = null;
this.registry = new PacketRegistry();
this.protocolState = ProtocolState.HANDSHAKE;
this.readBuffer = new ByteBuf();
this.expectedLength = -1;
this.queue = [];
}
setNetworkHandler(networkHandler) {
this.networkHandler = networkHandler;
}
connect(address, port, proxy) {
this.socket = new WebSocket("ws://" + proxy.address + ":" + proxy.port);
this.socket.binaryType = "arraybuffer";
this.socket.onopen = e => this._onOpen(e);
this.socket.onclose = e => this._onClose(e);
this.socket.onmessage = e => this._onMessage(e);
this.socket.onerror = e => this._onError(e);
this.address = address;
this.port = port;
}
_onOpen() {
this.connected = true;
// Send proxy handshake
let object = {
"address": this.address,
"port": this.port,
};
this.socket.send(JSON.stringify(object));
// Handle connect event
this.networkHandler.onConnect();
// Flush packet queue
this.flushPacketQueue();
}
sendPacket(packet) {
if (this.connected) {
this._sendPacketImmediately(packet);
} else {
this.queue.push(packet);
}
}
_sendPacketImmediately(packet) {
// Switch packet state
let packetState = this.registry.getPacketState(packet);
if (packetState !== this.protocolState) {
if (packetState === null) {
console.error("[Network] Tried to send unknown packet: " + packet);
return;
}
console.log("[Network] Switching protocol state from " + ProtocolState.getName(this.protocolState) + " to " + ProtocolState.getName(packetState));
this.protocolState = packetState;
}
// Write packet to buffer
let buffer = new ByteBuf();
buffer.writeByte(this.registry.getClientBoundPacketId(this.protocolState, packet));
packet.write(buffer);
// Write chunk header
let array = buffer.getArray();
let wrapper = new ByteBuf();
wrapper.writeVarInt(array.length);
wrapper.write(array);
let chunk = wrapper.getArray().buffer;
// Decrypt chunk
if (this.isEncrypted) {
chunk = this.encryption.encrypt(new Uint8Array(chunk));
}
// Send chunk
this.socket.send(chunk);
console.log("[Network] [OUT] " + packet.constructor.name);
}
_onMessage(event) {
let chunk = new Int8Array(event.data);
// Decrypt chunk
if (this.isEncrypted) {
chunk = this.encryption.decrypt(new Uint8Array(event.data));
}
// Read packet header
if (this.expectedLength === -1) {
let buf = new ByteBuf(chunk);
this.expectedLength = buf.readVarInt(); // Read packet length
this.readBuffer.setPosition(0);
chunk = chunk.slice(buf.getPosition());
}
// Fill packet content
this.readBuffer.write(chunk);
// Handle packet
if (this.readBuffer.getPosition() >= this.expectedLength) {
this.expectedLength = -1;
this.readBuffer.setPosition(0);
this.onPacketBufferReceived(this.readBuffer);
}
}
onPacketBufferReceived(buffer) {
let packetId = buffer.readByte(); // Read packet id
let clazz = this.registry.getServerBoundById(this.protocolState, packetId);
if (clazz === null) {
console.log("[Network] Unknown packet id: " + packetId);
return;
}
let packet = new clazz;
console.log("[Network] [IN] " + packet.constructor.name);
packet.read(buffer, buffer.length);
packet.handle(this.networkHandler);
}
_onError(event) {
}
_onClose(event) {
if (this.connected) {
this.networkHandler.onDisconnect("Disconnected from server");
}
this.connected = false;
}
close() {
this.connected = false;
this.socket.close();
}
flushPacketQueue() {
this.queue.forEach(packet => this.sendPacket(packet));
this.queue = [];
}
enableEncryption(secretKey) {
this.isEncrypted = true;
this.encryption = new aesjs.ModeOfOperation.cfb(secretKey, secretKey, 1);
}
}
@@ -0,0 +1,19 @@
export default class Packet {
constructor() {
}
write(buffer) {
}
read(buffer) {
}
handle(packetHandler) {
}
}
@@ -0,0 +1,18 @@
export default class PacketHandler {
onConnect() {
}
handleStatusResponse(packet) {
}
handleEncryptionRequest(packet) {
}
onDisconnect() {
}
}
@@ -0,0 +1,95 @@
import ProtocolState from "./ProtocolState.js";
import HandshakePacket from "./packet/handshake/client/HandshakePacket.js";
import StatusQueryPacket from "./packet/status/client/StatusQueryPacket.js";
import LoginStartPacket from "./packet/login/client/LoginStartPacket.js";
import StatusResponsePacket from "./packet/status/server/StatusResponsePacket.js";
import EncryptionRequestPacket from "./packet/login/server/EncryptionRequestPacket.js";
import EncryptionResponsePacket from "./packet/login/client/EncryptionResponsePacket.js";
import LoginDisconnectPacket from "./packet/login/server/LoginDisconnectPacket.js";
export default class PacketRegistry {
constructor() {
this.packetsClient = [];
this.packetsServer = [];
// Register handshake
this.registerClient(ProtocolState.HANDSHAKE, 0x00, HandshakePacket);
// Register server status
this.registerClient(ProtocolState.STATUS, 0x00, StatusQueryPacket);
this.registerServer(ProtocolState.STATUS, 0x00, StatusResponsePacket);
// Register login
this.registerServer(ProtocolState.LOGIN, 0x00, LoginDisconnectPacket);
this.registerServer(ProtocolState.LOGIN, 0x01, EncryptionRequestPacket);
this.registerClient(ProtocolState.LOGIN, 0x00, LoginStartPacket);
this.registerClient(ProtocolState.LOGIN, 0x01, EncryptionResponsePacket);
}
registerClient(state, id, packet) {
this._register(this.packetsClient, state, id, packet);
}
registerServer(state, id, packet) {
this._register(this.packetsServer, state, id, packet);
}
_register(registry, state, id, packet) {
if (typeof registry[state] === "undefined") {
registry[state] = [];
}
registry[state][id] = packet;
}
getServerBoundById(state, id) {
if (typeof this.packetsServer[state][id] === "undefined") {
return null;
}
return this.packetsServer[state][id];
}
getClientBoundById(state, id) {
if (typeof this.packetsClient[state][id] === "undefined") {
return null;
}
return this.packetsClient[state][id];
}
getClientBoundPacketId(state, packet) {
for (let id in this.packetsClient[state]) {
if (this.packetsClient[state][id] === packet.constructor) {
return id;
}
}
return null;
}
getServerBoundPacketId(state, packet) {
for (let id in this.packetsServer[state]) {
if (this.packetsServer[state][id] === packet.constructor) {
return id;
}
}
return null;
}
getPacketState(packet) {
for (const [state, value] of Object.entries(this.packetsClient)) {
for (let id in value) {
if (value[id] === packet.constructor) {
return parseInt(state);
}
}
}
for (const [state, value] of Object.entries(this.packetsServer)) {
for (let id in value) {
if (value[id] === packet.constructor) {
return parseInt(state);
}
}
}
return null;
}
}
@@ -0,0 +1,21 @@
export default class ProtocolState {
static HANDSHAKE = -1;
static PLAY = 0;
static STATUS = 1;
static LOGIN = 2;
static getName(state) {
switch (state) {
case ProtocolState.HANDSHAKE:
return "HANDSHAKE";
case ProtocolState.LOGIN:
return "LOGIN";
case ProtocolState.PLAY:
return "PLAY";
case ProtocolState.STATUS:
return "STATUS";
default:
return "UNKNOWN";
}
}
}
@@ -0,0 +1,31 @@
import PacketHandler from "../PacketHandler.js";
import EncryptionResponsePacket from "../packet/login/client/EncryptionResponsePacket.js";
import CryptManager from "../util/CryptManager.js";
import GuiDisconnected from "../../gui/screens/GuiDisconnected.js";
export default class NetworkLoginHandler extends PacketHandler {
constructor(networkManager) {
super();
this.networkManager = networkManager;
}
handleEncryptionRequest(packet) {
let secretKey = CryptManager.createNewSharedKey();
this.networkManager.sendPacket(new EncryptionResponsePacket(secretKey, packet.publicKey, packet.verifyToken));
// Enable encryption
this.networkManager.enableEncryption(secretKey);
}
handleLoginDisconnect(packet) {
console.log("[Network] Disconnected from server: " + packet.message);
this.networkManager.minecraft.displayScreen(new GuiDisconnected(packet.message));
}
onDisconnect() {
}
}
@@ -0,0 +1,22 @@
import Packet from "../../../Packet.js";
export default class HandshakePacket extends Packet {
constructor(version, nextState) {
super();
this.version = version;
this.nextState = nextState;
}
write(buffer) {
buffer.writeVarInt(this.version); // Protocol version
buffer.writeString("localhost"); // Server address
buffer.writeShort(25565); // Server port
buffer.writeVarInt(this.nextState); // Next state
}
read(buffer) {
}
}
@@ -0,0 +1,22 @@
import Packet from "../../../Packet.js";
import CryptManager from "../../../util/CryptManager.js";
export default class EncryptionResponsePacket extends Packet {
constructor(secretKey, publicKey, verifyToken) {
super();
this.secretKeyEncrypted = CryptManager.encryptRSA(publicKey, secretKey);
this.verifyTokenEncrypted = CryptManager.encryptRSA(publicKey, verifyToken);
}
write(buffer) {
buffer.writeByteArray(this.secretKeyEncrypted);
buffer.writeByteArray(this.verifyTokenEncrypted);
}
read(buffer) {
}
}
@@ -0,0 +1,18 @@
import Packet from "../../../Packet.js";
export default class LoginStartPacket extends Packet {
constructor(username) {
super();
this.username = username;
}
write(buffer) {
buffer.writeString(this.username);
}
read(buffer) {
this.username = buffer.readString();
}
}
@@ -0,0 +1,22 @@
import Packet from "../../../Packet.js";
export default class EncryptionRequestPacket extends Packet {
constructor() {
super();
}
write(buffer) {
}
read(buffer) {
this.serverId = buffer.readString();
this.publicKey = buffer.readByteArray();
this.verifyToken = buffer.readByteArray();
}
handle(handler) {
handler.handleEncryptionRequest(this);
}
}
@@ -0,0 +1,23 @@
import Packet from "../../../Packet.js";
import {format} from "../../../../../../../../../libraries/chat.js";
export default class LoginDisconnectPacket extends Packet {
constructor(message) {
super();
this.message = message;
}
write(buffer) {
buffer.writeString(this.message);
}
read(buffer) {
this.message = format(JSON.parse(buffer.readString()));
}
handle(handler) {
handler.handleLoginDisconnect(this);
}
}
@@ -0,0 +1,14 @@
import Packet from "../../../Packet.js";
export default class StatusQueryPacket extends Packet {
constructor() {
super();
}
write(buffer) {
}
read(buffer) {
}
}
@@ -0,0 +1,20 @@
import Packet from "../../../Packet.js";
export default class StatusResponsePacket extends Packet {
constructor() {
super();
}
write(buffer) {
}
read(buffer) {
this.object = JSON.parse(buffer.readString());
}
handle(packetHandler) {
packetHandler.handleStatusResponse(this)
}
}
@@ -0,0 +1,21 @@
import PacketHandler from "../PacketHandler.js";
import GuiDisconnected from "../../gui/screens/GuiDisconnected.js";
export default class NetworkStatusHandler extends PacketHandler {
constructor(minecraft, callback) {
super();
this.minecraft = minecraft;
this.callback = callback;
}
handleStatusResponse(packet) {
this.callback(packet.object);
}
onDisconnect() {
this.minecraft.displayScreen(new GuiDisconnected("NetworkManager lost"));
}
}
@@ -0,0 +1,26 @@
import NetworkManager from "../NetworkManager.js";
import NetworkStatusHandler from "./NetworkStatusHandler.js";
import Minecraft from "../../Minecraft.js";
import HandshakePacket from "../packet/handshake/client/HandshakePacket.js";
import ProtocolState from "../ProtocolState.js";
import StatusQueryPacket from "../packet/status/client/StatusQueryPacket.js";
export default class ServerPinger {
constructor(minecraft) {
this.minecraft = minecraft;
}
ping(address, port, callback) {
// Connect to server
this.connection = new NetworkManager(this.minecraft);
this.connection.setNetworkHandler(new NetworkStatusHandler(this.minecraft, callback));
this.connection.connect(address, port, Minecraft.PROXY);
// Request status
this.connection.sendPacket(new HandshakePacket(Minecraft.PROTOCOL_VERSION, ProtocolState.STATUS));
this.connection.sendPacket(new StatusQueryPacket());
}
}
@@ -0,0 +1,172 @@
export default class ByteBuf {
static SEGMENT_BITS = 0x7F;
static CONTINUE_BIT = 0x80;
constructor(array = new Uint8Array(0)) {
this.array = array;
this.pos = 0;
}
getPosition() {
return this.pos;
}
setPosition(pos) {
this.pos = pos;
}
length() {
return this.array.length;
}
getArray() {
return this.array;
}
readByte() {
return this.array[this.pos++];
}
readShort() {
return this.array[this.pos++] << 8 | this.array[this.pos++];
}
readInt() {
return this.array[this.pos++] << 24
| this.array[this.pos++] << 16
| this.array[this.pos++] << 8
| this.array[this.pos++];
}
readLong() {
return this.array[this.pos++] << 56
| this.array[this.pos++] << 48
| this.array[this.pos++] << 40
| this.array[this.pos++] << 32
| this.array[this.pos++] << 24
| this.array[this.pos++] << 16
| this.array[this.pos++] << 8
| this.array[this.pos++];
}
readFloat() {
return this.readInt() / (1 << 24);
}
readDouble() {
return this.readLong() / (1 << 53);
}
readString() {
let len = this.readVarInt();
let array = new Uint8Array(len);
this.read(array, len);
return new TextDecoder().decode(array);
}
writeByte(value) {
this.extendIfNeeded(1);
this.array[this.pos++] = value;
}
writeShort(value) {
this.extendIfNeeded(2);
this.array[this.pos++] = value >> 8;
this.array[this.pos++] = value;
}
writeInt(value) {
this.extendIfNeeded(4);
this.array[this.pos++] = value >> 24;
this.array[this.pos++] = value >> 16;
this.array[this.pos++] = value >> 8;
this.array[this.pos++] = value;
}
writeLong(value) {
this.extendIfNeeded(8);
this.array[this.pos++] = value >> 56;
this.array[this.pos++] = value >> 48;
this.array[this.pos++] = value >> 40;
this.array[this.pos++] = value >> 32;
this.array[this.pos++] = value >> 24;
this.array[this.pos++] = value >> 16;
this.array[this.pos++] = value >> 8;
this.array[this.pos++] = value;
}
writeFloat(value) {
this.writeInt(value * (1 << 24));
}
writeDouble(value) {
this.writeLong(value * (1 << 53));
}
writeString(value) {
let array = new TextEncoder().encode(value);
this.writeVarInt(array.length);
this.write(array);
}
writeVarInt(value) {
while (true) {
if ((value & ~ByteBuf.SEGMENT_BITS) === 0) {
this.writeByte(value);
return;
}
this.writeByte((value & ByteBuf.SEGMENT_BITS) | ByteBuf.CONTINUE_BIT);
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
}
}
write(array) {
this.extendIfNeeded(array.length);
for (let i = 0; i < array.length; i++) {
this.array[this.pos++] = array[i];
}
}
extendIfNeeded(bytes) {
if (this.pos + bytes > this.array.length) {
let newArray = new Uint8Array(this.array.length + bytes);
newArray.set(this.array);
this.array = newArray;
}
}
read(array, length) {
for (let i = 0; i < length; i++) {
array[i] = this.array[this.pos++];
}
}
readVarInt() {
let result = 0;
let shift = 0;
while (true) {
let b = this.readByte();
if ((b & ByteBuf.CONTINUE_BIT) === 0) {
return result | (b << shift);
}
result |= (b & ByteBuf.SEGMENT_BITS) << shift;
shift += 7;
}
}
readByteArray() {
let length = this.readVarInt();
let array = [];
this.read(array, length);
return new Int8Array(array);
}
writeByteArray(array) {
this.writeVarInt(array.length);
this.write(array);
}
}
@@ -0,0 +1,56 @@
import Random from "../../../util/Random.js";
import ByteBuf from "./ByteBuf.js";
import {bigIntToBytes, bytesToBigInt, modPow} from "../../../../../../../libraries/modpow.js";
import {parseAsn1} from "../../../../../../../libraries/asn1.js";
export default class CryptManager {
static createNewSharedKey() {
let key = new Uint8Array(16);
window.crypto.getRandomValues(key);
return key;
}
static encryptRSA(publicKey, data) {
// Parse asn1 public key
let asn1 = parseAsn1(new Uint8Array(publicKey))
// Extract n an e of the public key
let n = bytesToBigInt(asn1.children[1].children[0].children[0].value);
let e = bytesToBigInt(asn1.children[1].children[0].children[1].value);
// Check length of public key
let length = (n.toString(2).length + 7) >> 3;
if (length < data.length + 11) {
throw new Error("Data is too long to encrypt");
}
let buffer = new ByteBuf(new Uint8Array(data).reverse());
buffer.setPosition(buffer.length());
// Add padding
buffer.writeByte(0x0);
let random = new Random();
let x = [];
while (buffer.length() < length - 2) {
x[0] = 0;
while (x[0] === 0) random.nextBytes(x, x.length);
buffer.writeByte(x[0]);
}
buffer.writeByte(0x2);
buffer.writeByte(0x0);
// Reverse data
let reversed = buffer.getArray().reverse();
// Convert to bigint
let bigInt = bytesToBigInt(reversed);
// Encrypt
bigInt = modPow(bigInt, e, n);
// Convert to bytes
return bigIntToBytes(bigInt);
}
}
@@ -3,11 +3,14 @@ import MathHelper from "../../../util/MathHelper.js";
export default class FontRenderer {
static FONT_HEIGHT = 9;
static BITMAP_SIZE = 16;
static FIELD_SIZE = 8;
static COLOR_CODE_INDEX_LOOKUP = "0123456789abcdef";
static CHAR_INDEX_LOOKUP = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
static COLOR_PREFIX = '\u00a7';
constructor(minecraft) {
this.charWidths = [];
@@ -72,7 +75,7 @@ export default class FontRenderer {
let code = character.charCodeAt(0);
// Handle color codes if character is &
if (character === '&' && i !== string.length - 1) {
if (character === FontRenderer.COLOR_PREFIX && i !== string.length - 1) {
// Get the next character
let nextCharacter = string[i + 1];
@@ -125,7 +128,7 @@ export default class FontRenderer {
for (let i = 0; i < string.length; i++) {
// Check for color code
if (string[i] === '&') {
if (string[i] === FontRenderer.COLOR_PREFIX) {
// Skip the next character
i++;
} else {
@@ -173,4 +176,8 @@ export default class FontRenderer {
+ " saturate(" + saturate3 + "%)";
}
}
listFormattedStringToWidth(text, wrapWidth) {
return text.split("\n"); // TODO Implement wrap logic
}
}
+8
View File
@@ -0,0 +1,8 @@
export default class GameProfile {
constructor(username, uuid) {
this.username = username;
this.uuid = uuid;
}
}
+13
View File
@@ -15,6 +15,19 @@ export default class Random {
this.setSeed(seed);
}
nextBytes(bytes, length) {
let i = 0;
while (i < length) {
let rnd = this.nextInt();
let n = Math.min(length - i, 32 / 8);
while (n-- > 0) {
bytes[i++] = rnd & 0xff;
rnd >>= 8;
}
}
}
nextFloat() {
return this.next(24).toNumber() / (1 << 24);
}
+44
View File
@@ -0,0 +1,44 @@
import Long from "../../../../../libraries/long.js";
import Random from "./Random.js";
export default class UUID {
constructor(data) {
let msb = Long.fromNumber(0);
let lsb = Long.fromNumber(0);
for (let i = 0; i < 8; i++) {
msb = msb.shiftLeft(8).or(Long.fromInt(data[i] & 0xff));
}
for (let i = 8; i < 16; i++) {
lsb = lsb.shiftLeft(8).or(Long.fromInt(data[i] & 0xff));
}
this.mostSigBits = msb;
this.leastSigBits = lsb;
}
toString() {
return (UUID.digits(this.mostSigBits.shiftRightUnsigned(32), 8) + "-" +
UUID.digits(this.mostSigBits.shiftRightUnsigned(16), 4) + "-" +
UUID.digits(this.mostSigBits, 4) + "-" +
UUID.digits(this.leastSigBits.shiftRightUnsigned(48), 4) + "-" +
UUID.digits(this.leastSigBits, 12));
}
static randomUUID() {
let random = new Random();
let randomBytes = [];
random.nextBytes(randomBytes, 16);
randomBytes[6] &= 0x0f; /* clear version */
randomBytes[6] |= 0x40; /* set to version 4 */
randomBytes[8] &= 0x3f; /* clear variant */
randomBytes[8] |= 0x80; /* set to IETF variant */
return new UUID(randomBytes);
}
static digits(val, digits) {
let hi = Long.fromInt(1).shiftLeft(Long.fromInt(digits).multiply(4));
let num = hi.or(val.and(hi.add(Long.fromInt(-1))));
return num.toString(16).substr(1);
}
}