@futpib/parser 1.0.3 → 1.0.6

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 (262) hide show
  1. package/.claude/settings.local.json +24 -0
  2. package/.github/workflows/main.yml +1 -0
  3. package/build/androidPackageParser.js +30 -32
  4. package/build/arbitraryDalvikBytecode.d.ts +3 -3
  5. package/build/arbitraryDalvikBytecode.js +33 -27
  6. package/build/arbitraryDalvikExecutable.js +55 -17
  7. package/build/arbitraryJava.d.ts +31 -0
  8. package/build/arbitraryJava.js +532 -0
  9. package/build/arbitraryJavaScript.d.ts +3 -0
  10. package/build/arbitraryJavaScript.js +263 -0
  11. package/build/arbitraryJavascript.d.ts +3 -0
  12. package/build/arbitraryJavascript.js +263 -0
  13. package/build/arbitraryZig.d.ts +3 -0
  14. package/build/arbitraryZig.js +240 -0
  15. package/build/arbitraryZipStream.d.ts +1 -1
  16. package/build/arrayParser.js +72 -13
  17. package/build/backsmali.d.ts +4 -3
  18. package/build/backsmali.js +26 -6
  19. package/build/bash.d.ts +89 -0
  20. package/build/bash.js +1 -0
  21. package/build/bashParser.d.ts +6 -0
  22. package/build/bashParser.js +335 -0
  23. package/build/bashParser.test.d.ts +1 -0
  24. package/build/bashParser.test.js +343 -0
  25. package/build/bashParserEdgeCases.test.d.ts +1 -0
  26. package/build/bashParserEdgeCases.test.js +117 -0
  27. package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
  28. package/build/dalvikBytecodeParser/addressConversion.js +334 -0
  29. package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
  30. package/build/dalvikBytecodeParser/formatParsers.js +13 -14
  31. package/build/dalvikBytecodeParser.d.ts +60 -31
  32. package/build/dalvikBytecodeParser.js +92 -35
  33. package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
  34. package/build/dalvikBytecodeParser.test-d.js +268 -0
  35. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
  36. package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
  37. package/build/dalvikBytecodeUnparser.d.ts +2 -2
  38. package/build/dalvikBytecodeUnparser.js +23 -23
  39. package/build/dalvikBytecodeUnparser.test.js +7 -7
  40. package/build/dalvikExecutable.d.ts +3 -3
  41. package/build/dalvikExecutable.test-d.d.ts +1 -0
  42. package/build/dalvikExecutable.test-d.js +59 -0
  43. package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
  44. package/build/dalvikExecutableParser/typedNumbers.js +3 -0
  45. package/build/dalvikExecutableParser.d.ts +2 -1
  46. package/build/dalvikExecutableParser.js +96 -77
  47. package/build/dalvikExecutableParser.test.js +24 -3
  48. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
  49. package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
  50. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
  51. package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
  52. package/build/dalvikExecutableUnparser.d.ts +2 -2
  53. package/build/dalvikExecutableUnparser.test.js +2 -1
  54. package/build/disjunctionParser.d.ts +5 -3
  55. package/build/disjunctionParser.js +79 -17
  56. package/build/disjunctionParser.test-d.d.ts +1 -0
  57. package/build/disjunctionParser.test-d.js +72 -0
  58. package/build/elementSwitchParser.d.ts +4 -0
  59. package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
  60. package/build/elementSwitchParser.test-d.d.ts +1 -0
  61. package/build/elementSwitchParser.test-d.js +44 -0
  62. package/build/exactSequenceParser.d.ts +4 -2
  63. package/build/exactSequenceParser.test-d.d.ts +1 -0
  64. package/build/exactSequenceParser.test-d.js +36 -0
  65. package/build/fetchCid.js +2 -66
  66. package/build/index.d.ts +25 -2
  67. package/build/index.js +23 -1
  68. package/build/index.test.js +16 -1
  69. package/build/inputReader.d.ts +10 -0
  70. package/build/inputReader.js +36 -0
  71. package/build/java.d.ts +502 -0
  72. package/build/java.js +2 -0
  73. package/build/javaKeyStoreParser.js +14 -17
  74. package/build/javaParser.d.ts +51 -0
  75. package/build/javaParser.js +1538 -0
  76. package/build/javaParser.test.d.ts +1 -0
  77. package/build/javaParser.test.js +1287 -0
  78. package/build/javaScript.d.ts +35 -0
  79. package/build/javaScript.js +1 -0
  80. package/build/javaScriptParser.d.ts +9 -0
  81. package/build/javaScriptParser.js +34 -0
  82. package/build/javaScriptUnparser.d.ts +3 -0
  83. package/build/javaScriptUnparser.js +4 -0
  84. package/build/javaScriptUnparser.test.d.ts +1 -0
  85. package/build/javaScriptUnparser.test.js +24 -0
  86. package/build/javaUnparser.d.ts +2 -0
  87. package/build/javaUnparser.js +519 -0
  88. package/build/javaUnparser.test.d.ts +1 -0
  89. package/build/javaUnparser.test.js +24 -0
  90. package/build/javascript.d.ts +35 -0
  91. package/build/javascript.js +1 -0
  92. package/build/javascriptParser.d.ts +9 -0
  93. package/build/javascriptParser.js +34 -0
  94. package/build/javascriptUnparser.d.ts +3 -0
  95. package/build/javascriptUnparser.js +4 -0
  96. package/build/javascriptUnparser.test.d.ts +1 -0
  97. package/build/javascriptUnparser.test.js +24 -0
  98. package/build/jsonParser.js +2 -12
  99. package/build/lazyMessageError.d.ts +3 -0
  100. package/build/lookaheadParser.js +60 -3
  101. package/build/negativeLookaheadParser.js +70 -11
  102. package/build/nonEmptyArrayParser.js +72 -13
  103. package/build/objectParser.d.ts +12 -0
  104. package/build/objectParser.js +31 -0
  105. package/build/objectParser.test-d.d.ts +1 -0
  106. package/build/objectParser.test-d.js +112 -0
  107. package/build/objectParser.test.d.ts +1 -0
  108. package/build/objectParser.test.js +55 -0
  109. package/build/optionalParser.js +69 -10
  110. package/build/parser.d.ts +4 -0
  111. package/build/parser.js +3 -1
  112. package/build/parser.test.js +114 -1
  113. package/build/parserConsumedSequenceParser.js +66 -7
  114. package/build/parserContext.d.ts +6 -0
  115. package/build/parserContext.js +20 -11
  116. package/build/parserError.d.ts +119 -27
  117. package/build/parserError.js +16 -8
  118. package/build/regexpParser.d.ts +2 -0
  119. package/build/regexpParser.js +101 -0
  120. package/build/regexpParser.test.d.ts +1 -0
  121. package/build/regexpParser.test.js +114 -0
  122. package/build/regularExpression.d.ts +63 -0
  123. package/build/regularExpression.js +1 -0
  124. package/build/regularExpressionParser.d.ts +3 -0
  125. package/build/regularExpressionParser.js +600 -0
  126. package/build/regularExpressionParser.test.d.ts +1 -0
  127. package/build/regularExpressionParser.test.js +89 -0
  128. package/build/separatedArrayParser.js +73 -14
  129. package/build/separatedNonEmptyArrayParser.js +73 -14
  130. package/build/sliceBoundedParser.js +62 -5
  131. package/build/smaliParser.d.ts +7 -7
  132. package/build/smaliParser.js +185 -268
  133. package/build/smaliParser.test.js +58 -0
  134. package/build/stringEscapes.d.ts +5 -0
  135. package/build/stringEscapes.js +244 -0
  136. package/build/symbolicExpression.d.ts +29 -0
  137. package/build/symbolicExpression.js +1 -0
  138. package/build/symbolicExpressionParser.d.ts +4 -0
  139. package/build/symbolicExpressionParser.js +123 -0
  140. package/build/symbolicExpressionParser.test.d.ts +1 -0
  141. package/build/symbolicExpressionParser.test.js +289 -0
  142. package/build/terminatedArrayParser.js +113 -38
  143. package/build/terminatedArrayParser.test.js +4 -2
  144. package/build/tupleParser.d.ts +7 -15
  145. package/build/tupleParser.js +1 -0
  146. package/build/unionParser.d.ts +5 -3
  147. package/build/unionParser.js +7 -2
  148. package/build/unionParser.test-d.d.ts +1 -0
  149. package/build/unionParser.test-d.js +72 -0
  150. package/build/unionParser.test.js +10 -11
  151. package/build/zig.d.ts +280 -0
  152. package/build/zig.js +2 -0
  153. package/build/zigParser.d.ts +3 -0
  154. package/build/zigParser.js +1119 -0
  155. package/build/zigParser.test.d.ts +1 -0
  156. package/build/zigParser.test.js +1590 -0
  157. package/build/zigUnparser.d.ts +2 -0
  158. package/build/zigUnparser.js +460 -0
  159. package/build/zigUnparser.test.d.ts +1 -0
  160. package/build/zigUnparser.test.js +24 -0
  161. package/build/zipParser.js +19 -32
  162. package/build/zipUnparser.js +19 -7
  163. package/build/zipUnparser.test.js +1 -1
  164. package/node_modules-@types/s-expression/index.d.ts +5 -0
  165. package/package.json +25 -6
  166. package/src/androidPackageParser.ts +33 -60
  167. package/src/arbitraryDalvikBytecode.ts +39 -31
  168. package/src/arbitraryDalvikExecutable.ts +65 -20
  169. package/src/arbitraryJava.ts +804 -0
  170. package/src/arbitraryJavaScript.ts +410 -0
  171. package/src/arbitraryZig.ts +380 -0
  172. package/src/arrayParser.ts +1 -3
  173. package/src/backsmali.ts +35 -4
  174. package/src/bash.ts +127 -0
  175. package/src/bashParser.test.ts +590 -0
  176. package/src/bashParser.ts +498 -0
  177. package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
  178. package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
  179. package/src/dalvikBytecodeParser.test-d.ts +310 -0
  180. package/src/dalvikBytecodeParser.ts +194 -69
  181. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
  182. package/src/dalvikBytecodeUnparser.test.ts +7 -7
  183. package/src/dalvikBytecodeUnparser.ts +31 -30
  184. package/src/dalvikExecutable.test-d.ts +132 -0
  185. package/src/dalvikExecutable.ts +3 -3
  186. package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
  187. package/src/dalvikExecutableParser.test.ts +37 -3
  188. package/src/dalvikExecutableParser.test.ts.md +163 -2
  189. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  190. package/src/dalvikExecutableParser.ts +121 -139
  191. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
  192. package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
  193. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
  194. package/src/dalvikExecutableUnparser.test.ts +3 -2
  195. package/src/dalvikExecutableUnparser.ts +4 -4
  196. package/src/disjunctionParser.test-d.ts +105 -0
  197. package/src/disjunctionParser.ts +18 -15
  198. package/src/elementSwitchParser.test-d.ts +74 -0
  199. package/src/elementSwitchParser.ts +51 -0
  200. package/src/exactSequenceParser.test-d.ts +43 -0
  201. package/src/exactSequenceParser.ts +13 -8
  202. package/src/fetchCid.ts +2 -76
  203. package/src/index.test.ts +22 -1
  204. package/src/index.ts +119 -2
  205. package/src/inputReader.ts +53 -0
  206. package/src/java.ts +708 -0
  207. package/src/javaKeyStoreParser.ts +18 -32
  208. package/src/javaParser.test.ts +1592 -0
  209. package/src/javaParser.ts +2640 -0
  210. package/src/javaScript.ts +36 -0
  211. package/src/javaScriptParser.ts +57 -0
  212. package/src/javaScriptUnparser.test.ts +37 -0
  213. package/src/javaScriptUnparser.ts +7 -0
  214. package/src/javaUnparser.test.ts +37 -0
  215. package/src/javaUnparser.ts +640 -0
  216. package/src/jsonParser.ts +6 -27
  217. package/src/lookaheadParser.ts +2 -6
  218. package/src/negativeLookaheadParser.ts +1 -3
  219. package/src/nonEmptyArrayParser.ts +1 -3
  220. package/src/objectParser.test-d.ts +152 -0
  221. package/src/objectParser.test.ts +71 -0
  222. package/src/objectParser.ts +69 -0
  223. package/src/optionalParser.ts +1 -3
  224. package/src/parser.test.ts +151 -4
  225. package/src/parser.ts +11 -1
  226. package/src/parserConsumedSequenceParser.ts +2 -4
  227. package/src/parserContext.ts +26 -11
  228. package/src/parserError.ts +17 -3
  229. package/src/regexpParser.test.ts +264 -0
  230. package/src/regexpParser.ts +126 -0
  231. package/src/regularExpression.ts +24 -0
  232. package/src/regularExpressionParser.test.ts +102 -0
  233. package/src/regularExpressionParser.ts +920 -0
  234. package/src/separatedArrayParser.ts +1 -3
  235. package/src/separatedNonEmptyArrayParser.ts +1 -3
  236. package/src/sliceBoundedParser.test.ts +2 -2
  237. package/src/sliceBoundedParser.ts +15 -19
  238. package/src/smaliParser.test.ts +64 -0
  239. package/src/smaliParser.test.ts.md +12 -12
  240. package/src/smaliParser.test.ts.snap +0 -0
  241. package/src/smaliParser.ts +246 -534
  242. package/src/stringEscapes.ts +253 -0
  243. package/src/symbolicExpression.ts +17 -0
  244. package/src/symbolicExpressionParser.test.ts +466 -0
  245. package/src/symbolicExpressionParser.ts +190 -0
  246. package/src/terminatedArrayParser.test.ts +9 -6
  247. package/src/terminatedArrayParser.ts +25 -29
  248. package/src/tupleParser.ts +21 -18
  249. package/src/unionParser.test-d.ts +105 -0
  250. package/src/unionParser.test.ts +18 -17
  251. package/src/unionParser.ts +28 -16
  252. package/src/zig.ts +411 -0
  253. package/src/zigParser.test.ts +1693 -0
  254. package/src/zigParser.ts +1745 -0
  255. package/src/zigUnparser.test.ts +37 -0
  256. package/src/zigUnparser.ts +615 -0
  257. package/src/zipParser.ts +20 -56
  258. package/src/zipUnparser.test.ts +1 -1
  259. package/src/zipUnparser.ts +22 -7
  260. package/tsconfig.json +2 -2
  261. package/build/exactElementSwitchParser.d.ts +0 -3
  262. package/src/exactElementSwitchParser.ts +0 -41
@@ -0,0 +1,496 @@
1
+ import { type RawDalvikBytecodeOperation, type IndexResolvedOperation } from '../dalvikBytecodeParser.js';
2
+ import {
3
+ type CodeUnit, isoCodeUnit,
4
+ type InstructionIndex, isoInstructionIndex,
5
+ } from '../dalvikExecutableParser/typedNumbers.js';
6
+ import { operationFormats } from './operationFormats.js';
7
+ import { formatSizes } from './formatSizes.js';
8
+
9
+ /**
10
+ * Map from code unit offset to instruction index.
11
+ * Keys are raw code unit offsets, values are instruction indices.
12
+ */
13
+ export type CodeUnitToIndexMap = Map<CodeUnit, InstructionIndex>;
14
+
15
+ /**
16
+ * Map from instruction index to code unit offset.
17
+ * Keys are instruction indices, values are raw code unit offsets.
18
+ */
19
+ export type IndexToCodeUnitMap = Map<InstructionIndex, CodeUnit>;
20
+
21
+ /**
22
+ * Get the branch offsets array length from an operation, regardless of tier.
23
+ * Works with all field name variants: branchOffsetsCodeUnit, branchOffsetsIndex, targetInstructionIndices, branchOffsetIndices.
24
+ */
25
+ function getBranchOffsetsLength(operation: { operation: string }): number | undefined {
26
+ const op = operation as any;
27
+ return op.branchOffsetsCodeUnit?.length
28
+ ?? op.branchOffsetsIndex?.length
29
+ ?? op.targetInstructionIndices?.length
30
+ ?? op.branchOffsetIndices?.length;
31
+ }
32
+
33
+ /**
34
+ * Calculate the size of an operation in code units (16-bit words).
35
+ * Works with any tier of operation (Tier 1, 2, 3, or SmaliCodeOperation).
36
+ */
37
+ export function getOperationSizeInCodeUnits(operation: { operation: string; data?: Uint8Array | number[] }): number {
38
+ if (operation.operation === 'packed-switch-payload') {
39
+ const length = getBranchOffsetsLength(operation);
40
+ if (length === undefined) {
41
+ throw new Error('packed-switch-payload missing branch offsets array');
42
+ }
43
+ // Header (4 code units) + targets (2 code units each)
44
+ return (length * 2) + 4;
45
+ }
46
+
47
+ if (operation.operation === 'sparse-switch-payload') {
48
+ const length = getBranchOffsetsLength(operation);
49
+ if (length === undefined) {
50
+ throw new Error('sparse-switch-payload missing branch offsets array');
51
+ }
52
+ // Header (2 code units) + keys (2 code units each) + targets (2 code units each)
53
+ return (length * 4) + 2;
54
+ }
55
+
56
+ if (operation.operation === 'fill-array-data-payload') {
57
+ // data.length is already the total number of bytes (size * elementWidth)
58
+ const dataSize = operation.data!.length;
59
+ const paddingSize = dataSize % 2;
60
+ const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
61
+ return totalBytes / 2; // Convert to code units
62
+ }
63
+
64
+ const format = operationFormats[operation.operation as keyof typeof operationFormats];
65
+ if (!format) {
66
+ throw new Error(`Unknown operation format: ${operation.operation}`);
67
+ }
68
+
69
+ return formatSizes[format];
70
+ }
71
+
72
+ /**
73
+ * Build mapping from code unit offset to instruction index.
74
+ * Also maps the end position (total code units) to instructions.length.
75
+ */
76
+ export function buildCodeUnitToIndexMap(
77
+ instructions: RawDalvikBytecodeOperation[],
78
+ ): CodeUnitToIndexMap {
79
+ const map: CodeUnitToIndexMap = new Map();
80
+ let codeUnitOffset = 0;
81
+
82
+ for (let index = 0; index < instructions.length; index++) {
83
+ map.set(isoCodeUnit.wrap(codeUnitOffset), isoInstructionIndex.wrap(index));
84
+ codeUnitOffset += getOperationSizeInCodeUnits(instructions[index]);
85
+ }
86
+
87
+ // Map the end position
88
+ map.set(isoCodeUnit.wrap(codeUnitOffset), isoInstructionIndex.wrap(instructions.length));
89
+
90
+ return map;
91
+ }
92
+
93
+ /**
94
+ * Build mapping from instruction index to code unit offset.
95
+ * Also maps instructions.length to the total code units.
96
+ */
97
+ export function buildIndexToCodeUnitMap(
98
+ instructions: RawDalvikBytecodeOperation[],
99
+ ): IndexToCodeUnitMap {
100
+ const map: IndexToCodeUnitMap = new Map();
101
+ let codeUnitOffset = 0;
102
+
103
+ for (let index = 0; index < instructions.length; index++) {
104
+ map.set(isoInstructionIndex.wrap(index), isoCodeUnit.wrap(codeUnitOffset));
105
+ codeUnitOffset += getOperationSizeInCodeUnits(instructions[index]);
106
+ }
107
+
108
+ // Map the end position
109
+ map.set(isoInstructionIndex.wrap(instructions.length), isoCodeUnit.wrap(codeUnitOffset));
110
+
111
+ return map;
112
+ }
113
+
114
+ /**
115
+ * Convert a code unit offset to an instruction index.
116
+ */
117
+ export function codeUnitToInstructionIndex(
118
+ codeUnitOffset: CodeUnit,
119
+ codeUnitToIndexMap: CodeUnitToIndexMap,
120
+ ): InstructionIndex {
121
+ const index = codeUnitToIndexMap.get(codeUnitOffset);
122
+ if (index === undefined) {
123
+ throw new Error(`Invalid code unit offset: ${isoCodeUnit.unwrap(codeUnitOffset)}. Valid offsets: ${[...codeUnitToIndexMap.keys()].map(k => isoCodeUnit.unwrap(k)).join(', ')}`);
124
+ }
125
+
126
+ return index;
127
+ }
128
+
129
+ /**
130
+ * Convert an instruction index to a code unit offset.
131
+ */
132
+ export function instructionIndexToCodeUnit(
133
+ instructionIndex: InstructionIndex,
134
+ indexToCodeUnitMap: IndexToCodeUnitMap,
135
+ ): CodeUnit {
136
+ const offset = indexToCodeUnitMap.get(instructionIndex);
137
+ if (offset === undefined) {
138
+ throw new Error(`Invalid instruction index: ${isoInstructionIndex.unwrap(instructionIndex)}. Valid indices: ${[...indexToCodeUnitMap.keys()].map(k => isoInstructionIndex.unwrap(k)).join(', ')}`);
139
+ }
140
+
141
+ return offset;
142
+ }
143
+
144
+ /**
145
+ * Convert a relative branch offset (in code units) to a relative instruction offset.
146
+ * The offset is relative to the source instruction.
147
+ */
148
+ export function convertBranchOffsetToInstructionOffset(
149
+ branchOffsetInCodeUnits: CodeUnit,
150
+ sourceInstructionIndex: InstructionIndex,
151
+ indexToCodeUnitMap: IndexToCodeUnitMap,
152
+ codeUnitToIndexMap: CodeUnitToIndexMap,
153
+ ): InstructionIndex {
154
+ const sourceCodeUnit = indexToCodeUnitMap.get(sourceInstructionIndex);
155
+ if (sourceCodeUnit === undefined) {
156
+ throw new Error(`Invalid source instruction index: ${isoInstructionIndex.unwrap(sourceInstructionIndex)}`);
157
+ }
158
+
159
+ const targetCodeUnit = isoCodeUnit.wrap(isoCodeUnit.unwrap(sourceCodeUnit) + isoCodeUnit.unwrap(branchOffsetInCodeUnits));
160
+ const targetIndex = codeUnitToIndexMap.get(targetCodeUnit);
161
+ if (targetIndex === undefined) {
162
+ throw new Error(`Invalid branch target code unit: ${isoCodeUnit.unwrap(targetCodeUnit)} (from source ${isoCodeUnit.unwrap(sourceCodeUnit)} + offset ${isoCodeUnit.unwrap(branchOffsetInCodeUnits)})`);
163
+ }
164
+
165
+ return isoInstructionIndex.wrap(isoInstructionIndex.unwrap(targetIndex) - isoInstructionIndex.unwrap(sourceInstructionIndex));
166
+ }
167
+
168
+ /**
169
+ * Convert a relative instruction offset back to a code unit offset.
170
+ * The offset is relative to the source instruction.
171
+ */
172
+ export function convertInstructionOffsetToBranchOffset(
173
+ instructionOffset: InstructionIndex,
174
+ sourceInstructionIndex: InstructionIndex,
175
+ indexToCodeUnitMap: IndexToCodeUnitMap,
176
+ ): CodeUnit {
177
+ const sourceCodeUnit = indexToCodeUnitMap.get(sourceInstructionIndex);
178
+ if (sourceCodeUnit === undefined) {
179
+ throw new Error(`Invalid source instruction index: ${isoInstructionIndex.unwrap(sourceInstructionIndex)}`);
180
+ }
181
+
182
+ const targetInstructionIndex = isoInstructionIndex.wrap(isoInstructionIndex.unwrap(sourceInstructionIndex) + isoInstructionIndex.unwrap(instructionOffset));
183
+ const targetCodeUnit = indexToCodeUnitMap.get(targetInstructionIndex);
184
+ if (targetCodeUnit === undefined) {
185
+ throw new Error(`Invalid target instruction index: ${isoInstructionIndex.unwrap(targetInstructionIndex)}`);
186
+ }
187
+
188
+ return isoCodeUnit.wrap(isoCodeUnit.unwrap(targetCodeUnit) - isoCodeUnit.unwrap(sourceCodeUnit));
189
+ }
190
+
191
+ /**
192
+ * Find the switch instruction that references a payload at the given index.
193
+ * Returns the index of the switch instruction.
194
+ */
195
+ export function findSwitchInstructionForPayload(
196
+ instructions: RawDalvikBytecodeOperation[],
197
+ payloadIndex: InstructionIndex,
198
+ indexToCodeUnitMap: IndexToCodeUnitMap,
199
+ ): InstructionIndex {
200
+ const payloadCodeUnit = indexToCodeUnitMap.get(payloadIndex);
201
+ if (payloadCodeUnit === undefined) {
202
+ throw new Error(`Invalid payload index: ${isoInstructionIndex.unwrap(payloadIndex)}`);
203
+ }
204
+
205
+ for (let i = 0; i < instructions.length; i++) {
206
+ const inst = instructions[i];
207
+ if (
208
+ (inst.operation === 'packed-switch' || inst.operation === 'sparse-switch')
209
+ && 'branchOffsetCodeUnit' in inst
210
+ ) {
211
+ const sourceCodeUnit = indexToCodeUnitMap.get(isoInstructionIndex.wrap(i));
212
+ if (sourceCodeUnit !== undefined && isoCodeUnit.unwrap(sourceCodeUnit) + isoCodeUnit.unwrap(inst.branchOffsetCodeUnit) === isoCodeUnit.unwrap(payloadCodeUnit)) {
213
+ return isoInstructionIndex.wrap(i);
214
+ }
215
+ }
216
+ }
217
+
218
+ throw new Error(`No switch instruction found for payload at index ${isoInstructionIndex.unwrap(payloadIndex)}`);
219
+ }
220
+
221
+ // Type for Tier 2: InstructionIndex typed numbers (internal)
222
+ export type ConvertedBranchOffsetOperation<T> = T extends { branchOffsetCodeUnit: CodeUnit }
223
+ ? Omit<T, 'branchOffsetCodeUnit'> & { branchOffsetIndex: InstructionIndex }
224
+ : T extends { branchOffsetsCodeUnit: CodeUnit[] }
225
+ ? Omit<T, 'branchOffsetsCodeUnit'> & { branchOffsetsIndex: InstructionIndex[] }
226
+ : T;
227
+
228
+ export type ConvertedRawDalvikBytecodeOperation = ConvertedBranchOffsetOperation<RawDalvikBytecodeOperation>;
229
+
230
+ // Type for Tier 3: plain numbers with absolute instruction indices (public API)
231
+ export type ResolvedBranchOffsetOperation<T> = T extends { branchOffsetIndex: InstructionIndex }
232
+ ? Omit<T, 'branchOffsetIndex'> & { targetInstructionIndex: number }
233
+ : T extends { branchOffsetsIndex: InstructionIndex[] }
234
+ ? Omit<T, 'branchOffsetsIndex'> & { targetInstructionIndices: number[] }
235
+ : T;
236
+
237
+ // Final public type: applies both branch offset resolution and index resolution
238
+ // This ensures methodIndex→method, fieldIndex→field, typeIndex→type, stringIndex→string
239
+ export type DalvikBytecodeOperation = IndexResolvedOperation<ResolvedBranchOffsetOperation<ConvertedRawDalvikBytecodeOperation>>;
240
+ export type DalvikBytecode = DalvikBytecodeOperation[];
241
+
242
+ /**
243
+ * Convert all branch offsets in instructions from code units to instruction offsets.
244
+ * Tier 1 (CodeUnit) -> Tier 2 (InstructionIndex)
245
+ */
246
+ export function convertBranchOffsetsToInstructionOffsets(
247
+ instructions: RawDalvikBytecodeOperation[],
248
+ ): ConvertedRawDalvikBytecodeOperation[] {
249
+ const codeUnitToIndexMap = buildCodeUnitToIndexMap(instructions);
250
+ const indexToCodeUnitMap = buildIndexToCodeUnitMap(instructions);
251
+
252
+ return instructions.map((instruction, index) => {
253
+ // Handle single branchOffsetCodeUnit (goto, if-*, packed-switch, sparse-switch, fill-array-data)
254
+ if ('branchOffsetCodeUnit' in instruction) {
255
+ const { branchOffsetCodeUnit, ...rest } = instruction;
256
+ return {
257
+ ...rest,
258
+ branchOffsetIndex: convertBranchOffsetToInstructionOffset(
259
+ branchOffsetCodeUnit,
260
+ isoInstructionIndex.wrap(index),
261
+ indexToCodeUnitMap,
262
+ codeUnitToIndexMap,
263
+ ),
264
+ } as ConvertedRawDalvikBytecodeOperation;
265
+ }
266
+
267
+ // Handle branchOffsetsCodeUnit array (packed-switch-payload, sparse-switch-payload)
268
+ if ('branchOffsetsCodeUnit' in instruction) {
269
+ const { branchOffsetsCodeUnit, ...rest } = instruction;
270
+ // For payload instructions, find the referring switch instruction
271
+ const sourceIndex = findSwitchInstructionForPayload(instructions, isoInstructionIndex.wrap(index), indexToCodeUnitMap);
272
+ return {
273
+ ...rest,
274
+ branchOffsetsIndex: branchOffsetsCodeUnit.map(offset =>
275
+ convertBranchOffsetToInstructionOffset(
276
+ offset,
277
+ sourceIndex,
278
+ indexToCodeUnitMap,
279
+ codeUnitToIndexMap,
280
+ ),
281
+ ),
282
+ } as ConvertedRawDalvikBytecodeOperation;
283
+ }
284
+
285
+ return instruction as ConvertedRawDalvikBytecodeOperation;
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Unwrap instruction indices to plain numbers and convert relative to absolute.
291
+ * Tier 2 (InstructionIndex, relative) -> Tier 3 (plain number, absolute)
292
+ */
293
+ export function unwrapBranchOffsets(
294
+ instructions: ConvertedRawDalvikBytecodeOperation[],
295
+ ): DalvikBytecodeOperation[] {
296
+ // First pass: find switch instruction indices for payloads
297
+ const payloadToSwitchIndex = new Map<number, number>();
298
+ for (let i = 0; i < instructions.length; i++) {
299
+ const inst = instructions[i];
300
+ if (
301
+ (inst.operation === 'packed-switch' || inst.operation === 'sparse-switch')
302
+ && 'branchOffsetIndex' in inst
303
+ ) {
304
+ const payloadIndex = i + isoInstructionIndex.unwrap(inst.branchOffsetIndex);
305
+ payloadToSwitchIndex.set(payloadIndex, i);
306
+ }
307
+ }
308
+
309
+ return instructions.map((instruction, index) => {
310
+ if ('branchOffsetIndex' in instruction) {
311
+ const { branchOffsetIndex, ...rest } = instruction;
312
+ // Convert relative offset to absolute index
313
+ const targetInstructionIndex = index + isoInstructionIndex.unwrap(branchOffsetIndex);
314
+ return {
315
+ ...rest,
316
+ targetInstructionIndex,
317
+ } as DalvikBytecodeOperation;
318
+ }
319
+
320
+ if ('branchOffsetsIndex' in instruction) {
321
+ const { branchOffsetsIndex, ...rest } = instruction;
322
+ // For payloads, the offsets are relative to the switch instruction, not the payload
323
+ const switchIndex = payloadToSwitchIndex.get(index);
324
+ if (switchIndex === undefined) {
325
+ throw new Error(`No switch instruction found for payload at index ${index}`);
326
+ }
327
+ // Convert relative offsets to absolute indices
328
+ const targetInstructionIndices = branchOffsetsIndex.map(
329
+ offset => switchIndex + isoInstructionIndex.unwrap(offset),
330
+ );
331
+ return {
332
+ ...rest,
333
+ targetInstructionIndices,
334
+ } as DalvikBytecodeOperation;
335
+ }
336
+
337
+ return instruction as DalvikBytecodeOperation;
338
+ });
339
+ }
340
+
341
+ /**
342
+ * Wrap plain numbers to instruction indices and convert absolute to relative.
343
+ * Tier 3 (plain number, absolute) -> Tier 2 (InstructionIndex, relative)
344
+ */
345
+ export function wrapBranchOffsets(
346
+ instructions: DalvikBytecodeOperation[],
347
+ ): ConvertedRawDalvikBytecodeOperation[] {
348
+ // First pass: find switch instruction indices for payloads
349
+ const payloadToSwitchIndex = new Map<number, number>();
350
+ for (let i = 0; i < instructions.length; i++) {
351
+ const inst = instructions[i];
352
+ if (
353
+ (inst.operation === 'packed-switch' || inst.operation === 'sparse-switch')
354
+ && 'targetInstructionIndex' in inst
355
+ ) {
356
+ const payloadIndex = inst.targetInstructionIndex;
357
+ payloadToSwitchIndex.set(payloadIndex, i);
358
+ }
359
+ }
360
+
361
+ return instructions.map((instruction, index) => {
362
+ if ('targetInstructionIndex' in instruction) {
363
+ const { targetInstructionIndex, ...rest } = instruction;
364
+ // Convert absolute index to relative offset
365
+ const branchOffsetIndex = isoInstructionIndex.wrap(targetInstructionIndex - index);
366
+ return {
367
+ ...rest,
368
+ branchOffsetIndex,
369
+ } as ConvertedRawDalvikBytecodeOperation;
370
+ }
371
+
372
+ if ('targetInstructionIndices' in instruction) {
373
+ const { targetInstructionIndices, ...rest } = instruction;
374
+ // For payloads, the offsets are relative to the switch instruction, not the payload
375
+ const switchIndex = payloadToSwitchIndex.get(index);
376
+ if (switchIndex === undefined) {
377
+ throw new Error(`No switch instruction found for payload at index ${index}`);
378
+ }
379
+ // Convert absolute indices to relative offsets from switch instruction
380
+ const branchOffsetsIndex = targetInstructionIndices.map(
381
+ target => isoInstructionIndex.wrap(target - switchIndex),
382
+ );
383
+ return {
384
+ ...rest,
385
+ branchOffsetsIndex,
386
+ } as ConvertedRawDalvikBytecodeOperation;
387
+ }
388
+
389
+ return instruction as ConvertedRawDalvikBytecodeOperation;
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Build mapping from instruction index to code unit offset.
395
+ * Works with any tier of operations.
396
+ */
397
+ export function buildIndexToCodeUnitMapGeneric(
398
+ instructions: Array<{ operation: string; data?: Uint8Array | number[] }>,
399
+ ): IndexToCodeUnitMap {
400
+ const map: IndexToCodeUnitMap = new Map();
401
+ let codeUnitOffset = 0;
402
+
403
+ for (let index = 0; index < instructions.length; index++) {
404
+ map.set(isoInstructionIndex.wrap(index), isoCodeUnit.wrap(codeUnitOffset));
405
+ codeUnitOffset += getOperationSizeInCodeUnits(instructions[index]);
406
+ }
407
+
408
+ // Map the end position
409
+ map.set(isoInstructionIndex.wrap(instructions.length), isoCodeUnit.wrap(codeUnitOffset));
410
+
411
+ return map;
412
+ }
413
+
414
+ /**
415
+ * Build mapping from instruction index to code unit offset for Tier 2 operations.
416
+ */
417
+ export function buildIndexToCodeUnitMapFromConverted(
418
+ instructions: ConvertedRawDalvikBytecodeOperation[],
419
+ ): IndexToCodeUnitMap {
420
+ return buildIndexToCodeUnitMapGeneric(instructions);
421
+ }
422
+
423
+ /**
424
+ * Build mapping from instruction index to code unit offset for Tier 3 operations.
425
+ */
426
+ export function buildIndexToCodeUnitMapFromResolved(
427
+ instructions: DalvikBytecodeOperation[],
428
+ ): IndexToCodeUnitMap {
429
+ return buildIndexToCodeUnitMapGeneric(instructions);
430
+ }
431
+
432
+ /**
433
+ * Find the switch instruction that references a payload at the given index,
434
+ * when branch offsets are in instruction indices (Tier 2).
435
+ */
436
+ function findSwitchInstructionForPayloadByInstructionIndex(
437
+ instructions: ConvertedRawDalvikBytecodeOperation[],
438
+ payloadIndex: InstructionIndex,
439
+ ): InstructionIndex {
440
+ for (let i = 0; i < instructions.length; i++) {
441
+ const inst = instructions[i];
442
+ if (
443
+ (inst.operation === 'packed-switch' || inst.operation === 'sparse-switch')
444
+ && 'branchOffsetIndex' in inst
445
+ && i + isoInstructionIndex.unwrap(inst.branchOffsetIndex) === isoInstructionIndex.unwrap(payloadIndex)
446
+ ) {
447
+ return isoInstructionIndex.wrap(i);
448
+ }
449
+ }
450
+
451
+ throw new Error(`No switch instruction found for payload at index ${isoInstructionIndex.unwrap(payloadIndex)}`);
452
+ }
453
+
454
+ /**
455
+ * Convert all branch offsets in instructions from instruction offsets back to code units.
456
+ * Tier 2 (InstructionIndex) -> Tier 1 (CodeUnit)
457
+ */
458
+ export function convertInstructionOffsetsToBranchOffsets(
459
+ instructions: ConvertedRawDalvikBytecodeOperation[],
460
+ ): RawDalvikBytecodeOperation[] {
461
+ const indexToCodeUnitMap = buildIndexToCodeUnitMapFromConverted(instructions);
462
+
463
+ return instructions.map((instruction, index) => {
464
+ // Handle single branchOffsetIndex
465
+ if ('branchOffsetIndex' in instruction) {
466
+ const { branchOffsetIndex, ...rest } = instruction;
467
+ return {
468
+ ...rest,
469
+ branchOffsetCodeUnit: convertInstructionOffsetToBranchOffset(
470
+ branchOffsetIndex,
471
+ isoInstructionIndex.wrap(index),
472
+ indexToCodeUnitMap,
473
+ ),
474
+ } as RawDalvikBytecodeOperation;
475
+ }
476
+
477
+ // Handle branchOffsetsIndex array
478
+ if ('branchOffsetsIndex' in instruction) {
479
+ const { branchOffsetsIndex, ...rest } = instruction;
480
+ // For payload instructions, find the referring switch instruction
481
+ const sourceIndex = findSwitchInstructionForPayloadByInstructionIndex(instructions, isoInstructionIndex.wrap(index));
482
+ return {
483
+ ...rest,
484
+ branchOffsetsCodeUnit: branchOffsetsIndex.map(offset =>
485
+ convertInstructionOffsetToBranchOffset(
486
+ offset,
487
+ sourceIndex,
488
+ indexToCodeUnitMap,
489
+ ),
490
+ ),
491
+ } as RawDalvikBytecodeOperation;
492
+ }
493
+
494
+ return instruction as RawDalvikBytecodeOperation;
495
+ });
496
+ }
@@ -2,7 +2,9 @@ import { type Iso } from 'monocle-ts';
2
2
  import {
3
3
  byteParser, intParser, longParser, shortParser, ubyteParser, uintParser, ushortParser,
4
4
  } from '../dalvikExecutableParser/typeParsers.js';
5
+ import { type CodeUnit, isoCodeUnit } from '../dalvikExecutableParser/typedNumbers.js';
5
6
  import { createElementParser } from '../elementParser.js';
7
+ import { createObjectParser } from '../objectParser.js';
6
8
  import { type Parser, setParserName } from '../parser.js';
7
9
  import { promiseCompose } from '../promiseCompose.js';
8
10
  import { createTupleParser } from '../tupleParser.js';
@@ -20,13 +22,13 @@ export const nibblesParser: Parser<[ number, number ], Uint8Array> = promiseComp
20
22
  setParserName(nibblesParser, 'nibblesParser');
21
23
 
22
24
  type DalvikBytecodeFormat10t = {
23
- branchOffset: number;
25
+ branchOffsetCodeUnit: CodeUnit;
24
26
  };
25
27
 
26
28
  export const dalvikBytecodeFormat10tParser: Parser<DalvikBytecodeFormat10t, Uint8Array> = promiseCompose(
27
29
  byteParser,
28
30
  branchOffset => ({
29
- branchOffset,
31
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
30
32
  }),
31
33
  );
32
34
 
@@ -86,7 +88,7 @@ export const dalvikBytecodeFormat12xParser: Parser<DalvikBytecodeFormat12x, Uint
86
88
  );
87
89
 
88
90
  type DalvikBytecodeFormat20t = {
89
- branchOffset: number;
91
+ branchOffsetCodeUnit: CodeUnit;
90
92
  };
91
93
 
92
94
  export const dalvikBytecodeFormat20tParser: Parser<DalvikBytecodeFormat20t, Uint8Array> = promiseCompose(
@@ -94,11 +96,8 @@ export const dalvikBytecodeFormat20tParser: Parser<DalvikBytecodeFormat20t, Uint
94
96
  ubyteParser,
95
97
  shortParser,
96
98
  ]),
97
- ([
98
- _zero,
99
- branchOffset,
100
- ]) => ({
101
- branchOffset,
99
+ ([ _zero, branchOffset ]) => ({
100
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
102
101
  }),
103
102
  );
104
103
 
@@ -149,7 +148,7 @@ export const dalvikBytecodeFormat21hParser: Parser<DalvikBytecodeFormat21h, Uint
149
148
  );
150
149
 
151
150
  type DalvikBytecodeFormat21t = {
152
- branchOffset: number;
151
+ branchOffsetCodeUnit: CodeUnit;
153
152
  registers: number[];
154
153
  };
155
154
 
@@ -162,7 +161,7 @@ export const createDalvikBytecodeFormat21tParser = (): Parser<DalvikBytecodeForm
162
161
  register0,
163
162
  branchOffset,
164
163
  ]) => ({
165
- branchOffset,
164
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
166
165
  registers: [
167
166
  register0,
168
167
  ],
@@ -269,7 +268,7 @@ export const createDalvikBytecodeFormat22sParser = (): Parser<DalvikBytecodeForm
269
268
  );
270
269
 
271
270
  type DalvikBytecodeFormat22t = {
272
- branchOffset: number;
271
+ branchOffsetCodeUnit: CodeUnit;
273
272
  registers: number[];
274
273
  };
275
274
 
@@ -285,7 +284,7 @@ export const createDalvikBytecodeFormat22tParser = (): Parser<DalvikBytecodeForm
285
284
  ],
286
285
  branchOffset,
287
286
  ]) => ({
288
- branchOffset,
287
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
289
288
  registers: [
290
289
  register0,
291
290
  register1,
@@ -337,13 +336,13 @@ export const dalvikBytecodeFormat23xParser: Parser<DalvikBytecodeFormat23x, Uint
337
336
  );
338
337
 
339
338
  type DalvikBytecodeFormat30t = {
340
- branchOffset: number;
339
+ branchOffsetCodeUnit: CodeUnit;
341
340
  };
342
341
 
343
342
  export const dalvikBytecodeFormat30tParser: Parser<DalvikBytecodeFormat30t, Uint8Array> = promiseCompose(
344
343
  intParser,
345
344
  branchOffset => ({
346
- branchOffset,
345
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
347
346
  }),
348
347
  );
349
348
 
@@ -394,7 +393,7 @@ export const createDalvikBytecodeFormat31cParser = <Index>({
394
393
  );
395
394
 
396
395
  type DalvikBytecodeFormat31t = {
397
- branchOffset: number;
396
+ branchOffsetCodeUnit: CodeUnit;
398
397
  registers: number[];
399
398
  };
400
399
 
@@ -407,7 +406,7 @@ export const dalvikBytecodeFormat31tParser: Parser<DalvikBytecodeFormat31t, Uint
407
406
  register0,
408
407
  branchOffset,
409
408
  ]) => ({
410
- branchOffset,
409
+ branchOffsetCodeUnit: isoCodeUnit.wrap(branchOffset),
411
410
  registers: [
412
411
  register0,
413
412
  ],
@@ -530,19 +529,10 @@ type DalvikBytecodeFormat20bc = {
530
529
  index: number;
531
530
  };
532
531
 
533
- export const dalvikBytecodeFormat20bcParser: Parser<DalvikBytecodeFormat20bc, Uint8Array> = promiseCompose(
534
- createTupleParser([
535
- ubyteParser,
536
- ushortParser,
537
- ]),
538
- ([
539
- kind,
540
- index,
541
- ]) => ({
542
- kind,
543
- index,
544
- }),
545
- );
532
+ export const dalvikBytecodeFormat20bcParser: Parser<DalvikBytecodeFormat20bc, Uint8Array> = createObjectParser({
533
+ kind: ubyteParser,
534
+ index: ushortParser,
535
+ });
546
536
 
547
537
  // Format 22cs: field access quick (deprecated)
548
538
  type DalvikBytecodeFormat22cs = {