// Find speed limiter and pedal map in Bosch ME17.9.11 firmware
//@category Analysis

import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.address.AddressSet;
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.mem.Memory;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import ghidra.program.model.symbol.ReferenceManager;

public class FindSpeedAndPedal extends GhidraScript {

    @Override
    public void run() throws Exception {
        AddressFactory af = currentProgram.getAddressFactory();
        FunctionManager fm = currentProgram.getFunctionManager();
        Listing listing = currentProgram.getListing();
        Memory mem = currentProgram.getMemory();

        // ===========================================================================
        // 1) SPEED LIMITER SEARCH
        // ===========================================================================
        // ME17 typically uses km/h * 128 or km/h * 64 scaling for vehicle speed.
        // Common limit values (in different scalings):
        //   200 km/h: 0xC8 (raw), 0x3200 (×64), 0x6400 (×128), 0x4E20 (×100)
        //   210 km/h: 0xD2,        0x3480,       0x6900,       0x5208
        //   220 km/h: 0xDC,        0x3700,       0x6E00,       0x55F0
        //   240 km/h: 0xF0,        0x3C00,       0x7800,       0x5DC0
        //   250 km/h: 0xFA,        0x3E80,       0x7D00,       0x61A8
        //   160 km/h: 0xA0,        0x2800,       0x5000,       0x3E80

        println("================================================================");
        println("PART 1: SPEED LIMITER SEARCH");
        println("================================================================");

        // Scan calibration region for speed-limit-candidate words
        // (Speed limiter is usually one WORD in calibration data, read by code that compares vehicle_speed > limit)
        long calS = 0x80020000L;
        long calE = 0x8003FF03L;
        int[] candidates = {0xC8, 0xD2, 0xDC, 0xE6, 0xF0, 0xFA,         // raw km/h 200/210/220/230/240/250
                            0x3200, 0x3480, 0x3700, 0x3980, 0x3C00, 0x3E80,  // ×64
                            0x6400, 0x6900, 0x6E00, 0x7300, 0x7800, 0x7D00,  // ×128
                            0x4E20, 0x5208, 0x55F0, 0x59D8, 0x5DC0, 0x61A8}; // ×100

        println("--- Candidate 16-bit values in calibration region 0x20000..0x3FF03 ---");
        for (int cand : candidates) {
            int found = 0;
            for (long off = calS; off < calE - 1; off++) {
                Address a = af.getAddress(Long.toHexString(off));
                byte lo = mem.getByte(a);
                byte hi = mem.getByte(af.getAddress(Long.toHexString(off+1)));
                int val = ((hi & 0xFF) << 8) | (lo & 0xFF);
                if (val == cand) {
                    if (found < 4) {
                        // Show context
                        StringBuilder ctx = new StringBuilder();
                        for (int k=-2; k<6; k++) {
                            try {
                                byte b = mem.getByte(af.getAddress(Long.toHexString(off+k)));
                                ctx.append(String.format("%02X ", b & 0xFF));
                            } catch (Exception e) {}
                        }
                        println(String.format("  cand 0x%04X (dec %5d): @ 0x%08X  ctx: %s",
                                cand, cand, off, ctx.toString()));
                    }
                    found++;
                }
            }
            if (found > 0) println(String.format("    -> %d occurrences total", found));
        }

        // ===========================================================================
        // 2) Search ASW code for instructions that LOAD speed-limit-like immediate values
        // ===========================================================================
        println("\n--- Code search: instructions with speed-limit immediate values ---");
        Address asw_a = af.getAddress("80040000");
        Address asw_b = af.getAddress("800FFEFF");
        AddressSet aswSet = new AddressSet(asw_a, asw_b);

        // Look for movh.u or mov.u with 0xC8/0xCA/.../0xFA, or movh with high parts
        String[] codePatterns = {"#0xc8", "#0xd2", "#0xdc", "#0xe6", "#0xf0", "#0xfa",
                                  "#0x3200", "#0x3480", "#0x6400", "#0x7d00", "#0x4e20"};
        for (String pat : codePatterns) {
            int count = 0;
            InstructionIterator it = listing.getInstructions(aswSet, true);
            while (it.hasNext()) {
                Instruction ins = it.next();
                String s = ins.toString();
                if (s.contains(pat) && (s.startsWith("mov") || s.startsWith("addi") || s.startsWith("jge") || s.startsWith("jlt"))) {
                    if (count < 3) {
                        Function func = fm.getFunctionContaining(ins.getAddress());
                        String fname = (func != null) ? func.getName() : "<no-func>";
                        println(String.format("  pat=%s @0x%s [%s]: %s", pat, ins.getAddress(), fname, s));
                    }
                    count++;
                }
            }
            if (count > 0) println(String.format("    -> %d hits for %s", count, pat));
        }

        // ===========================================================================
        // 3) PEDAL MAP SEARCH
        // ===========================================================================
        // Pedal maps are 2D tables: pedal_pos × engine_rpm → target_torque (or %).
        // Typical: 8×16 or 16×16 bytes, values rising from low to ~255.
        // We'll search for table patterns: 16-byte rows with monotonically rising bytes 0..ff
        println("\n================================================================");
        println("PART 3: PEDAL MAP CANDIDATE SEARCH");
        println("================================================================");
        println("Looking for 16-byte rows with monotonic rise from low (~0-30) to high (>200)...");

        int found = 0;
        for (long off = calS; off < calE - 16; off++) {
            byte[] row = new byte[16];
            mem.getBytes(af.getAddress(Long.toHexString(off)), row);
            // Check monotonic non-decreasing, starts low, ends high
            int first = row[0] & 0xFF;
            int last  = row[15] & 0xFF;
            if (first > 30) continue;
            if (last < 180) continue;
            if (last < first + 100) continue;
            // Check mostly monotonic
            int violations = 0;
            for (int i = 1; i < 16; i++) {
                if ((row[i] & 0xFF) < (row[i-1] & 0xFF)) violations++;
            }
            if (violations > 1) continue;
            // Looks plausible
            if (found < 20) {
                StringBuilder hex = new StringBuilder();
                for (byte b : row) hex.append(String.format("%02X ", b & 0xFF));
                println(String.format("  @0x%08X: %s", off, hex.toString().trim()));
            }
            found++;
        }
        println(String.format("Total candidate rows: %d", found));

        println("\nDONE");
    }
}
