-
Notifications
You must be signed in to change notification settings - Fork 50
New version #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
New version #18
Changes from all commits
f309ac2
ca2c158
285ac21
101e179
b11b4bd
cdc2129
0934fc7
6f60392
707941a
9b4b03a
7b3351f
32be7c2
bf10b37
ab581e8
0ec27a4
a43de3f
118fa4d
bb10c1d
a518690
c8599d4
42a26d9
671df73
9fe07fa
a891806
f012470
b98c175
87b0823
fec4adb
8fd266b
84818a7
6ed8314
5153161
8605843
789936d
d94e98c
ed8e705
83c905b
576d373
0ff0788
c10a368
6e644dc
1b48dd6
10b7523
0c5082c
db9de7f
c6f5bfa
da083f6
c3ef3e2
9ac9b28
83dcc7a
c4f7182
85dc767
72d3d85
10a40ae
aef5ca1
4494c7b
cedb704
ade829b
3a58beb
8ede281
5ed6722
f7b0143
b34c066
096b5c4
1695ff7
c9c1d84
344646b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| */__pycache__/* | ||
| __pycache__/* | ||
| data/ | ||
| *.bak | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,83 @@ | ||
| from Translate.translate import translate_bytecode | ||
| from Translate.jump_blocks import CodeLine | ||
| from Simplify.simplify import simplify_translated_bytecode | ||
|
|
||
| import re | ||
| import pickle | ||
| from typing import Dict, List, Optional, Union | ||
hasherezade marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| class CodeLine: | ||
| def __init__(self, opcode="", line="", inst="", translated="", decompiled=""): | ||
| self.v8_opcode = opcode | ||
| self.line_num = line | ||
| self.v8_instruction = inst | ||
| self.translated = translated | ||
| self.decompiled = decompiled | ||
| self.visible = True | ||
| ### | ||
|
|
||
| class GlobalVars: | ||
| _STRING_RE = re.compile(r'"([^"\\]*(?:\\.[^"\\]*)*)"') | ||
| _FUNC_RE = re.compile(r'\b(func_([A-Za-z0-9_$]+)_0x[0-9a-fA-F]+)\b') | ||
|
|
||
| def __init__(self): | ||
| self.strings_set = None | ||
| self.funcs_map = None | ||
|
|
||
| def parse(self, value) -> bool: | ||
| is_parsed = False | ||
|
|
||
| strings = set(self._STRING_RE.findall(value)) | ||
| funcs = list(self._FUNC_RE.finditer(value)) | ||
|
|
||
| if strings: | ||
| is_parsed = True | ||
| self.strings_set = (self.strings_set or set()) | ||
| self.strings_set.update(strings) | ||
|
|
||
| if funcs: | ||
| is_parsed = True | ||
| self.funcs_map = (self.funcs_map or {}) | ||
|
|
||
| for match in funcs: | ||
| full_name = match.group(1) | ||
| short_name = match.group(2) | ||
| self.funcs_map[short_name] = full_name | ||
|
|
||
| return is_parsed | ||
|
|
||
| def is_filled(self) -> bool: | ||
| if self.strings_set or self.funcs_map: | ||
| return True | ||
| return False | ||
|
|
||
| def has_value(self, value) -> bool: | ||
| if self.strings_set is not None: | ||
| val = value.strip('"') | ||
| if (value in self.strings_set or val in self.strings_set): | ||
| return True | ||
| if self.funcs_map is not None: | ||
| if value in self.funcs_map.keys(): | ||
|
||
| return True | ||
| return False | ||
|
|
||
| def resolve_global_name(self, value) -> Optional[str]: | ||
|
|
||
| def _is_string(value): | ||
| if value.startswith('"') and value.endswith('"'): | ||
| return True | ||
| return False | ||
|
|
||
| if not self.is_filled(): | ||
| return None | ||
|
|
||
| if not _is_string(value): | ||
| return None | ||
|
|
||
| val = value.strip('"') | ||
| if self.strings_set is not None: | ||
| if (value in self.strings_set or val in self.strings_set): | ||
| return "global_" + val | ||
|
|
||
| if self.funcs_map is not None: | ||
| if val in self.funcs_map: | ||
| return self.funcs_map[val] | ||
|
|
||
| return None | ||
|
|
||
| ### | ||
|
|
||
| class SharedFunctionInfo: | ||
| def __init__(self): | ||
|
|
@@ -22,6 +89,8 @@ def __init__(self): | |
| self.code = None | ||
| self.const_pool = None | ||
| self.exception_table = None | ||
| self.visible = True | ||
| self.metadata = None | ||
|
|
||
| def is_fully_parsed(self): | ||
| return all( | ||
|
|
@@ -40,18 +109,62 @@ def translate_bytecode(self): | |
| def simplify_bytecode(self): | ||
| simplify_translated_bytecode(self, self.code) | ||
|
|
||
| def replace_const_pool(self): | ||
| replacements = {f"ConstPool[{idx}]": var for idx, var in enumerate(self.const_pool)} | ||
| def fill_global_variables(self, global_vars: GlobalVars): | ||
| """ | ||
| If the Global Vars were defined anywhere in this function, fill them in and store in the global structure. | ||
| """ | ||
|
|
||
| patternDef = re.compile(r'ConstPoolLiteral\[(\d+)\]') | ||
|
|
||
| for obj in self.code: | ||
| line = obj.decompiled | ||
| if "DeclareGlobals(" not in line: | ||
| continue | ||
| match = re.search(patternDef, line.strip()) | ||
| if not match: | ||
| continue | ||
| index = int(match.group(1)) | ||
hasherezade marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Ensure const_pool exists and index is within valid bounds; otherwise skip | ||
| if self.const_pool is None or not (0 <= index < len(self.const_pool)): | ||
| continue | ||
| if global_vars.parse(self.const_pool[index]): | ||
| return True | ||
| return False | ||
|
|
||
| def replace_const_pool(self, global_vars: GlobalVars): | ||
|
|
||
| def _replacement(match): | ||
| index = int(match.group(2)) | ||
| # Ensure const_pool exists and index is within valid bounds; otherwise leave unchanged | ||
| if self.const_pool is None or not (0 <= index < len(self.const_pool)): | ||
| return match.group(0) # Leave unchanged | ||
|
|
||
| value = self.const_pool[index] | ||
| if match.group(1) == "ConstPool": # Not ConstPoolLiteral | ||
|
|
||
| global_symbol = global_vars.resolve_global_name(value) | ||
| if global_symbol: | ||
| return global_symbol | ||
|
|
||
| return value.strip('"') | ||
| return value | ||
|
|
||
| # Regular expression to match patterns A[NUMBER] or B[NUMBER] | ||
| pattern = r'(ConstPoolLiteral|ConstPool)\[(\d+)\]' | ||
|
|
||
| #replacements = {f"ConstPool[{idx}]": var.strip('"') for idx, var in enumerate(self.const_pool)} | ||
| #replacements.update({f"ConstPoolLiteral[{idx}]": var for idx, var in enumerate(self.const_pool)}) | ||
|
|
||
| for line in self.code: | ||
| if not line.visible: | ||
| if "ConstPool" not in line.decompiled: | ||
| continue | ||
| for const_id, var in replacements.items(): | ||
| line.decompiled = line.decompiled.replace(const_id, var) | ||
| line.decompiled = re.sub(pattern, _replacement, line.decompiled) | ||
|
|
||
| def decompile(self): | ||
| def decompile(self, global_vars: GlobalVars): | ||
| self.translate_bytecode() | ||
| self.simplify_bytecode() | ||
| self.replace_const_pool() | ||
| self.fill_global_variables(global_vars) | ||
| self.replace_const_pool(global_vars) | ||
|
|
||
| def export(self, export_v8code=False, export_translated=False, export_decompiled=True): | ||
| export_func = self.create_function_header() + '\n' | ||
|
|
@@ -70,3 +183,39 @@ def export(self, export_v8code=False, export_translated=False, export_decompiled | |
| if export_line: | ||
| export_func += export_line + '\n' | ||
| return export_func | ||
|
|
||
| #### | ||
|
|
||
| FunctionsBlob = Union[Dict[str, "SharedFunctionInfo"], List["SharedFunctionInfo"]] | ||
|
|
||
| # Helper function for serializing multiple functions | ||
| def serialize_functions(functions: FunctionsBlob) -> bytes: | ||
| """Serialize decompiled output using pickle. | ||
|
|
||
| SECURITY NOTE: | ||
| Pickle is unsafe for untrusted input. Only load serialized files that you | ||
| generated yourself. | ||
| """ | ||
| return pickle.dumps(functions, protocol=pickle.HIGHEST_PROTOCOL) | ||
|
|
||
|
|
||
| def deserialize_functions(data: bytes) -> FunctionsBlob: | ||
| """Deserialize decompiled output using pickle. | ||
|
|
||
| SECURITY NOTE: | ||
| Unpickling can execute arbitrary code. Do not load files from untrusted | ||
| sources. | ||
| """ | ||
| return pickle.loads(data) | ||
|
|
||
|
|
||
| def save_functions_to_file(functions: FunctionsBlob, filename: str): | ||
| """Save decompiled output to a file (pickle).""" | ||
| with open(filename, 'wb') as f: | ||
| f.write(serialize_functions(functions)) | ||
|
|
||
|
|
||
| def load_functions_from_file(filename: str) -> FunctionsBlob: | ||
| """Load decompiled output from a file (pickle).""" | ||
| with open(filename, 'rb') as f: | ||
| return deserialize_functions(f.read()) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -18,31 +18,42 @@ | |||||
| <h2>Usage</h2> | ||||||
| <h3>Command-Line Arguments</h3> | ||||||
| <ul> | ||||||
| <li><code>input_file</code>: The input file name.</li> | ||||||
| <li><code>output_file</code>: The output file name.</li> | ||||||
| <li><code>--path</code>, <code>-p</code>: Path to disassembler binary (optional).</li> | ||||||
| <li><code>--disassembled</code>, <code>-d</code>: Indicate if the input file is already disassembled (optional).</li> | ||||||
| <li><code>--export_format</code>, <code>-e</code>: Specify the export format(s). Options are <code>v8_opcode</code>, <code>translated</code>, and <code>decompiled</code>. Multiple options can be combined (optional, default: <code>decompiled</code>).</li> | ||||||
| <li><code>--inp</code>, <code>-i</code>: The input file name</li> | ||||||
| <li><code>--out</code>, <code>-o</code>: Path to the output (depending on the type of the output, a single file or a directory tree may be generated)</li> | ||||||
| <li><code>--input_format</code>, <code>-f</code>: Indicate format of the input. Options are: <code>raw</code>: the output is a raw JSC file; <code>disassembled</code>: the input file is already disassembled; <code>serialized</code>: the input is already decompiled, and stored in a serialized format (pickle; trusted input only)</li> | ||||||
|
||||||
| <li><code>--input_format</code>, <code>-f</code>: Indicate format of the input. Options are: <code>raw</code>: the output is a raw JSC file; <code>disassembled</code>: the input file is already disassembled; <code>serialized</code>: the input is already decompiled, and stored in a serialized format (pickle; trusted input only)</li> | |
| <li><code>--input_format</code>, <code>-f</code>: Indicate format of the input. Options are: <code>raw</code>: the input is a raw JSC file; <code>disassembled</code>: the input file is already disassembled; <code>serialized</code>: the input is already decompiled, and stored in a serialized format (pickle; trusted input only)</li> |
Copilot
AI
Feb 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs say --path is required for raw input, but the current code path auto-detects the V8 version and selects a bundled disassembler from Bin/ when --path is omitted. Consider updating this line to reflect that --path is only needed to override the auto-selected binary or when the bundled binaries aren’t available.
| <li><code>--path</code>, <code>-p</code>: Path to disassembler binary. Required if the input is in the raw format.</li> | |
| <li><code>--path</code>, <code>-p</code>: Path to disassembler binary. By default, View8 auto-detects the V8 version and uses a bundled disassembler from <code>Bin/</code>; use this option to override the auto-selected binary or when no suitable bundled binary is available for raw input.</li> |
Copilot
AI
Feb 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error: 'specifing' should be 'specifying'.
| <p>By default, <code>view8</code> detects the V8 bytecode version of the input file (using <code>VersionDetector.exe</code>) and automatically searches for a compatible disassembler binary in the <code>Bin</code> folder. This can be changed by specifing a different disassembler binary, use the <code>--path</code> (or <code>-p</code>) option:</p> | |
| <p>By default, <code>view8</code> detects the V8 bytecode version of the input file (using <code>VersionDetector.exe</code>) and automatically searches for a compatible disassembler binary in the <code>Bin</code> folder. This can be changed by specifying a different disassembler binary, use the <code>--path</code> (or <code>-p</code>) option:</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code should be removed. If the old string formatting method is no longer needed, this commented line should be deleted rather than left in place.