// Inspect a specific function in detail
//@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 InspectFunc 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();

        // 1. Inspect FUN_8004B390 in full
        long[] funcs = { 0x8004B390L };
        DecompInterface decomp = new DecompInterface();
        decomp.openProgram(currentProgram);

        for (long f : funcs) {
            Address fAddr = af.getAddress(Long.toHexString(f));
            Function func = fm.getFunctionAt(fAddr);
            if (func == null) { println("Not found: 0x" + Long.toHexString(f)); continue; }
            println("================================================================");
            println(String.format("FUNCTION 0x%08X  body size=0x%X", f, func.getBody().getNumAddresses()));
            println("================================================================");

            // Full instruction listing
            println("--- ALL INSTRUCTIONS ---");
            InstructionIterator it = listing.getInstructions(func.getBody(), true);
            while (it.hasNext()) {
                Instruction ins = it.next();
                println(String.format("  0x%s: %s", ins.getAddress(), ins.toString()));
            }

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

            // Callers
            println("\n--- CALLERS ---");
            List<Reference> callers = collect(refMgr, fAddr);
            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);
                String insStr = (ins != null) ? ins.toString() : "<no-ins>";
                println(String.format("  caller 0x%s in [%s @ 0x%s]  insn=%s",
                        from, cname, cent, insStr));
            }
        }

        // 2. Also: find all instructions in ASW region (0x80040000..0x800FFEFF) that load WITH offset #0x1E
        // These would be candidates for accessing the cruise byte if the base register holds 0x80020230.
        println("\n================================================================");
        println("SEARCH: ld instructions with #0x1E offset in ASW");
        println("================================================================");
        long aswStart = 0x80040000L;
        long aswEnd   = 0x800FFEFFL;
        Address asw_a = af.getAddress(Long.toHexString(aswStart));
        Address asw_b = af.getAddress(Long.toHexString(aswEnd));
        ghidra.program.model.address.AddressSet aswSet = new ghidra.program.model.address.AddressSet(asw_a, asw_b);
        InstructionIterator allIns = listing.getInstructions(aswSet, true);
        int count = 0;
        while (allIns.hasNext()) {
            Instruction ins = allIns.next();
            String s = ins.toString();
            // Look for ld instructions with #0x1e offset (case-insensitive)
            if ((s.startsWith("ld.") || s.startsWith("st.")) &&
                (s.contains("#0x1e") || s.contains("#0x1E"))) {
                Function func = fm.getFunctionContaining(ins.getAddress());
                String fname = (func != null) ? func.getName() : "<no-func>";
                String fent = (func != null) ? func.getEntryPoint().toString() : "n/a";
                println(String.format("  0x%s [%s @ 0x%s]: %s", ins.getAddress(), fname, fent, s));
                count++;
                if (count > 200) { println("  ... truncated"); break; }
            }
        }
        println("Total ld/st with #0x1E: " + count);

        // Also search for offset #0x2c (= 0x8002025C - 0x80020230) and #0x36 (= 0x80020266 - 0x80020230)
        println("\n=== Search: #0x2c (cruise param 0x8002025C offset) ===");
        allIns = listing.getInstructions(aswSet, true);
        count = 0;
        while (allIns.hasNext()) {
            Instruction ins = allIns.next();
            String s = ins.toString();
            if ((s.startsWith("ld.") || s.startsWith("st.")) &&
                (s.contains("#0x2c") || s.contains("#0x2C"))) {
                Function func = fm.getFunctionContaining(ins.getAddress());
                String fname = (func != null) ? func.getName() : "<no-func>";
                println(String.format("  0x%s [%s]: %s", ins.getAddress(), fname, s));
                count++;
                if (count > 50) { println("  ..."); break; }
            }
        }

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