"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const N64Header_1 = require("./N64Header");
const IModLoaderAPI_1 = require("modloader64_api/IModLoaderAPI");
const path_1 = __importDefault(require("path"));
const StartInfoImpl_1 = require("./StartInfoImpl");
const fs_1 = __importDefault(require("fs"));
const EventHandler_1 = require("modloader64_api/EventHandler");
const modloader64_1 = require("../../modloader64");
const vec_1 = require("modloader64_api/Sylvain/vec");
const module_alias_1 = __importDefault(require("module-alias"));
const slash_1 = __importDefault(require("slash"));
const Mupen_1 = require("../../../monkeypatches/Mupen");
const DynarecWrapper_1 = __importDefault(require("./DynarecWrapper"));
class N64 {
    rawModule;
    mupen;
    rom_size;
    logger;
    lobby;
    isPaused = false;
    callbacks = new Map();
    texPath = "";
    cachePath = "";
    dynawrap;
    constructor(rom, logger, lobby, config) {
        this.logger = logger;
        this.lobby = lobby;
        module_alias_1.default.addAlias("@emulator", path_1.default.join(process.cwd(), "/emulator"));
        this.rawModule = require('@emulator/ml64_emu_addon.node');
        this.mupen = this.rawModule;
        let size = (0, vec_1.xy)(800, 600);
        if (global.ModLoader.hasOwnProperty("ScreenWidth") && global.ModLoader.hasOwnProperty("ScreenHeight")) {
            size.x = global.ModLoader["ScreenWidth"];
            size.y = global.ModLoader["ScreenHeight"];
        }
        else {
            if (fs_1.default.existsSync(path_1.default.join(".", "emulator", "mupen64plus.cfg"))) {
                let opts = {};
                let mupen = fs_1.default.readFileSync(path_1.default.join(".", "emulator", "mupen64plus.cfg")).toString();
                let lines = mupen.split("\n");
                for (let i = 0; i < lines.length; i++) {
                    if (lines[i].indexOf("[") > -1) {
                        continue;
                    }
                    if (lines[i].indexOf("#") > -1) {
                        continue;
                    }
                    if (lines[i].trim() === "") {
                        continue;
                    }
                    let s = lines[i].split("=");
                    opts[s[0].trim()] = s[1].trim().replace(/['"]+/g, "");
                }
                global.ModLoader["ScreenWidth"] = parseInt(opts["ScreenWidth"]);
                global.ModLoader["ScreenHeight"] = parseInt(opts["ScreenHeight"]);
            }
            else {
                global.ModLoader["ScreenWidth"] = 800;
                global.ModLoader["ScreenHeight"] = 600;
            }
        }
        size.x = global.ModLoader["ScreenWidth"];
        size.y = global.ModLoader["ScreenHeight"];
        let emu_dir = global["module-alias"]["moduleAliases"]["@emulator"];
        let _config = config.registerConfigCategory("Mupen64Plus");
        config.setData("Mupen64Plus", "rsp", "mupen64plus-rsp-hle");
        config.setData("Mupen64Plus", "video", "mupen64plus-video-gliden64");
        config.setData("Mupen64Plus", "audio", "mupen64plus-audio-sdl");
        config.setData("Mupen64Plus", "input", "mupen64plus-input-sdl");
        this.mupen.Frontend.startup(new StartInfoImpl_1.StartInfoImpl(global.ModLoader["WINDOW_TITLE"], size.x, size.y, emu_dir + "/mupen64plus", `${emu_dir}/${_config.rsp}`, `${emu_dir}/${_config.video}`, `${emu_dir}/${_config.audio}`, `${emu_dir}/${_config.input}`, emu_dir, emu_dir));
        this.texPath = this.mupen.M64p.Config.openSection("Video-GLideN64").getStringOr("txPath", "");
        this.cachePath = this.mupen.M64p.Config.openSection("Video-GLideN64").getStringOr("txCachePath", "");
        let doEvents = setInterval(() => this.mupen.Frontend.doEvents(), 10);
        const _64_MB = 64 * 1024 * 1024;
        let section = this.mupen.M64p.Config.openSection("Core");
        let screenshot_dir = path_1.default.resolve("./", "screenshots");
        if (!fs_1.default.existsSync(screenshot_dir)) {
            fs_1.default.mkdirSync(screenshot_dir);
        }
        section.setString("ScreenshotPath", screenshot_dir);
        this.mupen.M64p.Config.saveFile();
        this.registerCallback('window-closing', () => {
            if (this.mupen.M64p.getEmuState() === 3 /* EmuState.Paused */) {
                this.mupen.M64p.resume();
            }
            if (this.mupen.M64p.getEmuState() === 2 /* EmuState.Running */) {
                this.mupen.Frontend.stop();
            }
        });
        this.registerCallback('core-stopped', () => {
            clearInterval(doEvents);
            this.mupen.Frontend.shutdown();
            modloader64_1.internal_event_bus.emit('SHUTDOWN_EVERYTHING', {});
            setTimeout(() => {
                process.exit(0);
            }, 3000);
        });
        this.registerCallback('core-event', (event, data) => {
            if (event == 6 /* CoreEvent.SoftReset */) {
                try {
                    modloader64_1.internal_event_bus.emit("REGISTER_TICK_TIMEOUT", () => {
                        this.logger.info("Soft reset detected. Sending alert to plugins.");
                        EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_SOFT_RESET_PRE, {});
                        this.logger.info("Letting the reset go through...");
                        this.softReset();
                        modloader64_1.internal_event_bus.emit("CoreEvent.SoftReset", {});
                    });
                }
                catch (err) {
                    this.logger.error(err.stack);
                }
            }
            else if (event == 9 /* CoreEvent.TakeNextScreenshot */) {
                this.mupen.Frontend.takeNextScreenshot();
            }
            else if (event == 12 /* CoreEvent.VolumeUp */) {
                this.mupen.M64p.setAudioVolume(this.mupen.M64p.getAudioVolume() + 1);
                global.ModLoader["GLOBAL_VOLUME"] = this.mupen.M64p.getAudioVolume();
                EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_VOLUME_CHANGE, global.ModLoader["GLOBAL_VOLUME"]);
            }
            else if (event == 13 /* CoreEvent.VolumeDown */) {
                this.mupen.M64p.setAudioVolume(this.mupen.M64p.getAudioVolume() - 1);
                global.ModLoader["GLOBAL_VOLUME"] = this.mupen.M64p.getAudioVolume();
                EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_VOLUME_CHANGE, global.ModLoader["GLOBAL_VOLUME"]);
            }
            else if (event == 11 /* CoreEvent.VolumeMute */) {
                this.mupen.M64p.setAudioMuted(!this.mupen.M64p.isAudioMuted());
            }
            else if (event == 14 /* CoreEvent.SetFastForward */) {
                this.mupen.M64p.setSpeedFactor(300);
            }
            else if (event == 17 /* CoreEvent.UnsetFastForward */) {
                this.mupen.M64p.setSpeedFactor(100);
            }
            else if (event == 8 /* CoreEvent.SpeedUp */) {
                this.mupen.M64p.setSpeedFactor(this.mupen.M64p.getSpeedFactor() + 1);
            }
            else if (event == 7 /* CoreEvent.SpeedDown */) {
                this.mupen.M64p.setSpeedFactor(this.mupen.M64p.getSpeedFactor() - 1);
            }
            else if (event == 10 /* CoreEvent.TogglePause */) {
                if (!this.isPaused) {
                    this.mupen.M64p.pause();
                    this.isPaused = true;
                }
                else {
                    this.mupen.M64p.resume();
                    this.isPaused = false;
                }
            }
            else if (event == 2 /* CoreEvent.Stop */) {
                modloader64_1.internal_event_bus.emit("SHUTDOWN_EVERYTHING", {});
                setTimeout(() => {
                    process.exit(0);
                }, 3000);
            }
            else if (event == 0 /* CoreEvent.ChangeWindow */) {
                this.mupen.Frontend.toggleFullScreen();
            }
        });
        if (config.registerConfigCategory("ModLoader64").enableDebugger) {
            let conf = this.mupen.M64p.Config.openSection("Core");
            conf.setInt("R4300Emulator", 0);
            conf.setBool("EnableDebugger", true);
            conf.save();
            this.registerCallback("debug-init", () => {
                console.log("DEBUGGER INITIALIZED");
                this.mupen.M64p.Debugger.setRunState(2 /* RunState.Running */);
            });
            this.registerCallback("debug-update", (pc) => {
                console.log(`DEBUGGER: ${pc.toString(16)}`);
                let trig = this.mupen.M64p.Debugger.bpTriggeredBy();
                EventHandler_1.bus.emit("debug-update" /* DebuggerEvents.UPDATE */, trig);
            });
        }
        else {
            let conf = this.mupen.M64p.Config.openSection("Core");
            conf.setInt("R4300Emulator", 2);
            conf.setBool("EnableDebugger", false);
            conf.save();
        }
        logger.info("Loading rom: " + rom + ".");
        if (rom === "") {
            this.logger.error("No rom selected!");
            process.exit(2 /* ModLoaderErrorCodes.NO_ROM */);
        }
        if (!fs_1.default.existsSync(rom)) {
            this.logger.error("No rom selected!");
            process.exit(2 /* ModLoaderErrorCodes.NO_ROM */);
        }
        let _rom = fs_1.default.readFileSync(rom);
        this.mupen.M64p.openRomFromMemory(_rom, _64_MB);
        this.rom_size = _rom.byteLength;
        EventHandler_1.bus.on('openInputConfig', () => {
            this.mupen.Frontend.openInputConfig();
        });
        EventHandler_1.bus.on('openMemViewer', () => {
            this.mupen.Frontend.openMemViewer();
        });
        EventHandler_1.bus.on('openCheatConfig', () => {
            this.mupen.Frontend.openCheatConfig();
        });
        EventHandler_1.bus.on('toggleFullScreen', () => {
            this.mupen.Frontend.toggleFullScreen();
        });
        EventHandler_1.bus.on(IModLoaderAPI_1.ModLoaderEvents.OVERRIDE_TEXTURE_PATH, (p) => {
            this.mupen.M64p.Config.openSection("Video-GLideN64").setString("txPath", p);
            this.mupen.M64p.Config.openSection("Video-GLideN64").setString("txCachePath", path_1.default.resolve(path_1.default.parse(p).dir, "cache"));
            this.mupen.M64p.Config.openSection("Video-GLideN64").setBool("txHiresEnable", true);
            this.mupen.M64p.Config.openSection("Video-GLideN64").setBool("txHiresFullAlphaChannel", true);
            this.mupen.M64p.Config.openSection("Video-GLideN64").setBool("txEnhancedTextureFileStorage", false);
            this.mupen.M64p.Config.openSection("Video-GLideN64").setBool("txHiresTextureFileStorage", false);
            this.mupen.M64p.Config.openSection("Video-GLideN64").setBool("txSaveCache", true);
            this.mupen.M64p.Config.saveFile();
        });
        Mupen_1.MupenMonkeyPatches.patch(this.mupen);
        this.dynawrap = new DynarecWrapper_1.default(this.mupen);
    }
    nopCRC(rom) {
        let start = rom.subarray(0, 0x1000);
        let _s = start.indexOf(Buffer.from('8D680010', 'hex'));
        let _c = _s;
        let ok = false;
        if (_s > -1) {
            _c += 3;
            if (start.slice(_c, _c + 3).equals(Buffer.from('1014E8', 'hex'))) {
                _c += 4;
                if (start.readUInt8(_c) === 0x06) {
                    _c += 5;
                    if (start.slice(_c, _c + 2).equals(Buffer.from('8D68', 'hex'))) {
                        _c += 3;
                        if (start.slice(_c, _c + 3).equals(Buffer.from('141608', 'hex'))) {
                            _c += 4;
                            if (start.readUInt8(_c) === 0x03) {
                                _c += 0xD;
                                if (start.slice(_c, _c + 4).equals(Buffer.from('0411FFFF', 'hex'))) {
                                    ok = true;
                                }
                            }
                        }
                    }
                }
            }
        }
        if (ok) {
            this.logger.debug("Found standard crc check.");
            let payload_4 = Buffer.alloc(4);
            let payload_3 = Buffer.alloc(3);
            let payload_2 = Buffer.alloc(2);
            let payload_1 = Buffer.alloc(1);
            payload_4.copy(start, _s + 0);
            payload_3.copy(start, _s + 3);
            payload_1.copy(start, _s + 7);
            payload_2.copy(start, _s + 12);
            payload_3.copy(start, _s + 15);
            payload_1.copy(start, _s + 19);
            payload_4.copy(start, _s + 32);
        }
    }
    registerCallback(type, callback) {
        if (!this.callbacks.has(type)) {
            this.callbacks.set(type, []);
            this.mupen.Frontend.on(type, (event, data) => {
                for (let i = 0; i < this.callbacks.get(type).length; i++) {
                    this.callbacks.get(type)[i](event, data);
                }
            });
        }
        this.callbacks.get(type).push(callback);
    }
    getYaz0Encoder() {
        return this.mupen.Yaz0;
    }
    getInputAccess() {
        return this.mupen.M64p.Input;
    }
    getGfxAccess() {
        return this.mupen.Gfx;
    }
    getSDLAccess() {
        return this.mupen.SDL;
    }
    getImGuiAccess() {
        return this.mupen.ImGui;
    }
    on(which, callback) {
        this.registerCallback(which, callback);
    }
    startEmulator(preStartCallback) {
        let rom_r = this.mupen.M64p.Memory;
        let buf = preStartCallback();
        if (Buffer.isBuffer(buf)) {
            this.nopCRC(buf);
            rom_r.romWriteBuffer(0x0, buf);
        }
        this.setSaveDir(path_1.default.relative(path_1.default.resolve(global["module-alias"]["moduleAliases"]["@emulator"]), path_1.default.resolve(global["module-alias"]["moduleAliases"]["@emulator"], "saves", this.lobby)));
        this.mupen.Frontend.execute();
        modloader64_1.internal_event_bus.on('emulator_started', () => {
            global.ModLoader["GLOBAL_VOLUME"] = this.mupen.M64p.getAudioVolume();
            EventHandler_1.bus.emit(IModLoaderAPI_1.ModLoaderEvents.ON_VOLUME_CHANGE, global.ModLoader["GLOBAL_VOLUME"]);
        });
        modloader64_1.internal_event_bus.on('emulator_started', () => {
            if (this.texPath !== "") {
                /** @TODO rewrite all this shit. */
                this.mupen.M64p.Config.openSection("Video-GLideN64").setString("txPath", (0, slash_1.default)(this.texPath));
                this.mupen.M64p.Config.openSection("Video-GLideN64").setString("txCachePath", (0, slash_1.default)(this.cachePath));
            }
            this.mupen.M64p.Config.saveFile();
        });
        return this.mupen.M64p.Memory;
    }
    stopEmulator() {
        this.mupen.Frontend.stop();
    }
    finishInjects() {
        this.mupen.M64p.Memory.invalidateCachedCode();
    }
    isEmulatorReady() {
        return this.mupen.M64p.getEmuState() === 2 /* EmuState.Running */;
    }
    getLoadedRom() {
        let rom_r = this.mupen.M64p.Memory;
        const _64_MB = 64 * 1024 * 1024;
        let buf = rom_r.romReadBuffer(0x0, _64_MB);
        return buf;
    }
    getRomOriginalSize() {
        return this.rom_size;
    }
    getFrameCount() {
        return this.mupen.M64p.getNumElapsedFrames();
    }
    setFrameCount(num) {
    }
    pauseEmulator() {
        this.mupen.M64p.pause();
    }
    resumeEmulator() {
        this.mupen.M64p.resume();
    }
    getRomHeader() {
        let raw = this.mupen.M64p.Memory.romReadBuffer(0x0, 0x50);
        return new N64Header_1.N64Header(raw);
    }
    getMemoryAccess() {
        return this.mupen.M64p.Memory;
    }
    getRomAccess() {
        return this.mupen.M64p.Memory;
    }
    softReset() {
        this.mupen.M64p.softReset();
    }
    hardReset() {
        this.mupen.M64p.hardReset();
    }
    saveState(file) {
        this.mupen.M64p.saveStateToFile(file);
    }
    loadState(file) {
        this.mupen.M64p.loadStateFromFile(file);
    }
    setSaveDir(path) {
        let section = this.mupen.M64p.Config.openSection('Core');
        section.setString('SaveSRAMPath', path);
        section.save();
    }
    getUtils() {
        return this.mupen.Yaz0;
    }
    getSaveStateManager() {
        return this.mupen.M64p;
    }
    getDebuggerAccess() {
        return this.mupen.M64p.Debugger;
    }
    getInternalPlugin() {
        return path_1.default.resolve(__dirname, "MenubarPlugin.js");
    }
    getHiResTextureAccess() {
        return this.mupen.M64p;
    }
}
exports.default = N64;
//# sourceMappingURL=N64.js.map