"use strict";
/*
    This is a rewrite of ML's networking.
    The original networking was a mess and the oldest code in the codebase.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkEngine2_Client = exports.NetworkEngine2_Server = void 0;
const IModLoaderAPI_1 = require("modloader64_api/IModLoaderAPI");
const modloader64_1 = require("../modloader64");
const mluuid_1 = require("../uuid/mluuid");
const FakeNetworkPlayer_1 = require("./FakeNetworkPlayer");
const dgram_1 = __importDefault(require("dgram"));
const NetworkHandler_1 = require("modloader64_api/NetworkHandler");
const LobbyObjects_1 = require("./LobbyObjects");
const EventHandler_1 = require("modloader64_api/EventHandler");
const ModLoaderDefaultImpls_1 = require("modloader64_api/ModLoaderDefaultImpls");
const NetworkingPackets_1 = require("./NetworkingPackets");
const NetworkingEventBus_1 = require("./NetworkingEventBus");
const dns = __importStar(require("dns"));
const crypto_1 = __importDefault(require("crypto"));
const path_1 = __importDefault(require("path"));
const getAllFiles_1 = require("../getAllFiles");
const zlib_1 = __importDefault(require("zlib"));
const fs_1 = __importDefault(require("fs"));
const fakePlayer = new FakeNetworkPlayer_1.FakeNetworkPlayer();
/**
 * Storage for event handler stuff.
*/
class NetworkEngine2_ServerEventComponents {
    static NetworkSendBusServer_Targets = [];
    static NetworkBusServer_Targets = [];
    static NetworkQueryBusServer_Targets = [];
    static NetworkingEventBusServer_Targets = [];
    static protocolServer_Targets = [];
    static ServerHandlers = [];
    static serverDisconnectHandler;
    static ServerEmitters = [];
}
class NetworkEngine2_ClientEventComponents {
    //Client
    static NetworkSendBusClient_Targets = [];
    static NetworkBusClient_Targets = [];
    static NetworkQueryBusClient_Targets = [];
    static NetworkingEventBusClient_Targets = [];
    static protocolClient_Targets = [];
    static ClientHandlers = [];
    static clientDisconnectHandler;
    static ClientEmitters = [];
}
function ServerEventHandler(type, key) {
    return function (target, propertyKey, descriptor) {
        if (type === "NetworkBusServer" /* NetworkEngine2_EventType.NetworkBusServer */) {
            NetworkEngine2_ServerEventComponents.NetworkBusServer_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkQueryBusServer" /* NetworkEngine2_EventType.NetworkQueryBusServer */) {
            NetworkEngine2_ServerEventComponents.NetworkQueryBusServer_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkSendBusServer" /* NetworkEngine2_EventType.NetworkSendBusServer */) {
            NetworkEngine2_ServerEventComponents.NetworkSendBusServer_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkingEventBusServer" /* NetworkEngine2_EventType.NetworkingEventBusServer */) {
            NetworkEngine2_ServerEventComponents.NetworkingEventBusServer_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "protocolServer" /* NetworkEngine2_EventType.protocolServer */) {
            NetworkEngine2_ServerEventComponents.protocolServer_Targets.push({ key, prop: propertyKey });
        }
    };
}
function ClientEventHandler(type, key) {
    return function (target, propertyKey, descriptor) {
        if (type === "NetworkBus" /* NetworkEngine2_EventType.NetworkBus */) {
            NetworkEngine2_ClientEventComponents.NetworkBusClient_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkQueryBus" /* NetworkEngine2_EventType.NetworkQueryBus */) {
            NetworkEngine2_ClientEventComponents.NetworkQueryBusClient_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkSendBus" /* NetworkEngine2_EventType.NetworkSendBus */) {
            NetworkEngine2_ClientEventComponents.NetworkSendBusClient_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "NetworkingEventBusClient" /* NetworkEngine2_EventType.NetworkingEventBusClient */) {
            NetworkEngine2_ClientEventComponents.NetworkingEventBusClient_Targets.push({ key, prop: propertyKey });
        }
        else if (type === "protocolClient" /* NetworkEngine2_EventType.protocolClient */) {
            NetworkEngine2_ClientEventComponents.protocolClient_Targets.push({ key, prop: propertyKey });
        }
    };
}
class NetworkEngine2_Server {
    io;
    udpServer = dgram_1.default.createSocket('udp4');
    udpPort = -1;
    logger;
    masterConfig;
    config;
    modLoaderconfig;
    lobbyStorage = {};
    plugins = {};
    core = "";
    lobby_names = [];
    protocol = new NetworkHandler_1.NetworkEventBus();
    constructor(logger, config) {
        // Configuration.
        this.logger = logger;
        this.masterConfig = config;
        this.config = config.registerConfigCategory('NetworkEngine.Server');
        config.setData('NetworkEngine.Server', 'port', 8082);
        config.setData('NetworkEngine.Server', 'udpPort', 8082);
        config.setData('NetworkEngine.Server', 'patchSizeLimitMB', 10);
        config.setData('NetworkEngine.Server', 'noHashChecks', false);
        this.modLoaderconfig = this.masterConfig.registerConfigCategory('ModLoader64');
        NetworkEngine2_ServerEventComponents.serverDisconnectHandler = this.onSocketDisconnect.bind(this);
        modloader64_1.internal_event_bus.on('PLUGIN_LOADED', (args) => {
            let p = args[0].meta;
            this.plugins[p.name] = { version: p.version, hash: args[0].hash };
        });
        modloader64_1.internal_event_bus.on('CORE_LOADED', (args) => {
            this.core = args[0].name;
            if (config.registerConfigCategory("NetworkEngine.Client").isSinglePlayer) {
                this.config.port = parseInt("8082");
                config.registerConfigCategory('ModLoader64').isServer = true;
            }
        });
        NetworkingEventBus_1.NetworkingEventBusServer.on('getLobbyStorage', (evt) => {
            evt.obj = this.getLobbyStorage(evt.lobbyName, evt.plugin);
        });
        NetworkingEventBus_1.NetworkingEventBusServer.on('createLobbyStorage', (evt) => {
            this.createLobbyStorage(evt.lobbyName, evt.plugin, evt.obj);
        });
        NetworkingEventBus_1.NetworkingEventBusServer.on('getAllLobbies', (evt) => {
            evt["r"] = this.getAllLobbies();
        });
        NetworkHandler_1.NetworkQueryBusServer.on('isPlayerConnected', (evt) => {
            evt.connected = this.io.sockets.sockets[evt.player.uuid] !== undefined;
        });
    }
    getLobbies() {
        return this.io.sockets.adapter.rooms;
    }
    doesLobbyExist(Lobby) {
        return this.getLobbies().hasOwnProperty(Lobby);
    }
    createLobbyStorage_internal(ld, owner) {
        if (this.getLobbyStorage_internal(ld.name) !== null) {
            delete this.lobbyStorage[ld.name];
        }
        this.lobbyStorage[ld.name] = {};
        this.lobbyStorage[ld.name]['ModLoader64'] = new LobbyObjects_1.LobbyStorage(ld, owner);
        return this.lobbyStorage[ld.name]['ModLoader64'];
    }
    getLobbyStorage_internal(lobbyName) {
        try {
            return this.lobbyStorage[lobbyName].ModLoader64;
        }
        catch (err) { }
        return null;
    }
    createLobbyStorage(lobbyName, plugin, obj) {
        let mathisore = this.lobbyStorage[lobbyName]['ModLoader64'];
        if (!mathisore.hasOwnProperty('data')) {
            mathisore['data'] = {};
        }
        mathisore.data[plugin.pluginName] = obj;
    }
    getAllLobbies() {
        return this.lobbyStorage;
    }
    getLobbyStorage(lobbyName, plugin) {
        try {
            return this.lobbyStorage[lobbyName].ModLoader64.data[plugin.pluginName];
        }
        catch (err) { }
        return null;
    }
    getPlayerRinfo(player) {
        if (typeof (player) === 'string') {
            if (this.io.sockets.sockets[player] === undefined)
                return undefined;
            if (this.io.sockets.sockets[player]["ModLoader64"] === undefined)
                return undefined;
            return this.io.sockets.sockets[player].ModLoader64["rinfo"];
        }
        else {
            if (this.io.sockets.sockets[player.uuid] === undefined)
                return undefined;
            if (this.io.sockets.sockets[player.uuid]["ModLoader64"] === undefined)
                return undefined;
            return this.io.sockets.sockets[player.uuid].ModLoader64["rinfo"];
        }
    }
    sendToTarget(target, internalChannel, packet) {
        this.io.to(target).emit(internalChannel, packet);
    }
    onSocketDisconnect(socket) {
        //@ts-ignore
        let ML = socket.ModLoader64;
        if (ML === undefined)
            return;
        if (this.getLobbyStorage_internal(ML.lobby) === null)
            return;
        let lobby = this.getLobbyStorage_internal(ML.lobby);
        if (lobby.owner === ML.player.uuid) {
            // Owner left. Reassign.
            let index = lobby.players.indexOf(ML.player.uuid);
            if (index > -1) {
                lobby.players.splice(index, 1);
            }
            if (lobby.players.length > 0) {
                lobby.owner = lobby.players[0];
            }
            else {
                lobby.owner.uuid = "null";
                lobby.owner.nickname = "null";
            }
        }
        EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_LEAVE, new EventHandler_1.EventServerLeft(ML.player, ML.lobby));
        this.sendToTarget(ML.lobby, 'left', ML.player);
        EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_OWNER_CHANGE, new EventHandler_1.EventOwnerChanged(lobby.config.name, lobby.owner));
        this.sendToTarget(ML.lobby, 'newOwner', lobby.owner);
    }
    onUDPPacket(buf, rinfo) {
        try {
            let msg = buf.toString();
            if (msg.charAt(0) !== '{') {
                return;
            }
            let data = JSON.parse(msg);
            Object.defineProperty(data, "rinfo", {
                value: rinfo,
                writable: false,
            });
            Object.freeze(data);
            if (this.getLobbyStorage_internal(data.lobby) === null) {
                return;
            }
            NetworkHandler_1.NetworkBusServer.emit(data.packet_id, data);
            NetworkHandler_1.NetworkChannelBusServer.emit(data.channel, data);
            if (data.forward) {
                let json = JSON.stringify(data);
                let players = Object.keys(this.io.sockets.adapter.rooms[data.lobby].sockets);
                for (let i = 0; i < players.length; i++) {
                    if (players[i] === data.player.uuid)
                        continue;
                    let rinfo = this.getPlayerRinfo(players[i]);
                    if (rinfo !== undefined) {
                        this.udpServer.send(json, rinfo.port, rinfo.address);
                    }
                    else {
                        this.sendToTarget(players[i], 'msg', data);
                    }
                }
            }
        }
        catch (err) {
            this.logger.error(err);
        }
    }
    setup() {
        // Setup the socket.
        const server = require('http').createServer();
        this.io = require('socket.io')(server);
        server.listen(this.config.port);
        this.logger.info(`NetworkEngine2_Server set up on port ${this.config.port}.`);
        // If we are only a server fake this event so the process doesn't hang.
        if (!this.modLoaderconfig.isClient) {
            modloader64_1.internal_event_bus.emit('onNetworkConnect', {
                me: fakePlayer,
                patch: Buffer.alloc(1),
            });
        }
        this.udpPort = this.config.udpPort;
        this.udpServer.bind(this.config.udpPort);
        NetworkEngine2_ServerEventComponents.NetworkBusServer_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkBusServer.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ServerEventComponents.NetworkQueryBusServer_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkQueryBusServer.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ServerEventComponents.NetworkSendBusServer_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkSendBusServer.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ServerEventComponents.protocolServer_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            this.protocol.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ServerEventComponents.NetworkingEventBusServer_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkingEventBus_1.NetworkingEventBusServer.on(entry.key, fn.bind(this));
        });
        let protocol = ['version', 'LobbyRequest', 'playerJoined_reply', 'msg', 'toSpecificPlayer', 'onCrash'];
        protocol.forEach((str) => {
            NetworkEngine2_ServerEventComponents.ServerEmitters.push({
                key: str, fn: (socket, data) => {
                    this.protocol.emit(str, socket, data);
                }
            });
        });
        this.io.on('connection', (socket) => {
            NetworkEngine2_ServerEventComponents.ServerHandlers.forEach((value) => {
                socket.on(value.key, value.fn);
            });
            NetworkEngine2_ServerEventComponents.ServerEmitters.forEach((value) => {
                socket.on(value.key, (data) => {
                    value.fn(socket, data);
                });
            });
            socket.on('disconnect', () => {
                NetworkEngine2_ServerEventComponents.serverDisconnectHandler(socket);
            });
            NetworkingEventBus_1.NetworkingEventBusServer.emit('connection', socket);
        });
        this.udpServer.on('error', (err) => {
            this.logger.error(`server error:\n${err.stack}`);
            this.udpServer.close();
        });
        this.udpServer.on('message', this.onUDPPacket.bind(this));
        this.udpServer.on('listening', () => {
            const address = this.udpServer.address();
            this.logger.info(`UDP socket listening ${address.address}:${address.port}`);
        });
        setInterval(() => {
            let rm = [];
            let lobbies = {};
            for (let i = 0; i < this.lobby_names.length; i++) {
                if (this.getLobbyStorage_internal(this.lobby_names[i]) !== null) {
                    if (this.getLobbies()[this.lobby_names[i]] !== undefined) {
                        lobbies[this.lobby_names[i]] = Object.keys(this.getLobbies()[this.lobby_names[i]]['sockets']).length;
                    }
                    else {
                        rm.push(this.lobby_names[i]);
                    }
                }
                else {
                    rm.push(this.lobby_names[i]);
                }
            }
            if (rm.length > 0) {
                for (let i = 0; i < rm.length; i++) {
                    let index = this.lobby_names.indexOf(rm[i]);
                    let name = this.lobby_names.splice(index, 1)[0].trim();
                    this.logger.info("lobby " + name + " terminated.");
                    EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_DESTROY, name);
                    delete this.lobbyStorage[name];
                }
            }
            fs_1.default.writeFile("./lobbies.json", JSON.stringify(lobbies), () => { });
            Object.keys(this.io.sockets.sockets).forEach((player) => {
                let rinfo = this.getPlayerRinfo(player);
                if (rinfo !== undefined) {
                    this.udpServer.send(JSON.stringify(new NetworkingPackets_1.PingPacket(player)), rinfo.port, rinfo.address);
                }
                else {
                    this.sendToTarget(player, 'msg', new NetworkingPackets_1.PingPacket(player).setType(0 /* SocketType.TCP */));
                }
            });
        }, 60 * 1000);
    }
    /**
     * Main handler for mod packet broadcasts.
     * @param data
     */
    onMsg(data) {
        if (data.player === undefined) {
            data.player = fakePlayer;
        }
        if (data.socketType === 0 /* SocketType.TCP */) {
            this.sendToTarget(data.lobby, 'msg', data);
        }
        else {
            let uuids = Object.keys(this.io.sockets.adapter.rooms[data.lobby]);
            for (let i = 0; i < uuids.length; i++) {
                let mlo = this.io.sockets.sockets[uuids[i]].ModLoader64;
                if (mlo.hasOwnProperty("rinfo")) {
                    let rinfo = mlo.rinfo;
                    this.udpServer.send(JSON.stringify(data), rinfo.port, rinfo.address);
                }
            }
        }
    }
    /**
     * Handler for player to player direct communications.
     * @param data
     */
    onToPlayer(data) {
        if (data.player === undefined) {
            data.player = fakePlayer;
        }
        if (data.packet.socketType === 0 /* SocketType.TCP */) {
            this.sendToTarget(data.player.uuid, 'msg', data.packet);
        }
        else {
            let rinfo = this.getPlayerRinfo(data.player);
            if (rinfo !== undefined) {
                this.udpServer.send(JSON.stringify(data.packet), rinfo.port, rinfo.address);
            }
            else {
                this.sendToTarget(data.player.uuid, 'msg', data.packet);
            }
        }
    }
    /**
     * UDP test packet handler
     * @param packet
     */
    onUDPTest(packet) {
        let reply = JSON.parse(JSON.stringify(packet));
        reply.player = fakePlayer;
        this.udpServer.send(JSON.stringify(reply), packet.rinfo.port, packet.rinfo.address);
    }
    /**
     * UDP handler for turning UDP mode on for a client.
     * @param packet
     */
    onUDPModeOnPacket(packet) {
        this.io.sockets.sockets[packet.player.uuid].ModLoader64["rinfo"] = packet.rinfo;
    }
    /**
     * UDP handler for turning UDP mode off for a client.
     * @param packet
     */
    onUDPModeOffPacket(packet) {
        delete this.io.sockets.sockets[packet.player.uuid].ModLoader64["rinfo"];
    }
    /**
     * Final response in the ping/pong chain. Gives client latency info.
     * @param packet
     */
    onPongPacket(packet) {
        let ping = packet.timestamp - packet.serverTime;
        let roundtrip = Date.now() - packet.serverTime;
        this.sendToTarget(packet.player.uuid, 'msg', new NetworkingPackets_1.LatencyInfoPacket(packet.lobby, ping, roundtrip));
    }
    /**
     * Gets the owner of a lobby.
     * @param evt
     * @returns
     */
    onGetOwner(evt) {
        let lobby = this.getLobbyStorage_internal(evt.lobby);
        if (lobby === null)
            return;
        evt.owner = lobby.owner;
    }
    /**
     * Sets the owner of a lobby.
     * @param evt
     * @returns
     */
    onPromoteOwner(evt) {
        let lobby = this.getLobbyStorage_internal(evt.lobby);
        if (lobby === null)
            return;
        lobby.owner = evt.owner;
        EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_OWNER_CHANGE, new EventHandler_1.EventOwnerChanged(lobby.config.name, lobby.owner));
        this.sendToTarget(lobby.config.name, 'newOwner', lobby.owner);
    }
    /**
     * Main connection handler.
     * @param socket
     */
    onConnection(socket) {
        this.logger.info('Client ' + socket.id + ' connected.');
        this.sendToTarget(socket.id, 'uuid', { uuid: socket.id });
    }
    /**
     * Check the client's version.
     * @param socket
     * @param packet
     */
    onVersionCheck(socket, packet) {
        let mismatch = false;
        try {
            Object.keys(this.plugins).forEach((name) => {
                if (packet.plugins.hasOwnProperty(name)) {
                    if (this.plugins[name].version !== packet.plugins[name].version) {
                        mismatch = true;
                    }
                    else {
                        this.logger.info('Plugin ' + name + ' version check passed.');
                        if (this.plugins[name].hash === packet.plugins[name].hash) {
                            this.logger.info('Plugin ' + name + ' hash check passed.');
                        }
                        else {
                            mismatch = true;
                        }
                    }
                }
            });
        }
        catch (err) {
            this.sendToTarget(socket.id, 'versionBad', {
                client: { ml: packet.ml, plugins: packet.plugins, core: packet.core },
                server: new NetworkingPackets_1.VersionPacket(global.ModLoader.version, this.plugins, this.core, packet.discord),
            });
        }
        if (this.core !== packet.core) {
            mismatch = true;
        }
        if (this.config.noHashChecks && mismatch)
            mismatch = false;
        let evt = new IModLoaderAPI_1.VersionCheckEvent(packet.ml, packet.plugins);
        evt.canceled = mismatch;
        EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_VERSION_CHECK, evt);
        mismatch = evt.canceled;
        if (global.ModLoader.version === packet.ml && mismatch === false) {
            this.sendToTarget(socket.id, 'versionGood', {
                client: packet.ml,
                server: new NetworkingPackets_1.VersionPacket(global.ModLoader.version, this.plugins, this.core, packet.discord),
                patchLimit: this.config.patchSizeLimitMB
            });
            socket.join("__GLOBAL__");
        }
        else {
            this.sendToTarget(socket.id, 'versionBad', {
                client: { ml: packet.ml, plugins: packet.plugins, core: packet.core },
                server: new NetworkingPackets_1.VersionPacket(global.ModLoader.version, this.plugins, this.core, packet.discord),
            });
            setTimeout(function () {
                try {
                    socket.disconnect();
                }
                catch (err) { }
            }, 1000);
        }
    }
    /**
     * Player is requesting to join a lobby.
     * @param socket
     * @param lj
     */
    onLobbyRequest(socket, lj) {
        if (this.doesLobbyExist(lj.lobbyData.name) && this.getLobbyStorage_internal(lj.lobbyData.name) !== null) {
            // Lobby already exists.
            let storage = this.getLobbyStorage_internal(lj.lobbyData.name);
            if (storage.config.key === lj.lobbyData.key) {
                socket.join(storage.config.name);
                EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_JOIN, new EventHandler_1.EventServerJoined(lj.player, lj.lobbyData.name));
                //@ts-ignore
                socket['ModLoader64'] = {
                    lobby: storage.config.name,
                    player: lj.player
                };
                storage.players.push(lj.player);
                if (storage.owner.uuid === "null") {
                    storage.owner = lj.player;
                }
                this.sendToTarget(socket.id, 'LobbyReady', { storage: storage.config, udp: this.udpPort, owner: storage.owner });
                this.sendToTarget(lj.lobbyData.name, 'playerJoined', lj.player);
            }
            else {
                this.sendToTarget(socket.id, 'LobbyDenied_BadPassword', lj);
            }
        }
        else {
            // Lobby does not exist.
            this.logger.info('Creating lobby ' + lj.lobbyData.name + '.');
            socket.join(lj.lobbyData.name);
            let storage = this.createLobbyStorage_internal(lj.lobbyData, lj.player);
            EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_CREATE, lj.lobbyData.name);
            EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_DATA, storage.config);
            EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_JOIN, new EventHandler_1.EventServerJoined(lj.player, lj.lobbyData.name));
            //@ts-ignore
            socket['ModLoader64'] = {
                lobby: storage.config.name,
                player: lj.player,
            };
            this.sendToTarget(socket.id, 'LobbyReady', { storage: storage.config, udp: this.udpPort, owner: storage.owner });
            this.sendToTarget(lj.lobbyData.name, 'playerJoined', lj.player);
            EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_LOBBY_OWNER_CHANGE, new EventHandler_1.EventOwnerChanged(lj.lobbyData.name, storage.owner));
            this.lobby_names.push(lj.lobbyData.name);
        }
    }
    /**
     * Player replying to a join message.
     * @param socket
     * @param data
     */
    onJoinReply(socket, data) {
        this.sendToTarget(data.dest.uuid, 'playerJoined_bounce', data.player);
    }
    /**
     * Main message handler for sockets.
     * @param socket
     * @param data
     */
    onSocketMessage(socket, data) {
        Object.freeze(data);
        try {
            NetworkHandler_1.NetworkBusServer.emit(data.packet_id, data);
            NetworkHandler_1.NetworkChannelBusServer.emit(data.channel, data);
            if (data.forward) {
                socket.to(data.lobby).emit('msg', data);
            }
        }
        catch (err) {
            this.logger.error(err.stack);
            this.logger.error(JSON.stringify(data));
        }
    }
    /**
     * Send message to specific player.
     * @param socket
     * @param data
     */
    toSpecificPlayer(socket, data) {
        this.sendToTarget(data.player.uuid, 'msg', data.packet);
    }
    /**
     * Unused handler.
     * @param socket
     * @param data
     */
    onCrash(socket, data) {
    }
}
__decorate([
    ServerEventHandler("NetworkSendBusServer" /* NetworkEngine2_EventType.NetworkSendBusServer */, 'msg')
], NetworkEngine2_Server.prototype, "onMsg", null);
__decorate([
    ServerEventHandler("NetworkSendBusServer" /* NetworkEngine2_EventType.NetworkSendBusServer */, 'toPlayer')
], NetworkEngine2_Server.prototype, "onToPlayer", null);
__decorate([
    ServerEventHandler("NetworkBusServer" /* NetworkEngine2_EventType.NetworkBusServer */, 'UDPTestPacket')
], NetworkEngine2_Server.prototype, "onUDPTest", null);
__decorate([
    ServerEventHandler("NetworkBusServer" /* NetworkEngine2_EventType.NetworkBusServer */, 'UDPModeOnPacket')
], NetworkEngine2_Server.prototype, "onUDPModeOnPacket", null);
__decorate([
    ServerEventHandler("NetworkBusServer" /* NetworkEngine2_EventType.NetworkBusServer */, 'UDPModeOffPacket')
], NetworkEngine2_Server.prototype, "onUDPModeOffPacket", null);
__decorate([
    ServerEventHandler("NetworkBusServer" /* NetworkEngine2_EventType.NetworkBusServer */, 'PongPacket')
], NetworkEngine2_Server.prototype, "onPongPacket", null);
__decorate([
    ServerEventHandler("NetworkQueryBusServer" /* NetworkEngine2_EventType.NetworkQueryBusServer */, 'getOwner')
], NetworkEngine2_Server.prototype, "onGetOwner", null);
__decorate([
    ServerEventHandler("NetworkQueryBusServer" /* NetworkEngine2_EventType.NetworkQueryBusServer */, 'promoteOwner')
], NetworkEngine2_Server.prototype, "onPromoteOwner", null);
__decorate([
    ServerEventHandler("NetworkingEventBusServer" /* NetworkEngine2_EventType.NetworkingEventBusServer */, 'connection')
], NetworkEngine2_Server.prototype, "onConnection", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'version')
], NetworkEngine2_Server.prototype, "onVersionCheck", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'LobbyRequest')
], NetworkEngine2_Server.prototype, "onLobbyRequest", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'playerJoined_reply')
], NetworkEngine2_Server.prototype, "onJoinReply", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'msg')
], NetworkEngine2_Server.prototype, "onSocketMessage", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'toSpecificPlayer')
], NetworkEngine2_Server.prototype, "toSpecificPlayer", null);
__decorate([
    ServerEventHandler("protocolServer" /* NetworkEngine2_EventType.protocolServer */, 'onCrash')
], NetworkEngine2_Server.prototype, "onCrash", null);
exports.NetworkEngine2_Server = NetworkEngine2_Server;
class NetworkEngine2_Client {
    io = require('socket.io-client');
    socket = {};
    logger;
    config;
    modLoaderconfig;
    masterConfig;
    me;
    udpClient = dgram_1.default.createSocket('udp4');
    serverUDPPort = -1;
    isUDPEnabled = false;
    udpTestHandle;
    udpPingHandle;
    lastReceivedPing;
    packetBuffer = new Array();
    plugins = {};
    core = "";
    isConnectionReady = false;
    lastPacketBuffer = new Array();
    pluginConfiguredConnection = false;
    connectionTimer;
    discord;
    lobbyOwner;
    protocol = new NetworkHandler_1.NetworkEventBus();
    constructor(logger, config, discord) {
        this.logger = logger;
        // Configuration.
        this.config = config.registerConfigCategory('NetworkEngine.Client');
        config.setData('NetworkEngine.Client', "isSinglePlayer", false);
        config.setData('NetworkEngine.Client', 'forceServerOverride', false);
        config.setData('NetworkEngine.Client', 'ip', '127.0.0.1');
        config.setData('NetworkEngine.Client', 'port', 8082);
        config.setData('NetworkEngine.Client', 'lobby', mluuid_1.ML_UUID.getLobbyName());
        config.setData('NetworkEngine.Client', 'nickname', 'Player');
        config.setData('NetworkEngine.Client', 'password', '');
        config.setData('NetworkEngine.Client', 'forceTCPMode', false);
        this.masterConfig = config;
        this.modLoaderconfig = this.masterConfig.registerConfigCategory('ModLoader64');
        modloader64_1.internal_event_bus.on('PLUGIN_LOADED', (args) => {
            let p = args[0].meta;
            this.plugins[p.name] = { version: p.version, hash: args[0].hash };
            if (typeof args[0].instance.getServerURL === "function" && !this.config.forceServerOverride && !this.pluginConfiguredConnection && !this.config.isSinglePlayer) {
                this.logger.info("Using plugin server configuration: " + p.name + ".");
                let server_connection_setup = args[0].instance;
                this.config.ip = server_connection_setup.getServerURL().split(":")[0];
                this.config.port = parseInt(server_connection_setup.getServerURL().split(":")[1]);
                this.pluginConfiguredConnection = true;
            }
        });
        modloader64_1.internal_event_bus.on('CORE_LOADED', (args) => {
            this.core = args[0].name;
            if (this.config.isSinglePlayer) {
                this.logger.info("Dropping into single player mode due to config.");
                this.config.ip = "127.0.0.1";
                this.config.port = parseInt("8082");
            }
        });
        this.discord = discord;
    }
    onTick() {
        this.lastPacketBuffer.length = 0;
        while (this.packetBuffer.length > 0) {
            try {
                let data = this.packetBuffer.shift();
                this.lastPacketBuffer.push(data);
                NetworkHandler_1.NetworkBus.emit(data.packet_id, data);
                NetworkHandler_1.NetworkChannelBus.emit(data.channel, data);
            }
            catch (err) {
                this.logger.error(err.stack);
            }
        }
    }
    onTickPreInit() {
        this.lastPacketBuffer.length = 0;
        while (this.packetBuffer.length > 0) {
            try {
                let data = this.packetBuffer.shift();
                this.lastPacketBuffer.push(data);
                if (data.socketType == 2 /* SocketType.SETUP_PACKET */) {
                    NetworkHandler_1.NetworkBus.emit(data.packet_id, data);
                    NetworkHandler_1.NetworkChannelBus.emit(data.channel, data);
                }
            }
            catch (err) {
                this.logger.error(err.stack);
            }
        }
    }
    setup() {
        this.connectionTimer = setTimeout(() => {
            this.logger.error("Failed to connect.");
            process.exit(0);
        }, 30 * 1000);
        this.logger.info('Starting up NetworkEngine2_Client...');
        dns.resolve4(`${this.config.ip}`, this.setupPostDNS.bind(this));
    }
    onUDPMessage(msg, rinfo) {
        let packet = JSON.parse(msg.toString());
        this.packetBuffer.push(Object.freeze(packet));
    }
    onDisconnect() {
        this.isConnectionReady = false;
    }
    setupPostDNS(err, addresses) {
        if (addresses === undefined) {
            addresses = [this.config.ip];
        }
        else {
            this.logger.debug(`Resolving ${this.config.ip} to ${addresses}`);
        }
        if (addresses.length === 0)
            process.exit(10 /* ModLoaderErrorCodes.BAD_URL */);
        this.config.ip = addresses[0];
        let protocol = ['uuid', 'versionGood', 'versionBad', 'LobbyReady', 'LobbyDenied_BadPassword', 'left', 'newOwner', 'playerJoined', 'playerJoined_bounce', 'msg'];
        this.udpClient.on('message', (msg, rinfo) => {
            NetworkingEventBus_1.NetworkingEventBusClient.emit('onUDPMessage', msg, rinfo);
        });
        NetworkingEventBus_1.NetworkingEventBusClient.on('onUDPMessage', this.onUDPMessage.bind(this));
        protocol.forEach((str) => {
            NetworkEngine2_ClientEventComponents.ClientEmitters.push({
                key: str, fn: (socket, data) => {
                    this.protocol.emit(str, socket, data);
                }
            });
        });
        NetworkEngine2_ClientEventComponents.NetworkBusClient_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkBus.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ClientEventComponents.NetworkQueryBusClient_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkQueryBus.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ClientEventComponents.NetworkSendBusClient_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkHandler_1.NetworkSendBus.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ClientEventComponents.protocolClient_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            this.protocol.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ClientEventComponents.NetworkingEventBusClient_Targets.forEach((entry) => {
            let fn = this[entry.prop];
            NetworkingEventBus_1.NetworkingEventBusClient.on(entry.key, fn.bind(this));
        });
        NetworkEngine2_ClientEventComponents.clientDisconnectHandler = this.onDisconnect.bind(this);
        this.socket = this.io.connect('http://' + this.config.ip + ':' + this.config.port);
        this.socket.on('connect', () => {
            NetworkEngine2_ClientEventComponents.ClientHandlers.forEach((value) => {
                this.socket.on(value.key, value.fn);
            });
            NetworkEngine2_ClientEventComponents.ClientEmitters.forEach((value) => {
                this.socket.on(value.key, (data) => {
                    value.fn(this.socket, data);
                });
            });
            this.socket.on('disconnect', NetworkEngine2_ClientEventComponents.clientDisconnectHandler);
            NetworkingEventBus_1.NetworkingEventBusClient.emit('connect', this.socket);
        });
    }
    onConnection() {
        this.logger.info('Connected.');
        clearTimeout(this.connectionTimer);
    }
    onMsgSend(data) {
        data.player = this.me;
        data.lobby = this.config.lobby;
        if (data.player === undefined) {
            this.logger.error("Tried to send a packet with no local INetworkPlayer!");
            return;
        }
        if (data.socketType === 1 /* SocketType.UDP */ &&
            this.isUDPEnabled &&
            this.isConnectionReady) {
            this.udpClient.send(JSON.stringify(data), this.serverUDPPort, this.config.ip);
        }
        else {
            this.socket.emit('msg', JSON.parse(JSON.stringify(data)));
        }
    }
    onToPlayer(data) {
        data.packet.player = this.me;
        data.packet.lobby = this.config.lobby;
        this.socket.emit('toSpecificPlayer', data);
    }
    onUDPTest(data) {
        clearInterval(this.udpTestHandle);
        this.logger.info("UDP test passed.");
        let packet = new NetworkingPackets_1.UDPModeOnPacket(this.config.lobby);
        packet.player = this.me;
        this.udpClient.send(JSON.stringify(packet), this.serverUDPPort, this.config.ip);
        if (this.udpPingHandle === undefined) {
            this.udpPingHandle = setInterval(() => {
                if (this.lastReceivedPing === undefined || this.config.forceTCPMode) {
                    this.isUDPEnabled = false;
                    this.logger.warn('UDP disabled.');
                    NetworkHandler_1.NetworkSendBus.emit('msg', new NetworkingPackets_1.UDPModeOffPacket(this.config.lobby));
                    clearInterval(this.udpPingHandle);
                }
            }, 70 * 1000);
        }
    }
    onPing(packet) {
        this.lastReceivedPing = packet;
        if (packet.socketType === 0 /* SocketType.TCP */) {
            NetworkHandler_1.NetworkSendBus.emit('msg', new NetworkingPackets_1.PongPacket(packet.timestamp, this.config.lobby).setType(0 /* SocketType.TCP */));
        }
        else {
            NetworkHandler_1.NetworkSendBus.emit('msg', new NetworkingPackets_1.PongPacket(packet.timestamp, this.config.lobby).setType(1 /* SocketType.UDP */));
        }
    }
    onLatInfo(packet) {
        this.me.data["ninfo"] = { ping: packet.ping, rt: packet.roundtrip };
    }
    onOwnerQuery(evt) {
        evt.owner = this.lobbyOwner;
    }
    onUUID(socket, data) {
        this.me = new ModLoaderDefaultImpls_1.NetworkPlayer(this.config.nickname, data.uuid);
        this.socket.emit('version', new NetworkingPackets_1.VersionPacket(global.ModLoader.version, this.plugins, this.core, this.discord));
    }
    onVersionCheckGood(socket, data) {
        this.logger.info('Version good! ' + JSON.stringify(data.server));
        this.logger.info("This server has a " + data.patchLimit.toString() + "MB bps patch limit.");
        let ld = new NetworkHandler_1.LobbyData(this.config.lobby, crypto_1.default.createHash('md5').update(Buffer.from(this.config.password)).digest('hex'));
        EventHandler_1.bus.emit(EventHandler_1.EventsClient.CONFIGURE_LOBBY, ld);
        if (this.modLoaderconfig.patch !== '') {
            let mods = "./mods";
            if (global.ModLoader.hasOwnProperty("OVERRIDE_MODS_FOLDER")) {
                mods = global.ModLoader.OVERRIDE_MODS_FOLDER;
            }
            let files = (0, getAllFiles_1.getAllFiles)(mods, []);
            let patch_path = "";
            for (let i = 0; i < files.length; i++) {
                let p = path_1.default.parse(files[i]);
                if (p.base === this.modLoaderconfig.patch) {
                    patch_path = files[i];
                }
            }
            if (patch_path !== "") {
                let gzip = zlib_1.default.deflateSync(fs_1.default.readFileSync(path_1.default.resolve(patch_path)));
                if (gzip.byteLength > (data.patchLimit * 1024 * 1024)) {
                    this.logger.error("Patch file " + patch_path + " too large.");
                    process.exit(3 /* ModLoaderErrorCodes.BPS_FAILED */);
                }
                else {
                    ld.data['patch'] = gzip;
                    ld.data['patch_name'] = this.modLoaderconfig.patch;
                }
            }
        }
        //@ts-ignore
        this.socket.emit('LobbyRequest', new LobbyObjects_1.LobbyJoin(ld, this.me.toJSON()));
        EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_SERVER_CONNECTION, {});
    }
    onVersionCheckBad(socket, data) {
        this.logger.info('Version bad! ' + JSON.stringify(data));
        modloader64_1.internal_event_bus.emit('VERSION_BAD', JSON.stringify(data));
        setTimeout(() => {
            process.exit(4 /* ModLoaderErrorCodes.BAD_VERSION */);
        }, 1000);
    }
    onLobbyReady(socket, data) {
        let ld = data.storage;
        let udpPort = data.udp;
        this.logger.info('Joined lobby ' + ld.name + '.');
        this.lobbyOwner = data.owner;
        modloader64_1.internal_event_bus.emit("DISCORD_INVITE_SETUP", this.config);
        let p = Buffer.alloc(1);
        if (ld.data.hasOwnProperty('patch')) {
            p = zlib_1.default.inflateSync(ld.data.patch);
        }
        let udpTest = new NetworkingPackets_1.UDPTestPacket();
        udpTest.player = this.me;
        udpTest.lobby = this.config.lobby;
        this.isUDPEnabled = true;
        this.udpTestHandle = setTimeout(() => {
            this.isUDPEnabled = false;
            this.logger.warn('UDP disabled.');
        }, 30 * 1000);
        this.udpClient.send(JSON.stringify(udpTest), udpPort, this.config.ip);
        this.serverUDPPort = udpPort;
        modloader64_1.internal_event_bus.emit('onNetworkConnect', {
            me: this.me,
            patch: p,
            patch_name: ld.data.patch_name
        });
        EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_LOBBY_JOIN, ld);
        this.isConnectionReady = true;
    }
    onBadPW(socket, data) {
        this.logger.error('Failed to join lobby. Password incorrect.');
        setTimeout(() => {
            process.exit(7 /* ModLoaderErrorCodes.BAD_LOBBY_PASSWORD */);
        }, 1000);
    }
    onPlayerLeft(socket, player) {
        EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_PLAYER_LEAVE, player);
    }
    onNewOwner(socket, owner) {
        this.lobbyOwner = owner;
        EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_LOBBY_OWNER_CHANGE, new EventHandler_1.EventOwnerChanged(this.config.lobby, this.lobbyOwner));
    }
    onPlayerJoin(socket, player) {
        if (player.uuid !== this.me.uuid) {
            EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_PLAYER_JOIN, player);
            this.socket.emit('playerJoined_reply', { player: this.me, dest: player });
        }
    }
    onBounce(socket, player) {
        if (player.uuid !== this.me.uuid) {
            EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_PLAYER_JOIN, player);
        }
    }
    onMsgHandle(socket, data) {
        this.packetBuffer.push(Object.freeze(data));
    }
}
__decorate([
    ClientEventHandler("NetworkingEventBusClient" /* NetworkEngine2_EventType.NetworkingEventBusClient */, 'connect')
], NetworkEngine2_Client.prototype, "onConnection", null);
__decorate([
    ClientEventHandler("NetworkSendBus" /* NetworkEngine2_EventType.NetworkSendBus */, 'msg')
], NetworkEngine2_Client.prototype, "onMsgSend", null);
__decorate([
    ClientEventHandler("NetworkSendBus" /* NetworkEngine2_EventType.NetworkSendBus */, 'toPlayer')
], NetworkEngine2_Client.prototype, "onToPlayer", null);
__decorate([
    ClientEventHandler("NetworkBus" /* NetworkEngine2_EventType.NetworkBus */, 'UDPTestPacket')
], NetworkEngine2_Client.prototype, "onUDPTest", null);
__decorate([
    ClientEventHandler("NetworkBus" /* NetworkEngine2_EventType.NetworkBus */, 'PingPacket')
], NetworkEngine2_Client.prototype, "onPing", null);
__decorate([
    ClientEventHandler("NetworkBus" /* NetworkEngine2_EventType.NetworkBus */, 'LatencyInfoPacket')
], NetworkEngine2_Client.prototype, "onLatInfo", null);
__decorate([
    ClientEventHandler("NetworkQueryBus" /* NetworkEngine2_EventType.NetworkQueryBus */, 'getOwner')
], NetworkEngine2_Client.prototype, "onOwnerQuery", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'uuid')
], NetworkEngine2_Client.prototype, "onUUID", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'versionGood')
], NetworkEngine2_Client.prototype, "onVersionCheckGood", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'versionBad')
], NetworkEngine2_Client.prototype, "onVersionCheckBad", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'LobbyReady')
], NetworkEngine2_Client.prototype, "onLobbyReady", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'LobbyDenied_BadPassword')
], NetworkEngine2_Client.prototype, "onBadPW", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'left')
], NetworkEngine2_Client.prototype, "onPlayerLeft", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'newOwner')
], NetworkEngine2_Client.prototype, "onNewOwner", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'playerJoined')
], NetworkEngine2_Client.prototype, "onPlayerJoin", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'playerJoined_bounce')
], NetworkEngine2_Client.prototype, "onBounce", null);
__decorate([
    ClientEventHandler("protocolClient" /* NetworkEngine2_EventType.protocolClient */, 'msg')
], NetworkEngine2_Client.prototype, "onMsgHandle", null);
exports.NetworkEngine2_Client = NetworkEngine2_Client;
//# sourceMappingURL=NetworkEngine2.js.map