@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,2081 @@
1
+ import invariant from 'invariant';
2
+ import { dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
3
+ import { dalvikExecutableAccessFlagsDefault, dalvikExecutableFieldEquals, dalvikExecutableMethodEquals, isDalvikExecutableField, isDalvikExecutableMethod, } from './dalvikExecutable.js';
4
+ import { createExactSequenceParser } from './exactSequenceParser.js';
5
+ import { cloneParser, setParserName } from './parser.js';
6
+ import { promiseCompose } from './promiseCompose.js';
7
+ import { createTupleParser } from './tupleParser.js';
8
+ import { createUnionParser } from './unionParser.js';
9
+ import { createArrayParser } from './arrayParser.js';
10
+ import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
11
+ import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
12
+ import { createOptionalParser } from './optionalParser.js';
13
+ import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
14
+ import { createSeparatedArrayParser } from './separatedArrayParser.js';
15
+ import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
16
+ import { createDisjunctionParser } from './disjunctionParser.js';
17
+ import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
18
+ import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
19
+ import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
20
+ import { parserCreatorCompose } from './parserCreatorCompose.js';
21
+ import { createElementParser } from './elementParser.js';
22
+ import { createParserAccessorParser } from './parserAccessorParser.js';
23
+ function shortyFromLongy(longy) {
24
+ if (longy.startsWith('[')) {
25
+ return 'L';
26
+ }
27
+ return longy.slice(0, 1);
28
+ }
29
+ function getOperationFormatSize(operation) {
30
+ if (operation.operation === 'packed-switch-payload') {
31
+ return (operation.branchOffsetIndices.length * 2) + 4;
32
+ }
33
+ if (operation.operation === 'sparse-switch-payload') {
34
+ return (operation.branchOffsetIndices.length * 4) + 2;
35
+ }
36
+ if (operation.operation === 'fill-array-data-payload') {
37
+ const dataSize = operation.data.length; // in bytes
38
+ const paddingSize = dataSize % 2; // 1 if odd, 0 if even
39
+ const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
40
+ return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
41
+ }
42
+ const operationFormat = operationFormats[operation.operation];
43
+ invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
44
+ const operationSize = formatSizes[operationFormat];
45
+ invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
46
+ return operationSize;
47
+ }
48
+ // Helper function to convert raw annotation element values to tagged encoded values
49
+ function convertToTaggedEncodedValue(wrappedValue) {
50
+ const { kind, value } = wrappedValue;
51
+ // Handle type descriptors
52
+ if (kind === 'type') {
53
+ if (Array.isArray(value)) {
54
+ // Array of type descriptors
55
+ return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
56
+ }
57
+ // Single type descriptor
58
+ return { type: 'type', value };
59
+ }
60
+ // Handle string literals
61
+ if (kind === 'string') {
62
+ if (Array.isArray(value)) {
63
+ // Array of strings
64
+ return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
65
+ }
66
+ // Single string
67
+ return { type: 'string', value };
68
+ }
69
+ // Handle enum values
70
+ if (kind === 'enum') {
71
+ if (Array.isArray(value)) {
72
+ // Array of enum values
73
+ return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
74
+ }
75
+ // Single enum value
76
+ return { type: 'enum', value };
77
+ }
78
+ // Handle raw values (everything else)
79
+ // Handle null
80
+ if (value === null) {
81
+ return { type: 'null', value: null };
82
+ }
83
+ // Handle boolean
84
+ if (typeof value === 'boolean') {
85
+ return { type: 'boolean', value };
86
+ }
87
+ // Handle numbers - we need to determine the type based on context
88
+ // For annotation elements from smali, we'll use 'int' as the default
89
+ if (typeof value === 'number') {
90
+ // Check if it's a float or integer
91
+ if (!Number.isInteger(value)) {
92
+ return { type: 'float', value };
93
+ }
94
+ // For integers, default to 'int' type
95
+ // The actual byte/short/int distinction would need more context
96
+ return { type: 'int', value };
97
+ }
98
+ // Handle bigint
99
+ if (typeof value === 'bigint') {
100
+ return { type: 'long', value };
101
+ }
102
+ // Handle DalvikExecutableField (including enums)
103
+ if (isDalvikExecutableField(value)) {
104
+ // Note: We can't distinguish between 'field' and 'enum' without more context
105
+ // Default to 'field' type - the context in smali might help distinguish later
106
+ return { type: 'field', value };
107
+ }
108
+ // Handle DalvikExecutableMethod
109
+ if (isDalvikExecutableMethod(value)) {
110
+ return { type: 'method', value };
111
+ }
112
+ // Handle DalvikExecutablePrototype (method type)
113
+ if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
114
+ return { type: 'methodType', value: value };
115
+ }
116
+ // Handle annotations (subannotations)
117
+ if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
118
+ const subannotation = value;
119
+ // Convert subannotation to DalvikExecutableAnnotation
120
+ const annotation = {
121
+ type: subannotation.type,
122
+ visibility: 'build', // Subannotations default to 'build' visibility
123
+ elements: subannotation.elements.map(element => ({
124
+ name: element.name,
125
+ value: convertToTaggedEncodedValue(element.value),
126
+ })),
127
+ };
128
+ return { type: 'annotation', value: annotation };
129
+ }
130
+ // Handle arrays
131
+ if (Array.isArray(value)) {
132
+ return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
133
+ }
134
+ // Fallback - this shouldn't happen in well-formed smali
135
+ throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
136
+ }
137
+ const smaliNewlinesParser = promiseCompose(createNonEmptyArrayParser(createExactSequenceParser('\n')), _newlines => undefined);
138
+ const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
139
+ const smaliWhitespaceParser = promiseCompose(createArrayParser(smaliSingleWhitespaceParser), _indentation => undefined);
140
+ const smaliSingleIndentationParser = createExactSequenceParser(' ');
141
+ const smaliIndentationParser = promiseCompose(createArrayParser(smaliSingleIndentationParser), _indentation => undefined);
142
+ export const smaliCommentParser = promiseCompose(createTupleParser([
143
+ createExactSequenceParser('#'),
144
+ (async (parserContext) => {
145
+ const characters = [];
146
+ while (true) {
147
+ const character = await parserContext.peek(0);
148
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
149
+ invariant(character !== undefined, 'Unexpected end of input');
150
+ if (character !== '\n') {
151
+ characters.push(character);
152
+ parserContext.skip(1);
153
+ continue;
154
+ }
155
+ parserContext.skip(1);
156
+ break;
157
+ }
158
+ return characters.join('');
159
+ }),
160
+ ]), ([_hash, comment,]) => comment);
161
+ const smaliIndentedCommentParser = promiseCompose(createTupleParser([
162
+ createNonEmptyArrayParser(smaliSingleIndentationParser),
163
+ smaliCommentParser,
164
+ ]), ([_indentation, comment,]) => comment);
165
+ const smaliCommentsOrNewlinesParser = promiseCompose(createArrayParser(createUnionParser([
166
+ smaliNewlinesParser,
167
+ smaliIndentedCommentParser,
168
+ smaliCommentParser,
169
+ ])), newlinesOrComments => newlinesOrComments.filter((newlineOrComment) => typeof newlineOrComment === 'string'));
170
+ const smaliLineEndPraser = promiseCompose(createTupleParser([
171
+ createOptionalParser(smaliWhitespaceParser),
172
+ createUnionParser([
173
+ smaliNewlinesParser,
174
+ smaliCommentParser,
175
+ ]),
176
+ ]), ([_optionalWhitespace, newlineOrComment,]) => newlineOrComment);
177
+ const smaliIdentifierParser = async (parserContext) => {
178
+ const characters = [];
179
+ while (true) {
180
+ const character = await parserContext.peek(0);
181
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
182
+ invariant(character !== undefined, 'Unexpected end of input');
183
+ if (character === '_'
184
+ || (character >= 'a' && character <= 'z')
185
+ || (character >= 'A' && character <= 'Z')
186
+ || (character >= '0' && character <= '9')) {
187
+ parserContext.skip(1);
188
+ characters.push(character);
189
+ continue;
190
+ }
191
+ parserContext.invariant(characters.length > 0, 'Expected at least one character');
192
+ break;
193
+ }
194
+ return characters.join('');
195
+ };
196
+ setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
197
+ const elementParser = createElementParser();
198
+ const smaliHexNumberParser = promiseCompose(createTupleParser([
199
+ createOptionalParser(createExactSequenceParser('-')),
200
+ createExactSequenceParser('0x'),
201
+ createArrayParser(parserCreatorCompose(() => elementParser, character => async (parserContext) => {
202
+ parserContext.invariant(((character >= '0' && character <= '9')
203
+ || (character >= 'a' && character <= 'f')
204
+ || (character >= 'A' && character <= 'F')), 'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"', character);
205
+ return character;
206
+ })()),
207
+ createOptionalParser(createExactSequenceParser('L')),
208
+ ]), ([optionalMinus, _0x, valueCharacters, optionalL,]) => {
209
+ const value = valueCharacters.join('');
210
+ // If the 'L' suffix is present, use BigInt for long values
211
+ if (optionalL) {
212
+ const sign = optionalMinus ? -1n : 1n;
213
+ return sign * BigInt('0x' + value);
214
+ }
215
+ const sign = optionalMinus ? -1 : 1;
216
+ return sign * Number.parseInt(value, 16);
217
+ });
218
+ const smaliNumberParser = createUnionParser([
219
+ promiseCompose(createTupleParser([
220
+ createNegativeLookaheadParser(createUnionParser([
221
+ createExactSequenceParser('0x'),
222
+ createExactSequenceParser('-0x'),
223
+ ])),
224
+ jsonNumberParser,
225
+ createOptionalParser(createUnionParser([
226
+ createExactSequenceParser('f'),
227
+ createExactSequenceParser('F'),
228
+ ])),
229
+ ]), ([_not0x, number, optionalFloatSuffix,]) => {
230
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
231
+ // to match what would be stored in a DEX file
232
+ if (optionalFloatSuffix) {
233
+ const float32Array = new Float32Array(1);
234
+ float32Array[0] = number;
235
+ return float32Array[0];
236
+ }
237
+ return number;
238
+ }),
239
+ promiseCompose(smaliHexNumberParser, value => {
240
+ // For smaliNumberParser, we need to ensure we return a number
241
+ // BigInt values from hex parser should be converted if they fit in number range
242
+ if (typeof value === 'bigint') {
243
+ // This shouldn't happen in contexts where smaliNumberParser is used
244
+ // (registers, line numbers, etc) but if it does, convert to number
245
+ return Number(value);
246
+ }
247
+ return value;
248
+ }),
249
+ ]);
250
+ setParserName(smaliNumberParser, 'smaliNumberParser');
251
+ // Parser for field initial values that can include BigInt
252
+ const smaliFieldValueParser = createUnionParser([
253
+ promiseCompose(createTupleParser([
254
+ createNegativeLookaheadParser(createUnionParser([
255
+ createExactSequenceParser('0x'),
256
+ createExactSequenceParser('-0x'),
257
+ ])),
258
+ jsonNumberParser,
259
+ createOptionalParser(createUnionParser([
260
+ createExactSequenceParser('f'),
261
+ createExactSequenceParser('F'),
262
+ ])),
263
+ ]), ([_not0x, number, optionalFloatSuffix,]) => {
264
+ // If there's an 'f' or 'F' suffix, convert to 32-bit float precision
265
+ // to match what would be stored in a DEX file
266
+ if (optionalFloatSuffix) {
267
+ const float32Array = new Float32Array(1);
268
+ float32Array[0] = number;
269
+ return float32Array[0];
270
+ }
271
+ return number;
272
+ }),
273
+ smaliHexNumberParser,
274
+ ]);
275
+ const smaliQuotedStringParser = promiseCompose(jsonStringParser, string => string.replaceAll(String.raw `\'`, '\''));
276
+ // Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
277
+ const smaliCharacterLiteralParser = promiseCompose(createTupleParser([
278
+ createExactSequenceParser('\''),
279
+ createDisjunctionParser([
280
+ // Handle escape sequences (must come before regular characters)
281
+ promiseCompose(createExactSequenceParser(String.raw `\\`), () => '\\'),
282
+ promiseCompose(createExactSequenceParser(String.raw `\'`), () => '\''),
283
+ promiseCompose(createExactSequenceParser(String.raw `\"`), () => '"'),
284
+ promiseCompose(createExactSequenceParser(String.raw `\n`), () => '\n'),
285
+ promiseCompose(createExactSequenceParser(String.raw `\r`), () => '\r'),
286
+ promiseCompose(createExactSequenceParser(String.raw `\t`), () => '\t'),
287
+ promiseCompose(createExactSequenceParser(String.raw `\b`), () => '\b'),
288
+ promiseCompose(createExactSequenceParser(String.raw `\f`), () => '\f'),
289
+ // Handle regular characters (not a single quote)
290
+ parserCreatorCompose(() => createElementParser(), character => async (parserContext) => {
291
+ parserContext.invariant(character !== '\'', 'Unexpected single quote');
292
+ return character;
293
+ })(),
294
+ ]),
295
+ createExactSequenceParser('\''),
296
+ ]), ([, character]) => character.charCodeAt(0));
297
+ setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
298
+ // Parser that matches identifier continuation characters (letters, digits, $, -, _)
299
+ const smaliIdentifierContinuationParser = async (parserContext) => {
300
+ const character = await parserContext.peek(0);
301
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
302
+ invariant(character !== undefined, 'Unexpected end of input');
303
+ parserContext.invariant((character >= 'a' && character <= 'z')
304
+ || (character >= 'A' && character <= 'Z')
305
+ || (character >= '0' && character <= '9')
306
+ || character === '$'
307
+ || character === '-'
308
+ || character === '_', 'Expected identifier continuation character, got "%s"', character);
309
+ parserContext.skip(1);
310
+ return character;
311
+ };
312
+ setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
313
+ // Helper to create an access flag parser with word boundary check
314
+ const createAccessFlagParser = (keyword) => promiseCompose(createTupleParser([
315
+ createExactSequenceParser(keyword),
316
+ createNegativeLookaheadParser(smaliIdentifierContinuationParser),
317
+ ]), ([flag]) => flag);
318
+ const smaliAccessFlagsParser = promiseCompose(createSeparatedArrayParser(createUnionParser([
319
+ createAccessFlagParser('public'),
320
+ createAccessFlagParser('protected'),
321
+ createAccessFlagParser('private'),
322
+ createAccessFlagParser('final'),
323
+ createAccessFlagParser('bridge'),
324
+ createAccessFlagParser('synthetic'),
325
+ createAccessFlagParser('varargs'),
326
+ createAccessFlagParser('static'),
327
+ createAccessFlagParser('constructor'),
328
+ createAccessFlagParser('abstract'),
329
+ createAccessFlagParser('native'),
330
+ createAccessFlagParser('volatile'),
331
+ createAccessFlagParser('transient'),
332
+ createAccessFlagParser('synchronized'),
333
+ createAccessFlagParser('declared-synchronized'),
334
+ createAccessFlagParser('strict'),
335
+ createAccessFlagParser('interface'),
336
+ createAccessFlagParser('annotation'),
337
+ createAccessFlagParser('enum'),
338
+ ]), smaliSingleWhitespaceParser), accessFlagNames => {
339
+ const accessFlags = dalvikExecutableAccessFlagsDefault();
340
+ for (const accessFlagName of accessFlagNames) {
341
+ if (accessFlagName === 'declared-synchronized') {
342
+ accessFlags.declaredSynchronized = true;
343
+ }
344
+ else {
345
+ accessFlags[accessFlagName] = true;
346
+ }
347
+ }
348
+ return accessFlags;
349
+ });
350
+ const smaliClassDeclarationParser = promiseCompose(createTupleParser([
351
+ smaliCommentsOrNewlinesParser,
352
+ createExactSequenceParser('.class '),
353
+ createOptionalParser(promiseCompose(createTupleParser([
354
+ smaliAccessFlagsParser,
355
+ smaliSingleWhitespaceParser,
356
+ ]), ([accessFlags,]) => accessFlags)),
357
+ smaliTypeDescriptorParser,
358
+ smaliLineEndPraser,
359
+ ]), ([_commentsOrNewlines, _dotClass, accessFlags = dalvikExecutableAccessFlagsDefault(), classPath,]) => ({
360
+ accessFlags,
361
+ class: classPath,
362
+ }));
363
+ setParserName(smaliClassDeclarationParser, 'smaliClassDeclarationParser');
364
+ const smaliSuperDeclarationParser = promiseCompose(createTupleParser([
365
+ createExactSequenceParser('.super '),
366
+ smaliTypeDescriptorParser,
367
+ smaliLineEndPraser,
368
+ ]), ([_super, superclass, _newline,]) => ({
369
+ superclass,
370
+ }));
371
+ setParserName(smaliSuperDeclarationParser, 'smaliSuperDeclarationParser');
372
+ const smaliInterfaceDeclarationParser = promiseCompose(createTupleParser([
373
+ createExactSequenceParser('.implements '),
374
+ smaliTypeDescriptorParser,
375
+ smaliLineEndPraser,
376
+ ]), ([_interface, interface_, _newline,]) => interface_);
377
+ setParserName(smaliInterfaceDeclarationParser, 'smaliInterfaceDeclarationParser');
378
+ const smaliSourceDeclarationParser = promiseCompose(createTupleParser([
379
+ createExactSequenceParser('.source '),
380
+ smaliQuotedStringParser,
381
+ smaliLineEndPraser,
382
+ ]), ([_source, sourceFile, _newline,]) => ({
383
+ sourceFile,
384
+ }));
385
+ const smaliEnumValueParser = promiseCompose(createTupleParser([
386
+ createExactSequenceParser('.enum '),
387
+ smaliTypeDescriptorParser,
388
+ createExactSequenceParser('->'),
389
+ smaliMemberNameParser,
390
+ createExactSequenceParser(':'),
391
+ smaliTypeDescriptorParser,
392
+ ]), ([_enum, classType, _arrow, fieldName, _colon, fieldType,]) => ({
393
+ class: classType,
394
+ type: fieldType,
395
+ name: fieldName,
396
+ }));
397
+ setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
398
+ const smaliMethodPrototypeParser = promiseCompose(createTupleParser([
399
+ createExactSequenceParser('('),
400
+ createArrayParser(smaliTypeDescriptorParser),
401
+ createExactSequenceParser(')'),
402
+ smaliTypeDescriptorParser,
403
+ ]), ([_openParenthesis, parameters, _closeParenthesis, returnType,]) => ({
404
+ parameters,
405
+ returnType,
406
+ shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
407
+ if (parameter === 'V') {
408
+ return '';
409
+ }
410
+ return shortyFromLongy(parameter);
411
+ }).join(''),
412
+ }));
413
+ setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
414
+ const smaliParametersMethodParser = promiseCompose(createTupleParser([
415
+ smaliTypeDescriptorParser,
416
+ createExactSequenceParser('->'),
417
+ smaliMemberNameParser,
418
+ smaliMethodPrototypeParser,
419
+ ]), ([classPath, _separator, methodName, prototype,]) => ({
420
+ class: classPath,
421
+ prototype,
422
+ name: methodName,
423
+ }));
424
+ setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
425
+ // Forward declaration to handle circular reference
426
+ let smaliSubannotationParser;
427
+ const smaliAnnotationElementParser = promiseCompose(createTupleParser([
428
+ smaliIndentationParser,
429
+ smaliIdentifierParser,
430
+ createExactSequenceParser(' = '),
431
+ createDisjunctionParser([
432
+ promiseCompose(createParserAccessorParser(() => smaliSubannotationParser), value => ({ kind: 'raw', value })),
433
+ promiseCompose(smaliEnumValueParser, value => ({ kind: 'enum', value })),
434
+ promiseCompose(smaliQuotedStringParser, value => ({ kind: 'string', value })),
435
+ promiseCompose(smaliParametersMethodParser, value => ({ kind: 'raw', value })),
436
+ promiseCompose(smaliTypeDescriptorParser, value => ({ kind: 'type', value })),
437
+ promiseCompose(smaliNumberParser, value => ({ kind: 'raw', value })),
438
+ promiseCompose(createExactSequenceParser('true'), () => ({ kind: 'raw', value: true })),
439
+ promiseCompose(createExactSequenceParser('false'), () => ({ kind: 'raw', value: false })),
440
+ promiseCompose(createExactSequenceParser('null'), () => ({ kind: 'raw', value: null })),
441
+ promiseCompose(createExactSequenceParser('{}'), () => ({ kind: 'raw', value: [] })),
442
+ promiseCompose(createTupleParser([
443
+ createExactSequenceParser('{\n'),
444
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
445
+ smaliIndentationParser,
446
+ smaliEnumValueParser,
447
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
448
+ smaliLineEndPraser,
449
+ smaliIndentationParser,
450
+ createExactSequenceParser('}'),
451
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'enum', value })),
452
+ promiseCompose(createTupleParser([
453
+ createExactSequenceParser('{\n'),
454
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
455
+ smaliIndentationParser,
456
+ smaliTypeDescriptorParser,
457
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
458
+ smaliLineEndPraser,
459
+ smaliIndentationParser,
460
+ createExactSequenceParser('}'),
461
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'type', value })),
462
+ promiseCompose(createTupleParser([
463
+ createExactSequenceParser('{\n'),
464
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
465
+ smaliIndentationParser,
466
+ smaliQuotedStringParser,
467
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
468
+ smaliLineEndPraser,
469
+ smaliIndentationParser,
470
+ createExactSequenceParser('}'),
471
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'string', value })),
472
+ promiseCompose(createTupleParser([
473
+ createExactSequenceParser('{\n'),
474
+ createSeparatedArrayParser(promiseCompose(createTupleParser([
475
+ smaliIndentationParser,
476
+ smaliNumberParser,
477
+ ]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
478
+ smaliLineEndPraser,
479
+ smaliIndentationParser,
480
+ createExactSequenceParser('}'),
481
+ ]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'raw', value })),
482
+ ]),
483
+ smaliLineEndPraser,
484
+ ]), ([_indentation, name, _equalsSign, value, _newline,]) => ({
485
+ name,
486
+ value: value,
487
+ }));
488
+ setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
489
+ // Now define the subannotation parser
490
+ smaliSubannotationParser = promiseCompose(createTupleParser([
491
+ createExactSequenceParser('.subannotation '),
492
+ smaliTypeDescriptorParser,
493
+ smaliLineEndPraser,
494
+ createArrayParser(smaliAnnotationElementParser),
495
+ smaliIndentationParser,
496
+ createExactSequenceParser('.end subannotation'),
497
+ ]), ([_subannotation, type, _newline, elements, _indentation, _endSubannotation,]) => ({
498
+ type,
499
+ elements,
500
+ }));
501
+ setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
502
+ export const smaliAnnotationParser = promiseCompose(createTupleParser([
503
+ smaliIndentationParser,
504
+ createExactSequenceParser('.annotation '),
505
+ createUnionParser([
506
+ createExactSequenceParser('build'),
507
+ createExactSequenceParser('runtime'),
508
+ createExactSequenceParser('system'),
509
+ ]),
510
+ smaliSingleWhitespaceParser,
511
+ smaliTypeDescriptorParser,
512
+ smaliLineEndPraser,
513
+ createArrayParser(smaliAnnotationElementParser),
514
+ smaliIndentationParser,
515
+ createExactSequenceParser('.end annotation\n'),
516
+ ]), ([_indentation0, _annotation, visibility, _space, type, _newline, elements, _indentation1, _endAnnotation,]) => ({
517
+ type,
518
+ elements,
519
+ visibility,
520
+ }));
521
+ setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
522
+ export const smaliFieldParser = promiseCompose(createTupleParser([
523
+ createExactSequenceParser('.field '),
524
+ createOptionalParser(promiseCompose(createTupleParser([
525
+ smaliAccessFlagsParser,
526
+ smaliSingleWhitespaceParser,
527
+ ]), ([accessFlags, _space]) => accessFlags)),
528
+ smaliMemberNameParser,
529
+ createExactSequenceParser(':'),
530
+ smaliTypeDescriptorParser,
531
+ createOptionalParser(promiseCompose(createTupleParser([
532
+ createExactSequenceParser(' = '),
533
+ createUnionParser([
534
+ smaliCharacterLiteralParser,
535
+ smaliFieldValueParser,
536
+ smaliQuotedStringParser,
537
+ promiseCompose(createExactSequenceParser('true'), () => true),
538
+ promiseCompose(createExactSequenceParser('false'), () => false),
539
+ promiseCompose(createExactSequenceParser('null'), () => null),
540
+ ]),
541
+ ]), ([_equals, value,]) => value)),
542
+ smaliLineEndPraser,
543
+ createOptionalParser(promiseCompose(createTupleParser([
544
+ createSeparatedArrayParser(smaliAnnotationParser, smaliNewlinesParser),
545
+ createExactSequenceParser('.end field\n'),
546
+ ]), ([annotations, _endField,]) => annotations)),
547
+ ]), ([_field, accessFlags, name, _colon, type, initialValue, _newline, annotations,]) => ({
548
+ field: {
549
+ accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
550
+ field: {
551
+ class: 'FILLED_LATER',
552
+ type,
553
+ name,
554
+ },
555
+ },
556
+ annotations: annotations ?? [],
557
+ ...(initialValue !== undefined ? { initialValue } : {}),
558
+ }));
559
+ setParserName(smaliFieldParser, 'smaliFieldParser');
560
+ const smaliFieldsParser = promiseCompose(createSeparatedNonEmptyArrayParser(smaliFieldParser, smaliCommentsOrNewlinesParser), fieldsAndComments => {
561
+ let type = 'instanceField';
562
+ const staticFields = [];
563
+ const instanceFields = [];
564
+ for (const fieldOrComment of fieldsAndComments) {
565
+ if (Array.isArray(fieldOrComment)) {
566
+ for (const comment of fieldOrComment) {
567
+ if (comment === ' static fields') {
568
+ type = 'staticField';
569
+ }
570
+ if (comment === ' instance fields') {
571
+ type = 'instanceField';
572
+ }
573
+ }
574
+ continue;
575
+ }
576
+ invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
577
+ const field = fieldOrComment;
578
+ if (type === 'staticField'
579
+ || field.field.accessFlags.static) {
580
+ staticFields.push(field);
581
+ continue;
582
+ }
583
+ if (type === 'instanceField') {
584
+ instanceFields.push(field);
585
+ continue;
586
+ }
587
+ invariant(false, 'Expected field type');
588
+ }
589
+ return {
590
+ staticFields,
591
+ instanceFields,
592
+ };
593
+ });
594
+ setParserName(smaliFieldsParser, 'smaliFieldsParser');
595
+ const smaliShortyFieldTypeParser = createUnionParser([
596
+ createExactSequenceParser('Z'),
597
+ createExactSequenceParser('B'),
598
+ createExactSequenceParser('S'),
599
+ createExactSequenceParser('C'),
600
+ createExactSequenceParser('I'),
601
+ createExactSequenceParser('J'),
602
+ createExactSequenceParser('F'),
603
+ createExactSequenceParser('D'),
604
+ ]);
605
+ setParserName(smaliShortyFieldTypeParser, 'smaliShortyFieldTypeParser');
606
+ const smaliShortyReturnTypeParser = createUnionParser([
607
+ createExactSequenceParser('V'),
608
+ smaliShortyFieldTypeParser,
609
+ ]);
610
+ setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
611
+ function shortyGetInsSize(shorty) {
612
+ let size = 0;
613
+ for (const char of shorty.slice(1)) {
614
+ if (char === 'J' || char === 'D') {
615
+ size += 2;
616
+ }
617
+ else if (char !== 'V') {
618
+ size += 1;
619
+ }
620
+ }
621
+ return size;
622
+ }
623
+ const smaliCodeRegistersParser = promiseCompose(createTupleParser([
624
+ createExactSequenceParser(' .registers '),
625
+ smaliNumberParser,
626
+ smaliLineEndPraser,
627
+ ]), ([_registers, registers, _newline,]) => registers);
628
+ setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
629
+ const smaliCodeLineParser = promiseCompose(createTupleParser([
630
+ createExactSequenceParser(' .line '),
631
+ smaliNumberParser,
632
+ smaliLineEndPraser,
633
+ ]), ([_line, line, _newline,]) => line);
634
+ setParserName(smaliCodeLineParser, 'smaliCodeLineParser');
635
+ function isSmaliRegister(value) {
636
+ return (value !== null
637
+ && typeof value === 'object'
638
+ && 'prefix' in value
639
+ && 'index' in value
640
+ && typeof value.prefix === 'string'
641
+ && typeof value.index === 'number');
642
+ }
643
+ const smaliParametersRegisterParser = promiseCompose(createUnionParser([
644
+ createTupleParser([
645
+ createExactSequenceParser('v'),
646
+ smaliNumberParser,
647
+ ]),
648
+ createTupleParser([
649
+ createExactSequenceParser('p'),
650
+ smaliNumberParser,
651
+ ]),
652
+ ]), ([prefix, index,]) => ({
653
+ prefix,
654
+ index,
655
+ }));
656
+ setParserName(smaliParametersRegisterParser, 'smaliParametersRegisterParser');
657
+ const smaliCodeLocalParser = promiseCompose(createTupleParser([
658
+ createExactSequenceParser(' .local '),
659
+ smaliParametersRegisterParser,
660
+ createOptionalParser(createTupleParser([
661
+ createExactSequenceParser(','),
662
+ smaliWhitespaceParser,
663
+ smaliQuotedStringParser,
664
+ createExactSequenceParser(':'),
665
+ smaliTypeDescriptorParser,
666
+ ])),
667
+ smaliLineEndPraser,
668
+ ]), ([_local, register, nameAndType,]) => ({
669
+ register,
670
+ name: nameAndType?.[2],
671
+ type: nameAndType?.[4],
672
+ }));
673
+ setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
674
+ const smaliCodeEndLocalParser = promiseCompose(createTupleParser([
675
+ createExactSequenceParser(' .end local '),
676
+ smaliParametersRegisterParser,
677
+ smaliLineEndPraser,
678
+ ]), ([_endLocal, register, _newline,]) => register);
679
+ setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
680
+ const smaliCodeRestartLocalParser = promiseCompose(createTupleParser([
681
+ createExactSequenceParser(' .restart local '),
682
+ smaliParametersRegisterParser,
683
+ smaliLineEndPraser,
684
+ ]), ([_restartLocal, register, _newline,]) => register);
685
+ setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
686
+ const smaliCodePrologueEndParser = promiseCompose(createTupleParser([
687
+ createExactSequenceParser(' .prologue'),
688
+ smaliLineEndPraser,
689
+ ]), () => true);
690
+ setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
691
+ const smaliCodeEpilogueBeginParser = promiseCompose(createTupleParser([
692
+ createExactSequenceParser(' .epilogue'),
693
+ smaliLineEndPraser,
694
+ ]), () => true);
695
+ setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
696
+ export const smaliCodeParameterParser = promiseCompose(createTupleParser([
697
+ createExactSequenceParser(' .param '),
698
+ smaliParametersRegisterParser,
699
+ createOptionalParser(createTupleParser([
700
+ createExactSequenceParser(','),
701
+ smaliWhitespaceParser,
702
+ smaliQuotedStringParser,
703
+ smaliWhitespaceParser,
704
+ ])),
705
+ createOptionalParser(smaliWhitespaceParser),
706
+ smaliCommentsOrNewlinesParser,
707
+ createOptionalParser(createTupleParser([
708
+ smaliAnnotationParser,
709
+ smaliIndentationParser,
710
+ createExactSequenceParser('.end param\n'),
711
+ ])),
712
+ ]), ([_parameter, register, optionalCommaAndString, _whitespace, _commentOrNewline, optionalAnnotation,]) => {
713
+ let annotation;
714
+ if (optionalAnnotation) {
715
+ annotation = optionalAnnotation[0];
716
+ }
717
+ return {
718
+ register,
719
+ name: optionalCommaAndString?.[2],
720
+ annotation,
721
+ };
722
+ });
723
+ setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
724
+ const smaliCodeLabelParser = promiseCompose(createTupleParser([
725
+ createExactSequenceParser(':'),
726
+ smaliIdentifierParser,
727
+ ]), ([_label, label,]) => label);
728
+ setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
729
+ const smaliCodeLabelLineParser = promiseCompose(createTupleParser([
730
+ smaliIndentationParser,
731
+ smaliCodeLabelParser,
732
+ smaliLineEndPraser,
733
+ ]), ([_label, label, _newlabel,]) => label);
734
+ setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
735
+ const smaliCatchDirectiveParser = promiseCompose(createTupleParser([
736
+ smaliIndentationParser,
737
+ createExactSequenceParser('.catch'),
738
+ createUnionParser([
739
+ promiseCompose(createExactSequenceParser('all'), () => undefined),
740
+ promiseCompose(createTupleParser([
741
+ createExactSequenceParser(' '),
742
+ smaliTypeDescriptorParser,
743
+ ]), ([_space, type,]) => type),
744
+ ]),
745
+ createExactSequenceParser(' {'),
746
+ smaliCodeLabelParser,
747
+ createExactSequenceParser(' .. '),
748
+ smaliCodeLabelParser,
749
+ createExactSequenceParser('} '),
750
+ smaliCodeLabelParser,
751
+ smaliLineEndPraser,
752
+ ]), ([_indentation, _catch, type, _openBrace, startLabel, _dots, endLabel, _closeBrace, handlerLabel, _newline,]) => ({
753
+ type,
754
+ startLabel,
755
+ endLabel,
756
+ handlerLabel,
757
+ }));
758
+ setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
759
+ const smaliLabeledCatchDirectiveParser = promiseCompose(createTupleParser([
760
+ createArrayParser(smaliCodeLineParser),
761
+ createOptionalParser(smaliCodeLocalParser),
762
+ createArrayParser(smaliCodeLabelLineParser),
763
+ smaliCatchDirectiveParser,
764
+ ]), ([_lines, _local, labels, catchDirective,]) => ({
765
+ labels,
766
+ catchDirective,
767
+ }));
768
+ setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
769
+ const smaliParametersRegisterRangeParser = promiseCompose(createTupleParser([
770
+ createExactSequenceParser('{'),
771
+ smaliParametersRegisterParser,
772
+ createExactSequenceParser(' .. '),
773
+ smaliParametersRegisterParser,
774
+ createExactSequenceParser('}'),
775
+ ]), ([_openBrace, startRegister, _dotDot, endRegister, _closeBrace,]) => {
776
+ invariant(startRegister.prefix === endRegister.prefix, 'Register range must use the same prefix');
777
+ invariant(startRegister.index <= endRegister.index, 'Register range start must be less than or equal to end');
778
+ const registers = [];
779
+ for (let i = startRegister.index; i <= endRegister.index; i++) {
780
+ registers.push({
781
+ prefix: startRegister.prefix,
782
+ index: i,
783
+ });
784
+ }
785
+ return registers;
786
+ });
787
+ setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
788
+ const smaliParametersRegisterListParser = promiseCompose(createTupleParser([
789
+ createExactSequenceParser('{'),
790
+ createArrayParser(promiseCompose(createTupleParser([
791
+ smaliParametersRegisterParser,
792
+ createOptionalParser(createExactSequenceParser(', ')),
793
+ ]), ([parameter, _comma,]) => parameter)),
794
+ createExactSequenceParser('}'),
795
+ ]), ([_openBrace, parameters, _closeBrace,]) => parameters);
796
+ setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
797
+ const smaliParametersRegistersParser = createUnionParser([
798
+ smaliParametersRegisterRangeParser,
799
+ smaliParametersRegisterListParser,
800
+ ]);
801
+ setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
802
+ const smaliParametersStringParser = cloneParser(smaliQuotedStringParser);
803
+ setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
804
+ const smaliParametersIntegerParser = promiseCompose(createTupleParser([
805
+ createOptionalParser(createExactSequenceParser('-')),
806
+ createExactSequenceParser('0x'),
807
+ async (parserContext) => {
808
+ const characters = [];
809
+ while (true) {
810
+ const character = await parserContext.peek(0);
811
+ parserContext.invariant(character !== undefined, 'Unexpected end of input');
812
+ invariant(character !== undefined, 'Unexpected end of input');
813
+ if ((character >= '0' && character <= '9')
814
+ || (character >= 'a' && character <= 'f')
815
+ || (character >= 'A' && character <= 'F')) {
816
+ characters.push(character);
817
+ parserContext.skip(1);
818
+ continue;
819
+ }
820
+ break;
821
+ }
822
+ return characters.join('');
823
+ },
824
+ createOptionalParser(createUnionParser([
825
+ createExactSequenceParser('L'),
826
+ createExactSequenceParser('t'),
827
+ createExactSequenceParser('s'),
828
+ ])),
829
+ ]), ([optionalMinus, _0x, value, optionalSuffix,]) => {
830
+ if (optionalSuffix === 'L') {
831
+ const sign = optionalMinus ? -1n : 1n;
832
+ return sign * BigInt('0x' + value);
833
+ }
834
+ const sign = optionalMinus ? -1 : 1;
835
+ return sign * Number.parseInt(value, 16);
836
+ });
837
+ setParserName(smaliParametersIntegerParser, 'smaliParametersIntegerParser');
838
+ const smaliParametersLabelParser = promiseCompose(createTupleParser([
839
+ createExactSequenceParser(':'),
840
+ smaliIdentifierParser,
841
+ ]), ([_label, label,]) => label);
842
+ setParserName(smaliParametersLabelParser, 'smaliParametersLabelParser');
843
+ const smaliParametersTypeParser = cloneParser(smaliTypeDescriptorParser);
844
+ setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
845
+ const smaliParametersFieldParser = promiseCompose(createTupleParser([
846
+ smaliTypeDescriptorParser,
847
+ createExactSequenceParser('->'),
848
+ smaliMemberNameParser,
849
+ createExactSequenceParser(':'),
850
+ smaliTypeDescriptorParser,
851
+ ]), ([classPath, _separator, fieldName, _colon, type,]) => ({
852
+ class: classPath,
853
+ name: fieldName,
854
+ type,
855
+ }));
856
+ setParserName(smaliParametersFieldParser, 'smaliParametersFieldParser');
857
+ const smaliCodeOperationParametersParser = createArrayParser(promiseCompose(createTupleParser([
858
+ createUnionParser([
859
+ smaliParametersRegisterParser,
860
+ smaliParametersRegistersParser,
861
+ smaliParametersStringParser,
862
+ smaliParametersIntegerParser,
863
+ promiseCompose(createTupleParser([
864
+ createNegativeLookaheadParser(smaliParametersIntegerParser),
865
+ smaliNumberParser,
866
+ ]), ([_notInteger, number]) => number),
867
+ smaliParametersLabelParser,
868
+ promiseCompose(createTupleParser([
869
+ createNegativeLookaheadParser(smaliParametersMethodParser),
870
+ createNegativeLookaheadParser(smaliParametersFieldParser),
871
+ smaliParametersTypeParser,
872
+ ]), ([_notMethod, _notField, type,]) => type),
873
+ smaliParametersMethodParser,
874
+ smaliParametersFieldParser,
875
+ ]),
876
+ createOptionalParser(createExactSequenceParser(', ')),
877
+ ]), ([parameter, _comma,]) => parameter));
878
+ setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
879
+ const smaliCodeOperationNameParser = promiseCompose(createTupleParser([
880
+ promiseCompose(createSeparatedArrayParser(smaliIdentifierParser, createExactSequenceParser('-')), parts => parts.join('-')),
881
+ createOptionalParser(promiseCompose(createTupleParser([
882
+ createExactSequenceParser('/'),
883
+ smaliIdentifierParser,
884
+ ]), ([slash, name]) => slash + name)),
885
+ ]), ([name, optionalSlashName,]) => name + (optionalSlashName || ''));
886
+ setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
887
+ const smaliOneLineCodeOperationParser = promiseCompose(createTupleParser([
888
+ smaliSingleIndentationParser,
889
+ smaliCodeOperationNameParser,
890
+ promiseCompose(createOptionalParser(createTupleParser([
891
+ smaliSingleWhitespaceParser,
892
+ smaliCodeOperationParametersParser,
893
+ ])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
894
+ smaliLineEndPraser,
895
+ ]), ([_indent, operation, parameters, _newline,]) => ({
896
+ operation,
897
+ parameters,
898
+ }));
899
+ setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
900
+ const createMultilineSmaliCodeOperationLabelsBodyParser = operationName => promiseCompose(createTupleParser([
901
+ createArrayParser(promiseCompose(createTupleParser([
902
+ smaliIndentationParser,
903
+ smaliCodeLabelParser,
904
+ smaliLineEndPraser,
905
+ ]), ([_indentation, label, _newline,]) => label)),
906
+ smaliIndentationParser,
907
+ createExactSequenceParser('.end '),
908
+ createExactSequenceParser(operationName),
909
+ smaliLineEndPraser,
910
+ ]), ([labels,]) => labels);
911
+ const createMultilineSmaliCodeOperationLabelMapBodyParser = operationName => promiseCompose(createTupleParser([
912
+ createArrayParser(promiseCompose(createTupleParser([
913
+ smaliIndentationParser,
914
+ smaliParametersIntegerParser,
915
+ createExactSequenceParser(' -> '),
916
+ smaliCodeLabelParser,
917
+ smaliLineEndPraser,
918
+ ]), ([_indentation, key, _arrow, label, _newline,]) => [
919
+ key,
920
+ label,
921
+ ])),
922
+ smaliIndentationParser,
923
+ createExactSequenceParser('.end '),
924
+ createExactSequenceParser(operationName),
925
+ smaliLineEndPraser,
926
+ ]), ([labels,]) => labels);
927
+ const createMultilineSmaliCodeOperationIntegersBodyParser = operationName => promiseCompose(createTupleParser([
928
+ createArrayParser(promiseCompose(createTupleParser([
929
+ smaliIndentationParser,
930
+ smaliParametersIntegerParser,
931
+ smaliLineEndPraser,
932
+ ]), ([_indentation, key,]) => key)),
933
+ smaliIndentationParser,
934
+ createExactSequenceParser('.end '),
935
+ createExactSequenceParser(operationName),
936
+ smaliLineEndPraser,
937
+ ]), ([labels,]) => labels);
938
+ const createMultilineSmaliCodeOperationBodyParser = operationName => createUnionParser([
939
+ createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
940
+ createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
941
+ createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
942
+ ]);
943
+ const smaliMultilineCodeOperationParser = parserCreatorCompose(() => promiseCompose(createTupleParser([
944
+ smaliSingleIndentationParser,
945
+ createExactSequenceParser('.'),
946
+ smaliCodeOperationNameParser,
947
+ promiseCompose(createOptionalParser(createTupleParser([
948
+ smaliSingleWhitespaceParser,
949
+ smaliCodeOperationParametersParser,
950
+ ])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
951
+ smaliLineEndPraser,
952
+ ]), ([_indent, _dot, operation, parameters, _newline,]) => ({
953
+ operation,
954
+ parameters,
955
+ })), ({ operation, parameters }) => promiseCompose(createMultilineSmaliCodeOperationBodyParser(operation), body => ({
956
+ operation,
957
+ parameters,
958
+ body,
959
+ })))();
960
+ setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
961
+ const smaliLooseCodeOperationParser = createUnionParser([
962
+ smaliOneLineCodeOperationParser,
963
+ smaliMultilineCodeOperationParser,
964
+ ]);
965
+ setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
966
+ export const smaliCodeOperationParser = promiseCompose(smaliLooseCodeOperationParser, operation => {
967
+ const operation_ = {
968
+ operation: operation.operation,
969
+ // Line,
970
+ // local,
971
+ // parameters: (operation as any).parameters,
972
+ }; // TODO
973
+ if ('body' in operation) {
974
+ const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
975
+ invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
976
+ operation_.operation = operationDexName;
977
+ if (operationDexName === 'sparse-switch-payload') {
978
+ const map = new Map(operation.body);
979
+ operation_.keys = [...map.keys()];
980
+ operation_.branchLabels = [...map.values()];
981
+ }
982
+ else if (operationDexName === 'fill-array-data-payload') {
983
+ // Get elementWidth from parameters (first parameter)
984
+ const elementWidth = operation.parameters[0];
985
+ invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
986
+ operation_.elementWidth = elementWidth;
987
+ // Convert integer values to bytes (little-endian)
988
+ const values = operation.body;
989
+ const data = [];
990
+ for (const value of values) {
991
+ // Use BigInt for bitwise operations to preserve full precision
992
+ const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
993
+ // Convert to bytes based on elementWidth (little-endian)
994
+ for (let i = 0; i < elementWidth; i++) {
995
+ data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xffn));
996
+ }
997
+ }
998
+ operation_.data = data;
999
+ }
1000
+ else {
1001
+ operation_.branchLabels = operation.body;
1002
+ }
1003
+ }
1004
+ const hasRegisters = Array.isArray(operation.parameters.at(0));
1005
+ if (hasRegisters) {
1006
+ operation_.registers = [];
1007
+ }
1008
+ for (const parameter of operation.parameters.flat() ?? []) {
1009
+ if (isDalvikExecutableMethod(parameter)) {
1010
+ operation_.method = parameter;
1011
+ continue;
1012
+ }
1013
+ if (isDalvikExecutableField(parameter)) {
1014
+ operation_.field = parameter;
1015
+ continue;
1016
+ }
1017
+ if (isSmaliRegister(parameter)) {
1018
+ operation_.registers ||= [];
1019
+ operation_.registers.push(parameter);
1020
+ continue;
1021
+ }
1022
+ if (typeof parameter === 'string') {
1023
+ if (operationsWithTypeArgument.has(operation_.operation)) {
1024
+ operation_.type = parameter;
1025
+ continue;
1026
+ }
1027
+ if (operationsWithBranchLabelArgument.has(operation_.operation)) {
1028
+ operation_.branchLabel = parameter;
1029
+ continue;
1030
+ }
1031
+ operation_.string = parameter;
1032
+ continue;
1033
+ }
1034
+ if (typeof parameter === 'number' || typeof parameter === 'bigint') {
1035
+ // Skip for fill-array-data-payload, already handled in body processing
1036
+ if (operation_.operation === 'fill-array-data-payload') {
1037
+ continue;
1038
+ }
1039
+ // Const-wide operations always use bigint values
1040
+ if (operationsWithBigintValue.has(operation_.operation)) {
1041
+ operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
1042
+ }
1043
+ else {
1044
+ operation_.value = parameter;
1045
+ }
1046
+ continue;
1047
+ }
1048
+ invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
1049
+ }
1050
+ return operation_;
1051
+ });
1052
+ setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
1053
+ const operationsWithTypeArgument = new Set([
1054
+ 'new-instance',
1055
+ 'new-array',
1056
+ 'filled-new-array',
1057
+ 'filled-new-array/range',
1058
+ 'check-cast',
1059
+ 'instance-of',
1060
+ 'const-class',
1061
+ ]);
1062
+ const operationsWithBigintValue = new Set([
1063
+ 'const-wide/16',
1064
+ 'const-wide/32',
1065
+ 'const-wide',
1066
+ ]);
1067
+ const operationsWithBranchLabelArgument = new Set([
1068
+ 'goto',
1069
+ 'goto/16',
1070
+ 'goto/32',
1071
+ 'if-eq',
1072
+ 'if-ne',
1073
+ 'if-lt',
1074
+ 'if-ge',
1075
+ 'if-gt',
1076
+ 'if-le',
1077
+ 'if-eqz',
1078
+ 'if-nez',
1079
+ 'if-ltz',
1080
+ 'if-gez',
1081
+ 'if-gtz',
1082
+ 'if-lez',
1083
+ 'packed-switch',
1084
+ 'sparse-switch',
1085
+ 'fill-array-data',
1086
+ ]);
1087
+ const payloadOperationSmaliNameToDexName = new Map(Object.entries({
1088
+ 'packed-switch': 'packed-switch-payload',
1089
+ 'sparse-switch': 'sparse-switch-payload',
1090
+ 'array-data': 'fill-array-data-payload',
1091
+ }));
1092
+ function isOperationWithLabels(value) {
1093
+ return (typeof value === 'object'
1094
+ && value !== null
1095
+ && 'labels' in value
1096
+ && value.labels instanceof Set);
1097
+ }
1098
+ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
1099
+ createArrayParser(smaliCodeLineParser),
1100
+ createOptionalParser(smaliCodeLocalParser),
1101
+ createOptionalParser(smaliCodeEndLocalParser),
1102
+ createOptionalParser(smaliCodeRestartLocalParser),
1103
+ createOptionalParser(smaliCodePrologueEndParser),
1104
+ createOptionalParser(smaliCodeEpilogueBeginParser),
1105
+ createArrayParser(smaliCodeLabelLineParser),
1106
+ smaliCodeOperationParser,
1107
+ ]), ([lines, local, endLocal, restartLocal, prologueEnd, epilogueBegin, labels, operation,]) => {
1108
+ const debugDirectives = [];
1109
+ for (const line of lines) {
1110
+ debugDirectives.push({ type: 'line', line });
1111
+ }
1112
+ if (local) {
1113
+ debugDirectives.push({ type: 'startLocal', local });
1114
+ }
1115
+ if (endLocal) {
1116
+ debugDirectives.push({ type: 'endLocal', register: endLocal });
1117
+ }
1118
+ if (restartLocal) {
1119
+ debugDirectives.push({ type: 'restartLocal', register: restartLocal });
1120
+ }
1121
+ if (prologueEnd) {
1122
+ debugDirectives.push({ type: 'setPrologueEnd' });
1123
+ }
1124
+ if (epilogueBegin) {
1125
+ debugDirectives.push({ type: 'setEpilogueBegin' });
1126
+ }
1127
+ return {
1128
+ debugDirectives,
1129
+ labels,
1130
+ operation,
1131
+ };
1132
+ });
1133
+ setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
1134
+ const smaliExecutableCodeParser = promiseCompose(createTupleParser([
1135
+ createOptionalParser(smaliCodeRegistersParser),
1136
+ createArrayParser(smaliAnnotationParser),
1137
+ createArrayParser(smaliCodeParameterParser),
1138
+ createOptionalParser(smaliCommentsOrNewlinesParser),
1139
+ createSeparatedArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
1140
+ createArrayParser(promiseCompose(createTupleParser([
1141
+ createOptionalParser(smaliCommentsOrNewlinesParser),
1142
+ createDisjunctionParser([
1143
+ smaliIndentedCommentParser,
1144
+ smaliAnnotatedCodeOperationParser,
1145
+ smaliLabeledCatchDirectiveParser,
1146
+ ]),
1147
+ ]), ([_commentsOrNewlinesParser, operation,]) => operation)),
1148
+ ]), ([registersSize, annotations1, parameters, _leadingCommentsOrNewlines, annotations2, instructionsAndCatchDirectives,]) => {
1149
+ const annotations = [...annotations1, ...annotations2];
1150
+ const instructions = [];
1151
+ const annotatedOperations = [];
1152
+ const catchDirectives = [];
1153
+ const catchDirectiveLabels = new Map();
1154
+ for (const item of instructionsAndCatchDirectives) {
1155
+ if (item && typeof item === 'object') {
1156
+ if ('labels' in item && 'catchDirective' in item) {
1157
+ // This is a SmaliLabeledCatchDirective
1158
+ const labeledCatch = item;
1159
+ catchDirectives.push(labeledCatch.catchDirective);
1160
+ catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
1161
+ }
1162
+ else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
1163
+ // This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
1164
+ catchDirectives.push(item);
1165
+ }
1166
+ else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
1167
+ // This is a SmaliAnnotatedCodeOperation
1168
+ const annotated = item;
1169
+ annotatedOperations.push(annotated);
1170
+ const operation = annotated.operation;
1171
+ if (annotated.labels.length > 0) {
1172
+ operation.labels = new Set(annotated.labels);
1173
+ }
1174
+ instructions.push(operation);
1175
+ }
1176
+ else if (item !== undefined) {
1177
+ // This is a SmaliCodeOperation (fallback)
1178
+ instructions.push(item);
1179
+ }
1180
+ }
1181
+ }
1182
+ if (registersSize === undefined
1183
+ && parameters !== undefined) {
1184
+ registersSize = parameters.length;
1185
+ }
1186
+ invariant(registersSize !== undefined, 'Expected registers size to be defined');
1187
+ for (const [operationIndex, operation] of instructions.entries()) {
1188
+ if ('branchLabel' in operation
1189
+ && typeof operation.branchLabel === 'string') {
1190
+ for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
1191
+ if (!isOperationWithLabels(targetOperation)) {
1192
+ continue;
1193
+ }
1194
+ if (!targetOperation.labels.has(operation.branchLabel)) {
1195
+ continue;
1196
+ }
1197
+ delete operation.branchLabel;
1198
+ operation.branchOffsetIndex = targetOperationIndex - operationIndex;
1199
+ break;
1200
+ }
1201
+ }
1202
+ if ('branchLabels' in operation
1203
+ && typeof operation.branchLabels === 'object'
1204
+ && Array.isArray(operation.branchLabels)) {
1205
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
1206
+ && typeof sourceOperation.branchOffsetIndex === 'number'
1207
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
1208
+ invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
1209
+ const [sourceOperation] = sourceOperations;
1210
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
1211
+ operation.branchOffsetIndices = operation.branchLabels.map((branchLabel) => {
1212
+ for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
1213
+ if (!isOperationWithLabels(targetOperation)) {
1214
+ continue;
1215
+ }
1216
+ if (!targetOperation.labels.has(branchLabel)) {
1217
+ continue;
1218
+ }
1219
+ return targetOperationIndex - sourceOperationIndex;
1220
+ }
1221
+ invariant(false, 'Expected to find branch label %s', branchLabel);
1222
+ });
1223
+ delete operation.branchLabels;
1224
+ }
1225
+ }
1226
+ const branchOffsetByBranchOffsetIndex = new Map();
1227
+ let operationOffset = 0;
1228
+ for (const [operationIndex, operation] of instructions.entries()) {
1229
+ const operationSize = getOperationFormatSize(operation);
1230
+ branchOffsetByBranchOffsetIndex.set(operationIndex, operationOffset);
1231
+ operationOffset += operationSize;
1232
+ }
1233
+ for (const [operationIndex, operation] of instructions.entries()) {
1234
+ if ('branchOffsetIndex' in operation
1235
+ && typeof operation.branchOffsetIndex === 'number') {
1236
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
1237
+ invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operation.branchOffsetIndex);
1238
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
1239
+ invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operationIndex);
1240
+ operation.branchOffset = operationOffset - branchOffset;
1241
+ }
1242
+ if ('branchOffsetIndices' in operation
1243
+ && typeof operation.branchOffsetIndices === 'object'
1244
+ && Array.isArray(operation.branchOffsetIndices)) {
1245
+ const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
1246
+ && typeof sourceOperation.branchOffsetIndex === 'number'
1247
+ && sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
1248
+ invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
1249
+ const [sourceOperation] = sourceOperations;
1250
+ const sourceOperationIndex = instructions.indexOf(sourceOperation);
1251
+ operation.branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex) => {
1252
+ const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
1253
+ invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex + branchOffsetIndex);
1254
+ const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
1255
+ invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex);
1256
+ return operationOffset - branchOffset;
1257
+ });
1258
+ }
1259
+ }
1260
+ // Build label-to-index mapping
1261
+ // Labels attached to instructions map to that instruction's index
1262
+ // Labels before catch directives should map to the position they mark
1263
+ const labelToIndexMap = new Map();
1264
+ // First, map labels from instructions
1265
+ for (const [operationIndex, operation] of instructions.entries()) {
1266
+ if (operation
1267
+ && typeof operation === 'object'
1268
+ && 'labels' in operation
1269
+ && operation.labels instanceof Set) {
1270
+ for (const label of operation.labels) {
1271
+ labelToIndexMap.set(label, operationIndex);
1272
+ }
1273
+ }
1274
+ }
1275
+ // Now handle labels from catch directives
1276
+ // We need to figure out where each catch directive appears in the original sequence
1277
+ let instructionArrayIndex = 0;
1278
+ for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
1279
+ const item = instructionsAndCatchDirectives[i];
1280
+ if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
1281
+ // This is a catch directive with labels
1282
+ // These labels should map to the current instruction index
1283
+ // (which is where the next instruction would be)
1284
+ const labeledCatch = item;
1285
+ for (const label of labeledCatch.labels) {
1286
+ labelToIndexMap.set(label, instructionArrayIndex);
1287
+ }
1288
+ }
1289
+ else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
1290
+ // This is an instruction, increment the counter
1291
+ instructionArrayIndex++;
1292
+ }
1293
+ }
1294
+ // Build tries array from catch directives
1295
+ const triesByRange = new Map();
1296
+ for (const catchDirective of catchDirectives) {
1297
+ // Find the start and end instruction indices
1298
+ const startIndex = labelToIndexMap.get(catchDirective.startLabel);
1299
+ const endIndex = labelToIndexMap.get(catchDirective.endLabel);
1300
+ const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
1301
+ invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
1302
+ invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
1303
+ invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
1304
+ const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
1305
+ const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
1306
+ const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
1307
+ invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
1308
+ invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
1309
+ invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
1310
+ const instructionCount = endAddress - startAddress;
1311
+ const rangeKey = `${startAddress}-${instructionCount}`;
1312
+ let tryEntry = triesByRange.get(rangeKey);
1313
+ if (!tryEntry) {
1314
+ tryEntry = {
1315
+ startAddress,
1316
+ instructionCount,
1317
+ handlers: [],
1318
+ catchAllAddress: undefined,
1319
+ };
1320
+ triesByRange.set(rangeKey, tryEntry);
1321
+ }
1322
+ if (catchDirective.type === undefined) {
1323
+ // .catchall
1324
+ tryEntry.catchAllAddress = handlerAddress;
1325
+ }
1326
+ else {
1327
+ // .catch Type
1328
+ tryEntry.handlers.push({
1329
+ type: catchDirective.type,
1330
+ address: handlerAddress,
1331
+ });
1332
+ }
1333
+ }
1334
+ const tries = Array.from(triesByRange.values()).map(tryEntry => ({
1335
+ startAddress: tryEntry.startAddress,
1336
+ instructionCount: tryEntry.instructionCount,
1337
+ handler: {
1338
+ handlers: tryEntry.handlers,
1339
+ catchAllAddress: tryEntry.catchAllAddress,
1340
+ },
1341
+ }));
1342
+ for (const operation of instructions) {
1343
+ delete operation.labels;
1344
+ delete operation.branchOffsetIndex;
1345
+ delete operation.branchOffsetIndices;
1346
+ }
1347
+ let debugInfo;
1348
+ const debugBytecode = [];
1349
+ let currentLine;
1350
+ let lineStart;
1351
+ // Helper to convert SmaliRegister to register number
1352
+ const getRegisterNum = (register) => {
1353
+ // In Smali, 'v' registers start from 0, 'p' registers are parameters
1354
+ // For now, we'll just use the index. The actual conversion may need
1355
+ // to account for registersSize and parameter mapping.
1356
+ if (register.prefix === 'v') {
1357
+ return register.index;
1358
+ }
1359
+ else {
1360
+ // 'p' registers: need to map to actual register numbers
1361
+ // p0 starts at (registersSize - insSize)
1362
+ // For now, just use the index as we don't have accurate insSize
1363
+ return register.index;
1364
+ }
1365
+ };
1366
+ // Helper to emit accumulated line/address changes as special opcode or explicit opcodes
1367
+ const emitPendingChanges = (lineDiff, addressDiff) => {
1368
+ // Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
1369
+ // Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
1370
+ if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
1371
+ const adjusted = (lineDiff + 4) + 15 * addressDiff;
1372
+ const opcode = 0x0A + adjusted;
1373
+ if (opcode <= 0xFF) {
1374
+ debugBytecode.push({
1375
+ type: 'special',
1376
+ value: opcode,
1377
+ });
1378
+ return;
1379
+ }
1380
+ }
1381
+ // Fall back to explicit opcodes
1382
+ if (addressDiff > 0) {
1383
+ debugBytecode.push({
1384
+ type: 'advancePc',
1385
+ addressDiff,
1386
+ });
1387
+ }
1388
+ if (lineDiff !== 0) {
1389
+ debugBytecode.push({
1390
+ type: 'advanceLine',
1391
+ lineDiff,
1392
+ });
1393
+ }
1394
+ };
1395
+ let accumulatedAddressDiff = 0;
1396
+ let accumulatedLineDiff = 0;
1397
+ let lastAddress = 0;
1398
+ let hasEmittedFirstDebugEvent = false;
1399
+ for (let i = 0; i < annotatedOperations.length; i++) {
1400
+ const annotated = annotatedOperations[i];
1401
+ const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
1402
+ // Accumulate address difference (only after we've started tracking)
1403
+ if (i > 0 && hasEmittedFirstDebugEvent) {
1404
+ accumulatedAddressDiff += operationAddress - lastAddress;
1405
+ }
1406
+ lastAddress = operationAddress;
1407
+ // Process line directives first to update accumulatedLineDiff
1408
+ for (const directive of annotated.debugDirectives) {
1409
+ if (directive.type === 'line') {
1410
+ if (lineStart === undefined) {
1411
+ lineStart = directive.line;
1412
+ currentLine = directive.line;
1413
+ }
1414
+ else if (currentLine !== undefined) {
1415
+ accumulatedLineDiff += directive.line - currentLine;
1416
+ currentLine = directive.line;
1417
+ }
1418
+ }
1419
+ }
1420
+ // Emit accumulated changes before any other debug events
1421
+ const hasOtherDebugEvents = annotated.debugDirectives.some(d => d.type !== 'line');
1422
+ if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
1423
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
1424
+ accumulatedLineDiff = 0;
1425
+ accumulatedAddressDiff = 0;
1426
+ hasEmittedFirstDebugEvent = true;
1427
+ }
1428
+ // Emit other debug directives
1429
+ for (const directive of annotated.debugDirectives) {
1430
+ if (directive.type === 'startLocal') {
1431
+ debugBytecode.push({
1432
+ type: 'startLocal',
1433
+ registerNum: getRegisterNum(directive.local.register),
1434
+ name: directive.local.name,
1435
+ type_: directive.local.type,
1436
+ });
1437
+ }
1438
+ else if (directive.type === 'endLocal') {
1439
+ debugBytecode.push({
1440
+ type: 'endLocal',
1441
+ registerNum: getRegisterNum(directive.register),
1442
+ });
1443
+ }
1444
+ else if (directive.type === 'restartLocal') {
1445
+ debugBytecode.push({
1446
+ type: 'restartLocal',
1447
+ registerNum: getRegisterNum(directive.register),
1448
+ });
1449
+ }
1450
+ else if (directive.type === 'setPrologueEnd') {
1451
+ debugBytecode.push({
1452
+ type: 'setPrologueEnd',
1453
+ });
1454
+ }
1455
+ else if (directive.type === 'setEpilogueBegin') {
1456
+ debugBytecode.push({
1457
+ type: 'setEpilogueBegin',
1458
+ });
1459
+ }
1460
+ }
1461
+ }
1462
+ // Emit any remaining accumulated changes at the end
1463
+ if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
1464
+ emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
1465
+ }
1466
+ if (lineStart !== undefined) {
1467
+ // Extract parameter names from .param directives
1468
+ const parameterNames = [];
1469
+ // TODO: Extract from parameters array if needed
1470
+ // If we have a lineStart but no bytecode, we need to emit a special opcode
1471
+ // to establish the initial state. The special value 0x0E (14) means:
1472
+ // adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
1473
+ if (debugBytecode.length === 0) {
1474
+ debugBytecode.push({
1475
+ type: 'special',
1476
+ value: 0x0A + 4, // 14 in decimal
1477
+ });
1478
+ }
1479
+ debugInfo = {
1480
+ lineStart,
1481
+ parameterNames,
1482
+ bytecode: debugBytecode,
1483
+ };
1484
+ }
1485
+ return {
1486
+ dalvikExecutableCode: {
1487
+ registersSize,
1488
+ insSize: -1, // TODO
1489
+ outsSize: -1, // TODO
1490
+ debugInfo,
1491
+ instructions: instructions, // TODO
1492
+ tries,
1493
+ // _annotations,
1494
+ },
1495
+ parameters,
1496
+ parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
1497
+ methodAnnotations: annotations,
1498
+ };
1499
+ });
1500
+ setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
1501
+ // TODO: ???
1502
+ const sortRegistersOperations = new Set([
1503
+ 'if-eq',
1504
+ 'if-ne',
1505
+ ]);
1506
+ // All 12x format operations need register reversal because:
1507
+ // - Bytecode format is B|A (B in high nibble, A in low nibble)
1508
+ // - nibblesParser returns [B, A]
1509
+ // - Smali syntax is "op vA, vB" (destination first)
1510
+ // - So we need to reverse to get [A, B]
1511
+ const reverseRegistersOperations = new Set([
1512
+ // Move operations
1513
+ 'move',
1514
+ 'move-wide',
1515
+ 'move-object',
1516
+ // Unary operations (negation, bitwise not)
1517
+ 'neg-int',
1518
+ 'not-int',
1519
+ 'neg-long',
1520
+ 'not-long',
1521
+ 'neg-float',
1522
+ 'neg-double',
1523
+ // Type conversion operations
1524
+ 'int-to-long',
1525
+ 'int-to-float',
1526
+ 'int-to-double',
1527
+ 'long-to-int',
1528
+ 'long-to-float',
1529
+ 'long-to-double',
1530
+ 'float-to-int',
1531
+ 'float-to-long',
1532
+ 'float-to-double',
1533
+ 'double-to-int',
1534
+ 'double-to-long',
1535
+ 'double-to-float',
1536
+ 'int-to-byte',
1537
+ 'int-to-char',
1538
+ 'int-to-short',
1539
+ // Binary operations with /2addr suffix
1540
+ 'add-int/2addr',
1541
+ 'sub-int/2addr',
1542
+ 'mul-int/2addr',
1543
+ 'div-int/2addr',
1544
+ 'rem-int/2addr',
1545
+ 'and-int/2addr',
1546
+ 'or-int/2addr',
1547
+ 'xor-int/2addr',
1548
+ 'shl-int/2addr',
1549
+ 'shr-int/2addr',
1550
+ 'ushr-int/2addr',
1551
+ 'add-long/2addr',
1552
+ 'sub-long/2addr',
1553
+ 'mul-long/2addr',
1554
+ 'div-long/2addr',
1555
+ 'rem-long/2addr',
1556
+ 'and-long/2addr',
1557
+ 'or-long/2addr',
1558
+ 'xor-long/2addr',
1559
+ 'shl-long/2addr',
1560
+ 'shr-long/2addr',
1561
+ 'ushr-long/2addr',
1562
+ 'add-float/2addr',
1563
+ 'sub-float/2addr',
1564
+ 'mul-float/2addr',
1565
+ 'div-float/2addr',
1566
+ 'rem-float/2addr',
1567
+ 'add-double/2addr',
1568
+ 'sub-double/2addr',
1569
+ 'mul-double/2addr',
1570
+ 'div-double/2addr',
1571
+ 'rem-double/2addr',
1572
+ ]);
1573
+ function normalizeOperation(operation) {
1574
+ if (sortRegistersOperations.has(operation.operation)
1575
+ && 'registers' in operation) {
1576
+ operation.registers.sort((a, b) => a - b);
1577
+ }
1578
+ if (reverseRegistersOperations.has(operation.operation)
1579
+ && 'registers' in operation) {
1580
+ operation.registers.reverse();
1581
+ }
1582
+ return operation;
1583
+ }
1584
+ export const smaliMethodParser = promiseCompose(createTupleParser([
1585
+ createExactSequenceParser('.method '),
1586
+ createOptionalParser(promiseCompose(createTupleParser([
1587
+ smaliAccessFlagsParser,
1588
+ smaliSingleWhitespaceParser,
1589
+ ]), ([accessFlags]) => accessFlags)),
1590
+ smaliMemberNameParser,
1591
+ smaliMethodPrototypeParser,
1592
+ smaliLineEndPraser,
1593
+ smaliExecutableCodeParser,
1594
+ createArrayParser(smaliCodeLineParser),
1595
+ createExactSequenceParser('.end method\n'),
1596
+ ]), ([_method, accessFlagsOrUndefined, name, prototype, _newline, { dalvikExecutableCode, parameters, parameterAnnotations, methodAnnotations, }, _lines, _endMethod,]) => {
1597
+ const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
1598
+ let code = dalvikExecutableCode;
1599
+ if (accessFlags.native && code.instructions.length === 0) {
1600
+ code = undefined;
1601
+ }
1602
+ if (code) {
1603
+ const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
1604
+ code.insSize = insSize;
1605
+ for (const operation of code.instructions) {
1606
+ const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation); // TODO
1607
+ if (smaliRegisters.length === 0) {
1608
+ continue;
1609
+ }
1610
+ const registers = smaliRegisters.map(({ prefix, index }) => {
1611
+ if (prefix === 'v') {
1612
+ return index;
1613
+ }
1614
+ if (prefix === 'p') {
1615
+ return code.registersSize - insSize + index;
1616
+ }
1617
+ invariant(false, 'Expected prefix to be v or p');
1618
+ });
1619
+ operation.registers = registers; // TODO
1620
+ }
1621
+ let outsSize = 0;
1622
+ for (const operation of code.instructions) {
1623
+ if (!operation.operation.startsWith('invoke-')) {
1624
+ continue;
1625
+ }
1626
+ const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
1627
+ outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
1628
+ }
1629
+ code.outsSize = outsSize;
1630
+ // Populate debug info parameter names
1631
+ // The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
1632
+ if (code.debugInfo) {
1633
+ const paramCount = accessFlags.static ? insSize : insSize - 1;
1634
+ const paramNames = Array(paramCount).fill(undefined);
1635
+ // Map parameter names from .param directives
1636
+ // For non-static methods: p0 is 'this', p1 is first param (index 0)
1637
+ // For static methods: p0 is first param (index 0)
1638
+ for (const param of parameters) {
1639
+ if (param.register.prefix === 'p' && param.name) {
1640
+ const pRegister = param.register.index;
1641
+ // Adjust for 'this' parameter in non-static methods
1642
+ const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
1643
+ if (paramIndex >= 0 && paramIndex < paramCount) {
1644
+ paramNames[paramIndex] = param.name;
1645
+ }
1646
+ }
1647
+ }
1648
+ code.debugInfo.parameterNames = paramNames;
1649
+ }
1650
+ }
1651
+ return {
1652
+ dalvikExecutableMethodWithAccess: {
1653
+ accessFlags,
1654
+ method: {
1655
+ class: 'FILLED_LATER',
1656
+ prototype,
1657
+ name,
1658
+ },
1659
+ code: code?.instructions.length
1660
+ ? {
1661
+ ...code,
1662
+ instructions: code.instructions.map(normalizeOperation),
1663
+ }
1664
+ : undefined,
1665
+ },
1666
+ parameterAnnotations,
1667
+ methodAnnotations,
1668
+ };
1669
+ });
1670
+ setParserName(smaliMethodParser, 'smaliMethodParser');
1671
+ const smaliMethodsParser = promiseCompose(createArrayParser(createDisjunctionParser([
1672
+ smaliMethodParser,
1673
+ smaliCommentsOrNewlinesParser,
1674
+ ])), methodsAndComments => {
1675
+ let type = 'directMethod';
1676
+ const directMethods = [];
1677
+ const virtualMethods = [];
1678
+ const parameterAnnotations = [];
1679
+ const methodAnnotations = [];
1680
+ function pushParameterAnnotation(annotation) {
1681
+ if (annotation.annotations.length === 0) {
1682
+ return;
1683
+ }
1684
+ const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(parameterAnnotation.method, annotation.method));
1685
+ if (existingMethod) {
1686
+ for (const [index, parameterAnnotations_] of annotation.annotations.entries()) {
1687
+ const existingParameterAnnotations = existingMethod.annotations.at(index);
1688
+ if (existingParameterAnnotations) {
1689
+ existingParameterAnnotations.push(...parameterAnnotations_);
1690
+ }
1691
+ else {
1692
+ existingMethod.annotations[index] = parameterAnnotations_;
1693
+ }
1694
+ }
1695
+ return;
1696
+ }
1697
+ parameterAnnotations.push(annotation);
1698
+ }
1699
+ for (const methodOrComment of methodsAndComments) {
1700
+ if (Array.isArray(methodOrComment)) {
1701
+ for (const comment of methodOrComment) {
1702
+ if (comment === ' direct methods') {
1703
+ type = 'directMethod';
1704
+ }
1705
+ if (comment === ' virtual methods') {
1706
+ type = 'virtualMethod';
1707
+ }
1708
+ }
1709
+ continue;
1710
+ }
1711
+ invariant(typeof methodOrComment === 'object', 'Expected method or comment');
1712
+ const method = methodOrComment;
1713
+ if (method.methodAnnotations.length > 0) {
1714
+ methodAnnotations.push({
1715
+ method: method.dalvikExecutableMethodWithAccess.method,
1716
+ annotations: method.methodAnnotations.map(annotation => ({
1717
+ type: annotation.type,
1718
+ visibility: annotation.visibility,
1719
+ elements: annotation.elements.map(element => ({
1720
+ name: element.name,
1721
+ value: convertToTaggedEncodedValue(element.value),
1722
+ })),
1723
+ })),
1724
+ });
1725
+ }
1726
+ // Create an annotations array for all parameters, not just those with annotations
1727
+ // In smali, instance methods have p0 as 'this', p1 as first param, etc.
1728
+ // But DEX parameter annotations only include the actual parameters (not 'this')
1729
+ const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
1730
+ const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
1731
+ const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
1732
+ const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
1733
+ const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
1734
+ if (parameterAnnotation?.annotation) {
1735
+ return [{
1736
+ type: parameterAnnotation.annotation.type,
1737
+ visibility: parameterAnnotation.annotation.visibility,
1738
+ elements: parameterAnnotation.annotation.elements.map(element => ({
1739
+ name: element.name,
1740
+ value: convertToTaggedEncodedValue(element.value),
1741
+ })),
1742
+ }];
1743
+ }
1744
+ return [];
1745
+ });
1746
+ // Only push parameter annotations if there are some actual annotations
1747
+ if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
1748
+ pushParameterAnnotation({
1749
+ method: method.dalvikExecutableMethodWithAccess.method,
1750
+ annotations: allParameterAnnotations,
1751
+ });
1752
+ }
1753
+ if (type === 'directMethod') {
1754
+ directMethods.push(method.dalvikExecutableMethodWithAccess);
1755
+ continue;
1756
+ }
1757
+ if (type === 'virtualMethod') {
1758
+ virtualMethods.push(method.dalvikExecutableMethodWithAccess);
1759
+ continue;
1760
+ }
1761
+ invariant(false, 'Expected method type');
1762
+ }
1763
+ // Sort parameter annotations by method index in the combined method list
1764
+ // to match the order in the DEX file's annotations directory
1765
+ const allMethods = [...directMethods, ...virtualMethods];
1766
+ parameterAnnotations.sort((a, b) => {
1767
+ const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
1768
+ const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
1769
+ return indexA - indexB;
1770
+ });
1771
+ return {
1772
+ directMethods,
1773
+ virtualMethods,
1774
+ parameterAnnotations,
1775
+ methodAnnotations,
1776
+ };
1777
+ });
1778
+ setParserName(smaliMethodsParser, 'smaliMethodsParser');
1779
+ export const smaliParser = promiseCompose(createTupleParser([
1780
+ smaliClassDeclarationParser,
1781
+ smaliSuperDeclarationParser,
1782
+ createOptionalParser(smaliSourceDeclarationParser),
1783
+ createOptionalParser(promiseCompose(createTupleParser([
1784
+ smaliCommentsOrNewlinesParser,
1785
+ createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
1786
+ ]), ([_commentsOrNewlines, interfaces,]) => interfaces)),
1787
+ createOptionalParser(promiseCompose(createTupleParser([
1788
+ smaliCommentsOrNewlinesParser,
1789
+ createSeparatedNonEmptyArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
1790
+ ]), ([_commentsOrNewlines, classAnnotations,]) => classAnnotations)),
1791
+ createOptionalParser(promiseCompose(createTupleParser([
1792
+ smaliCommentsOrNewlinesParser,
1793
+ smaliFieldsParser,
1794
+ ]), ([_commentsOrNewlines, fields,]) => fields)),
1795
+ smaliMethodsParser,
1796
+ ]), ([{ accessFlags, class: class_, }, { superclass, }, sourceFileObject, interfaces, classAnnotations, smaliFields, methods,]) => {
1797
+ const sourceFile = sourceFileObject?.sourceFile;
1798
+ // Create staticValues array matching DEX format:
1799
+ // - Find the last static field with a non-default initializer
1800
+ // - Create array up to that index with values/nulls
1801
+ // - Fields after the last initializer are not included
1802
+ // - Default values (false, null) are not considered initializers
1803
+ const staticFieldsList = smaliFields?.staticFields ?? [];
1804
+ let lastIndexWithInitializer = -1;
1805
+ for (let i = staticFieldsList.length - 1; i >= 0; i--) {
1806
+ const initValue = staticFieldsList[i].initialValue;
1807
+ // Only consider non-default values as initializers
1808
+ // Numbers, bigints, strings, and true are non-default
1809
+ // false and null are default values
1810
+ if (initValue !== undefined && typeof initValue === 'number') {
1811
+ lastIndexWithInitializer = i;
1812
+ break;
1813
+ }
1814
+ if (initValue !== undefined && typeof initValue === 'bigint') {
1815
+ lastIndexWithInitializer = i;
1816
+ break;
1817
+ }
1818
+ if (initValue !== undefined && typeof initValue === 'string') {
1819
+ lastIndexWithInitializer = i;
1820
+ break;
1821
+ }
1822
+ if (initValue === true) {
1823
+ lastIndexWithInitializer = i;
1824
+ break;
1825
+ }
1826
+ }
1827
+ const staticValues = lastIndexWithInitializer === -1
1828
+ ? []
1829
+ : staticFieldsList
1830
+ .slice(0, lastIndexWithInitializer + 1)
1831
+ .map(smaliField => {
1832
+ const fieldType = smaliField.field.field.type;
1833
+ if (smaliField.initialValue === undefined) {
1834
+ // For integer types without initializer, DEX stores 0
1835
+ if (fieldType === 'I') {
1836
+ return { type: 'int', value: 0 };
1837
+ }
1838
+ if (fieldType === 'B') {
1839
+ return { type: 'byte', value: 0 };
1840
+ }
1841
+ if (fieldType === 'S') {
1842
+ return { type: 'short', value: 0 };
1843
+ }
1844
+ if (fieldType === 'C') {
1845
+ return { type: 'char', value: 0 };
1846
+ }
1847
+ // For long types without initializer, DEX stores 0n
1848
+ if (fieldType === 'J') {
1849
+ return { type: 'long', value: 0n };
1850
+ }
1851
+ // For float/double types without initializer, DEX stores 0
1852
+ if (fieldType === 'F') {
1853
+ return { type: 'float', value: 0 };
1854
+ }
1855
+ if (fieldType === 'D') {
1856
+ return { type: 'double', value: 0 };
1857
+ }
1858
+ // For boolean types without initializer, DEX stores false
1859
+ if (fieldType === 'Z') {
1860
+ return { type: 'boolean', value: false };
1861
+ }
1862
+ // For other types (reference types, etc.), return null
1863
+ return { type: 'null', value: null };
1864
+ }
1865
+ // Numeric values are stored in static values array
1866
+ if (typeof smaliField.initialValue === 'number') {
1867
+ // Convert to BigInt for long (J) types
1868
+ if (fieldType === 'J') {
1869
+ return { type: 'long', value: BigInt(smaliField.initialValue) };
1870
+ }
1871
+ if (fieldType === 'B') {
1872
+ return { type: 'byte', value: smaliField.initialValue };
1873
+ }
1874
+ if (fieldType === 'S') {
1875
+ return { type: 'short', value: smaliField.initialValue };
1876
+ }
1877
+ if (fieldType === 'C') {
1878
+ return { type: 'char', value: smaliField.initialValue };
1879
+ }
1880
+ if (fieldType === 'F') {
1881
+ return { type: 'float', value: smaliField.initialValue };
1882
+ }
1883
+ if (fieldType === 'D') {
1884
+ return { type: 'double', value: smaliField.initialValue };
1885
+ }
1886
+ // Default to int for other numeric types
1887
+ return { type: 'int', value: smaliField.initialValue };
1888
+ }
1889
+ // BigInt values for long (J) types
1890
+ if (typeof smaliField.initialValue === 'bigint') {
1891
+ return { type: 'long', value: smaliField.initialValue };
1892
+ }
1893
+ // String values should be stored as string type
1894
+ if (typeof smaliField.initialValue === 'string') {
1895
+ return { type: 'string', value: smaliField.initialValue };
1896
+ }
1897
+ // Boolean true is a non-default value and should be in staticValues
1898
+ if (smaliField.initialValue === true) {
1899
+ return { type: 'boolean', value: true };
1900
+ }
1901
+ // Boolean false and null are default values
1902
+ // For boolean fields with explicit false, return false
1903
+ if (smaliField.initialValue === false && fieldType === 'Z') {
1904
+ return { type: 'boolean', value: false };
1905
+ }
1906
+ // For null or other default values, return null
1907
+ return { type: 'null', value: null };
1908
+ });
1909
+ const fields = {
1910
+ staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
1911
+ instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
1912
+ };
1913
+ // Sort fields to match DEX file order (by field name, then type)
1914
+ // This matches the field order in class_data_item in DEX files
1915
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
1916
+ const sortFields = (fieldsList) => {
1917
+ fieldsList.sort((a, b) => {
1918
+ // First by field name (case-sensitive comparison)
1919
+ if (a.field.name !== b.field.name) {
1920
+ return a.field.name < b.field.name ? -1 : 1;
1921
+ }
1922
+ // Then by field type (case-sensitive comparison)
1923
+ return a.field.type < b.field.type ? -1 : 1;
1924
+ });
1925
+ };
1926
+ sortFields(fields.staticFields);
1927
+ sortFields(fields.instanceFields);
1928
+ const annotations = {
1929
+ classAnnotations: (classAnnotations ?? []).map(annotation => ({
1930
+ type: annotation.type,
1931
+ visibility: annotation.visibility,
1932
+ elements: annotation.elements.map(element => ({
1933
+ name: element.name,
1934
+ value: convertToTaggedEncodedValue(element.value),
1935
+ })),
1936
+ })),
1937
+ fieldAnnotations: [],
1938
+ methodAnnotations: methods.methodAnnotations,
1939
+ parameterAnnotations: methods.parameterAnnotations,
1940
+ };
1941
+ for (const smaliField of [...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? [])]) {
1942
+ if (smaliField.annotations.length === 0) {
1943
+ continue;
1944
+ }
1945
+ const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(fieldAnnotation.field, smaliField.field.field));
1946
+ if (existingFieldAnnotations) {
1947
+ existingFieldAnnotations.annotations ??= [];
1948
+ existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
1949
+ type: annotation.type,
1950
+ visibility: annotation.visibility,
1951
+ elements: annotation.elements.map(element => ({
1952
+ name: element.name,
1953
+ value: convertToTaggedEncodedValue(element.value),
1954
+ })),
1955
+ })));
1956
+ continue;
1957
+ }
1958
+ annotations.fieldAnnotations.push({
1959
+ field: smaliField.field.field,
1960
+ annotations: smaliField.annotations.map(annotation => ({
1961
+ type: annotation.type,
1962
+ visibility: annotation.visibility,
1963
+ elements: annotation.elements.map(element => ({
1964
+ name: element.name,
1965
+ value: convertToTaggedEncodedValue(element.value),
1966
+ })),
1967
+ })),
1968
+ });
1969
+ }
1970
+ // Sort field annotations to match DEX file order (by field name, then type)
1971
+ // This matches the annotations_directory_item field_annotations order in DEX files
1972
+ // Use binary string comparison (case-sensitive) to match UTF-8 byte order
1973
+ annotations.fieldAnnotations.sort((a, b) => {
1974
+ // First by field name (case-sensitive comparison)
1975
+ if (a.field.name !== b.field.name) {
1976
+ return a.field.name < b.field.name ? -1 : 1;
1977
+ }
1978
+ // Then by field type (case-sensitive comparison)
1979
+ return a.field.type < b.field.type ? -1 : 1;
1980
+ });
1981
+ // Compute synthetic flag from members if not explicitly set
1982
+ // This matches baksmali behavior where synthetic flag at class level is not output
1983
+ // but can be inferred from all members being synthetic
1984
+ const allMembers = [
1985
+ ...fields.staticFields,
1986
+ ...fields.instanceFields,
1987
+ ...methods.directMethods,
1988
+ // Note: virtualMethods are not included, matching DEX parser behavior
1989
+ ];
1990
+ const allMembersAreSynthetic = (allMembers.every(member => member.accessFlags.synthetic)
1991
+ && allMembers.length > 0);
1992
+ const finalAccessFlags = {
1993
+ ...accessFlags,
1994
+ // Use the synthetic flag from the class declaration, or compute it from members if not set
1995
+ synthetic: accessFlags.synthetic || allMembersAreSynthetic,
1996
+ };
1997
+ return {
1998
+ accessFlags: finalAccessFlags,
1999
+ class: class_,
2000
+ superclass,
2001
+ sourceFile,
2002
+ annotations: ((annotations.classAnnotations?.length
2003
+ || annotations.fieldAnnotations?.length
2004
+ || annotations.methodAnnotations?.length
2005
+ || annotations.parameterAnnotations?.length)
2006
+ ? ({
2007
+ classAnnotations: annotations.classAnnotations,
2008
+ fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
2009
+ ...fieldAnnotation,
2010
+ field: {
2011
+ ...fieldAnnotation.field,
2012
+ class: class_,
2013
+ },
2014
+ })),
2015
+ methodAnnotations: annotations.methodAnnotations
2016
+ .map(methodAnnotation => ({
2017
+ ...methodAnnotation,
2018
+ method: {
2019
+ ...methodAnnotation.method,
2020
+ class: class_,
2021
+ },
2022
+ }))
2023
+ // Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
2024
+ // This matches the method_idx order in the DEX file's method_id table
2025
+ .sort((a, b) => {
2026
+ // Sort by method name first (using code point comparison, not locale-aware)
2027
+ if (a.method.name !== b.method.name) {
2028
+ return a.method.name < b.method.name ? -1 : 1;
2029
+ }
2030
+ // Then by shorty (prototype signature)
2031
+ return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
2032
+ }),
2033
+ parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
2034
+ ...parameterAnnotation,
2035
+ method: {
2036
+ ...parameterAnnotation.method,
2037
+ class: class_,
2038
+ },
2039
+ })),
2040
+ })
2041
+ : undefined),
2042
+ classData: ((methods.directMethods.length > 0
2043
+ || methods.virtualMethods.length > 0
2044
+ || (fields?.staticFields.length ?? 0)
2045
+ || (fields?.instanceFields.length ?? 0))
2046
+ ? ({
2047
+ directMethods: methods.directMethods.map(method => ({
2048
+ ...method,
2049
+ method: {
2050
+ ...method.method,
2051
+ class: class_,
2052
+ },
2053
+ })),
2054
+ virtualMethods: methods.virtualMethods.map(method => ({
2055
+ ...method,
2056
+ method: {
2057
+ ...method.method,
2058
+ class: class_,
2059
+ },
2060
+ })),
2061
+ staticFields: (fields?.staticFields ?? []).map(field => ({
2062
+ ...field,
2063
+ field: {
2064
+ ...field.field,
2065
+ class: class_,
2066
+ },
2067
+ })),
2068
+ instanceFields: (fields?.instanceFields ?? []).map(field => ({
2069
+ ...field,
2070
+ field: {
2071
+ ...field.field,
2072
+ class: class_,
2073
+ },
2074
+ })),
2075
+ })
2076
+ : undefined),
2077
+ interfaces: interfaces ?? [],
2078
+ staticValues,
2079
+ };
2080
+ });
2081
+ setParserName(smaliParser, 'smaliParser');