const { utils: { book_new, json_to_sheet }, writeFileXLSX } = require("xlsx");

/* start decompiler */
const DecompInterface = JavaHelper.getClass('ghidra.app.decompiler.DecompInterface');
const decompiler = new DecompInterface();
decompiler.openProgram(currentProgram);

/* get decompiled C source */
const src = (() => {
  const fname = '_TSTCellToCellStorage';

  /* find address to function */
  const fsymbs = currentProgram.getSymbolTable().getGlobalSymbols(fname);
  if(!fsymbs) throw new Error(`Global Symbol ${fname} cannot be found`);

  /* find function */
  const fn = currentProgram.getFunctionManager().getFunctionAt(fsymbs[0].getAddress());
  if(!fn) throw new Error(`Function ${fname} cannot be found`);

  /* decompile function */
  const decomp = decompiler.decompileFunction(fn, 10000, null);
  if (decomp.isTimedOut() || !decomp.decompileCompleted()) throw new Error(`Function ${fname} at ${fn} could not be decompiled`);

  /* get and return generated C code */
  const src = decomp.getDecompiledFunction().getC();
  if(!src) throw new Error(`Function ${fname} at ${fn} decompilation`);
  return src;
})();

/* offset[n] will be the name of the field at bit `n` (mask `1 << n`) */
let offset = [];

/* combine split lines and lazily extract data from C source */
let n = -1;
src.split(/[\r\n]+/).reduce((acc,row,i) => {
  if(i <= 9) acc.rows.push(row);
  else if(row.match(/[{};]$/)) {
    if(acc.buf) { row = acc.buf + row.trim(); acc.buf = ""; }
    acc.rows.push(row);
  } else acc.buf += acc.buf ? row.trim() : row.replace(/[\r\n]+$/,"");
  return acc;
}, {rows:[], buf:""}).rows.forEach(line => {
  if(line.match(/^  if.*(char|short| >> | & )/)) {
    n = -1;
    if(!line.match(/Var/)) return;
    if(line.match(/char/)) n = 7;
    else if(line.match(/short/)) n = 15;
    else if(line.match(/>>/)) n = parseInt(line.match(/>> ([1-9]\d*|0x[0-9a-f]+)/)[1]);
    else if(line.match(/&/)) n = Math.round(Math.log2(parseInt(line.match(/& ([1-9]\d*|0x[0-9a-f]+)/)[1])));
  } else if(line.match(/PTR__objc_msgSend/) && n >= 0) {
    if(line.match(/AssertionHandler|NSString|stringWithUTF8String|Failure/)) return;
    if(!line.match(/,/)) return; // suppress noop
    const ptr = line.match(/PTR_s_\w+/)[0];
    offset[+n] = ptr;
    n = -1;
  }
});

/* construct objects with "Mask" (hex string) and "Internal Name" */
const aoo = offset.map((name, idx) => ({
  Mask: "0x" + (1 << idx).toString(16),
  "Internal Name": name.replace(/^PTR_s_|_[0-9a-f]*$/g,"").replace(/[A-Z]/g, " $&").toLowerCase().replace(/ i d/, " ID")
}));

/* create worksheet */
const ws = json_to_sheet(aoo);
/* write workbook */
const wb = book_new(ws, "Offsets");
/* write to XLSX */ 
writeFileXLSX(wb, "SheetJSGhidraTSTCell.xlsx");