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
99d831c4
Commit
99d831c4
authored
Mar 08, 2015
by
Skylot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
core: use source file information for deobfuscation, fix code style issues
parent
a532287d
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
151 additions
and
78 deletions
+151
-78
JadxCLIArgs.java
jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
+1
-1
ClassGen.java
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
+8
-0
Deobfuscator.java
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
+74
-50
NameMapper.java
jadx-core/src/main/java/jadx/core/deobf/NameMapper.java
+14
-0
PackageNode.java
jadx-core/src/main/java/jadx/core/deobf/PackageNode.java
+3
-24
ClassInfo.java
jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java
+8
-1
ClassNode.java
jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
+4
-2
TestNameMapper.groovy
jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy
+39
-0
No files found.
jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
View file @
99d831c4
...
...
@@ -57,7 +57,7 @@ public final class JadxCLIArgs implements IJadxArgs {
protected
int
deobfuscationMinLength
=
2
;
@Parameter
(
names
=
{
"--deobf-max"
},
description
=
"max length of name"
)
protected
int
deobfuscationMaxLength
=
40
;
protected
int
deobfuscationMaxLength
=
64
;
@Parameter
(
names
=
{
"--deobf-rewrite-cfg"
},
description
=
"force to save deobfuscation map"
)
protected
boolean
deobfuscationForceSave
=
false
;
...
...
jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
View file @
99d831c4
...
...
@@ -130,6 +130,7 @@ public class ClassGen {
annotationGen
.
addForClass
(
clsCode
);
insertSourceFileInfo
(
clsCode
,
cls
);
insertRenameInfo
(
clsCode
,
cls
);
clsCode
.
startLine
(
af
.
makeString
());
if
(
af
.
isInterface
())
{
if
(
af
.
isAnnotation
())
{
...
...
@@ -547,6 +548,13 @@ public class ClassGen {
}
}
private
void
insertRenameInfo
(
CodeWriter
code
,
ClassNode
cls
)
{
ClassInfo
classInfo
=
cls
.
getClassInfo
();
if
(
classInfo
.
isRenamed
())
{
code
.
startLine
(
"/* renamed from: "
).
add
(
classInfo
.
getFullName
()).
add
(
" */"
);
}
}
public
ClassGen
getParentGen
()
{
return
parentGen
==
null
?
this
:
parentGen
;
}
...
...
jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java
View file @
99d831c4
package
jadx
.
core
.
deobf
;
import
jadx.api.IJadxArgs
;
import
jadx.core.dex.attributes.AType
;
import
jadx.core.dex.attributes.nodes.SourceFileAttr
;
import
jadx.core.dex.info.ClassInfo
;
import
jadx.core.dex.nodes.ClassNode
;
import
jadx.core.dex.nodes.DexNode
;
...
...
@@ -16,6 +18,7 @@ import java.util.Set;
import
java.util.TreeSet
;
import
org.apache.commons.io.FileUtils
;
import
org.jetbrains.annotations.NotNull
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
...
...
@@ -31,17 +34,18 @@ public class Deobfuscator {
private
final
Map
<
ClassInfo
,
DeobfClsInfo
>
clsMap
=
new
HashMap
<
ClassInfo
,
DeobfClsInfo
>();
private
final
IJadxArgs
args
;
private
final
File
deobfMapFile
;
@NotNull
private
final
List
<
DexNode
>
dexNodes
;
private
int
maxLength
=
40
;
private
int
minLength
=
2
;
private
final
int
maxLength
;
private
final
int
minLength
;
private
int
pkgIndex
=
0
;
private
int
clsIndex
=
0
;
private
PackageNode
rootPackage
=
new
PackageNode
(
""
);
private
final
PackageNode
rootPackage
=
new
PackageNode
(
""
);
private
Map
<
String
,
String
>
preLoadClsMap
=
Collections
.
emptyMap
();
public
Deobfuscator
(
IJadxArgs
args
,
List
<
DexNode
>
dexNodes
,
File
deobfMapFile
)
{
public
Deobfuscator
(
IJadxArgs
args
,
@NotNull
List
<
DexNode
>
dexNodes
,
File
deobfMapFile
)
{
this
.
args
=
args
;
this
.
dexNodes
=
dexNodes
;
this
.
deobfMapFile
=
deobfMapFile
;
...
...
@@ -75,7 +79,7 @@ public class Deobfuscator {
}
}
p
ublic
void
process
()
{
p
rivate
void
process
()
{
preProcess
();
if
(
DEBUG
)
{
dumpAlias
();
...
...
@@ -87,7 +91,9 @@ public class Deobfuscator {
for
(
ClassNode
classNode
:
dexNode
.
getClasses
())
{
ClassInfo
clsInfo
=
classNode
.
getClassInfo
();
String
fullName
=
getClassFullName
(
clsInfo
);
clsInfo
.
rename
(
dexNode
,
fullName
);
if
(!
fullName
.
equals
(
clsInfo
.
getFullName
()))
{
clsInfo
.
rename
(
dexNode
,
fullName
);
}
}
}
}
...
...
@@ -99,7 +105,7 @@ public class Deobfuscator {
* @param create if {@code true} then will create all absent objects
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
*/
p
ublic
PackageNode
getPackageNode
(
String
fullPkgName
,
boolean
create
)
{
p
rivate
PackageNode
getPackageNode
(
String
fullPkgName
,
boolean
create
)
{
if
(
fullPkgName
.
isEmpty
()
||
fullPkgName
.
equals
(
classNameSeparator
))
{
return
rootPackage
;
}
...
...
@@ -118,23 +124,24 @@ public class Deobfuscator {
}
parentNode
=
result
;
result
=
result
.
getInnerPackageByName
(
pkgName
);
if
(
(
result
==
null
)
&&
(
create
)
)
{
if
(
result
==
null
&&
create
)
{
result
=
new
PackageNode
(
pkgName
);
parentNode
.
addInnerPackage
(
result
);
}
}
while
(!
fullPkgName
.
isEmpty
()
&&
(
result
!=
null
)
);
}
while
(!
fullPkgName
.
isEmpty
()
&&
result
!=
null
);
return
result
;
}
private
final
class
DeobfClsInfo
{
public
ClassNode
cls
;
public
PackageNode
pkg
;
public
String
alias
;
public
final
ClassNode
cls
;
public
final
PackageNode
pkg
;
public
final
String
alias
;
public
DeobfClsInfo
(
ClassNode
cls
,
PackageNode
pkg
)
{
public
DeobfClsInfo
(
ClassNode
cls
,
PackageNode
pkg
,
String
alias
)
{
this
.
cls
=
cls
;
this
.
pkg
=
pkg
;
this
.
alias
=
alias
;
}
public
String
makeNameWithoutPkg
()
{
...
...
@@ -152,7 +159,7 @@ public class Deobfuscator {
prefix
=
""
;
}
return
prefix
+
(
(
this
.
alias
!=
null
)
?
this
.
alias
:
this
.
cls
.
getShortName
());
return
prefix
+
(
this
.
alias
!=
null
?
this
.
alias
:
this
.
cls
.
getShortName
());
}
public
String
getFullName
()
{
...
...
@@ -160,7 +167,7 @@ public class Deobfuscator {
}
}
p
ublic
String
getNameWithoutPackage
(
ClassInfo
clsInfo
)
{
p
rivate
String
getNameWithoutPackage
(
ClassInfo
clsInfo
)
{
String
prefix
;
ClassInfo
parentClsInfo
=
clsInfo
.
getParentClass
();
if
(
parentClsInfo
!=
null
)
{
...
...
@@ -178,39 +185,52 @@ public class Deobfuscator {
}
private
void
doClass
(
ClassNode
cls
)
{
final
String
pkgFullName
=
cls
.
getClassInfo
().
getPackage
();
ClassInfo
classInfo
=
cls
.
getClassInfo
();
String
pkgFullName
=
classInfo
.
getPackage
();
PackageNode
pkg
=
getPackageNode
(
pkgFullName
,
true
);
doPkg
(
pkg
,
pkgFullName
);
if
(
preLoadClsMap
.
containsKey
(
cls
.
getClassInfo
().
getFullName
()))
{
DeobfClsInfo
clsInfo
=
new
DeobfClsInfo
(
cls
,
pkg
);
clsInfo
.
alias
=
preLoadClsMap
.
get
(
cls
.
getFullName
()
);
clsMap
.
put
(
cl
s
.
getClassInfo
(),
clsInfo
);
String
fullName
=
classInfo
.
getFullName
();
if
(
preLoadClsMap
.
containsKey
(
fullName
))
{
String
alias
=
preLoadClsMap
.
get
(
fullName
);
clsMap
.
put
(
cl
assInfo
,
new
DeobfClsInfo
(
cls
,
pkg
,
alias
)
);
return
;
}
if
(
clsMap
.
containsKey
(
cls
.
getClassInfo
()))
{
if
(
clsMap
.
containsKey
(
classInfo
))
{
return
;
}
if
(
shouldRename
(
classInfo
.
getShortName
()))
{
String
alias
=
makeClsAlias
(
cls
);
clsMap
.
put
(
classInfo
,
new
DeobfClsInfo
(
cls
,
pkg
,
alias
));
}
}
final
String
className
=
cls
.
getClassInfo
().
getShortName
();
if
(
shouldRename
(
className
))
{
DeobfClsInfo
clsInfo
=
new
DeobfClsInfo
(
cls
,
pkg
);
clsInfo
.
alias
=
String
.
format
(
"C%04d%s"
,
clsIndex
++,
short4LongName
(
className
));
clsMap
.
put
(
cls
.
getClassInfo
(),
clsInfo
);
private
String
makeClsAlias
(
ClassNode
cls
)
{
SourceFileAttr
sourceFileAttr
=
cls
.
get
(
AType
.
SOURCE_FILE
);
if
(
sourceFileAttr
!=
null
)
{
String
name
=
sourceFileAttr
.
getFileName
();
if
(
name
.
endsWith
(
".java"
))
{
name
=
name
.
substring
(
0
,
name
.
length
()
-
".java"
.
length
());
}
if
(
NameMapper
.
isValidIdentifier
(
name
)
&&
!
NameMapper
.
isReserved
(
name
))
{
// TODO: check if no class with this name exists or already renamed
cls
.
remove
(
AType
.
SOURCE_FILE
);
return
name
;
}
}
String
clsName
=
cls
.
getClassInfo
().
getShortName
();
return
String
.
format
(
"C%04d%s"
,
clsIndex
++,
short4LongName
(
clsName
));
}
private
String
short4LongName
(
String
name
)
{
if
(
name
.
length
()
>
maxLength
)
{
return
"x"
+
Integer
.
toHexString
(
name
.
hashCode
());
}
else
{
return
name
;
}
return
name
;
}
private
Set
<
String
>
pkgSet
=
new
TreeSet
<
String
>();
private
final
Set
<
String
>
pkgSet
=
new
TreeSet
<
String
>();
private
void
doPkg
(
PackageNode
pkg
,
String
fullName
)
{
if
(
pkgSet
.
contains
(
fullName
))
{
...
...
@@ -218,7 +238,7 @@ public class Deobfuscator {
}
pkgSet
.
add
(
fullName
);
// doPkg for all parent packages except root that not hasAli
sa
s
// doPkg for all parent packages except root that not hasAli
ase
s
PackageNode
parentPkg
=
pkg
.
getParentPackage
();
while
(!
parentPkg
.
getName
().
isEmpty
())
{
if
(!
parentPkg
.
hasAlias
())
{
...
...
@@ -235,11 +255,9 @@ public class Deobfuscator {
}
private
void
preProcess
()
{
if
(
dexNodes
!=
null
)
{
for
(
DexNode
dexNode
:
dexNodes
)
{
for
(
ClassNode
cls
:
dexNode
.
getClasses
())
{
doClass
(
cls
);
}
for
(
DexNode
dexNode
:
dexNodes
)
{
for
(
ClassNode
cls
:
dexNode
.
getClasses
())
{
doClass
(
cls
);
}
}
}
...
...
@@ -273,24 +291,21 @@ public class Deobfuscator {
*
* @throws IOException
*/
p
ublic
void
load
()
throws
IOException
{
p
rivate
void
load
()
throws
IOException
{
if
(!
deobfMapFile
.
exists
())
{
return
;
}
List
<
String
>
lines
=
FileUtils
.
readLines
(
deobfMapFile
,
MAP_FILE_CHARSET
);
for
(
String
l
:
lines
)
{
l
=
l
.
trim
();
if
(
l
.
startsWith
(
"p "
))
{
final
String
rule
=
l
.
substring
(
2
);
final
String
va
[]
=
rule
.
split
(
"="
);
String
va
[]
=
splitAndTrim
(
l
);
if
(
va
.
length
==
2
)
{
PackageNode
pkg
=
getPackageNode
(
va
[
0
],
true
);
pkg
.
setAlias
(
va
[
1
]);
}
}
else
if
(
l
.
startsWith
(
"c "
))
{
final
String
rule
=
l
.
substring
(
2
);
final
String
va
[]
=
rule
.
split
(
"="
);
String
va
[]
=
splitAndTrim
(
l
);
if
(
va
.
length
==
2
)
{
if
(
preLoadClsMap
.
isEmpty
())
{
preLoadClsMap
=
new
HashMap
<
String
,
String
>();
...
...
@@ -301,6 +316,14 @@ public class Deobfuscator {
}
}
private
static
String
[]
splitAndTrim
(
String
str
)
{
String
[]
v
=
str
.
substring
(
2
).
split
(
"="
);
for
(
int
i
=
0
;
i
<
v
.
length
;
i
++)
{
v
[
i
]
=
v
[
i
].
trim
();
}
return
v
;
}
private
static
void
dfsPackageName
(
List
<
String
>
list
,
String
prefix
,
PackageNode
node
)
{
for
(
PackageNode
pp
:
node
.
getInnerPackages
())
{
dfsPackageName
(
list
,
prefix
+
'.'
+
node
.
getName
(),
pp
);
...
...
@@ -313,7 +336,7 @@ public class Deobfuscator {
/**
* Saves DefaultDeobfuscator presets
*/
p
ublic
void
save
()
throws
IOException
{
p
rivate
void
save
()
throws
IOException
{
List
<
String
>
list
=
new
ArrayList
<
String
>();
// packages
for
(
PackageNode
p
:
rootPackage
.
getInnerPackages
())
{
...
...
@@ -327,7 +350,8 @@ public class Deobfuscator {
// classes
for
(
DeobfClsInfo
deobfClsInfo
:
clsMap
.
values
())
{
if
(
deobfClsInfo
.
alias
!=
null
)
{
list
.
add
(
String
.
format
(
"c %s=%s"
,
deobfClsInfo
.
cls
.
getFullName
(),
deobfClsInfo
.
alias
));
list
.
add
(
String
.
format
(
"c %s=%s"
,
deobfClsInfo
.
cls
.
getClassInfo
().
getFullName
(),
deobfClsInfo
.
alias
));
}
}
Collections
.
sort
(
list
);
...
...
@@ -335,7 +359,7 @@ public class Deobfuscator {
list
.
clear
();
}
p
ublic
String
getPackageName
(
String
packageName
)
{
p
rivate
String
getPackageName
(
String
packageName
)
{
final
PackageNode
pkg
=
getPackageNode
(
packageName
,
false
);
if
(
pkg
!=
null
)
{
return
pkg
.
getFullAlias
();
...
...
@@ -343,7 +367,7 @@ public class Deobfuscator {
return
packageName
;
}
p
ublic
String
getClassName
(
ClassInfo
clsInfo
)
{
p
rivate
String
getClassName
(
ClassInfo
clsInfo
)
{
final
DeobfClsInfo
deobfClsInfo
=
clsMap
.
get
(
clsInfo
);
if
(
deobfClsInfo
!=
null
)
{
return
deobfClsInfo
.
makeNameWithoutPkg
();
...
...
@@ -351,11 +375,11 @@ public class Deobfuscator {
return
getNameWithoutPackage
(
clsInfo
);
}
p
ublic
String
getClassFullName
(
ClassNode
cls
)
{
p
rivate
String
getClassFullName
(
ClassNode
cls
)
{
return
getClassFullName
(
cls
.
getClassInfo
());
}
p
ublic
String
getClassFullName
(
ClassInfo
clsInfo
)
{
p
rivate
String
getClassFullName
(
ClassInfo
clsInfo
)
{
final
DeobfClsInfo
deobfClsInfo
=
clsMap
.
get
(
clsInfo
);
if
(
deobfClsInfo
!=
null
)
{
return
deobfClsInfo
.
getFullName
();
...
...
jadx-core/src/main/java/jadx/core/deobf/NameMapper.java
View file @
99d831c4
...
...
@@ -3,9 +3,16 @@ package jadx.core.deobf;
import
java.util.Arrays
;
import
java.util.HashSet
;
import
java.util.Set
;
import
java.util.regex.Pattern
;
public
class
NameMapper
{
private
static
final
Pattern
VALID_JAVA_IDENTIFIER
=
Pattern
.
compile
(
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"
);
private
static
final
Pattern
VALID_JAVA_FULL_IDENTIFIER
=
Pattern
.
compile
(
"("
+
VALID_JAVA_IDENTIFIER
+
"\\.)*"
+
VALID_JAVA_IDENTIFIER
);
private
static
final
Set
<
String
>
RESERVED_NAMES
=
new
HashSet
<
String
>(
Arrays
.
asList
(
new
String
[]{
"abstract"
,
...
...
@@ -68,4 +75,11 @@ public class NameMapper {
return
RESERVED_NAMES
.
contains
(
str
);
}
public
static
boolean
isValidIdentifier
(
String
str
)
{
return
VALID_JAVA_IDENTIFIER
.
matcher
(
str
).
matches
();
}
public
static
boolean
isValidFullIdentifier
(
String
str
)
{
return
VALID_JAVA_FULL_IDENTIFIER
.
matcher
(
str
).
matches
();
}
}
jadx-core/src/main/java/jadx/core/deobf/PackageNode.java
View file @
99d831c4
package
jadx
.
core
.
deobf
;
import
jadx.core.dex.nodes.ClassNode
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
...
...
@@ -10,12 +8,11 @@ import java.util.Stack;
public
class
PackageNode
{
private
PackageNode
parentPackage
;
private
List
<
ClassNode
>
innerClasses
=
Collections
.
emptyList
();
private
List
<
PackageNode
>
innerPackages
=
Collections
.
emptyList
();
p
ublic
static
final
char
separatorChar
=
'.'
;
p
rivate
static
final
char
separatorChar
=
'.'
;
private
String
packageName
;
private
final
String
packageName
;
private
String
packageAlias
;
private
String
cachedPackageFullName
;
...
...
@@ -40,10 +37,8 @@ public class PackageNode {
result
.
append
(
separatorChar
);
result
.
append
(
pp
.
pop
().
getName
());
}
cachedPackageFullName
=
result
.
toString
();
}
return
cachedPackageFullName
;
}
...
...
@@ -51,7 +46,6 @@ public class PackageNode {
if
(
packageAlias
!=
null
)
{
return
packageAlias
;
}
return
packageName
;
}
...
...
@@ -60,7 +54,7 @@ public class PackageNode {
}
public
boolean
hasAlias
()
{
return
(
packageAlias
!=
null
)
;
return
packageAlias
!=
null
;
}
public
String
getFullAlias
()
{
...
...
@@ -72,10 +66,8 @@ public class PackageNode {
result
.
append
(
separatorChar
);
result
.
append
(
pp
.
pop
().
getAlias
());
}
cachedPackageFullAlias
=
result
.
toString
();
}
return
cachedPackageFullAlias
;
}
...
...
@@ -87,17 +79,6 @@ public class PackageNode {
return
innerPackages
;
}
public
List
<
ClassNode
>
getInnerClasses
()
{
return
innerClasses
;
}
public
void
addInnerClass
(
ClassNode
cls
)
{
if
(
innerClasses
.
isEmpty
())
{
innerClasses
=
new
ArrayList
<
ClassNode
>();
}
innerClasses
.
add
(
cls
);
}
public
void
addInnerPackage
(
PackageNode
pkg
)
{
if
(
innerPackages
.
isEmpty
())
{
innerPackages
=
new
ArrayList
<
PackageNode
>();
...
...
@@ -120,7 +101,6 @@ public class PackageNode {
break
;
}
}
return
result
;
}
...
...
@@ -140,7 +120,6 @@ public class PackageNode {
currentP
=
parentP
;
parentP
=
currentP
.
getParentPackage
();
}
return
pp
;
}
}
jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java
View file @
99d831c4
...
...
@@ -62,7 +62,14 @@ public final class ClassInfo {
}
public
void
rename
(
DexNode
dex
,
String
fullName
)
{
this
.
alias
=
new
ClassInfo
(
dex
,
ArgType
.
object
(
fullName
),
isInner
());
ClassInfo
newAlias
=
new
ClassInfo
(
dex
,
ArgType
.
object
(
fullName
),
isInner
());
if
(!
alias
.
getFullName
().
equals
(
newAlias
.
getFullName
()))
{
this
.
alias
=
newAlias
;
}
}
public
boolean
isRenamed
()
{
return
alias
!=
this
;
}
public
ClassInfo
getAlias
()
{
...
...
jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
View file @
99d831c4
...
...
@@ -112,8 +112,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int
sfIdx
=
cls
.
getSourceFileIndex
();
if
(
sfIdx
!=
DexNode
.
NO_INDEX
)
{
String
fileName
=
dex
.
getString
(
sfIdx
);
if
(!
clsInfo
.
getFullName
().
contains
(
fileName
.
replace
(
".java"
,
""
))
&&
!
fileName
.
equals
(
"SourceFile"
))
{
if
(
clsInfo
!=
null
&&
!
clsInfo
.
getFullName
().
contains
(
fileName
.
replace
(
".java"
,
""
))
&&
!
fileName
.
equals
(
"SourceFile"
)
&&
!
fileName
.
equals
(
"\""
))
{
this
.
addAttr
(
new
SourceFileAttr
(
fileName
));
LOG
.
debug
(
"Class '{}' compiled from '{}'"
,
this
,
fileName
);
}
...
...
jadx-core/src/test/groovy/jadx/tests/TestNameMapper.groovy
0 → 100644
View file @
99d831c4
package
jadx.tests
import
spock.lang.Specification
import
static
jadx
.
core
.
deobf
.
NameMapper
.
isValidFullIdentifier
class
TestNameMapper
extends
Specification
{
def
"test is Valid Full Identifier"
()
{
expect:
isValidFullIdentifier
(
valid
)
where:
valid
<<
[
'C'
,
'Cc'
,
'b.C'
,
'b.Cc'
,
'aAa.b.Cc'
,
'a.b.Cc'
,
'a.b.C_c'
,
'a.b.C$c'
,
'a.b.C9'
]
}
def
"test is not Valid Full Identifier"
()
{
expect:
!
isValidFullIdentifier
(
invalid
)
where:
invalid
<<
[
''
,
'5'
,
'7A'
,
'.C'
,
'b.9C'
,
'b..C'
,
]
}
}
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