unidecompiler-plugin-python-pyc 0.1.1__tar.gz → 0.1.3__tar.gz

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 (17) hide show
  1. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/PKG-INFO +2 -2
  2. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/pyproject.toml +2 -2
  3. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/lifter.py +83 -126
  4. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/pyc.py +65 -4
  5. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/PKG-INFO +2 -2
  6. unidecompiler_plugin_python_pyc-0.1.3/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +1 -0
  7. unidecompiler_plugin_python_pyc-0.1.1/src/unidecompiler_plugin_python_pyc.egg-info/requires.txt +0 -1
  8. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/README.md +0 -0
  9. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/setup.cfg +0 -0
  10. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/__init__.py +0 -0
  11. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/plugin.py +0 -0
  12. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/simulation.py +0 -0
  13. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc/support.py +0 -0
  14. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/SOURCES.txt +0 -0
  15. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/dependency_links.txt +0 -0
  16. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/entry_points.txt +0 -0
  17. {unidecompiler_plugin_python_pyc-0.1.1 → unidecompiler_plugin_python_pyc-0.1.3}/src/unidecompiler_plugin_python_pyc.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unidecompiler-plugin-python-pyc
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Python bytecode frontend plugin for unidecompiler
5
5
  Author-email: Wker <1670133844@qq.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/Wker666/unidecompiler
9
9
  Project-URL: Issues, https://github.com/Wker666/unidecompiler/issues
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: unidecompiler<0.2.0,>=0.1.1
12
+ Requires-Dist: unidecompiler<0.2.0,>=0.1.6
13
13
 
14
14
  # unidecompiler-plugin-python-pyc
15
15
 
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "unidecompiler-plugin-python-pyc"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "Python bytecode frontend plugin for unidecompiler"
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
11
11
  authors = [{ name = "Wker", email = "1670133844@qq.com" }]
12
12
  requires-python = ">=3.11"
13
- dependencies = ["unidecompiler>=0.1.1,<0.2.0"]
13
+ dependencies = ["unidecompiler>=0.1.6,<0.2.0"]
14
14
 
15
15
  [project.urls]
16
16
  Homepage = "https://github.com/Wker666/unidecompiler"
@@ -32,11 +32,13 @@ from unidecompiler.core.effects import (
32
32
  Iterate,
33
33
  MakeFunctionValue,
34
34
  MergeMap,
35
+ OfferImplicitCallArguments,
35
36
  Pop,
36
37
  Push,
37
38
  RaiseTop,
38
39
  RaiseWithCause,
39
40
  ReraiseTop,
41
+ ResumeValue,
40
42
  ReturnTop,
41
43
  ReturnVoid,
42
44
  StoreAttr,
@@ -55,7 +57,6 @@ from unidecompiler.core.effects import (
55
57
  )
56
58
  from unidecompiler.core.vm_module import assemble_vm_module
57
59
  from unidecompiler.core.ir import (
58
- Assign,
59
60
  BinaryOp,
60
61
  CapturedVar,
61
62
  Const,
@@ -79,18 +80,17 @@ from unidecompiler.core.vm_function import (
79
80
  from unidecompiler.core.vm_bytecode import VMBytecodeStep
80
81
  from unidecompiler.core.vm_hints import VMHint
81
82
  from unidecompiler.core.vm_operands import VMDecodedInstruction, VMOperand
83
+ from unidecompiler.provenance import ByteRange
82
84
  from unidecompiler.core.vm_region import (
83
85
  VMLinearState,
84
86
  VMRegionCallbacks,
87
+ VMRegionSlice,
85
88
  VMRegionOpcodeClasses,
86
89
  VMRegionProfile,
87
90
  VMStatefulCallbacks,
88
91
  build_hint_region_profile,
89
92
  lift_control_region as lift_vm_control_region,
90
93
  )
91
- from unidecompiler.core.vm_structures import (
92
- vm_unsupported,
93
- )
94
94
  from unidecompiler_plugin_python_pyc.pyc import PycCodeObject, PycExceptionRegion, PycModule
95
95
 
96
96
 
@@ -121,7 +121,6 @@ BINARY_SYMBOLS = {
121
121
  COMPLEX_OPS = {
122
122
  }
123
123
  IGNORED_OPS = {
124
- "RESUME",
125
124
  "CACHE",
126
125
  "NOP",
127
126
  "EXTENDED_ARG",
@@ -129,7 +128,7 @@ IGNORED_OPS = {
129
128
  "COPY_FREE_VARS",
130
129
  }
131
130
  PYTHON_REGION_OPCODE_CLASSES = VMRegionOpcodeClasses(
132
- noise=frozenset({"END_FOR", "POP_TOP", "NOP", "RESUME"}),
131
+ noise=frozenset({"END_FOR", "POP_TOP", "NOP"}),
133
132
  control=frozenset(
134
133
  {
135
134
  "POP_JUMP_IF_FALSE",
@@ -282,7 +281,10 @@ def _build_set(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
282
281
 
283
282
 
284
283
  def _get_iter(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
285
- return (Iterate(source=source),)
284
+ return (
285
+ Iterate(source=source),
286
+ OfferImplicitCallArguments(source=source, max_explicit_arg_count=0),
287
+ )
286
288
 
287
289
 
288
290
  def _unpack_sequence(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
@@ -359,7 +361,10 @@ def _binary_slice(_context, _instruction, source: SourceRef) -> tuple[Effect, ..
359
361
 
360
362
 
361
363
  def _store_slice(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
362
- return (StoreItemEffect(source=source),)
364
+ return (
365
+ BuildCall(source=source, arg_count=2, callee=Global(name="slice", source=source)),
366
+ StoreItemEffect(source=source, order="value-obj-key"),
367
+ )
363
368
 
364
369
 
365
370
  def _list_extend(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
@@ -442,8 +447,8 @@ def _map_add(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
442
447
  return (StoreItemAtDepth(source=source, depth=_instruction_count(instruction)),)
443
448
 
444
449
 
445
- def _set_add(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
446
- return (SetAdd(source=source),)
450
+ def _set_add(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
451
+ return (SetAdd(source=source, depth=_instruction_count(instruction)),)
447
452
 
448
453
 
449
454
  def _store_local(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
@@ -483,6 +488,13 @@ def _yield_value(_context, instruction, source: SourceRef) -> tuple[Effect, ...]
483
488
  return (YieldTop(source=source, default=Const(value=None, source=source)),)
484
489
 
485
490
 
491
+ def _resume(_context, instruction, source: SourceRef) -> tuple[Effect, ...]:
492
+ # CPython's resume mode 5 continues a ``yield`` expression, whose value
493
+ # is supplied by the caller. Await/send modes manage their result in the
494
+ # surrounding protocol and must not manufacture an extra stack value.
495
+ return (ResumeValue(source=source),) if _instruction_count(instruction) == 5 else ()
496
+
497
+
486
498
  def _send(_context, _instruction, source: SourceRef) -> tuple[Effect, ...]:
487
499
  return (Pop(source=source, count=1, allow_missing=True),)
488
500
 
@@ -604,6 +616,7 @@ PYTHON_EFFECT_TABLE = VMEffectTable(
604
616
  "BUILD_TUPLE": _build_tuple,
605
617
  "BUILD_SET": _build_set,
606
618
  "SET_ADD": _set_add,
619
+ "RESUME": _resume,
607
620
  "GET_ITER": _get_iter,
608
621
  "GET_YIELD_FROM_ITER": _get_iter,
609
622
  "UNPACK_SEQUENCE": _unpack_sequence,
@@ -683,8 +696,8 @@ def lift_code_object(code: PycCodeObject) -> FunctionIR:
683
696
  _python_function_spec(code),
684
697
  steps,
685
698
  profile=_python_region_profile(steps, instructions),
686
- callbacks=_python_region_callbacks(code, instructions),
687
- stateful_callbacks=_python_stateful_callbacks(code, instructions),
699
+ callbacks_factory=lambda normalized_steps: _python_region_callbacks(code, normalized_steps),
700
+ stateful_callbacks_factory=lambda normalized_steps: _python_stateful_callbacks(code, normalized_steps),
688
701
  initial_locals=_python_initial_locals(code),
689
702
  raw_window=lambda index: _raw_instruction_window(instructions, index),
690
703
  )
@@ -811,6 +824,7 @@ def _python_decoded_instruction(instruction, source: SourceRef) -> VMDecodedInst
811
824
  source=source,
812
825
  operands=tuple(operands),
813
826
  raw=_raw_instruction_line(instruction),
827
+ artifact_range=(None if instruction.artifact_offset is None or instruction.size is None else ByteRange(instruction.artifact_offset, instruction.size)),
814
828
  )
815
829
 
816
830
 
@@ -882,21 +896,40 @@ def _python_initial_locals(code: PycCodeObject) -> dict[str, Expr]:
882
896
 
883
897
  def _python_region_callbacks(
884
898
  code: PycCodeObject,
885
- instructions: tuple[object, ...],
899
+ prepared_steps: tuple[VMBytecodeStep, ...],
886
900
  ) -> VMRegionCallbacks[VMBytecodeStep]:
901
+ def lift_slice(slice_start, slice_end, stack):
902
+ slice_steps = prepared_steps[slice_start:slice_end]
903
+ result = lift_steps(
904
+ slice_steps,
905
+ initial_locals=_python_initial_locals(code),
906
+ initial_stack=stack,
907
+ )
908
+ if result.state.diagnostics:
909
+ return VMRegionSlice(stopped_at=slice_start)
910
+ output: list[object] = list(result.state.statements)
911
+ if result.stopped_at is not None and result.state.terminator is None:
912
+ return VMRegionSlice(
913
+ statements=tuple(output),
914
+ stopped_at=slice_start + slice_steps.index(result.stopped_at),
915
+ )
916
+ if result.state.terminator is not None:
917
+ output.append(result.state.terminator)
918
+ return VMRegionSlice(statements=tuple(output))
919
+
920
+ def lift_expr(slice_start, slice_end, stack):
921
+ result = lift_steps(
922
+ prepared_steps[slice_start:slice_end],
923
+ initial_locals=_python_initial_locals(code),
924
+ initial_stack=stack,
925
+ )
926
+ if result.state.diagnostics or not result.state.stack:
927
+ return None
928
+ return result.state.stack[-1]
929
+
887
930
  return VMRegionCallbacks(
888
- lift_slice=lambda slice_start, slice_end, stack: _lift_instruction_slice(
889
- code,
890
- instructions[slice_start:slice_end],
891
- [],
892
- stack,
893
- ),
894
- lift_expr=lambda slice_start, slice_end, stack: _lift_stack_expr(
895
- code,
896
- instructions[slice_start:slice_end],
897
- [],
898
- stack,
899
- ),
931
+ lift_slice=lift_slice,
932
+ lift_expr=lift_expr,
900
933
  lift_iter_loop=lambda _get_iter_index, _iterable: None,
901
934
  lift_async_iter_loop=lambda _prefix_start, _get_aiter_index, _region_end: None,
902
935
  lift_comprehension=lambda _prefix_start, _get_iter_index, _region_end, _iterable: None,
@@ -905,45 +938,38 @@ def _python_region_callbacks(
905
938
 
906
939
  def _python_stateful_callbacks(
907
940
  code: PycCodeObject,
908
- instructions: tuple[object, ...],
941
+ prepared_steps: tuple[VMBytecodeStep, ...],
909
942
  ) -> VMStatefulCallbacks[VMBytecodeStep]:
943
+ def lift_linear(start, end, locals_, stack):
944
+ slice_steps = prepared_steps[start:end]
945
+ result = lift_steps(
946
+ slice_steps,
947
+ initial_locals=locals_,
948
+ initial_stack=stack,
949
+ )
950
+ stopped_at_terminal_end = (
951
+ result.stopped_at is slice_steps[-1]
952
+ if slice_steps and result.state.terminator is not None
953
+ else False
954
+ )
955
+ if result.state.diagnostics or (
956
+ result.stopped_at is not None and not stopped_at_terminal_end
957
+ ):
958
+ return None
959
+ return VMLinearState(
960
+ locals=result.state.locals,
961
+ stack=tuple(result.state.stack),
962
+ statements=tuple(result.state.statements),
963
+ terminator=result.state.terminator,
964
+ )
965
+
910
966
  return VMStatefulCallbacks(
911
967
  initial_locals=lambda: _python_initial_locals(code),
912
- lift_linear=lambda start, end, locals, stack: _python_linear_state(
913
- instructions[start:end],
914
- locals,
915
- stack,
916
- ),
968
+ lift_linear=lift_linear,
917
969
  branch_condition=_python_branch_condition,
918
970
  )
919
971
 
920
972
 
921
- def _python_linear_state(
922
- instructions: tuple[object, ...],
923
- initial_locals: dict[str, Expr],
924
- initial_stack: tuple[Expr, ...],
925
- ) -> VMLinearState | None:
926
- result = _run_python_stack_slice(
927
- _PycLinearLocals(tuple(initial_locals)),
928
- instructions,
929
- dict(initial_locals),
930
- initial_stack,
931
- )
932
- stopped_at_terminal_end = (
933
- getattr(result.stopped_at, "source", None) == _python_source(instructions[-1])
934
- if instructions and result.state.terminator is not None
935
- else False
936
- )
937
- if result.state.diagnostics or (result.stopped_at is not None and not stopped_at_terminal_end):
938
- return None
939
- return VMLinearState(
940
- locals=result.state.locals,
941
- stack=tuple(result.state.stack),
942
- statements=tuple(result.state.statements),
943
- terminator=result.state.terminator,
944
- )
945
-
946
-
947
973
  def _python_branch_condition(branch: VMBytecodeStep, stack: tuple[Expr, ...]) -> Expr | None:
948
974
  if not stack:
949
975
  return None
@@ -960,11 +986,6 @@ def _python_branch_condition(branch: VMBytecodeStep, stack: tuple[Expr, ...]) ->
960
986
  return condition
961
987
 
962
988
 
963
- class _PycLinearLocals:
964
- def __init__(self, varnames: tuple[str, ...]) -> None:
965
- self.varnames = varnames
966
-
967
-
968
989
  def _python_region_profile(
969
990
  steps: tuple[VMBytecodeStep, ...],
970
991
  instructions: tuple[object, ...],
@@ -997,70 +1018,6 @@ def _await_region_end(instructions: tuple[object, ...], start: int, end: int) ->
997
1018
  return None
998
1019
 
999
1020
 
1000
- def _lift_instruction_slice(
1001
- code: PycCodeObject,
1002
- instructions: tuple[object, ...],
1003
- assignments: list[Assign],
1004
- preloaded_stack: tuple[Expr, ...] = (),
1005
- ) -> tuple[object, ...]:
1006
- if not instructions:
1007
- return ()
1008
- initial_locals = {
1009
- name: Var(name=name, source=SourceRef(frontend="python-pyc", detail=f"local:{name}"))
1010
- for name in code.varnames
1011
- }
1012
- for assignment in assignments:
1013
- initial_locals[assignment.target.name] = assignment.target
1014
- result = _run_python_stack_slice(code, instructions, initial_locals, preloaded_stack)
1015
- if result.state.diagnostics:
1016
- return ()
1017
- output: list[object] = list(result.state.statements)
1018
- if result.stopped_at is not None and result.state.terminator is None:
1019
- try:
1020
- stopped_index = instructions.index(result.stopped_at)
1021
- except ValueError:
1022
- stopped_index = 0
1023
- output.append(
1024
- vm_unsupported(
1025
- source=result.stopped_at.source,
1026
- message="unsupported region",
1027
- detail=f"stopped at {result.stopped_at.opcode}",
1028
- raw=(result.stopped_at.raw,) if result.stopped_at.raw else _raw_instruction_window(instructions, stopped_index),
1029
- )
1030
- )
1031
- if result.state.terminator is not None:
1032
- output.append(result.state.terminator)
1033
- return tuple(output)
1034
-
1035
-
1036
- def _lift_stack_expr(
1037
- code: PycCodeObject,
1038
- instructions: tuple[object, ...],
1039
- assignments: list[Assign],
1040
- preloaded_stack: tuple[Expr, ...] = (),
1041
- ) -> Expr | None:
1042
- initial_locals = {
1043
- name: Var(name=name, source=SourceRef(frontend="python-pyc", detail=f"local:{name}"))
1044
- for name in code.varnames
1045
- }
1046
- for assignment in assignments:
1047
- initial_locals[assignment.target.name] = assignment.target
1048
- result = _run_python_stack_slice(code, instructions, initial_locals, preloaded_stack)
1049
- if result.state.diagnostics or not result.state.stack:
1050
- return None
1051
- return result.state.stack[-1]
1052
-
1053
-
1054
- def _run_python_stack_slice(
1055
- code: PycCodeObject,
1056
- instructions: tuple[object, ...],
1057
- initial_locals: dict[str, Expr],
1058
- initial_stack: tuple[Expr, ...] = (),
1059
- ):
1060
- steps = _python_bytecode_steps(instructions)
1061
- return lift_steps(steps, initial_locals=initial_locals, initial_stack=initial_stack)
1062
-
1063
-
1064
1021
  def _instruction_count(instruction) -> int:
1065
1022
  return instruction.arg if isinstance(instruction.arg, int) else 0
1066
1023
 
@@ -24,6 +24,8 @@ class PycInstruction:
24
24
  argval: object
25
25
  argrepr: str
26
26
  starts_line: int | None
27
+ artifact_offset: int | None = None
28
+ size: int | None = None
27
29
 
28
30
 
29
31
  @dataclass(frozen=True)
@@ -51,6 +53,7 @@ class PycCodeObject:
51
53
  instructions: tuple[PycInstruction, ...]
52
54
  exception_regions: tuple[PycExceptionRegion, ...]
53
55
  children: tuple["PycCodeObject", ...]
56
+ artifact_code_offset: int | None = None
54
57
 
55
58
 
56
59
  @dataclass(frozen=True)
@@ -85,17 +88,19 @@ def decode_pyc(data: bytes, filename: str | None = None) -> PycModule:
85
88
  return PycModule(
86
89
  magic=data[:4],
87
90
  flags=flags,
88
- code=_decode_code_object(code),
91
+ code=_decode_code_object(code, _marshal_code_offsets(data, code)),
89
92
  filename=filename,
90
93
  )
91
94
 
92
95
 
93
- def _decode_code_object(code: types.CodeType) -> PycCodeObject:
96
+ def _decode_code_object(code: types.CodeType, code_offsets: dict[int, int]) -> PycCodeObject:
94
97
  children = tuple(
95
- _decode_code_object(const)
98
+ _decode_code_object(const, code_offsets)
96
99
  for const in code.co_consts
97
100
  if isinstance(const, types.CodeType)
98
101
  )
102
+ instructions = tuple(dis.get_instructions(code))
103
+ code_start = code_offsets.get(id(code))
99
104
  return PycCodeObject(
100
105
  name=code.co_name,
101
106
  argcount=code.co_argcount,
@@ -119,8 +124,10 @@ def _decode_code_object(code: types.CodeType) -> PycCodeObject:
119
124
  argval=instruction.argval,
120
125
  argrepr=instruction.argrepr,
121
126
  starts_line=instruction.starts_line,
127
+ artifact_offset=None if code_start is None else code_start + instruction.offset,
128
+ size=None if code_start is None else _instruction_size(instructions, index, len(code.co_code)),
122
129
  )
123
- for instruction in dis.get_instructions(code)
130
+ for index, instruction in enumerate(instructions)
124
131
  ),
125
132
  exception_regions=tuple(
126
133
  PycExceptionRegion(
@@ -133,4 +140,58 @@ def _decode_code_object(code: types.CodeType) -> PycCodeObject:
133
140
  for entry in dis.Bytecode(code).exception_entries
134
141
  ),
135
142
  children=children,
143
+ artifact_code_offset=code_start,
136
144
  )
145
+
146
+
147
+ def _instruction_size(instructions: tuple[dis.Instruction, ...], index: int, code_size: int) -> int:
148
+ next_offset = instructions[index + 1].offset if index + 1 < len(instructions) else code_size
149
+ return next_offset - instructions[index].offset
150
+
151
+
152
+ def _marshal_code_offsets(data: bytes, root: types.CodeType) -> dict[int, int]:
153
+ """Find only unambiguous marshal byte-string records for ``co_code`` values.
154
+
155
+ CPython serializes code bytes as a marshal ``TYPE_STRING`` record. The
156
+ frontend verifies both that record's tag and its length, then accepts a
157
+ location only when that code byte sequence has exactly one candidate. A
158
+ repeated or otherwise ambiguous value is deliberately left unmapped.
159
+ """
160
+ code_objects = tuple(_walk_code_objects(root))
161
+ candidates: dict[bytes, list[int]] = {}
162
+ for code in code_objects:
163
+ payload = code.co_code
164
+ if payload in candidates:
165
+ continue
166
+ candidates[payload] = _marshal_string_payload_offsets(data, payload)
167
+ assigned: set[int] = set()
168
+ offsets: dict[int, int] = {}
169
+ for code in code_objects:
170
+ matches = [offset for offset in candidates[code.co_code] if offset not in assigned]
171
+ if len(matches) == 1:
172
+ offsets[id(code)] = matches[0]
173
+ assigned.add(matches[0])
174
+ return offsets
175
+
176
+
177
+ def _walk_code_objects(code: types.CodeType):
178
+ yield code
179
+ for value in code.co_consts:
180
+ if isinstance(value, types.CodeType):
181
+ yield from _walk_code_objects(value)
182
+
183
+
184
+ def _marshal_string_payload_offsets(data: bytes, payload: bytes) -> list[int]:
185
+ if not payload:
186
+ return []
187
+ result: list[int] = []
188
+ size = len(payload)
189
+ for header in range(PYC_MIN_HEADER_SIZE, len(data) - size - 4):
190
+ if (data[header] & 0x7F) != ord("s"):
191
+ continue
192
+ if int.from_bytes(data[header + 1 : header + 5], "little", signed=True) != size:
193
+ continue
194
+ start = header + 5
195
+ if data[start : start + size] == payload:
196
+ result.append(start)
197
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unidecompiler-plugin-python-pyc
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Python bytecode frontend plugin for unidecompiler
5
5
  Author-email: Wker <1670133844@qq.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/Wker666/unidecompiler
9
9
  Project-URL: Issues, https://github.com/Wker666/unidecompiler/issues
10
10
  Requires-Python: >=3.11
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: unidecompiler<0.2.0,>=0.1.1
12
+ Requires-Dist: unidecompiler<0.2.0,>=0.1.6
13
13
 
14
14
  # unidecompiler-plugin-python-pyc
15
15