"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.internal_event_bus = void 0;
const config_1 = __importDefault(require("./config"));
const pluginLoader_1 = __importDefault(require("./pluginLoader"));
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 GUITunnel_1 = require("modloader64_api/GUITunnel");
const crypto_1 = __importDefault(require("crypto"));
const pakVerifier_1 = require("./pakVerifier");
const PakFormat_1 = require("modloader64_api/PakFormat");
const adm_zip_1 = __importDefault(require("adm-zip"));
const module_alias_1 = __importDefault(require("module-alias"));
const PatchManager_1 = require("modloader64_api/Patchers/PatchManager");
const ModLoaderRPC_1 = require("./rpc/ModLoaderRPC");
const Cloudmax_1 = require("./Cloudmax");
const getAllFiles_1 = require("./getAllFiles");
const PakPatch_1 = require("./PakPatch");
const ConsoleManager_1 = __importDefault(require("./ConsoleManager"));
const MupenDescriptor_1 = require("./consoles/mupen/MupenDescriptor");
const NetworkEngine2_1 = require("./networking/NetworkEngine2");
exports.internal_event_bus = new EventHandler_1.EventBus();
class ModLoader64 {
    logger;
    config = new config_1.default(process.cwd() + '/ModLoader64-config.json');
    data = this.config.registerConfigCategory('ModLoader64');
    clientConfig = this.config.registerConfigCategory('NetworkEngine.Client');
    plugins;
    rom_folder = './roms';
    mods_folder = './mods';
    roms;
    Server;
    Client;
    RPC;
    rom_path;
    emulator;
    tunnel;
    done = false;
    isFirstRun = false;
    consoleDescManager;
    constructor(logger, discord) {
        // TODO: Move this?
        module_alias_1.default.addAlias("@sound", path_1.default.join(process.cwd(), "/emulator"));
        global.ModLoader["logger"] = logger;
        if (global.ModLoader.hasOwnProperty("OVERRIDE_MODS_FOLDER")) {
            this.mods_folder = global.ModLoader.OVERRIDE_MODS_FOLDER;
        }
        if (global.ModLoader.hasOwnProperty("OVERRIDE_ROMS_FOLDER")) {
            this.rom_folder = global.ModLoader.OVERRIDE_ROMS_FOLDER;
        }
        if (!fs_1.default.existsSync(this.rom_folder)) {
            fs_1.default.mkdirSync(this.rom_folder);
        }
        if (!fs_1.default.existsSync(this.mods_folder)) {
            fs_1.default.mkdirSync(this.mods_folder);
        }
        this.roms = fs_1.default.readdirSync(this.rom_folder);
        this.logger = logger;
        this.consoleDescManager = new ConsoleManager_1.default(this.logger.getLogger("BindingManager"));
        let mods_folder_array = [this.mods_folder];
        this.plugins = new pluginLoader_1.default(mods_folder_array, this.config, this.logger.getLogger("PluginLoader"));
        this.Server = new NetworkEngine2_1.NetworkEngine2_Server(this.logger.getLogger("NetworkEngine.Server"), this.config);
        this.Client = new NetworkEngine2_1.NetworkEngine2_Client(this.logger.getLogger("NetworkEngine.Client"), this.config, discord);
        this.RPC = new ModLoaderRPC_1.ModLoaderRPC();
        if (process.platform === 'win32') {
            let rl = require('readline').createInterface({
                input: process.stdin,
                output: process.stdout,
            });
            rl.on('SIGINT', function () {
                // @ts-ignore
                process.emit('SIGINT');
            });
        }
        process.on('SIGINT', function () {
            //graceful shutdown
            exports.internal_event_bus.emit('SHUTDOWN_EVERYTHING', {});
            process.exit(0);
        });
        process.on('message', (msg) => {
            let packet = JSON.parse(msg);
            EventHandler_1.bus.emit(packet.event, packet);
        });
        EventHandler_1.bus.on('SHUTDOWN_EVERYTHING', () => {
            exports.internal_event_bus.emit('SHUTDOWN_EVERYTHING', {});
        });
    }
    start() {
        this.tunnel = new GUITunnel_1.GUITunnel(process, 'internal_event_bus', this);
        let ofn = exports.internal_event_bus.emit.bind(exports.internal_event_bus);
        ((inst) => {
            exports.internal_event_bus.emit = function (event, ...values) {
                try {
                    inst.tunnel.send(event, values);
                }
                catch (err) {
                }
                return ofn(event, values);
            };
        })(this);
        exports.internal_event_bus.on('SHUTDOWN_EVERYTHING', () => {
            this.emulator.stopEmulator();
        });
        let BPS = require('./BPS');
        (0, PatchManager_1.registerPatchType)(".bps", new BPS());
        (0, PatchManager_1.registerPatchType)(".txt", new Cloudmax_1.Cloudmax());
        (0, PatchManager_1.registerPatchType)(".pak", new PakPatch_1.PakPatch());
        this.preinit();
    }
    preinit() {
        // Figure out what consoles we have available.
        this.consoleDescManager.registerConsole(new MupenDescriptor_1.MupenDescriptor());
        let bindings_dir = "./bindings";
        if (!fs_1.default.existsSync(bindings_dir)) {
            fs_1.default.mkdirSync(bindings_dir);
        }
        fs_1.default.readdirSync(bindings_dir).forEach((d) => {
            let dir = path_1.default.resolve(bindings_dir, d);
            let meta = path_1.default.resolve(dir, "package.json");
            if (!fs_1.default.existsSync(meta))
                return;
            let data = JSON.parse(fs_1.default.readFileSync(meta).toString());
            let main = path_1.default.resolve(dir, data.main);
            let constr = require(main).default;
            this.consoleDescManager.registerConsole(new constr());
        });
        // Set up config.
        this.config.setData('ModLoader64', 'rom', 'Legend of Zelda, The - Ocarina of Time (U) (V1.0) [!].z64');
        this.config.setData('ModLoader64', 'patch', '');
        this.config.setData('ModLoader64', 'isServer', true);
        this.config.setData('ModLoader64', 'isClient', true);
        this.config.setData('ModLoader64', 'supportedConsoles', this.consoleDescManager.getAllConsoleTags(), true);
        this.config.setData('ModLoader64', 'selectedConsole', 'Mupen64Plus');
        this.config.setData('ModLoader64', 'coreOverride', '');
        this.config.setData('ModLoader64', 'disableVIUpdates', false);
        this.config.setData('ModLoader64', 'enableDebugger', false);
        if (this.data.supportedConsoles.indexOf(this.data.selectedConsole) === -1) {
            this.data.selectedConsole = this.data.supportedConsoles[0];
        }
        if (global.ModLoader["FIRST_RUN"]) {
            process.exit(0);
        }
        let roms = (0, getAllFiles_1.getAllFiles)(this.rom_folder, []);
        for (let i = 0; i < roms.length; i++) {
            let p = path_1.default.parse(roms[i]);
            if (p.base === this.data['rom']) {
                this.rom_path = path_1.default.resolve(roms[i]);
                break;
            }
        }
        if (this.rom_path === undefined) {
            this.rom_path = "";
        }
        if (path_1.default.parse(this.rom_path).ext === ".zip") {
            // Some dumbass tried to load a zip file. Babysit it.
            let temp = fs_1.default.mkdtempSync("ModLoader64_temp_");
            let z = new adm_zip_1.default(this.rom_path);
            z.extractAllTo(temp, true);
            fs_1.default.readdirSync(temp).forEach((file) => {
                let p = path_1.default.parse(file);
                this.rom_path = path_1.default.resolve(path_1.default.join(temp, p.base));
            });
        }
        EventHandler_1.bus.on(IModLoaderAPI_1.ModLoaderEvents.ON_EXTERNAL_API_REGISTER, (data) => {
            if (!data.processed) {
                data.processed = true;
                this.logger.debug(`Loading API: ${data.name}@${data.version}`);
                module_alias_1.default.addAlias(data.name, data.path);
                module_alias_1.default.addAlias(`@${data.name}`, data.path);
            }
        });
        if (fs_1.default.existsSync("./lib")) {
            fs_1.default.readdirSync("./lib").forEach((f) => {
                let file = path_1.default.resolve("./lib", f);
                if (fs_1.default.lstatSync(file).isDirectory()) {
                    let m = fs_1.default.readdirSync(file);
                    for (let i = 0; i < m.length; i++) {
                        let m1 = path_1.default.resolve(file, m[i]);
                        module_alias_1.default.addAlias(path_1.default.parse(m1).name, m1);
                        module_alias_1.default.addAlias(`@${path_1.default.parse(m1).name}`, m1);
                    }
                }
                else {
                    this.logger.warn(`${file} is not a directory!`);
                }
            });
        }
        let auto_wire_cores = (p) => {
            if (fs_1.default.lstatSync(p).isFile())
                return;
            fs_1.default.readdirSync(p).forEach(file => {
                let f = path_1.default.join(p, file);
                if (!fs_1.default.lstatSync(f).isDirectory()) {
                    let parse = path_1.default.parse(file);
                    if (parse.ext === ".js") {
                        try {
                            let rq = require(path_1.default.resolve(f));
                            let p = rq[parse.name];
                            if (rq.hasOwnProperty("default")) {
                                p = rq["default"];
                            }
                            let i = new p();
                            this.plugins.registerCorePlugin(i.constructor.name, i);
                            this.logger.info('Auto-wiring core: ' + i.constructor.name);
                        }
                        catch (err) {
                            console.log(err);
                        }
                    }
                }
                else {
                    console.log(`Looking deeper for cores... ${f}`);
                    if (path_1.default.parse(f).name === "build") {
                        console.log("Found a build folder");
                        fs_1.default.readdirSync(f).forEach((f1) => {
                            let f2 = path_1.default.join(f, f1);
                            if (fs_1.default.lstatSync(f2).isDirectory()) {
                                console.log(`Found some shit in the build folder ${f2}`);
                                auto_wire_cores(f2);
                            }
                        });
                    }
                }
            });
        };
        this.logger.info('Loading internal cores...');
        auto_wire_cores(path_1.default.resolve(path_1.default.join(__dirname, '/cores')));
        let cores_folder = "./cores";
        if (global.ModLoader.hasOwnProperty("OVERRIDE_CORES_FOLDER")) {
            cores_folder = global.ModLoader.OVERRIDE_CORES_FOLDER;
        }
        if (!fs_1.default.existsSync(cores_folder)) {
            fs_1.default.mkdirSync(cores_folder);
        }
        module_alias_1.default.addPath(path_1.default.resolve(cores_folder));
        this.logger.info('Loading external cores...');
        let ext_core_path = path_1.default.resolve(path_1.default.join(cores_folder));
        fs_1.default.readdirSync(ext_core_path).forEach(file => {
            let f = path_1.default.join(ext_core_path, file);
            if (f.indexOf(".disabled") === -1) {
                if (path_1.default.parse(f).ext === ".pak") {
                    let pak = new PakFormat_1.Pak(f);
                    let v = new pakVerifier_1.pakVerifier(this.logger);
                    if (v.verifyPak(pak, f)) {
                        let dir = v.extractPakToTemp(pak, f);
                        let d = path_1.default.resolve(dir, '../');
                        module_alias_1.default.addPath(d);
                        auto_wire_cores(dir);
                    }
                }
                else {
                    auto_wire_cores(f);
                }
            }
        });
        let side = 2 /* ProxySide.UNIVERSAL */;
        if (this.data.isServer)
            side = 1 /* ProxySide.SERVER */;
        if (this.data.isClient)
            side = 0 /* ProxySide.CLIENT */;
        this.emulator = this.consoleDescManager.getConsole(this.data.selectedConsole).constructConsole(side, this.rom_path, this.logger, this.clientConfig.lobby, this.config);
        exports.internal_event_bus.emit('preinit_done', {});
        this.init();
    }
    init() {
        global.ModLoader["startupDelay"] = 0;
        let loaded_rom_header = this.emulator.getRomHeader();
        let core_match = null;
        let core_key = '';
        if (this.data.coreOverride !== '') {
            core_key = this.data.coreOverride;
            core_match = this.plugins.core_plugins[core_key];
        }
        if (fs_1.default.existsSync(this.rom_path)) {
            this.logger.info('Parsing rom header...');
            Object.keys(this.plugins.core_plugins).forEach((key) => {
                if (typeof this.plugins.core_plugins[key].header === "string") {
                    if (loaded_rom_header.id === this.plugins.core_plugins[key].header) {
                        core_match = this.plugins.core_plugins[key];
                        core_key = key;
                    }
                }
                else if (this.plugins.core_plugins[key].header instanceof Array) {
                    for (let i = 0; i < this.plugins.core_plugins[key].header.length; i++) {
                        if (loaded_rom_header.id === this.plugins.core_plugins[key].header[i]) {
                            core_match = this.plugins.core_plugins[key];
                            core_key = key;
                        }
                    }
                }
            });
        }
        if (core_match !== null) {
            this.logger.info('Auto-selected core: ' + core_key);
            this.plugins.selected_core = core_key;
        }
        else {
            this.logger.error('Failed to find a compatible core for the selected rom!');
            this.logger.debug(JSON.stringify(loaded_rom_header, null, 2));
            this.logger.info('Setting core to DummyCore.');
            this.plugins.selected_core = 'DummyCore';
        }
        // Load the plugins
        this.plugins.loadPluginsConstruct(loaded_rom_header, this.emulator);
        EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_ROM_PATH, this.rom_path);
        let evt = { rom: this.emulator.getLoadedRom() };
        EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_PRE_ROM_LOAD, evt);
        if (this.data.isClient) {
            this.emulator.getRomAccess().romWriteBuffer(0x0, evt.rom);
        }
        EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_ROM_HEADER_PARSED, loaded_rom_header);
        this.plugins.loadPluginsPreInit(this.emulator, this.Client);
        exports.internal_event_bus.emit('onPreInitDone', {});
        // Set up networking.
        exports.internal_event_bus.on('onNetworkConnect', (evt) => {
            let nextStage = new Promise((resolve, reject) => {
                let wait = setInterval(() => {
                    if (global.ModLoader.startupDelay === 0) {
                        clearInterval(wait);
                        resolve(undefined);
                    }
                }, 1);
            }).then(() => {
                this.postinit(evt);
            });
        });
        if (this.data.isServer) {
            this.Server.setup();
        }
        if (this.data.isClient) {
            this.Client.setup();
            this.RPC.setup();
        }
        exports.internal_event_bus.emit('onInitDone', {});
    }
    postinit(result) {
        if (this.done) {
            this.plugins.resetPlayerInstance(result[0].me);
            return;
        }
        if (fs_1.default.existsSync(this.rom_path) || this.data.isServer) {
            this.plugins.loadPluginsInit(result[0].me, this.emulator, this.Client);
            this.logger.info(`Setting up ${this.data.selectedConsole}...`);
            let instance = this;
            let mupen;
            let load_mupen = new Promise(function (resolve, reject) {
                if (instance.data.isClient) {
                    if (!fs_1.default.existsSync('./saves')) {
                        fs_1.default.mkdirSync('./saves');
                    }
                    if (!fs_1.default.existsSync(path_1.default.join('./saves', instance.Client.config.lobby))) {
                        fs_1.default.mkdirSync(path_1.default.join('./saves', instance.Client.config.lobby));
                    }
                    instance.emulator.setSaveDir(path_1.default.resolve(path_1.default.join('./saves', instance.Client.config.lobby)) + '/');
                    mupen = instance.emulator.startEmulator(() => {
                        let p = result[0].patch;
                        let rom_data = instance.emulator.getLoadedRom();
                        let evt = { rom: rom_data };
                        if (instance.data.isClient) {
                            try {
                                EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_ROM_PATCHED_PRE, evt);
                            }
                            catch (err) {
                                throw err;
                            }
                        }
                        if (p.byteLength > 1 && rom_data.byteLength > 1) {
                            try {
                                let hash = crypto_1.default.createHash('md5').update(rom_data).digest('hex');
                                instance.logger.info('Patching rom...');
                                let nbuf = PatchManager_1.PatchTypes.get(path_1.default.parse(result[0].patch_name).ext).patch(rom_data.slice(0, instance.emulator.getRomOriginalSize()), p);
                                if (nbuf !== undefined) {
                                    nbuf.copy(rom_data);
                                }
                                let newHash = crypto_1.default.createHash('md5').update(rom_data).digest('hex');
                                instance.logger.info(hash);
                                instance.logger.info(newHash);
                                evt["hash"] = newHash;
                                evt["oldhash"] = hash;
                            }
                            catch (err) {
                                if (err) {
                                    instance.logger.error(err);
                                    process.exit(3 /* ModLoaderErrorCodes.BPS_FAILED */);
                                }
                            }
                        }
                        try {
                            EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_ROM_PATCHED, evt);
                            EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_ROM_PATCHED_POST, evt);
                        }
                        catch (err) {
                            throw err;
                        }
                        return evt.rom;
                    });
                    let wait = setInterval(() => {
                        if (instance.emulator.isEmulatorReady()) {
                            clearInterval(wait);
                            exports.internal_event_bus.emit('emulator_started', {});
                            resolve(undefined);
                        }
                    }, 1);
                }
                else {
                    resolve(undefined);
                }
            });
            load_mupen.then(function () {
                try {
                    instance.logger.info('Finishing plugin init...');
                    instance.plugins.loadPluginsPostinit(mupen, instance.emulator, instance.data);
                    exports.internal_event_bus.emit('onPostInitDone', {});
                    instance.done = true;
                }
                catch (err) {
                    console.log(err);
                }
            }).catch((err) => {
                this.logger.error(err.stack);
                process.exit(1);
            });
        }
    }
}
exports.default = ModLoader64;
//# sourceMappingURL=modloader64.js.map