[
  {
    "path": ".gitignore",
    "content": "/example/lib/_agent.js\n/example/node_modules\n/node_modules\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 NowSecure, Inc\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# frida-trace\n\nTrace APIs declaratively through [Frida](https://www.frida.re).\n\n## Example\n\n```js\nimport trace from 'frida-trace';\n\nconst func = trace.func;\nconst argIn = trace.argIn;\nconst argOut = trace.argOut;\nconst retval = trace.retval;\n\nconst types = trace.types;\nconst pointer = types.pointer;\nconst INT = types.INT;\nconst POINTER = types.POINTER;\nconst UTF8 = types.UTF8;\n\ntrace({\n  module: 'libsqlite3.dylib',\n  functions: [\n    func('sqlite3_open', retval(INT), [\n      argIn('filename', UTF8),\n      argOut('ppDb', pointer(POINTER), when('result', isZero)),\n    ]),\n    func('sqlite3_prepare_v2', retval(INT), [\n      argIn('db', POINTER),\n      argIn('zSql', [UTF8, bind('length', 'nByte')]),\n      argIn('nByte', INT),\n      argOut('ppStmt', pointer(POINTER), when('result', isZero)),\n    ])\n  ],\n  callbacks: {\n    onEvent(event) {\n      console.log('onEvent! ' + JSON.stringify(event, null, 2));\n    },\n    onEnter(event, context) {\n      event.trace = Thread.backtrace(context)\n        .map(DebugSymbol.fromAddress)\n        .filter(x => x.name);\n    },\n    onError(e) {\n      console.error(e);\n    }\n  }\n});\n\nfunction isZero(value) {\n  return value === 0;\n}\n```\n\n## Auto-generating boilerplate from header files\n\n```sh\n$ ./bin/parse-header.js /usr/include/sqlite3.h | ./bin/generate-boilerplate.js\ntrace({\n  module: 'libfoo.dylib',\n  functions: [\n    func('sqlite3_libversion', retval(UTF8), []),\n    func('sqlite3_sourceid', retval(UTF8), []),\n    func('sqlite3_libversion_number', retval(INT), []),\n    func('sqlite3_compileoption_used', retval(INT), [\n      argIn('zOptName', UTF8)\n    ]),\n    func('sqlite3_compileoption_get', retval(UTF8), [\n      argIn('N', INT)\n    ]),\n    func('sqlite3_threadsafe', retval(INT), []),\n    func('sqlite3_close', retval(INT), [\n      argIn('a1', POINTER)\n    ]),\n    func('sqlite3_close_v2', retval(INT), [\n      argIn('a1', POINTER)\n    ]),\n    func('sqlite3_exec', retval(INT), [\n      argIn('a1', POINTER),\n      argIn('sql', UTF8),\n      argIn('callback', POINTER),\n      argIn('a4', POINTER),\n      argOut('errmsg', pointer(POINTER), when('result', isZero))\n    ]),\n...\n```\n"
  },
  {
    "path": "example/bin/trace.js",
    "content": "#!/usr/bin/env node\nimport Application from '../lib/application';\nimport chalk from 'chalk';\nimport { hexy } from 'hexy';\n\nclass ConsoleUI {\n  onEvents(events) {\n    events.forEach(event => {\n      const args = event.args;\n      const argNames = Object.keys(args);\n\n      const heading = event.name + '(' + argNames.reduce((result, name) => {\n        const value = args[name];\n        if (!(value instanceof Buffer))\n          result.push(name + '=' + annotate(value));\n        return result;\n      }, []).join(', ') + ') => ' + event.result;\n\n      const sections = argNames.reduce((result, name) => {\n        const value = args[name];\n        if (value instanceof Buffer) {\n          result.push('\\n' + name + ':\\n' + hexy(value, { format: 'twos' }));\n        }\n        return result;\n      }, []).join('\\n');\n\n      console.log(chalk.green(heading) + '\\n' + sections);\n    });\n  }\n\n  onOutput(pid, fd, data) {\n    let text = data.toString();\n    if (text[text.length - 1] === '\\n')\n      text = text.substr(0, text.length - 1);\n    const lines = text.split('\\n');\n    const prefix = ((fd === 1) ? chalk.bold('stdout>') : chalk.red('stderr>')) + ' ';\n    console.log(prefix + lines.join('\\n' + prefix) + '\\n');\n  }\n\n  onError(error) {\n    console.error(error);\n    process.exitCode = 1;\n  }\n}\n\nfunction annotate(value) {\n  if (typeof value === 'string')\n    return '\"' + value + '\"';\n  else\n    return value;\n}\n\nfunction usage() {\n  console.error('Usage: ' + process.argv[0] + ' <device-id> launch|attach args...');\n  process.exit(1);\n}\n\nif (process.argv.length < 5)\n  usage();\n\nconst device = process.argv[2];\nconst action = process.argv[3];\nlet target;\nif (action === 'launch') {\n  target = {\n    device: device,\n    argv: process.argv.slice(4)\n  };\n} else if (action === 'attach') {\n  target = {\n    device: device,\n    pid: parseInt(process.argv[4], 10)\n  };\n} else {\n  usage();\n}\n\nconst ui = new ConsoleUI();\nconst application = new Application(ui);\napplication.run(target).catch(ui.onError.bind(ui));\n"
  },
  {
    "path": "example/lib/agent/index.js",
    "content": "import trace from 'frida-trace';\n\nconst func = trace.func;\nconst argIn = trace.argIn;\nconst argOut = trace.argOut;\nconst retval = trace.retval;\n\nconst types = trace.types;\nconst pointer = types.pointer;\nconst INT = types.INT;\nconst POINTER = types.POINTER;\nconst UTF8 = types.UTF8;\nconst BYTE_ARRAY = types.BYTE_ARRAY;\n\nconst bind = trace.bind;\nconst when = trace.when;\n\nrpc.exports = {\n  dispose() {\n    flushEvents();\n  },\n};\n\ntrace({\n  module: null,\n  functions: [\n    func('strlen', retval(INT), [\n      argIn('s', UTF8)\n    ]),\n    func('read', retval(INT), [\n      argIn('fd', INT),\n      argOut('buffer', [BYTE_ARRAY, bind('length', 'result')], when('result', isGreaterThanZero)),\n      argIn('length', INT),\n    ]),\n  ],\n  callbacks: {\n    onEvent(event) {\n      scheduleEvent(event);\n    },\n    onError(e) {\n      console.error(e);\n    }\n  }\n});\n\nconst pending = [];\nlet timer = null;\n\nfunction scheduleEvent(event) {\n  pending.push(event);\n  if (timer === null) {\n    timer = setTimeout(() => {\n      timer = null;\n      flushEvents();\n    }, 500);\n  }\n}\n\nfunction flushEvents() {\n  if (timer !== null) {\n    clearTimeout(timer);\n    timer = null;\n  }\n  const events = pending.splice(0);\n\n  const blobs = events.reduce((result, event, index) => {\n    const args = event.args;\n    Object.keys(args).forEach(argName => {\n      const argValue = args[argName];\n      if (argValue instanceof ArrayBuffer) {\n        result.push([index, argName, argValue]);\n      }\n    });\n    return result;\n  }, []);\n\n  const dataSize = blobs.reduce((total, blob) => total + blob[2].byteLength, 0);\n  const data = new Uint8Array(dataSize);\n  let offset = 0;\n  const mappings = blobs.map(blob => {\n    const chunk = new Uint8Array(blob[2]);\n    data.set(chunk, offset);\n\n    const length = chunk.length;\n    const mapping = [blob[0], blob[1], offset, length];\n    offset += length;\n\n    return mapping;\n  });\n\n  send({\n    name: '+events',\n    payload: {\n      items: events,\n      mappings: mappings\n    }\n  }, data.buffer);\n}\n\nfunction isGreaterThanZero(value) {\n  return value > 0;\n}\n"
  },
  {
    "path": "example/lib/application.js",
    "content": "import frida from 'frida';\nimport fs from 'fs';\nimport path from 'path';\nimport util from 'util';\n\nconst readFile = util.promisify(fs.readFile);\n\nexport default class Application {\n  constructor(ui) {\n    this.ui = ui;\n\n    this._device = null;\n    this._pid = 0;\n    this._session = null;\n    this._script = null;\n    this._done = new Promise((resolve) => {\n      this._onDone = resolve;\n    });\n  }\n\n  async run(target) {\n    const device = await frida.getDevice(target.device);\n    this._device = device;\n\n    const onOutput = this._onOutput.bind(this);\n    device.output.connect(onOutput);\n\n    try {\n      const spawn = target.hasOwnProperty('argv');\n\n      let pid;\n      if (spawn)\n        pid = await device.spawn(target.argv);\n      else\n        pid = target.pid;\n      this._pid = pid;\n\n      const session = await device.attach(pid);\n      this._session = session;\n\n      const agentCode = await import('./_agent');\n      const agent = await readFile(agentCode, 'utf8');\n      const script = await session.createScript(agent);\n      this._script = script;\n\n      const onMessage = this._onMessage.bind(this);\n      script.message.connect(onMessage);\n\n      try {\n        await script.load();\n\n        if (spawn)\n          await device.resume(pid);\n\n        await this._waitUntilDone();\n      } finally {\n        script.message.disconnect(onMessage);\n      }\n    } finally {\n      device.output.disconnect(onOutput);\n    }\n  }\n\n  _waitUntilDone() {\n    return this._done;\n  }\n\n  _onOutput(pid, fd, data) {\n    this.ui.onOutput(pid, fd, data);\n  }\n\n  _onMessage(message, data) {\n    if (message.type === 'send') {\n      const stanza = message.payload;\n      switch (stanza.name) {\n      case '+events': {\n        const payload = stanza.payload;\n        const items = payload.items;\n        const mappings = payload.mappings;\n        mappings.forEach(mapping => {\n          const index = mapping[0];\n          const argName = mapping[1];\n          const offset = mapping[2];\n          const length = mapping[3];\n          items[index].args[argName] = Buffer.from(data, offset, length);\n        });\n        this.ui.onEvents(items);\n        break;\n      }\n      default:\n        console.error(JSON.stringify(message, null, 2));\n        break;\n      }\n    } else {\n      console.error(JSON.stringify(message, null, 2));\n    }\n  }\n}\n"
  },
  {
    "path": "example/package.json",
    "content": "{\n  \"name\": \"frida-trace-example\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Example showing how to use frida-trace\",\n  \"main\": \"lib/application.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prepare\": \"npm run build\",\n    \"build\": \"frida-compile lib/agent/index.js -o lib/_agent.js\",\n    \"watch\": \"frida-compile lib/agent/index.js -o lib/_agent.js -w\"\n  },\n  \"dependencies\": {\n    \"chalk\": \"^2.4.2\",\n    \"frida\": \"^16.1.4\",\n    \"hexy\": \"^0.3.0\"\n  },\n  \"devDependencies\": {\n    \"frida-compile\": \"^16.3.0\",\n    \"frida-trace\": \"^3.0.0\"\n  }\n}\n"
  },
  {
    "path": "frida-trace-tools/bin/generate-boilerplate.js",
    "content": "#!/usr/bin/env node\nimport concat from 'concat-stream';\n\nconst input = process.stdin;\ninput.setEncoding('utf-8');\ninput.pipe(concat(writeCode));\ninput.resume();\n\nconst output = process.stdout;\n\nfunction writeCode (data) {\n  const api = JSON.parse(data);\n  const code = apiDescriptionToCode(api);\n  process.stdout.write(code);\n}\n\nfunction apiDescriptionToCode (api) {\n  const {functions, structs} = api;\n\n  return []\n    .concat(functionDescriptionsToCode(functions))\n    .concat(structDescriptionsToCode(structs))\n    .join('\\n');\n}\n\nfunction functionDescriptionsToCode (funcs) {\n  if (funcs.length === 0) {\n    return [];\n  }\n\n  const funcDecls = funcs.map(func => {\n    const [name, retType, args] = func;\n    return funcDescriptionToCode({\n      name: name,\n      retType: retType,\n      args: args\n    }, 1);\n  });\n\n  const code = `trace({\n  module: 'libfoo.dylib',\n  functions: [\n    ${funcDecls.join(',\\n    ')}\n  ],\n  callbacks: {\n    onEvent(event) {\n      console.log('onEvent! ' + JSON.stringify(event, null, 2));\n    },\n    onError(e) {\n      console.error(e);\n    }\n  }\n});\n\nfunction isZero(value) {\n  return value === 0;\n}\n`;\n\n  return [code];\n}\n\nfunction structDescriptionsToCode (structs) {\n  if (structs.length === 0) {\n    return [];\n  }\n\n  const code = structs\n    .reduce((state, [name, funcs]) => {\n      const body = structDescriptionToCode(funcs);\n\n      let code;\n      const {previous} = state;\n      if (previous !== null && strippedName(name) === strippedName(previous.name) && body.startsWith(previous.body)) {\n        code = `const ${name} = ${previous.name}.concat([\n${body.substr(previous.body.length + 2)}\n]);`;\n      } else {\n        code = `const ${name} = [\n  ${body}\n];`;\n      }\n\n      state.entries.push(code);\n      state.previous = {\n        name: name,\n        body: body\n      };\n\n      return state;\n    }, {\n      entries: [],\n      previous: null\n    })\n    .entries\n    .join('\\n\\n');\n\n  return [code];\n}\n\nfunction strippedName (name) {\n  return name.replace(/[0-9]/g, '');\n}\n\nfunction structDescriptionToCode (funcs) {\n  const funcDecls = funcs\n    .filter(([, , retType]) => retType !== null)\n    .reduce((state, [offset, name, retType, args]) => {\n      const padding = offset - state.previousOffset - 1;\n      if (padding > 0) {\n        state.items.push(`padding(${padding})`);\n      }\n      state.previousOffset = offset;\n\n      state.items.push(funcDescriptionToCode({\n        offset: offset,\n        name: name,\n        retType: retType,\n        args: args\n      }, 0));\n\n      return state;\n    }, {\n      items: [],\n      previousOffset: 0\n    })\n    .items;\n  return funcDecls.join(',\\n  ');\n}\n\nfunction funcDescriptionToCode (func, indentLevel) {\n  const indentation = makeIndentation(indentLevel);\n  let argDecls;\n  if (func.args.length > 0) {\n    argDecls = `[\n${indentation}    ${func.args.map(argDescriptionToCode, func).join(',\\n' + indentation + '    ')}\n${indentation}  ]`;\n  } else {\n    argDecls = '[]';\n  }\n  return `func('${func.name}', ${retTypeDescriptionToCode(func.retType)}, ${argDecls})`;\n}\n\nfunction argDescriptionToCode (arg) {\n  const name = arg[0];\n  const type = arg[1];\n  const direction = argDirection(arg);\n\n  let condition;\n  if (direction === 'Out' && this.retType === 'Int')\n    condition = `, when('result', isZero)`;\n  else\n    condition = '';\n\n  return `arg${direction}('${name}', ${typeDescriptionToCode(type)}${condition})`;\n}\n\nfunction retTypeDescriptionToCode (type) {\n  if (type === 'Void') {\n    return 'null';\n  }\n\n  return `retval(${typeDescriptionToCode(type)})`;\n}\n\nfunction typeDescriptionToCode (type) {\n  if (typeof type === 'object') {\n    if (type.length === 2) {\n      const pointee = type[1];\n      if (pointee[0] === 'Char_S')\n        return 'UTF8';\n    } else if (type.length > 2) {\n      if (isPointer(type[1]))\n        return 'pointer(POINTER)';\n    }\n    return 'POINTER';\n  } else if (type === 'UChar') {\n    return 'BYTE';\n  } else {\n    return type.toUpperCase();\n  }\n}\n\nfunction argDirection (arg) {\n  const type = arg[1];\n  if (typeof type === 'object' && type.length > 2) {\n    if (isPointer(type[1]))\n      return 'Out';\n    else\n      return 'In';\n  } else {\n    return 'In';\n  }\n}\n\nfunction isPointer (type) {\n  return type[0] === 'Pointer';\n}\n\nfunction makeIndentation (level) {\n  const result = [];\n  for (let i = 0; i !== level; i++) {\n    result.push('  ');\n  }\n  return result.join('');\n}\n"
  },
  {
    "path": "frida-trace-tools/bin/parse-header.js",
    "content": "#!/usr/bin/env node\nimport clang from 'frida-libclang';\nimport dynamicClang from 'frida-libclang/lib/dynamic_clang';\n\nconst clangApi = dynamicClang.libclang;\nconst {CXCallingConv_Invalid} = dynamicClang.CONSTANTS.CXCallingConv;\n\nconst Cursor = clang.Cursor;\nconst Index = clang.Index;\nconst TranslationUnit = clang.TranslationUnit;\nconst Type = clang.Type;\n\nif (process.argv.length !== 3) {\n  process.stderr.write('Usage: ' + process.argv[1] + ' /path/to/header.h\\n');\n  process.exit(1);\n}\n\nconst data = parseHeader(process.argv[2]);\nprocess.stdout.write(JSON.stringify(data));\n\nfunction parseHeader (path) {\n  const index = new Index(true, true);\n  const unit = TranslationUnit.fromSource(index, path, ['-I/usr/include']);\n\n  const funcs = [];\n  const structs = [];\n\n  unit.cursor.visitChildren(function (parent) {\n    switch (this.kind) {\n      case Cursor.FunctionDecl:\n        funcs.push(parseFunction(this));\n        break;\n      case Cursor.StructDecl: {\n        const struct = parseStruct(this);\n        const [name, funcs] = struct;\n        if (name !== null && funcs.length > 0) {\n          structs.push(struct);\n        }\n        break;\n      }\n    }\n    return Cursor.Continue;\n  });\n\n  index.dispose();\n\n  return {\n    functions: funcs,\n    structs: structs\n  };\n}\n\nfunction parseFunction (cursor) {\n  const name = cursor.spelling;\n\n  const retType = parseType(new Type(clangApi.clang_getCursorResultType(cursor._instance)));\n  const args = [];\n\n  cursor.visitChildren(function (parent) {\n    switch (this.kind) {\n      case Cursor.ParmDecl:\n        const argName = this.spelling || 'a' + (args.length + 1);\n        const argType = parseType(this.type);\n        args.push([argName, argType]);\n        break;\n      default:\n        break;\n    }\n\n    return Cursor.Continue;\n  });\n\n  return [name, retType, args];\n}\n\nfunction parseStruct (cursor) {\n  const name = cursor.spelling;\n  if (name === '') {\n    return [null, []];\n  }\n\n  const funcs = [];\n\n  let func = null;\n  let args = null;\n\n  cursor.visitChildren(function (parent) {\n    switch (this.kind) {\n      case Cursor.FieldDecl:\n        if (isFunctionPointer(this.type)) {\n          const offset = clangApi.clang_Cursor_getOffsetOfField(this._instance) / 8 / 8;\n\n          args = [];\n          func = [offset, this.spelling, 'Void', args];\n\n          funcs.push(func);\n\n          return Cursor.Recurse;\n        }\n        break;\n      case Cursor.TypeRef:\n        func[2] = parseType(this);\n        break;\n      case Cursor.ParmDecl:\n        const argName = this.spelling || 'a' + (args.length + 1);\n        const argType = parseType(this.type);\n        args.push([argName, argType]);\n        break;\n      default:\n        break;\n    }\n\n    return Cursor.Continue;\n  });\n\n  return [name, funcs];\n}\n\nfunction isFunctionPointer (type) {\n  const name = type.spelling;\n  if (name !== 'Pointer') {\n    return false;\n  }\n\n  const conv = clangApi.clang_getFunctionTypeCallingConv(clangApi.clang_getPointeeType(type._instance));\n\n  return conv !== CXCallingConv_Invalid;\n}\n\nfunction parseType (type) {\n  const name = type.spelling;\n  if (name === 'Pointer') {\n    const path = [\n      ['Pointer', parseQualifiers(type)]\n    ];\n\n    let t = type;\n    do {\n      t = new Type(clangApi.clang_getPointeeType(t._instance));\n      path.push([t.spelling, parseQualifiers(t)]);\n    } while (t.spelling === 'Pointer');\n\n    return path;\n  } else if (name === 'Typedef') {\n    return parseType(new Type(clangApi.clang_getCanonicalType(type._instance)));\n  } else {\n    return name;\n  }\n}\n\nfunction parseQualifiers (type) {\n  return clangApi.clang_isConstQualifiedType(type._instance) ? ['const'] : [];\n}\n"
  },
  {
    "path": "frida-trace-tools/package.json",
    "content": "{\n  \"name\": \"frida-trace-tools\",\n  \"version\": \"3.3.0\",\n  \"description\": \"Generate trace declarations from docs\",\n  \"files\": [\n    \"/bin/*.js\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/nowsecure/frida-trace.git\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/nowsecure/frida-trace/issues\"\n  },\n  \"homepage\": \"https://github.com/nowsecure/frida-trace#readme\",\n  \"scripts\": {\n    \"frida-trace-generate-boilerplate\": \"bin/generate-boilerplate.js\",\n    \"frida-trace-parse-header\": \"bin/parse-header.js\"\n  },\n  \"devDependencies\": {\n    \"concat-stream\": \"^2.0.0\",\n    \"frida-libclang\": \"0.0.12\"\n  }\n}\n"
  },
  {
    "path": "index.js",
    "content": "const IN = Symbol('in');\nconst OUT = Symbol('out');\nconst IN_OUT = Symbol('in-out');\n\nconst pointerSize = Process.pointerSize;\n\nexport default function trace (spec) {\n  const {module, vtable, functions} = spec;\n  const {onError} = spec.callbacks;\n\n  const listeners = [];\n  const intercept = makeInterceptor(spec);\n\n  if (module !== undefined) {\n    const mod = Process.getModuleByName(module);\n    functions.forEach(func => {\n      const {name} = func;\n\n      const impl = mod.findExportByName(name);\n      if (impl === null) {\n        onError(new Error(`Failed to resolve ${module}!${name}`));\n        return;\n      }\n\n      listeners.push(intercept(func, impl));\n    });\n  } else if (vtable !== undefined) {\n    let offset = 0;\n\n    for (let entry of functions) {\n      const isPadding = Array.isArray(entry);\n      if (isPadding) {\n        const [n] = entry;\n        offset += n * pointerSize;\n        continue;\n      }\n\n      let impl;\n      try {\n        impl = vtable.add(offset).readPointer();\n      } catch (e) {\n        onError(new Error(`Failed to read from vtable at offset ${offset}: ${e}`));\n        break;\n      }\n\n      listeners.push(intercept(entry, impl));\n\n      offset += pointerSize;\n    }\n  } else {\n    throw new Error('Either a module or a vtable must be specified');\n  }\n\n  return new Session(listeners);\n}\n\ntrace.func = func;\ntrace.argIn = argIn;\ntrace.argOut = argOut;\ntrace.argInOut = argInOut;\ntrace.retval = retval;\ntrace.padding = padding;\n\ntrace.bind = bind;\ntrace.when = when;\n\ntrace.types = {\n  BOOL: bool(),\n  BYTE: byte(),\n  SHORT: short(),\n  INT: int(),\n  POINTER: pointer(),\n  BYTE_ARRAY: byteArray(),\n  UTF8: utf8(),\n  UTF16: utf16(),\n  CSTRING: cstring(),\n\n  bool: bool,\n  byte: byte,\n  short: short,\n  int: int,\n  pointer: pointer,\n  byteArray: byteArray,\n  utf8: utf8,\n  utf16: utf16,\n  cstring: cstring\n};\n\nfunction makeInterceptor (spec) {\n  const {shouldSkip, onEvent, onEnter, onLeave, onError} = spec.callbacks;\n\n  return function (func, impl) {\n    const name = func.name;\n\n    const inputActions = [];\n    const outputActions = [];\n    if (!computeActions(func, inputActions, outputActions)) {\n      onError(new Error(`Oops. It seems ${module}!${name} has circular dependencies.`));\n      return;\n    }\n\n    const numArgs = func.args.length;\n    const numInputActions = inputActions.length;\n    const numOutputActions = outputActions.length;\n\n    return Interceptor.attach(impl, {\n      onEnter (args) {\n        if (shouldSkip?.(this)) {\n          this._skip = true;\n          return;\n        }\n        const values = [];\n        for (let i = 0; i !== numArgs; i++) {\n          values.push(args[i]);\n        }\n\n        const event = new Event(name);\n        for (let i = 0; i !== numInputActions; i++) {\n          const [action, params] = inputActions[i];\n          action(values, event, params);\n        }\n        if (onEnter !== undefined) {\n          onEnter.call(this, event, args);\n        }\n\n        this.values = values;\n        this.event = event;\n      },\n      onLeave (retval) {\n        if (this._skip === true) {\n          return;\n        }\n        const values = this.values;\n        const event = this.event;\n\n        values.push(retval);\n\n        for (let i = 0; i !== numOutputActions; i++) {\n          const [action, params] = outputActions[i];\n          action(values, event, params);\n        }\n        if (onLeave !== undefined) {\n          onLeave.call(this, event, retval);\n        }\n\n        onEvent(event);\n      }\n    });\n  };\n}\n\nfunction computeActions (func, inputActions, outputActions) {\n  const args = func.args.slice();\n  if (func.ret !== null) {\n    args.push(func.ret);\n  }\n\n  const satisfied = new Set();\n  let previousSatisfiedSize;\n\n  do {\n    previousSatisfiedSize = satisfied.size;\n\n    args.forEach(function (arg, index) {\n      if (satisfied.has(arg.name)) {\n        return;\n      }\n      const remaining = arg.requires.filter(dep => !satisfied.has(dep));\n      if (remaining.length === 0) {\n        inputActions.push(computeAction(arg, index));\n        satisfied.add(arg.name);\n      }\n    });\n  } while (satisfied.size !== previousSatisfiedSize);\n\n  satisfied.add('$out');\n\n  do {\n    previousSatisfiedSize = satisfied.size;\n\n    args.forEach(function (arg, index) {\n      if (satisfied.has(arg.name)) {\n        return;\n      }\n      const remaining = arg.requires.filter(dep => !satisfied.has(dep));\n      if (remaining.length === 0) {\n        outputActions.push(computeAction(arg, index));\n        satisfied.add(arg.name);\n      }\n    });\n  } while (satisfied.size !== previousSatisfiedSize);\n\n  return !args.some(arg => !satisfied.has(arg.name));\n}\n\nfunction computeAction (arg, index) {\n  const {name, type, condition} = arg;\n\n  const hasDependentType = Array.isArray(type);\n  const hasCondition = condition !== null;\n\n  if (hasDependentType) {\n    if (hasCondition) {\n      return [readValueWithDependentTypeConditionally, [index, name, type[0].parse, type[1], condition]];\n    }\n    return [readValueWithDependentType, [index, name, type[0].parse, type[1]]];\n  }\n  if (hasCondition) {\n    return [readValueConditionally, [index, name, type.parse, condition]];\n  }\n  return [readValue, [index, name, type.parse]];\n}\n\nfunction readValue (values, event, params) {\n  const [index, name, parse] = params;\n\n  event.set(name, parse(values[index]));\n}\n\nfunction readValueConditionally (values, event, params) {\n  const [index, name, parse, condition] = params;\n\n  if (condition.predicate(event.get(condition.value))) {\n    event.set(name, parse(values[index]));\n  }\n}\n\nfunction readValueWithDependentType (values, event, params) {\n  const [index, name, parse, binding] = params;\n\n  const typeParameters = {};\n  typeParameters[binding.property] = event.get(binding.value);\n  event.set(name, parse(values[index], typeParameters));\n}\n\nfunction readValueWithDependentTypeConditionally (values, event, params) {\n  const [index, name, parse, binding, condition] = params;\n\n  if (condition.predicate(event.get(condition.value))) {\n    const typeParameters = {};\n    typeParameters[binding.property] = event.get(binding.value);\n    event.set(name, parse(values[index], typeParameters));\n  }\n}\n\nfunction func (name, ret, args) {\n  return {\n    name: name,\n    ret: ret,\n    args: args\n  };\n}\n\nfunction argIn (name, type, condition) {\n  return arg(IN, name, type, condition);\n}\n\nfunction argOut (name, type, condition) {\n  return arg(OUT, name, type, condition);\n}\n\nfunction argInOut (name, type, condition) {\n  return arg(IN_OUT, name, type, condition);\n}\n\nfunction arg (direction, name, type, condition) {\n  condition = condition || null;\n\n  return {\n    direction: direction,\n    name: name,\n    type: type,\n    condition: condition,\n    requires: dependencies(direction, type, condition)\n  };\n}\n\nfunction retval (type, condition) {\n  return argOut('result', type, condition);\n}\n\nfunction padding (n) {\n  return [n];\n}\n\nfunction bind (property, value) {\n  return {\n    property: property,\n    value: value\n  };\n}\n\nfunction when (value, predicate) {\n  return {\n    value: value,\n    predicate: predicate\n  };\n}\n\nfunction dependencies (direction, type, condition) {\n  const result = [];\n\n  if (direction === OUT) {\n    result.push('$out');\n  }\n\n  if (Array.isArray(type)) {\n    result.push(type[1].value);\n  }\n\n  if (condition !== null) {\n    result.push(condition.value);\n  }\n\n  return result;\n}\n\nfunction bool () {\n  return {\n    parse (rawValue) {\n      return !!rawValue.toInt32();\n    },\n    read (ptr) {\n      return !!ptr.readU8();\n    }\n  };\n}\n\nfunction byte () {\n  return {\n    parse (rawValue) {\n      return rawValue.toInt32() & 0xff;\n    },\n    read (ptr) {\n      return ptr.readU8();\n    }\n  };\n}\n\nfunction short () {\n  return {\n    parse (rawValue) {\n      return rawValue.toInt32() & 0xffff;\n    },\n    read (ptr) {\n      return ptr.readShort();\n    }\n  };\n}\n\nfunction int () {\n  return {\n    parse (rawValue) {\n      return rawValue.toInt32();\n    },\n    read (ptr) {\n      return ptr.readInt();\n    }\n  };\n}\n\nfunction pointer (pointee) {\n  return {\n    parse (rawValue, parameters) {\n      if (pointee) {\n        if (rawValue.isNull()) {\n          return null;\n        } else {\n          return pointee.read(rawValue, parameters);\n        }\n      } else {\n        return rawValue;\n      }\n    },\n    read (ptr) {\n      return ptr.readPointer();\n    }\n  };\n}\n\nfunction byteArray () {\n  return pointer({\n    read (ptr, parameters) {\n      return ptr.readByteArray(parameters.length);\n    }\n  });\n}\n\nfunction utf8 () {\n  return pointer({\n    read (ptr, parameters) {\n      const length = (parameters === undefined) ? -1 : parameters.length;\n      return ptr.readUtf8String(length);\n    }\n  });\n}\n\nfunction utf16 () {\n  return pointer({\n    read (ptr, parameters) {\n      const length = (parameters === undefined) ? -1 : parameters.length;\n      return ptr.readUtf16String(length);\n    }\n  });\n}\n\nfunction cstring () {\n  return pointer({\n    read (ptr, parameters) {\n      const length = (parameters === undefined) ? -1 : parameters.length;\n      return ptr.readCString(length);\n    }\n  });\n}\n\nclass Session {\n  constructor (listeners) {\n    this._listeners = listeners;\n  }\n\n  stop () {\n    this._listeners.forEach(listener => {\n      listener.detach();\n    });\n  }\n}\n\nclass Event {\n  constructor (name) {\n    this.name = name;\n    this.args = {};\n  }\n\n  get (key) {\n    return (key === 'result') ? this.result : this.args[key];\n  }\n\n  set (key, value) {\n    if (key === 'result') {\n      this.result = value;\n    } else {\n      this.args[key] = value;\n    }\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"frida-trace\",\n  \"version\": \"4.0.1\",\n  \"description\": \"Trace APIs declaratively\",\n  \"keywords\": [\n    \"frida-gum\"\n  ],\n  \"main\": \"index.js\",\n  \"type\": \"module\",\n  \"files\": [\n    \"/index.js\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/nowsecure/frida-trace.git\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/nowsecure/frida-trace/issues\"\n  },\n  \"homepage\": \"https://github.com/nowsecure/frida-trace#readme\"\n}\n"
  }
]