state("bgb") {}
state("bgb64") {}
state("gambatte") {}
state("gambatte_qt") {}
state("gambatte_qt_nonpsr") {}
state("gambatte_speedrun") {}
state("emuhawk") {}

startup {
    //-------------------------------------------------------------//
    settings.Add("entrances", true, "Dungeon Entrances");
    settings.Add("instruments", true, "Dungeon Ends (Instruments)");
    settings.Add("items", true, "Items");
    settings.Add("misc", true, "Miscellaneous");

    settings.CurrentDefaultParent = "entrances";
    settings.Add("d1Enter", true, "Tail Cave (D1)");
    settings.Add("d2Enter", true, "Bottle Grotto (D2)");
    settings.Add("d3Enter", true, "Key Cavern (D3)");
    settings.Add("d4Enter", true, "Angler's Tunnel (D4)");
    settings.Add("d5Enter", true, "Catfish's Maw (D5)");
    settings.Add("d6Enter", true, "Face Shrine (D6)");
    settings.Add("d7Enter", true, "Eagle's Tower (D7)");
    settings.Add("d8Enter", true, "Turtle Rock (D8)");
    settings.Add("d0Enter", false, "Color Dungeon (D0)");

    settings.CurrentDefaultParent = "instruments";
    settings.Add("d1End", true, "Full Moon Cello (D1)");
    settings.Add("d2End", true, "Conch Horn (D2)");
    settings.Add("d3End", true, "Sea Lily's Bell (D3)");
    settings.Add("d4End", true, "Surf Harp (D4)");
    settings.Add("d5End", true, "Wind Marimba (D5)");
    settings.Add("d6End", true, "Coral Triangle (D6)");
    settings.Add("d7End", true, "Organ of Evening Calm (D7)");
    settings.Add("d8End", true, "Thunder Drum (D8)");
    settings.Add("d0End", false, "Tunic Upgrade (D0)");
    settings.Add("eggStairs", true, "Wind Fish's Egg (stairs)");

    settings.CurrentDefaultParent = "items";
    settings.Add("tailKey", false, "Tail Key");
    settings.Add("slimeKey", false, "Slime Key");
    settings.Add("anglerKey", false, "Angler Key");
    settings.Add("faceKey", false, "Face Key");
    settings.Add("birdKey", false, "Bird Key");
    settings.Add("feather", false, "Feather");
    settings.Add("bracelet", false, "Bracelet (L1)");
    settings.Add("boots", false, "Boots");
    settings.Add("ocarina", false, "Ocarina");
    settings.Add("flippers", false, "Flippers");
    settings.Add("hookshot", false, "Hookshot");
    settings.Add("l2Shield", false, "Shield (L2)");
    settings.Add("magicRod", false, "Magic Rod");
    settings.Add("magnifyingLens", false, "Magnifying Lens");
    settings.Add("boomerang", false, "Boomerang");
    settings.Add("l1Sword", false, "Sword (L1)");
    settings.Add("l2Sword", false, "Sword (L2)");
    settings.Add("yoshi", false, "Yoshi Doll");

    settings.CurrentDefaultParent = "misc";
    settings.Add("house", false, "Leave starting house");
    settings.Add("woods", false, "Leaving the Mysterious Woods");
    settings.Add("shop", false, "Shoplifting");
    settings.Add("marin", false, "Marin");
    settings.Add("walrus", false, "Walrus");
    settings.Add("d2Exit", false, "Exit D2");
    settings.Add("d8Exit", false, "Exit D8 to Mountaintop");
    settings.Add("song1", false, "Song #1 (Ballad of the Wind Fish)");
    settings.Add("song2", false, "Song #2 (Manbo's Mambo)");
    settings.Add("song3", false, "Song #3 (Frog's Song of Soul)");
    settings.Add("creditsWarp", false, "Credits Warp (ACE)");
    //-------------------------------------------------------------//

    refreshRate = 0.5;

    vars.TryFindOffsets = (Func<Process, int, long, bool>)((proc, memorySize, baseAddress) => {
        long romOffset = 0;
        long wramOffset = 0;
        string state = proc.ProcessName.ToLower();
        if (state.Contains("gambatte")) {
            IntPtr scanOffset = vars.SigScan(proc, 0, "20 ?? ?? ?? 20 ?? ?? ?? 20 ?? ?? ?? 20 ?? ?? ?? 05 00 00");
            romOffset = (long)scanOffset - 0x18;
            wramOffset = (long)scanOffset - 0x10;
        } else if (state == "emuhawk") {
            IntPtr scanOffset = vars.SigScan(proc, 0, "05 00 00 00 ?? 00 00 00 00 ?? ?? 00 ?? 40 ?? 00 00 ?? ?? 00 00 00 00 00 ?? 00 00 00 00 00 00 00 00 00 00 00 ?? ?? ?? 00 ?? 00 00 00 00 00 ?? 00 ?? 00 00 00 00 00 00 00 ?? ?? ?? ?? ?? ?? 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F8 00 00 00");
            romOffset = (long)scanOffset - 0x50;
            wramOffset = (long)scanOffset - 0x40;
        } else if (state == "bgb") {
            IntPtr scanOffset = vars.SigScan(proc, 12, "6D 61 69 6E 6C 6F 6F 70 83 C4 F4 A1 ?? ?? ?? ??");
            var sharedOffset = new DeepPointer(scanOffset, 0, 0, 0x34).Deref<int>(proc);
            romOffset = sharedOffset + 0x10;
            wramOffset = sharedOffset + 0x108;
        } else if (state == "bgb64") {
            IntPtr scanOffset = vars.SigScan(proc, 20, "48 83 EC 28 48 8B 05 ?? ?? ?? ?? 48 83 38 00 74 1A 48 8B 05 ?? ?? ?? ?? 48 8B 00 80 B8 ?? ?? ?? ?? 00 74 07");
            IntPtr baseOffset = scanOffset + proc.ReadValue<int>(scanOffset) + 4;
            var sharedOffset = new DeepPointer(baseOffset, 0, 0x44).Deref<int>(proc);
            romOffset = sharedOffset + 0x18;
            wramOffset = sharedOffset + 0x190;
        }

        if (proc.ReadValue<int>((IntPtr)romOffset) != 0) {
            vars.watchers = vars.GetWatcherList((int)(romOffset - baseAddress), (int)(wramOffset - baseAddress));
            vars.GetSplitList(); //calling now will prevent lag on first timer start

            vars.watchers["version"].Update(proc);
            print(string.Format("[Autosplitter] Game Version: {0}", (vars.watchers["version"].Current == 0x80) ? "LADX" : "LA"));
            print("[Autosplitter] ROM Pointer: " + romOffset.ToString("X8"));
            print("[Autosplitter] WRAM Pointer: " + wramOffset.ToString("X8"));
            
            return true;
        }
        
        return false;
    });

    vars.SigScan = (Func<Process, int, string, IntPtr>)((proc, offset, signature) => {
        var target = new SigScanTarget(offset, signature);
        IntPtr result = IntPtr.Zero;
        foreach (var page in proc.MemoryPages(true)) {
            var scanner = new SignatureScanner(proc, page.BaseAddress, (int)page.RegionSize);
            if ((result = scanner.Scan(target)) != IntPtr.Zero) {
                break;
            }
        }

        return result;
    });

    vars.Current = (Func<string, int, bool>)((name, value) => {
        return vars.watchers[name].Current == value;
    });

    vars.Changed = (Func<string, int, bool>)((name, value) => {
        return vars.watchers[name].Changed && vars.watchers[name].Current == value;
    });

    vars.Instrument = (Func<int, bool>)((index) => {
        var flags = vars.watchers["dungeonFlags"].Current;
        var dungeon = BitConverter.GetBytes(flags)[index];
        return ((dungeon >> 1) & 1) == 1;
    });

    vars.GetWatcherList = (Func<int, int, MemoryWatcherList>)((romOffset, wramOffset) => {
        return new MemoryWatcherList {
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x03B0)) { Name = "objectState" },
            new MemoryWatcher<long>(new DeepPointer(wramOffset, 0x1B65)) { Name = "dungeonFlags" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B54)) { Name = "overworldScreen" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1BAE)) { Name = "submapScreen" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B60)) { Name = "submapIndex" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x13CA)) { Name = "music" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x13CB)) { Name = "music2" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x13C8)) { Name = "sound" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B11)) { Name = "tailKey" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x191D)) { Name = "featherRoom" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1920)) { Name = "braceletRoom" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1946)) { Name = "bootsRoom" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1ABE)) { Name = "ocarinaRoom" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B0C)) { Name = "flippers" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B44)) { Name = "shieldLevel" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1A37)) { Name = "magicRodRoom" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B0E)) { Name = "tradingItem" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B6E)) { Name = "shopThefts" },
            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0x1B73)) { Name = "marin" },

            new MemoryWatcher<byte>(new DeepPointer(wramOffset, 0xEFF)) { Name = "resetCheck" },
            new MemoryWatcher<short>(new DeepPointer(wramOffset, 0x1B95)) { Name = "gameState" },
            new MemoryWatcher<byte>(new DeepPointer(romOffset, 0x143)) { Name = "version" },
        };
    });

    vars.GetSplitList = (Func<Dictionary<string, bool>>)(() => {
        var splits = new Dictionary<string, bool> {
            { "d1Enter", vars.Current("submapScreen", 0x3B) && vars.Current("overworldScreen", 0xD3) },
            { "d2Enter", vars.Current("submapIndex", 0x01) && vars.Current("submapScreen", 0x3A) },
            { "d3Enter", vars.Current("submapIndex", 0x02) && vars.Current("submapScreen", 0x39) },
            { "d4Enter", vars.Current("submapIndex", 0x03) && vars.Current("submapScreen", 0x3B) },
            { "d5Enter", vars.Current("submapIndex", 0x04) && vars.Current("submapScreen", 0x3F) },
            { "d6Enter", vars.Current("submapIndex", 0x05) && (vars.Current("submapScreen", 0x3B) || vars.Current("submapScreen", 0x08)) },
            { "d7Enter", vars.Current("submapIndex", 0x06) && vars.Current("submapScreen", 0x39) },
            { "d8Enter", vars.Current("submapIndex", 0x07) && (vars.Current("submapScreen", 0x3B) || vars.Current("submapScreen", 0x12) || vars.Current("submapScreen", 0x15)) },
            { "d0Enter", vars.Current("submapIndex", 0xFF) && vars.Current("submapScreen", 0x3A) },
            { "d0End", vars.Current("sound", 0x01) && vars.Current("music", 0x0C) && vars.Current("overworldScreen", 0x77) },
            { "eggStairs", vars.Current("gameState", 0x0201) },
            
            { "tailKey", vars.Changed("tailKey", 0x01) && vars.Current("overworldScreen", 0x41) },
            { "slimeKey", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xC6) },
            { "anglerKey", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xCE) },
            { "faceKey", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xAC) },
            { "birdKey", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0x0A) },
            { "feather", vars.Changed("featherRoom", 0x98) && vars.Current("submapScreen", 0x20) },
            { "bracelet", vars.Changed("braceletRoom", 0x91) && vars.Current("submapIndex", 0x01) },
            { "boots", vars.Changed("bootsRoom", 0x9B) && vars.Current("submapIndex", 0x02) },
            { "ocarina", vars.Changed("ocarinaRoom", 0x90) && vars.Current("submapIndex", 0x13) },
            { "flippers", vars.Changed("flippers", 0x01) && vars.Current("submapIndex", 0x03) },
            { "hookshot", vars.Current("music", 0x10) && vars.Current("submapIndex", 0x04) },
            { "l2Shield", vars.Changed("shieldLevel", 0x02) && vars.Current("submapIndex", 0x06) },
            { "magicRod", vars.Changed("magicRodRoom", 0x98) && vars.Current("submapIndex", 0x07) },
            { "magnifyingLens", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xE9) },
            { "boomerang", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xF4) },
            { "l1Sword", vars.Current("music", 0x0F) && vars.Current("overworldScreen", 0xF2) },
            { "l2Sword", vars.Current("music", 0x0F) && vars.Current("overworldScreen", 0x8A) },
            { "yoshi", vars.Changed("tradingItem", 0x01) && vars.Current("overworldScreen", 0xB3) },

            { "house", vars.Current("overworldScreen", 0xA2) },
            { "woods", vars.Current("overworldScreen", 0x90) && vars.Current("tailKey", 0x01) },
            { "shop", vars.Changed("shopThefts", 0x02) && vars.Current("overworldScreen", 0x93) },
            { "marin", vars.Changed("marin", 0x01) && vars.Current("overworldScreen", 0xF5) },
            { "walrus", vars.Current("objectState", 0x05) && vars.Current("overworldScreen", 0xFD) },
            { "d2Exit", vars.Current("submapScreen", 0x3A) && vars.Current("overworldScreen", 0x24) && vars.Current("music2", 0x05) },
            { "d8Exit", vars.Current("submapScreen", 0x12) && vars.Current("overworldScreen", 0x00) },
            { "song1", vars.Current("music", 0x10) && (vars.Current("overworldScreen", 0xDC) || vars.Current("overworldScreen", 0x92)) },
            { "song2", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0x2A) },
            { "song3", vars.Current("music", 0x10) && vars.Current("overworldScreen", 0xD4) },
            { "creditsWarp", vars.Current("gameState", 0x0101) },
        };

        if (vars.watchers["version"].Current == 0) { //LA
            splits.Add("d1End", vars.Instrument(0) && vars.Current("music", 0x05));
            splits.Add("d2End", vars.Instrument(1) && vars.Current("music", 0x05) && !vars.Current("music2", 0x15));
            splits.Add("d3End", vars.Instrument(2) && vars.Current("music", 0x05));
            splits.Add("d4End", vars.Instrument(3) && vars.Current("music", 0x05));
            splits.Add("d5End", vars.Instrument(4) && vars.Current("music", 0x05));
            splits.Add("d6End", vars.Instrument(5) && vars.Current("music", 0x05));
            splits.Add("d7End", vars.Instrument(6) && vars.Current("music", 0x06));
            splits.Add("d8End", vars.Instrument(7) && vars.Current("music", 0x06));
        } else if (vars.watchers["version"].Current == 0x80) { //LADX
            splits.Add("d1End", vars.Current("music", 0x0B) && vars.Current("submapScreen", 0x16) && vars.Current("overworldScreen", 0xD3));
            splits.Add("d2End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x01));
            splits.Add("d3End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x02));
            splits.Add("d4End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x03));
            splits.Add("d5End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x04));
            splits.Add("d6End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x05));
            splits.Add("d7End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x06));
            splits.Add("d8End", vars.Current("music", 0x0B) && vars.Current("submapIndex", 0x07));
        }

        return splits;
    });
}

init {
    vars.watchers = new MemoryWatcherList();
    vars.pastSplits = new HashSet<string>();

    if (!vars.TryFindOffsets(game, modules.First().ModuleMemorySize, (long)modules.First().BaseAddress)) {
        throw new Exception("[Autosplitter] Emulated memory not yet initialized.");
    } else {
        refreshRate = 200/3.0;
    }
}

update {
    if (timer.CurrentPhase == TimerPhase.NotRunning && vars.pastSplits.Count > 0) {
        vars.pastSplits.Clear();
    }
    
    vars.watchers.UpdateAll(game);
}

start {
    return vars.watchers["gameState"].Current == 0x0902;
}

reset {
    return vars.watchers["resetCheck"].Current > 0;
}

split {
    var splits = vars.GetSplitList();

    foreach (var split in splits) {
        if (settings[split.Key] && split.Value && !vars.pastSplits.Contains(split.Key)) {
            vars.pastSplits.Add(split.Key);
            print("[Autosplitter] Split: " + split.Key);
            return true;
        }
    }
}

exit {
    refreshRate = 0.5;
}