See the code behind the binary.
Drop in any compiled file and get readable, annotated source back in minutes. Faster, cleaner, and more accurate than the tools you know.
Everything you need to read a binary.
Built for reverse engineers, security researchers, and curious developers who are tired of fighting their tools.
Dramatically faster
Parallel mode spreads the work across as many CPU cores as you have, decompiling many functions at once to cut processing time on large binaries dramatically.
Actually readable
Type inference, variable naming, and structural reconstruction produce output that is close to the original source except for information lost during compilation.
Private by default
Your files are processed in isolation, never stored beyond the job, and deleted once results are delivered.
Up-to-date bytecode support
Handles the latest React Native (Hermes) bytecode version plus several earlier ones, and is updated regularly as new virtual machine versions ship.
Symbolic analysis
Beyond disassembly: we reconstruct control flow, recover structs, and label standard library calls automatically.
Built for teams
Share results with your team, annotate findings, and keep a searchable history of every analysis.
See it side by side.
The same production React Native bundle, decompiled by both tools. Every snippet below is copied verbatim from each tool's output — nothing hand-edited.
Source: forum-admin-mobileapp · index.android.bundle · Hermes HBC v96 (React Native 0.74) · 9,043 functions.
| Capability | hermes-dec | Decompiler X |
|---|---|---|
| Structured control flow (if / for / while) hermes-dec: for(;;)switch(ip) state machine | ✗ | ✔ |
| Loops rendered as loops | ✗ | ✔ |
| switch statements hermes-dec: compare-and-goto chain | ~ | ✔ |
| try / catch / finally as real blocks hermes-dec: comment labels + CatchBlockStart() | ✗ | ✔ |
| Compound expressions (folded registers) | ✗ | ✔ |
| JSX reconstruction Decompiler X --jsx | ✗ | ✔ |
| Always emits syntactically valid JS both have rough edges | ✗ | ~ |
| Output is directly re-runnable neither guarantees executable output | ✗ | ✗ |
| No pseudo-builtins in output hermes-dec emits CatchBlockStart(), etc. | ✗ | ✔ |
Loop reconstruction
for…inDecompiler X rebuilds a real for (let key in t5) { … continue; } loop. hermes-dec turns the same iteration into _fun6_ip = 78; continue jumps between numbered case labels — the loop is nowhere to be seen.
let t3 = function metroImportAll(par1) {
let r1;
let t0 = env1;
let call_get = t0.get(par1);
if (call_get) {
let t2 = env2;
if (call_get.importedAll !== t2) {
return call_get.importedAll;
} else {
r1 = t2;
}
} else {
r1 = t0;
}
let t5 = env8(par1);
if (t5) {
let r4;
let r2;
if (t5.__esModule) {
r1 = t5;
r4 = par1;
} else {
let r8;
let r6;
r1 = t5;
let obj = {};
if (t5) {
r8 = 0;
for (let key in t5) {
let call_call = hasOwnProperty.call(t5, key);
if (!(call_call)) {
r6 = key;
continue;
} else {
obj[key] = t5[key];
r6 = key;
continue;
}
}
}
obj["default"] = t5;
r1 = obj;
r4 = par1;
}
let call_get1 = env1.get(r4);
call_get1.importedAll = r1;
return r1;
} else {
r1 = undefined;
}
return r1;
};
r5 = function(a0) { // Original name: metroImportAll, environment: r2
_fun6: for(var _fun6_ip = 0; ; ) switch(_fun6_ip) {
case 0:
r4 = a0;
r1 = _closure1_slot1;
r0 = r1.get;
r0 = r0.bind(r1)(r4);
if(!r0) { _fun6_ip = 37; continue _fun6 }
case 23:
r3 = r0.importedAll;
r1 = _closure1_slot2;
if(!(r3 === r1)) { _fun6_ip = 149; continue _fun6 }
case 37:
r3 = _closure1_slot9;
r1 = undefined;
r5 = r3.bind(r1)(r4);
if(!r5) { _fun6_ip = 62; continue _fun6 }
case 51:
r3 = r5.__esModule;
r1 = r5;
if(r3) { _fun6_ip = 127; continue _fun6 }
case 62:
r3 = {};
if(!r5) { _fun6_ip = 118; continue _fun6 }
case 67:
r9 = r5;
for(r6 in r9)
case 78:
{
case 87:
r12 = r6;
r13 = _closure1_slot3;
r11 = r13.call;
r11 = r11.bind(r13)(r5, r12);
if(!r11) { _fun6_ip = 78; continue _fun6 }
case 108:
r11 = r5[r12];
r3[r12] = r11;
_fun6_ip = 78; continue _fun6;
}
case 118:
r3['default'] = r5;
r1 = r3;
case 127:
r3 = _closure1_slot1;
r2 = r3.get;
r2 = r2.bind(r3)(r4);
r2['importedAll'] = r1;
return r1;
case 149:
r0 = r0.importedAll;
return r0;
}
};
Conditional structuring
if / elseDecompiler X recovers nested if/else with early return. hermes-dec expresses each branch as if(!cond){ _fun5_ip = N; continue } forward gotos inside a dispatch loop.
let t2 = function metroImportDefault(par1) {
let r1 = env1;
let call_get = r1.get(par1);
if (call_get) {
r1 = env2;
if (call_get.importedDefault !== r1) {
return call_get.importedDefault;
} else {
}
} else {
}
let t5 = env8(par1);
if (t5) {
if (t5.__esModule) {
r1 = t5["default"];
} else {
r1 = t5;
}
} else {
r1 = t5;
}
let call_get1 = env1.get(par1);
call_get1.importedDefault = r1;
return r1;
};
r6 = function(a0) { // Original name: metroImportDefault, environment: r2
_fun5: for(var _fun5_ip = 0; ; ) switch(_fun5_ip) {
case 0:
r4 = a0;
r1 = _closure1_slot1;
r0 = r1.get;
r0 = r0.bind(r1)(r4);
if(!r0) { _fun5_ip = 37; continue _fun5 }
case 23:
r3 = r0.importedDefault;
r1 = _closure1_slot2;
if(!(r3 === r1)) { _fun5_ip = 92; continue _fun5 }
case 37:
r3 = _closure1_slot9;
r1 = undefined;
r3 = r3.bind(r1)(r4);
r1 = r3;
if(!r3) { _fun5_ip = 70; continue _fun5 }
case 54:
r5 = r3.__esModule;
r1 = r3;
if(!r5) { _fun5_ip = 70; continue _fun5 }
case 65:
r1 = r3.default;
case 70:
r3 = _closure1_slot1;
r2 = r3.get;
r2 = r2.bind(r3)(r4);
r2['importedDefault'] = r1;
return r1;
case 92:
r0 = r0.importedDefault;
return r0;
}
};
Exception handling
try / catchDecompiler X emits a genuine try { … } catch (e) { … }. hermes-dec marks the region with comment labels (// try_start_0, // catch_target0) and a pseudo-builtin CatchBlockStart(arg_register=7) that is not valid JavaScript.
var env11 = function guardedLoadModule(_par1, par2) {
if (!(env3)) {
if (par1.ErrorUtils) {
let r4;
env3 = true;
try {
let t2 = env13;
r4 = undefined;
let t3 = t2(_par1, par2);
r4 = t3;
} catch (e) {
par1.ErrorUtils.reportFatalError(e);
}
env3 = false;
return r4;
}
}
return env13(_par1, par2);
};
r0 = function(a0, a1) { // Original name: guardedLoadModule, environment: r2
_fun7: for(var _fun7_ip = 0; ; ) switch(_fun7_ip) {
case 0:
r1 = a0;
r0 = a1;
r3 = undefined;
r4 = undefined;
r5 = _closure1_slot4;
if(r5) { _fun7_ip = 88; continue _fun7 }
case 20:
r5 = _closure1_slot0;
r5 = r5.ErrorUtils;
if(!r5) { _fun7_ip = 88; continue _fun7 }
case 33:
r5 = true;
_closure1_slot4 = r5;
case 39: // try_start_0
r7 = _closure1_slot14;
r6 = r1;
r5 = r0;
r4 = r7.bind(r3)(r6, r5);
case 55: // try_end0
_fun7_ip = 80; continue _fun7;
case 57: // catch_target0
CatchBlockStart(arg_register=7);
r5 = _closure1_slot0;
r6 = r5.ErrorUtils;
r5 = r6.reportFatalError;
r5 = r5.bind(r6)(r7);
case 80:
r5 = false;
_closure1_slot4 = r5;
return r4;
case 88:
r2 = _closure1_slot14;
r0 = r2.bind(r3)(r1, r0);
return r0;
}
};
Switch statement
switchDecompiler X keeps a recognizable switch (par1) { case 16: … } over React's lane bitmasks. hermes-dec lowers it to ~30 sequential compare-and-goto blocks whose case labels are bytecode offsets, not the switch values.
function computeExpirationTime(par1, par2) {
if (1 !== par1) if (2 !== par1) if (4 !== par1) if (8 !== par1) {
switch (par1) {
case 16:
case 32:
case 64:
case 128:
case 256:
case 512:
case 1024:
case 2048:
case 4096:
case 8192:
case 16384:
case 32768:
case 65536:
case 131072:
case 262144:
case 524288:
case 1048576:
case 2097152:
break;
default:
switch (par1) {
case 4194304:
case 8388608:
case 16777216:
case 33554432:
break;
default:
switch (par1) {
case 67108864:
case 134217728:
case 268435456:
case 536870912:
case 1073741824:
break;
default:
return -1;
break;
};
return -1;
break;
};
return -1;
break;
}
return par2 + 5000;
}
return par2 + 250;
}
r0 = function(a0, a1) { // Original name: computeExpirationTime, environment: r1
_fun633: for(var _fun633_ip = 0; ; ) switch(_fun633_ip) {
case 0:
r2 = a0;
r1 = a1;
r0 = 1;
if(!(r0 !== r2)) { _fun633_ip = 391; continue _fun633 }
case 16:
r0 = 2;
if(!(r0 !== r2)) { _fun633_ip = 391; continue _fun633 }
case 26:
r0 = 4;
if(!(r0 !== r2)) { _fun633_ip = 391; continue _fun633 }
case 36:
r0 = 8;
if(!(r0 !== r2)) { _fun633_ip = 391; continue _fun633 }
case 46:
r0 = 16;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 56:
r0 = 32;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 66:
r0 = 64;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 76:
r0 = 128;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 86:
r0 = 256;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 99:
r0 = 512;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 112:
r0 = 1024;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 125:
r0 = 2048;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 138:
r0 = 4096;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 151:
r0 = 8192;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 164:
r0 = 16384;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 177:
r0 = 32768;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 190:
r0 = 65536;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 203:
r0 = 131072;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 216:
r0 = 262144;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 229:
r0 = 524288;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 242:
r0 = 1048576;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 255:
r0 = 2097152;
if(!(r0 !== r2)) { _fun633_ip = 379; continue _fun633 }
case 265:
r0 = 4194304;
if(!(r0 !== r2)) { _fun633_ip = 371; continue _fun633 }
case 275:
r0 = 8388608;
if(!(r0 !== r2)) { _fun633_ip = 371; continue _fun633 }
case 285:
r0 = 16777216;
if(!(r0 !== r2)) { _fun633_ip = 371; continue _fun633 }
case 295:
r0 = 33554432;
if(!(r0 !== r2)) { _fun633_ip = 371; continue _fun633 }
case 305:
r0 = 67108864;
if(!(r0 !== r2)) { _fun633_ip = 363; continue _fun633 }
case 315:
r0 = 134217728;
if(!(r0 !== r2)) { _fun633_ip = 363; continue _fun633 }
case 325:
r0 = 268435456;
if(!(r0 !== r2)) { _fun633_ip = 363; continue _fun633 }
case 335:
r0 = 536870912;
if(!(r0 !== r2)) { _fun633_ip = 363; continue _fun633 }
case 345:
r0 = 1073741824;
if(!(r0 !== r2)) { _fun633_ip = 363; continue _fun633 }
case 355:
r0 = -1;
return r0;
case 363:
r0 = -1;
return r0;
case 371:
r0 = -1;
return r0;
case 379:
r0 = 5000;
r0 = r1 + r0;
return r0;
case 391:
r0 = 250;
r0 = r1 + r0;
return r0;
}
Expression folding
expressionsDecompiler X folds register chains back into compound expressions (t1 & -536870913) with named locals. hermes-dec keeps every intermediate as its own register write threaded through goto blocks.
function getLanesToRetrySynchronouslyOnError(par1, par2) {
if (par1.errorRecoveryDisabledLanes & par2) {
return 0;
} else {
let r1;
let t1 = par1.pendingLanes;
if (0 !== (t1 & -536870913)) {
r1 = t1 & -536870913;
} else {
let val0;
if (t1 & -536870913 & 536870912) {
val0 = 536870912;
} else {
val0 = 0;
}
r1 = val0;
}
return r1;
}
}
r0 = function(a0, a1) { // Original name: getLanesToRetrySynchronouslyOnError, environment: r1
_fun634: for(var _fun634_ip = 0; ; ) switch(_fun634_ip) {
case 0:
r1 = a0;
r2 = r1.errorRecoveryDisabledLanes;
r0 = a1;
r2 = r2 & r0;
r0 = 0;
if(r2) { _fun634_ip = 67; continue _fun634 }
case 21:
r2 = r1.pendingLanes;
r1 = -536870913;
r2 = r2 & r1;
r1 = r2;
if(!(r0 === r1)) { _fun634_ip = 65; continue _fun634 }
case 44:
r3 = 536870912;
r4 = r2 & r3;
r2 = 0;
if(!r4) { _fun634_ip = 62; continue _fun634 }
case 59:
r2 = r3;
case 62:
r1 = r2;
case 65:
return r1;
case 67:
return r0;
}
};
ES5 inheritance helper
class / superThis RN 0.74 bundle was transpiled to ES5 before Hermes, so neither tool sees a class keyword. Decompiler X still renders Babel's _callSuper helper as clean if/else over Reflect.construct. (Decompiler X reconstructs native class syntax on v98 bundles.)
var env16 = function _callSuper(par1, par2, par3) {
let r0;
let call_default = env5["default"](par2);
let t3 = env4["default"];
if (env17()) {
let val0;
let t6 = global.Reflect.construct;
if (par3) {
val0 = par3;
} else {
val0 = [];
}
let call_default2 = env5["default"](par1);
let call_construct = t6.call(global.Reflect, call_default, val0, call_default2.constructor);
r0 = call_construct;
} else {
let call_apply = call_default.apply(par1, par3);
r0 = call_apply;
}
return t3(par1, r0);
};
r0 = function(a0, a1, a2) { // Original name: _callSuper, environment: r2
_fun119: for(var _fun119_ip = 0; ; ) switch(_fun119_ip) {
case 0:
r3 = a0;
r5 = a2;
r0 = _closure1_slot6;
r1 = r0.default;
r2 = undefined;
r0 = a1;
r8 = r1.bind(r2)(r0);
r0 = _closure1_slot5;
r1 = r0.default;
r0 = _closure1_slot20;
r0 = r0.bind(r2)();
if(r0) { _fun119_ip = 61; continue _fun119 }
case 48:
r0 = r8.apply;
r0 = r0.bind(r8)(r3, r5);
_fun119_ip = 107; continue _fun119;
case 61:
r6 = global;
r7 = r6.Reflect;
r6 = r7.construct;
if(r5) { _fun119_ip = 81; continue _fun119 }
case 77:
r5 = new Array(0);
case 81:
r4 = _closure1_slot6;
r4 = r4.default;
r4 = r4.bind(r2)(r3);
r4 = r4.constructor;
r0 = r6.bind(r7)(r8, r5, r4);
case 107:
r0 = r1.bind(r2)(r3, r0);
return r0;
}
};
Object construction
object literalA deliberately modest example, for balance: for pure straight-line code hermes-dec is fairly close. Decompiler X still reads better (named obj, dot access, env names) but here the gap is small.
let t4 = function unpackModuleId(par1) {
let t0 = env4;
let t1 = env5;
let obj = {};
obj.segmentId = par1 >>> t0;
obj.localId = par1 & t1;
return obj;
};
r3 = function(a0) { // Original name: unpackModuleId, environment: r2
r1 = a0;
r2 = _closure1_slot5;
r2 = r1 >>> r2;
r0 = _closure1_slot6;
r1 = r1 & r0;
r0 = {};
r0['segmentId'] = r2;
r0['localId'] = r1;
return r0;
};
JSX reconstruction
JSXWith JSX reconstruction on, Decompiler X rebuilds the real element tree: <Pressable onPress={…}><View><Text>{title}</Text>…</Pressable>. hermes-dec builds each element's props object field-by-field and calls jsx via .bind(r3)(Component, props) — and even destructures props as the invalid identifier r2.aria-busy.
// ... (Button props/state computed above, lines 96399-96537, elided) ...
}
let t23 = env7.jsx;
let t25 = env7.jsx;
let t27 = env5["default"];
return <env4 accessible={par1.accessible} accessibilityActions={par1.accessibilityActions} onAccessibilityAction={par1.onAccessibilityAction} accessibilityLabel={t1 || t0} accessibilityHint={par1.accessibilityHint} accessibilityLanguage={par1.accessibilityLanguage} accessibilityRole="button" accessibilityState={val7} importantForAccessibility={val13} hasTVPreferredFocus={par1.hasTVPreferredFocus} nextFocusDown={par1.nextFocusDown} nextFocusForward={par1.nextFocusForward} nextFocusLeft={par1.nextFocusLeft} nextFocusRight={par1.nextFocusRight} nextFocusUp={par1.nextFocusUp} testID={par1.testID} disabled={r11} onPress={par1.onPress} touchSoundDisabled={par1.touchSoundDisabled}><env5 style={arr}><env1 style={arr1} disabled={r11}>{val12}</env1></env5></env4>;
};
r1 = function(a0) { // Original name: Button, environment: r4
_fun2887: for(var _fun2887_ip = 0; ; ) switch(_fun2887_ip) {
case 0:
r2 = a0;
r23 = r2.accessibilityLabel;
r11 = r2.accessibilityState;
r29 = r2.aria-busy;
r27 = r2.aria-checked;
r19 = r2.aria-disabled;
r10 = r2.aria-expanded;
r22 = r2.aria-label;
r3 = r2.aria-selected;
r0 = r2.importantForAccessibility;
r30 = r2.color;
r5 = r2.onPress;
r4 = r2.touchSoundDisabled;
r18 = r2.title;
r17 = r2.hasTVPreferredFocus;
r16 = r2.nextFocusDown;
r15 = r2.nextFocusForward;
r14 = r2.nextFocusLeft;
r13 = r2.nextFocusRight;
r8 = r2.nextFocusUp;
r6 = r2.testID;
r26 = r2.accessible;
r25 = r2.accessibilityActions;
// ... (branches computing styles/labels, lines 112537-112700, elided) ...
r0 = _closure1_slot6;
r1 = r0.jsx;
r0 = {};
r0['accessible'] = r26;
r0['accessibilityActions'] = r25;
r0['onAccessibilityAction'] = r24;
if(r22) { _fun2887_ip = 657; continue _fun2887 }
case 654:
r22 = r23;
case 657:
r0['accessibilityLabel'] = r22;
r0['accessibilityHint'] = r21;
r0['accessibilityLanguage'] = r20;
r20 = 'button';
r0['accessibilityRole'] = r20;
r0['accessibilityState'] = r19;
r0['importantForAccessibility'] = r18;
r0['hasTVPreferredFocus'] = r17;
r0['nextFocusDown'] = r16;
r0['nextFocusForward'] = r15;
r0['nextFocusLeft'] = r14;
r0['nextFocusRight'] = r13;
r0['nextFocusUp'] = r8;
r0['testID'] = r6;
r0['disabled'] = r11;
r0['onPress'] = r5;
r0['touchSoundDisabled'] = r4;
r8 = _closure1_slot6;
r6 = r8.jsx;
r4 = _closure1_slot4;
r5 = r4.default;
r4 = {};
r4['style'] = r9;
r9 = r8.jsx;
r7 = _closure1_slot0;
r8 = r7.default;
r7 = {};
r7['style'] = r12;
r7['disabled'] = r11;
r7['children'] = r10;
r7 = r9.bind(r3)(r8, r7);
r4['children'] = r7;
r4 = r6.bind(r3)(r5, r4);
r0['children'] = r4;
r0 = r1.bind(r3)(r2, r0);
return r0;
Async / Promise
asyncDecompiler X: clean if/else with new Error("No mutationFn found") and a Promise.reject shape. hermes-dec renders new Error(…) as Object.create + new r8[r5](r7,r6) + (r1 instanceof Object ? r1 : r4).
obj1.fn = function fn() {
if (par0.options.mutationFn) {
return par0.options.mutationFn(par1, env1_local2);
} else {
let t8 = global.Promise.reject;
let t10 = new global.Error("No mutationFn found");
return t8.call(global.Promise, t10);
}
};
r36 = function() { // Original name: fn, environment: r35
_fun6088: for(var _fun6088_ip = 0; ; ) switch(_fun6088_ip) {
case 0:
r1 = _closure5_slot1;
r1 = r1.options;
r1 = r1.mutationFn;
if(r1) { _fun6088_ip = 71; continue _fun6088 }
case 21:
r1 = global;
r3 = r1.Promise;
r2 = r3.reject;
r5 = r1.Error;
r1 = r5.prototype;
r4 = Object.create(r1, {constructor: {value: r5}});
r7 = 'No mutationFn found';
r8 = r4;
r1 = new r8[r5](r7, r6);
r1 = r1 instanceof Object ? r1 : r4;
r1 = r2.bind(r3)(r1);
return r1;
case 71:
r1 = _closure5_slot1;
r3 = r1.options;
r2 = r3.mutationFn;
r1 = _closure5_slot0;
r0 = _closure5_slot2;
r0 = r2.bind(r3)(r1, r0);
return r0;
}
};
Ready to see it on your own binary?
Upload a file, get an email when your decompiled source is ready. No signup required.
Submit a file