depyo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/depyo.js +213 -0
  4. package/lib/BinaryReader.js +153 -0
  5. package/lib/OpCode.js +90 -0
  6. package/lib/OpCodes.js +940 -0
  7. package/lib/PycDecompiler.js +2031 -0
  8. package/lib/PycDisassembler.js +55 -0
  9. package/lib/PycReader.js +905 -0
  10. package/lib/PycResult.js +82 -0
  11. package/lib/PythonObject.js +242 -0
  12. package/lib/Unpickle.js +173 -0
  13. package/lib/ast/ast_node.js +3442 -0
  14. package/lib/bytecode/python_1_0.js +116 -0
  15. package/lib/bytecode/python_1_1.js +116 -0
  16. package/lib/bytecode/python_1_3.js +119 -0
  17. package/lib/bytecode/python_1_4.js +121 -0
  18. package/lib/bytecode/python_1_5.js +120 -0
  19. package/lib/bytecode/python_1_6.js +124 -0
  20. package/lib/bytecode/python_2_0.js +137 -0
  21. package/lib/bytecode/python_2_1.js +142 -0
  22. package/lib/bytecode/python_2_2.js +147 -0
  23. package/lib/bytecode/python_2_3.js +145 -0
  24. package/lib/bytecode/python_2_4.js +147 -0
  25. package/lib/bytecode/python_2_5.js +147 -0
  26. package/lib/bytecode/python_2_6.js +147 -0
  27. package/lib/bytecode/python_2_7.js +151 -0
  28. package/lib/bytecode/python_3_0.js +132 -0
  29. package/lib/bytecode/python_3_1.js +135 -0
  30. package/lib/bytecode/python_3_10.js +312 -0
  31. package/lib/bytecode/python_3_11.js +284 -0
  32. package/lib/bytecode/python_3_12.js +327 -0
  33. package/lib/bytecode/python_3_13.js +173 -0
  34. package/lib/bytecode/python_3_14.js +177 -0
  35. package/lib/bytecode/python_3_2.js +136 -0
  36. package/lib/bytecode/python_3_3.js +136 -0
  37. package/lib/bytecode/python_3_4.js +137 -0
  38. package/lib/bytecode/python_3_5.js +149 -0
  39. package/lib/bytecode/python_3_6.js +153 -0
  40. package/lib/bytecode/python_3_7.js +292 -0
  41. package/lib/bytecode/python_3_8.js +294 -0
  42. package/lib/bytecode/python_3_9.js +296 -0
  43. package/lib/code_reader.js +146 -0
  44. package/lib/handlers/binary_ops.js +174 -0
  45. package/lib/handlers/collections_update.js +239 -0
  46. package/lib/handlers/comparisons.js +95 -0
  47. package/lib/handlers/context_managers.js +250 -0
  48. package/lib/handlers/control_flow_jumps.js +954 -0
  49. package/lib/handlers/exceptions_blocks.js +952 -0
  50. package/lib/handlers/formatting.js +31 -0
  51. package/lib/handlers/function_calls.js +496 -0
  52. package/lib/handlers/function_class_build.js +330 -0
  53. package/lib/handlers/generators_async.js +172 -0
  54. package/lib/handlers/imports.js +53 -0
  55. package/lib/handlers/load_store_names.js +711 -0
  56. package/lib/handlers/loop_iterator.js +318 -0
  57. package/lib/handlers/misc_other.js +1201 -0
  58. package/lib/handlers/pattern_matching.js +226 -0
  59. package/lib/handlers/stack_ops.js +280 -0
  60. package/lib/handlers/subscript_slice.js +394 -0
  61. package/lib/handlers/unary_ops.js +91 -0
  62. package/lib/handlers/unpack.js +141 -0
  63. package/lib/stack_history.js +63 -0
  64. package/lib/zip_reader.js +217 -0
  65. package/package.json +35 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2011-2024 Sergey Kuznetsov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # depyo — Python bytecode decompiler in Node.js
2
+
3
+ Depyo converts Python `.pyc` files (or archives of them) back to readable Python source. It aims for broad coverage (Python 1.0 through 3.14) and fast throughput, with fixtures for modern features (exception groups, pattern matching, walrus, f-strings, async, context managers).
4
+
5
+ ## Why depyo?
6
+ - **Wide version coverage:** Opcode tables and expected outputs for Python 1.0–3.14, plus decompilation support for PyPy bytecode sets.
7
+ - **Modern features:** WITH_EXCEPT_START/PREP_RERAISE_STAR, async/await, walrus, match/case, f-strings, type params, dict/set merges.
8
+ - **Workflow friendly:** CLI options for asm dumps, raw spacing hints, raw `.pyc` preservation, and flattened output paths.
9
+ - **Verification harness:** `run-fixtures.js` and `run-matrix.js` compare decompiled output against expected fixtures across versions.
10
+
11
+ ## Install
12
+ - Global: `npm i -g depyo`
13
+ - One-off: `npx depyo <file.pyc>`
14
+
15
+ Node.js 20+ recommended (matches CI).
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ # Decompile a single .pyc
21
+ node depyo.js /path/to/file.pyc
22
+
23
+ # Decompile a zip of .pyc files, emit asm and keep raw bytes
24
+ node depyo.js --asm --raw my_archive.zip
25
+
26
+ # Write sources next to inputs (skip directory mirroring)
27
+ node depyo.js --skip-path /path/to/file.pyc
28
+
29
+ # Dump to stdout instead of files
30
+ node depyo.js --out /path/to/file.pyc
31
+ ```
32
+
33
+ ### CLI options
34
+ - `--asm` emit `.pyasm` disassembly alongside source
35
+ - `--raw` emit raw `.pyc` next to output
36
+ - `--raw-spacing` preserve blank lines/comment gaps
37
+ - `--dump` dump marshalled object tree
38
+ - `--stats` print throughput stats
39
+ - `--skip-source-gen` skip writing `.py` (use with `--asm/--dump`)
40
+ - `--skip-path` flatten output paths (write next to input)
41
+ - `--out` print source to stdout instead of files
42
+ - `--basedir <dir>` override output root (default: alongside input)
43
+ - `--file-ext <ext>` change emitted extension (default `py`)
44
+
45
+ ## Examples
46
+ - Disassemble only (no source): `node depyo.js --skip-source-gen --asm file.pyc`
47
+ - Keep raw + disassembly next to source: `node depyo.js --raw --asm path/to/file.pyc`
48
+ - Flatten outputs (helpful for bulk zips): `node depyo.js --skip-path archive.zip`
49
+
50
+ ## Testing
51
+ - Smoke per version:
52
+ ```bash
53
+ node scripts/run-fixtures.js --root test/bytecode_3.14 --pattern py314_with_except_star --fail-fast
54
+ node scripts/run-fixtures.js --root test/bytecode_3.6 --pattern py36_fstrings --fail-fast
55
+ ```
56
+ - Matrix (all versions, optional PyPy):
57
+ ```bash
58
+ node scripts/run-matrix.js # full sweep
59
+ node scripts/run-matrix.js --pattern py311_exception_groups --fail-fast
60
+ ```
61
+ - Modern fixtures are generated via `test/generate_modern_tests.py` (Python 3.8+ on PATH).
62
+
63
+ ## Support matrix
64
+ - Python 1.0–3.14 opcode tables with expected fixtures.
65
+ - Modern features: match/case, walrus, f-strings, exception groups, type params.
66
+ - PyPy bytecode sets decompile; expected files are not yet part of CI.
67
+ - Legacy CI smokes (1.x/2.7/3.0–3.6) are informational (`continue-on-error`); modern feature checks are blocking.
68
+
69
+ ## Known limitations
70
+ - **Inline comprehensions (Python 3.12+):** PEP 709 inlines list/dict/set comprehensions into parent code objects. Depyo currently reconstructs these as for-loops rather than comprehension expressions. Functions, classes, match/case, exception handling, and other constructs work correctly.
71
+
72
+ ## Contributing / DX tips
73
+ - Use `node scripts/run-fixtures.js --pattern <piece>` for fast repros.
74
+ - For full coverage, `node scripts/run-matrix.js --fail-fast` (optionally add `--pattern`).
75
+ - Enable `--raw-spacing` to inspect potential comment/blank-line gaps.
76
+ - `--stats` helps when profiling throughput.
77
+
78
+ Comments and docs are in English; output mirrors the target Python version syntax.
79
+
80
+ ## Comparison snapshot (at a glance)
81
+
82
+ | Project | Supported versions | Modern features (match, walrus, f-strings, exc groups) | Delivery | Expected fixtures | Notes |
83
+ | ------------------ | --------------------------- | ------------------------------------------------------ | ------------ | ----------------- | ----------------------------------------- |
84
+ | depyo | 1.0–3.14 (PyPy decompiles) | Yes | npm/npx, CLI | Yes (1.0–3.14) | Node.js CLI, asm/raw-spacing options |
85
+ | uncompyle6/decompyle3 | 2.x–3.12+ (lag on 3.13/3.14) | Partial (depends on branch) | pip | Partial | Python-based, slower adoption of new ops |
86
+ | pycdc (C++) | Mostly 2.x–3.x (limited new) | Partial | source build | No | Fast, but modern coverage limited |
87
+
88
+ ## Quick benchmark (informal)
89
+ - Machine: local Node 25, single-thread.
90
+ - Case: `py314_exception_groups.pyc` decompiled 50× in-process: ~5.3 ms total (≈0.1 ms per decompile).
91
+ Use `node depyo.js --stats <file.pyc>` for your environment.
92
+
93
+ ## Promotion ideas (OSS)
94
+ - Announce on HN/Reddit (Show HN / r/Python) with npm/npx one-liners.
95
+ - Add to awesome lists (`awesome-python`, `awesome-reverse-engineering`).
96
+ - Provide asciinema/GIF of `npx depyo file.pyc` + `--asm`.
97
+ - Encourage contributions via Issues/Discussions and `help wanted` labels.
package/depyo.js ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {PycReader} = require('./lib/PycReader')
4
+ const PycDecompiler = require('./lib/PycDecompiler');
5
+ const PycDisassembler = require('./lib/PycDisassembler');
6
+ const ZipReader = require('./lib/zip_reader');
7
+ const zlib = require('zlib');
8
+ const fs = require('fs');
9
+ const Path = require('path');
10
+ const PycResult = require('./lib/PycResult');
11
+
12
+ let g_baseDir = './decompiled/';
13
+ global.g_cliArgs = {
14
+ debug: false,
15
+ raw: false,
16
+ rawSpacing: false,
17
+ dump: false,
18
+ asm: false,
19
+ stats: false,
20
+ skipSource: false,
21
+ skipPath: false,
22
+ sendToStdout: false,
23
+ fileExt: 'py',
24
+ baseDir: null,
25
+ filenames: []
26
+ };
27
+
28
+ let g_totalInThroughput = 0;
29
+ let g_totalOutThroughput = 0;
30
+ let g_totalExecTime = 0;
31
+ let g_totalFiles = 0;
32
+
33
+ function printUsage() {
34
+ console.log(`Usage: node depyo.js [options] <file.pyc|archive.zip> [...]
35
+
36
+ Options:
37
+ --asm Emit .pyasm disassembly alongside source
38
+ --debug Verbose logging during decompilation
39
+ --raw Save raw .pyc next to output
40
+ --raw-spacing Preserve blank lines (show potential comment gaps)
41
+ --dump Dump marshalled object tree (.dump)
42
+ --stats Print throughput stats
43
+ --skip-source-gen Do not emit .py source (useful with --asm/--dump)
44
+ --skip-path Flatten output paths (write files next to inputs)
45
+ --out Print decompiled source to stdout instead of files
46
+ --basedir <path> Output base directory (default: alongside input)
47
+ --file-ext <ext> Extension for generated source (default: py)
48
+ `);
49
+ }
50
+
51
+ function parseCLIParams() {
52
+ for (let idx = 2; idx < process.argv.length; idx++ ) {
53
+ let cliParam = process.argv[idx];
54
+ if (cliParam.toLowerCase() == "--asm") {
55
+ g_cliArgs.asm = true;
56
+ } else if (cliParam.toLowerCase() == "--debug") {
57
+ g_cliArgs.debug = true;
58
+ } else if (cliParam.toLowerCase() == "--raw") {
59
+ g_cliArgs.raw = true;
60
+ } else if (cliParam.toLowerCase() == "--raw-spacing") {
61
+ g_cliArgs.rawSpacing = true;
62
+ } else if (cliParam.toLowerCase() == "--dump") {
63
+ g_cliArgs.dump = true;
64
+ } else if (cliParam.toLowerCase() == "--stats") {
65
+ g_cliArgs.stats = true;
66
+ } else if (cliParam.toLowerCase() == "--skip-source-gen") {
67
+ g_cliArgs.skipSource = true;
68
+ } else if (cliParam.toLowerCase() == "--skip-path") {
69
+ g_cliArgs.skipPath = true;
70
+ } else if (cliParam.toLowerCase() == "--out") {
71
+ g_cliArgs.sendToStdout = true;
72
+ } else if (cliParam.toLowerCase() == "--basedir") {
73
+ g_cliArgs.baseDir = process.argv[++idx];
74
+ } else if (cliParam.toLowerCase() == "--file-ext") {
75
+ g_cliArgs.fileExt = process.argv[++idx];
76
+ } else if (cliParam.toLowerCase() == "--help" || cliParam.toLowerCase() == "-h") {
77
+ printUsage();
78
+ process.exit(0);
79
+ } else {
80
+ if (cliParam && cliParam.trim().length > 0) {
81
+ g_cliArgs.filenames.push(cliParam);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ function decompilePycObject(data) {
88
+ try
89
+ {
90
+ let filename = null, obj = null;
91
+ let startTS = process.hrtime.bigint();
92
+ let rdr = new PycReader(data);
93
+ try {
94
+ obj = rdr.ReadObject();
95
+ filename = g_baseDir + obj.FileName;
96
+ } catch (ex) {
97
+ if (ex instanceof PycReader.LoadError) {
98
+ // Save the binary file if it not already exists for future manual analysis.
99
+ if (!ex.FileName) {
100
+ if (global.g_cliArgs?.debug) {
101
+ console.log(`LoadError: ${ex.message} at position ${ex.position}`);
102
+ }
103
+ return;
104
+ }
105
+ filename = g_baseDir + ex.FileName;
106
+ let dirPath = Path.dirname(filename);
107
+ if (!g_cliArgs.skipPath && !fs.existsSync(dirPath)) {
108
+ fs.mkdirSync(dirPath, {recursive: true});
109
+ }
110
+ let filenamePyc = filename.substring(0, filename.lastIndexOf('.')) + ".pyc";
111
+ fs.writeFileSync(filenamePyc, rdr.Reader);
112
+ console.log(`Error: ${ex.message}\nFile: ${filenamePyc}\nPosition: ${ex.position}`);
113
+ return;
114
+ }
115
+ console.log(`Error: ${ex.message}\nStack:\n${ex.stacktrace}`);
116
+ return;
117
+ }
118
+
119
+ if (!g_cliArgs.sendToStdout) {
120
+ console.log(`Processing ${filename}...`);
121
+ }
122
+ let dirPath = Path.dirname(filename);
123
+ let filenameBase = filename.substring(0, filename.lastIndexOf('.'));
124
+ if (g_cliArgs.skipPath) {
125
+ filenameBase = "./" + filenameBase.substring(dirPath.length + 1);
126
+ }
127
+
128
+ if (!g_cliArgs.sendToStdout) {
129
+ if (!g_cliArgs.skipPath && !fs.existsSync(dirPath)) {
130
+ fs.mkdirSync(dirPath, {recursive: true});
131
+ }
132
+ if (g_cliArgs.raw) {
133
+ fs.writeFileSync(filenameBase + ".pyc", rdr.Reader);
134
+ }
135
+ if (g_cliArgs.dump) {
136
+ fs.writeFileSync(filenameBase + ".dump", PycReader.DumpObject(obj));
137
+ }
138
+ if (g_cliArgs.asm) {
139
+ fs.writeFileSync(filenameBase + ".pyasm", PycDisassembler.Disassemble(rdr, obj));
140
+ }
141
+ }
142
+ let genStartTS = process.hrtime.bigint();
143
+ let decompiler = new PycDecompiler(obj);
144
+ let ast = decompiler.decompile();
145
+ let pycResult = ast.codeFragment();
146
+ let pySrc = pycResult.toString();
147
+ if (!pySrc.endsWith("\n")) {
148
+ pySrc += "\n";
149
+ }
150
+ let genSecs = parseInt(process.hrtime.bigint() - genStartTS) / 1000000000;
151
+ if (g_cliArgs.sendToStdout) {
152
+ // console.log(`\n\n${filenameBase}.${g_cliArgs.fileExt}\n-------\n${pySrc}`);
153
+ console.log(pySrc);
154
+ } else {
155
+ fs.writeFileSync(filenameBase + "." + g_cliArgs.fileExt, pySrc);
156
+ }
157
+ let secs = parseInt(process.hrtime.bigint() - startTS) / 1000000000;
158
+ g_totalExecTime += secs;
159
+ let inThroughput = data.length / genSecs;
160
+ let outThroughput = pySrc.length / genSecs;
161
+ g_totalInThroughput += data.length;
162
+ g_totalOutThroughput += pySrc.length;
163
+ g_totalFiles++;
164
+ if (g_cliArgs.stats) {
165
+ console.log(`Done in ${genSecs.toFixed(3)}s. In: ${data.length} bytes (${inThroughput.toFixed(2)} B/s). Out: ${pySrc.length} bytes (${outThroughput.toFixed(2)} B/s).`);
166
+ }
167
+ }
168
+ catch (ex)
169
+ {
170
+ console.log(`EXCEPTION: ${ex}`);
171
+ if (g_cliArgs.debug) {
172
+ console.log(ex.stack);
173
+ }
174
+ }
175
+ }
176
+
177
+
178
+ function DecompileModule(filenames)
179
+ {
180
+ for (let filename of filenames) {
181
+ try {
182
+ let zipReader = new ZipReader(filename);
183
+ if (zipReader.isZipFile()) {
184
+ for (let entry of zipReader.entries()) {
185
+ let uncompressedData = zlib.inflateSync(entry.data);
186
+
187
+ decompilePycObject(uncompressedData);
188
+ }
189
+ } else {
190
+ decompilePycObject(filename);
191
+ }
192
+ } catch (ex) {
193
+ console.log(`Processing of file ${filename} was unsuccessful due to: ${ex.message}`);
194
+ }
195
+ }
196
+ }
197
+
198
+ parseCLIParams()
199
+ if (g_cliArgs.filenames.length === 0) {
200
+ printUsage();
201
+ process.exit(1);
202
+ }
203
+
204
+ const baseInputDir = g_cliArgs.baseDir ? g_cliArgs.baseDir : Path.dirname(g_cliArgs.filenames[0] || '.');
205
+ g_baseDir = Path.resolve(baseInputDir, 'decompiled') + '/';
206
+
207
+ DecompileModule(g_cliArgs.filenames);
208
+
209
+ if (!g_cliArgs.sendToStdout) {
210
+ const inRate = (g_totalInThroughput / g_totalExecTime).toFixed(2);
211
+ const outRate = (g_totalOutThroughput / g_totalExecTime).toFixed(2);
212
+ console.log(`Processed ${g_totalFiles} files in ${g_totalExecTime.toFixed(3)}s. In: ${g_totalInThroughput} bytes (${inRate} B/s). Out: ${g_totalOutThroughput} bytes (${outRate} B/s).`);
213
+ }
@@ -0,0 +1,153 @@
1
+ class BinaryReader {
2
+ constructor(code) {
3
+ if (!code) {
4
+ return;
5
+ }
6
+
7
+ this._reader = Buffer.from(code);
8
+ this._pc = 0;
9
+ }
10
+
11
+ get pc() {
12
+ return this._pc;
13
+ }
14
+
15
+ set pc(pos) {
16
+ this._pc = pos;
17
+ }
18
+
19
+ get EOF() {
20
+ return this._pc >= this._reader.length;
21
+ }
22
+
23
+ get Reader() {
24
+ return this._reader;
25
+ }
26
+
27
+ readChar() {
28
+ return String.fromCharCode([this.readByte()]);
29
+ }
30
+
31
+ readByte() {
32
+ let value = this._reader.readUInt8(this.pc, true);
33
+ this.pc++;
34
+ return value;
35
+ }
36
+
37
+ readUInt16() {
38
+ let value = this._reader.readUInt16LE(this.pc, true);
39
+ this.pc += 2;
40
+ return value;
41
+ }
42
+
43
+ readUInt16BE() {
44
+ let value = this._reader.readUInt16BE(this.pc, true);
45
+ this.pc += 2;
46
+ return value;
47
+ }
48
+
49
+ readInt16() {
50
+ let value = this._reader.readInt16LE(this.pc, true);
51
+ this.pc += 2;
52
+ return value;
53
+ }
54
+
55
+ readInt16BE() {
56
+ let value = this._reader.readInt16BE(this.pc, true);
57
+ this.pc += 2;
58
+ return value;
59
+ }
60
+
61
+ readInt32() {
62
+ let value = this._reader.readInt32LE(this.pc, true);
63
+ this.pc += 4;
64
+ return value;
65
+ }
66
+
67
+ readInt32BE() {
68
+ let value = this._reader.readInt32BE(this.pc, true);
69
+ this.pc += 4;
70
+ return value;
71
+ }
72
+
73
+ readUInt32() {
74
+ let value = this._reader.readUInt32LE(this.pc, true);
75
+ this.pc += 4;
76
+ return value;
77
+ }
78
+
79
+ readUInt32BE() {
80
+ let value = this._reader.readUInt32BE(this.pc, true);
81
+ this.pc += 4;
82
+ return value;
83
+ }
84
+
85
+ readLong() {
86
+ let high = this.readInt32()
87
+ let low = this.readInt32()
88
+ return {low, high};
89
+ }
90
+
91
+ readLongBE() {
92
+ let high = this.readInt32BE()
93
+ let low = this.readInt32BE()
94
+ return {low, high};
95
+ }
96
+
97
+ readULong() {
98
+ let high = this.readUInt()
99
+ let low = this.readUInt()
100
+ return {low, high};
101
+ }
102
+
103
+ readULongBE() {
104
+ let high = this.readUIntBE()
105
+ let low = this.readUIntBE()
106
+ return {low, high};
107
+ }
108
+
109
+ readFloat() {
110
+ let value = this._reader.readFloatLE(this.pc, true);
111
+ this.pc += 4;
112
+ return value;
113
+ }
114
+
115
+ readFloatBE() {
116
+ let value = this._reader.readFloatBE(this.pc, true);
117
+ this.pc += 4;
118
+ return value;
119
+ }
120
+
121
+ readDouble() {
122
+ let value = this._reader.readDoubleLE(this.pc, true);
123
+ this.pc += 8;
124
+ return value;
125
+ }
126
+
127
+ readDoubleBE() {
128
+ let value = this._reader.readDoubleBE(this.pc, true);
129
+ this.pc += 8;
130
+ return value;
131
+ }
132
+
133
+ readBytes(length) {
134
+ if (this.pc + length > this._reader.length) {
135
+ throw Error("Requested more data than buffer holds");
136
+ }
137
+ let value = this._reader.slice(this.pc, this.pc + length);
138
+ this.pc += length;
139
+ return value;
140
+ }
141
+
142
+ readString(length) {
143
+ let value = this.readBytes(length).toString('utf8');
144
+ return value;
145
+ }
146
+ }
147
+
148
+ module.exports = {BinaryReader};
149
+
150
+ // Registering classes in global scope for propoer class deserialization.
151
+ for (let className of Object.keys(module.exports)) {
152
+ global[className] = new module.exports[className]();
153
+ }
package/lib/OpCode.js ADDED
@@ -0,0 +1,90 @@
1
+ class OpCode {
2
+ OpCodeID = 0;
3
+ InstructionName = null;
4
+ HasArgument = false;
5
+ Argument = 0;
6
+ HasName = false;
7
+ Name = null;
8
+ HasJumpRelative = false;
9
+ HasJumpAbsolute = false;
10
+ HasNegativeOffset = false;
11
+ HasConstant = false;
12
+ Constant = null;
13
+ ConstantObject = null;
14
+ HasCompare = false;
15
+ CompareOperator = null;
16
+ HasLocal = false;
17
+ LocalName = null;
18
+ HasBinaryOp = false;
19
+ BinaryOp = null;
20
+ HasIntrisic1 = false;
21
+ Intrisic1 = null;
22
+ HasIntrisic2 = false;
23
+ Intrisic2 = null;
24
+ HasFree = false;
25
+ FreeName = null;
26
+ Offset = 0;
27
+ LineNo = 0;
28
+ CodeBlock = null;
29
+ InstructionIndex = -1;
30
+ Size = 2; // Default size for Python 3.6+ word-aligned instructions
31
+
32
+ constructor(opcode, name, opts) {
33
+ this.OpCodeID = opcode;
34
+ this.InstructionName = name;
35
+
36
+ if (opts && typeof(opts) == "object") {
37
+ for (let [key, value] of Object.entries(opts)) {
38
+ if (key in this) {
39
+ this[key] = value;
40
+ }
41
+ }
42
+ }
43
+
44
+ }
45
+
46
+ get Prev() {
47
+ if (!this.CodeBlock || this.InstructionIndex < 1) {
48
+ return null;
49
+ }
50
+ return this.CodeBlock.Instructions[this.InstructionIndex - 1];
51
+ }
52
+
53
+ get Next() {
54
+ if (!this.CodeBlock || this.InstructionIndex < 0 || this.InstructionIndex >= this.CodeBlock.Instructions.length - 1) {
55
+ return null;
56
+ }
57
+ return this.CodeBlock.Instructions[this.InstructionIndex + 1];
58
+ }
59
+
60
+ get JumpTarget () {
61
+ if (this.HasJumpRelative) {
62
+ // Python < 3.6: 3-byte instructions (opcode + 2 arg bytes)
63
+ // Python 3.6+: 2-byte instructions (opcode + arg byte)
64
+ return this.Offset + this.Size + this.Argument;
65
+ } else if (this.HasJumpAbsolute) {
66
+ return this.Argument;
67
+ } else {
68
+ return 0;
69
+ }
70
+ }
71
+
72
+ get Label() {
73
+ return this.Name || this.LocalName || this.FreeName;
74
+ }
75
+
76
+ Clone() {
77
+ let clone = new OpCode();
78
+ for (let [key, value] of Object.entries(this)) {
79
+ clone[key] = value;
80
+ }
81
+ return clone;
82
+ }
83
+ }
84
+
85
+ module.exports = OpCode;
86
+
87
+ // Registering classes in global scope for propoer class deserialization.
88
+ for (let className of Object.keys(module.exports)) {
89
+ global[className] = new module.exports[className]();
90
+ }