// Decompile functions that SET bit 0 of 0xD000197A (cruise master gate)
// to find what conditions/calibration trigger it.
//@category Analysis

import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionManager;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import ghidra.program.model.symbol.ReferenceManager;

public class MasterGateTrigger extends GhidraScript {

    private List<Reference> collect(ReferenceManager refMgr, Address a) {
        List<Reference> list = new ArrayList<>();
        ReferenceIterator it = refMgr.getReferencesTo(a);
        while (it.hasNext()) list.add(it.next());
        return list;
    }

    @Override
    public void run() throws Exception {
        AddressFactory af = currentProgram.getAddressFactory();
        FunctionManager fm = currentProgram.getFunctionManager();
        Listing listing = currentProgram.getListing();
        ReferenceManager refMgr = currentProgram.getReferenceManager();

        DecompInterface decomp = new DecompInterface();
        decomp.openProgram(currentProgram);

        // Sites that SET bit 0 of 0xD000197A
        long[] setSites = { 0x80050A7EL, 0x800510C4L };
        long[] clearSites = { 0x80050A6AL, 0x800510CEL };

        for (long site : setSites) {
            Address siteAddr = af.getAddress(Long.toHexString(site));
            Function func = fm.getFunctionContaining(siteAddr);
            if (func == null) {
                println(String.format("\n!!! Site 0x%X is NOT inside any known function — examining nearby code", site));
                // Look at instructions around the site
                println("--- 40 bytes before and 40 bytes after ---");
                for (long off = site - 0x40; off < site + 0x40; off++) {
                    Address a = af.getAddress(Long.toHexString(off));
                    Instruction ins = listing.getInstructionAt(a);
                    if (ins != null) {
                        String marker = (off == site) ? "  <<<<<<<<<< THE SET" : "";
                        println(String.format("  0x%s: %s%s", a, ins.toString(), marker));
                        off += ins.getLength() - 1; // skip ahead
                    }
                }
                continue;
            }
            println("\n================================================================");
            println(String.format("SITE 0x%X is inside FUNC %s @ 0x%s (size 0x%X)",
                    site, func.getName(), func.getEntryPoint(), func.getBody().getNumAddresses()));
            println("================================================================");

            // List all instructions in the function
            println("--- KEY INSTRUCTIONS in func (loads/stores/jumps) ---");
            int count = 0;
            InstructionIterator it = listing.getInstructions(func.getBody(), true);
            while (it.hasNext()) {
                Instruction ins = it.next();
                String s = ins.toString();
                String mark = (ins.getAddress().getOffset() == site) ? "  <<<<< SET bit 0" : "";
                // Only print interesting instructions
                if (s.startsWith("ld") || s.startsWith("st") || s.startsWith("j") || s.startsWith("call") ||
                    s.startsWith("cmp") || s.startsWith("ne") || s.startsWith("eq") || s.contains("0xd000")) {
                    println(String.format("  0x%s: %s%s", ins.getAddress(), s, mark));
                    count++;
                    if (count > 100) { println("  ... truncated"); break; }
                }
            }

            // Decompile
            println("--- DECOMPILED ---");
            DecompileResults res = decomp.decompileFunction(func, 60, monitor);
            if (res.decompileCompleted()) {
                println(res.getDecompiledFunction().getC());
            } else {
                println("Decomp failed: " + res.getErrorMessage());
            }

            // Callers — what calls this function?
            println("--- CALLERS of this function ---");
            List<Reference> callers = collect(refMgr, func.getEntryPoint());
            for (Reference r : callers) {
                Address from = r.getFromAddress();
                Function caller = fm.getFunctionContaining(from);
                String cname = (caller != null) ? caller.getName() : "<no-func>";
                String cent = (caller != null) ? caller.getEntryPoint().toString() : "n/a";
                Instruction ins = listing.getInstructionAt(from);
                println(String.format("  from 0x%s in [%s @ 0x%s] insn=%s",
                        from, cname, cent, ins != null ? ins.toString() : "?"));
            }
        }

        decomp.dispose();
        println("\nDONE");
    }
}
