'use strict';

const r2frida = require('./plugin'); // eslint-disable-line
const config = require('./config');

let cachedRanges = [];

function read (params) {
  const { offset, count, fast } = params;
  if (r2frida.hookedRead !== null) {
    return r2frida.hookedRead(offset, count);
  }
  if (r2frida.safeio) {
    if (cachedRanges.length === 0) {
      cachedRanges = Process.enumerateRanges('').map(
        (map) => [map.base, ptr(map.base).add(map.size)]);
    }
    // TODO: invalidate ranges at some point to refresh
    // process.nextTick(() => { cachedRanges = null; }
    const o = ptr(offset);
    for (const map of cachedRanges) {
      if (o.compare(map[0]) >= 0 && o.compare(map[1]) < 0) {
        let left = count;
        if (o.add(count).compare(map[1]) > 0) {
          const rest = o.add(count).sub(map[1]);
          left = left.sub(rest);
        }
        const bytes = Memory.readByteArray(o, left);
        return [{}, (bytes !== null) ? bytes : []];
      }
    }
    return [{}, []];
  }
  if (offset < 0) {
    return [{}, []];
  }
  try {
    const bytes = Memory.readByteArray(ptr(offset), count);
    // console.log("FAST", offset);
    return [{}, (bytes !== null) ? bytes : []];
  } catch (e) {
    if (!fast) {
      try {
      // console.log("SLOW", offset);
        const readStarts = ptr(offset);
        const readEnds = readStarts.add(count);
        const currentRange = Process.getRangeByAddress(readStarts); // this is very slow
        const moduleEnds = currentRange.base.add(currentRange.size);
        const left = (readEnds.compare(moduleEnds) > 0
          ? readEnds
          : moduleEnds).sub(offset);
        const bytes = Memory.readByteArray(ptr(offset), +left);
        return [{}, (bytes !== null) ? bytes : []];
      } catch (e) {
      // do nothing
      }
    }
  }
  return [{}, []];
}

function isExecutable (address) {
  const currentRange = Process.getRangeByAddress(address);
  return currentRange.protection.indexOf('x') !== -1;
}

function write (params, data) {
  if (typeof r2frida.hookedWrite === 'function') {
    return r2frida.hookedWrite(params.offset, data);
  }
  if (config.getBoolean('patch.code') && isExecutable(ptr(params.offset))) {
    if (typeof Memory.patchCode === 'function') {
      Memory.patchCode(ptr(params.offset), 1, function (ptr) {
        Memory.writeByteArray(ptr, data);
      });
    } else {
      Memory.writeByteArray(ptr(params.offset), data);
    }
  } else {
    Memory.writeByteArray(ptr(params.offset), data);
  }
  return [{}, null];
}

module.exports = {
  read: read,
  write: write
};

@@ -303,24 +303,36 @@
 a.safeio) %7B%0A
+    try %7B%0A  
     if (cach
@@ -352,24 +352,26 @@
 th === 0) %7B%0A
+  
       cached
@@ -420,16 +420,18 @@
         
+  
 (map) =%3E
@@ -469,24 +469,26 @@
 ap.size)%5D);%0A
+  
     %7D%0A    //
@@ -485,16 +485,18 @@
   %7D%0A    
+  
 // TODO:
@@ -543,16 +543,18 @@
 esh%0A    
+ 
+ 
 // proce
@@ -596,24 +596,26 @@
 null; %7D%0A    
+  
 const o = pt
@@ -629,16 +629,18 @@
 t);%0A    
+ 
+ 
 for (con
@@ -661,24 +661,26 @@
 edRanges) %7B%0A
+  
       if (o.
@@ -736,16 +736,18 @@
         
+  
 let left
@@ -756,16 +756,18 @@
  count;%0A
+  
         
@@ -808,32 +808,34 @@
  0) %7B%0A          
+  
 const rest = o.a
@@ -867,16 +867,18 @@
         
+ 
+ 
 left = l
@@ -896,26 +896,30 @@
 t);%0A        
-%7D%0A
+  %7D%0A  
         cons
@@ -955,24 +955,26 @@
 y(o, left);%0A
+  
         retu
@@ -1019,24 +1019,30 @@
 ;%0A      
+  
 %7D%0A    
+ 
+ 
 %7D%0A
+  
     retu
@@ -1050,24 +1050,87 @@
 n %5B%7B%7D, %5B%5D%5D;%0A
+    %7D catch (e) %7B%0A      console.error('safeio-read', e);%0A    %7D%0A
   %7D%0A  if (of

Improve error handling in the cached io read logic