Skip to content

Commit acef8b7

Browse files
committed
Refactoring
prepare to support more decompiler tools Add clear cache command
1 parent 44ff4e2 commit acef8b7

File tree

10 files changed

+167
-91
lines changed

10 files changed

+167
-91
lines changed

src/command/clear-cache.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
}

src/command/decompile.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
const decompiler = decompilerFactory.getSmailDecompiler("jadx")
29+
const resultUri = await decompiler.decompile(uri)
30+
showDecompileResult(resultUri, provider)
31+
} catch(err: any) {
32+
window.showErrorMessage(`Decompile failed: ${err.message}`)
33+
}
34+
})
35+
}

src/decompiler/JadxDecompiler.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/decompiler/SmaliDecompiler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { OutputChannel } from "vscode";
1+
import { Uri } from "vscode";
22

33
export interface SmaliDecompiler {
4-
decompile(inputFilePath: string, outputChannel: OutputChannel, options?: any): Promise<string>
4+
decompile(smaliFileUri: Uri, options?: any): Promise<Uri>
5+
}
6+
7+
export class DecompileError extends Error {
8+
constructor(msg: string) {
9+
super(msg);
10+
Object.setPrototypeOf(this, DecompileError.prototype);
11+
}
512
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
export class JadxDecompiler implements SmaliDecompiler {
13+
constructor(
14+
public sourceOutputDir: string,
15+
public outputChannel: OutputChannel,
16+
) {
17+
18+
}
19+
20+
private getOutputFilePath(smaliClassName: string) {
21+
return join(this.sourceOutputDir, (smaliClassName.includes("/") ? "" : "defpackage/") + smaliClassName + ".java")
22+
}
23+
24+
private async getJadxPath(): Promise<string> {
25+
const jadxPath: string | undefined = workspace.getConfiguration("smali2java").get("jadxPath")
26+
if (!jadxPath) throw new DecompileError("The jadx executable path has not been configured")
27+
if (!(await fsAsync.stat(jadxPath)).isFile()) throw new DecompileError("Illegal jadx executable path")
28+
return jadxPath
29+
}
30+
31+
async decompile(smaliFileUri: Uri, options?: any): Promise<Uri> {
32+
const smaliClassName = await getSmaliDocumentClassNameFromUri(smaliFileUri)
33+
if (!smaliClassName) throw new DecompileError("Illegal smali file")
34+
const outputFilePath = this.getOutputFilePath(smaliClassName)
35+
const { stdout, stderr } = await execAsync(`${await this.getJadxPath()} "${smaliFileUri.fsPath}" -ds "${this.sourceOutputDir}" ${options ?? ""}`)
36+
this.outputChannel.append(stdout)
37+
if (stderr && stderr.length > 0) {
38+
this.outputChannel.show()
39+
this.outputChannel.append(stderr)
40+
throw new DecompileError("View the output for details")
41+
}
42+
try {
43+
await fsAsync.stat(outputFilePath)
44+
} catch {
45+
throw new DecompileError("The compiled file is not found")
46+
}
47+
return Uri.from({
48+
scheme: JavaCodeProvider.scheme,
49+
path: outputFilePath
50+
})
51+
}
52+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
}

src/extension.ts

Lines changed: 10 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,15 @@
1-
import { window, workspace, ExtensionContext, commands, Uri, languages, ProgressLocation, Progress, CancellationToken, TextDocument } from 'vscode';
2-
import { getSmaliDocumentClassName } from './util/smali-util';
3-
import { promises as fs } from 'fs';
4-
import { JadxDecompiler } from './decompiler/JadxDecompiler';
5-
import { join } from "path"
6-
import JavaCodeProvider from './provider';
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';
75

86
export function activate(context: ExtensionContext) {
97
const provider = new JavaCodeProvider()
10-
workspace.registerTextDocumentContentProvider(JavaCodeProvider.scheme, provider);
11-
const outputChannel = window.createOutputChannel("Smali2Java")
12-
let decompileCommandRegistration = commands.registerCommand("smali2java.decompileCurrentSmaliToJava", async (uri: Uri) => {
13-
const jadxPath: string | undefined = workspace.getConfiguration("smali2java").get("jadxPath")
14-
if (!jadxPath) {
15-
window.showErrorMessage("Please configure the jadx executable path first")
16-
return
17-
}
18-
if (!(await fs.stat(jadxPath)).isFile()) {
19-
window.showErrorMessage("Invalid jadx executable path")
20-
return
21-
}
22-
const document = window.activeTextEditor?.document
23-
if (!document) {
24-
window.showErrorMessage("No editor is active")
25-
return
26-
}
27-
const className = getSmaliDocumentClassName(document)
28-
if (!className) {
29-
window.showErrorMessage("Invalid smali file")
30-
return
31-
}
32-
window.withProgress({
33-
location: ProgressLocation.Notification,
34-
title: "Decompiling",
35-
cancellable: true
36-
},async (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => {
37-
const decompiler = new JadxDecompiler(jadxPath, join(context.globalStorageUri.fsPath, "decompiled", "temp"), className)
38-
try {
39-
const resultJavaFilePath = await decompiler.decompile(uri.fsPath, outputChannel)
40-
const resultUri = Uri.parse(`${JavaCodeProvider.scheme}:${resultJavaFilePath}`)
41-
const loadedDocument = workspace.textDocuments.find(document => !document.isClosed && document.uri.toString() == resultUri.toString())
42-
if (loadedDocument) {
43-
provider.onDidChangeEmitter.fire(resultUri)
44-
await window.showTextDocument(loadedDocument)
45-
return
46-
}
47-
const textDoc = await workspace.openTextDocument(resultUri);
48-
const javaDoc = await languages.setTextDocumentLanguage(textDoc, "java")
49-
await window.showTextDocument(javaDoc)
50-
} catch(err) {
51-
window.showErrorMessage("Decompile failed")
52-
}
53-
})
54-
});
55-
context.subscriptions.push(decompileCommandRegistration);
8+
context.subscriptions.push(
9+
workspace.registerTextDocumentContentProvider(JavaCodeProvider.scheme, provider),
10+
commands.registerCommand("smali2java.decompileCurrentSmaliToJava", DecompileCommand(context, provider)),
11+
commands.registerCommand("smali2java.clearCache", clearCacheCommand(context))
12+
);
5613
}
5714

58-
59-
export function deactivate() { }
15+
export function deactivate() { }

src/util/smali-util.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { TextDocument } from "vscode";
1+
import { TextDocument, Uri } from "vscode";
2+
import * as fs from "fs"
3+
import * as readline from "readline"
24

35
export function getSmaliDocumentClassName(document: TextDocument) {
46
const count = document.lineCount;
@@ -10,4 +12,25 @@ export function getSmaliDocumentClassName(document: TextDocument) {
1012
}
1113
}
1214
return undefined;
15+
}
16+
17+
export async function getSmaliDocumentClassNameFromUri(uri: Uri): Promise<string | undefined> {
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<string>(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+
})
1336
}

0 commit comments

Comments
 (0)