
// Converts an integer (unicode value) to a char
function itoa(i: number)
{ 
   return String.fromCharCode(i);
}
// Converts a char into to an integer (unicode value)
function atoi(a: { charCodeAt: () => any; })
{ 
   return a.charCodeAt();
}
// i to hex
function itohex(i: { toString: (arg0: number) => any; })
{
  var s = i.toString(16);
  s = ('00000000' + s).slice(-8);

   return [s.slice(6, 8), s.slice(4, 6), s.slice(2, 4), s.slice(0, 2)].join(' ');
}
// itohex(0x1122334);
function str2hex(s: string | any[]) { // buffer is an ArrayBuffer
  // for each element, we want to get its two-digit hexadecimal representation
  const hexParts = [];
  for(let i = 0; i < s.length; i++) {
    // convert value to hexadecimal
    const hex = atoi(s[i]).toString(16);
 
    // pad with zeros to length 2
    const paddedHex = ('00' + hex).slice(-2);
 
    // push to array
    hexParts.push(paddedHex);
  }
 
  // join all the hex values of the elements into a single string
  return hexParts.join(' ');
}
// str2hex('ssssk')
function buf2hex(buffer: Iterable<number>) { // buffer is an ArrayBuffer
  // create a byte array (Uint8Array) that we can use to read the array buffer
  const byteArray = new Uint8Array(buffer);
 
  // for each element, we want to get its two-digit hexadecimal representation
  const hexParts = [];
  for(let i = 0; i < byteArray.length; i++) {
    // convert value to hexadecimal
    const hex = byteArray[i].toString(16);
 
    // pad with zeros to length 2
    const paddedHex = ('00' + hex).slice(-2);
 
    // push to array
    hexParts.push(paddedHex);
  }
 
  // join all the hex values of the elements into a single string
  return hexParts.join(' ');
}


// 根据类名获取虚表地址
function GetVTableByClassName(dll_name: string, func: string) {
  var module = Module.load(dll_name);
  var s = ".?AV" + func + "@@";
  var results = Memory.scanSync(module.base, module.size, str2hex(s));

  // class GxxLogin::LoginAccountPage `RTTI Type Descriptor'
  // var nRTTITypeDescriptor = eval(results[0].address)-8;
  var nRTTITypeDescriptor = results[0].address-0x08;
  // MyLogD(results[0].address, itohex(nRTTITypeDescriptor));
  results = Memory.scanSync(module.base, module.size, itohex(nRTTITypeDescriptor));
  // MyLogD(JSON.stringify(results));

  // RTTI Complete Object Locator
  var nRTTI_COL = eval(results[0].address) - 0x0c;
  // MyLogD(nRTTI_COL, itohex(nRTTI_COL));
  results = Memory.scanSync(module.base, module.size, itohex(nRTTI_COL));
  // MyLogD(JSON.stringify(results), eval(results[0].address), eval(results[0].address)-0+0x04);
  return eval(results[0].address)-0+0x04;
}
// MyLogD(GetVTable('LoginAccountPage@GxxLogin'));


// 通过对象获取虚表
//    .?AVContextualErrorEdit@Phoenix@@   战网的 accountNameEdit 、 passwordEdit 都是这个类实现的
//      .data:1164F34C ; public class Phoenix::ContextualErrorEdit /* mdisp:0 */ :
//      .data:1164F34C ;   public class QWidget /* mdisp:0 */ :
//      .data:1164F34C ;     public class QObject /* mdisp:0 */,
//      .data:1164F34C ;     public class QPaintDevice /* mdisp:8 */
//      .data:1164F34C ; class Phoenix::ContextualErrorEdit `RTTI Type Descriptor'
//      .data:1164F34C ??_R0?AVContextualErrorEdit@Phoenix@@@8 dd offset ??_7type_info@@6B@
function GetVTableNameByObj(obj: string | number | NativePointer) {
  // TODO：
  var pCol = ptr(obj).readPointer().sub(4).readPointer();
  var pTypeDescriptor = pCol.add(4*3).readPointer();
  return pTypeDescriptor.add(4*2).readCString()
}

console.log('GetVTableNameByObj =', GetVTableNameByObj(ptr('0x821be68')));

// demo() {
//   let address = Module.getExportByName("User32.dll", "MessageBoxW");
//   let m2 = Memory.alloc(Process.pageSize);
//   let asm = new X86Writer(m2);
//   asm.putPushU32(0x00000001);
//   asm.putPushU32(2);
//   asm.putPushU32(3);
//   asm.putPushU32(4);
//   asm.putCallAddress(address);
//   asm.putRet();
//   asm.putPushReg("eax");
//   asm.flush();

//   // this.show_asm(m2);
//   this.show_asm(ptr(0x01003E21));
// }

function show_asm(start: NativePointer, length: number = 10) {
  for (let index = 0; index < length; index++) {
      let inst = Instruction.parse(start);
      // console.log(JSON.stringify(inst));
      let byteArray = start.readByteArray(inst.size);
      let byteCode = Array.prototype.slice.call(new Uint8Array(byteArray!));
      let mCode = byteCode.map(x => x.toString(16).padStart(2, "0")).join(" ").toUpperCase();
      console.log(inst.address.toString().toUpperCase().replace("0X", "0x"), mCode.padEnd(14, " "), "\t", inst.toString().toUpperCase().replace("0X", "0x"));

      start = inst.next;
      if (start.readU32() == 0) break;
  }
}

function testSnippets() {
  let address = Module.getExportByName("User32.dll", "MessageBoxW");
  show_asm(address);
}

export { testSnippets };