├── .gitignore ├── res ├── icon.png ├── dark │ └── decompile.png ├── light │ └── decompile.png └── snapshot │ └── usage.gif ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .vscodeignore ├── src ├── decompiler │ ├── SmaliDecompilerFactory.ts │ ├── SmaliDecompiler.ts │ └── impl │ │ ├── SmaliDecompilerFactoryImpl.ts │ │ └── JadxDecompiler.ts ├── command │ ├── clear-cache.ts │ └── decompile.ts ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── provider │ └── JavaCodeProvider.ts ├── extension.ts └── util │ └── smali-util.ts ├── .eslintrc.json ├── tsconfig.json ├── README_CN.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── vsc-extension-quickstart.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/only52607/smali2java/HEAD/res/icon.png -------------------------------------------------------------------------------- /res/dark/decompile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/only52607/smali2java/HEAD/res/dark/decompile.png -------------------------------------------------------------------------------- /res/light/decompile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/only52607/smali2java/HEAD/res/light/decompile.png -------------------------------------------------------------------------------- /res/snapshot/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/only52607/smali2java/HEAD/res/snapshot/usage.gif -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | vsc-extension-quickstart.md 9 | **/tsconfig.json 10 | **/.eslintrc.json 11 | **/*.map 12 | **/*.ts 13 | -------------------------------------------------------------------------------- /src/decompiler/SmaliDecompilerFactory.ts: -------------------------------------------------------------------------------- 1 | import { SmaliDecompiler } from "./SmaliDecompiler"; 2 | 3 | // TODO: Support more decompiler 4 | export type DecompilerName = "jadx" 5 | 6 | export interface SmaliDecompilerFactory { 7 | getSmailDecompiler(decompilerName: DecompilerName): SmaliDecompiler 8 | } -------------------------------------------------------------------------------- /src/decompiler/SmaliDecompiler.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from "vscode"; 2 | 3 | export interface SmaliDecompiler { 4 | decompile(smaliFileUri: Uri, options?: any): Promise 5 | } 6 | 7 | export class DecompileError extends Error { 8 | constructor(msg: string) { 9 | super(msg); 10 | Object.setPrototypeOf(this, DecompileError.prototype); 11 | } 12 | } -------------------------------------------------------------------------------- /src/command/clear-cache.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path" 2 | import { ExtensionContext, window } from "vscode" 3 | import { promises as fsAsync } from "fs" 4 | 5 | export default (context: ExtensionContext) => async () => { 6 | const tempPath = join(context.globalStorageUri.fsPath, "decompiled") 7 | await fsAsync.rmdir(tempPath, { recursive: true }) 8 | window.showInformationMessage("Decompiled files has been cleared") 9 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/provider/JavaCodeProvider.ts: -------------------------------------------------------------------------------- 1 | import { Uri, TextDocumentContentProvider, EventEmitter } from 'vscode'; 2 | import { promises as fs } from 'fs'; 3 | 4 | export default class JavaCodeProvider implements TextDocumentContentProvider { 5 | static scheme = 'smali2java'; 6 | 7 | onDidChangeEmitter = new EventEmitter() 8 | onDidChange = this.onDidChangeEmitter.event 9 | 10 | dispose() { 11 | this.onDidChangeEmitter.dispose() 12 | } 13 | 14 | async provideTextDocumentContent(uri: Uri): Promise { 15 | const buffer = await fs.readFile(uri.path) 16 | return buffer.toString() 17 | } 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { workspace, ExtensionContext, commands } from 'vscode'; 2 | import JavaCodeProvider from './provider/JavaCodeProvider'; 3 | import DecompileCommand from './command/decompile'; 4 | import clearCacheCommand from './command/clear-cache'; 5 | 6 | export function activate(context: ExtensionContext) { 7 | const provider = new JavaCodeProvider(); 8 | context.subscriptions.push( 9 | workspace.registerTextDocumentContentProvider(JavaCodeProvider.scheme, provider), 10 | commands.registerCommand("smali2java.decompileThisFile", DecompileCommand(context, provider)), 11 | commands.registerCommand("smali2java.clearCache", clearCacheCommand(context)) 12 | ); 13 | } 14 | 15 | export function deactivate() { } -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Smali2Java 2 | 3 | smali2java是一个vscode插件,允许你随时将一个`smali`文件反编译为java代码,这在测试`smali`修改效果时很有帮助。 4 | 5 | ## 插件安装 6 | https://marketplace.visualstudio.com/items?itemName=ooooonly.smali2java 7 | 8 | ## 使用方法 9 | 10 | 1. 将 `smali2java.decompiler.jadx.path`配置项设置为`jadx`可执行文件路径。 11 | 12 | > [jadx](https://github.com/skylot/jadx) 是一个优秀的反编译工具。 Smali2Java 使用它完成反编译工作。后续将会支持调用更多反编译工具。 13 | 14 | 下载 [jadx](https://github.com/skylot/jadx), 解压至某处, 修改配置项 `smali2java.decompiler.jadx.path` 为你的jadx可执行文件路径 (不是 jadx-gui)。 15 | - 比如: C:/Program Files/jadx/bin/jadx.bat 16 | 17 | 2. 使用vscode打开smali文件。在右键菜单中选择 `Decompile to Java` 。或者直接点击标题栏上的 `Decompile` 按钮. 18 | 19 | ![Usage](./res/snapshot/usage.gif) 20 | 21 | ## 插件设置 22 | 23 | * `smali2java.decompiler.jadx.path`: jadx可执行文件路径 24 | * `smali2java.decompiler.jadx.options`: jadx命令行参数 25 | -------------------------------------------------------------------------------- /src/decompiler/impl/SmaliDecompilerFactoryImpl.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import { OutputChannel, window } from "vscode"; 3 | import { SmaliDecompiler } from "../SmaliDecompiler"; 4 | import { SmaliDecompilerFactory } from "../SmaliDecompilerFactory"; 5 | import { JadxDecompiler } from "./JadxDecompiler"; 6 | 7 | export class SmaliDecompilerFactoryImpl implements SmaliDecompilerFactory { 8 | outputChannel: OutputChannel; 9 | constructor( 10 | public decompileTempPath: string 11 | ) { 12 | this.outputChannel = window.createOutputChannel("Smali2Java") 13 | } 14 | getSmailDecompiler(decompilerName: "jadx"): SmaliDecompiler { 15 | switch (decompilerName) { 16 | case "jadx": 17 | return new JadxDecompiler(join(this.decompileTempPath, "jadx"), this.outputChannel) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "smali2java" extension will be documented in this file. 4 | 5 | ## [1.1.1] 6 | 7 | ### Fixed 8 | 9 | Fixed unexpected character escaping in the path when executing shell commands. 10 | 11 | ## [1.1.0] 12 | 13 | ### Changes 14 | 15 | The old configuration keys are deprecated, you need to update the configuration, read README.md for details. 16 | 17 | ### Fixed 18 | 19 | - Missing command options (#10). 20 | 21 | ### Improve 22 | 23 | - New decompilation icon button in the title bar. 24 | 25 | ## [1.0.1] 26 | 27 | ### Fixed 28 | 29 | - Decompile failed when execute command from command bar(#7). 30 | 31 | ## [1.0.0] 32 | 33 | ### Fixed 34 | 35 | - Fixed command not being activated when no smali language extension is installed(#5). 36 | 37 | 38 | ## [0.1.0] - 2022-02-13 39 | 40 | ### Added 41 | 42 | - Clear cache command to clear the decompiled files. -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 ooooonly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smali2Java 2 | 3 | Smali2Java is a vscode extension that allows you to decompile a single `smali` file into Java code, which can be useful especially if you want to check that your modified smali code is correct. 4 | 5 | [中文说明](/README_CN.md) 6 | 7 | ## Usage 8 | 9 | 1. Configure the path to the `jadx` executable in `smali2java.decompiler.jadx.path`. 10 | 11 | > [Jadx](https://github.com/skylot/jadx) is an excellent Java bytecode decompiler. Smali2Java uses it for decompilation. More decompilation tools will be supported in future releases. 12 | 13 | Download [Jadx](https://github.com/skylot/jadx), unzip it somewhere, and modify the configuration item `smali2java.decompiler.jadx.path` to point to the path of the Jadx executable (not jadx-gui). 14 | - example: C:/Program Files/jadx/bin/jadx.bat 15 | 16 | 2. Open a smali file using vscode.Then select `Decompile This File` from the editor context menu. Or just click the `Decompile` in the editor title bar. 17 | 18 | ![Usage](./res/snapshot/usage.gif) 19 | 20 | ## Extension Settings 21 | 22 | * `smali2java.decompiler.jadx.path`: Path to jadx (or jadx.bat for windows). 23 | * `smali2java.decompiler.jadx.options`: Options used to run jadx decompilation command (will be appended directly to the end of the command). 24 | -------------------------------------------------------------------------------- /src/util/smali-util.ts: -------------------------------------------------------------------------------- 1 | import { TextDocument, Uri } from "vscode"; 2 | import * as fs from "fs" 3 | import * as readline from "readline" 4 | 5 | export function getSmaliDocumentClassName(document: TextDocument) { 6 | const count = document.lineCount; 7 | for (let i = 0; i < count; i++) { 8 | const line = document.lineAt(i).text; 9 | const result = line.match(/\.class.*? L(.+);/); 10 | if (result && result.length == 2) { 11 | return result[1]; 12 | } 13 | } 14 | return undefined; 15 | } 16 | 17 | export async function getSmaliDocumentClassNameFromUri(uri: Uri): Promise { 18 | const readStream = fs.createReadStream(uri.fsPath) 19 | const reader = readline.createInterface({ 20 | input: readStream, 21 | output: process.stdout, 22 | terminal: false 23 | }); 24 | return await new Promise(resolve => { 25 | reader.on('line', (line) => { 26 | const result = line.match(/\.class.*? L(.+);/); 27 | if (result && result.length == 2) { 28 | resolve(result[1]) 29 | // It doesn't work 30 | reader.close() 31 | readStream.close() 32 | readStream.destroy() 33 | } 34 | }); 35 | }) 36 | } -------------------------------------------------------------------------------- /src/command/decompile.ts: -------------------------------------------------------------------------------- 1 | import { window, workspace, ExtensionContext, Uri, languages, ProgressLocation, Progress, CancellationToken } from 'vscode'; 2 | import JavaCodeProvider from '../provider/JavaCodeProvider'; 3 | import { SmaliDecompilerFactoryImpl } from '../decompiler/impl/SmaliDecompilerFactoryImpl'; 4 | import { SmaliDecompilerFactory } from '../decompiler/SmaliDecompilerFactory'; 5 | import { join } from 'path'; 6 | 7 | async function showDecompileResult(uri: Uri, provider: JavaCodeProvider) { 8 | const loadedDocument = workspace.textDocuments.find(document => !document.isClosed && document.uri.toString() == uri.toString()) 9 | if (loadedDocument) { 10 | provider.onDidChangeEmitter.fire(uri) 11 | await window.showTextDocument(loadedDocument) 12 | return 13 | } 14 | const textDoc = await workspace.openTextDocument(uri); 15 | const javaDoc = await languages.setTextDocumentLanguage(textDoc, "java") 16 | await window.showTextDocument(javaDoc) 17 | } 18 | 19 | export default (context: ExtensionContext, provider: JavaCodeProvider) => { 20 | const decompilerFactory: SmaliDecompilerFactory = new SmaliDecompilerFactoryImpl(join(context.globalStorageUri.fsPath, "decompiled")) 21 | const decompileProgressOptions = { 22 | location: ProgressLocation.Notification, 23 | title: "Decompiling", 24 | cancellable: true 25 | } 26 | return async (uri: Uri) => window.withProgress(decompileProgressOptions, async (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => { 27 | try { 28 | uri = uri ?? window.activeTextEditor?.document?.uri 29 | if (!uri) throw { message: "No active document" } 30 | const decompiler = decompilerFactory.getSmailDecompiler("jadx") 31 | const resultUri = await decompiler.decompile(uri) 32 | showDecompileResult(resultUri, provider) 33 | } catch(err: any) { 34 | window.showErrorMessage(`Decompile failed: ${err.message}`) 35 | } 36 | }) 37 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | 25 | ## Explore the API 26 | 27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 28 | 29 | ## Run tests 30 | 31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 32 | * Press `F5` to run the tests in a new window with your extension loaded. 33 | * See the output of the test result in the debug console. 34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 36 | * You can create folders inside the `test` folder to structure your tests any way you want. 37 | 38 | ## Go further 39 | 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | -------------------------------------------------------------------------------- /src/decompiler/impl/JadxDecompiler.ts: -------------------------------------------------------------------------------- 1 | import { DecompileError, SmaliDecompiler } from "../SmaliDecompiler" 2 | import { OutputChannel, Uri, workspace } from "vscode"; 3 | import { join } from "path"; 4 | import { promises as fsAsync } from "fs" 5 | import JavaCodeProvider from "../../provider/JavaCodeProvider"; 6 | import { promisify } from 'util' 7 | import { exec } from "child_process" 8 | import { getSmaliDocumentClassNameFromUri } from "../../util/smali-util"; 9 | 10 | const execAsync = promisify(exec) 11 | 12 | interface JadxConfig { 13 | path?: string, 14 | options?: string 15 | } 16 | 17 | export class JadxDecompiler implements SmaliDecompiler { 18 | constructor( 19 | public sourceOutputDir: string, 20 | public outputChannel: OutputChannel, 21 | ) { 22 | } 23 | 24 | private getOutputFilePath(smaliClassName: string) { 25 | return join(this.sourceOutputDir, (smaliClassName.includes("/") ? "" : "defpackage/") + smaliClassName + ".java") 26 | } 27 | 28 | private async loadConfig(): Promise { 29 | const config = workspace.getConfiguration("smali2java.decompiler.jadx") 30 | return { 31 | path: config.get("path"), 32 | options: config.get("options") 33 | } 34 | } 35 | 36 | async decompile(smaliFileUri: Uri): Promise { 37 | const smaliClassName = await getSmaliDocumentClassNameFromUri(smaliFileUri) 38 | if (!smaliClassName) throw new DecompileError("Illegal smali file") 39 | const config = await this.loadConfig() 40 | if (!config.path) throw new DecompileError("The jadx executable path has not been configured") 41 | if (!(await fsAsync.stat(config.path)).isFile()) throw new DecompileError("Illegal jadx executable path") 42 | const outputFilePath = this.getOutputFilePath(smaliClassName) 43 | const { stdout, stderr } = await execAsync(`${await config.path} ${this.quote(smaliFileUri.fsPath)} -ds ${this.quote(this.sourceOutputDir)} ${config.options ?? ""}`) 44 | this.outputChannel.append(stdout) 45 | if (stderr && stderr.length > 0) { 46 | this.outputChannel.show() 47 | this.outputChannel.append(stderr) 48 | throw new DecompileError("View the output for details") 49 | } 50 | try { 51 | await fsAsync.stat(outputFilePath) 52 | } catch(e) { 53 | throw new DecompileError(`Error is caught when reading ${outputFilePath}: ${e}`) 54 | } 55 | return Uri.from({ 56 | scheme: JavaCodeProvider.scheme, 57 | path: outputFilePath 58 | }) 59 | } 60 | 61 | private quote(str: string) { 62 | if (process.platform == "win32") { 63 | return `"${str}"` 64 | } 65 | return `'${str}'` 66 | } 67 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smali2java", 3 | "author": "ooooonly", 4 | "publisher": "ooooonly", 5 | "displayName": "Smali2Java", 6 | "description": "Decompile single smali file instantly", 7 | "version": "1.1.1", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "icon": "res/icon.png", 10 | "engines": { 11 | "vscode": "^1.58.0" 12 | }, 13 | "categories": [ 14 | "Other" 15 | ], 16 | "activationEvents": [ 17 | "onLanguage:smali" 18 | ], 19 | "keywords": [ 20 | "smali", 21 | "smali2java", 22 | "decompile", 23 | "decompiler" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/only52607/smali2java.git" 28 | }, 29 | "homepage": "https://github.com/only52607/smali2java/blob/main/README.md", 30 | "bugs": { 31 | "url": "https://github.com/only52607/smali2java/issues", 32 | "email": "ooooonly@foxmail.com" 33 | }, 34 | "main": "./out/extension.js", 35 | "contributes": { 36 | "commands": [ 37 | { 38 | "command": "smali2java.decompileThisFile", 39 | "title": "Decompile This File", 40 | "shortTitle": "Decompile", 41 | "category": "Smali", 42 | "enablement": "true", 43 | "icon": { 44 | "dark": "./res/dark/decompile.png", 45 | "light": "./res/light/decompile.png" 46 | } 47 | }, 48 | { 49 | "command": "smali2java.clearCache", 50 | "title": "Clear decompiled files", 51 | "shortTitle": "Clear cache", 52 | "category": "Smali", 53 | "enablement": "true" 54 | } 55 | ], 56 | "menus": { 57 | "editor/title": [ 58 | { 59 | "command": "smali2java.decompileThisFile", 60 | "when": "resourceExtname == .smali || editorLangId == smali", 61 | "group": "navigation" 62 | } 63 | ], 64 | "editor/context": [ 65 | { 66 | "command": "smali2java.decompileThisFile", 67 | "when": "resourceExtname == .smali || editorLangId == smali", 68 | "group": "navigation" 69 | } 70 | ] 71 | }, 72 | "configuration": { 73 | "title": "Smali2Java", 74 | "properties": { 75 | "smali2java.decompiler.jadx.path": { 76 | "type": "string", 77 | "default": null, 78 | "description": "Path to jadx (or jadx.bat for windows)." 79 | }, 80 | "smali2java.decompiler.jadx.options": { 81 | "type": "string", 82 | "default": "", 83 | "description": "Options used to run jadx decompilation command (will be appended directly to the end of the command)." 84 | } 85 | } 86 | } 87 | }, 88 | "scripts": { 89 | "vscode:prepublish": "npm run compile", 90 | "compile": "tsc -p ./", 91 | "watch": "tsc -watch -p ./", 92 | "pretest": "npm run compile && npm run lint", 93 | "lint": "eslint src --ext ts", 94 | "test": "node ./out/test/runTest.js" 95 | }, 96 | "devDependencies": { 97 | "@types/vscode": "^1.58.0", 98 | "@types/glob": "^7.1.3", 99 | "@types/mocha": "^8.2.2", 100 | "@types/node": "14.x", 101 | "eslint": "^7.27.0", 102 | "@typescript-eslint/eslint-plugin": "^4.26.0", 103 | "@typescript-eslint/parser": "^4.26.0", 104 | "glob": "^7.1.7", 105 | "mocha": "^8.4.0", 106 | "typescript": "^4.3.2", 107 | "vscode-test": "^1.5.2" 108 | } 109 | } --------------------------------------------------------------------------------