Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
J
jadx
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open-source
jadx
Commits
ed385e8c
Unverified
Commit
ed385e8c
authored
Jun 18, 2019
by
skylot
Committed by
GitHub
Jun 18, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: output decompilation results in json format (#676)
parent
554e119e
Hide whitespace changes
Inline
Side-by-side
Showing
39 changed files
with
1085 additions
and
139 deletions
+1085
-139
README.md
README.md
+2
-0
JCommanderWrapper.java
jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java
+10
-0
JadxCLIArgs.java
jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
+16
-12
build.gradle
jadx-core/build.gradle
+1
-0
CodePosition.java
jadx-core/src/main/java/jadx/api/CodePosition.java
+9
-1
JadxArgs.java
jadx-core/src/main/java/jadx/api/JadxArgs.java
+19
-0
JadxDecompiler.java
jadx-core/src/main/java/jadx/api/JadxDecompiler.java
+1
-1
ClassGen.java
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
+37
-54
CodeGen.java
jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
+42
-13
CodeWriter.java
jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java
+16
-7
InsnGen.java
jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
+6
-1
MethodGen.java
jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
+57
-31
RegionGen.java
jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
+12
-1
JsonCodeGen.java
...ore/src/main/java/jadx/core/codegen/json/JsonCodeGen.java
+224
-0
JsonMappingGen.java
.../src/main/java/jadx/core/codegen/json/JsonMappingGen.java
+107
-0
JsonClass.java
...e/src/main/java/jadx/core/codegen/json/cls/JsonClass.java
+94
-0
JsonCodeLine.java
...rc/main/java/jadx/core/codegen/json/cls/JsonCodeLine.java
+33
-0
JsonField.java
...e/src/main/java/jadx/core/codegen/json/cls/JsonField.java
+5
-0
JsonMethod.java
.../src/main/java/jadx/core/codegen/json/cls/JsonMethod.java
+51
-0
JsonNode.java
...re/src/main/java/jadx/core/codegen/json/cls/JsonNode.java
+40
-0
JsonClsMapping.java
...n/java/jadx/core/codegen/json/mapping/JsonClsMapping.java
+71
-0
JsonFieldMapping.java
...java/jadx/core/codegen/json/mapping/JsonFieldMapping.java
+22
-0
JsonMapping.java
...main/java/jadx/core/codegen/json/mapping/JsonMapping.java
+15
-0
JsonMthMapping.java
...n/java/jadx/core/codegen/json/mapping/JsonMthMapping.java
+40
-0
Deobfuscator.java
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
+1
-1
AccessInfo.java
jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
+4
-0
FieldInfo.java
jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java
+6
-0
MethodInfo.java
jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java
+1
-1
MethodNode.java
jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
+4
-0
FallbackModeVisitor.java
...main/java/jadx/core/dex/visitors/FallbackModeVisitor.java
+9
-0
RenameVisitor.java
...e/src/main/java/jadx/core/dex/visitors/RenameVisitor.java
+4
-0
SaveCode.java
jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java
+18
-4
CodeGenUtils.java
jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java
+8
-0
ImmutableList.java
jadx-core/src/main/java/jadx/core/utils/ImmutableList.java
+19
-4
Utils.java
jadx-core/src/main/java/jadx/core/utils/Utils.java
+13
-0
InputFile.java
jadx-core/src/main/java/jadx/core/utils/files/InputFile.java
+1
-1
TestJsonOutput.java
...st/java/jadx/tests/integration/others/TestJsonOutput.java
+62
-0
build.gradle
jadx-gui/build.gradle
+0
-1
JadxSettings.java
jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+5
-6
No files found.
README.md
View file @
ed385e8c
...
...
@@ -66,6 +66,8 @@ options:
-j, --threads-count - processing threads count
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json' (default: java)
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
...
...
jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java
View file @
ed385e8c
...
...
@@ -112,6 +112,16 @@ public class JCommanderWrapper<T> {
// ignore
}
}
if
(
fieldType
==
String
.
class
)
{
try
{
String
val
=
(
String
)
f
.
get
(
args
);
if
(
val
!=
null
)
{
opt
.
append
(
" (default: "
).
append
(
val
).
append
(
')'
);
}
}
catch
(
Exception
e
)
{
// ignore
}
}
}
private
static
void
addSpaces
(
StringBuilder
str
,
int
count
)
{
...
...
jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
View file @
ed385e8c
...
...
@@ -46,6 +46,9 @@ public class JadxCLIArgs {
@Parameter
(
names
=
{
"--single-class"
},
description
=
"decompile a single class"
)
protected
String
singleClass
=
null
;
@Parameter
(
names
=
{
"--output-format"
},
description
=
"can be 'java' or 'json'"
)
protected
String
outputFormat
=
"java"
;
@Parameter
(
names
=
{
"-e"
,
"--export-gradle"
},
description
=
"save as android gradle project"
)
protected
boolean
exportAsGradleProject
=
false
;
...
...
@@ -86,7 +89,18 @@ public class JadxCLIArgs {
protected
boolean
deobfuscationForceSave
=
false
;
@Parameter
(
names
=
{
"--deobf-use-sourcename"
},
description
=
"use source file name as class name alias"
)
protected
boolean
deobfuscationUseSourceNameAsAlias
=
true
;
protected
boolean
deobfuscationUseSourceNameAsAlias
=
false
;
@Parameter
(
names
=
{
"--rename-flags"
},
description
=
"what to rename, comma-separated,"
+
" 'case' for system case sensitivity,"
+
" 'valid' for java identifiers,"
+
" 'printable' characters,"
+
" 'none' or 'all' (default)"
,
converter
=
RenameConverter
.
class
)
protected
Set
<
RenameEnum
>
renameFlags
=
EnumSet
.
allOf
(
RenameEnum
.
class
);
@Parameter
(
names
=
{
"--fs-case-sensitive"
},
description
=
"treat filesystem as case sensitive, false by default"
)
protected
boolean
fsCaseSensitive
=
false
;
...
...
@@ -100,17 +114,6 @@ public class JadxCLIArgs {
@Parameter
(
names
=
{
"-f"
,
"--fallback"
},
description
=
"make simple dump (using goto instead of 'if', 'for', etc)"
)
protected
boolean
fallbackMode
=
false
;
@Parameter
(
names
=
{
"--rename-flags"
},
description
=
"what to rename, comma-separated,"
+
" 'case' for system case sensitivity,"
+
" 'valid' for java identifiers,"
+
" 'printable' characters,"
+
" 'none' or 'all' (default)"
,
converter
=
RenameConverter
.
class
)
protected
Set
<
RenameEnum
>
renameFlags
=
EnumSet
.
allOf
(
RenameEnum
.
class
);
@Parameter
(
names
=
{
"-v"
,
"--verbose"
},
description
=
"verbose output"
)
protected
boolean
verbose
=
false
;
...
...
@@ -178,6 +181,7 @@ public class JadxCLIArgs {
args
.
setOutDir
(
FileUtils
.
toFile
(
outDir
));
args
.
setOutDirSrc
(
FileUtils
.
toFile
(
outDirSrc
));
args
.
setOutDirRes
(
FileUtils
.
toFile
(
outDirRes
));
args
.
setOutputFormat
(
JadxArgs
.
OutputFormatEnum
.
valueOf
(
outputFormat
.
toUpperCase
()));
args
.
setThreadsCount
(
threadsCount
);
args
.
setSkipSources
(
skipSources
);
if
(
singleClass
!=
null
)
{
...
...
jadx-core/build.gradle
View file @
ed385e8c
...
...
@@ -8,6 +8,7 @@ dependencies {
compile
'org.ow2.asm:asm:7.1'
compile
'org.jetbrains:annotations:17.0.0'
compile
'uk.com.robust-it:cloning:1.9.12'
compile
'com.google.code.gson:gson:2.8.5'
compile
'org.smali:baksmali:2.2.7'
compile
(
'org.smali:smali:2.2.7'
)
{
...
...
jadx-core/src/main/java/jadx/api/CodePosition.java
View file @
ed385e8c
...
...
@@ -57,6 +57,14 @@ public final class CodePosition {
@Override
public
String
toString
()
{
return
line
+
':'
+
offset
+
(
node
!=
null
?
" "
+
node
:
""
);
StringBuilder
sb
=
new
StringBuilder
();
sb
.
append
(
line
);
if
(
offset
!=
0
)
{
sb
.
append
(
':'
).
append
(
offset
);
}
if
(
node
!=
null
)
{
sb
.
append
(
' '
).
append
(
node
);
}
return
sb
.
toString
();
}
}
jadx-core/src/main/java/jadx/api/JadxArgs.java
View file @
ed385e8c
...
...
@@ -62,6 +62,12 @@ public class JadxArgs {
private
Set
<
RenameEnum
>
renameFlags
=
EnumSet
.
allOf
(
RenameEnum
.
class
);
public
enum
OutputFormatEnum
{
JAVA
,
JSON
}
private
OutputFormatEnum
outputFormat
=
OutputFormatEnum
.
JAVA
;
public
JadxArgs
()
{
// use default options
}
...
...
@@ -308,6 +314,18 @@ public class JadxArgs {
}
}
public
OutputFormatEnum
getOutputFormat
()
{
return
outputFormat
;
}
public
boolean
isJsonOutput
()
{
return
outputFormat
==
OutputFormatEnum
.
JSON
;
}
public
void
setOutputFormat
(
OutputFormatEnum
outputFormat
)
{
this
.
outputFormat
=
outputFormat
;
}
@Override
public
String
toString
()
{
return
"JadxArgs{"
+
"inputFiles="
+
inputFiles
...
...
@@ -333,6 +351,7 @@ public class JadxArgs {
+
", exportAsGradleProject="
+
exportAsGradleProject
+
", fsCaseSensitive="
+
fsCaseSensitive
+
", renameFlags="
+
renameFlags
+
", outputFormat="
+
outputFormat
+
'}'
;
}
}
jadx-core/src/main/java/jadx/api/JadxDecompiler.java
View file @
ed385e8c
...
...
@@ -215,7 +215,7 @@ public final class JadxDecompiler {
executor
.
execute
(()
->
{
try
{
cls
.
decompile
();
SaveCode
.
save
(
outDir
,
args
,
cls
.
getClassNode
());
SaveCode
.
save
(
outDir
,
cls
.
getClassNode
());
}
catch
(
Exception
e
)
{
LOG
.
error
(
"Error saving class: {}"
,
cls
.
getFullName
(),
e
);
}
...
...
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
View file @
ed385e8c
...
...
@@ -19,7 +19,6 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr;
import
jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField
;
import
jadx.core.dex.attributes.nodes.JadxError
;
import
jadx.core.dex.attributes.nodes.LineAttrNode
;
import
jadx.core.dex.attributes.nodes.SourceFileAttr
;
import
jadx.core.dex.info.AccessInfo
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.instructions.args.ArgType
;
...
...
@@ -128,8 +127,8 @@ public class ClassGen {
}
annotationGen
.
addForClass
(
clsCode
);
insertSourceFileInfo
(
clsCode
,
cls
);
insertRenameInfo
(
clsCode
,
cls
);
CodeGenUtils
.
addSourceFileInfo
(
clsCode
,
cls
);
clsCode
.
startLine
(
af
.
makeString
());
if
(
af
.
isInterface
())
{
if
(
af
.
isAnnotation
())
{
...
...
@@ -290,29 +289,21 @@ public class ClassGen {
return
false
;
}
private
void
addMethod
(
CodeWriter
code
,
MethodNode
mth
)
throws
CodegenException
{
public
void
addMethod
(
CodeWriter
code
,
MethodNode
mth
)
throws
CodegenException
{
CodeGenUtils
.
addComments
(
code
,
mth
);
if
(
mth
.
getAccessFlags
().
isAbstract
()
||
mth
.
getAccessFlags
().
isNative
())
{
MethodGen
mthGen
=
new
MethodGen
(
this
,
mth
);
mthGen
.
addDefinition
(
code
);
if
(
cls
.
getAccessFlags
().
isAnnotation
())
{
Object
def
=
annotationGen
.
getAnnotationDefaultValue
(
mth
.
getName
());
if
(
def
!=
null
)
{
code
.
add
(
" default "
);
annotationGen
.
encodeValue
(
code
,
def
);
}
}
code
.
add
(
';'
);
}
else
{
CodeGenUtils
.
addComments
(
code
,
mth
);
insertDecompilationProblems
(
code
,
mth
);
boolean
badCode
=
mth
.
contains
(
AFlag
.
INCONSISTENT_CODE
);
if
(
badCode
&&
showInconsistentCode
)
{
code
.
startLine
(
"/* Code decompiled incorrectly, please refer to instructions dump. */"
);
mth
.
remove
(
AFlag
.
INCONSISTENT_CODE
);
badCode
=
false
;
}
MethodGen
mthGen
;
if
(
badCode
||
mth
.
contains
(
AType
.
JADX_ERROR
)
||
fallback
)
{
if
(
badCode
||
fallback
||
mth
.
contains
(
AType
.
JADX_ERROR
)
||
mth
.
getRegion
()
==
null
)
{
mthGen
=
MethodGen
.
getFallbackMethodGen
(
mth
);
}
else
{
mthGen
=
new
MethodGen
(
this
,
mth
);
...
...
@@ -322,12 +313,7 @@ public class ClassGen {
}
code
.
add
(
'{'
);
code
.
incIndent
();
insertSourceFileInfo
(
code
,
mth
);
if
(
fallback
)
{
mthGen
.
addFallbackMethodCode
(
code
);
}
else
{
mthGen
.
addInstructions
(
code
);
}
mthGen
.
addInstructions
(
code
);
code
.
decIndent
();
code
.
startLine
(
'}'
);
}
...
...
@@ -357,37 +343,41 @@ public class ClassGen {
private
void
addFields
(
CodeWriter
code
)
throws
CodegenException
{
addEnumFields
(
code
);
for
(
FieldNode
f
:
cls
.
getFields
())
{
if
(
f
.
contains
(
AFlag
.
DONT_GENERATE
))
{
continue
;
}
CodeGenUtils
.
addComments
(
code
,
f
);
annotationGen
.
addForField
(
code
,
f
);
addField
(
code
,
f
);
}
}
if
(
f
.
getFieldInfo
().
isRenamed
())
{
code
.
newLine
();
CodeGenUtils
.
addRenamedComment
(
code
,
f
,
f
.
getName
());
}
code
.
startLine
(
f
.
getAccessFlags
().
makeString
());
useType
(
code
,
f
.
getType
());
code
.
add
(
' '
);
code
.
attachDefinition
(
f
);
code
.
add
(
f
.
getAlias
());
FieldInitAttr
fv
=
f
.
get
(
AType
.
FIELD_INIT
);
if
(
fv
!=
null
)
{
code
.
add
(
" = "
);
if
(
fv
.
getValue
()
==
null
)
{
code
.
add
(
TypeGen
.
literalToString
(
0
,
f
.
getType
(),
cls
,
fallback
));
}
else
{
if
(
fv
.
getValueType
()
==
InitType
.
CONST
)
{
annotationGen
.
encodeValue
(
code
,
fv
.
getValue
());
}
else
if
(
fv
.
getValueType
()
==
InitType
.
INSN
)
{
InsnGen
insnGen
=
makeInsnGen
(
fv
.
getInsnMth
());
addInsnBody
(
insnGen
,
code
,
fv
.
getInsn
());
}
public
void
addField
(
CodeWriter
code
,
FieldNode
f
)
{
if
(
f
.
contains
(
AFlag
.
DONT_GENERATE
))
{
return
;
}
CodeGenUtils
.
addComments
(
code
,
f
);
annotationGen
.
addForField
(
code
,
f
);
if
(
f
.
getFieldInfo
().
isRenamed
())
{
code
.
newLine
();
CodeGenUtils
.
addRenamedComment
(
code
,
f
,
f
.
getName
());
}
code
.
startLine
(
f
.
getAccessFlags
().
makeString
());
useType
(
code
,
f
.
getType
());
code
.
add
(
' '
);
code
.
attachDefinition
(
f
);
code
.
add
(
f
.
getAlias
());
FieldInitAttr
fv
=
f
.
get
(
AType
.
FIELD_INIT
);
if
(
fv
!=
null
)
{
code
.
add
(
" = "
);
if
(
fv
.
getValue
()
==
null
)
{
code
.
add
(
TypeGen
.
literalToString
(
0
,
f
.
getType
(),
cls
,
fallback
));
}
else
{
if
(
fv
.
getValueType
()
==
InitType
.
CONST
)
{
annotationGen
.
encodeValue
(
code
,
fv
.
getValue
());
}
else
if
(
fv
.
getValueType
()
==
InitType
.
INSN
)
{
InsnGen
insnGen
=
makeInsnGen
(
fv
.
getInsnMth
());
addInsnBody
(
insnGen
,
code
,
fv
.
getInsn
());
}
}
code
.
add
(
';'
);
}
code
.
add
(
';'
);
}
private
boolean
isFieldsPresents
()
{
...
...
@@ -569,7 +559,7 @@ public class ClassGen {
}
}
p
rivate
Set
<
ClassInfo
>
getImports
()
{
p
ublic
Set
<
ClassInfo
>
getImports
()
{
if
(
parentGen
!=
null
)
{
return
parentGen
.
getImports
();
}
else
{
...
...
@@ -615,13 +605,6 @@ public class ClassGen {
return
searchCollision
(
dex
,
useCls
.
getParentClass
(),
searchCls
);
}
private
void
insertSourceFileInfo
(
CodeWriter
code
,
AttrNode
node
)
{
SourceFileAttr
sourceFileAttr
=
node
.
get
(
AType
.
SOURCE_FILE
);
if
(
sourceFileAttr
!=
null
)
{
code
.
startLine
(
"/* compiled from: "
).
add
(
sourceFileAttr
.
getFileName
()).
add
(
" */"
);
}
}
private
void
insertRenameInfo
(
CodeWriter
code
,
ClassNode
cls
)
{
ClassInfo
classInfo
=
cls
.
getClassInfo
();
if
(
classInfo
.
hasAlias
())
{
...
...
jadx-core/src/main/java/jadx/core/codegen/CodeGen.java
View file @
ed385e8c
package
jadx
.
core
.
codegen
;
import
java.util.concurrent.Callable
;
import
jadx.api.JadxArgs
;
import
jadx.core.codegen.json.JsonCodeGen
;
import
jadx.core.dex.attributes.AFlag
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.utils.exceptions.CodegenException
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
public
class
CodeGen
{
public
static
void
generate
(
ClassNode
cls
)
throws
CodegenException
{
public
static
void
generate
(
ClassNode
cls
)
{
if
(
cls
.
contains
(
AFlag
.
DONT_GENERATE
))
{
cls
.
setCode
(
CodeWriter
.
EMPTY
);
}
else
{
ClassGen
clsGen
=
new
ClassGen
(
cls
,
cls
.
root
().
getArgs
());
CodeWriter
code
;
try
{
code
=
clsGen
.
makeClass
();
}
catch
(
Exception
e
)
{
if
(
cls
.
contains
(
AFlag
.
RESTART_CODEGEN
))
{
cls
.
remove
(
AFlag
.
RESTART_CODEGEN
);
code
=
clsGen
.
makeClass
();
}
else
{
throw
new
JadxRuntimeException
(
"Code generation error"
,
e
);
JadxArgs
args
=
cls
.
root
().
getArgs
();
switch
(
args
.
getOutputFormat
())
{
case
JAVA:
generateJavaCode
(
cls
,
args
);
break
;
case
JSON:
generateJson
(
cls
);
break
;
}
}
}
private
static
void
generateJavaCode
(
ClassNode
cls
,
JadxArgs
args
)
{
ClassGen
clsGen
=
new
ClassGen
(
cls
,
args
);
CodeWriter
code
=
wrapCodeGen
(
cls
,
clsGen:
:
makeClass
);
cls
.
setCode
(
code
);
}
private
static
void
generateJson
(
ClassNode
cls
)
{
JsonCodeGen
codeGen
=
new
JsonCodeGen
(
cls
);
String
clsJson
=
wrapCodeGen
(
cls
,
codeGen:
:
process
);
cls
.
setCode
(
new
CodeWriter
(
clsJson
));
}
private
static
<
R
>
R
wrapCodeGen
(
ClassNode
cls
,
Callable
<
R
>
codeGenFunc
)
{
try
{
return
codeGenFunc
.
call
();
}
catch
(
Exception
e
)
{
if
(
cls
.
contains
(
AFlag
.
RESTART_CODEGEN
))
{
cls
.
remove
(
AFlag
.
RESTART_CODEGEN
);
try
{
return
codeGenFunc
.
call
();
}
catch
(
Exception
ex
)
{
throw
new
JadxRuntimeException
(
"Code generation error after restart"
,
ex
);
}
}
else
{
throw
new
JadxRuntimeException
(
"Code generation error"
,
e
);
}
cls
.
setCode
(
code
);
}
}
...
...
jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java
View file @
ed385e8c
...
...
@@ -4,7 +4,6 @@ import java.io.File;
import
java.io.PrintWriter
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Iterator
;
import
java.util.Map
;
import
java.util.TreeMap
;
...
...
@@ -37,7 +36,7 @@ public class CodeWriter {
INDENT_STR
+
INDENT_STR
+
INDENT_STR
+
INDENT_STR
+
INDENT_STR
,
};
private
StringBuilder
buf
=
new
StringBuilder
()
;
private
StringBuilder
buf
;
@Nullable
private
String
code
;
private
String
indentStr
;
...
...
@@ -49,6 +48,7 @@ public class CodeWriter {
private
Map
<
Integer
,
Integer
>
lineMap
=
Collections
.
emptyMap
();
public
CodeWriter
()
{
this
.
buf
=
new
StringBuilder
();
this
.
indent
=
0
;
this
.
indentStr
=
""
;
if
(
ADD_LINE_NUMBERS
)
{
...
...
@@ -56,6 +56,12 @@ public class CodeWriter {
}
}
// create filled instance (just string wrapper)
public
CodeWriter
(
String
code
)
{
this
.
buf
=
null
;
this
.
code
=
code
;
}
public
CodeWriter
startLine
()
{
addLine
();
addLineIndent
();
...
...
@@ -225,6 +231,10 @@ public class CodeWriter {
attachAnnotation
(
obj
,
new
CodePosition
(
line
,
offset
+
1
));
}
public
void
attachLineAnnotation
(
Object
obj
)
{
attachAnnotation
(
obj
,
new
CodePosition
(
line
,
0
));
}
private
Object
attachAnnotation
(
Object
obj
,
CodePosition
pos
)
{
if
(
annotations
.
isEmpty
())
{
annotations
=
new
HashMap
<>();
...
...
@@ -260,16 +270,15 @@ public class CodeWriter {
code
=
buf
.
toString
();
buf
=
null
;
Iterator
<
Map
.
Entry
<
CodePosition
,
Object
>>
it
=
annotations
.
entrySet
().
iterator
();
while
(
it
.
hasNext
())
{
Map
.
Entry
<
CodePosition
,
Object
>
entry
=
it
.
next
();
annotations
.
entrySet
().
removeIf
(
entry
->
{
Object
v
=
entry
.
getValue
();
if
(
v
instanceof
DefinitionWrapper
)
{
LineAttrNode
l
=
((
DefinitionWrapper
)
v
).
getNode
();
l
.
setDecompiledLine
(
entry
.
getKey
().
getLine
());
it
.
remove
()
;
return
true
;
}
}
return
false
;
});
return
this
;
}
...
...
jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
View file @
ed385e8c
...
...
@@ -63,6 +63,7 @@ public class InsnGen {
protected
final
MethodNode
mth
;
protected
final
RootNode
root
;
protected
final
boolean
fallback
;
protected
final
boolean
attachInsns
;
protected
enum
Flags
{
BODY_ONLY
,
...
...
@@ -73,8 +74,9 @@ public class InsnGen {
public
InsnGen
(
MethodGen
mgen
,
boolean
fallback
)
{
this
.
mgen
=
mgen
;
this
.
mth
=
mgen
.
getMethodNode
();
this
.
root
=
mth
.
dex
().
root
();
this
.
root
=
mth
.
root
();
this
.
fallback
=
fallback
;
this
.
attachInsns
=
root
.
getArgs
().
isJsonOutput
();
}
private
boolean
isFallback
()
{
...
...
@@ -222,6 +224,9 @@ public class InsnGen {
}
else
{
if
(
flag
!=
Flags
.
INLINE
)
{
code
.
startLineWithNum
(
insn
.
getSourceLine
());
if
(
attachInsns
)
{
code
.
attachLineAnnotation
(
insn
);
}
}
if
(
insn
.
getResult
()
!=
null
)
{
SSAVar
var
=
insn
.
getResult
().
getSVar
();
...
...
jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
View file @
ed385e8c
...
...
@@ -85,9 +85,14 @@ public class MethodGen {
ai
=
ai
.
remove
(
AccessFlags
.
ACC_PUBLIC
);
}
if
(
mth
.
getMethodInfo
().
isRenamed
()
&&
!
ai
.
isConstructor
())
{
if
(
mth
.
getMethodInfo
().
hasAlias
()
&&
!
ai
.
isConstructor
())
{
CodeGenUtils
.
addRenamedComment
(
code
,
mth
,
mth
.
getName
());
}
CodeGenUtils
.
addSourceFileInfo
(
code
,
mth
);
if
(
mth
.
contains
(
AFlag
.
INCONSISTENT_CODE
))
{
code
.
startLine
(
"/* Code decompiled incorrectly, please refer to instructions dump. */"
);
}
code
.
startLineWithNum
(
mth
.
getSourceLine
());
code
.
add
(
ai
.
makeString
());
if
(
Consts
.
DEBUG
)
{
...
...
@@ -125,6 +130,15 @@ public class MethodGen {
code
.
add
(
')'
);
annotationGen
.
addThrows
(
mth
,
code
);
// add default value if in annotation class
if
(
mth
.
getParentClass
().
getAccessFlags
().
isAnnotation
())
{
Object
def
=
annotationGen
.
getAnnotationDefaultValue
(
mth
.
getName
());
if
(
def
!=
null
)
{
code
.
add
(
" default "
);
annotationGen
.
encodeValue
(
code
,
def
);
}
}
return
true
;
}
...
...
@@ -181,41 +195,49 @@ public class MethodGen {
}
public
void
addInstructions
(
CodeWriter
code
)
throws
CodegenException
{
if
(
mth
.
contains
(
AType
.
JADX_ERROR
)
||
mth
.
contains
(
AFlag
.
INCONSISTENT_CODE
)
||
mth
.
getRegion
()
==
null
)
{
code
.
startLine
(
"/*"
);
if
(
mth
.
root
().
getArgs
().
isFallbackMode
())
{
addFallbackMethodCode
(
code
);
code
.
startLine
(
"*/"
);
code
.
startLine
(
"throw new UnsupportedOperationException(\"Method not decompiled: "
)
.
add
(
mth
.
getParentClass
().
getClassInfo
().
getAliasFullName
())
.
add
(
'.'
)
.
add
(
mth
.
getAlias
())
.
add
(
'('
)
.
add
(
Utils
.
listToString
(
mth
.
getMethodInfo
().
getArgumentsTypes
()))
.
add
(
"):"
)
.
add
(
mth
.
getMethodInfo
().
getReturnType
().
toString
())
.
add
(
"\");"
);
}
else
if
(
classGen
.
isFallbackMode
())
{
dumpInstructions
(
code
);
}
else
{
try
{
RegionGen
regionGen
=
new
RegionGen
(
this
);
regionGen
.
makeRegion
(
code
,
mth
.
getRegion
());
}
catch
(
StackOverflowError
|
BootstrapMethodError
e
)
{
mth
.
addError
(
"Method code generation error"
,
new
JadxOverflowException
(
"StackOverflow"
));
classGen
.
insertDecompilationProblems
(
code
,
mth
);
addInstructions
(
code
);
}
catch
(
Exception
e
)
{
if
(
mth
.
getParentClass
().
getTopParentClass
().
contains
(
AFlag
.
RESTART_CODEGEN
))
{
throw
e
;
}
mth
.
addError
(
"Method code generation error"
,
e
);
classGen
.
insertDecompilationProblems
(
code
,
mth
);
addInstructions
(
code
);
addRegionInsns
(
code
);
}
}
public
void
addRegionInsns
(
CodeWriter
code
)
throws
CodegenException
{
try
{
RegionGen
regionGen
=
new
RegionGen
(
this
);
regionGen
.
makeRegion
(
code
,
mth
.
getRegion
());
}
catch
(
StackOverflowError
|
BootstrapMethodError
e
)
{
mth
.
addError
(
"Method code generation error"
,
new
JadxOverflowException
(
"StackOverflow"
));
classGen
.
insertDecompilationProblems
(
code
,
mth
);
dumpInstructions
(
code
);
}
catch
(
Exception
e
)
{
if
(
mth
.
getParentClass
().
getTopParentClass
().
contains
(
AFlag
.
RESTART_CODEGEN
))
{
throw
e
;
}
mth
.
addError
(
"Method code generation error"
,
e
);
classGen
.
insertDecompilationProblems
(
code
,
mth
);
dumpInstructions
(
code
);
}
}
public
void
dumpInstructions
(
CodeWriter
code
)
{
code
.
startLine
(
"/*"
);
addFallbackMethodCode
(
code
);
code
.
startLine
(
"*/"
);
code
.
startLine
(
"throw new UnsupportedOperationException(\"Method not decompiled: "
)
.
add
(
mth
.
getParentClass
().
getClassInfo
().
getAliasFullName
())
.
add
(
'.'
)
.
add
(
mth
.
getAlias
())
.
add
(
'('
)
.
add
(
Utils
.
listToString
(
mth
.
getMethodInfo
().
getArgumentsTypes
()))
.
add
(
"):"
)
.
add
(
mth
.
getMethodInfo
().
getReturnType
().
toString
())
.
add
(
"\");"
);
}
public
void
addFallbackMethodCode
(
CodeWriter
code
)
{
if
(
mth
.
getInstructions
()
==
null
)
{
// load original instructions
...
...
@@ -244,6 +266,7 @@ public class MethodGen {
public
static
void
addFallbackInsns
(
CodeWriter
code
,
MethodNode
mth
,
InsnNode
[]
insnArr
,
boolean
addLabels
)
{
InsnGen
insnGen
=
new
InsnGen
(
getFallbackMethodGen
(
mth
),
true
);
boolean
attachInsns
=
mth
.
root
().
getArgs
().
isJsonOutput
();
InsnNode
prevInsn
=
null
;
for
(
InsnNode
insn
:
insnArr
)
{
if
(
insn
==
null
)
{
...
...
@@ -259,6 +282,9 @@ public class MethodGen {
}
try
{
code
.
startLine
();
if
(
attachInsns
)
{
code
.
attachLineAnnotation
(
insn
);
}
RegisterArg
resArg
=
insn
.
getResult
();
if
(
resArg
!=
null
)
{
ArgType
varType
=
resArg
.
getInitType
();
...
...
@@ -304,7 +330,7 @@ public class MethodGen {
* Return fallback variant of method codegen
*/
public
static
MethodGen
getFallbackMethodGen
(
MethodNode
mth
)
{
ClassGen
clsGen
=
new
ClassGen
(
mth
.
getParentClass
(),
null
,
tru
e
,
true
,
true
);
ClassGen
clsGen
=
new
ClassGen
(
mth
.
getParentClass
(),
null
,
fals
e
,
true
,
true
);
return
new
MethodGen
(
clsGen
,
mth
);
}
...
...
jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
View file @
ed385e8c
...
...
@@ -121,6 +121,17 @@ public class RegionGen extends InsnGen {
}
else
{
code
.
attachSourceLine
(
region
.
getSourceLine
());
}
if
(
attachInsns
)
{
List
<
BlockNode
>
conditionBlocks
=
region
.
getConditionBlocks
();
if
(!
conditionBlocks
.
isEmpty
())
{
BlockNode
blockNode
=
conditionBlocks
.
get
(
0
);
InsnNode
lastInsn
=
BlockUtils
.
getLastInsn
(
blockNode
);
if
(
lastInsn
!=
null
)
{
code
.
attachLineAnnotation
(
lastInsn
);
}
}
}
code
.
add
(
"if ("
);
new
ConditionGen
(
this
).
add
(
code
,
region
.
getCondition
());
code
.
add
(
") {"
);
...
...
@@ -128,7 +139,7 @@ public class RegionGen extends InsnGen {
code
.
startLine
(
'}'
);
IContainer
els
=
region
.
getElseRegion
();
if
(
els
!=
null
&&
RegionUtils
.
notEmpty
(
els
))
{
if
(
RegionUtils
.
notEmpty
(
els
))
{
code
.
add
(
" else "
);
if
(
connectElseIf
(
code
,
els
))
{
return
;
...
...
jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
org.jetbrains.annotations.Nullable
;
import
com.google.gson.FieldNamingPolicy
;
import
com.google.gson.Gson
;
import
com.google.gson.GsonBuilder
;
import
jadx.api.CodePosition
;
import
jadx.api.JadxArgs
;
import
jadx.core.codegen.ClassGen
;
import
jadx.core.codegen.CodeWriter
;
import
jadx.core.codegen.MethodGen
;
import
jadx.core.codegen.json.cls.JsonClass
;
import
jadx.core.codegen.json.cls.JsonCodeLine
;
import
jadx.core.codegen.json.cls.JsonField
;
import
jadx.core.codegen.json.cls.JsonMethod
;
import
jadx.core.dex.attributes.AFlag
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.instructions.args.ArgType
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.FieldNode
;
import
jadx.core.dex.nodes.InsnNode
;
import
jadx.core.dex.nodes.MethodNode
;
import
jadx.core.dex.nodes.RootNode
;
import
jadx.core.utils.CodeGenUtils
;
import
jadx.core.utils.Utils
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
public
class
JsonCodeGen
{
private
static
final
Gson
GSON
=
new
GsonBuilder
()
.
setPrettyPrinting
()
.
setFieldNamingPolicy
(
FieldNamingPolicy
.
LOWER_CASE_WITH_DASHES
)
.
disableHtmlEscaping
()
.
create
();
private
final
ClassNode
cls
;
private
final
JadxArgs
args
;
private
final
RootNode
root
;
public
JsonCodeGen
(
ClassNode
cls
)
{
this
.
cls
=
cls
;
this
.
root
=
cls
.
root
();
this
.
args
=
root
.
getArgs
();
}
public
String
process
()
{
JsonClass
jsonCls
=
processCls
(
cls
,
null
);
return
GSON
.
toJson
(
jsonCls
);
}
private
JsonClass
processCls
(
ClassNode
cls
,
@Nullable
ClassGen
parentCodeGen
)
{
ClassGen
classGen
;
if
(
parentCodeGen
==
null
)
{
classGen
=
new
ClassGen
(
cls
,
args
);
}
else
{
classGen
=
new
ClassGen
(
cls
,
parentCodeGen
);
}
ClassInfo
classInfo
=
cls
.
getClassInfo
();
JsonClass
jsonCls
=
new
JsonClass
();
jsonCls
.
setPkg
(
classInfo
.
getAliasPkg
());
jsonCls
.
setDex
(
cls
.
dex
().
getDexFile
().
getName
());
jsonCls
.
setName
(
classInfo
.
getFullName
());
if
(
classInfo
.
hasAlias
())
{
jsonCls
.
setAlias
(
classInfo
.
getAliasFullName
());
}
jsonCls
.
setType
(
getClassTypeStr
(
cls
));
jsonCls
.
setAccessFlags
(
cls
.
getAccessFlags
().
rawValue
());
if
(!
Objects
.
equals
(
cls
.
getSuperClass
(),
ArgType
.
OBJECT
))
{
jsonCls
.
setSuperClass
(
getTypeAlias
(
cls
.
getSuperClass
()));
}
if
(!
cls
.
getInterfaces
().
isEmpty
())
{
jsonCls
.
setInterfaces
(
Utils
.
collectionMap
(
cls
.
getInterfaces
(),
this
::
getTypeAlias
));
}
CodeWriter
cw
=
new
CodeWriter
();
CodeGenUtils
.
addComments
(
cw
,
cls
);
classGen
.
insertDecompilationProblems
(
cw
,
cls
);
classGen
.
addClassDeclaration
(
cw
);
jsonCls
.
setDeclaration
(
cw
.
finish
().
toString
());
addFields
(
cls
,
jsonCls
,
classGen
);
addMethods
(
cls
,
jsonCls
,
classGen
);
addInnerClasses
(
cls
,
jsonCls
,
classGen
);
if
(!
cls
.
getClassInfo
().
isInner
())
{
List
<
String
>
imports
=
Utils
.
collectionMap
(
classGen
.
getImports
(),
ClassInfo:
:
getAliasFullName
);
Collections
.
sort
(
imports
);
jsonCls
.
setImports
(
imports
);
}
return
jsonCls
;
}
private
void
addInnerClasses
(
ClassNode
cls
,
JsonClass
jsonCls
,
ClassGen
classGen
)
{
List
<
ClassNode
>
innerClasses
=
cls
.
getInnerClasses
();
if
(
innerClasses
.
isEmpty
())
{
return
;
}
jsonCls
.
setInnerClasses
(
new
ArrayList
<>(
innerClasses
.
size
()));
for
(
ClassNode
innerCls
:
innerClasses
)
{
if
(
innerCls
.
contains
(
AFlag
.
DONT_GENERATE
))
{
continue
;
}
JsonClass
innerJsonCls
=
processCls
(
innerCls
,
classGen
);
jsonCls
.
getInnerClasses
().
add
(
innerJsonCls
);
}
}
private
void
addFields
(
ClassNode
cls
,
JsonClass
jsonCls
,
ClassGen
classGen
)
{
jsonCls
.
setFields
(
new
ArrayList
<>());
for
(
FieldNode
field
:
cls
.
getFields
())
{
if
(
field
.
contains
(
AFlag
.
DONT_GENERATE
))
{
continue
;
}
JsonField
jsonField
=
new
JsonField
();
jsonField
.
setName
(
field
.
getName
());
if
(
field
.
getFieldInfo
().
hasAlias
())
{
jsonField
.
setAlias
(
field
.
getAlias
());
}
CodeWriter
cw
=
new
CodeWriter
();
classGen
.
addField
(
cw
,
field
);
jsonField
.
setDeclaration
(
cw
.
finish
().
toString
());
jsonField
.
setAccessFlags
(
field
.
getAccessFlags
().
rawValue
());
jsonCls
.
getFields
().
add
(
jsonField
);
}
}
private
void
addMethods
(
ClassNode
cls
,
JsonClass
jsonCls
,
ClassGen
classGen
)
{
jsonCls
.
setMethods
(
new
ArrayList
<>());
for
(
MethodNode
mth
:
cls
.
getMethods
())
{
if
(
mth
.
contains
(
AFlag
.
DONT_GENERATE
))
{
continue
;
}
JsonMethod
jsonMth
=
new
JsonMethod
();
jsonMth
.
setName
(
mth
.
getName
());
if
(
mth
.
getMethodInfo
().
hasAlias
())
{
jsonMth
.
setAlias
(
mth
.
getAlias
());
}
jsonMth
.
setSignature
(
mth
.
getMethodInfo
().
getShortId
());
jsonMth
.
setReturnType
(
getTypeAlias
(
mth
.
getReturnType
()));
jsonMth
.
setArguments
(
Utils
.
collectionMap
(
mth
.
getMethodInfo
().
getArgumentsTypes
(),
this
::
getTypeAlias
));
MethodGen
mthGen
=
new
MethodGen
(
classGen
,
mth
);
CodeWriter
cw
=
new
CodeWriter
();
mthGen
.
addDefinition
(
cw
);
jsonMth
.
setDeclaration
(
cw
.
finish
().
toString
());
jsonMth
.
setAccessFlags
(
mth
.
getAccessFlags
().
rawValue
());
jsonMth
.
setLines
(
fillMthCode
(
mth
,
mthGen
));
jsonMth
.
setOffset
(
"0x"
+
Long
.
toHexString
(
mth
.
getMethodCodeOffset
()));
jsonCls
.
getMethods
().
add
(
jsonMth
);
}
}
private
List
<
JsonCodeLine
>
fillMthCode
(
MethodNode
mth
,
MethodGen
mthGen
)
{
if
(
mth
.
isNoCode
())
{
return
Collections
.
emptyList
();
}
CodeWriter
code
=
new
CodeWriter
();
try
{
mthGen
.
addInstructions
(
code
);
}
catch
(
Exception
e
)
{
throw
new
JadxRuntimeException
(
"Method generation error"
,
e
);
}
code
.
finish
();
String
codeStr
=
code
.
toString
();
if
(
codeStr
.
isEmpty
())
{
return
Collections
.
emptyList
();
}
String
[]
lines
=
codeStr
.
split
(
CodeWriter
.
NL
);
Map
<
Integer
,
Integer
>
lineMapping
=
code
.
getLineMapping
();
Map
<
CodePosition
,
Object
>
annotations
=
code
.
getAnnotations
();
long
mthCodeOffset
=
mth
.
getMethodCodeOffset
()
+
16
;
int
linesCount
=
lines
.
length
;
List
<
JsonCodeLine
>
codeLines
=
new
ArrayList
<>(
linesCount
);
for
(
int
i
=
0
;
i
<
linesCount
;
i
++)
{
String
codeLine
=
lines
[
i
];
int
line
=
i
+
2
;
JsonCodeLine
jsonCodeLine
=
new
JsonCodeLine
();
jsonCodeLine
.
setCode
(
codeLine
);
jsonCodeLine
.
setSourceLine
(
lineMapping
.
get
(
line
));
Object
obj
=
annotations
.
get
(
new
CodePosition
(
line
,
0
));
if
(
obj
instanceof
InsnNode
)
{
int
offset
=
((
InsnNode
)
obj
).
getOffset
();
jsonCodeLine
.
setOffset
(
"0x"
+
Long
.
toHexString
(
mthCodeOffset
+
offset
*
2
));
}
codeLines
.
add
(
jsonCodeLine
);
}
return
codeLines
;
}
private
String
getTypeAlias
(
ArgType
clsType
)
{
if
(
Objects
.
equals
(
clsType
,
ArgType
.
OBJECT
))
{
return
ArgType
.
OBJECT
.
getObject
();
}
if
(
clsType
.
isObject
())
{
ClassInfo
classInfo
=
ClassInfo
.
fromType
(
root
,
clsType
);
return
classInfo
.
getAliasFullName
();
}
return
clsType
.
toString
();
}
private
String
getClassTypeStr
(
ClassNode
cls
)
{
if
(
cls
.
isEnum
())
{
return
"enum"
;
}
if
(
cls
.
getAccessFlags
().
isInterface
())
{
return
"interface"
;
}
return
"class"
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/JsonMappingGen.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
;
import
java.io.File
;
import
java.io.FileWriter
;
import
java.io.Writer
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
com.google.gson.FieldNamingPolicy
;
import
com.google.gson.Gson
;
import
com.google.gson.GsonBuilder
;
import
jadx.api.JadxArgs
;
import
jadx.core.codegen.json.mapping.JsonClsMapping
;
import
jadx.core.codegen.json.mapping.JsonFieldMapping
;
import
jadx.core.codegen.json.mapping.JsonMapping
;
import
jadx.core.codegen.json.mapping.JsonMthMapping
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.info.MethodInfo
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.FieldNode
;
import
jadx.core.dex.nodes.MethodNode
;
import
jadx.core.dex.nodes.RootNode
;
import
jadx.core.utils.exceptions.JadxRuntimeException
;
import
jadx.core.utils.files.FileUtils
;
public
class
JsonMappingGen
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
JsonMappingGen
.
class
);
private
static
final
Gson
GSON
=
new
GsonBuilder
()
.
setPrettyPrinting
()
.
setFieldNamingPolicy
(
FieldNamingPolicy
.
LOWER_CASE_WITH_DASHES
)
.
disableHtmlEscaping
()
.
create
();
public
static
void
dump
(
RootNode
root
)
{
JsonMapping
mapping
=
new
JsonMapping
();
fillMapping
(
mapping
,
root
);
JadxArgs
args
=
root
.
getArgs
();
File
outDirSrc
=
args
.
getOutDirSrc
().
getAbsoluteFile
();
File
mappingFile
=
new
File
(
outDirSrc
,
"mapping.json"
);
FileUtils
.
makeDirsForFile
(
mappingFile
);
try
(
Writer
writer
=
new
FileWriter
(
mappingFile
))
{
GSON
.
toJson
(
mapping
,
writer
);
LOG
.
info
(
"Save mappings to {}"
,
mappingFile
.
getAbsolutePath
());
}
catch
(
Exception
e
)
{
throw
new
JadxRuntimeException
(
"Failed to save mapping json"
,
e
);
}
}
private
static
void
fillMapping
(
JsonMapping
mapping
,
RootNode
root
)
{
List
<
ClassNode
>
classes
=
root
.
getClasses
(
true
);
mapping
.
setClasses
(
new
ArrayList
<>(
classes
.
size
()));
for
(
ClassNode
cls
:
classes
)
{
ClassInfo
classInfo
=
cls
.
getClassInfo
();
JsonClsMapping
jsonCls
=
new
JsonClsMapping
();
jsonCls
.
setName
(
classInfo
.
getRawName
());
jsonCls
.
setAlias
(
classInfo
.
getAliasFullName
());
jsonCls
.
setInner
(
classInfo
.
isInner
());
jsonCls
.
setJson
(
cls
.
getTopParentClass
().
getClassInfo
().
getAliasFullPath
()
+
".json"
);
if
(
classInfo
.
isInner
())
{
jsonCls
.
setTopClass
(
cls
.
getTopParentClass
().
getClassInfo
().
getFullName
());
}
addFields
(
cls
,
jsonCls
);
addMethods
(
cls
,
jsonCls
);
mapping
.
getClasses
().
add
(
jsonCls
);
}
}
private
static
void
addMethods
(
ClassNode
cls
,
JsonClsMapping
jsonCls
)
{
List
<
MethodNode
>
methods
=
cls
.
getMethods
();
if
(
methods
.
isEmpty
())
{
return
;
}
jsonCls
.
setMethods
(
new
ArrayList
<>(
methods
.
size
()));
for
(
MethodNode
method
:
methods
)
{
JsonMthMapping
jsonMethod
=
new
JsonMthMapping
();
MethodInfo
methodInfo
=
method
.
getMethodInfo
();
jsonMethod
.
setSignature
(
methodInfo
.
getShortId
());
jsonMethod
.
setName
(
methodInfo
.
getName
());
jsonMethod
.
setAlias
(
methodInfo
.
getAlias
());
jsonMethod
.
setOffset
(
"0x"
+
Long
.
toHexString
(
method
.
getMethodCodeOffset
()));
jsonCls
.
getMethods
().
add
(
jsonMethod
);
}
}
private
static
void
addFields
(
ClassNode
cls
,
JsonClsMapping
jsonCls
)
{
List
<
FieldNode
>
fields
=
cls
.
getFields
();
if
(
fields
.
isEmpty
())
{
return
;
}
jsonCls
.
setFields
(
new
ArrayList
<>(
fields
.
size
()));
for
(
FieldNode
field
:
fields
)
{
JsonFieldMapping
jsonField
=
new
JsonFieldMapping
();
jsonField
.
setName
(
field
.
getName
());
jsonField
.
setAlias
(
field
.
getAlias
());
jsonCls
.
getFields
().
add
(
jsonField
);
}
}
private
JsonMappingGen
()
{
}
}
jadx-core/src/main/java/jadx/core/codegen/json/cls/JsonClass.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
cls
;
import
java.util.List
;
import
com.google.gson.annotations.SerializedName
;
public
class
JsonClass
extends
JsonNode
{
@SerializedName
(
"package"
)
private
String
pkg
;
private
String
type
;
// class, interface, enum
@SerializedName
(
"extends"
)
private
String
superClass
;
@SerializedName
(
"implements"
)
private
List
<
String
>
interfaces
;
private
String
dex
;
private
List
<
JsonField
>
fields
;
private
List
<
JsonMethod
>
methods
;
private
List
<
JsonClass
>
innerClasses
;
private
List
<
String
>
imports
;
public
String
getType
()
{
return
type
;
}
public
void
setType
(
String
type
)
{
this
.
type
=
type
;
}
public
String
getSuperClass
()
{
return
superClass
;
}
public
void
setSuperClass
(
String
superClass
)
{
this
.
superClass
=
superClass
;
}
public
List
<
String
>
getInterfaces
()
{
return
interfaces
;
}
public
void
setInterfaces
(
List
<
String
>
interfaces
)
{
this
.
interfaces
=
interfaces
;
}
public
List
<
JsonField
>
getFields
()
{
return
fields
;
}
public
void
setFields
(
List
<
JsonField
>
fields
)
{
this
.
fields
=
fields
;
}
public
List
<
JsonMethod
>
getMethods
()
{
return
methods
;
}
public
void
setMethods
(
List
<
JsonMethod
>
methods
)
{
this
.
methods
=
methods
;
}
public
List
<
JsonClass
>
getInnerClasses
()
{
return
innerClasses
;
}
public
void
setInnerClasses
(
List
<
JsonClass
>
innerClasses
)
{
this
.
innerClasses
=
innerClasses
;
}
public
String
getPkg
()
{
return
pkg
;
}
public
void
setPkg
(
String
pkg
)
{
this
.
pkg
=
pkg
;
}
public
String
getDex
()
{
return
dex
;
}
public
void
setDex
(
String
dex
)
{
this
.
dex
=
dex
;
}
public
List
<
String
>
getImports
()
{
return
imports
;
}
public
void
setImports
(
List
<
String
>
imports
)
{
this
.
imports
=
imports
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/cls/JsonCodeLine.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
cls
;
import
org.jetbrains.annotations.Nullable
;
public
class
JsonCodeLine
{
private
String
code
;
private
String
offset
;
private
Integer
sourceLine
;
public
String
getCode
()
{
return
code
;
}
public
void
setCode
(
String
code
)
{
this
.
code
=
code
;
}
public
String
getOffset
()
{
return
offset
;
}
public
void
setOffset
(
String
offset
)
{
this
.
offset
=
offset
;
}
public
Integer
getSourceLine
()
{
return
sourceLine
;
}
public
void
setSourceLine
(
@Nullable
Integer
sourceLine
)
{
this
.
sourceLine
=
sourceLine
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/cls/JsonField.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
cls
;
public
class
JsonField
extends
JsonNode
{
String
type
;
}
jadx-core/src/main/java/jadx/core/codegen/json/cls/JsonMethod.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
cls
;
import
java.util.List
;
public
class
JsonMethod
extends
JsonNode
{
private
String
signature
;
private
String
returnType
;
private
List
<
String
>
arguments
;
private
List
<
JsonCodeLine
>
lines
;
private
String
offset
;
public
String
getSignature
()
{
return
signature
;
}
public
void
setSignature
(
String
signature
)
{
this
.
signature
=
signature
;
}
public
String
getReturnType
()
{
return
returnType
;
}
public
void
setReturnType
(
String
returnType
)
{
this
.
returnType
=
returnType
;
}
public
List
<
String
>
getArguments
()
{
return
arguments
;
}
public
void
setArguments
(
List
<
String
>
arguments
)
{
this
.
arguments
=
arguments
;
}
public
List
<
JsonCodeLine
>
getLines
()
{
return
lines
;
}
public
void
setLines
(
List
<
JsonCodeLine
>
lines
)
{
this
.
lines
=
lines
;
}
public
String
getOffset
()
{
return
offset
;
}
public
void
setOffset
(
String
offset
)
{
this
.
offset
=
offset
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/cls/JsonNode.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
cls
;
public
class
JsonNode
{
private
String
name
;
private
String
alias
;
private
String
declaration
;
private
int
accessFlags
;
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
String
getAlias
()
{
return
alias
;
}
public
void
setAlias
(
String
alias
)
{
this
.
alias
=
alias
;
}
public
String
getDeclaration
()
{
return
declaration
;
}
public
void
setDeclaration
(
String
declaration
)
{
this
.
declaration
=
declaration
;
}
public
int
getAccessFlags
()
{
return
accessFlags
;
}
public
void
setAccessFlags
(
int
accessFlags
)
{
this
.
accessFlags
=
accessFlags
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/mapping/JsonClsMapping.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
mapping
;
import
java.util.List
;
public
class
JsonClsMapping
{
private
String
name
;
private
String
alias
;
private
String
json
;
private
boolean
inner
;
private
String
topClass
;
private
List
<
JsonFieldMapping
>
fields
;
private
List
<
JsonMthMapping
>
methods
;
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
String
getAlias
()
{
return
alias
;
}
public
void
setAlias
(
String
alias
)
{
this
.
alias
=
alias
;
}
public
String
getJson
()
{
return
json
;
}
public
void
setJson
(
String
json
)
{
this
.
json
=
json
;
}
public
boolean
isInner
()
{
return
inner
;
}
public
void
setInner
(
boolean
inner
)
{
this
.
inner
=
inner
;
}
public
String
getTopClass
()
{
return
topClass
;
}
public
void
setTopClass
(
String
topClass
)
{
this
.
topClass
=
topClass
;
}
public
List
<
JsonFieldMapping
>
getFields
()
{
return
fields
;
}
public
void
setFields
(
List
<
JsonFieldMapping
>
fields
)
{
this
.
fields
=
fields
;
}
public
List
<
JsonMthMapping
>
getMethods
()
{
return
methods
;
}
public
void
setMethods
(
List
<
JsonMthMapping
>
methods
)
{
this
.
methods
=
methods
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/mapping/JsonFieldMapping.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
mapping
;
public
class
JsonFieldMapping
{
private
String
name
;
private
String
alias
;
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
String
getAlias
()
{
return
alias
;
}
public
void
setAlias
(
String
alias
)
{
this
.
alias
=
alias
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/mapping/JsonMapping.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
mapping
;
import
java.util.List
;
public
class
JsonMapping
{
private
List
<
JsonClsMapping
>
classes
;
public
List
<
JsonClsMapping
>
getClasses
()
{
return
classes
;
}
public
void
setClasses
(
List
<
JsonClsMapping
>
classes
)
{
this
.
classes
=
classes
;
}
}
jadx-core/src/main/java/jadx/core/codegen/json/mapping/JsonMthMapping.java
0 → 100644
View file @
ed385e8c
package
jadx
.
core
.
codegen
.
json
.
mapping
;
public
class
JsonMthMapping
{
private
String
signature
;
private
String
name
;
private
String
alias
;
private
String
offset
;
public
String
getSignature
()
{
return
signature
;
}
public
void
setSignature
(
String
signature
)
{
this
.
signature
=
signature
;
}
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
String
getAlias
()
{
return
alias
;
}
public
void
setAlias
(
String
alias
)
{
this
.
alias
=
alias
;
}
public
String
getOffset
()
{
return
offset
;
}
public
void
setOffset
(
String
offset
)
{
this
.
offset
=
offset
;
}
}
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
View file @
ed385e8c
...
...
@@ -142,7 +142,7 @@ public class Deobfuscator {
}
for
(
MethodInfo
mth
:
o
.
getMethods
())
{
if
(
aliasToUse
==
null
)
{
if
(
mth
.
isRenamed
()
&&
!
mth
.
isAliasFromPreset
())
{
if
(
mth
.
hasAlias
()
&&
!
mth
.
isAliasFromPreset
())
{
mth
.
setAlias
(
String
.
format
(
"mo%d%s"
,
id
,
prepareNamePart
(
mth
.
getName
())));
}
aliasToUse
=
mth
.
getAlias
();
...
...
jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
View file @
ed385e8c
...
...
@@ -201,6 +201,10 @@ public class AccessInfo {
}
}
public
int
rawValue
()
{
return
accFlags
;
}
@Override
public
String
toString
()
{
return
"AccessInfo: "
+
type
+
" 0x"
+
Integer
.
toHexString
(
accFlags
)
+
" ("
+
rawString
()
+
')'
;
...
...
jadx-core/src/main/java/jadx/core/dex/info/FieldInfo.java
View file @
ed385e8c
package
jadx
.
core
.
dex
.
info
;
import
java.util.Objects
;
import
com.android.dex.FieldId
;
import
jadx.core.codegen.TypeGen
;
...
...
@@ -53,6 +55,10 @@ public final class FieldInfo {
this
.
alias
=
alias
;
}
public
boolean
hasAlias
()
{
return
!
Objects
.
equals
(
name
,
alias
);
}
public
String
getFullId
()
{
return
declClass
.
getFullName
()
+
'.'
+
name
+
':'
+
TypeGen
.
signature
(
type
);
}
...
...
jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java
View file @
ed385e8c
...
...
@@ -130,7 +130,7 @@ public final class MethodInfo {
this
.
alias
=
alias
;
}
public
boolean
isRenamed
()
{
public
boolean
hasAlias
()
{
return
!
name
.
equals
(
alias
);
}
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
View file @
ed385e8c
...
...
@@ -667,6 +667,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
return
mthInfo
;
}
public
long
getMethodCodeOffset
()
{
return
noCode
?
0
:
methodData
.
getCodeOffset
();
}
/**
* Stat method.
* Calculate instructions count as a measure of method size
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/FallbackModeVisitor.java
View file @
ed385e8c
package
jadx
.
core
.
dex
.
visitors
;
import
jadx.core.codegen.json.JsonMappingGen
;
import
jadx.core.dex.attributes.AType
;
import
jadx.core.dex.nodes.InsnNode
;
import
jadx.core.dex.nodes.MethodNode
;
import
jadx.core.dex.nodes.RootNode
;
import
jadx.core.dex.trycatch.CatchAttr
;
import
jadx.core.utils.exceptions.JadxException
;
public
class
FallbackModeVisitor
extends
AbstractVisitor
{
@Override
public
void
init
(
RootNode
root
)
{
if
(
root
.
getArgs
().
isJsonOutput
())
{
JsonMappingGen
.
dump
(
root
);
}
}
@Override
public
void
visit
(
MethodNode
mth
)
throws
JadxException
{
if
(
mth
.
isNoCode
())
{
return
;
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java
View file @
ed385e8c
...
...
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import
jadx.api.JadxArgs
;
import
jadx.core.Consts
;
import
jadx.core.codegen.json.JsonMappingGen
;
import
jadx.core.deobf.Deobfuscator
;
import
jadx.core.deobf.NameMapper
;
import
jadx.core.dex.attributes.AFlag
;
...
...
@@ -51,6 +52,9 @@ public class RenameVisitor extends AbstractVisitor {
deobfuscator
.
savePresets
();
deobfuscator
.
clear
();
}
if
(
args
.
isJsonOutput
())
{
JsonMappingGen
.
dump
(
root
);
}
}
private
static
void
checkClasses
(
Deobfuscator
deobfuscator
,
RootNode
root
,
JadxArgs
args
)
{
...
...
jadx-core/src/main/java/jadx/core/dex/visitors/SaveCode.java
View file @
ed385e8c
...
...
@@ -13,7 +13,7 @@ public class SaveCode {
private
SaveCode
()
{
}
public
static
void
save
(
File
dir
,
JadxArgs
args
,
ClassNode
cls
)
{
public
static
void
save
(
File
dir
,
ClassNode
cls
)
{
if
(
cls
.
contains
(
AFlag
.
DONT_GENERATE
))
{
return
;
}
...
...
@@ -21,10 +21,24 @@ public class SaveCode {
if
(
clsCode
==
null
)
{
throw
new
JadxRuntimeException
(
"Code not generated for class "
+
cls
.
getFullName
());
}
String
fileName
=
cls
.
getClassInfo
().
getAliasFullPath
()
+
".java"
;
if
(
args
.
isFallbackMode
())
{
fileName
+=
".jadx"
;
if
(
clsCode
==
CodeWriter
.
EMPTY
)
{
return
;
}
String
fileName
=
cls
.
getClassInfo
().
getAliasFullPath
()
+
getFileExtension
(
cls
);
clsCode
.
save
(
dir
,
fileName
);
}
private
static
String
getFileExtension
(
ClassNode
cls
)
{
JadxArgs
.
OutputFormatEnum
outputFormat
=
cls
.
root
().
getArgs
().
getOutputFormat
();
switch
(
outputFormat
)
{
case
JAVA:
return
".java"
;
case
JSON:
return
".json"
;
default
:
throw
new
JadxRuntimeException
(
"Unknown output format: "
+
outputFormat
);
}
}
}
jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java
View file @
ed385e8c
...
...
@@ -6,6 +6,7 @@ import jadx.core.codegen.CodeWriter;
import
jadx.core.dex.attributes.AType
;
import
jadx.core.dex.attributes.AttrNode
;
import
jadx.core.dex.attributes.nodes.RenameReasonAttr
;
import
jadx.core.dex.attributes.nodes.SourceFileAttr
;
public
class
CodeGenUtils
{
...
...
@@ -27,6 +28,13 @@ public class CodeGenUtils {
code
.
add
(
" */"
);
}
public
static
void
addSourceFileInfo
(
CodeWriter
code
,
AttrNode
node
)
{
SourceFileAttr
sourceFileAttr
=
node
.
get
(
AType
.
SOURCE_FILE
);
if
(
sourceFileAttr
!=
null
)
{
code
.
startLine
(
"/* compiled from: "
).
add
(
sourceFileAttr
.
getFileName
()).
add
(
" */"
);
}
}
private
CodeGenUtils
()
{
}
}
jadx-core/src/main/java/jadx/core/utils/ImmutableList.java
View file @
ed385e8c
...
...
@@ -210,11 +210,26 @@ public final class ImmutableList<E> implements List<E>, RandomAccess {
if
(
this
==
o
)
{
return
true
;
}
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
{
return
false
;
if
(
o
instanceof
ImmutableList
)
{
ImmutableList
<?>
other
=
(
ImmutableList
<?>)
o
;
return
Arrays
.
equals
(
arr
,
other
.
arr
);
}
ImmutableList
<?>
that
=
(
ImmutableList
<?>)
o
;
return
Arrays
.
equals
(
arr
,
that
.
arr
);
if
(
o
instanceof
List
)
{
List
<?>
other
=
(
List
<?>)
o
;
int
size
=
size
();
if
(
size
!=
other
.
size
())
{
return
false
;
}
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
E
e1
=
arr
[
i
];
Object
e2
=
other
.
get
(
i
);
if
(!
Objects
.
equals
(
e1
,
e2
))
{
return
false
;
}
}
return
true
;
}
return
false
;
}
@Override
...
...
jadx-core/src/main/java/jadx/core/utils/Utils.java
View file @
ed385e8c
...
...
@@ -3,7 +3,9 @@ package jadx.core.utils;
import
java.io.OutputStream
;
import
java.io.PrintWriter
;
import
java.io.StringWriter
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Iterator
;
...
...
@@ -154,6 +156,17 @@ public class Utils {
}
}
public
static
<
T
,
R
>
List
<
R
>
collectionMap
(
Collection
<
T
>
list
,
Function
<
T
,
R
>
mapFunc
)
{
if
(
list
==
null
||
list
.
isEmpty
())
{
return
Collections
.
emptyList
();
}
List
<
R
>
result
=
new
ArrayList
<>(
list
.
size
());
for
(
T
t
:
list
)
{
result
.
add
(
mapFunc
.
apply
(
t
));
}
return
result
;
}
public
static
<
T
>
List
<
T
>
lockList
(
List
<
T
>
list
)
{
if
(
list
.
isEmpty
())
{
return
Collections
.
emptyList
();
...
...
jadx-core/src/main/java/jadx/core/utils/files/InputFile.java
View file @
ed385e8c
...
...
@@ -97,7 +97,7 @@ public class InputFile {
}
private
void
addDexFile
(
Path
path
)
throws
IOException
{
addDexFile
(
""
,
path
);
addDexFile
(
path
.
getFileName
().
toString
()
,
path
);
}
private
void
addDexFile
(
String
fileName
,
Path
path
)
throws
IOException
{
...
...
jadx-core/src/test/java/jadx/tests/integration/others/TestJsonOutput.java
0 → 100644
View file @
ed385e8c
package
jadx
.
tests
.
integration
.
others
;
import
java.util.List
;
import
org.junit.jupiter.api.Test
;
import
jadx.api.JadxArgs
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.tests.api.IntegrationTest
;
import
static
jadx
.
tests
.
api
.
utils
.
JadxMatchers
.
containsOne
;
import
static
org
.
hamcrest
.
MatcherAssert
.
assertThat
;
import
static
org
.
hamcrest
.
Matchers
.
containsString
;
public
class
TestJsonOutput
extends
IntegrationTest
{
public
static
class
TestCls
{
private
final
String
prefix
=
"list: "
;
static
{
System
.
out
.
println
(
"test"
);
}
public
void
test
(
boolean
b
,
List
<
String
>
list
)
{
if
(
b
)
{
System
.
out
.
println
(
prefix
+
list
);
}
}
public
static
class
Inner
implements
Runnable
{
@Override
public
void
run
()
{
System
.
out
.
println
(
"run"
);
}
}
}
@Test
public
void
test
()
{
disableCompilation
();
args
.
setOutputFormat
(
JadxArgs
.
OutputFormatEnum
.
JSON
);
ClassNode
cls
=
getClassNode
(
TestCls
.
class
);
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsString
(
"\"offset\": \"0x"
));
assertThat
(
code
,
containsOne
(
"public static class Inner implements Runnable"
));
}
@Test
public
void
testFallback
()
{
disableCompilation
();
setFallback
();
args
.
setOutputFormat
(
JadxArgs
.
OutputFormatEnum
.
JSON
);
ClassNode
cls
=
getClassNode
(
TestCls
.
class
);
String
code
=
cls
.
getCode
().
toString
();
assertThat
(
code
,
containsString
(
"\"offset\": \"0x"
));
assertThat
(
code
,
containsOne
(
"public static class Inner implements java.lang.Runnable"
));
}
}
jadx-gui/build.gradle
View file @
ed385e8c
...
...
@@ -15,7 +15,6 @@ dependencies {
compile
(
project
(
":jadx-cli"
))
compile
'com.fifesoft:rsyntaxtextarea:3.0.2'
compile
'com.google.code.gson:gson:2.8.5'
compile
files
(
'libs/jfontchooser-1.0.5.jar'
)
compile
'hu.kazocsaba:image-viewer:1.2.3'
...
...
jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
View file @
ed385e8c
package
jadx
.
gui
.
settings
;
import
java.awt.Font
;
import
java.awt.GraphicsDevice
;
import
java.awt.GraphicsEnvironment
;
import
java.awt.Window
;
import
java.awt.*
;
import
java.nio.file.Path
;
import
java.nio.file.Paths
;
import
java.util.ArrayList
;
...
...
@@ -15,7 +12,7 @@ import java.util.Map;
import
java.util.Set
;
import
java.util.function.Consumer
;
import
javax.swing.
JFrame
;
import
javax.swing.
*
;
import
org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
;
import
org.jetbrains.annotations.Nullable
;
...
...
@@ -42,7 +39,9 @@ public class JadxSettings extends JadxCLIArgs {
private
static
final
Font
DEFAULT_FONT
=
new
RSyntaxTextArea
().
getFont
();
static
final
Set
<
String
>
SKIP_FIELDS
=
new
HashSet
<>(
Arrays
.
asList
(
"files"
,
"input"
,
"outDir"
,
"outDirSrc"
,
"outDirRes"
,
"verbose"
,
"printVersion"
,
"printHelp"
));
"files"
,
"input"
,
"outDir"
,
"outDirSrc"
,
"outDirRes"
,
"outputFormat"
,
"verbose"
,
"printVersion"
,
"printHelp"
));
private
Path
lastSaveProjectPath
=
USER_HOME
;
private
Path
lastOpenFilePath
=
USER_HOME
;
private
Path
lastSaveFilePath
=
USER_HOME
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment