"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const IModLoaderAPI_1 = require("modloader64_api/IModLoaderAPI");
const EventHandler_1 = require("modloader64_api/EventHandler");
const NetworkHandler_1 = require("modloader64_api/NetworkHandler");
const modloader64_1 = require("./modloader64");
const CoreInjection_1 = require("modloader64_api/CoreInjection");
const LobbyManager_1 = require("./networking/LobbyManager");
const PakFormat_1 = require("modloader64_api/PakFormat");
const crypto_1 = __importDefault(require("crypto"));
const GUITunnel_1 = require("modloader64_api/GUITunnel");
const frameTimeoutContainer_1 = require("./frameTimeoutContainer");
const PayloadManager_1 = require("./PayloadManager");
const pakVerifier_1 = require("./pakVerifier");
const module_alias_1 = __importDefault(require("module-alias"));
const Math_1 = require("./Math");
const ModLoaderAPIInjector_1 = require("modloader64_api/ModLoaderAPIInjector");
const PluginLifecycle_1 = require("modloader64_api/PluginLifecycle");
const mluuid_1 = require("./uuid/mluuid");
const Utils_1 = require("../monkeypatches/Utils");
const ModLoadOrder_1 = require("./ModLoadOrder");
const SidedProxy_1 = require("modloader64_api/SidedProxy/SidedProxy");
const getAllFiles_1 = require("./getAllFiles");
const adm_zip_1 = __importDefault(require("adm-zip"));
const SoundSystem_1 = require("./AudioAPI/API/SoundSystem");
const ISoundSystem_1 = require("modloader64_api/Sound/ISoundSystem");
const BindVar_1 = require("modloader64_api/BindVar");
const heap_1 = require("modloader64_api/heap");
const accessor_1 = require("./drahsid/accessor");
class pluginLoader {
    plugin_directories;
    core_plugins = {};
    plugin_folders = [];
    plugins = [];
    selected_core = '';
    loaded_core = {};
    config;
    logger;
    onTickHandle;
    onViHandle;
    onResourceHandle;
    header;
    curFrame = -1;
    frameTimeouts = new Map();
    frameIntervals = new Map();
    payloadManager;
    injector;
    heapSetup;
    lifecycle_funcs = new Map();
    processNextFrame = true;
    resetting = false;
    temporaryNetworkLoop;
    constructor(dirs, config, logger) {
        this.plugin_directories = dirs;
        this.config = config;
        this.logger = logger;
        let cleanup = function () {
            console.log(`Trying cleanup in ${process.cwd()}...`);
            fs_1.default.readdirSync(process.cwd()).forEach((file) => {
                let parse = path_1.default.parse(file);
                if (parse.name.indexOf('ModLoader64_temp_') > -1) {
                    fs_1.default.rmdirSync(path_1.default.resolve(process.cwd(), file), { recursive: true });
                }
            });
            if (fs_1.default.existsSync(path_1.default.resolve(process.cwd(), "asarunpack"))) {
                fs_1.default.rmdirSync(path_1.default.resolve(process.cwd(), "asarunpack"), { recursive: true });
            }
        };
        modloader64_1.internal_event_bus.on('SHUTDOWN_EVERYTHING', () => {
            cleanup();
        });
        this.lifecycle_funcs.set("preinit" /* LifeCycleEvents.PREINIT */, []);
        this.lifecycle_funcs.set("init" /* LifeCycleEvents.INIT */, []);
        this.lifecycle_funcs.set("postinit" /* LifeCycleEvents.POSTINIT */, []);
        this.lifecycle_funcs.set("ontick" /* LifeCycleEvents.ONTICK */, []);
        this.lifecycle_funcs.set("onposttick" /* LifeCycleEvents.ONPOSTTICK */, []);
        this.lifecycle_funcs.set("onviupdate" /* LifeCycleEvents.ONVIUPDATE */, []);
        this.lifecycle_funcs.set("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */, []);
        PluginLifecycle_1.lifecyclebus.on("preinit" /* LifeCycleEvents.PREINIT */, (handler) => {
            this.lifecycle_funcs.get("preinit" /* LifeCycleEvents.PREINIT */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("init" /* LifeCycleEvents.INIT */, (handler) => {
            this.lifecycle_funcs.get("init" /* LifeCycleEvents.INIT */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("postinit" /* LifeCycleEvents.POSTINIT */, (handler) => {
            this.lifecycle_funcs.get("postinit" /* LifeCycleEvents.POSTINIT */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("ontick" /* LifeCycleEvents.ONTICK */, (handler) => {
            this.lifecycle_funcs.get("ontick" /* LifeCycleEvents.ONTICK */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("onposttick" /* LifeCycleEvents.ONPOSTTICK */, (handler) => {
            this.lifecycle_funcs.get("onposttick" /* LifeCycleEvents.ONPOSTTICK */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("onviupdate" /* LifeCycleEvents.ONVIUPDATE */, (handler) => {
            this.lifecycle_funcs.get("onviupdate" /* LifeCycleEvents.ONVIUPDATE */).push(handler);
        });
        PluginLifecycle_1.lifecyclebus.on("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */, (handler) => {
            this.lifecycle_funcs.get("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */).push(handler);
        });
        // reverse
        PluginLifecycle_1.lifecyclebus.on("preinit_destroy" /* LifeCycleEvents.PREINIT_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("preinit" /* LifeCycleEvents.PREINIT */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("preinit" /* LifeCycleEvents.PREINIT */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("init_destroy" /* LifeCycleEvents.INIT_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("init" /* LifeCycleEvents.INIT */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("init" /* LifeCycleEvents.INIT */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("postinit_destroy" /* LifeCycleEvents.POSTINIT_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("postinit" /* LifeCycleEvents.POSTINIT */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("postinit" /* LifeCycleEvents.POSTINIT */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("ontick_destroy" /* LifeCycleEvents.ONTICK_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("ontick" /* LifeCycleEvents.ONTICK */).indexOf(handler);
            console.log(i);
            if (i > -1)
                this.lifecycle_funcs.get("ontick" /* LifeCycleEvents.ONTICK */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("onposttick_destroy" /* LifeCycleEvents.ONPOSTTICK_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("onposttick" /* LifeCycleEvents.ONPOSTTICK */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("onposttick" /* LifeCycleEvents.ONPOSTTICK */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("onviupdate_destroy" /* LifeCycleEvents.ONVIUPDATE_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("onviupdate" /* LifeCycleEvents.ONVIUPDATE */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("onviupdate" /* LifeCycleEvents.ONVIUPDATE */).splice(i, 1);
        });
        PluginLifecycle_1.lifecyclebus.on("oncreateresources_destroy" /* LifeCycleEvents.ONCREATERESOURCES_DESTROY */, (handler) => {
            let i = this.lifecycle_funcs.get("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */).indexOf(handler);
            if (i > -1)
                this.lifecycle_funcs.get("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */).splice(i, 1);
        });
    }
    registerCorePlugin(name, core) {
        this.core_plugins[name] = core;
    }
    registerPlugin(plugin) {
        this.plugins.push(plugin);
    }
    processInternalPlugin(pluginPath, iconsole) {
        if (pluginPath === "")
            return;
        let parse = path_1.default.parse(pluginPath);
        let p = require(pluginPath);
        let plugin = new p();
        plugin['ModLoader'] = {};
        plugin['ModLoader']['logger'] = this.logger.getLogger(parse.name);
        plugin['ModLoader']['config'] = this.config;
        plugin['ModLoader']['publicBus'] = EventHandler_1.bus;
        plugin['ModLoader']['privateBus'] = new EventHandler_1.EventBus();
        plugin["Binding"] = iconsole;
        Object.defineProperty(plugin, 'pluginName', {
            value: pluginPath,
            writable: false,
        });
        Object.defineProperty(plugin, 'pluginHash', {
            value: "",
            writable: false,
        });
        let mlconfig = this.config.registerConfigCategory('ModLoader64');
        (0, EventHandler_1.setupEventHandlers)(plugin, EventHandler_1.bus);
        (0, EventHandler_1.setupPrivateEventHandlers)(plugin, plugin.ModLoader.privateBus);
        (0, NetworkHandler_1.setupNetworkHandlers)(plugin);
        (0, CoreInjection_1.setupCoreInject)(plugin, this.loaded_core);
        (0, PluginLifecycle_1.setupLifecycle_IPlugin)(plugin);
        (0, PluginLifecycle_1.setupLifecycle)(plugin);
        Object.keys(plugin).forEach((key) => {
            if (plugin[key] !== null && plugin[key] !== undefined) {
                if (plugin[key] === plugin)
                    return;
                (0, SidedProxy_1.setupParentReference)(plugin[key], plugin);
                (0, ModLoaderAPIInjector_1.setupMLInjects)(plugin[key], plugin.ModLoader);
                (0, CoreInjection_1.setupCoreInject)(plugin[key], this.loaded_core);
                (0, EventHandler_1.setupEventHandlers)(plugin[key], EventHandler_1.bus);
                (0, EventHandler_1.setupPrivateEventHandlers)(plugin[key], plugin.ModLoader.privateBus);
                (0, NetworkHandler_1.setupNetworkHandlers)(plugin[key]);
                (0, PluginLifecycle_1.setupLifecycle)(plugin[key]);
                (0, BindVar_1.setupBindVar)(plugin[key], iconsole.getMemoryAccess());
                (0, EventHandler_1.markPrototypeProcessed)(plugin[key]);
            }
        });
        let fn = (instance, parent) => {
            (0, SidedProxy_1.setupParentReference)(instance, parent);
            (0, ModLoaderAPIInjector_1.setupMLInjects)(instance, parent.ModLoader);
            (0, CoreInjection_1.setupCoreInject)(instance, this.loaded_core);
            (0, EventHandler_1.setupEventHandlers)(instance, EventHandler_1.bus);
            (0, EventHandler_1.setupPrivateEventHandlers)(instance, parent.ModLoader.privateBus);
            (0, NetworkHandler_1.setupNetworkHandlers)(instance);
            (0, PluginLifecycle_1.setupLifecycle)(instance);
            Object.keys(instance).forEach((key) => {
                if (instance[key] !== null && instance[key] !== undefined) {
                    if (instance[key] === instance)
                        return;
                    (0, SidedProxy_1.setupParentReference)(instance[key], parent);
                    (0, ModLoaderAPIInjector_1.setupMLInjects)(instance[key], plugin.ModLoader);
                    (0, CoreInjection_1.setupCoreInject)(instance[key], this.loaded_core);
                    (0, EventHandler_1.setupEventHandlers)(instance[key], EventHandler_1.bus);
                    (0, EventHandler_1.setupPrivateEventHandlers)(instance[key], parent.ModLoader.privateBus);
                    (0, NetworkHandler_1.setupNetworkHandlers)(instance[key]);
                    (0, PluginLifecycle_1.setupLifecycle)(instance[key]);
                    (0, BindVar_1.setupBindVar)(instance[key], iconsole.getMemoryAccess());
                    (0, EventHandler_1.markPrototypeProcessed)(instance[key]);
                }
            });
            let children = (0, SidedProxy_1.setupSidedProxy)(instance, mlconfig.isClient, mlconfig.isServer, this.selected_core);
            for (let i = 0; i < children.length; i++) {
                fn(children[i], plugin);
            }
            (0, EventHandler_1.markPrototypeProcessed)(instance);
        };
        let children = (0, SidedProxy_1.setupSidedProxy)(plugin, mlconfig.isClient, mlconfig.isServer, this.selected_core);
        for (let i = 0; i < children.length; i++) {
            fn(children[i], plugin);
        }
        (0, EventHandler_1.markPrototypeProcessed)(plugin);
        Object.defineProperty(plugin, 'metadata', {
            value: {},
            writable: false,
        });
        this.registerPlugin(plugin);
    }
    processFolder(dir, iconsole) {
        if (!fs_1.default.existsSync(dir)) {
            return;
        }
        let hash = "";
        let parse = path_1.default.parse(dir);
        if (parse.ext === '.pak') {
            let pakFile = new PakFormat_1.Pak(path_1.default.resolve(dir));
            let v = new pakVerifier_1.pakVerifier(this.logger);
            if (!v.verifyPak(pakFile, dir)) {
                return;
            }
            // Unpak first.
            dir = v.extractPakToTemp(pakFile, dir);
            hash = pakFile.pak.footer._hash;
        }
        if (parse.ext === ".zip") {
            let zipFile = new adm_zip_1.default(path_1.default.resolve(dir));
            let ndir = fs_1.default.mkdtempSync('ModLoader64_temp_');
            zipFile.extractAllTo(ndir);
            let d = "";
            fs_1.default.readdirSync(ndir).forEach((dir) => {
                d = dir;
            });
            dir = path_1.default.join(ndir, d);
            let f = new PakFormat_1.PakFooter();
            f.generateHash(zipFile.toBuffer());
            hash = f._hash;
        }
        if (parse.ext === ".asar" && global.ModLoader["ASAR_SUPPORT"]) {
            dir = path_1.default.resolve(dir);
        }
        if (!fs_1.default.lstatSync(path_1.default.resolve(dir)).isDirectory() && !fs_1.default.lstatSync(path_1.default.resolve(dir)).isSymbolicLink()) {
            return;
        }
        let pkg_file = path_1.default.resolve(path_1.default.join(dir, 'package.json'));
        if (!fs_1.default.existsSync(pkg_file)) {
            this.logger.error('Plugin ' + parse.name + ' is missing package.json. Skipping.');
            return;
        }
        let pkg = JSON.parse(fs_1.default.readFileSync(pkg_file).toString());
        EventHandler_1.bus.emit("PLUGIN_CONSIDERATION", pkg);
        if (typeof pkg.core === "string") {
            if (pkg.core !== this.selected_core && pkg.core !== '*') {
                this.logger.info('Plugin ' + pkg.name + ' does not belong to this core. Skipping.');
                return;
            }
        }
        else if (pkg.core instanceof Array) {
            let possibles = pkg.core;
            let yes = false;
            for (let i = 0; i < possibles.length; i++) {
                if (possibles[i] === this.selected_core) {
                    yes = true;
                }
            }
            if (!yes) {
                this.logger.info('Plugin ' + pkg.name + ' does not belong to this core. Skipping.');
                return;
            }
        }
        this.logger.info('--------------------');
        this.logger.info('plugin: ' + pkg.name);
        this.logger.info('version: ' + pkg.version);
        this.logger.info('author: ' + pkg.author);
        this.logger.info('additional credits: ' + pkg.credits);
        if (pkg.main === undefined || pkg.main === "") {
            return;
        }
        if (pkg.hasOwnProperty("asarunpack")) {
            if (!fs_1.default.existsSync(path_1.default.resolve(process.cwd(), "asarunpack"))) {
                fs_1.default.mkdirSync(path_1.default.resolve(process.cwd(), "asarunpack"));
            }
            let unpack = pkg.asarunpack;
            for (let i = 0; i < unpack.length; i++) {
                let f = path_1.default.resolve(dir, unpack[i]);
                if (fs_1.default.existsSync(f)) {
                    fs_1.default.copyFileSync(f, path_1.default.resolve(process.cwd(), "asarunpack", path_1.default.parse(f).base));
                }
            }
        }
        let file = path_1.default.resolve(path_1.default.join(dir, pkg.main));
        module_alias_1.default.addAlias("@" + pkg.name.replace(" ", "_"), path_1.default.resolve(dir));
        parse = path_1.default.parse(file);
        let p;
        if (p === undefined && fs_1.default.existsSync(path_1.default.resolve(parse.dir, parse.name + ".js"))) {
            p = require(path_1.default.resolve(parse.dir, parse.name + ".js"));
        }
        try {
            if (p["default"] !== undefined) {
                p = p["default"];
            }
        }
        catch (err) {
            this.logger.error(err);
        }
        let plugin = undefined;
        try {
            plugin = new p();
        }
        catch (err) {
            this.logger.error(err);
        }
        if (plugin === undefined) {
            this.logger.error("Invalid plugin? Skipping.");
            return;
        }
        plugin['ModLoader'] = {};
        plugin['ModLoader']['logger'] = this.logger.getLogger(parse.name);
        plugin['ModLoader']['config'] = this.config;
        plugin['ModLoader']['publicBus'] = EventHandler_1.bus;
        plugin['ModLoader']['privateBus'] = new EventHandler_1.EventBus();
        Object.defineProperty(plugin, 'pluginName', {
            value: pkg.name,
            writable: false,
        });
        Object.defineProperty(plugin, 'pluginHash', {
            value: hash,
            writable: false,
        });
        let mlconfig = this.config.registerConfigCategory('ModLoader64');
        (0, EventHandler_1.setupEventHandlers)(plugin, EventHandler_1.bus);
        (0, EventHandler_1.setupPrivateEventHandlers)(plugin, plugin.ModLoader.privateBus);
        (0, NetworkHandler_1.setupNetworkHandlers)(plugin);
        (0, CoreInjection_1.setupCoreInject)(plugin, this.loaded_core);
        (0, PluginLifecycle_1.setupLifecycle_IPlugin)(plugin);
        (0, PluginLifecycle_1.setupLifecycle)(plugin);
        (0, BindVar_1.setupBindVar)(plugin, iconsole.getMemoryAccess());
        Object.keys(plugin).forEach((key) => {
            if (plugin[key] !== null && plugin[key] !== undefined) {
                if (plugin[key] === plugin)
                    return; // Don't do anything if its an instance of itself. @MegaMech.
                (0, SidedProxy_1.setupParentReference)(plugin[key], plugin);
                (0, ModLoaderAPIInjector_1.setupMLInjects)(plugin[key], plugin.ModLoader);
                (0, CoreInjection_1.setupCoreInject)(plugin[key], this.loaded_core);
                (0, EventHandler_1.setupEventHandlers)(plugin[key], EventHandler_1.bus);
                (0, EventHandler_1.setupPrivateEventHandlers)(plugin[key], plugin.ModLoader.privateBus);
                (0, NetworkHandler_1.setupNetworkHandlers)(plugin[key]);
                (0, PluginLifecycle_1.setupLifecycle)(plugin[key]);
                (0, BindVar_1.setupBindVar)(plugin[key], iconsole.getMemoryAccess());
                (0, EventHandler_1.markPrototypeProcessed)(plugin[key]);
            }
        });
        let fn = (instance, parent) => {
            (0, SidedProxy_1.setupParentReference)(instance, parent);
            (0, ModLoaderAPIInjector_1.setupMLInjects)(instance, parent.ModLoader);
            (0, CoreInjection_1.setupCoreInject)(instance, this.loaded_core);
            (0, EventHandler_1.setupEventHandlers)(instance, EventHandler_1.bus);
            (0, EventHandler_1.setupPrivateEventHandlers)(instance, parent.ModLoader.privateBus);
            (0, NetworkHandler_1.setupNetworkHandlers)(instance);
            (0, PluginLifecycle_1.setupLifecycle)(instance);
            Object.keys(instance).forEach((key) => {
                if (instance[key] !== null && instance[key] !== undefined) {
                    if (instance[key] === instance)
                        return;
                    (0, SidedProxy_1.setupParentReference)(instance[key], parent);
                    (0, ModLoaderAPIInjector_1.setupMLInjects)(instance[key], plugin.ModLoader);
                    (0, CoreInjection_1.setupCoreInject)(instance[key], this.loaded_core);
                    (0, EventHandler_1.setupEventHandlers)(instance[key], EventHandler_1.bus);
                    (0, EventHandler_1.setupPrivateEventHandlers)(instance[key], plugin.ModLoader.privateBus);
                    (0, NetworkHandler_1.setupNetworkHandlers)(instance[key]);
                    (0, PluginLifecycle_1.setupLifecycle)(instance[key]);
                    (0, BindVar_1.setupBindVar)(instance[key], iconsole.getMemoryAccess());
                    (0, EventHandler_1.markPrototypeProcessed)(instance[key]);
                }
            });
            let children = (0, SidedProxy_1.setupSidedProxy)(instance, mlconfig.isClient, mlconfig.isServer, this.selected_core);
            for (let i = 0; i < children.length; i++) {
                fn(children[i], plugin);
            }
            (0, EventHandler_1.markPrototypeProcessed)(instance);
        };
        let children = (0, SidedProxy_1.setupSidedProxy)(plugin, mlconfig.isClient, mlconfig.isServer, this.selected_core);
        for (let i = 0; i < children.length; i++) {
            fn(children[i], plugin);
        }
        (0, EventHandler_1.markPrototypeProcessed)(plugin);
        Object.defineProperty(plugin, 'metadata', {
            value: pkg,
            writable: false,
        });
        this.logger.info("Registered plugin " + pkg.name + ".");
        this.registerPlugin(plugin);
        this.plugin_folders.push(parse.dir);
        modloader64_1.internal_event_bus.emit('PLUGIN_LOADED', { meta: pkg, instance: plugin, hash: hash });
    }
    loadPluginsConstruct(header, iconsole, overrideCore = '') {
        // Start the core plugin.
        this.header = header;
        global.ModLoader["ROM_HEADER"] = this.header;
        if (overrideCore !== '') {
            this.selected_core = overrideCore;
        }
        let core = this.core_plugins[this.selected_core];
        Object.freeze(this.logger);
        core['ModLoader'] = {};
        core['ModLoader']['logger'] = this.logger.getLogger(this.selected_core);
        core['ModLoader']['config'] = this.config;
        core['ModLoader']['publicBus'] = EventHandler_1.bus;
        core['ModLoader']['privateBus'] = new EventHandler_1.EventBus();
        this.loaded_core = core;
        Object.defineProperty(this.loaded_core, 'rom_header', {
            value: header,
            writable: false,
        });
        Object.defineProperty(this.loaded_core, 'pluginName', {
            value: this.selected_core,
            writable: false,
        });
        (0, EventHandler_1.setupEventHandlers)(this.loaded_core, EventHandler_1.bus);
        (0, EventHandler_1.setupPrivateEventHandlers)(this.loaded_core, this.loaded_core.ModLoader.privateBus);
        (0, NetworkHandler_1.setupNetworkHandlers)(this.loaded_core);
        (0, PluginLifecycle_1.setupLifecycle)(this.loaded_core);
        (0, EventHandler_1.markPrototypeProcessed)(this.loaded_core);
        Object.keys(this.loaded_core).forEach((key) => {
            if (this.loaded_core[key] !== null && this.loaded_core[key] !== undefined) {
                if (this.loaded_core[key] === this.loaded_core)
                    return;
                (0, ModLoaderAPIInjector_1.setupMLInjects)(this.loaded_core[key], this.loaded_core.ModLoader);
                (0, CoreInjection_1.setupCoreInject)(this.loaded_core[key], this.loaded_core);
                (0, EventHandler_1.setupEventHandlers)(this.loaded_core[key], EventHandler_1.bus);
                (0, EventHandler_1.setupPrivateEventHandlers)(this.loaded_core[key], this.loaded_core.ModLoader.privateBus);
                (0, PluginLifecycle_1.setupLifecycle)(this.loaded_core[key]);
                (0, NetworkHandler_1.setupNetworkHandlers)(this.loaded_core[key]);
                (0, BindVar_1.setupBindVar)(this.loaded_core[key], this.loaded_core.ModLoader.emulator);
                (0, EventHandler_1.markPrototypeProcessed)(this.loaded_core[key]);
            }
        });
        // IExtendedCore
        if (this.loaded_core["postconstructor"] !== undefined) {
            this.loaded_core.postconstructor();
        }
        modloader64_1.internal_event_bus.emit("CORE_LOADED", { name: this.selected_core, obj: this.loaded_core });
        // Start internal plugins.
        this.processInternalPlugin(iconsole.getInternalPlugin(), iconsole);
        // Start external plugins.
        if (fs_1.default.existsSync("./load_order.json")) {
            let order = new ModLoadOrder_1.ModLoadOrder();
            order = JSON.parse(fs_1.default.readFileSync("./load_order.json").toString());
            this.logger.info("Using load order saved from GUI...");
            let files = [];
            this.plugin_directories.forEach((dir) => {
                files = (0, getAllFiles_1.getAllFiles)(dir, files);
            });
            Object.keys(order.loadOrder).forEach((key) => {
                for (let i = 0; i < files.length; i++) {
                    let p = path_1.default.parse(files[i]).base;
                    if (p === key && order.loadOrder[key] === "true") {
                        this.processFolder(path_1.default.resolve(files[i]), iconsole);
                        break;
                    }
                }
            });
        }
        else {
            this.plugin_directories.forEach((dir) => {
                if (fs_1.default.lstatSync(dir).isDirectory()) {
                    let temp1 = path_1.default.resolve(path_1.default.join(dir));
                    fs_1.default.readdirSync(temp1).forEach((file) => {
                        let temp2 = path_1.default.join(temp1, file);
                        this.processFolder(temp2, iconsole);
                    });
                }
            });
        }
        modloader64_1.internal_event_bus.on('onNetworkConnect', (evt) => {
            this.loaded_core.ModLoader.me = evt[0].me;
            this.plugins.forEach((plugin) => {
                plugin.ModLoader.me = evt[0].me;
            });
        });
    }
    loadPluginsPreInit(iconsole, net) {
        Object.freeze(NetworkHandler_1.ClientController);
        Object.freeze(NetworkHandler_1.ServerController);
        let utils = iconsole.getUtils();
        utils.hashBuffer = (buf) => {
            return crypto_1.default
                .createHash('md5')
                .update(buf)
                .digest('hex');
        };
        utils.clearBuffer = (buf) => {
            buf.fill('00', 0, buf.byteLength, 'hex');
            return buf;
        };
        utils.cloneBuffer = (buf) => {
            let b = Buffer.alloc(buf.byteLength);
            buf.copy(b);
            return b;
        };
        utils.setTimeoutFrames = (fn, frames) => {
            if (frames <= 0) {
                frames = 1;
            }
            let id = mluuid_1.ML_UUID.getUUID();
            this.frameTimeouts.set(id, new frameTimeoutContainer_1.frameTimeoutContainer(fn, frames));
            return id;
        };
        utils.setIntervalFrames = (fn, frames) => {
            if (frames <= 0) {
                frames = 1;
            }
            let id = mluuid_1.ML_UUID.getUUID();
            this.frameIntervals.set(id, new frameTimeoutContainer_1.frameTimeoutContainer(fn, frames));
            return id;
        };
        utils.clearIntervalFrames = (id) => {
            if (this.frameIntervals.has(id)) {
                return this.frameIntervals.delete(id);
            }
            return false;
        };
        utils.getUUID = () => { return mluuid_1.ML_UUID.getUUID(); };
        // Backwards compatibility is arse.
        global.ModLoader["deprecationWarnings"] = { utilBitCount8: false, utilBitCount16: false, utilBitCount32: false, utilBitCountBuffer: false };
        utils.utilBitCount8 = (value) => {
            if (!global.ModLoader.deprecationWarnings.utilBitCount8) {
                this.logger.warn("utilBitCount8 is deprecated. Please use bitCount8 on IMemory instead.");
                global.ModLoader.deprecationWarnings.utilBitCount8 = true;
            }
            return iconsole.getMemoryAccess().bitCount8(value);
        };
        utils.utilBitCount16 = (value) => {
            if (!global.ModLoader.deprecationWarnings.utilBitCount16) {
                this.logger.warn("utilBitCount16 is deprecated. Please use bitCount16 on IMemory instead.");
                global.ModLoader.deprecationWarnings.utilBitCount16 = true;
            }
            return iconsole.getMemoryAccess().bitCount16(value);
        };
        utils.utilBitCount32 = (value) => {
            if (!global.ModLoader.deprecationWarnings.utilBitCount32) {
                this.logger.warn("utilBitCount32 is deprecated. Please use bitCount32 on IMemory instead.");
                global.ModLoader.deprecationWarnings.utilBitCount32 = true;
            }
            return iconsole.getMemoryAccess().bitCount32(value);
        };
        utils.utilBitCountBuffer = (buf, offset, length) => {
            if (!global.ModLoader.deprecationWarnings.utilBitCountBuffer) {
                this.logger.warn("utilBitCountBuffer is deprecated. Please use bitCountBuffer on IMemory instead.");
                global.ModLoader.deprecationWarnings.utilBitCountBuffer = true;
            }
            return iconsole.getMemoryAccess().bitCountBuffer(buf, offset, length);
        };
        let fn = (modid) => {
            for (let i = 0; i < this.plugins.length; i++) {
                if (this.plugins[i].pluginName === modid) {
                    return true;
                }
            }
            return this.selected_core === modid;
        };
        fn = Object.freeze(fn);
        // Monkey patch Yaz0Encode to have a cache.
        let monkeypatch = new Utils_1.MonkeyPatch_Yaz0Encode(utils, iconsole.getYaz0Encoder());
        monkeypatch.patch();
        let monkeypatch2 = new Utils_1.MonkeyPatch_Yaz0Decode(utils, iconsole.getYaz0Encoder());
        monkeypatch2.patch();
        Object.freeze(utils);
        let lobby = this.config.data['NetworkEngine.Client']['lobby'];
        Object.freeze(lobby);
        let lma = Object.freeze(new LobbyManager_1.LobbyManager());
        let rom = Object.assign({}, iconsole.getRomAccess());
        (0, accessor_1.SetupRomAccessors)(rom);
        rom = Object.freeze(rom);
        let mlconfig = this.config.registerConfigCategory('ModLoader64');
        let ss;
        if (mlconfig.isClient) {
            ss = Object.freeze(new SoundSystem_1.SoundSystem());
        }
        else {
            ss = Object.freeze(new ISoundSystem_1.FakeSoundImpl());
        }
        let debug = Object.freeze(iconsole.getDebuggerAccess());
        let hr = Object.freeze(iconsole.getHiResTextureAccess());
        try {
            this.loaded_core.ModLoader.clientSide = NetworkHandler_1.ClientController;
            this.loaded_core.ModLoader.serverSide = NetworkHandler_1.ServerController;
            this.loaded_core.ModLoader.utils = utils;
            this.loaded_core.ModLoader.clientLobby = lobby;
            this.loaded_core.ModLoader.lobbyManager = lma;
            this.loaded_core.ModLoader.rom = rom;
            this.loaded_core.ModLoader.isClient = mlconfig.isClient;
            this.loaded_core.ModLoader.isServer = mlconfig.isServer;
            this.loaded_core.ModLoader.isModLoaded = fn;
            this.loaded_core.ModLoader.sound = ss;
            this.loaded_core.ModLoader.debugger = debug;
            this.loaded_core.ModLoader.hires_texture_management = hr;
            this.loaded_core.preinit();
        }
        catch (err) {
            if (err) {
                this.logger.error(err);
                this.logger.error("Failed to configure core!");
                this.logger.error(this.selected_core);
                this.logger.error(this.loaded_core.constructor.name);
                process.exit(1);
            }
        }
        this.plugins.forEach((plugin) => {
            plugin.ModLoader.clientSide = NetworkHandler_1.ClientController;
            plugin.ModLoader.serverSide = NetworkHandler_1.ServerController;
            plugin.ModLoader.utils = utils;
            plugin.ModLoader.clientLobby = lobby;
            plugin.ModLoader.lobbyManager = lma;
            plugin.ModLoader.rom = rom;
            plugin.ModLoader.isClient = mlconfig.isClient;
            plugin.ModLoader.isServer = mlconfig.isServer;
            plugin.ModLoader.sound = ss;
            plugin.ModLoader.isModLoaded = fn;
            plugin.ModLoader.debugger = debug;
            plugin.ModLoader.hires_texture_management = hr;
        });
        this.lifecycle_funcs.get("preinit" /* LifeCycleEvents.PREINIT */).forEach((value) => {
            try {
                value();
            }
            catch (err) {
                this.logger.error("preinit error");
                this.logger.error(err.stack);
                process.exit(1);
            }
        });
        if (net !== undefined) {
            this.temporaryNetworkLoop = setInterval(net.onTickPreInit.bind(net), 16);
        }
        else {
            this.temporaryNetworkLoop = setInterval(() => { }, 16);
        }
    }
    resetPlayerInstance(me) {
        this.loaded_core.ModLoader.me = me;
        this.plugins.forEach((plugin) => {
            plugin.ModLoader.me = me;
        });
    }
    loadPluginsInit(me, iconsole, net) {
        this.resetPlayerInstance(me);
        this.loaded_core.init();
        this.lifecycle_funcs.get("init" /* LifeCycleEvents.INIT */).forEach((value) => {
            try {
                value();
            }
            catch (err) {
                this.logger.error("init error");
                this.logger.error(err.stack);
                process.exit(1);
            }
        });
        this.onTickHandle = () => {
            try {
                let frame = iconsole.getFrameCount();
                this.loaded_core.onTick(frame);
                for (const [key, value] of this.lifecycle_funcs.get("ontick" /* LifeCycleEvents.ONTICK */).entries()) {
                    value(frame);
                }
                net.onTick();
                for (const [key, value] of this.lifecycle_funcs.get("onposttick" /* LifeCycleEvents.ONPOSTTICK */).entries()) {
                    value(frame);
                }
                for (const [key, value] of this.frameTimeouts.entries()) {
                    if (value.frames <= 0) {
                        value.fn();
                        this.frameTimeouts.delete(key);
                    }
                    else {
                        value.frames--;
                    }
                }
                for (const [key, value] of this.frameIntervals.entries()) {
                    if (value.frames <= 0) {
                        value.fn();
                        value.frames = value.originalFrames;
                    }
                    else {
                        value.frames--;
                    }
                }
                this.curFrame = frame;
            }
            catch (err) {
                this.logger.error("onTick error");
                this.logger.error(err.stack);
                process.exit(1);
            }
        };
        this.onViHandle = () => {
            this.lifecycle_funcs.get("onviupdate" /* LifeCycleEvents.ONVIUPDATE */).forEach((value) => {
                try {
                    value();
                }
                catch (err) {
                    this.logger.error("vi update error");
                    this.logger.error(err.stack);
                }
            });
        };
        this.onResourceHandle = () => {
            this.lifecycle_funcs.get("oncreateresources" /* LifeCycleEvents.ONCREATERESOURCES */).forEach((value) => {
                try {
                    value();
                }
                catch (err) {
                    this.logger.error("create resources error");
                    this.logger.error(err.stack);
                }
            });
        };
        Object.freeze(this.onTickHandle);
        Object.freeze(this.onViHandle);
        iconsole.on("core-started" /* Emulator_Callbacks.core_started */, () => {
            this.loaded_core.ModLoader.utils.setTimeoutFrames(() => {
                this.heapSetup();
            }, 1);
            this.loaded_core.ModLoader.utils.setTimeoutFrames(() => {
                this.injector();
            }, 20);
        });
    }
    loadPluginsPostinit(emulator, iconsole, config) {
        this.payloadManager = Object.freeze(new PayloadManager_1.PayloadManager(iconsole.getMemoryAccess(), this.logger));
        let mainConfig = this.config.registerConfigCategory('ModLoader64');
        let emu = emulator;
        let math = Object.freeze(new Math_1.Math(emu));
        this.loaded_core.ModLoader.emulator = emu;
        let savestates = iconsole.getSaveStateManager();
        Object.freeze(savestates);
        this.loaded_core.ModLoader.savestates = savestates;
        this.loaded_core.ModLoader.gui = Object.freeze(new GUITunnel_1.GUIAPI('core', this.loaded_core));
        this.loaded_core.ModLoader.payloadManager = this.payloadManager;
        this.loaded_core.ModLoader.math = math;
        let imgui = iconsole.getImGuiAccess();
        let sdl = iconsole.getSDLAccess();
        let gfx = iconsole.getGfxAccess();
        let input = iconsole.getInputAccess();
        gfx.createTexture = () => {
            //@ts-ignore
            let t = new gfx.Texture();
            return t;
        };
        gfx.createFont = () => {
            //@ts-ignore
            let f = new gfx.Font();
            return f;
        };
        Object.freeze(imgui);
        Object.freeze(sdl);
        Object.freeze(gfx);
        Object.freeze(input);
        this.loaded_core.ModLoader.ImGui = imgui;
        this.loaded_core.ModLoader.SDL = sdl;
        this.loaded_core.ModLoader.Gfx = gfx;
        this.loaded_core.ModLoader.Input = input;
        this.loaded_core.postinit();
        this.plugins.forEach((plugin) => {
            plugin.ModLoader.emulator = emu;
            plugin.ModLoader.payloadManager = this.payloadManager;
            plugin.ModLoader.math = math;
            plugin.ModLoader.gui = Object.freeze(new GUITunnel_1.GUIAPI(plugin.pluginName, plugin));
            plugin.ModLoader.savestates = savestates;
            plugin.ModLoader.ImGui = imgui;
            plugin.ModLoader.SDL = sdl;
            plugin.ModLoader.Gfx = gfx;
            plugin.ModLoader.Input = input;
        });
        this.lifecycle_funcs.get("postinit" /* LifeCycleEvents.POSTINIT */).forEach((value) => {
            try {
                value();
            }
            catch (err) {
                this.logger.error("postinit error");
                this.logger.error(err.stack);
                process.exit(1);
            }
        });
        this.plugins.forEach((plugin) => {
            if (mainConfig.isClient) {
                EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_PLUGIN_READY, plugin);
            }
            if (mainConfig.isServer) {
                EventHandler_1.bus.emit(EventHandler_1.EventsServer.ON_PLUGIN_READY, plugin);
            }
        });
        this.heapSetup = () => {
            if (config.isClient) {
                let evt = {};
                EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_HEAP_SETUP, evt);
                let heap = undefined;
                if (this.loaded_core.heap_size > 0) {
                    heap = new heap_1.Heap(this.loaded_core.ModLoader.emulator, this.loaded_core.heap_start, this.loaded_core.heap_size);
                }
                let dgfx = undefined;
                if (evt.hasOwnProperty("gfx_heap_start")) {
                    dgfx = new heap_1.Heap(this.loaded_core.ModLoader.emulator, evt.gfx_heap_start, evt.gfx_heap_size);
                }
                this.loaded_core.ModLoader.heap = heap;
                this.loaded_core.ModLoader.gfx_heap = dgfx;
                this.plugins.forEach((plugin) => {
                    plugin.ModLoader.heap = heap;
                    plugin.ModLoader.gfx_heap = dgfx;
                });
                this.loaded_core.ModLoader.utils.setTimeoutFrames(() => {
                    EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_HEAP_READY, {});
                }, 1);
            }
        };
        this.injector = () => {
            if (config.isClient) {
                this.logger.debug("Starting injection...");
                this.plugin_folders.forEach((dir) => {
                    let test = path_1.default.join(dir, 'payloads', this.header.country_code + this.header.revision.toString());
                    if (fs_1.default.existsSync(test)) {
                        if (fs_1.default.lstatSync(test).isDirectory()) {
                            fs_1.default.readdirSync(test).forEach((payload) => {
                                let result = this.payloadManager.parseFile(path_1.default.join(test, payload));
                                EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_PAYLOAD_INJECTED, {
                                    file: payload,
                                    result,
                                });
                            });
                        }
                    }
                });
                EventHandler_1.bus.emit(EventHandler_1.EventsClient.ON_INJECT_FINISHED, {});
                iconsole.finishInjects();
            }
            this.logger.debug("Injection finished.");
        };
        if (config.isClient) {
            modloader64_1.internal_event_bus.on('REGISTER_TICK_TIMEOUT', (fn) => {
                this.loaded_core.ModLoader.utils.setTimeoutFrames(fn[0], 1);
            });
            clearInterval(this.temporaryNetworkLoop);
            this.temporaryNetworkLoop = undefined;
            iconsole.on("new-frame" /* Emulator_Callbacks.new_frame */, this.onTickHandle);
            if (!config.disableVIUpdates) {
                iconsole.on("vi-update" /* Emulator_Callbacks.vi_update */, this.onViHandle);
                iconsole.on("create-resources" /* Emulator_Callbacks.create_resources */, this.onResourceHandle);
                modloader64_1.internal_event_bus.on('CoreEvent.SoftReset', () => {
                    this.logger.info("Reinvoking the payload injector...");
                    this.reinject(() => {
                        this.logger.info("Soft reset complete. Sending alert to plugins.");
                        EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_SOFT_RESET_POST, {});
                    });
                });
            }
        }
    }
    reinject(callback) {
        this.loaded_core.ModLoader.utils.setTimeoutFrames(() => {
            this.heapSetup();
        }, 1);
        this.loaded_core.ModLoader.utils.setTimeoutFrames(() => {
            this.injector();
            callback();
        }, 20);
    }
}
exports.default = pluginLoader;
//# sourceMappingURL=pluginLoader.js.map