@futpib/parser 1.0.1 → 1.0.3

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 (425) hide show
  1. package/.github/copilot-instructions.md +149 -0
  2. package/.github/workflows/copilot-setup-steps.yml +18 -0
  3. package/.github/workflows/main.yml +29 -8
  4. package/.yarn/releases/yarn-4.9.4.cjs +942 -0
  5. package/.yarnrc.yml +1 -1
  6. package/build/allSettledStream.js +32 -14
  7. package/build/allSettledStream.test.js +32 -0
  8. package/build/androidPackage.d.ts +39 -0
  9. package/build/androidPackageParser.d.ts +17 -0
  10. package/build/androidPackageParser.js +185 -0
  11. package/build/androidPackageParser.test.js +22 -0
  12. package/build/androidPackageUnparser.d.ts +4 -0
  13. package/build/androidPackageUnparser.js +94 -0
  14. package/build/androidPackageUnparser.test.js +26 -0
  15. package/build/arbitrarilySlicedAsyncInterable.d.ts +3 -1
  16. package/build/arbitrarilySlicedAsyncInterable.js +3 -3
  17. package/build/arbitrarilySlicedAsyncInterator.js +2 -1
  18. package/build/arbitraryDalvikBytecode.d.ts +4 -0
  19. package/build/arbitraryDalvikBytecode.js +640 -0
  20. package/build/arbitraryDalvikExecutable.d.ts +3 -0
  21. package/build/arbitraryDalvikExecutable.js +282 -0
  22. package/build/arbitraryDosDateTime.js +1 -0
  23. package/build/arbitraryZipStream.js +1 -1
  24. package/build/arrayParser.js +2 -2
  25. package/build/arrayParser.test.js +3 -3
  26. package/build/arrayUnparser.d.ts +1 -1
  27. package/build/backsmali.d.ts +3 -0
  28. package/build/backsmali.js +50 -0
  29. package/build/bsonParser.js +6 -2
  30. package/build/customInvariant.d.ts +2 -1
  31. package/build/customInvariant.js +4 -6
  32. package/build/dalvikBytecodeParser/formatParsers.d.ts +171 -0
  33. package/build/dalvikBytecodeParser/formatParsers.js +304 -0
  34. package/build/dalvikBytecodeParser/formatSizes.d.ts +34 -0
  35. package/build/dalvikBytecodeParser/formatSizes.js +34 -0
  36. package/build/dalvikBytecodeParser/operationFormats.d.ts +225 -0
  37. package/build/dalvikBytecodeParser/operationFormats.js +225 -0
  38. package/build/dalvikBytecodeParser.d.ts +1207 -0
  39. package/build/dalvikBytecodeParser.js +1289 -0
  40. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +152 -0
  41. package/build/dalvikBytecodeUnparser/formatUnparsers.js +225 -0
  42. package/build/dalvikBytecodeUnparser.d.ts +3 -0
  43. package/build/dalvikBytecodeUnparser.js +642 -0
  44. package/build/dalvikBytecodeUnparser.test.js +25 -0
  45. package/build/dalvikExecutable.d.ts +215 -0
  46. package/build/dalvikExecutable.js +56 -0
  47. package/build/dalvikExecutableParser/stringSyntaxParser.d.ts +4 -0
  48. package/build/dalvikExecutableParser/stringSyntaxParser.js +76 -0
  49. package/build/dalvikExecutableParser/typeParsers.d.ts +11 -0
  50. package/build/dalvikExecutableParser/typeParsers.js +39 -0
  51. package/build/dalvikExecutableParser/typedNumbers.d.ts +106 -0
  52. package/build/dalvikExecutableParser/typedNumbers.js +18 -0
  53. package/build/dalvikExecutableParser.d.ts +5 -0
  54. package/build/dalvikExecutableParser.js +1757 -0
  55. package/build/dalvikExecutableParser.test.js +72 -0
  56. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +275 -0
  57. package/build/dalvikExecutableUnparser/annotationUnparsers.d.ts +14 -0
  58. package/build/dalvikExecutableUnparser/annotationUnparsers.js +97 -0
  59. package/build/dalvikExecutableUnparser/poolBuilders.d.ts +49 -0
  60. package/build/dalvikExecutableUnparser/poolBuilders.js +140 -0
  61. package/build/dalvikExecutableUnparser/poolScanners.d.ts +4 -0
  62. package/build/dalvikExecutableUnparser/poolScanners.js +220 -0
  63. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +25 -0
  64. package/build/dalvikExecutableUnparser/sectionUnparsers.js +581 -0
  65. package/build/dalvikExecutableUnparser/utils.d.ts +10 -0
  66. package/build/dalvikExecutableUnparser/utils.js +108 -0
  67. package/build/dalvikExecutableUnparser.d.ts +4 -0
  68. package/build/dalvikExecutableUnparser.js +406 -0
  69. package/build/dalvikExecutableUnparser.test.js +31 -0
  70. package/build/debugLogInputParser.d.ts +4 -0
  71. package/build/debugLogInputParser.js +16 -0
  72. package/build/debugLogParser.js +14 -3
  73. package/build/disjunctionParser.d.ts +2 -1
  74. package/build/disjunctionParser.js +6 -4
  75. package/build/elementTerminatedArrayParser.d.ts +3 -0
  76. package/build/elementTerminatedArrayParser.js +18 -0
  77. package/build/elementTerminatedArrayParser.test.js +52 -0
  78. package/build/elementTerminatedSequenceArrayParser.d.ts +3 -0
  79. package/build/elementTerminatedSequenceArrayParser.js +32 -0
  80. package/build/elementTerminatedSequenceArrayParser.test.js +34 -0
  81. package/build/elementTerminatedSequenceParser.d.ts +3 -0
  82. package/build/elementTerminatedSequenceParser.js +27 -0
  83. package/build/elementTerminatedSequenceParser.test.js +34 -0
  84. package/build/endOfInputParser.d.ts +1 -1
  85. package/build/exactElementParser.js +10 -5
  86. package/build/exactElementSwitchParser.d.ts +3 -0
  87. package/build/exactElementSwitchParser.js +22 -0
  88. package/build/exactSequenceParser.d.ts +2 -1
  89. package/build/exactSequenceParser.js +12 -2
  90. package/build/fetchCid.d.ts +1 -0
  91. package/build/fetchCid.js +103 -0
  92. package/build/fetchCid.test.js +16 -0
  93. package/build/fixedLengthSequenceParser.d.ts +1 -0
  94. package/build/fixedLengthSequenceParser.js +18 -1
  95. package/build/fixedLengthSequenceParser.test.js +41 -0
  96. package/build/hasExecutable.d.ts +1 -0
  97. package/build/hasExecutable.js +8 -0
  98. package/build/highResolutionTimer.d.ts +16 -0
  99. package/build/highResolutionTimer.js +42 -0
  100. package/build/inputReader.d.ts +11 -0
  101. package/build/inputReader.js +37 -0
  102. package/build/inputReader.test.js +161 -8
  103. package/build/inputReaderState.d.ts +10 -0
  104. package/build/inputReaderState.js +16 -0
  105. package/build/inspect.d.ts +1 -0
  106. package/build/inspect.js +7 -0
  107. package/build/javaKeyStoreParser.test.js +8 -8
  108. package/build/jsonParser.d.ts +2 -0
  109. package/build/jsonParser.js +19 -22
  110. package/build/lazyMessageError.d.ts +48 -0
  111. package/build/lazyMessageError.js +53 -0
  112. package/build/lazyMessageError.test.d.ts +1 -0
  113. package/build/lazyMessageError.test.js +15 -0
  114. package/build/leb128Parser.d.ts +7 -0
  115. package/build/leb128Parser.js +82 -0
  116. package/build/leb128Parser.test.d.ts +1 -0
  117. package/build/leb128Parser.test.js +107 -0
  118. package/build/lookaheadParser.d.ts +2 -0
  119. package/build/lookaheadParser.js +14 -0
  120. package/build/negativeLookaheadParser.js +22 -16
  121. package/build/negativeLookaheadParser.test.d.ts +1 -0
  122. package/build/negativeLookaheadParser.test.js +30 -0
  123. package/build/noStackCaptureOverheadError.d.ts +4 -0
  124. package/build/noStackCaptureOverheadError.js +9 -0
  125. package/build/noStackCaptureOverheadError.test.d.ts +1 -0
  126. package/build/noStackCaptureOverheadError.test.js +15 -0
  127. package/build/nonEmptyArrayParser.d.ts +2 -0
  128. package/build/nonEmptyArrayParser.js +32 -0
  129. package/build/nonEmptyArrayParser.test.d.ts +1 -0
  130. package/build/nonEmptyArrayParser.test.js +17 -0
  131. package/build/optionalParser.js +2 -2
  132. package/build/parser.d.ts +11 -1
  133. package/build/parser.js +82 -32
  134. package/build/parser.test.js +141 -25
  135. package/build/parserAccessorParser.js +9 -1
  136. package/build/parserConsumedSequenceParser.d.ts +1 -1
  137. package/build/parserConsumedSequenceParser.js +21 -16
  138. package/build/parserContext.d.ts +22 -6
  139. package/build/parserContext.js +113 -57
  140. package/build/parserContext.test.js +33 -2
  141. package/build/parserCreatorCompose.d.ts +1 -0
  142. package/build/parserCreatorCompose.js +8 -2
  143. package/build/parserError.d.ts +605 -40
  144. package/build/parserError.js +98 -53
  145. package/build/parserImplementationInvariant.d.ts +1 -1
  146. package/build/parserImplementationInvariant.js +2 -2
  147. package/build/parserInputCompanion.d.ts +15 -0
  148. package/build/parserInputCompanion.js +38 -0
  149. package/build/promiseCompose.d.ts +1 -1
  150. package/build/promiseCompose.js +11 -1
  151. package/build/promiseSettled.d.ts +1 -0
  152. package/build/promiseSettled.js +4 -0
  153. package/build/separatedArrayParser.d.ts +2 -0
  154. package/build/separatedArrayParser.js +39 -0
  155. package/build/separatedArrayParser.test.d.ts +1 -0
  156. package/build/separatedArrayParser.test.js +21 -0
  157. package/build/separatedNonEmptyArrayParser.d.ts +2 -0
  158. package/build/separatedNonEmptyArrayParser.js +40 -0
  159. package/build/separatedNonEmptyArrayParser.test.d.ts +1 -0
  160. package/build/separatedNonEmptyArrayParser.test.js +66 -0
  161. package/build/sequenceBuffer.d.ts +10 -0
  162. package/build/sequenceBuffer.js +54 -2
  163. package/build/sequenceBuffer.test.js +57 -0
  164. package/build/sequenceTerminatedSequenceParser.d.ts +5 -0
  165. package/build/sequenceTerminatedSequenceParser.js +32 -0
  166. package/build/sequenceTerminatedSequenceParser.test.d.ts +1 -0
  167. package/build/sequenceTerminatedSequenceParser.test.js +37 -0
  168. package/build/sequenceUnparser.d.ts +1 -1
  169. package/build/skipParser.d.ts +1 -1
  170. package/build/skipParser.js +4 -2
  171. package/build/skipToParser.d.ts +2 -0
  172. package/build/skipToParser.js +11 -0
  173. package/build/sliceBoundedParser.d.ts +1 -1
  174. package/build/sliceBoundedParser.js +7 -2
  175. package/build/sliceBoundedParser.test.js +30 -1
  176. package/build/smali.d.ts +1 -0
  177. package/build/smali.js +21 -0
  178. package/build/smaliParser.d.ts +68 -0
  179. package/build/smaliParser.js +2081 -0
  180. package/build/smaliParser.test.d.ts +1 -0
  181. package/build/smaliParser.test.js +410 -0
  182. package/build/stringFromAsyncIterable.d.ts +1 -0
  183. package/build/stringFromAsyncIterable.js +7 -0
  184. package/build/terminatedArrayParser.d.ts +3 -1
  185. package/build/terminatedArrayParser.js +79 -2
  186. package/build/terminatedArrayParser.test.d.ts +1 -0
  187. package/build/terminatedArrayParser.test.js +131 -0
  188. package/build/toAsyncIterable.d.ts +1 -0
  189. package/build/toAsyncIterable.js +7 -0
  190. package/build/toAsyncIterator.d.ts +1 -0
  191. package/build/toAsyncIterator.js +33 -0
  192. package/build/tupleParser.d.ts +4 -0
  193. package/build/tupleParser.js +1 -5
  194. package/build/unionParser.d.ts +2 -1
  195. package/build/unionParser.js +29 -14
  196. package/build/unionParser.test.d.ts +1 -0
  197. package/build/unionParser.test.js +60 -0
  198. package/build/unparser.d.ts +3 -3
  199. package/build/unparser.js +6 -4
  200. package/build/unparser.test.js +7 -19
  201. package/build/unparserContext.d.ts +2 -2
  202. package/build/unparserContext.js +2 -3
  203. package/build/unparserError.d.ts +2 -1
  204. package/build/unparserError.js +2 -1
  205. package/build/unparserImplementationInvariant.d.ts +1 -1
  206. package/build/unparserOutputCompanion.d.ts +1 -1
  207. package/build/unparserOutputCompanion.js +1 -1
  208. package/build/zipParser.d.ts +7 -2
  209. package/build/zipParser.js +36 -12
  210. package/build/zipUnparser.d.ts +7 -4
  211. package/build/zipUnparser.js +64 -45
  212. package/build/zipUnparser.test.js +15 -15
  213. package/package.json +33 -24
  214. package/src/allSettledStream.test.ts +40 -0
  215. package/src/allSettledStream.ts +47 -15
  216. package/src/androidPackage.ts +48 -0
  217. package/src/androidPackageParser.test.ts +27 -0
  218. package/src/{apkParser.test.ts.md → androidPackageParser.test.ts.md} +4 -4
  219. package/src/androidPackageParser.test.ts.snap +0 -0
  220. package/src/androidPackageParser.ts +398 -0
  221. package/src/androidPackageUnparser.test.ts +34 -0
  222. package/src/androidPackageUnparser.ts +126 -0
  223. package/src/arbitrarilySlicedAsyncInterable.ts +7 -2
  224. package/src/arbitrarilySlicedAsyncInterator.ts +4 -4
  225. package/src/arbitraryDalvikBytecode.ts +992 -0
  226. package/src/arbitraryDalvikExecutable.ts +434 -0
  227. package/src/arbitraryDosDateTime.ts +1 -0
  228. package/src/arbitraryZipStream.ts +1 -1
  229. package/src/arrayParser.test.ts +3 -3
  230. package/src/arrayParser.ts +2 -2
  231. package/src/arrayUnparser.ts +2 -2
  232. package/src/backsmali.ts +74 -0
  233. package/src/bsonParser.test.ts +12 -14
  234. package/src/bsonParser.ts +13 -2
  235. package/src/customInvariant.ts +8 -12
  236. package/src/dalvikBytecodeParser/formatParsers.ts +780 -0
  237. package/src/dalvikBytecodeParser/formatSizes.ts +35 -0
  238. package/src/dalvikBytecodeParser/operationFormats.ts +226 -0
  239. package/src/dalvikBytecodeParser.ts +2873 -0
  240. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +442 -0
  241. package/src/dalvikBytecodeUnparser.test.ts +44 -0
  242. package/src/dalvikBytecodeUnparser.ts +758 -0
  243. package/src/dalvikExecutable.ts +282 -0
  244. package/src/dalvikExecutableParser/stringSyntaxParser.ts +145 -0
  245. package/src/dalvikExecutableParser/typeParsers.ts +74 -0
  246. package/src/dalvikExecutableParser/typedNumbers.ts +57 -0
  247. package/src/dalvikExecutableParser.test.ts +89 -0
  248. package/src/dalvikExecutableParser.test.ts.md +634 -0
  249. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  250. package/src/dalvikExecutableParser.ts +3245 -0
  251. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +363 -0
  252. package/src/dalvikExecutableUnparser/annotationUnparsers.ts +135 -0
  253. package/src/dalvikExecutableUnparser/poolBuilders.ts +189 -0
  254. package/src/dalvikExecutableUnparser/poolScanners.ts +297 -0
  255. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +683 -0
  256. package/src/dalvikExecutableUnparser/utils.ts +149 -0
  257. package/src/dalvikExecutableUnparser.test.ts +57 -0
  258. package/src/dalvikExecutableUnparser.ts +581 -0
  259. package/src/debugLogInputParser.ts +28 -0
  260. package/src/debugLogParser.ts +19 -3
  261. package/src/disjunctionParser.ts +12 -7
  262. package/src/elementTerminatedArrayParser.test.ts +99 -0
  263. package/src/elementTerminatedArrayParser.ts +31 -0
  264. package/src/elementTerminatedSequenceArrayParser.test.ts +52 -0
  265. package/src/elementTerminatedSequenceArrayParser.ts +52 -0
  266. package/src/elementTerminatedSequenceParser.test.ts +52 -0
  267. package/src/elementTerminatedSequenceParser.ts +43 -0
  268. package/src/endOfInputParser.ts +1 -1
  269. package/src/exactElementParser.ts +17 -11
  270. package/src/exactElementSwitchParser.ts +41 -0
  271. package/src/exactSequenceParser.ts +23 -2
  272. package/src/fetchCid.test.ts +20 -0
  273. package/src/fetchCid.ts +121 -0
  274. package/src/fixedLengthSequenceParser.test.ts +75 -0
  275. package/src/fixedLengthSequenceParser.ts +28 -1
  276. package/src/hasExecutable.ts +11 -0
  277. package/src/highResolutionTimer.ts +49 -0
  278. package/src/inputReader.test.ts +204 -8
  279. package/src/inputReader.ts +76 -3
  280. package/src/inputReaderState.ts +33 -0
  281. package/src/inspect.ts +9 -0
  282. package/src/javaKeyStoreParser.test.ts +12 -15
  283. package/src/javaKeyStoreParser.ts +2 -6
  284. package/src/jsonParser.test.ts +2 -4
  285. package/src/jsonParser.ts +46 -62
  286. package/src/lazyMessageError.test.ts +21 -0
  287. package/src/lazyMessageError.ts +88 -0
  288. package/src/leb128Parser.test.ts +173 -0
  289. package/src/leb128Parser.ts +125 -0
  290. package/src/lookaheadParser.ts +19 -0
  291. package/src/negativeLookaheadParser.test.ts +49 -0
  292. package/src/negativeLookaheadParser.ts +27 -15
  293. package/src/noStackCaptureOverheadError.test.ts +17 -0
  294. package/src/noStackCaptureOverheadError.ts +12 -0
  295. package/src/nonEmptyArrayParser.test.ts +21 -0
  296. package/src/nonEmptyArrayParser.ts +44 -0
  297. package/src/optionalParser.ts +3 -2
  298. package/src/parser.test.ts +203 -31
  299. package/src/parser.test.ts.md +34 -27
  300. package/src/parser.test.ts.snap +0 -0
  301. package/src/parser.ts +172 -45
  302. package/src/parserAccessorParser.ts +12 -2
  303. package/src/parserConsumedSequenceParser.ts +26 -17
  304. package/src/parserContext.test.ts +37 -2
  305. package/src/parserContext.ts +185 -79
  306. package/src/parserCreatorCompose.ts +20 -2
  307. package/src/parserError.ts +144 -61
  308. package/src/parserImplementationInvariant.ts +3 -3
  309. package/src/parserInputCompanion.ts +55 -0
  310. package/src/promiseCompose.ts +17 -3
  311. package/src/promiseSettled.ts +6 -0
  312. package/src/separatedArrayParser.test.ts +34 -0
  313. package/src/separatedArrayParser.ts +55 -0
  314. package/src/separatedNonEmptyArrayParser.test.ts +117 -0
  315. package/src/separatedNonEmptyArrayParser.ts +61 -0
  316. package/src/sequenceBuffer.test.ts +70 -0
  317. package/src/sequenceBuffer.ts +88 -2
  318. package/src/sequenceTerminatedSequenceParser.test.ts +58 -0
  319. package/src/sequenceTerminatedSequenceParser.ts +62 -0
  320. package/src/sequenceUnparser.ts +2 -2
  321. package/src/skipParser.ts +7 -5
  322. package/src/skipToParser.ts +16 -0
  323. package/src/sliceBoundedParser.test.ts +35 -1
  324. package/src/sliceBoundedParser.ts +19 -1
  325. package/src/smali.ts +29 -0
  326. package/src/smaliParser.test.ts +443 -0
  327. package/src/smaliParser.test.ts.md +3907 -0
  328. package/src/smaliParser.test.ts.snap +0 -0
  329. package/src/smaliParser.ts +3348 -0
  330. package/src/stringFromAsyncIterable.ts +9 -0
  331. package/src/terminatedArrayParser.test.ts +258 -0
  332. package/src/terminatedArrayParser.ts +109 -6
  333. package/src/toAsyncIterable.ts +7 -0
  334. package/src/toAsyncIterator.ts +48 -0
  335. package/src/tupleParser.ts +8 -5
  336. package/src/uint8Array.ts +2 -3
  337. package/src/unionParser.test.ts +78 -0
  338. package/src/unionParser.ts +47 -21
  339. package/src/unparser.test.ts +18 -34
  340. package/src/unparser.ts +13 -9
  341. package/src/unparserContext.ts +9 -13
  342. package/src/unparserError.ts +2 -1
  343. package/src/unparserImplementationInvariant.ts +1 -1
  344. package/src/unparserOutputCompanion.ts +1 -1
  345. package/src/zip.ts +2 -6
  346. package/src/zipParser.ts +71 -20
  347. package/src/zipUnparser.test.ts +19 -19
  348. package/src/zipUnparser.ts +139 -90
  349. package/tsconfig.json +7 -1
  350. package/xo.config.ts +15 -0
  351. package/.yarn/releases/yarn-4.5.3.cjs +0 -934
  352. package/build/apk.d.ts +0 -39
  353. package/build/apkParser.d.ts +0 -16
  354. package/build/apkParser.js +0 -164
  355. package/build/apkParser.test.js +0 -22
  356. package/build/apkUnparser.d.ts +0 -4
  357. package/build/apkUnparser.js +0 -90
  358. package/build/apkUnparser.test.js +0 -26
  359. package/build/arbitraryDosDate.d.ts +0 -2
  360. package/build/arbitraryDosDate.js +0 -8
  361. package/build/arbitraryZipEntry.d.ts +0 -3
  362. package/build/arbitraryZipEntry.js +0 -26
  363. package/build/createDisjunctionParser.d.ts +0 -2
  364. package/build/createDisjunctionParser.js +0 -47
  365. package/build/createExactParser.d.ts +0 -2
  366. package/build/createExactParser.js +0 -12
  367. package/build/createSequentialUnionParser.d.ts +0 -2
  368. package/build/createSequentialUnionParser.js +0 -69
  369. package/build/fixedLengthChunkParser.d.ts +0 -2
  370. package/build/fixedLengthChunkParser.js +0 -12
  371. package/build/fixedLengthParser.d.ts +0 -2
  372. package/build/fixedLengthParser.js +0 -12
  373. package/build/inputChunkBuffer.d.ts +0 -15
  374. package/build/inputChunkBuffer.js +0 -40
  375. package/build/inputChunkBuffer.test.js +0 -34
  376. package/build/inputCompanion.d.ts +0 -18
  377. package/build/inputCompanion.js +0 -28
  378. package/build/invariantDefined.d.ts +0 -1
  379. package/build/invariantDefined.js +0 -5
  380. package/build/invariantIdentity.d.ts +0 -3
  381. package/build/invariantIdentity.js +0 -5
  382. package/build/javaKeystoreParser.d.ts +0 -2
  383. package/build/javaKeystoreParser.js +0 -7
  384. package/build/jsonParser2.d.ts +0 -3
  385. package/build/jsonParser2.js +0 -52
  386. package/build/jsonParser2.test.js +0 -22
  387. package/build/negativeLookahead.d.ts +0 -2
  388. package/build/negativeLookahead.js +0 -18
  389. package/build/parserCompose.d.ts +0 -3
  390. package/build/parserCompose.js +0 -7
  391. package/build/parserImplementationInvariantInvariant.d.ts +0 -3
  392. package/build/parserImplementationInvariantInvariant.js +0 -15
  393. package/build/parserInvariant.d.ts +0 -4
  394. package/build/parserInvariant.js +0 -11
  395. package/build/promiseFish.d.ts +0 -1
  396. package/build/promiseFish.js +0 -3
  397. package/build/sequenceParser.d.ts +0 -3
  398. package/build/sequenceParser.js +0 -10
  399. package/build/terminatedSequenceParser.d.ts +0 -2
  400. package/build/terminatedSequenceParser.js +0 -24
  401. package/build/unparserInputCompanion.d.ts +0 -15
  402. package/build/unparserInputCompanion.js +0 -13
  403. package/build/zipEntry.d.ts +0 -28
  404. package/build/zipFile.d.ts +0 -32
  405. package/build/zipFileEntry.d.ts +0 -6
  406. package/src/apk.ts +0 -48
  407. package/src/apkParser.test.ts +0 -30
  408. package/src/apkParser.test.ts.snap +0 -0
  409. package/src/apkParser.ts +0 -392
  410. package/src/apkUnparser.test.ts +0 -37
  411. package/src/apkUnparser.ts +0 -120
  412. package/src/invariantDefined.ts +0 -6
  413. package/src/invariantIdentity.ts +0 -8
  414. /package/build/{apk.js → androidPackage.js} +0 -0
  415. /package/build/{apkParser.test.d.ts → androidPackageParser.test.d.ts} +0 -0
  416. /package/build/{apkUnparser.test.d.ts → androidPackageUnparser.test.d.ts} +0 -0
  417. /package/build/{arbitraryDosPermissions.d.ts → dalvikBytecodeUnparser.test.d.ts} +0 -0
  418. /package/build/{arbitraryDosPermissions.js → dalvikExecutableParser.test.d.ts} +0 -0
  419. /package/build/{inputChunkBuffer.test.d.ts → dalvikExecutableParserAgainstSmaliParser.test.d.ts} +0 -0
  420. /package/build/{jsonParser2.test.d.ts → dalvikExecutableUnparser.test.d.ts} +0 -0
  421. /package/build/{parserParsingInvariant.d.ts → elementTerminatedArrayParser.test.d.ts} +0 -0
  422. /package/build/{parserParsingInvariant.js → elementTerminatedSequenceArrayParser.test.d.ts} +0 -0
  423. /package/build/{zipEntry.js → elementTerminatedSequenceParser.test.d.ts} +0 -0
  424. /package/build/{zipFile.js → fetchCid.test.d.ts} +0 -0
  425. /package/build/{zipFileEntry.js → fixedLengthSequenceParser.test.d.ts} +0 -0
@@ -0,0 +1,3348 @@
1
+ import invariant from 'invariant';
2
+ import { type Simplify } from 'type-fest';
3
+ import { type DalvikBytecode, type DalvikBytecodeOperation, dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
4
+ import {
5
+ type DalvikExecutableAccessFlags, dalvikExecutableAccessFlagsDefault, type DalvikExecutableAnnotation, type DalvikExecutableClassAnnotations, type DalvikExecutableClassData, type DalvikExecutableClassDefinition, type DalvikExecutableClassMethodAnnotation, type DalvikExecutableClassParameterAnnotation, type DalvikExecutableCode, type DalvikExecutableDebugInfo, type DalvikExecutableEncodedValue, type DalvikExecutableField, dalvikExecutableFieldEquals, type DalvikExecutableFieldWithAccess, type DalvikExecutableMethod, dalvikExecutableMethodEquals, type DalvikExecutableMethodWithAccess, type DalvikExecutablePrototype, isDalvikExecutableField, isDalvikExecutableMethod,
6
+ } from './dalvikExecutable.js';
7
+ import { createExactSequenceParser } from './exactSequenceParser.js';
8
+ import { cloneParser, type Parser, setParserName } from './parser.js';
9
+ import { type ParserContext } from './parserContext.js';
10
+ import { promiseCompose } from './promiseCompose.js';
11
+ import { createTupleParser } from './tupleParser.js';
12
+ import { createUnionParser } from './unionParser.js';
13
+ import { createArrayParser } from './arrayParser.js';
14
+ import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
15
+ import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
16
+ import { createOptionalParser } from './optionalParser.js';
17
+ import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
18
+ import { createSeparatedArrayParser } from './separatedArrayParser.js';
19
+ import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
20
+ import { createDisjunctionParser } from './disjunctionParser.js';
21
+ import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
22
+ import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
23
+ import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
24
+ import { parserCreatorCompose } from './parserCreatorCompose.js';
25
+ import { type IndexIntoMethodIds } from './dalvikExecutableParser/typedNumbers.js';
26
+ import { createDebugLogInputParser } from './debugLogInputParser.js';
27
+ import { createDebugLogParser } from './debugLogParser.js';
28
+ import { createElementParser } from './elementParser.js';
29
+ import { createTerminatedArrayParser } from './terminatedArrayParser.js';
30
+ import { createParserAccessorParser } from './parserAccessorParser.js';
31
+
32
+ function shortyFromLongy(longy: string): string {
33
+ if (longy.startsWith('[')) {
34
+ return 'L';
35
+ }
36
+
37
+ return longy.slice(0, 1);
38
+ }
39
+
40
+ function getOperationFormatSize(operation: SmaliCodeOperation): number {
41
+ if (operation.operation === 'packed-switch-payload') {
42
+ return (operation.branchOffsetIndices.length * 2) + 4;
43
+ }
44
+
45
+ if (operation.operation === 'sparse-switch-payload') {
46
+ return (operation.branchOffsetIndices.length * 4) + 2;
47
+ }
48
+
49
+ if (operation.operation === 'fill-array-data-payload') {
50
+ const dataSize = operation.data.length; // in bytes
51
+ const paddingSize = dataSize % 2; // 1 if odd, 0 if even
52
+ const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
53
+ return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
54
+ }
55
+
56
+ const operationFormat = operationFormats[operation.operation as keyof typeof operationFormats];
57
+ invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
58
+
59
+ const operationSize = formatSizes[operationFormat];
60
+ invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
61
+
62
+ return operationSize;
63
+ }
64
+
65
+ // Helper function to convert raw annotation element values to tagged encoded values
66
+ function convertToTaggedEncodedValue(wrappedValue: SmaliAnnotationElementValue): DalvikExecutableEncodedValue {
67
+ const { kind, value } = wrappedValue;
68
+
69
+ // Handle type descriptors
70
+ if (kind === 'type') {
71
+ if (Array.isArray(value)) {
72
+ // Array of type descriptors
73
+ return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
74
+ }
75
+ // Single type descriptor
76
+ return { type: 'type', value };
77
+ }
78
+
79
+ // Handle string literals
80
+ if (kind === 'string') {
81
+ if (Array.isArray(value)) {
82
+ // Array of strings
83
+ return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
84
+ }
85
+ // Single string
86
+ return { type: 'string', value };
87
+ }
88
+
89
+ // Handle enum values
90
+ if (kind === 'enum') {
91
+ if (Array.isArray(value)) {
92
+ // Array of enum values
93
+ return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
94
+ }
95
+ // Single enum value
96
+ return { type: 'enum', value };
97
+ }
98
+
99
+ // Handle raw values (everything else)
100
+ // Handle null
101
+ if (value === null) {
102
+ return { type: 'null', value: null };
103
+ }
104
+
105
+ // Handle boolean
106
+ if (typeof value === 'boolean') {
107
+ return { type: 'boolean', value };
108
+ }
109
+
110
+ // Handle numbers - we need to determine the type based on context
111
+ // For annotation elements from smali, we'll use 'int' as the default
112
+ if (typeof value === 'number') {
113
+ // Check if it's a float or integer
114
+ if (!Number.isInteger(value)) {
115
+ return { type: 'float', value };
116
+ }
117
+ // For integers, default to 'int' type
118
+ // The actual byte/short/int distinction would need more context
119
+ return { type: 'int', value };
120
+ }
121
+
122
+ // Handle bigint
123
+ if (typeof value === 'bigint') {
124
+ return { type: 'long', value };
125
+ }
126
+
127
+ // Handle DalvikExecutableField (including enums)
128
+ if (isDalvikExecutableField(value)) {
129
+ // Note: We can't distinguish between 'field' and 'enum' without more context
130
+ // Default to 'field' type - the context in smali might help distinguish later
131
+ return { type: 'field', value };
132
+ }
133
+
134
+ // Handle DalvikExecutableMethod
135
+ if (isDalvikExecutableMethod(value)) {
136
+ return { type: 'method', value };
137
+ }
138
+
139
+ // Handle DalvikExecutablePrototype (method type)
140
+ if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
141
+ return { type: 'methodType', value: value as DalvikExecutablePrototype };
142
+ }
143
+
144
+ // Handle annotations (subannotations)
145
+ if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
146
+ const subannotation = value as SmaliSubannotation;
147
+ // Convert subannotation to DalvikExecutableAnnotation
148
+ const annotation: DalvikExecutableAnnotation = {
149
+ type: subannotation.type,
150
+ visibility: 'build', // Subannotations default to 'build' visibility
151
+ elements: subannotation.elements.map(element => ({
152
+ name: element.name,
153
+ value: convertToTaggedEncodedValue(element.value),
154
+ })),
155
+ };
156
+ return { type: 'annotation', value: annotation };
157
+ }
158
+
159
+ // Handle arrays
160
+ if (Array.isArray(value)) {
161
+ return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
162
+ }
163
+
164
+ // Fallback - this shouldn't happen in well-formed smali
165
+ throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
166
+ }
167
+
168
+
169
+ const smaliNewlinesParser: Parser<void, string> = promiseCompose(
170
+ createNonEmptyArrayParser(createExactSequenceParser('\n')),
171
+ _newlines => undefined,
172
+ );
173
+
174
+ const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
175
+
176
+ const smaliWhitespaceParser: Parser<void, string> = promiseCompose(
177
+ createArrayParser(smaliSingleWhitespaceParser),
178
+ _indentation => undefined,
179
+ );
180
+
181
+ const smaliSingleIndentationParser = createExactSequenceParser(' ');
182
+
183
+ const smaliIndentationParser: Parser<void, string> = promiseCompose(
184
+ createArrayParser(smaliSingleIndentationParser),
185
+ _indentation => undefined,
186
+ );
187
+
188
+ export const smaliCommentParser: Parser<string, string> = promiseCompose(
189
+ createTupleParser([
190
+ createExactSequenceParser('#'),
191
+ (async (parserContext: ParserContext<string, string>) => {
192
+ const characters: string[] = [];
193
+
194
+ while (true) {
195
+ const character = await parserContext.peek(0);
196
+
197
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
198
+
199
+ invariant(character !== undefined, 'Unexpected end of input');
200
+
201
+ if (character !== '\n') {
202
+ characters.push(character);
203
+
204
+ parserContext.skip(1);
205
+
206
+ continue;
207
+ }
208
+
209
+ parserContext.skip(1);
210
+
211
+ break;
212
+ }
213
+
214
+ return characters.join('');
215
+ }),
216
+ ]),
217
+ ([
218
+ _hash,
219
+ comment,
220
+ ]) => comment,
221
+ );
222
+
223
+ const smaliIndentedCommentParser: Parser<string, string> = promiseCompose(
224
+ createTupleParser([
225
+ createNonEmptyArrayParser(smaliSingleIndentationParser),
226
+ smaliCommentParser,
227
+ ]),
228
+ ([
229
+ _indentation,
230
+ comment,
231
+ ]) => comment,
232
+ );
233
+
234
+ const smaliCommentsOrNewlinesParser: Parser<string[], string> = promiseCompose(
235
+ createArrayParser(createUnionParser<undefined | string, string, string>([
236
+ smaliNewlinesParser,
237
+ smaliIndentedCommentParser,
238
+ smaliCommentParser,
239
+ ])),
240
+ newlinesOrComments => newlinesOrComments.filter((newlineOrComment): newlineOrComment is string => typeof newlineOrComment === 'string'),
241
+ );
242
+
243
+ const smaliLineEndPraser: Parser<undefined | string, string> = promiseCompose(
244
+ createTupleParser([
245
+ createOptionalParser(smaliWhitespaceParser),
246
+ createUnionParser<undefined | string, string, string>([
247
+ smaliNewlinesParser,
248
+ smaliCommentParser,
249
+ ]),
250
+ ]),
251
+ ([
252
+ _optionalWhitespace,
253
+ newlineOrComment,
254
+ ]) => newlineOrComment,
255
+ );
256
+
257
+ const smaliIdentifierParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
258
+ const characters: string[] = [];
259
+
260
+ while (true) {
261
+ const character = await parserContext.peek(0);
262
+
263
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
264
+
265
+ invariant(character !== undefined, 'Unexpected end of input');
266
+
267
+ if (
268
+ character === '_'
269
+ || (
270
+ character >= 'a' && character <= 'z'
271
+ )
272
+ || (
273
+ character >= 'A' && character <= 'Z'
274
+ )
275
+ || (
276
+ character >= '0' && character <= '9'
277
+ )
278
+ ) {
279
+ parserContext.skip(1);
280
+
281
+ characters.push(character);
282
+
283
+ continue;
284
+ }
285
+
286
+ parserContext.invariant(characters.length > 0, 'Expected at least one character');
287
+
288
+ break;
289
+ }
290
+
291
+ return characters.join('');
292
+ };
293
+
294
+ setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
295
+
296
+ const elementParser = createElementParser<string>();
297
+
298
+ const smaliHexNumberParser: Parser<number | bigint, string> = promiseCompose(
299
+ createTupleParser([
300
+ createOptionalParser(createExactSequenceParser('-')),
301
+ createExactSequenceParser('0x'),
302
+ createArrayParser(parserCreatorCompose(
303
+ () => elementParser,
304
+ character => async parserContext => {
305
+ parserContext.invariant(
306
+ (
307
+ (character >= '0' && character <= '9')
308
+ || (character >= 'a' && character <= 'f')
309
+ || (character >= 'A' && character <= 'F')
310
+ ),
311
+ 'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"',
312
+ character,
313
+ );
314
+
315
+ return character;
316
+ },
317
+ )()),
318
+ createOptionalParser(createExactSequenceParser('L')),
319
+ ]),
320
+ ([
321
+ optionalMinus,
322
+ _0x,
323
+ valueCharacters,
324
+ optionalL,
325
+ ]) => {
326
+ const value = valueCharacters.join('');
327
+
328
+ // If the 'L' suffix is present, use BigInt for long values
329
+ if (optionalL) {
330
+ const sign = optionalMinus ? -1n : 1n;
331
+ return sign * BigInt('0x' + value);
332
+ }
333
+
334
+ const sign = optionalMinus ? -1 : 1;
335
+ return sign * Number.parseInt(value, 16);
336
+ },
337
+ );
338
+
339
+ const smaliNumberParser = createUnionParser<number, string>([
340
+ promiseCompose(
341
+ createTupleParser([
342
+ createNegativeLookaheadParser(createUnionParser([
343
+ createExactSequenceParser('0x'),
344
+ createExactSequenceParser('-0x'),
345
+ ])),
346
+ jsonNumberParser,
347
+ createOptionalParser(createUnionParser([
348
+ createExactSequenceParser('f'),
349
+ createExactSequenceParser('F'),
350
+ ])),
351
+ ]),
352
+ ([
353
+ _not0x,
354
+ number,
355
+ optionalFloatSuffix,
356
+ ]) => {
357
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
358
+ // to match what would be stored in a DEX file
359
+ if (optionalFloatSuffix) {
360
+ const float32Array = new Float32Array(1);
361
+ float32Array[0] = number;
362
+ return float32Array[0];
363
+ }
364
+
365
+ return number;
366
+ },
367
+ ),
368
+ promiseCompose(
369
+ smaliHexNumberParser,
370
+ value => {
371
+ // For smaliNumberParser, we need to ensure we return a number
372
+ // BigInt values from hex parser should be converted if they fit in number range
373
+ if (typeof value === 'bigint') {
374
+ // This shouldn't happen in contexts where smaliNumberParser is used
375
+ // (registers, line numbers, etc) but if it does, convert to number
376
+ return Number(value);
377
+ }
378
+ return value;
379
+ },
380
+ ),
381
+ ]);
382
+
383
+ setParserName(smaliNumberParser, 'smaliNumberParser');
384
+
385
+ // Parser for field initial values that can include BigInt
386
+ const smaliFieldValueParser = createUnionParser<number | bigint, string>([
387
+ promiseCompose(
388
+ createTupleParser([
389
+ createNegativeLookaheadParser(createUnionParser([
390
+ createExactSequenceParser('0x'),
391
+ createExactSequenceParser('-0x'),
392
+ ])),
393
+ jsonNumberParser,
394
+ createOptionalParser(createUnionParser([
395
+ createExactSequenceParser('f'),
396
+ createExactSequenceParser('F'),
397
+ ])),
398
+ ]),
399
+ ([
400
+ _not0x,
401
+ number,
402
+ optionalFloatSuffix,
403
+ ]) => {
404
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
405
+ // to match what would be stored in a DEX file
406
+ if (optionalFloatSuffix) {
407
+ const float32Array = new Float32Array(1);
408
+ float32Array[0] = number;
409
+ return float32Array[0];
410
+ }
411
+
412
+ return number;
413
+ },
414
+ ),
415
+ smaliHexNumberParser,
416
+ ]);
417
+
418
+ const smaliQuotedStringParser: Parser<string, string> = promiseCompose(
419
+ jsonStringParser,
420
+ string => string.replaceAll(String.raw`\'`, '\''),
421
+ );
422
+
423
+ // Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
424
+ const smaliCharacterLiteralParser: Parser<number, string> = promiseCompose(
425
+ createTupleParser([
426
+ createExactSequenceParser('\''),
427
+ createDisjunctionParser<string, string>([
428
+ // Handle escape sequences (must come before regular characters)
429
+ promiseCompose(createExactSequenceParser(String.raw`\\`), () => '\\'),
430
+ promiseCompose(createExactSequenceParser(String.raw`\'`), () => '\''),
431
+ promiseCompose(createExactSequenceParser(String.raw`\"`), () => '"'),
432
+ promiseCompose(createExactSequenceParser(String.raw`\n`), () => '\n'),
433
+ promiseCompose(createExactSequenceParser(String.raw`\r`), () => '\r'),
434
+ promiseCompose(createExactSequenceParser(String.raw`\t`), () => '\t'),
435
+ promiseCompose(createExactSequenceParser(String.raw`\b`), () => '\b'),
436
+ promiseCompose(createExactSequenceParser(String.raw`\f`), () => '\f'),
437
+ // Handle regular characters (not a single quote)
438
+ parserCreatorCompose(
439
+ () => createElementParser<string>(),
440
+ character => async parserContext => {
441
+ parserContext.invariant(character !== '\'', 'Unexpected single quote');
442
+ return character;
443
+ },
444
+ )(),
445
+ ]),
446
+ createExactSequenceParser('\''),
447
+ ]),
448
+ ([, character]) => character.charCodeAt(0),
449
+ );
450
+
451
+ setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
452
+
453
+ // Parser that matches identifier continuation characters (letters, digits, $, -, _)
454
+ const smaliIdentifierContinuationParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
455
+ const character = await parserContext.peek(0);
456
+
457
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
458
+
459
+ invariant(character !== undefined, 'Unexpected end of input');
460
+
461
+ parserContext.invariant(
462
+ (character >= 'a' && character <= 'z')
463
+ || (character >= 'A' && character <= 'Z')
464
+ || (character >= '0' && character <= '9')
465
+ || character === '$'
466
+ || character === '-'
467
+ || character === '_',
468
+ 'Expected identifier continuation character, got "%s"',
469
+ character,
470
+ );
471
+
472
+ parserContext.skip(1);
473
+
474
+ return character;
475
+ };
476
+
477
+ setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
478
+
479
+ // Helper to create an access flag parser with word boundary check
480
+ const createAccessFlagParser = (keyword: string): Parser<typeof keyword, string> => promiseCompose(
481
+ createTupleParser([
482
+ createExactSequenceParser(keyword),
483
+ createNegativeLookaheadParser(smaliIdentifierContinuationParser),
484
+ ]),
485
+ ([flag]) => flag,
486
+ );
487
+
488
+ const smaliAccessFlagsParser: Parser<DalvikExecutableAccessFlags, string> = promiseCompose(
489
+ createSeparatedArrayParser(
490
+ createUnionParser<keyof DalvikExecutableAccessFlags | 'declared-synchronized', string>([
491
+ createAccessFlagParser('public'),
492
+ createAccessFlagParser('protected'),
493
+ createAccessFlagParser('private'),
494
+ createAccessFlagParser('final'),
495
+ createAccessFlagParser('bridge'),
496
+ createAccessFlagParser('synthetic'),
497
+ createAccessFlagParser('varargs'),
498
+ createAccessFlagParser('static'),
499
+ createAccessFlagParser('constructor'),
500
+ createAccessFlagParser('abstract'),
501
+ createAccessFlagParser('native'),
502
+ createAccessFlagParser('volatile'),
503
+ createAccessFlagParser('transient'),
504
+ createAccessFlagParser('synchronized'),
505
+ createAccessFlagParser('declared-synchronized'),
506
+ createAccessFlagParser('strict'),
507
+ createAccessFlagParser('interface'),
508
+ createAccessFlagParser('annotation'),
509
+ createAccessFlagParser('enum'),
510
+ ]),
511
+ smaliSingleWhitespaceParser,
512
+ ),
513
+ accessFlagNames => {
514
+ const accessFlags = dalvikExecutableAccessFlagsDefault();
515
+
516
+ for (const accessFlagName of accessFlagNames) {
517
+ if (accessFlagName === 'declared-synchronized') {
518
+ accessFlags.declaredSynchronized = true;
519
+ } else {
520
+ accessFlags[accessFlagName] = true;
521
+ }
522
+ }
523
+
524
+ return accessFlags;
525
+ },
526
+ );
527
+
528
+ const smaliClassDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'accessFlags' | 'class'>, string> = promiseCompose(
529
+ createTupleParser([
530
+ smaliCommentsOrNewlinesParser,
531
+ createExactSequenceParser('.class '),
532
+ createOptionalParser(promiseCompose(
533
+ createTupleParser([
534
+ smaliAccessFlagsParser,
535
+ smaliSingleWhitespaceParser,
536
+ ]),
537
+ ([
538
+ accessFlags,
539
+ ]) => accessFlags,
540
+ )),
541
+ smaliTypeDescriptorParser,
542
+ smaliLineEndPraser,
543
+ ]),
544
+ ([
545
+ _commentsOrNewlines,
546
+ _dotClass,
547
+ accessFlags = dalvikExecutableAccessFlagsDefault(),
548
+ classPath,
549
+ ]) => ({
550
+ accessFlags,
551
+ class: classPath,
552
+ }),
553
+ );
554
+
555
+ setParserName(smaliClassDeclarationParser, 'smaliClassDeclarationParser');
556
+
557
+ const smaliSuperDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'superclass'>, string> = promiseCompose(
558
+ createTupleParser([
559
+ createExactSequenceParser('.super '),
560
+ smaliTypeDescriptorParser,
561
+ smaliLineEndPraser,
562
+ ]),
563
+ ([
564
+ _super,
565
+ superclass,
566
+ _newline,
567
+ ]) => ({
568
+ superclass,
569
+ }),
570
+ );
571
+
572
+ setParserName(smaliSuperDeclarationParser, 'smaliSuperDeclarationParser');
573
+
574
+ const smaliInterfaceDeclarationParser: Parser<string, string> = promiseCompose(
575
+ createTupleParser([
576
+ createExactSequenceParser('.implements '),
577
+ smaliTypeDescriptorParser,
578
+ smaliLineEndPraser,
579
+ ]),
580
+ ([
581
+ _interface,
582
+ interface_,
583
+ _newline,
584
+ ]) => interface_,
585
+ );
586
+
587
+ setParserName(smaliInterfaceDeclarationParser, 'smaliInterfaceDeclarationParser');
588
+
589
+ const smaliSourceDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'sourceFile'>, string> = promiseCompose(
590
+ createTupleParser([
591
+ createExactSequenceParser('.source '),
592
+ smaliQuotedStringParser,
593
+ smaliLineEndPraser,
594
+ ]),
595
+ ([
596
+ _source,
597
+ sourceFile,
598
+ _newline,
599
+ ]) => ({
600
+ sourceFile,
601
+ }),
602
+ );
603
+
604
+ // Wrapper type to distinguish different value types in smali annotation elements
605
+ type SmaliAnnotationElementValue =
606
+ | { kind: 'type'; value: string | string[] }
607
+ | { kind: 'string'; value: string | string[] }
608
+ | { kind: 'enum'; value: DalvikExecutableField | DalvikExecutableField[] }
609
+ | { kind: 'raw'; value: unknown };
610
+
611
+ type SmaliAnnotationElement = {
612
+ name: string;
613
+ value: SmaliAnnotationElementValue;
614
+ };
615
+
616
+ const smaliEnumValueParser: Parser<DalvikExecutableField, string> = promiseCompose(
617
+ createTupleParser([
618
+ createExactSequenceParser('.enum '),
619
+ smaliTypeDescriptorParser,
620
+ createExactSequenceParser('->'),
621
+ smaliMemberNameParser,
622
+ createExactSequenceParser(':'),
623
+ smaliTypeDescriptorParser,
624
+ ]),
625
+ ([
626
+ _enum,
627
+ classType,
628
+ _arrow,
629
+ fieldName,
630
+ _colon,
631
+ fieldType,
632
+ ]) => ({
633
+ class: classType,
634
+ type: fieldType,
635
+ name: fieldName,
636
+ }),
637
+ );
638
+
639
+ setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
640
+
641
+ const smaliMethodPrototypeParser: Parser<DalvikExecutablePrototype, string> = promiseCompose(
642
+ createTupleParser([
643
+ createExactSequenceParser('('),
644
+ createArrayParser(smaliTypeDescriptorParser),
645
+ createExactSequenceParser(')'),
646
+ smaliTypeDescriptorParser,
647
+ ]),
648
+ ([
649
+ _openParenthesis,
650
+ parameters,
651
+ _closeParenthesis,
652
+ returnType,
653
+ ]) => ({
654
+ parameters,
655
+ returnType,
656
+ shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
657
+ if (parameter === 'V') {
658
+ return '';
659
+ }
660
+
661
+ return shortyFromLongy(parameter);
662
+ }).join(''),
663
+ }),
664
+ );
665
+
666
+ setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
667
+
668
+ const smaliParametersMethodParser: Parser<DalvikExecutableMethod, string> = promiseCompose(
669
+ createTupleParser([
670
+ smaliTypeDescriptorParser,
671
+ createExactSequenceParser('->'),
672
+ smaliMemberNameParser,
673
+ smaliMethodPrototypeParser,
674
+ ]),
675
+ ([
676
+ classPath,
677
+ _separator,
678
+ methodName,
679
+ prototype,
680
+ ]) => ({
681
+ class: classPath,
682
+ prototype,
683
+ name: methodName,
684
+ }),
685
+ );
686
+
687
+ setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
688
+
689
+ type SmaliSubannotation = {
690
+ type: string;
691
+ elements: SmaliAnnotationElement[];
692
+ };
693
+
694
+ // Forward declaration to handle circular reference
695
+ let smaliSubannotationParser: Parser<SmaliSubannotation, string>;
696
+
697
+ const smaliAnnotationElementParser: Parser<SmaliAnnotationElement, string> = promiseCompose(
698
+ createTupleParser([
699
+ smaliIndentationParser,
700
+ smaliIdentifierParser,
701
+ createExactSequenceParser(' = '),
702
+ createDisjunctionParser([
703
+ promiseCompose(
704
+ createParserAccessorParser(() => smaliSubannotationParser),
705
+ value => ({ kind: 'raw' as const, value }),
706
+ ),
707
+ promiseCompose(
708
+ smaliEnumValueParser,
709
+ value => ({ kind: 'enum' as const, value }),
710
+ ),
711
+ promiseCompose(
712
+ smaliQuotedStringParser,
713
+ value => ({ kind: 'string' as const, value }),
714
+ ),
715
+ promiseCompose(
716
+ smaliParametersMethodParser,
717
+ value => ({ kind: 'raw' as const, value }),
718
+ ),
719
+ promiseCompose(
720
+ smaliTypeDescriptorParser,
721
+ value => ({ kind: 'type' as const, value }),
722
+ ),
723
+ promiseCompose(
724
+ smaliNumberParser,
725
+ value => ({ kind: 'raw' as const, value }),
726
+ ),
727
+ promiseCompose(
728
+ createExactSequenceParser('true'),
729
+ () => ({ kind: 'raw' as const, value: true }),
730
+ ),
731
+ promiseCompose(
732
+ createExactSequenceParser('false'),
733
+ () => ({ kind: 'raw' as const, value: false }),
734
+ ),
735
+ promiseCompose(
736
+ createExactSequenceParser('null'),
737
+ () => ({ kind: 'raw' as const, value: null }),
738
+ ),
739
+ promiseCompose(
740
+ createExactSequenceParser('{}'),
741
+ () => ({ kind: 'raw' as const, value: [] }),
742
+ ),
743
+ promiseCompose(
744
+ createTupleParser([
745
+ createExactSequenceParser('{\n'),
746
+ createSeparatedArrayParser(
747
+ promiseCompose(
748
+ createTupleParser([
749
+ smaliIndentationParser,
750
+ smaliEnumValueParser,
751
+ ]),
752
+ ([
753
+ _indentation,
754
+ value,
755
+ ]) => value,
756
+ ),
757
+ createExactSequenceParser(',\n'),
758
+ ),
759
+ smaliLineEndPraser,
760
+ smaliIndentationParser,
761
+ createExactSequenceParser('}'),
762
+ ]),
763
+ ([
764
+ _openBrace,
765
+ value,
766
+ _closeBrace,
767
+ ]) => ({ kind: 'enum' as const, value }),
768
+ ),
769
+ promiseCompose(
770
+ createTupleParser([
771
+ createExactSequenceParser('{\n'),
772
+ createSeparatedArrayParser(
773
+ promiseCompose(
774
+ createTupleParser([
775
+ smaliIndentationParser,
776
+ smaliTypeDescriptorParser,
777
+ ]),
778
+ ([
779
+ _indentation,
780
+ value,
781
+ ]) => value,
782
+ ),
783
+ createExactSequenceParser(',\n'),
784
+ ),
785
+ smaliLineEndPraser,
786
+ smaliIndentationParser,
787
+ createExactSequenceParser('}'),
788
+ ]),
789
+ ([
790
+ _openBrace,
791
+ value,
792
+ _closeBrace,
793
+ ]) => ({ kind: 'type' as const, value }),
794
+ ),
795
+ promiseCompose(
796
+ createTupleParser([
797
+ createExactSequenceParser('{\n'),
798
+ createSeparatedArrayParser(
799
+ promiseCompose(
800
+ createTupleParser([
801
+ smaliIndentationParser,
802
+ smaliQuotedStringParser,
803
+ ]),
804
+ ([
805
+ _indentation,
806
+ value,
807
+ ]) => value,
808
+ ),
809
+ createExactSequenceParser(',\n'),
810
+ ),
811
+ smaliLineEndPraser,
812
+ smaliIndentationParser,
813
+ createExactSequenceParser('}'),
814
+ ]),
815
+ ([
816
+ _openBrace,
817
+ value,
818
+ _closeBrace,
819
+ ]) => ({ kind: 'string' as const, value }),
820
+ ),
821
+ promiseCompose(
822
+ createTupleParser([
823
+ createExactSequenceParser('{\n'),
824
+ createSeparatedArrayParser(
825
+ promiseCompose(
826
+ createTupleParser([
827
+ smaliIndentationParser,
828
+ smaliNumberParser,
829
+ ]),
830
+ ([
831
+ _indentation,
832
+ value,
833
+ ]) => value,
834
+ ),
835
+ createExactSequenceParser(',\n'),
836
+ ),
837
+ smaliLineEndPraser,
838
+ smaliIndentationParser,
839
+ createExactSequenceParser('}'),
840
+ ]),
841
+ ([
842
+ _openBrace,
843
+ value,
844
+ _closeBrace,
845
+ ]) => ({ kind: 'raw' as const, value }),
846
+ ),
847
+ ]),
848
+ smaliLineEndPraser,
849
+ ]),
850
+ ([
851
+ _indentation,
852
+ name,
853
+ _equalsSign,
854
+ value,
855
+ _newline,
856
+ ]): SmaliAnnotationElement => ({
857
+ name,
858
+ value: value as SmaliAnnotationElementValue,
859
+ }),
860
+ );
861
+
862
+ setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
863
+
864
+ // Now define the subannotation parser
865
+ smaliSubannotationParser = promiseCompose(
866
+ createTupleParser([
867
+ createExactSequenceParser('.subannotation '),
868
+ smaliTypeDescriptorParser,
869
+ smaliLineEndPraser,
870
+ createArrayParser(smaliAnnotationElementParser),
871
+ smaliIndentationParser,
872
+ createExactSequenceParser('.end subannotation'),
873
+ ]),
874
+ ([
875
+ _subannotation,
876
+ type,
877
+ _newline,
878
+ elements,
879
+ _indentation,
880
+ _endSubannotation,
881
+ ]) => ({
882
+ type,
883
+ elements,
884
+ }),
885
+ );
886
+
887
+ setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
888
+
889
+ type SmaliAnnotation = {
890
+ type: string;
891
+ elements: SmaliAnnotationElement[];
892
+ visibility: 'build' | 'runtime' | 'system';
893
+ };
894
+
895
+ export const smaliAnnotationParser: Parser<SmaliAnnotation, string> = promiseCompose(
896
+ createTupleParser([
897
+ smaliIndentationParser,
898
+ createExactSequenceParser('.annotation '),
899
+ createUnionParser<'build' | 'runtime' | 'system', string, string>([
900
+ createExactSequenceParser('build'),
901
+ createExactSequenceParser('runtime'),
902
+ createExactSequenceParser('system'),
903
+ ]),
904
+ smaliSingleWhitespaceParser,
905
+ smaliTypeDescriptorParser,
906
+ smaliLineEndPraser,
907
+ createArrayParser(smaliAnnotationElementParser),
908
+ smaliIndentationParser,
909
+ createExactSequenceParser('.end annotation\n'),
910
+ ]),
911
+ ([
912
+ _indentation0,
913
+ _annotation,
914
+ visibility,
915
+ _space,
916
+ type,
917
+ _newline,
918
+ elements,
919
+ _indentation1,
920
+ _endAnnotation,
921
+ ]) => ({
922
+ type,
923
+ elements,
924
+ visibility,
925
+ }),
926
+ );
927
+
928
+ setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
929
+
930
+ type SmaliField = {
931
+ field: DalvikExecutableFieldWithAccess;
932
+ annotations: SmaliAnnotation[];
933
+ initialValue?: number | bigint | string | boolean | null;
934
+ };
935
+
936
+ export const smaliFieldParser: Parser<SmaliField, string> = promiseCompose(
937
+ createTupleParser([
938
+ createExactSequenceParser('.field '),
939
+ createOptionalParser(promiseCompose(
940
+ createTupleParser([
941
+ smaliAccessFlagsParser,
942
+ smaliSingleWhitespaceParser,
943
+ ]),
944
+ ([accessFlags, _space]) => accessFlags,
945
+ )),
946
+ smaliMemberNameParser,
947
+ createExactSequenceParser(':'),
948
+ smaliTypeDescriptorParser,
949
+ createOptionalParser(promiseCompose(
950
+ createTupleParser([
951
+ createExactSequenceParser(' = '),
952
+ createUnionParser<number | bigint | string | boolean | null, string>([
953
+ smaliCharacterLiteralParser,
954
+ smaliFieldValueParser,
955
+ smaliQuotedStringParser,
956
+ promiseCompose(
957
+ createExactSequenceParser('true'),
958
+ () => true,
959
+ ),
960
+ promiseCompose(
961
+ createExactSequenceParser('false'),
962
+ () => false,
963
+ ),
964
+ promiseCompose(
965
+ createExactSequenceParser('null'),
966
+ () => null,
967
+ ),
968
+ ]),
969
+ ]),
970
+ ([
971
+ _equals,
972
+ value,
973
+ ]) => value,
974
+ )),
975
+ smaliLineEndPraser,
976
+ createOptionalParser(promiseCompose(
977
+ createTupleParser([
978
+ createSeparatedArrayParser(
979
+ smaliAnnotationParser,
980
+ smaliNewlinesParser,
981
+ ),
982
+ createExactSequenceParser('.end field\n'),
983
+ ]),
984
+ ([
985
+ annotations,
986
+ _endField,
987
+ ]) => annotations,
988
+ )),
989
+ ]),
990
+ ([
991
+ _field,
992
+ accessFlags,
993
+ name,
994
+ _colon,
995
+ type,
996
+ initialValue,
997
+ _newline,
998
+ annotations,
999
+ ]) => ({
1000
+ field: {
1001
+ accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
1002
+ field: {
1003
+ class: 'FILLED_LATER',
1004
+ type,
1005
+ name,
1006
+ },
1007
+ },
1008
+ annotations: annotations ?? [],
1009
+ ...(initialValue !== undefined ? { initialValue } : {}),
1010
+ }),
1011
+ );
1012
+
1013
+ setParserName(smaliFieldParser, 'smaliFieldParser');
1014
+
1015
+ type SmaliFields = {
1016
+ staticFields: SmaliField[];
1017
+ instanceFields: SmaliField[];
1018
+ };
1019
+
1020
+ const smaliFieldsParser: Parser<SmaliFields, string> = promiseCompose(
1021
+ createSeparatedNonEmptyArrayParser<string[] | SmaliField, string>(
1022
+ smaliFieldParser,
1023
+ smaliCommentsOrNewlinesParser,
1024
+ ),
1025
+ fieldsAndComments => {
1026
+ let type: 'staticField' | 'instanceField' = 'instanceField';
1027
+
1028
+ const staticFields: SmaliField[] = [];
1029
+ const instanceFields: SmaliField[] = [];
1030
+
1031
+ for (const fieldOrComment of fieldsAndComments) {
1032
+ if (Array.isArray(fieldOrComment)) {
1033
+ for (const comment of fieldOrComment) {
1034
+ if (comment === ' static fields') {
1035
+ type = 'staticField';
1036
+ }
1037
+
1038
+ if (comment === ' instance fields') {
1039
+ type = 'instanceField';
1040
+ }
1041
+ }
1042
+
1043
+ continue;
1044
+ }
1045
+
1046
+ invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
1047
+
1048
+ const field = fieldOrComment;
1049
+
1050
+ if (
1051
+ type === 'staticField'
1052
+ || field.field.accessFlags.static
1053
+ ) {
1054
+ staticFields.push(field);
1055
+
1056
+ continue;
1057
+ }
1058
+
1059
+ if (type === 'instanceField') {
1060
+ instanceFields.push(field);
1061
+
1062
+ continue;
1063
+ }
1064
+
1065
+ invariant(false, 'Expected field type');
1066
+ }
1067
+
1068
+ return {
1069
+ staticFields,
1070
+ instanceFields,
1071
+ };
1072
+ },
1073
+ );
1074
+
1075
+ setParserName(smaliFieldsParser, 'smaliFieldsParser');
1076
+
1077
+ const smaliShortyFieldTypeParser: Parser<string, string> = createUnionParser([
1078
+ createExactSequenceParser('Z'),
1079
+ createExactSequenceParser('B'),
1080
+ createExactSequenceParser('S'),
1081
+ createExactSequenceParser('C'),
1082
+ createExactSequenceParser('I'),
1083
+ createExactSequenceParser('J'),
1084
+ createExactSequenceParser('F'),
1085
+ createExactSequenceParser('D'),
1086
+ ]);
1087
+
1088
+ setParserName(smaliShortyFieldTypeParser, 'smaliShortyFieldTypeParser');
1089
+
1090
+ const smaliShortyReturnTypeParser: Parser<string, string> = createUnionParser([
1091
+ createExactSequenceParser('V'),
1092
+ smaliShortyFieldTypeParser,
1093
+ ]);
1094
+
1095
+ setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
1096
+
1097
+ function shortyGetInsSize(shorty: string): number {
1098
+ let size = 0;
1099
+
1100
+ for (const char of shorty.slice(1)) {
1101
+ if (char === 'J' || char === 'D') {
1102
+ size += 2;
1103
+ } else if (char !== 'V') {
1104
+ size += 1;
1105
+ }
1106
+ }
1107
+
1108
+ return size;
1109
+ }
1110
+
1111
+ const smaliCodeRegistersParser: Parser<number, string> = promiseCompose(
1112
+ createTupleParser([
1113
+ createExactSequenceParser(' .registers '),
1114
+ smaliNumberParser,
1115
+ smaliLineEndPraser,
1116
+ ]),
1117
+ ([
1118
+ _registers,
1119
+ registers,
1120
+ _newline,
1121
+ ]) => registers,
1122
+ );
1123
+
1124
+ setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
1125
+
1126
+ const smaliCodeLineParser: Parser<number, string> = promiseCompose(
1127
+ createTupleParser([
1128
+ createExactSequenceParser(' .line '),
1129
+ smaliNumberParser,
1130
+ smaliLineEndPraser,
1131
+ ]),
1132
+ ([
1133
+ _line,
1134
+ line,
1135
+ _newline,
1136
+ ]) => line,
1137
+ );
1138
+
1139
+ setParserName(smaliCodeLineParser, 'smaliCodeLineParser');
1140
+
1141
+ type SmaliRegister = {
1142
+ prefix: 'v' | 'p';
1143
+ index: number;
1144
+ };
1145
+
1146
+ function isSmaliRegister(value: unknown): value is SmaliRegister {
1147
+ return (
1148
+ value !== null
1149
+ && typeof value === 'object'
1150
+ && 'prefix' in value
1151
+ && 'index' in value
1152
+ && typeof (value as SmaliRegister).prefix === 'string'
1153
+ && typeof (value as SmaliRegister).index === 'number'
1154
+ );
1155
+ }
1156
+
1157
+ const smaliParametersRegisterParser: Parser<SmaliRegister, string> = promiseCompose(
1158
+ createUnionParser<['v' | 'p', number], string>([
1159
+ createTupleParser([
1160
+ createExactSequenceParser('v'),
1161
+ smaliNumberParser,
1162
+ ]),
1163
+ createTupleParser([
1164
+ createExactSequenceParser('p'),
1165
+ smaliNumberParser,
1166
+ ]),
1167
+ ]),
1168
+ ([
1169
+ prefix,
1170
+ index,
1171
+ ]) => ({
1172
+ prefix,
1173
+ index,
1174
+ }),
1175
+ );
1176
+
1177
+ setParserName(smaliParametersRegisterParser, 'smaliParametersRegisterParser');
1178
+
1179
+ type SmaliCodeLocal = {
1180
+ register: SmaliRegister;
1181
+ name: string | undefined;
1182
+ type: string | undefined;
1183
+ };
1184
+
1185
+ const smaliCodeLocalParser: Parser<SmaliCodeLocal, string> = promiseCompose(
1186
+ createTupleParser([
1187
+ createExactSequenceParser(' .local '),
1188
+ smaliParametersRegisterParser,
1189
+ createOptionalParser(createTupleParser([
1190
+ createExactSequenceParser(','),
1191
+ smaliWhitespaceParser,
1192
+ smaliQuotedStringParser,
1193
+ createExactSequenceParser(':'),
1194
+ smaliTypeDescriptorParser,
1195
+ ])),
1196
+ smaliLineEndPraser,
1197
+ ]),
1198
+ ([
1199
+ _local,
1200
+ register,
1201
+ nameAndType,
1202
+ ]) => ({
1203
+ register,
1204
+ name: nameAndType?.[2],
1205
+ type: nameAndType?.[4],
1206
+ }),
1207
+ );
1208
+
1209
+ setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
1210
+
1211
+ const smaliCodeEndLocalParser: Parser<SmaliRegister, string> = promiseCompose(
1212
+ createTupleParser([
1213
+ createExactSequenceParser(' .end local '),
1214
+ smaliParametersRegisterParser,
1215
+ smaliLineEndPraser,
1216
+ ]),
1217
+ ([
1218
+ _endLocal,
1219
+ register,
1220
+ _newline,
1221
+ ]) => register,
1222
+ );
1223
+
1224
+ setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
1225
+
1226
+ const smaliCodeRestartLocalParser: Parser<SmaliRegister, string> = promiseCompose(
1227
+ createTupleParser([
1228
+ createExactSequenceParser(' .restart local '),
1229
+ smaliParametersRegisterParser,
1230
+ smaliLineEndPraser,
1231
+ ]),
1232
+ ([
1233
+ _restartLocal,
1234
+ register,
1235
+ _newline,
1236
+ ]) => register,
1237
+ );
1238
+
1239
+ setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
1240
+
1241
+ const smaliCodePrologueEndParser: Parser<true, string> = promiseCompose(
1242
+ createTupleParser([
1243
+ createExactSequenceParser(' .prologue'),
1244
+ smaliLineEndPraser,
1245
+ ]),
1246
+ () => true as const,
1247
+ );
1248
+
1249
+ setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
1250
+
1251
+ const smaliCodeEpilogueBeginParser: Parser<true, string> = promiseCompose(
1252
+ createTupleParser([
1253
+ createExactSequenceParser(' .epilogue'),
1254
+ smaliLineEndPraser,
1255
+ ]),
1256
+ () => true as const,
1257
+ );
1258
+
1259
+ setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
1260
+
1261
+ type SmaliCodeParameter = {
1262
+ register: SmaliRegister;
1263
+ name: string | undefined;
1264
+ annotation: SmaliAnnotation | undefined;
1265
+ };
1266
+
1267
+ export const smaliCodeParameterParser: Parser<SmaliCodeParameter, string> = promiseCompose(
1268
+ createTupleParser([
1269
+ createExactSequenceParser(' .param '),
1270
+ smaliParametersRegisterParser,
1271
+ createOptionalParser(createTupleParser([
1272
+ createExactSequenceParser(','),
1273
+ smaliWhitespaceParser,
1274
+ smaliQuotedStringParser,
1275
+ smaliWhitespaceParser,
1276
+ ])),
1277
+ createOptionalParser(smaliWhitespaceParser),
1278
+ smaliCommentsOrNewlinesParser,
1279
+ createOptionalParser(createTupleParser([
1280
+ smaliAnnotationParser,
1281
+ smaliIndentationParser,
1282
+ createExactSequenceParser('.end param\n'),
1283
+ ])),
1284
+ ]),
1285
+ ([
1286
+ _parameter,
1287
+ register,
1288
+ optionalCommaAndString,
1289
+ _whitespace,
1290
+ _commentOrNewline,
1291
+ optionalAnnotation,
1292
+ ]) => {
1293
+ let annotation: undefined | SmaliAnnotation;
1294
+
1295
+ if (optionalAnnotation) {
1296
+ annotation = optionalAnnotation[0];
1297
+ }
1298
+
1299
+ return {
1300
+ register,
1301
+ name: optionalCommaAndString?.[2],
1302
+ annotation,
1303
+ };
1304
+ },
1305
+ );
1306
+
1307
+ setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
1308
+
1309
+ const smaliCodeLabelParser: Parser<string, string> = promiseCompose(
1310
+ createTupleParser([
1311
+ createExactSequenceParser(':'),
1312
+ smaliIdentifierParser,
1313
+ ]),
1314
+ ([
1315
+ _label,
1316
+ label,
1317
+ ]) => label,
1318
+ );
1319
+
1320
+ setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
1321
+
1322
+ const smaliCodeLabelLineParser: Parser<string, string> = promiseCompose(
1323
+ createTupleParser([
1324
+ smaliIndentationParser,
1325
+ smaliCodeLabelParser,
1326
+ smaliLineEndPraser,
1327
+ ]),
1328
+ ([
1329
+ _label,
1330
+ label,
1331
+ _newlabel,
1332
+ ]) => label,
1333
+ );
1334
+
1335
+ setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
1336
+
1337
+ type SmaliCatchDirective = {
1338
+ type: string | undefined; // undefined for .catchall
1339
+ startLabel: string;
1340
+ endLabel: string;
1341
+ handlerLabel: string;
1342
+ };
1343
+
1344
+ const smaliCatchDirectiveParser: Parser<SmaliCatchDirective, string> = promiseCompose(
1345
+ createTupleParser([
1346
+ smaliIndentationParser,
1347
+ createExactSequenceParser('.catch'),
1348
+ createUnionParser<string | undefined, string, string>([
1349
+ promiseCompose(
1350
+ createExactSequenceParser('all'),
1351
+ () => undefined as undefined,
1352
+ ),
1353
+ promiseCompose(
1354
+ createTupleParser([
1355
+ createExactSequenceParser(' '),
1356
+ smaliTypeDescriptorParser,
1357
+ ]),
1358
+ ([
1359
+ _space,
1360
+ type,
1361
+ ]) => type,
1362
+ ),
1363
+ ]),
1364
+ createExactSequenceParser(' {'),
1365
+ smaliCodeLabelParser,
1366
+ createExactSequenceParser(' .. '),
1367
+ smaliCodeLabelParser,
1368
+ createExactSequenceParser('} '),
1369
+ smaliCodeLabelParser,
1370
+ smaliLineEndPraser,
1371
+ ]),
1372
+ ([
1373
+ _indentation,
1374
+ _catch,
1375
+ type,
1376
+ _openBrace,
1377
+ startLabel,
1378
+ _dots,
1379
+ endLabel,
1380
+ _closeBrace,
1381
+ handlerLabel,
1382
+ _newline,
1383
+ ]) => ({
1384
+ type,
1385
+ startLabel,
1386
+ endLabel,
1387
+ handlerLabel,
1388
+ }),
1389
+ );
1390
+
1391
+ setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
1392
+
1393
+ type SmaliLabeledCatchDirective = {
1394
+ labels: string[];
1395
+ catchDirective: SmaliCatchDirective;
1396
+ };
1397
+
1398
+ const smaliLabeledCatchDirectiveParser: Parser<SmaliLabeledCatchDirective, string> = promiseCompose(
1399
+ createTupleParser([
1400
+ createArrayParser(smaliCodeLineParser),
1401
+ createOptionalParser(smaliCodeLocalParser),
1402
+ createArrayParser(smaliCodeLabelLineParser),
1403
+ smaliCatchDirectiveParser,
1404
+ ]),
1405
+ ([
1406
+ _lines,
1407
+ _local,
1408
+ labels,
1409
+ catchDirective,
1410
+ ]) => ({
1411
+ labels,
1412
+ catchDirective,
1413
+ }),
1414
+ );
1415
+
1416
+ setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
1417
+
1418
+ const smaliParametersRegisterRangeParser: Parser<SmaliRegister[], string> = promiseCompose(
1419
+ createTupleParser([
1420
+ createExactSequenceParser('{'),
1421
+ smaliParametersRegisterParser,
1422
+ createExactSequenceParser(' .. '),
1423
+ smaliParametersRegisterParser,
1424
+ createExactSequenceParser('}'),
1425
+ ]),
1426
+ ([
1427
+ _openBrace,
1428
+ startRegister,
1429
+ _dotDot,
1430
+ endRegister,
1431
+ _closeBrace,
1432
+ ]) => {
1433
+ invariant(
1434
+ startRegister.prefix === endRegister.prefix,
1435
+ 'Register range must use the same prefix',
1436
+ );
1437
+
1438
+ invariant(
1439
+ startRegister.index <= endRegister.index,
1440
+ 'Register range start must be less than or equal to end',
1441
+ );
1442
+
1443
+ const registers: SmaliRegister[] = [];
1444
+ for (let i = startRegister.index; i <= endRegister.index; i++) {
1445
+ registers.push({
1446
+ prefix: startRegister.prefix,
1447
+ index: i,
1448
+ });
1449
+ }
1450
+
1451
+ return registers;
1452
+ },
1453
+ );
1454
+
1455
+ setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
1456
+
1457
+ const smaliParametersRegisterListParser: Parser<SmaliRegister[], string> = promiseCompose(
1458
+ createTupleParser([
1459
+ createExactSequenceParser('{'),
1460
+ createArrayParser(promiseCompose(
1461
+ createTupleParser([
1462
+ smaliParametersRegisterParser,
1463
+ createOptionalParser(createExactSequenceParser(', ')),
1464
+ ]),
1465
+ ([
1466
+ parameter,
1467
+ _comma,
1468
+ ]) => parameter,
1469
+ )),
1470
+ createExactSequenceParser('}'),
1471
+ ]),
1472
+ ([
1473
+ _openBrace,
1474
+ parameters,
1475
+ _closeBrace,
1476
+ ]) => parameters,
1477
+ );
1478
+
1479
+ setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
1480
+
1481
+ const smaliParametersRegistersParser: Parser<SmaliRegister[], string> = createUnionParser([
1482
+ smaliParametersRegisterRangeParser,
1483
+ smaliParametersRegisterListParser,
1484
+ ]);
1485
+
1486
+ setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
1487
+
1488
+ const smaliParametersStringParser: Parser<string, string> = cloneParser(smaliQuotedStringParser);
1489
+
1490
+ setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
1491
+
1492
+ const smaliParametersIntegerParser: Parser<number | bigint, string> = promiseCompose(
1493
+ createTupleParser([
1494
+ createOptionalParser(createExactSequenceParser('-')),
1495
+ createExactSequenceParser('0x'),
1496
+ async parserContext => {
1497
+ const characters: string[] = [];
1498
+
1499
+ while (true) {
1500
+ const character = await parserContext.peek(0);
1501
+
1502
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
1503
+
1504
+ invariant(character !== undefined, 'Unexpected end of input');
1505
+
1506
+ if (
1507
+ (character >= '0' && character <= '9')
1508
+ || (character >= 'a' && character <= 'f')
1509
+ || (character >= 'A' && character <= 'F')
1510
+ ) {
1511
+ characters.push(character);
1512
+
1513
+ parserContext.skip(1);
1514
+
1515
+ continue;
1516
+ }
1517
+
1518
+ break;
1519
+ }
1520
+
1521
+ return characters.join('');
1522
+ },
1523
+ createOptionalParser(createUnionParser([
1524
+ createExactSequenceParser('L'),
1525
+ createExactSequenceParser('t'),
1526
+ createExactSequenceParser('s'),
1527
+ ])),
1528
+ ]),
1529
+ ([
1530
+ optionalMinus,
1531
+ _0x,
1532
+ value,
1533
+ optionalSuffix,
1534
+ ]) => {
1535
+ if (optionalSuffix === 'L') {
1536
+ const sign = optionalMinus ? -1n : 1n;
1537
+
1538
+ return sign * BigInt('0x' + value);
1539
+ }
1540
+
1541
+ const sign = optionalMinus ? -1 : 1;
1542
+
1543
+ return sign * Number.parseInt(value, 16);
1544
+ },
1545
+ );
1546
+
1547
+ setParserName(smaliParametersIntegerParser, 'smaliParametersIntegerParser');
1548
+
1549
+ const smaliParametersLabelParser: Parser<string, string> = promiseCompose(
1550
+ createTupleParser([
1551
+ createExactSequenceParser(':'),
1552
+ smaliIdentifierParser,
1553
+ ]),
1554
+ ([
1555
+ _label,
1556
+ label,
1557
+ ]) => label,
1558
+ );
1559
+
1560
+ setParserName(smaliParametersLabelParser, 'smaliParametersLabelParser');
1561
+
1562
+ const smaliParametersTypeParser: Parser<string, string> = cloneParser(smaliTypeDescriptorParser);
1563
+
1564
+ setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
1565
+
1566
+ const smaliParametersFieldParser: Parser<DalvikExecutableField, string> = promiseCompose(
1567
+ createTupleParser([
1568
+ smaliTypeDescriptorParser,
1569
+ createExactSequenceParser('->'),
1570
+ smaliMemberNameParser,
1571
+ createExactSequenceParser(':'),
1572
+ smaliTypeDescriptorParser,
1573
+ ]),
1574
+ ([
1575
+ classPath,
1576
+ _separator,
1577
+ fieldName,
1578
+ _colon,
1579
+ type,
1580
+ ]) => ({
1581
+ class: classPath,
1582
+ name: fieldName,
1583
+ type,
1584
+ }),
1585
+ );
1586
+
1587
+ setParserName(smaliParametersFieldParser, 'smaliParametersFieldParser');
1588
+
1589
+ type SmaliCodeOperationParameter =
1590
+ | SmaliRegister
1591
+ | SmaliRegister[]
1592
+ | string
1593
+ | DalvikExecutableMethod
1594
+ ;
1595
+
1596
+ const smaliCodeOperationParametersParser: Parser<SmaliCodeOperationParameter[], string> = createArrayParser(promiseCompose(
1597
+ createTupleParser([
1598
+ createUnionParser<SmaliCodeOperationParameter, string>([
1599
+ smaliParametersRegisterParser,
1600
+ smaliParametersRegistersParser,
1601
+ smaliParametersStringParser,
1602
+ smaliParametersIntegerParser,
1603
+ promiseCompose(
1604
+ createTupleParser([
1605
+ createNegativeLookaheadParser(smaliParametersIntegerParser),
1606
+ smaliNumberParser,
1607
+ ]),
1608
+ ([ _notInteger, number ]) => number,
1609
+ ),
1610
+ smaliParametersLabelParser,
1611
+ promiseCompose(
1612
+ createTupleParser([
1613
+ createNegativeLookaheadParser(smaliParametersMethodParser),
1614
+ createNegativeLookaheadParser(smaliParametersFieldParser),
1615
+ smaliParametersTypeParser,
1616
+ ]),
1617
+ ([
1618
+ _notMethod,
1619
+ _notField,
1620
+ type,
1621
+ ]) => type,
1622
+ ),
1623
+ smaliParametersMethodParser,
1624
+ smaliParametersFieldParser,
1625
+ ]),
1626
+ createOptionalParser(createExactSequenceParser(', ')),
1627
+ ]),
1628
+ ([
1629
+ parameter,
1630
+ _comma,
1631
+ ]) => parameter,
1632
+ ));
1633
+
1634
+ setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
1635
+
1636
+ const smaliCodeOperationNameParser: Parser<string, string> = promiseCompose(
1637
+ createTupleParser([
1638
+ promiseCompose(
1639
+ createSeparatedArrayParser(
1640
+ smaliIdentifierParser,
1641
+ createExactSequenceParser('-'),
1642
+ ),
1643
+ parts => parts.join('-'),
1644
+ ),
1645
+ createOptionalParser(promiseCompose(
1646
+ createTupleParser([
1647
+ createExactSequenceParser('/'),
1648
+ smaliIdentifierParser,
1649
+ ]),
1650
+ ([ slash, name ]) => slash + name,
1651
+ )),
1652
+ ]),
1653
+ ([
1654
+ name,
1655
+ optionalSlashName,
1656
+ ]) => name + (optionalSlashName || ''),
1657
+ );
1658
+
1659
+ setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
1660
+
1661
+ type SmaliOneLineCodeOperation = {
1662
+ operation: string;
1663
+ parameters: SmaliCodeOperationParameter[];
1664
+ };
1665
+
1666
+ const smaliOneLineCodeOperationParser: Parser<SmaliOneLineCodeOperation, string> = promiseCompose(
1667
+ createTupleParser([
1668
+ smaliSingleIndentationParser,
1669
+ smaliCodeOperationNameParser,
1670
+ promiseCompose(
1671
+ createOptionalParser(createTupleParser([
1672
+ smaliSingleWhitespaceParser,
1673
+ smaliCodeOperationParametersParser,
1674
+ ])),
1675
+ undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
1676
+ ),
1677
+ smaliLineEndPraser,
1678
+ ]),
1679
+ ([
1680
+ _indent,
1681
+ operation,
1682
+ parameters,
1683
+ _newline,
1684
+ ]) => ({
1685
+ operation,
1686
+ parameters,
1687
+ }),
1688
+ );
1689
+
1690
+ setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
1691
+
1692
+ type SmaliCodeOperationLabelsBody = string[];
1693
+
1694
+ const createMultilineSmaliCodeOperationLabelsBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelsBody, string> = operationName => promiseCompose(
1695
+ createTupleParser([
1696
+ createArrayParser(promiseCompose(
1697
+ createTupleParser([
1698
+ smaliIndentationParser,
1699
+ smaliCodeLabelParser,
1700
+ smaliLineEndPraser,
1701
+ ]),
1702
+ ([
1703
+ _indentation,
1704
+ label,
1705
+ _newline,
1706
+ ]) => label,
1707
+ )),
1708
+ smaliIndentationParser,
1709
+ createExactSequenceParser('.end '),
1710
+ createExactSequenceParser(operationName),
1711
+ smaliLineEndPraser,
1712
+ ]),
1713
+ ([
1714
+ labels,
1715
+ ]) => labels,
1716
+ );
1717
+
1718
+ type SmaliCodeOperationLabelMapBody = Array<readonly [number | bigint, string]>;
1719
+
1720
+ const createMultilineSmaliCodeOperationLabelMapBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelMapBody, string> = operationName => promiseCompose(
1721
+ createTupleParser([
1722
+ createArrayParser(promiseCompose(
1723
+ createTupleParser([
1724
+ smaliIndentationParser,
1725
+ smaliParametersIntegerParser,
1726
+ createExactSequenceParser(' -> '),
1727
+ smaliCodeLabelParser,
1728
+ smaliLineEndPraser,
1729
+ ]),
1730
+ ([
1731
+ _indentation,
1732
+ key,
1733
+ _arrow,
1734
+ label,
1735
+ _newline,
1736
+ ]) => [
1737
+ key,
1738
+ label,
1739
+ ] as const,
1740
+ )),
1741
+ smaliIndentationParser,
1742
+ createExactSequenceParser('.end '),
1743
+ createExactSequenceParser(operationName),
1744
+ smaliLineEndPraser,
1745
+ ]),
1746
+ ([
1747
+ labels,
1748
+ ]) => labels,
1749
+ );
1750
+
1751
+ type SmaliCodeOperationIntegersBody = Array<number | bigint>;
1752
+
1753
+ const createMultilineSmaliCodeOperationIntegersBodyParser: (operationName: string) => Parser<SmaliCodeOperationIntegersBody, string> = operationName => promiseCompose(
1754
+ createTupleParser([
1755
+ createArrayParser(promiseCompose(
1756
+ createTupleParser([
1757
+ smaliIndentationParser,
1758
+ smaliParametersIntegerParser,
1759
+ smaliLineEndPraser,
1760
+ ]),
1761
+ ([
1762
+ _indentation,
1763
+ key,
1764
+ ]) => key,
1765
+ )),
1766
+ smaliIndentationParser,
1767
+ createExactSequenceParser('.end '),
1768
+ createExactSequenceParser(operationName),
1769
+ smaliLineEndPraser,
1770
+ ]),
1771
+ ([
1772
+ labels,
1773
+ ]) => labels,
1774
+ );
1775
+
1776
+ type SmaliCodeOperationBody =
1777
+ | SmaliCodeOperationLabelsBody
1778
+ | SmaliCodeOperationIntegersBody
1779
+ | SmaliCodeOperationLabelMapBody
1780
+ ;
1781
+
1782
+ const createMultilineSmaliCodeOperationBodyParser: (operationName: string) => Parser<SmaliCodeOperationBody, string> = operationName => createUnionParser([
1783
+ createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
1784
+ createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
1785
+ createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
1786
+ ]);
1787
+
1788
+ type SmaliMultilineCodeOperation = {
1789
+ operation: string;
1790
+ parameters: SmaliCodeOperationParameter[];
1791
+ body: SmaliCodeOperationBody;
1792
+ };
1793
+
1794
+ const smaliMultilineCodeOperationParser: Parser<SmaliMultilineCodeOperation, string> = parserCreatorCompose(
1795
+ () => promiseCompose(
1796
+ createTupleParser([
1797
+ smaliSingleIndentationParser,
1798
+ createExactSequenceParser('.'),
1799
+ smaliCodeOperationNameParser,
1800
+ promiseCompose(
1801
+ createOptionalParser(createTupleParser([
1802
+ smaliSingleWhitespaceParser,
1803
+ smaliCodeOperationParametersParser,
1804
+ ])),
1805
+ undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
1806
+ ),
1807
+ smaliLineEndPraser,
1808
+ ]),
1809
+ ([
1810
+ _indent,
1811
+ _dot,
1812
+ operation,
1813
+ parameters,
1814
+ _newline,
1815
+ ]) => ({
1816
+ operation,
1817
+ parameters,
1818
+ }),
1819
+ ),
1820
+ ({ operation, parameters }) => promiseCompose(
1821
+ createMultilineSmaliCodeOperationBodyParser(operation),
1822
+ body => ({
1823
+ operation,
1824
+ parameters,
1825
+ body,
1826
+ }),
1827
+ ),
1828
+ )();
1829
+
1830
+ setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
1831
+
1832
+ type SmaliLooseCodeOperation =
1833
+ | SmaliOneLineCodeOperation
1834
+ | SmaliMultilineCodeOperation
1835
+ ;
1836
+
1837
+ const smaliLooseCodeOperationParser: Parser<SmaliLooseCodeOperation, string> = createUnionParser([
1838
+ smaliOneLineCodeOperationParser,
1839
+ smaliMultilineCodeOperationParser,
1840
+ ]);
1841
+
1842
+ setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
1843
+
1844
+ type SmaliCodeOperationFromDalvikBytecodeOperation<T extends DalvikBytecodeOperation> =
1845
+ T extends { branchOffsets: number[] }
1846
+ ? Simplify<Omit<T, 'branchOffsets'> & { branchOffsetIndices: number[] }>
1847
+ : T extends { branchOffset: number }
1848
+ ? Simplify<Omit<T, 'branchOffset'> & { branchOffsetIndex: number }>
1849
+ : T extends { methodIndex: IndexIntoMethodIds }
1850
+ ? Simplify<Omit<T, 'methodIndex'> & { method: DalvikExecutableMethod }>
1851
+ : T
1852
+ ;
1853
+
1854
+ type SmaliCodeOperation = SmaliCodeOperationFromDalvikBytecodeOperation<DalvikBytecodeOperation>;
1855
+
1856
+ export const smaliCodeOperationParser: Parser<SmaliCodeOperation, string> = promiseCompose(
1857
+ smaliLooseCodeOperationParser,
1858
+ operation => {
1859
+ const operation_ = {
1860
+ operation: operation.operation,
1861
+ // Line,
1862
+ // local,
1863
+ // parameters: (operation as any).parameters,
1864
+ } as any; // TODO
1865
+
1866
+ if ('body' in operation) {
1867
+ const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
1868
+
1869
+ invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
1870
+
1871
+ operation_.operation = operationDexName;
1872
+
1873
+ if (operationDexName === 'sparse-switch-payload') {
1874
+ const map = new Map<number | bigint, string>(operation.body as SmaliCodeOperationLabelMapBody);
1875
+ operation_.keys = [ ...map.keys() ];
1876
+ operation_.branchLabels = [ ...map.values() ];
1877
+ } else if (operationDexName === 'fill-array-data-payload') {
1878
+ // Get elementWidth from parameters (first parameter)
1879
+ const elementWidth = operation.parameters[0];
1880
+ invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
1881
+
1882
+ operation_.elementWidth = elementWidth;
1883
+
1884
+ // Convert integer values to bytes (little-endian)
1885
+ const values = operation.body as SmaliCodeOperationIntegersBody;
1886
+ const data: number[] = [];
1887
+
1888
+ for (const value of values) {
1889
+ // Use BigInt for bitwise operations to preserve full precision
1890
+ const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
1891
+
1892
+ // Convert to bytes based on elementWidth (little-endian)
1893
+ for (let i = 0; i < elementWidth; i++) {
1894
+ data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xFFn));
1895
+ }
1896
+ }
1897
+
1898
+ operation_.data = data;
1899
+ } else {
1900
+ operation_.branchLabels = operation.body;
1901
+ }
1902
+ }
1903
+
1904
+ const hasRegisters = Array.isArray(operation.parameters.at(0));
1905
+ if (hasRegisters) {
1906
+ operation_.registers = [];
1907
+ }
1908
+
1909
+ for (const parameter of (operation as unknown as { parameters: unknown[] }).parameters.flat() ?? []) {
1910
+ if (isDalvikExecutableMethod(parameter)) {
1911
+ operation_.method = parameter;
1912
+
1913
+ continue;
1914
+ }
1915
+
1916
+ if (isDalvikExecutableField(parameter)) {
1917
+ operation_.field = parameter;
1918
+
1919
+ continue;
1920
+ }
1921
+
1922
+ if (isSmaliRegister(parameter)) {
1923
+ operation_.registers ||= [];
1924
+
1925
+ operation_.registers.push(parameter);
1926
+
1927
+ continue;
1928
+ }
1929
+
1930
+ if (typeof parameter === 'string') {
1931
+ if (operationsWithTypeArgument.has(operation_.operation)) {
1932
+ operation_.type = parameter;
1933
+
1934
+ continue;
1935
+ }
1936
+
1937
+ if (operationsWithBranchLabelArgument.has(operation_.operation)) {
1938
+ operation_.branchLabel = parameter;
1939
+
1940
+ continue;
1941
+ }
1942
+
1943
+ operation_.string = parameter;
1944
+
1945
+ continue;
1946
+ }
1947
+
1948
+ if (typeof parameter === 'number' || typeof parameter === 'bigint') {
1949
+ // Skip for fill-array-data-payload, already handled in body processing
1950
+ if (operation_.operation === 'fill-array-data-payload') {
1951
+ continue;
1952
+ }
1953
+
1954
+ // Const-wide operations always use bigint values
1955
+ if (operationsWithBigintValue.has(operation_.operation)) {
1956
+ operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
1957
+ } else {
1958
+ operation_.value = parameter;
1959
+ }
1960
+
1961
+ continue;
1962
+ }
1963
+
1964
+ invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
1965
+ }
1966
+
1967
+ return operation_;
1968
+ },
1969
+ );
1970
+
1971
+ setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
1972
+
1973
+ const operationsWithTypeArgument = new Set<DalvikBytecodeOperation['operation']>([
1974
+ 'new-instance',
1975
+ 'new-array',
1976
+ 'filled-new-array',
1977
+ 'filled-new-array/range',
1978
+ 'check-cast',
1979
+ 'instance-of',
1980
+ 'const-class',
1981
+ ]);
1982
+
1983
+ const operationsWithBigintValue = new Set<DalvikBytecodeOperation['operation']>([
1984
+ 'const-wide/16',
1985
+ 'const-wide/32',
1986
+ 'const-wide',
1987
+ ]);
1988
+
1989
+ const operationsWithBranchLabelArgument = new Set<DalvikBytecodeOperation['operation']>([
1990
+ 'goto',
1991
+ 'goto/16',
1992
+ 'goto/32',
1993
+ 'if-eq',
1994
+ 'if-ne',
1995
+ 'if-lt',
1996
+ 'if-ge',
1997
+ 'if-gt',
1998
+ 'if-le',
1999
+ 'if-eqz',
2000
+ 'if-nez',
2001
+ 'if-ltz',
2002
+ 'if-gez',
2003
+ 'if-gtz',
2004
+ 'if-lez',
2005
+ 'packed-switch',
2006
+ 'sparse-switch',
2007
+ 'fill-array-data',
2008
+ ]);
2009
+
2010
+ const payloadOperationSmaliNameToDexName = new Map<string, string>(Object.entries({
2011
+ 'packed-switch': 'packed-switch-payload',
2012
+ 'sparse-switch': 'sparse-switch-payload',
2013
+ 'array-data': 'fill-array-data-payload',
2014
+ }));
2015
+
2016
+ function isOperationWithLabels(value: unknown): value is DalvikBytecodeOperation & {
2017
+ labels: Set<string>;
2018
+ } {
2019
+ return (
2020
+ typeof value === 'object'
2021
+ && value !== null
2022
+ && 'labels' in value
2023
+ && value.labels instanceof Set
2024
+ );
2025
+ }
2026
+
2027
+ type SmaliDebugDirective =
2028
+ | { type: 'line'; line: number }
2029
+ | { type: 'startLocal'; local: SmaliCodeLocal }
2030
+ | { type: 'endLocal'; register: SmaliRegister }
2031
+ | { type: 'restartLocal'; register: SmaliRegister }
2032
+ | { type: 'setPrologueEnd' }
2033
+ | { type: 'setEpilogueBegin' };
2034
+
2035
+ type SmaliAnnotatedCodeOperation = {
2036
+ debugDirectives: SmaliDebugDirective[];
2037
+ labels: string[];
2038
+ operation: SmaliCodeOperation;
2039
+ };
2040
+
2041
+ const smaliAnnotatedCodeOperationParser: Parser<SmaliAnnotatedCodeOperation, string> = promiseCompose(
2042
+ createTupleParser([
2043
+ createArrayParser(smaliCodeLineParser),
2044
+ createOptionalParser(smaliCodeLocalParser),
2045
+ createOptionalParser(smaliCodeEndLocalParser),
2046
+ createOptionalParser(smaliCodeRestartLocalParser),
2047
+ createOptionalParser(smaliCodePrologueEndParser),
2048
+ createOptionalParser(smaliCodeEpilogueBeginParser),
2049
+ createArrayParser(smaliCodeLabelLineParser),
2050
+ smaliCodeOperationParser,
2051
+ ]),
2052
+ ([
2053
+ lines,
2054
+ local,
2055
+ endLocal,
2056
+ restartLocal,
2057
+ prologueEnd,
2058
+ epilogueBegin,
2059
+ labels,
2060
+ operation,
2061
+ ]) => {
2062
+ const debugDirectives: SmaliDebugDirective[] = [];
2063
+
2064
+ for (const line of lines) {
2065
+ debugDirectives.push({ type: 'line', line });
2066
+ }
2067
+
2068
+ if (local) {
2069
+ debugDirectives.push({ type: 'startLocal', local });
2070
+ }
2071
+
2072
+ if (endLocal) {
2073
+ debugDirectives.push({ type: 'endLocal', register: endLocal });
2074
+ }
2075
+
2076
+ if (restartLocal) {
2077
+ debugDirectives.push({ type: 'restartLocal', register: restartLocal });
2078
+ }
2079
+
2080
+ if (prologueEnd) {
2081
+ debugDirectives.push({ type: 'setPrologueEnd' });
2082
+ }
2083
+
2084
+ if (epilogueBegin) {
2085
+ debugDirectives.push({ type: 'setEpilogueBegin' });
2086
+ }
2087
+
2088
+ return {
2089
+ debugDirectives,
2090
+ labels,
2091
+ operation,
2092
+ };
2093
+ },
2094
+ );
2095
+
2096
+ setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
2097
+
2098
+ type SmaliExecutableCode<DalvikBytecode> = {
2099
+ dalvikExecutableCode: DalvikExecutableCode<DalvikBytecode>;
2100
+ parameters: SmaliCodeParameter[];
2101
+ parameterAnnotations: SmaliCodeParameter[]; // TODO?: SmaliAnnotation[]
2102
+ methodAnnotations: SmaliAnnotation[];
2103
+ };
2104
+
2105
+ const smaliExecutableCodeParser: Parser<SmaliExecutableCode<DalvikBytecode>, string> = promiseCompose(
2106
+ createTupleParser([
2107
+ createOptionalParser(smaliCodeRegistersParser),
2108
+ createArrayParser(smaliAnnotationParser),
2109
+ createArrayParser(smaliCodeParameterParser),
2110
+ createOptionalParser(smaliCommentsOrNewlinesParser),
2111
+ createSeparatedArrayParser(
2112
+ smaliAnnotationParser,
2113
+ smaliCommentsOrNewlinesParser,
2114
+ ),
2115
+ createArrayParser(promiseCompose(
2116
+ createTupleParser([
2117
+ createOptionalParser(smaliCommentsOrNewlinesParser),
2118
+ createDisjunctionParser([
2119
+ smaliIndentedCommentParser,
2120
+ smaliAnnotatedCodeOperationParser,
2121
+ smaliLabeledCatchDirectiveParser,
2122
+ ]),
2123
+ ]),
2124
+ ([
2125
+ _commentsOrNewlinesParser,
2126
+ operation,
2127
+ ]) => operation,
2128
+ )),
2129
+ ]),
2130
+ ([
2131
+ registersSize,
2132
+ annotations1,
2133
+ parameters,
2134
+ _leadingCommentsOrNewlines,
2135
+ annotations2,
2136
+ instructionsAndCatchDirectives,
2137
+ ]) => {
2138
+ const annotations = [ ...annotations1, ...annotations2 ];
2139
+ const instructions: SmaliCodeOperation[] = [];
2140
+ const annotatedOperations: SmaliAnnotatedCodeOperation[] = [];
2141
+ const catchDirectives: SmaliCatchDirective[] = [];
2142
+ const catchDirectiveLabels: Map<SmaliCatchDirective, string[]> = new Map();
2143
+
2144
+ for (const item of instructionsAndCatchDirectives) {
2145
+ if (item && typeof item === 'object') {
2146
+ if ('labels' in item && 'catchDirective' in item) {
2147
+ // This is a SmaliLabeledCatchDirective
2148
+ const labeledCatch = item as SmaliLabeledCatchDirective;
2149
+ catchDirectives.push(labeledCatch.catchDirective);
2150
+ catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
2151
+ } else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
2152
+ // This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
2153
+ catchDirectives.push(item as SmaliCatchDirective);
2154
+ } else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
2155
+ // This is a SmaliAnnotatedCodeOperation
2156
+ const annotated = item as SmaliAnnotatedCodeOperation;
2157
+ annotatedOperations.push(annotated);
2158
+ const operation = annotated.operation;
2159
+ if (annotated.labels.length > 0) {
2160
+ (operation as any).labels = new Set(annotated.labels);
2161
+ }
2162
+ instructions.push(operation);
2163
+ } else if (item !== undefined) {
2164
+ // This is a SmaliCodeOperation (fallback)
2165
+ instructions.push(item as SmaliCodeOperation);
2166
+ }
2167
+ }
2168
+ }
2169
+
2170
+ if (
2171
+ registersSize === undefined
2172
+ && parameters !== undefined
2173
+ ) {
2174
+ registersSize = parameters.length;
2175
+ }
2176
+
2177
+ invariant(
2178
+ registersSize !== undefined,
2179
+ 'Expected registers size to be defined',
2180
+ );
2181
+
2182
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2183
+ if (
2184
+ 'branchLabel' in operation
2185
+ && typeof operation.branchLabel === 'string'
2186
+ ) {
2187
+ for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
2188
+ if (!isOperationWithLabels(targetOperation)) {
2189
+ continue;
2190
+ }
2191
+
2192
+ if (!targetOperation.labels.has(operation.branchLabel)) {
2193
+ continue;
2194
+ }
2195
+
2196
+ delete (operation as any).branchLabel;
2197
+ (operation as any).branchOffsetIndex = targetOperationIndex - operationIndex;
2198
+
2199
+ break;
2200
+ }
2201
+ }
2202
+
2203
+ if (
2204
+ 'branchLabels' in operation
2205
+ && typeof operation.branchLabels === 'object'
2206
+ && Array.isArray(operation.branchLabels)
2207
+ ) {
2208
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
2209
+ 'branchOffsetIndex' in sourceOperation
2210
+ && typeof sourceOperation.branchOffsetIndex === 'number'
2211
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
2212
+ ));
2213
+
2214
+ invariant(
2215
+ sourceOperations.length === 1,
2216
+ 'Expected exactly one source operation to point to %s',
2217
+ JSON.stringify(operation),
2218
+ );
2219
+
2220
+ const [ sourceOperation ] = sourceOperations;
2221
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
2222
+
2223
+ (operation as any).branchOffsetIndices = operation.branchLabels.map((branchLabel: string) => {
2224
+ for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
2225
+ if (!isOperationWithLabels(targetOperation)) {
2226
+ continue;
2227
+ }
2228
+
2229
+ if (!targetOperation.labels.has(branchLabel)) {
2230
+ continue;
2231
+ }
2232
+
2233
+ return targetOperationIndex - sourceOperationIndex;
2234
+ }
2235
+
2236
+ invariant(false, 'Expected to find branch label %s', branchLabel);
2237
+ });
2238
+
2239
+ delete (operation as any).branchLabels;
2240
+ }
2241
+ }
2242
+
2243
+ const branchOffsetByBranchOffsetIndex = new Map<number, number>();
2244
+
2245
+ let operationOffset = 0;
2246
+
2247
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2248
+ const operationSize = getOperationFormatSize(operation);
2249
+
2250
+ branchOffsetByBranchOffsetIndex.set(
2251
+ operationIndex,
2252
+ operationOffset,
2253
+ );
2254
+
2255
+ operationOffset += operationSize;
2256
+ }
2257
+
2258
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2259
+ if (
2260
+ 'branchOffsetIndex' in operation
2261
+ && typeof operation.branchOffsetIndex === 'number'
2262
+ ) {
2263
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
2264
+ invariant(
2265
+ operationOffset !== undefined,
2266
+ 'Expected branch offset for operation index %s, but got undefined',
2267
+ operation.branchOffsetIndex,
2268
+ );
2269
+
2270
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
2271
+ invariant(
2272
+ branchOffset !== undefined,
2273
+ 'Expected branch offset for operation index %s, but got undefined',
2274
+ operationIndex,
2275
+ );
2276
+
2277
+ (operation as any).branchOffset = operationOffset - branchOffset;
2278
+ }
2279
+
2280
+ if (
2281
+ 'branchOffsetIndices' in operation
2282
+ && typeof operation.branchOffsetIndices === 'object'
2283
+ && Array.isArray(operation.branchOffsetIndices)
2284
+ ) {
2285
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
2286
+ 'branchOffsetIndex' in sourceOperation
2287
+ && typeof sourceOperation.branchOffsetIndex === 'number'
2288
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
2289
+ ));
2290
+
2291
+ invariant(
2292
+ sourceOperations.length === 1,
2293
+ 'Expected exactly one source operation to point to %s',
2294
+ JSON.stringify(operation),
2295
+ );
2296
+
2297
+ const [ sourceOperation ] = sourceOperations;
2298
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
2299
+
2300
+ (operation as any).branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex: number) => {
2301
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
2302
+ invariant(
2303
+ operationOffset !== undefined,
2304
+ 'Expected branch offset for operation index %s, but got undefined',
2305
+ sourceOperationIndex + branchOffsetIndex,
2306
+ );
2307
+
2308
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
2309
+ invariant(
2310
+ branchOffset !== undefined,
2311
+ 'Expected branch offset for operation index %s, but got undefined',
2312
+ sourceOperationIndex,
2313
+ );
2314
+
2315
+ return operationOffset - branchOffset;
2316
+ });
2317
+ }
2318
+ }
2319
+
2320
+ // Build label-to-index mapping
2321
+ // Labels attached to instructions map to that instruction's index
2322
+ // Labels before catch directives should map to the position they mark
2323
+ const labelToIndexMap = new Map<string, number>();
2324
+
2325
+ // First, map labels from instructions
2326
+ for (const [ operationIndex, operation ] of instructions.entries()) {
2327
+ if (
2328
+ operation
2329
+ && typeof operation === 'object'
2330
+ && 'labels' in operation
2331
+ && operation.labels instanceof Set
2332
+ ) {
2333
+ for (const label of operation.labels) {
2334
+ labelToIndexMap.set(label, operationIndex);
2335
+ }
2336
+ }
2337
+ }
2338
+
2339
+ // Now handle labels from catch directives
2340
+ // We need to figure out where each catch directive appears in the original sequence
2341
+ let instructionArrayIndex = 0;
2342
+ for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
2343
+ const item = instructionsAndCatchDirectives[i];
2344
+ if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
2345
+ // This is a catch directive with labels
2346
+ // These labels should map to the current instruction index
2347
+ // (which is where the next instruction would be)
2348
+ const labeledCatch = item as SmaliLabeledCatchDirective;
2349
+ for (const label of labeledCatch.labels) {
2350
+ labelToIndexMap.set(label, instructionArrayIndex);
2351
+ }
2352
+ } else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
2353
+ // This is an instruction, increment the counter
2354
+ instructionArrayIndex++;
2355
+ }
2356
+ }
2357
+
2358
+ // Build tries array from catch directives
2359
+ const triesByRange = new Map<string, {
2360
+ startAddress: number;
2361
+ instructionCount: number;
2362
+ handlers: Array<{ type: string; address: number }>;
2363
+ catchAllAddress: number | undefined;
2364
+ }>();
2365
+
2366
+ for (const catchDirective of catchDirectives) {
2367
+ // Find the start and end instruction indices
2368
+ const startIndex = labelToIndexMap.get(catchDirective.startLabel);
2369
+ const endIndex = labelToIndexMap.get(catchDirective.endLabel);
2370
+ const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
2371
+
2372
+ invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
2373
+ invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
2374
+ invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
2375
+
2376
+ const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
2377
+ const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
2378
+ const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
2379
+
2380
+ invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
2381
+ invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
2382
+ invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
2383
+
2384
+ const instructionCount = endAddress - startAddress;
2385
+ const rangeKey = `${startAddress}-${instructionCount}`;
2386
+
2387
+ let tryEntry = triesByRange.get(rangeKey);
2388
+ if (!tryEntry) {
2389
+ tryEntry = {
2390
+ startAddress,
2391
+ instructionCount,
2392
+ handlers: [],
2393
+ catchAllAddress: undefined,
2394
+ };
2395
+ triesByRange.set(rangeKey, tryEntry);
2396
+ }
2397
+
2398
+ if (catchDirective.type === undefined) {
2399
+ // .catchall
2400
+ tryEntry.catchAllAddress = handlerAddress;
2401
+ } else {
2402
+ // .catch Type
2403
+ tryEntry.handlers.push({
2404
+ type: catchDirective.type,
2405
+ address: handlerAddress,
2406
+ });
2407
+ }
2408
+ }
2409
+
2410
+ const tries = Array.from(triesByRange.values()).map(tryEntry => ({
2411
+ startAddress: tryEntry.startAddress,
2412
+ instructionCount: tryEntry.instructionCount,
2413
+ handler: {
2414
+ handlers: tryEntry.handlers,
2415
+ catchAllAddress: tryEntry.catchAllAddress,
2416
+ },
2417
+ }));
2418
+
2419
+ for (const operation of instructions) {
2420
+ delete (operation as any).labels;
2421
+ delete (operation as any).branchOffsetIndex;
2422
+ delete (operation as any).branchOffsetIndices;
2423
+ }
2424
+
2425
+ // Build debug info from debug directives
2426
+ type DebugByteCodeValue = DalvikExecutableDebugInfo['bytecode'][number];
2427
+ let debugInfo: DalvikExecutableDebugInfo | undefined;
2428
+ const debugBytecode: DebugByteCodeValue[] = [];
2429
+ let currentLine: number | undefined;
2430
+ let lineStart: number | undefined;
2431
+
2432
+ // Helper to convert SmaliRegister to register number
2433
+ const getRegisterNum = (register: SmaliRegister): number => {
2434
+ // In Smali, 'v' registers start from 0, 'p' registers are parameters
2435
+ // For now, we'll just use the index. The actual conversion may need
2436
+ // to account for registersSize and parameter mapping.
2437
+ if (register.prefix === 'v') {
2438
+ return register.index;
2439
+ } else {
2440
+ // 'p' registers: need to map to actual register numbers
2441
+ // p0 starts at (registersSize - insSize)
2442
+ // For now, just use the index as we don't have accurate insSize
2443
+ return register.index;
2444
+ }
2445
+ };
2446
+
2447
+ // Helper to emit accumulated line/address changes as special opcode or explicit opcodes
2448
+ const emitPendingChanges = (lineDiff: number, addressDiff: number) => {
2449
+ // Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
2450
+ // Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
2451
+ if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
2452
+ const adjusted = (lineDiff + 4) + 15 * addressDiff;
2453
+ const opcode = 0x0A + adjusted;
2454
+ if (opcode <= 0xFF) {
2455
+ debugBytecode.push({
2456
+ type: 'special',
2457
+ value: opcode,
2458
+ });
2459
+ return;
2460
+ }
2461
+ }
2462
+
2463
+ // Fall back to explicit opcodes
2464
+ if (addressDiff > 0) {
2465
+ debugBytecode.push({
2466
+ type: 'advancePc',
2467
+ addressDiff,
2468
+ });
2469
+ }
2470
+ if (lineDiff !== 0) {
2471
+ debugBytecode.push({
2472
+ type: 'advanceLine',
2473
+ lineDiff,
2474
+ });
2475
+ }
2476
+ };
2477
+
2478
+ let accumulatedAddressDiff = 0;
2479
+ let accumulatedLineDiff = 0;
2480
+ let lastAddress = 0;
2481
+ let hasEmittedFirstDebugEvent = false;
2482
+
2483
+ for (let i = 0; i < annotatedOperations.length; i++) {
2484
+ const annotated = annotatedOperations[i];
2485
+ const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
2486
+
2487
+ // Accumulate address difference (only after we've started tracking)
2488
+ if (i > 0 && hasEmittedFirstDebugEvent) {
2489
+ accumulatedAddressDiff += operationAddress - lastAddress;
2490
+ }
2491
+ lastAddress = operationAddress;
2492
+
2493
+ // Process line directives first to update accumulatedLineDiff
2494
+ for (const directive of annotated.debugDirectives) {
2495
+ if (directive.type === 'line') {
2496
+ if (lineStart === undefined) {
2497
+ lineStart = directive.line;
2498
+ currentLine = directive.line;
2499
+ } else if (currentLine !== undefined) {
2500
+ accumulatedLineDiff += directive.line - currentLine;
2501
+ currentLine = directive.line;
2502
+ }
2503
+ }
2504
+ }
2505
+
2506
+ // Emit accumulated changes before any other debug events
2507
+ const hasOtherDebugEvents = annotated.debugDirectives.some(d =>
2508
+ d.type !== 'line'
2509
+ );
2510
+
2511
+ if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
2512
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
2513
+ accumulatedLineDiff = 0;
2514
+ accumulatedAddressDiff = 0;
2515
+ hasEmittedFirstDebugEvent = true;
2516
+ }
2517
+
2518
+ // Emit other debug directives
2519
+ for (const directive of annotated.debugDirectives) {
2520
+ if (directive.type === 'startLocal') {
2521
+ debugBytecode.push({
2522
+ type: 'startLocal',
2523
+ registerNum: getRegisterNum(directive.local.register),
2524
+ name: directive.local.name,
2525
+ type_: directive.local.type,
2526
+ });
2527
+ } else if (directive.type === 'endLocal') {
2528
+ debugBytecode.push({
2529
+ type: 'endLocal',
2530
+ registerNum: getRegisterNum(directive.register),
2531
+ });
2532
+ } else if (directive.type === 'restartLocal') {
2533
+ debugBytecode.push({
2534
+ type: 'restartLocal',
2535
+ registerNum: getRegisterNum(directive.register),
2536
+ });
2537
+ } else if (directive.type === 'setPrologueEnd') {
2538
+ debugBytecode.push({
2539
+ type: 'setPrologueEnd',
2540
+ });
2541
+ } else if (directive.type === 'setEpilogueBegin') {
2542
+ debugBytecode.push({
2543
+ type: 'setEpilogueBegin',
2544
+ });
2545
+ }
2546
+ }
2547
+ }
2548
+
2549
+ // Emit any remaining accumulated changes at the end
2550
+ if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
2551
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
2552
+ }
2553
+
2554
+ if (lineStart !== undefined) {
2555
+ // Extract parameter names from .param directives
2556
+ const parameterNames: Array<undefined | string> = [];
2557
+ // TODO: Extract from parameters array if needed
2558
+
2559
+ // If we have a lineStart but no bytecode, we need to emit a special opcode
2560
+ // to establish the initial state. The special value 0x0E (14) means:
2561
+ // adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
2562
+ if (debugBytecode.length === 0) {
2563
+ debugBytecode.push({
2564
+ type: 'special',
2565
+ value: 0x0A + 4, // 14 in decimal
2566
+ });
2567
+ }
2568
+
2569
+ debugInfo = {
2570
+ lineStart,
2571
+ parameterNames,
2572
+ bytecode: debugBytecode,
2573
+ };
2574
+ }
2575
+
2576
+ return {
2577
+ dalvikExecutableCode: {
2578
+ registersSize,
2579
+ insSize: -1, // TODO
2580
+ outsSize: -1, // TODO
2581
+ debugInfo,
2582
+ instructions: instructions as any, // TODO
2583
+ tries,
2584
+ // _annotations,
2585
+ },
2586
+ parameters,
2587
+ parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
2588
+ methodAnnotations: annotations,
2589
+ };
2590
+ },
2591
+ );
2592
+
2593
+ setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
2594
+
2595
+ // TODO: ???
2596
+ const sortRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
2597
+ 'if-eq',
2598
+ 'if-ne',
2599
+ ]);
2600
+
2601
+ // All 12x format operations need register reversal because:
2602
+ // - Bytecode format is B|A (B in high nibble, A in low nibble)
2603
+ // - nibblesParser returns [B, A]
2604
+ // - Smali syntax is "op vA, vB" (destination first)
2605
+ // - So we need to reverse to get [A, B]
2606
+ const reverseRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
2607
+ // Move operations
2608
+ 'move',
2609
+ 'move-wide',
2610
+ 'move-object',
2611
+
2612
+ // Unary operations (negation, bitwise not)
2613
+ 'neg-int',
2614
+ 'not-int',
2615
+ 'neg-long',
2616
+ 'not-long',
2617
+ 'neg-float',
2618
+ 'neg-double',
2619
+
2620
+ // Type conversion operations
2621
+ 'int-to-long',
2622
+ 'int-to-float',
2623
+ 'int-to-double',
2624
+ 'long-to-int',
2625
+ 'long-to-float',
2626
+ 'long-to-double',
2627
+ 'float-to-int',
2628
+ 'float-to-long',
2629
+ 'float-to-double',
2630
+ 'double-to-int',
2631
+ 'double-to-long',
2632
+ 'double-to-float',
2633
+ 'int-to-byte',
2634
+ 'int-to-char',
2635
+ 'int-to-short',
2636
+
2637
+ // Binary operations with /2addr suffix
2638
+ 'add-int/2addr',
2639
+ 'sub-int/2addr',
2640
+ 'mul-int/2addr',
2641
+ 'div-int/2addr',
2642
+ 'rem-int/2addr',
2643
+ 'and-int/2addr',
2644
+ 'or-int/2addr',
2645
+ 'xor-int/2addr',
2646
+ 'shl-int/2addr',
2647
+ 'shr-int/2addr',
2648
+ 'ushr-int/2addr',
2649
+
2650
+ 'add-long/2addr',
2651
+ 'sub-long/2addr',
2652
+ 'mul-long/2addr',
2653
+ 'div-long/2addr',
2654
+ 'rem-long/2addr',
2655
+ 'and-long/2addr',
2656
+ 'or-long/2addr',
2657
+ 'xor-long/2addr',
2658
+ 'shl-long/2addr',
2659
+ 'shr-long/2addr',
2660
+ 'ushr-long/2addr',
2661
+
2662
+ 'add-float/2addr',
2663
+ 'sub-float/2addr',
2664
+ 'mul-float/2addr',
2665
+ 'div-float/2addr',
2666
+ 'rem-float/2addr',
2667
+
2668
+ 'add-double/2addr',
2669
+ 'sub-double/2addr',
2670
+ 'mul-double/2addr',
2671
+ 'div-double/2addr',
2672
+ 'rem-double/2addr',
2673
+ ]);
2674
+
2675
+ function normalizeOperation(operation: DalvikBytecodeOperation): DalvikBytecodeOperation {
2676
+ if (
2677
+ sortRegistersOperations.has(operation.operation)
2678
+ && 'registers' in operation
2679
+ ) {
2680
+ operation.registers.sort((a, b) => a - b);
2681
+ }
2682
+
2683
+ if (
2684
+ reverseRegistersOperations.has(operation.operation)
2685
+ && 'registers' in operation
2686
+ ) {
2687
+ operation.registers.reverse();
2688
+ }
2689
+
2690
+ return operation;
2691
+ }
2692
+
2693
+ type SmaliMethod<DalvikBytecode> = {
2694
+ dalvikExecutableMethodWithAccess: DalvikExecutableMethodWithAccess<DalvikBytecode>;
2695
+ parameterAnnotations: SmaliCodeParameter[];
2696
+ methodAnnotations: SmaliAnnotation[];
2697
+ };
2698
+
2699
+ export const smaliMethodParser: Parser<SmaliMethod<DalvikBytecode>, string> = promiseCompose(
2700
+ createTupleParser([
2701
+ createExactSequenceParser('.method '),
2702
+ createOptionalParser(promiseCompose(
2703
+ createTupleParser([
2704
+ smaliAccessFlagsParser,
2705
+ smaliSingleWhitespaceParser,
2706
+ ]),
2707
+ ([accessFlags]) => accessFlags,
2708
+ )),
2709
+ smaliMemberNameParser,
2710
+ smaliMethodPrototypeParser,
2711
+ smaliLineEndPraser,
2712
+ smaliExecutableCodeParser,
2713
+ createArrayParser(smaliCodeLineParser),
2714
+ createExactSequenceParser('.end method\n'),
2715
+ ]),
2716
+ ([
2717
+ _method,
2718
+ accessFlagsOrUndefined,
2719
+ name,
2720
+ prototype,
2721
+ _newline,
2722
+ {
2723
+ dalvikExecutableCode,
2724
+ parameters,
2725
+ parameterAnnotations,
2726
+ methodAnnotations,
2727
+ },
2728
+ _lines,
2729
+ _endMethod,
2730
+ ]) => {
2731
+ const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
2732
+ let code: DalvikExecutableCode<DalvikBytecode> | undefined = dalvikExecutableCode;
2733
+
2734
+ if (accessFlags.native && code.instructions.length === 0) {
2735
+ code = undefined;
2736
+ }
2737
+
2738
+ if (code) {
2739
+ const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
2740
+
2741
+ code.insSize = insSize;
2742
+
2743
+ for (const operation of code.instructions) {
2744
+ const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation) as unknown[] as SmaliRegister[]; // TODO
2745
+
2746
+ if (smaliRegisters.length === 0) {
2747
+ continue;
2748
+ }
2749
+
2750
+ const registers: number[] = smaliRegisters.map(({ prefix, index }) => {
2751
+ if (prefix === 'v') {
2752
+ return index;
2753
+ }
2754
+
2755
+ if (prefix === 'p') {
2756
+ return code.registersSize - insSize + index;
2757
+ }
2758
+
2759
+ invariant(false, 'Expected prefix to be v or p');
2760
+ });
2761
+
2762
+ (operation as any).registers = registers; // TODO
2763
+ }
2764
+
2765
+ let outsSize = 0;
2766
+
2767
+ for (const operation of code.instructions) {
2768
+ if (!operation.operation.startsWith('invoke-')) {
2769
+ continue;
2770
+ }
2771
+
2772
+ const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
2773
+
2774
+ outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
2775
+ }
2776
+
2777
+ code.outsSize = outsSize;
2778
+
2779
+ // Populate debug info parameter names
2780
+ // The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
2781
+ if (code.debugInfo) {
2782
+ const paramCount = accessFlags.static ? insSize : insSize - 1;
2783
+ const paramNames: Array<undefined | string> = Array(paramCount).fill(undefined);
2784
+
2785
+ // Map parameter names from .param directives
2786
+ // For non-static methods: p0 is 'this', p1 is first param (index 0)
2787
+ // For static methods: p0 is first param (index 0)
2788
+ for (const param of parameters) {
2789
+ if (param.register.prefix === 'p' && param.name) {
2790
+ const pRegister = param.register.index;
2791
+ // Adjust for 'this' parameter in non-static methods
2792
+ const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
2793
+ if (paramIndex >= 0 && paramIndex < paramCount) {
2794
+ paramNames[paramIndex] = param.name;
2795
+ }
2796
+ }
2797
+ }
2798
+
2799
+ code.debugInfo.parameterNames = paramNames;
2800
+ }
2801
+ }
2802
+
2803
+ return {
2804
+ dalvikExecutableMethodWithAccess: {
2805
+ accessFlags,
2806
+ method: {
2807
+ class: 'FILLED_LATER',
2808
+ prototype,
2809
+ name,
2810
+ },
2811
+ code: code?.instructions.length
2812
+ ? {
2813
+ ...code,
2814
+ instructions: code.instructions.map(normalizeOperation),
2815
+ }
2816
+ : undefined,
2817
+ },
2818
+ parameterAnnotations,
2819
+ methodAnnotations,
2820
+ };
2821
+ },
2822
+ );
2823
+
2824
+ setParserName(smaliMethodParser, 'smaliMethodParser');
2825
+
2826
+ type SmaliMethods = Pick<DalvikExecutableClassData<DalvikBytecode>, 'directMethods' | 'virtualMethods'> & {
2827
+ parameterAnnotations: DalvikExecutableClassParameterAnnotation[];
2828
+ methodAnnotations: DalvikExecutableClassMethodAnnotation[];
2829
+ };
2830
+
2831
+ const smaliMethodsParser: Parser<SmaliMethods, string> = promiseCompose(
2832
+ createArrayParser<string[] | SmaliMethod<DalvikBytecode>, string>(createDisjunctionParser<string[] | SmaliMethod<DalvikBytecode>, string, string>([
2833
+ smaliMethodParser,
2834
+ smaliCommentsOrNewlinesParser,
2835
+ ])),
2836
+ methodsAndComments => {
2837
+ let type: 'directMethod' | 'virtualMethod' = 'directMethod';
2838
+
2839
+ const directMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
2840
+ const virtualMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
2841
+
2842
+ const parameterAnnotations: DalvikExecutableClassParameterAnnotation[] = [];
2843
+ const methodAnnotations: DalvikExecutableClassMethodAnnotation[] = [];
2844
+
2845
+ function pushParameterAnnotation(annotation: DalvikExecutableClassParameterAnnotation) {
2846
+ if (annotation.annotations.length === 0) {
2847
+ return;
2848
+ }
2849
+
2850
+ const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(
2851
+ parameterAnnotation.method,
2852
+ annotation.method,
2853
+ ));
2854
+
2855
+ if (existingMethod) {
2856
+ for (const [ index, parameterAnnotations_ ] of annotation.annotations.entries()) {
2857
+ const existingParameterAnnotations = existingMethod.annotations.at(index);
2858
+
2859
+ if (existingParameterAnnotations) {
2860
+ existingParameterAnnotations.push(...parameterAnnotations_);
2861
+ } else {
2862
+ existingMethod.annotations[index] = parameterAnnotations_;
2863
+ }
2864
+ }
2865
+
2866
+ return;
2867
+ }
2868
+
2869
+ parameterAnnotations.push(annotation);
2870
+ }
2871
+
2872
+ for (const methodOrComment of methodsAndComments) {
2873
+ if (Array.isArray(methodOrComment)) {
2874
+ for (const comment of methodOrComment) {
2875
+ if (comment === ' direct methods') {
2876
+ type = 'directMethod';
2877
+ }
2878
+
2879
+ if (comment === ' virtual methods') {
2880
+ type = 'virtualMethod';
2881
+ }
2882
+ }
2883
+
2884
+ continue;
2885
+ }
2886
+
2887
+ invariant(typeof methodOrComment === 'object', 'Expected method or comment');
2888
+
2889
+ const method = methodOrComment;
2890
+
2891
+ if (method.methodAnnotations.length > 0) {
2892
+ methodAnnotations.push({
2893
+ method: method.dalvikExecutableMethodWithAccess.method,
2894
+ annotations: method.methodAnnotations.map(annotation => ({
2895
+ type: annotation.type,
2896
+ visibility: annotation.visibility,
2897
+ elements: annotation.elements.map(element => ({
2898
+ name: element.name,
2899
+ value: convertToTaggedEncodedValue(element.value),
2900
+ })),
2901
+ })),
2902
+ });
2903
+ }
2904
+
2905
+ // Create an annotations array for all parameters, not just those with annotations
2906
+ // In smali, instance methods have p0 as 'this', p1 as first param, etc.
2907
+ // But DEX parameter annotations only include the actual parameters (not 'this')
2908
+ const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
2909
+ const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
2910
+
2911
+ const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
2912
+ const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
2913
+ const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
2914
+
2915
+ if (parameterAnnotation?.annotation) {
2916
+ return [ {
2917
+ type: parameterAnnotation.annotation.type,
2918
+ visibility: parameterAnnotation.annotation.visibility,
2919
+ elements: parameterAnnotation.annotation.elements.map(element => ({
2920
+ name: element.name,
2921
+ value: convertToTaggedEncodedValue(element.value),
2922
+ })),
2923
+ } ];
2924
+ }
2925
+
2926
+ return [];
2927
+ });
2928
+
2929
+ // Only push parameter annotations if there are some actual annotations
2930
+ if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
2931
+ pushParameterAnnotation({
2932
+ method: method.dalvikExecutableMethodWithAccess.method,
2933
+ annotations: allParameterAnnotations,
2934
+ });
2935
+ }
2936
+
2937
+ if (type === 'directMethod') {
2938
+ directMethods.push(method.dalvikExecutableMethodWithAccess);
2939
+
2940
+ continue;
2941
+ }
2942
+
2943
+ if (type === 'virtualMethod') {
2944
+ virtualMethods.push(method.dalvikExecutableMethodWithAccess);
2945
+
2946
+ continue;
2947
+ }
2948
+
2949
+ invariant(false, 'Expected method type');
2950
+ }
2951
+
2952
+ // Sort parameter annotations by method index in the combined method list
2953
+ // to match the order in the DEX file's annotations directory
2954
+ const allMethods = [...directMethods, ...virtualMethods];
2955
+ parameterAnnotations.sort((a, b) => {
2956
+ const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
2957
+ const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
2958
+ return indexA - indexB;
2959
+ });
2960
+
2961
+ return {
2962
+ directMethods,
2963
+ virtualMethods,
2964
+ parameterAnnotations,
2965
+ methodAnnotations,
2966
+ };
2967
+ },
2968
+ );
2969
+
2970
+ setParserName(smaliMethodsParser, 'smaliMethodsParser');
2971
+
2972
+ export const smaliParser: Parser<DalvikExecutableClassDefinition<DalvikBytecode>, string> = promiseCompose(
2973
+ createTupleParser([
2974
+ smaliClassDeclarationParser,
2975
+ smaliSuperDeclarationParser,
2976
+ createOptionalParser(smaliSourceDeclarationParser),
2977
+ createOptionalParser(promiseCompose(
2978
+ createTupleParser([
2979
+ smaliCommentsOrNewlinesParser,
2980
+ createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
2981
+ ]),
2982
+ ([
2983
+ _commentsOrNewlines,
2984
+ interfaces,
2985
+ ]) => interfaces,
2986
+ )),
2987
+ createOptionalParser(promiseCompose(
2988
+ createTupleParser([
2989
+ smaliCommentsOrNewlinesParser,
2990
+ createSeparatedNonEmptyArrayParser<SmaliAnnotation, string>(
2991
+ smaliAnnotationParser,
2992
+ smaliCommentsOrNewlinesParser,
2993
+ ),
2994
+ ]),
2995
+ ([
2996
+ _commentsOrNewlines,
2997
+ classAnnotations,
2998
+ ]) => classAnnotations,
2999
+ )),
3000
+ createOptionalParser(promiseCompose(
3001
+ createTupleParser([
3002
+ smaliCommentsOrNewlinesParser,
3003
+ smaliFieldsParser,
3004
+ ]),
3005
+ ([
3006
+ _commentsOrNewlines,
3007
+ fields,
3008
+ ]) => fields,
3009
+ )),
3010
+ smaliMethodsParser,
3011
+ ]),
3012
+ ([
3013
+ {
3014
+ accessFlags,
3015
+ class: class_,
3016
+ },
3017
+ {
3018
+ superclass,
3019
+ },
3020
+ sourceFileObject,
3021
+ interfaces,
3022
+ classAnnotations,
3023
+ smaliFields,
3024
+ methods,
3025
+ ]) => {
3026
+ const sourceFile = sourceFileObject?.sourceFile;
3027
+ // Create staticValues array matching DEX format:
3028
+ // - Find the last static field with a non-default initializer
3029
+ // - Create array up to that index with values/nulls
3030
+ // - Fields after the last initializer are not included
3031
+ // - Default values (false, null) are not considered initializers
3032
+ const staticFieldsList = smaliFields?.staticFields ?? [];
3033
+ let lastIndexWithInitializer = -1;
3034
+ for (let i = staticFieldsList.length - 1; i >= 0; i--) {
3035
+ const initValue = staticFieldsList[i].initialValue;
3036
+ // Only consider non-default values as initializers
3037
+ // Numbers, bigints, strings, and true are non-default
3038
+ // false and null are default values
3039
+ if (initValue !== undefined && typeof initValue === 'number') {
3040
+ lastIndexWithInitializer = i;
3041
+ break;
3042
+ }
3043
+
3044
+ if (initValue !== undefined && typeof initValue === 'bigint') {
3045
+ lastIndexWithInitializer = i;
3046
+ break;
3047
+ }
3048
+
3049
+ if (initValue !== undefined && typeof initValue === 'string') {
3050
+ lastIndexWithInitializer = i;
3051
+ break;
3052
+ }
3053
+
3054
+ if (initValue === true) {
3055
+ lastIndexWithInitializer = i;
3056
+ break;
3057
+ }
3058
+ }
3059
+
3060
+ const staticValues = lastIndexWithInitializer === -1
3061
+ ? []
3062
+ : staticFieldsList
3063
+ .slice(0, lastIndexWithInitializer + 1)
3064
+ .map(smaliField => {
3065
+ const fieldType = smaliField.field.field.type;
3066
+
3067
+ if (smaliField.initialValue === undefined) {
3068
+ // For integer types without initializer, DEX stores 0
3069
+ if (fieldType === 'I') {
3070
+ return { type: 'int' as const, value: 0 };
3071
+ }
3072
+ if (fieldType === 'B') {
3073
+ return { type: 'byte' as const, value: 0 };
3074
+ }
3075
+ if (fieldType === 'S') {
3076
+ return { type: 'short' as const, value: 0 };
3077
+ }
3078
+ if (fieldType === 'C') {
3079
+ return { type: 'char' as const, value: 0 };
3080
+ }
3081
+ // For long types without initializer, DEX stores 0n
3082
+ if (fieldType === 'J') {
3083
+ return { type: 'long' as const, value: 0n };
3084
+ }
3085
+ // For float/double types without initializer, DEX stores 0
3086
+ if (fieldType === 'F') {
3087
+ return { type: 'float' as const, value: 0 };
3088
+ }
3089
+ if (fieldType === 'D') {
3090
+ return { type: 'double' as const, value: 0 };
3091
+ }
3092
+ // For boolean types without initializer, DEX stores false
3093
+ if (fieldType === 'Z') {
3094
+ return { type: 'boolean' as const, value: false };
3095
+ }
3096
+ // For other types (reference types, etc.), return null
3097
+ return { type: 'null' as const, value: null };
3098
+ }
3099
+
3100
+ // Numeric values are stored in static values array
3101
+ if (typeof smaliField.initialValue === 'number') {
3102
+ // Convert to BigInt for long (J) types
3103
+ if (fieldType === 'J') {
3104
+ return { type: 'long' as const, value: BigInt(smaliField.initialValue) };
3105
+ }
3106
+ if (fieldType === 'B') {
3107
+ return { type: 'byte' as const, value: smaliField.initialValue };
3108
+ }
3109
+ if (fieldType === 'S') {
3110
+ return { type: 'short' as const, value: smaliField.initialValue };
3111
+ }
3112
+ if (fieldType === 'C') {
3113
+ return { type: 'char' as const, value: smaliField.initialValue };
3114
+ }
3115
+ if (fieldType === 'F') {
3116
+ return { type: 'float' as const, value: smaliField.initialValue };
3117
+ }
3118
+ if (fieldType === 'D') {
3119
+ return { type: 'double' as const, value: smaliField.initialValue };
3120
+ }
3121
+ // Default to int for other numeric types
3122
+ return { type: 'int' as const, value: smaliField.initialValue };
3123
+ }
3124
+
3125
+ // BigInt values for long (J) types
3126
+ if (typeof smaliField.initialValue === 'bigint') {
3127
+ return { type: 'long' as const, value: smaliField.initialValue };
3128
+ }
3129
+
3130
+ // String values should be stored as string type
3131
+ if (typeof smaliField.initialValue === 'string') {
3132
+ return { type: 'string' as const, value: smaliField.initialValue };
3133
+ }
3134
+
3135
+ // Boolean true is a non-default value and should be in staticValues
3136
+ if (smaliField.initialValue === true) {
3137
+ return { type: 'boolean' as const, value: true };
3138
+ }
3139
+
3140
+ // Boolean false and null are default values
3141
+ // For boolean fields with explicit false, return false
3142
+ if (smaliField.initialValue === false && fieldType === 'Z') {
3143
+ return { type: 'boolean' as const, value: false };
3144
+ }
3145
+
3146
+ // For null or other default values, return null
3147
+ return { type: 'null' as const, value: null };
3148
+ });
3149
+ const fields = {
3150
+ staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
3151
+ instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
3152
+ };
3153
+
3154
+ // Sort fields to match DEX file order (by field name, then type)
3155
+ // This matches the field order in class_data_item in DEX files
3156
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
3157
+ const sortFields = (fieldsList: DalvikExecutableFieldWithAccess[]) => {
3158
+ fieldsList.sort((a, b) => {
3159
+ // First by field name (case-sensitive comparison)
3160
+ if (a.field.name !== b.field.name) {
3161
+ return a.field.name < b.field.name ? -1 : 1;
3162
+ }
3163
+ // Then by field type (case-sensitive comparison)
3164
+ return a.field.type < b.field.type ? -1 : 1;
3165
+ });
3166
+ };
3167
+
3168
+ sortFields(fields.staticFields);
3169
+ sortFields(fields.instanceFields);
3170
+
3171
+ const annotations: DalvikExecutableClassAnnotations = {
3172
+ classAnnotations: (classAnnotations ?? []).map(annotation => ({
3173
+ type: annotation.type,
3174
+ visibility: annotation.visibility,
3175
+ elements: annotation.elements.map(element => ({
3176
+ name: element.name,
3177
+ value: convertToTaggedEncodedValue(element.value),
3178
+ })),
3179
+ })),
3180
+ fieldAnnotations: [],
3181
+ methodAnnotations: methods.methodAnnotations,
3182
+ parameterAnnotations: methods.parameterAnnotations,
3183
+ };
3184
+
3185
+ for (const smaliField of [ ...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? []) ]) {
3186
+ if (smaliField.annotations.length === 0) {
3187
+ continue;
3188
+ }
3189
+
3190
+ const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(
3191
+ fieldAnnotation.field,
3192
+ smaliField.field.field,
3193
+ ));
3194
+
3195
+ if (existingFieldAnnotations) {
3196
+ existingFieldAnnotations.annotations ??= [];
3197
+ existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
3198
+ type: annotation.type,
3199
+ visibility: annotation.visibility,
3200
+ elements: annotation.elements.map(element => ({
3201
+ name: element.name,
3202
+ value: convertToTaggedEncodedValue(element.value),
3203
+ })),
3204
+ })));
3205
+ continue;
3206
+ }
3207
+
3208
+ annotations.fieldAnnotations.push({
3209
+ field: smaliField.field.field,
3210
+ annotations: smaliField.annotations.map(annotation => ({
3211
+ type: annotation.type,
3212
+ visibility: annotation.visibility,
3213
+ elements: annotation.elements.map(element => ({
3214
+ name: element.name,
3215
+ value: convertToTaggedEncodedValue(element.value),
3216
+ })),
3217
+ })),
3218
+ });
3219
+ }
3220
+
3221
+ // Sort field annotations to match DEX file order (by field name, then type)
3222
+ // This matches the annotations_directory_item field_annotations order in DEX files
3223
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
3224
+ annotations.fieldAnnotations.sort((a, b) => {
3225
+ // First by field name (case-sensitive comparison)
3226
+ if (a.field.name !== b.field.name) {
3227
+ return a.field.name < b.field.name ? -1 : 1;
3228
+ }
3229
+ // Then by field type (case-sensitive comparison)
3230
+ return a.field.type < b.field.type ? -1 : 1;
3231
+ });
3232
+
3233
+ // Compute synthetic flag from members if not explicitly set
3234
+ // This matches baksmali behavior where synthetic flag at class level is not output
3235
+ // but can be inferred from all members being synthetic
3236
+ const allMembers = [
3237
+ ...fields.staticFields,
3238
+ ...fields.instanceFields,
3239
+ ...methods.directMethods,
3240
+ // Note: virtualMethods are not included, matching DEX parser behavior
3241
+ ];
3242
+
3243
+ const allMembersAreSynthetic = (
3244
+ allMembers.every(member => member.accessFlags.synthetic)
3245
+ && allMembers.length > 0
3246
+ );
3247
+
3248
+ const finalAccessFlags = {
3249
+ ...accessFlags,
3250
+ // Use the synthetic flag from the class declaration, or compute it from members if not set
3251
+ synthetic: accessFlags.synthetic || allMembersAreSynthetic,
3252
+ };
3253
+
3254
+ return {
3255
+ accessFlags: finalAccessFlags,
3256
+ class: class_,
3257
+ superclass,
3258
+ sourceFile,
3259
+ annotations: (
3260
+ (
3261
+ annotations.classAnnotations?.length
3262
+ || annotations.fieldAnnotations?.length
3263
+ || annotations.methodAnnotations?.length
3264
+ || annotations.parameterAnnotations?.length
3265
+ )
3266
+ ? ({
3267
+ classAnnotations: annotations.classAnnotations,
3268
+ fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
3269
+ ...fieldAnnotation,
3270
+ field: {
3271
+ ...fieldAnnotation.field,
3272
+ class: class_,
3273
+ },
3274
+ })),
3275
+ methodAnnotations: annotations.methodAnnotations
3276
+ .map(methodAnnotation => ({
3277
+ ...methodAnnotation,
3278
+ method: {
3279
+ ...methodAnnotation.method,
3280
+ class: class_,
3281
+ },
3282
+ }))
3283
+ // Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
3284
+ // This matches the method_idx order in the DEX file's method_id table
3285
+ .sort((a, b) => {
3286
+ // Sort by method name first (using code point comparison, not locale-aware)
3287
+ if (a.method.name !== b.method.name) {
3288
+ return a.method.name < b.method.name ? -1 : 1;
3289
+ }
3290
+ // Then by shorty (prototype signature)
3291
+ return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
3292
+ }),
3293
+ parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
3294
+ ...parameterAnnotation,
3295
+ method: {
3296
+ ...parameterAnnotation.method,
3297
+ class: class_,
3298
+ },
3299
+ })),
3300
+ })
3301
+ : undefined
3302
+ ),
3303
+ classData: (
3304
+ (
3305
+ methods.directMethods.length > 0
3306
+ || methods.virtualMethods.length > 0
3307
+ || (fields?.staticFields.length ?? 0)
3308
+ || (fields?.instanceFields.length ?? 0)
3309
+ )
3310
+ ? ({
3311
+ directMethods: methods.directMethods.map(method => ({
3312
+ ...method,
3313
+ method: {
3314
+ ...method.method,
3315
+ class: class_,
3316
+ },
3317
+ })),
3318
+ virtualMethods: methods.virtualMethods.map(method => ({
3319
+ ...method,
3320
+ method: {
3321
+ ...method.method,
3322
+ class: class_,
3323
+ },
3324
+ })),
3325
+ staticFields: (fields?.staticFields ?? []).map(field => ({
3326
+ ...field,
3327
+ field: {
3328
+ ...field.field,
3329
+ class: class_,
3330
+ },
3331
+ })),
3332
+ instanceFields: (fields?.instanceFields ?? []).map(field => ({
3333
+ ...field,
3334
+ field: {
3335
+ ...field.field,
3336
+ class: class_,
3337
+ },
3338
+ })),
3339
+ })
3340
+ : undefined
3341
+ ),
3342
+ interfaces: interfaces ?? [],
3343
+ staticValues,
3344
+ };
3345
+ },
3346
+ );
3347
+
3348
+ setParserName(smaliParser, 'smaliParser');