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
e0ffb018
Commit
e0ffb018
authored
Jan 06, 2015
by
Skylot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
core: first implementation of '.arsc' parser
parent
53be92c6
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1020 additions
and
84 deletions
+1020
-84
ResourcesLoader.java
jadx-core/src/main/java/jadx/api/ResourcesLoader.java
+7
-2
BinaryXMLParser.java
...-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java
+3
-80
CommonBinaryParser.java
...re/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java
+62
-0
ParserConstants.java
...-core/src/main/java/jadx/core/xmlgen/ParserConstants.java
+190
-0
ParserStream.java
jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
+93
-1
ResTableParser.java
jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
+284
-0
ResourceStorage.java
...-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java
+43
-0
EntryConfig.java
...ore/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java
+38
-0
RawNamedValue.java
...e/src/main/java/jadx/core/xmlgen/entry/RawNamedValue.java
+19
-0
RawValue.java
jadx-core/src/main/java/jadx/core/xmlgen/entry/RawValue.java
+24
-0
ResourceEntry.java
...e/src/main/java/jadx/core/xmlgen/entry/ResourceEntry.java
+88
-0
ValuesParser.java
...re/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java
+168
-0
JResource.java
jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java
+1
-1
No files found.
jadx-core/src/main/java/jadx/api/ResourcesLoader.java
View file @
e0ffb018
...
...
@@ -2,7 +2,9 @@ package jadx.api;
import
jadx.api.ResourceFile.ZipRef
;
import
jadx.core.codegen.CodeWriter
;
import
jadx.core.utils.exceptions.JadxException
;
import
jadx.core.utils.files.InputFile
;
import
jadx.core.xmlgen.ResTableParser
;
import
java.io.BufferedInputStream
;
import
java.io.ByteArrayOutputStream
;
...
...
@@ -19,11 +21,11 @@ import org.slf4j.Logger;
import
org.slf4j.LoggerFactory
;
// TODO: move to core package
final
class
ResourcesLoader
{
public
final
class
ResourcesLoader
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
ResourcesLoader
.
class
);
private
static
final
int
READ_BUFFER_SIZE
=
8
*
1024
;
private
static
final
int
LOAD_SIZE_LIMIT
=
500
*
1024
;
private
static
final
int
LOAD_SIZE_LIMIT
=
10
*
1024
*
1024
;
private
JadxDecompiler
jadxRef
;
...
...
@@ -81,6 +83,9 @@ final class ResourcesLoader {
case
MANIFEST:
case
XML:
return
jadxRef
.
getXmlParser
().
parse
(
inputStream
);
case
ARSC:
return
new
ResTableParser
().
decodeToCodeWriter
(
inputStream
);
}
return
loadToCodeWriter
(
inputStream
);
}
...
...
jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java
View file @
e0ffb018
...
...
@@ -11,7 +11,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.lang.reflect.Field
;
import
java.nio.charset.Charset
;
import
java.util.HashMap
;
import
java.util.Map
;
...
...
@@ -29,37 +28,11 @@ import org.slf4j.LoggerFactory;
Check Element chunk size
*/
public
class
BinaryXMLParser
{
public
class
BinaryXMLParser
extends
CommonBinaryParser
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
BinaryXMLParser
.
class
);
private
static
final
Charset
STRING_CHARSET_UTF16
=
Charset
.
forName
(
"UTF-16LE"
);
private
static
final
Charset
STRING_CHARSET_UTF8
=
Charset
.
forName
(
"UTF-8"
);
private
static
final
int
RES_NULL_TYPE
=
0x0000
;
private
static
final
int
RES_STRING_POOL_TYPE
=
0x0001
;
private
static
final
int
RES_TABLE_TYPE
=
0x0002
;
private
static
final
int
RES_XML_TYPE
=
0x0003
;
private
static
final
int
RES_XML_FIRST_CHUNK_TYPE
=
0x0100
;
private
static
final
int
RES_XML_START_NAMESPACE_TYPE
=
0x0100
;
private
static
final
int
RES_XML_END_NAMESPACE_TYPE
=
0x0101
;
private
static
final
int
RES_XML_START_ELEMENT_TYPE
=
0x0102
;
private
static
final
int
RES_XML_END_ELEMENT_TYPE
=
0x0103
;
private
static
final
int
RES_XML_CDATA_TYPE
=
0x0104
;
private
static
final
int
RES_XML_LAST_CHUNK_TYPE
=
0x017f
;
private
static
final
int
RES_XML_RESOURCE_MAP_TYPE
=
0x0180
;
private
static
final
int
RES_TABLE_PACKAGE_TYPE
=
0x0200
;
private
static
final
int
RES_TABLE_TYPE_TYPE
=
0x0201
;
private
static
final
int
RES_TABLE_TYPE_SPEC_TYPE
=
0x0202
;
// string pool flags
private
static
final
int
SORTED_FLAG
=
1
;
private
static
final
int
UTF8_FLAG
=
1
<<
8
;
private
CodeWriter
writer
;
private
ParserStream
is
;
private
String
[]
strings
;
private
String
nsPrefix
=
"ERROR"
;
...
...
@@ -129,7 +102,7 @@ public class BinaryXMLParser {
// NullType is just doing nothing
break
;
case
RES_STRING_POOL_TYPE:
parseStringPool
();
strings
=
parseStringPoolNoType
();
break
;
case
RES_XML_RESOURCE_MAP_TYPE:
parseResourceMap
();
...
...
@@ -154,51 +127,6 @@ public class BinaryXMLParser {
}
}
private
void
parseStringPool
()
throws
IOException
{
if
(
is
.
readInt16
()
!=
0x001c
)
{
die
(
"Header header size not 28"
);
}
int
hsize
=
is
.
readInt32
();
int
stringCount
=
is
.
readInt32
();
int
styleCount
=
is
.
readInt32
();
int
flags
=
is
.
readInt32
();
int
stringsStart
=
is
.
readInt32
();
int
stylesStart
=
is
.
readInt32
();
// skip string offsets
is
.
skip
(
stringCount
*
4
);
strings
=
new
String
[
stringCount
];
if
((
flags
&
UTF8_FLAG
)
!=
0
)
{
// UTF-8
long
start
=
is
.
getPos
();
for
(
int
i
=
0
;
i
<
stringCount
;
i
++)
{
int
charsCount
=
is
.
decodeLength8
();
int
len
=
is
.
decodeLength8
();
strings
[
i
]
=
new
String
(
is
.
readArray
(
len
),
STRING_CHARSET_UTF8
);
int
zero
=
is
.
readInt8
();
if
(
zero
!=
0
)
{
die
(
"Not a trailing zero at string end: "
+
zero
+
", "
+
strings
[
i
]);
}
}
long
shift
=
is
.
getPos
()
-
start
;
if
(
shift
%
2
!=
0
)
{
is
.
skip
(
1
);
}
}
else
{
// UTF-16
for
(
int
i
=
0
;
i
<
stringCount
;
i
++)
{
int
len
=
is
.
decodeLength16
();
strings
[
i
]
=
new
String
(
is
.
readArray
(
len
*
2
),
STRING_CHARSET_UTF16
);
int
zero
=
is
.
readInt16
();
if
(
zero
!=
0
)
{
die
(
"Not a trailing zero at string end: "
+
zero
+
", "
+
strings
[
i
]);
}
}
}
if
(
styleCount
!=
0
)
{
die
(
"Styles parsing in string pool not yet implemented"
);
}
}
private
void
parseResourceMap
()
throws
IOException
{
if
(
is
.
readInt16
()
!=
0x8
)
{
die
(
"Header size of resmap is not 8!"
);
...
...
@@ -356,7 +284,7 @@ public class BinaryXMLParser {
break
;
default
:
writer
.
add
(
"UNKNOWN_DATA_TYPE_
"
+
attrValDataType
);
writer
.
add
(
"UNKNOWN_DATA_TYPE_
0x"
+
Integer
.
toHexString
(
attrValDataType
)
);
break
;
}
}
...
...
@@ -387,9 +315,4 @@ public class BinaryXMLParser {
writer
.
decIndent
();
}
}
private
void
die
(
String
message
)
{
throw
new
JadxRuntimeException
(
"Decode error: "
+
message
+
", position: 0x"
+
Long
.
toHexString
(
is
.
getPos
()));
}
}
jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
;
import
java.io.IOException
;
public
class
CommonBinaryParser
extends
ParserConstants
{
protected
ParserStream
is
;
protected
String
[]
parseStringPool
()
throws
IOException
{
is
.
checkInt16
(
RES_STRING_POOL_TYPE
,
"String pool expected"
);
return
parseStringPoolNoType
();
}
protected
String
[]
parseStringPoolNoType
()
throws
IOException
{
long
start
=
is
.
getPos
()
-
2
;
is
.
checkInt16
(
0x001c
,
"String pool header size not 0x001c"
);
long
size
=
is
.
readUInt32
();
int
stringCount
=
is
.
readInt32
();
int
styleCount
=
is
.
readInt32
();
int
flags
=
is
.
readInt32
();
long
stringsStart
=
is
.
readInt32
();
long
stylesStart
=
is
.
readInt32
();
int
[]
stringsOffset
=
is
.
readInt32Array
(
stringCount
);
int
[]
stylesOffset
=
is
.
readInt32Array
(
styleCount
);
is
.
checkPos
(
start
+
stringsStart
,
"Expected strings start"
);
String
[]
strings
=
new
String
[
stringCount
];
if
((
flags
&
UTF8_FLAG
)
!=
0
)
{
// UTF-8
for
(
int
i
=
0
;
i
<
stringCount
;
i
++)
{
// is.checkPos(start + stringsStart + stringsOffset[i], "Expected string start");
strings
[
i
]
=
is
.
readString8
();
}
}
else
{
// UTF-16
long
stringsStartOffset
=
start
+
stringsStart
;
for
(
int
i
=
0
;
i
<
stringCount
;
i
++)
{
// is.checkPos(stringsStartOffset + stringsOffset[i], "Expected string start");
// TODO: don't trust specified string length, read until \0
// TODO: stringsOffset can be same for different indexes
strings
[
i
]
=
is
.
readString16
();
}
}
if
(
stylesStart
!=
0
)
{
is
.
checkPos
(
start
+
stylesStart
,
"Expected styles start"
);
if
(
styleCount
!=
0
)
{
// TODO: implement styles parsing
}
}
// skip padding zeroes
is
.
skip
(
start
+
size
-
is
.
getPos
());
return
strings
;
}
protected
void
die
(
String
message
)
throws
IOException
{
throw
new
IOException
(
"Decode error: "
+
message
+
", position: 0x"
+
Long
.
toHexString
(
is
.
getPos
()));
}
}
jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
;
public
class
ParserConstants
{
/**
* Chunk types
*/
protected
static
final
int
RES_NULL_TYPE
=
0x0000
;
protected
static
final
int
RES_STRING_POOL_TYPE
=
0x0001
;
protected
static
final
int
RES_TABLE_TYPE
=
0x0002
;
protected
static
final
int
RES_XML_TYPE
=
0x0003
;
protected
static
final
int
RES_XML_FIRST_CHUNK_TYPE
=
0x0100
;
protected
static
final
int
RES_XML_START_NAMESPACE_TYPE
=
0x0100
;
protected
static
final
int
RES_XML_END_NAMESPACE_TYPE
=
0x0101
;
protected
static
final
int
RES_XML_START_ELEMENT_TYPE
=
0x0102
;
protected
static
final
int
RES_XML_END_ELEMENT_TYPE
=
0x0103
;
protected
static
final
int
RES_XML_CDATA_TYPE
=
0x0104
;
protected
static
final
int
RES_XML_LAST_CHUNK_TYPE
=
0x017f
;
protected
static
final
int
RES_XML_RESOURCE_MAP_TYPE
=
0x0180
;
protected
static
final
int
RES_TABLE_PACKAGE_TYPE
=
0x0200
;
protected
static
final
int
RES_TABLE_TYPE_TYPE
=
0x0201
;
protected
static
final
int
RES_TABLE_TYPE_SPEC_TYPE
=
0x0202
;
/**
* Type constants
*/
// Contains no data.
protected
static
final
int
TYPE_NULL
=
0x00
;
// The 'data' holds a ResTable_ref, a reference to another resource table entry.
protected
static
final
int
TYPE_REFERENCE
=
0x01
;
// The 'data' holds an attribute resource identifier.
protected
static
final
int
TYPE_ATTRIBUTE
=
0x02
;
// The 'data' holds an index into the containing resource table's global value string pool.
protected
static
final
int
TYPE_STRING
=
0x03
;
// The 'data' holds a single-precision floating point number.
protected
static
final
int
TYPE_FLOAT
=
0x04
;
// The 'data' holds a complex number encoding a dimension value, such as "100in".
protected
static
final
int
TYPE_DIMENSION
=
0x05
;
// The 'data' holds a complex number encoding a fraction of a container.
protected
static
final
int
TYPE_FRACTION
=
0x06
;
// Beginning of integer flavors...
protected
static
final
int
TYPE_FIRST_INT
=
0x10
;
// The 'data' is a raw integer value of the form n..n.
protected
static
final
int
TYPE_INT_DEC
=
0x10
;
// The 'data' is a raw integer value of the form 0xn..n.
protected
static
final
int
TYPE_INT_HEX
=
0x11
;
// The 'data' is either 0 or 1, for input "false" or "true" respectively.
protected
static
final
int
TYPE_INT_BOOLEAN
=
0x12
;
// Beginning of color integer flavors...
protected
static
final
int
TYPE_FIRST_COLOR_INT
=
0x1c
;
// The 'data' is a raw integer value of the form #aarrggbb.
protected
static
final
int
TYPE_INT_COLOR_ARGB8
=
0x1c
;
// The 'data' is a raw integer value of the form #rrggbb.
protected
static
final
int
TYPE_INT_COLOR_RGB8
=
0x1d
;
// The 'data' is a raw integer value of the form #argb.
protected
static
final
int
TYPE_INT_COLOR_ARGB4
=
0x1e
;
// The 'data' is a raw integer value of the form #rgb.
protected
static
final
int
TYPE_INT_COLOR_RGB4
=
0x1f
;
// ...end of integer flavors.
protected
static
final
int
TYPE_LAST_COLOR_INT
=
0x1f
;
// ...end of integer flavors.
protected
static
final
int
TYPE_LAST_INT
=
0x1f
;
// Where the unit type information is. This gives us 16 possible
// types, as defined below.
protected
static
final
int
COMPLEX_UNIT_SHIFT
=
0
;
protected
static
final
int
COMPLEX_UNIT_MASK
=
0xf
;
// TYPE_DIMENSION: Value is raw pixels.
protected
static
final
int
COMPLEX_UNIT_PX
=
0
;
// TYPE_DIMENSION: Value is Device Independent Pixels.
protected
static
final
int
COMPLEX_UNIT_DIP
=
1
;
// TYPE_DIMENSION: Value is a Scaled device independent Pixels.
protected
static
final
int
COMPLEX_UNIT_SP
=
2
;
// TYPE_DIMENSION: Value is in points.
protected
static
final
int
COMPLEX_UNIT_PT
=
3
;
// TYPE_DIMENSION: Value is in inches.
protected
static
final
int
COMPLEX_UNIT_IN
=
4
;
// TYPE_DIMENSION: Value is in millimeters.
protected
static
final
int
COMPLEX_UNIT_MM
=
5
;
// TYPE_FRACTION: A basic fraction of the overall size.
protected
static
final
int
COMPLEX_UNIT_FRACTION
=
0
;
// TYPE_FRACTION: A fraction of the parent size.
protected
static
final
int
COMPLEX_UNIT_FRACTION_PARENT
=
1
;
// Where the radix information is, telling where the decimal place
// appears in the mantissa. This give us 4 possible fixed point
// representations as defined below.
protected
static
final
int
COMPLEX_RADIX_SHIFT
=
4
;
protected
static
final
int
COMPLEX_RADIX_MASK
=
0x3
;
// The mantissa is an integral number -- i.e., 0xnnnnnn.0
protected
static
final
int
COMPLEX_RADIX_23p0
=
0
;
// The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
protected
static
final
int
COMPLEX_RADIX_16p7
=
1
;
// The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
protected
static
final
int
COMPLEX_RADIX_8p15
=
2
;
// The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
protected
static
final
int
COMPLEX_RADIX_0p23
=
3
;
// Where the actual value is. This gives us 23 bits of
// precision. The top bit is the sign.
protected
static
final
int
COMPLEX_MANTISSA_SHIFT
=
8
;
protected
static
final
int
COMPLEX_MANTISSA_MASK
=
0xffffff
;
protected
static
final
double
MANTISSA_MULT
=
1.0f
/
(
1
<<
COMPLEX_MANTISSA_SHIFT
);
protected
static
final
double
[]
RADIX_MULTS
=
new
double
[]{
1.0f
*
MANTISSA_MULT
,
1.0f
/
(
1
<<
7
)
*
MANTISSA_MULT
,
1.0f
/
(
1
<<
15
)
*
MANTISSA_MULT
,
1.0f
/
(
1
<<
23
)
*
MANTISSA_MULT
};
/**
* String pool flags
*/
protected
static
final
int
SORTED_FLAG
=
1
;
protected
static
final
int
UTF8_FLAG
=
1
<<
8
;
protected
static
final
int
NO_ENTRY
=
0xFFFFFFFF
;
/**
* ResTable_entry
*/
// If set, this is a complex entry, holding a set of name/value mappings.
// It is followed by an array of ResTable_map structures.
protected
static
final
int
FLAG_COMPLEX
=
0x0001
;
// If set, this resource has been declared public, so libraries are allowed to reference it.
protected
static
final
int
FLAG_PUBLIC
=
0x0002
;
/**
* ResTable_map
*/
protected
static
final
int
ATTR_TYPE
=
ResMakeInternal
(
0
);
// For integral attributes, this is the minimum value it can hold.
protected
static
final
int
ATTR_MIN
=
ResMakeInternal
(
1
);
// For integral attributes, this is the maximum value it can hold.
protected
static
final
int
ATTR_MAX
=
ResMakeInternal
(
2
);
// Localization of this resource is can be encouraged or required with an aapt flag if this is set
protected
static
final
int
ATTR_L10N
=
ResMakeInternal
(
3
);
// for plural support, see android.content.res.PluralRules#attrForQuantity(int)
protected
static
final
int
ATTR_OTHER
=
ResMakeInternal
(
4
);
protected
static
final
int
ATTR_ZERO
=
ResMakeInternal
(
5
);
protected
static
final
int
ATTR_ONE
=
ResMakeInternal
(
6
);
protected
static
final
int
ATTR_TWO
=
ResMakeInternal
(
7
);
protected
static
final
int
ATTR_FEW
=
ResMakeInternal
(
8
);
protected
static
final
int
ATTR_MANY
=
ResMakeInternal
(
9
);
private
static
int
ResMakeInternal
(
int
entry
)
{
return
0x01000000
|
(
entry
&
0xFFFF
);
}
protected
static
boolean
isResInternalId
(
int
resid
)
{
return
((
resid
&
0xFFFF0000
)
!=
0
&&
(
resid
&
0xFF0000
)
==
0
);
}
// Bit mask of allowed types, for use with ATTR_TYPE.
protected
static
final
int
ATTR_TYPE_ANY
=
0x0000FFFF
;
// Attribute holds a references to another resource.
protected
static
final
int
ATTR_TYPE_REFERENCE
=
1
;
// Attribute holds a generic string.
protected
static
final
int
ATTR_TYPE_STRING
=
1
<<
1
;
// Attribute holds an integer value. ATTR_MIN and ATTR_MIN can
// optionally specify a constrained range of possible integer values.
protected
static
final
int
ATTR_TYPE_INTEGER
=
1
<<
2
;
// Attribute holds a boolean integer.
protected
static
final
int
ATTR_TYPE_BOOLEAN
=
1
<<
3
;
// Attribute holds a color value.
protected
static
final
int
ATTR_TYPE_COLOR
=
1
<<
4
;
// Attribute holds a floating point value.
protected
static
final
int
ATTR_TYPE_FLOAT
=
1
<<
5
;
// Attribute holds a dimension value, such as "20px".
protected
static
final
int
ATTR_TYPE_DIMENSION
=
1
<<
6
;
// Attribute holds a fraction value, such as "20%".
protected
static
final
int
ATTR_TYPE_FRACTION
=
1
<<
7
;
// Attribute holds an enumeration. The enumeration values are
// supplied as additional entries in the map.
protected
static
final
int
ATTR_TYPE_ENUM
=
1
<<
16
;
// Attribute holds a bitmaks of flags. The flag bit values are
// supplied as additional entries in the map.
protected
static
final
int
ATTR_TYPE_FLAGS
=
1
<<
17
;
// Enum of localization modes, for use with ATTR_L10N
protected
static
final
int
ATTR_L10N_NOT_REQUIRED
=
0
;
protected
static
final
int
ATTR_L10N_SUGGESTED
=
1
;
}
jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
View file @
e0ffb018
...
...
@@ -2,9 +2,13 @@ package jadx.core.xmlgen;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.nio.charset.Charset
;
public
class
ParserStream
{
protected
static
final
Charset
STRING_CHARSET_UTF16
=
Charset
.
forName
(
"UTF-16LE"
);
protected
static
final
Charset
STRING_CHARSET_UTF8
=
Charset
.
forName
(
"UTF-8"
);
private
final
InputStream
input
;
private
long
readPos
=
0
;
...
...
@@ -38,7 +42,50 @@ public class ParserStream {
return
b4
<<
24
|
(
b3
&
0xFF
)
<<
16
|
(
b2
&
0xFF
)
<<
8
|
(
b1
&
0xFF
);
}
public
byte
[]
readArray
(
int
count
)
throws
IOException
{
public
long
readUInt32
()
throws
IOException
{
return
readInt32
()
&
0xFFFFFFFF
L
;
}
public
String
readString8Fixed
(
int
len
)
throws
IOException
{
String
str
=
new
String
(
readInt8Array
(
len
),
STRING_CHARSET_UTF8
);
return
str
.
trim
();
}
public
String
readString16Fixed
(
int
len
)
throws
IOException
{
String
str
=
new
String
(
readInt8Array
(
len
*
2
),
STRING_CHARSET_UTF16
);
return
str
.
trim
();
}
public
String
readString8
()
throws
IOException
{
decodeLength8
();
int
len
=
decodeLength8
();
String
str
=
new
String
(
readInt8Array
(
len
),
STRING_CHARSET_UTF8
);
checkInt8
(
0
,
"Not a trailing zero at string8 end"
);
return
str
;
}
public
String
readString16
()
throws
IOException
{
int
len
=
decodeLength16
();
String
str
=
new
String
(
readInt8Array
(
len
),
STRING_CHARSET_UTF16
);
checkInt16
(
0
,
"Not a trailing zero at string16 end"
);
return
str
;
}
public
int
[]
readInt32Array
(
int
count
)
throws
IOException
{
if
(
count
==
0
)
{
return
new
int
[
0
];
}
int
[]
arr
=
new
int
[
count
];
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
arr
[
i
]
=
readInt32
();
}
return
arr
;
}
public
byte
[]
readInt8Array
(
int
count
)
throws
IOException
{
if
(
count
==
0
)
{
return
new
byte
[
0
];
}
readPos
+=
count
;
byte
[]
arr
=
new
byte
[
count
];
int
pos
=
input
.
read
(
arr
,
0
,
count
);
...
...
@@ -64,6 +111,46 @@ public class ParserStream {
}
}
public
void
checkInt8
(
int
expected
,
String
error
)
throws
IOException
{
int
v
=
readInt8
();
if
(
v
!=
expected
)
{
throwException
(
error
,
expected
,
v
);
}
}
public
void
checkInt16
(
int
expected
,
String
error
)
throws
IOException
{
int
v
=
readInt16
();
if
(
v
!=
expected
)
{
throwException
(
error
,
expected
,
v
);
}
}
private
void
throwException
(
String
error
,
int
expected
,
int
actual
)
throws
IOException
{
throw
new
IOException
(
error
+
", expected: 0x"
+
Integer
.
toHexString
(
expected
)
+
", actual: 0x"
+
Integer
.
toHexString
(
actual
)
+
", offset: 0x"
+
Long
.
toHexString
(
getPos
()));
}
public
void
checkPos
(
long
expectedOffset
,
String
error
)
throws
IOException
{
if
(
getPos
()
!=
expectedOffset
)
{
throw
new
IOException
(
error
+
", expected offset: 0x"
+
Long
.
toHexString
(
expectedOffset
)
+
", actual: 0x"
+
Long
.
toHexString
(
getPos
()));
}
}
public
void
skipToPos
(
long
expectedOffset
,
String
error
)
throws
IOException
{
long
pos
=
getPos
();
if
(
pos
<
expectedOffset
)
{
skip
(
expectedOffset
-
pos
);
pos
=
getPos
();
}
if
(
pos
!=
expectedOffset
)
{
throw
new
IOException
(
error
+
", expected offset: 0x"
+
Long
.
toHexString
(
expectedOffset
)
+
", actual: 0x"
+
Long
.
toHexString
(
pos
));
}
}
public
int
decodeLength8
()
throws
IOException
{
int
len
=
readInt8
();
if
((
len
&
0x80
)
!=
0
)
{
...
...
@@ -79,4 +166,9 @@ public class ParserStream {
}
return
len
;
}
@Override
public
String
toString
()
{
return
"pos: 0x"
+
Long
.
toHexString
(
readPos
);
}
}
jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
;
import
jadx.core.codegen.CodeWriter
;
import
jadx.core.utils.Utils
;
import
jadx.core.xmlgen.entry.EntryConfig
;
import
jadx.core.xmlgen.entry.RawNamedValue
;
import
jadx.core.xmlgen.entry.RawValue
;
import
jadx.core.xmlgen.entry.ResourceEntry
;
import
jadx.core.xmlgen.entry.ValuesParser
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
public
class
ResTableParser
extends
CommonBinaryParser
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
ResTableParser
.
class
);
private
static
final
class
PackageChunk
{
private
final
int
id
;
private
final
String
name
;
private
final
String
[]
typeStrings
;
private
final
String
[]
keyStrings
;
private
PackageChunk
(
int
id
,
String
name
,
String
[]
typeStrings
,
String
[]
keyStrings
)
{
this
.
id
=
id
;
this
.
name
=
name
;
this
.
typeStrings
=
typeStrings
;
this
.
keyStrings
=
keyStrings
;
}
public
int
getId
()
{
return
id
;
}
public
String
getName
()
{
return
name
;
}
public
String
[]
getTypeStrings
()
{
return
typeStrings
;
}
public
String
[]
getKeyStrings
()
{
return
keyStrings
;
}
}
private
String
[]
strings
;
private
final
ResourceStorage
resStorage
=
new
ResourceStorage
();
public
void
decode
(
InputStream
inputStream
)
throws
IOException
{
is
=
new
ParserStream
(
inputStream
);
decodeTableChunk
();
resStorage
.
finish
();
}
public
CodeWriter
decodeToCodeWriter
(
InputStream
inputStream
)
{
try
{
decode
(
inputStream
);
}
catch
(
IOException
e
)
{
LOG
.
debug
(
"arsc decode failed"
,
e
);
CodeWriter
cw
=
new
CodeWriter
();
cw
.
add
(
"Error decode arsc"
);
cw
.
startLine
(
Utils
.
getStackTrace
(
e
));
return
cw
;
}
CodeWriter
writer
=
new
CodeWriter
();
ValuesParser
vp
=
new
ValuesParser
(
strings
,
resStorage
);
for
(
ResourceEntry
ri
:
resStorage
.
getResources
())
{
writer
.
startLine
(
ri
+
": "
+
vp
.
getValueString
(
ri
));
}
writer
.
finish
();
return
writer
;
}
public
ResourceStorage
getResStorage
()
{
return
resStorage
;
}
void
decodeTableChunk
()
throws
IOException
{
is
.
checkInt16
(
RES_TABLE_TYPE
,
"Not a table chunk"
);
is
.
checkInt16
(
0x000c
,
"Unexpected table header size"
);
/*int size = */
is
.
readInt32
();
int
pkgCount
=
is
.
readInt32
();
strings
=
parseStringPool
();
for
(
int
i
=
0
;
i
<
pkgCount
;
i
++)
{
parsePackage
();
}
}
private
PackageChunk
parsePackage
()
throws
IOException
{
long
start
=
is
.
getPos
();
is
.
checkInt16
(
RES_TABLE_PACKAGE_TYPE
,
"Not a table chunk"
);
int
headerSize
=
is
.
readInt16
();
if
(
headerSize
!=
0x011c
&&
headerSize
!=
0x0120
)
{
die
(
"Unexpected package header size"
);
}
long
size
=
is
.
readUInt32
();
long
endPos
=
start
+
size
;
int
id
=
is
.
readInt32
();
String
name
=
is
.
readString16Fixed
(
128
);
long
typeStringsOffset
=
start
+
is
.
readInt32
();
/* int lastPublicType = */
is
.
readInt32
();
long
keyStringsOffset
=
start
+
is
.
readInt32
();
/* int lastPublicKey = */
is
.
readInt32
();
if
(
headerSize
==
0x0120
)
{
/* int typeIdOffset = */
is
.
readInt32
();
}
String
[]
typeStrings
=
null
;
if
(
typeStringsOffset
!=
0
)
{
is
.
skipToPos
(
typeStringsOffset
,
"Expected typeStrings string pool"
);
typeStrings
=
parseStringPool
();
}
String
[]
keyStrings
=
null
;
if
(
keyStringsOffset
!=
0
)
{
is
.
skipToPos
(
keyStringsOffset
,
"Expected keyStrings string pool"
);
keyStrings
=
parseStringPool
();
}
PackageChunk
pkg
=
new
PackageChunk
(
id
,
name
,
typeStrings
,
keyStrings
);
while
(
is
.
getPos
()
<
endPos
)
{
long
chunkStart
=
is
.
getPos
();
int
type
=
is
.
readInt16
();
if
(
type
==
RES_NULL_TYPE
)
{
continue
;
}
if
(
type
==
RES_TABLE_TYPE_SPEC_TYPE
)
{
parseTypeSpecChunk
();
}
else
if
(
type
==
RES_TABLE_TYPE_TYPE
)
{
parseTypeChunk
(
chunkStart
,
pkg
);
}
}
return
pkg
;
}
private
void
parseTypeSpecChunk
()
throws
IOException
{
is
.
checkInt16
(
0x0010
,
"Unexpected type spec header size"
);
/*int size = */
is
.
readInt32
();
int
id
=
is
.
readInt8
();
is
.
skip
(
3
);
int
entryCount
=
is
.
readInt32
();
for
(
int
i
=
0
;
i
<
entryCount
;
i
++)
{
int
entryFlag
=
is
.
readInt32
();
}
}
private
void
parseTypeChunk
(
long
start
,
PackageChunk
pkg
)
throws
IOException
{
int
headerSize
=
is
.
readInt16
();
if
(
headerSize
!=
0x34
&&
headerSize
!=
0x38
&&
headerSize
!=
0x44
)
{
die
(
"Unexpected type header size: 0x"
+
Integer
.
toHexString
(
headerSize
));
}
/*int size =*/
is
.
readInt32
();
int
id
=
is
.
readInt8
();
is
.
checkInt8
(
0
,
"type chunk, res0"
);
is
.
checkInt16
(
0
,
"type chunk, res1"
);
int
entryCount
=
is
.
readInt32
();
long
entriesStart
=
start
+
is
.
readInt32
();
EntryConfig
config
=
parseConfig
();
int
[]
entryIndexes
=
new
int
[
entryCount
];
for
(
int
i
=
0
;
i
<
entryCount
;
i
++)
{
entryIndexes
[
i
]
=
is
.
readInt32
();
}
is
.
checkPos
(
entriesStart
,
"Expected entry start"
);
for
(
int
i
=
0
;
i
<
entryCount
;
i
++)
{
if
(
entryIndexes
[
i
]
!=
NO_ENTRY
)
{
parseEntry
(
pkg
,
id
,
i
,
config
);
}
}
}
private
void
parseEntry
(
PackageChunk
pkg
,
int
typeId
,
int
entryId
,
EntryConfig
config
)
throws
IOException
{
/* int size = */
is
.
readInt16
();
int
flags
=
is
.
readInt16
();
int
key
=
is
.
readInt32
();
int
resRef
=
pkg
.
getId
()
<<
24
|
typeId
<<
16
|
entryId
;
String
typeName
=
pkg
.
getTypeStrings
()[
typeId
-
1
];
String
keyName
=
pkg
.
getKeyStrings
()[
key
];
ResourceEntry
ri
=
new
ResourceEntry
(
resRef
,
pkg
.
getName
(),
typeName
,
keyName
);
ri
.
setConfig
(
config
);
if
((
flags
&
FLAG_COMPLEX
)
==
0
)
{
ri
.
setSimpleValue
(
parseValue
());
}
else
{
int
parentRef
=
is
.
readInt32
();
ri
.
setParentRef
(
parentRef
);
int
count
=
is
.
readInt32
();
List
<
RawNamedValue
>
values
=
new
ArrayList
<
RawNamedValue
>(
count
);
for
(
int
i
=
0
;
i
<
count
;
i
++)
{
values
.
add
(
parseValueMap
());
}
ri
.
setNamedValues
(
values
);
}
resStorage
.
add
(
ri
);
}
private
RawNamedValue
parseValueMap
()
throws
IOException
{
int
nameRef
=
is
.
readInt32
();
return
new
RawNamedValue
(
nameRef
,
parseValue
());
}
private
RawValue
parseValue
()
throws
IOException
{
is
.
checkInt16
(
8
,
"value size"
);
is
.
checkInt8
(
0
,
"value res0 not 0"
);
int
dataType
=
is
.
readInt8
();
int
data
=
is
.
readInt32
();
return
new
RawValue
(
dataType
,
data
);
}
private
EntryConfig
parseConfig
()
throws
IOException
{
long
start
=
is
.
getPos
();
int
size
=
is
.
readInt32
();
EntryConfig
config
=
new
EntryConfig
();
is
.
readInt16
();
//mcc
is
.
readInt16
();
//mnc
config
.
setLanguage
(
parseLocale
());
config
.
setCountry
(
parseLocale
());
int
orientation
=
is
.
readInt8
();
int
touchscreen
=
is
.
readInt8
();
int
density
=
is
.
readInt16
();
/*
is.readInt8(); // keyboard
is.readInt8(); // navigation
is.readInt8(); // inputFlags
is.readInt8(); // inputPad0
is.readInt16(); // screenWidth
is.readInt16(); // screenHeight
is.readInt16(); // sdkVersion
is.readInt16(); // minorVersion
is.readInt8(); // screenLayout
is.readInt8(); // uiMode
is.readInt16(); // smallestScreenWidthDp
is.readInt16(); // screenWidthDp
is.readInt16(); // screenHeightDp
*/
is
.
skipToPos
(
start
+
size
,
"Skip config parsing"
);
return
config
;
}
private
String
parseLocale
()
throws
IOException
{
int
b1
=
is
.
readInt8
();
int
b2
=
is
.
readInt8
();
String
str
=
null
;
if
(
b1
!=
0
&&
b2
!=
0
)
{
if
((
b1
&
0x80
)
==
0
)
{
str
=
new
String
(
new
char
[]{(
char
)
b1
,
(
char
)
b2
});
}
else
{
LOG
.
warn
(
"TODO: parse locale: 0x"
+
Integer
.
toHexString
(
b1
)
+
Integer
.
toHexString
(
b1
));
}
}
return
str
;
}
}
jadx-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
;
import
jadx.core.utils.Utils
;
import
jadx.core.xmlgen.entry.ResourceEntry
;
import
java.util.ArrayList
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.List
;
public
class
ResourceStorage
{
private
static
final
Comparator
<
ResourceEntry
>
COMPARATOR
=
new
Comparator
<
ResourceEntry
>()
{
@Override
public
int
compare
(
ResourceEntry
a
,
ResourceEntry
b
)
{
return
Utils
.
compare
(
a
.
getId
(),
b
.
getId
());
}
};
private
final
List
<
ResourceEntry
>
list
=
new
ArrayList
<
ResourceEntry
>();
public
Collection
<
ResourceEntry
>
getResources
()
{
return
list
;
}
public
void
add
(
ResourceEntry
ri
)
{
list
.
add
(
ri
);
}
public
void
finish
()
{
Collections
.
sort
(
list
,
COMPARATOR
);
}
public
ResourceEntry
getByRef
(
int
refId
)
{
ResourceEntry
key
=
new
ResourceEntry
(
refId
);
int
index
=
Collections
.
binarySearch
(
list
,
key
,
COMPARATOR
);
if
(
index
<
0
)
{
return
null
;
}
return
list
.
get
(
index
);
}
}
jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
.
entry
;
public
class
EntryConfig
{
private
String
language
;
private
String
country
;
public
void
setLanguage
(
String
language
)
{
this
.
language
=
language
;
}
public
String
getLanguage
()
{
return
language
;
}
public
void
setCountry
(
String
country
)
{
this
.
country
=
country
;
}
public
String
getCountry
()
{
return
country
;
}
@Override
public
String
toString
()
{
StringBuilder
sb
=
new
StringBuilder
();
if
(
language
!=
null
)
{
sb
.
append
(
language
);
}
if
(
country
!=
null
)
{
sb
.
append
(
"-r"
).
append
(
country
);
}
if
(
sb
.
length
()
!=
0
)
{
sb
.
insert
(
0
,
" ["
);
sb
.
append
(
']'
);
}
return
sb
.
toString
();
}
}
jadx-core/src/main/java/jadx/core/xmlgen/entry/RawNamedValue.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
.
entry
;
public
class
RawNamedValue
{
private
final
int
nameRef
;
private
final
RawValue
rawValue
;
public
RawNamedValue
(
int
nameRef
,
RawValue
rawValue
)
{
this
.
nameRef
=
nameRef
;
this
.
rawValue
=
rawValue
;
}
public
int
getNameRef
()
{
return
nameRef
;
}
public
RawValue
getRawValue
()
{
return
rawValue
;
}
}
jadx-core/src/main/java/jadx/core/xmlgen/entry/RawValue.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
.
entry
;
public
final
class
RawValue
{
private
final
int
dataType
;
private
final
int
data
;
public
RawValue
(
int
dataType
,
int
data
)
{
this
.
dataType
=
dataType
;
this
.
data
=
data
;
}
public
int
getDataType
()
{
return
dataType
;
}
public
int
getData
()
{
return
data
;
}
@Override
public
String
toString
()
{
return
"RawValue: type=0x"
+
Integer
.
toHexString
(
dataType
)
+
", value="
+
data
;
}
}
jadx-core/src/main/java/jadx/core/xmlgen/entry/ResourceEntry.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
.
entry
;
import
java.util.List
;
public
final
class
ResourceEntry
{
private
final
int
id
;
private
final
String
pkgName
;
private
final
String
typeName
;
private
final
String
keyName
;
private
int
parentRef
;
private
RawValue
simpleValue
;
private
List
<
RawNamedValue
>
namedValues
;
private
EntryConfig
config
;
public
ResourceEntry
(
int
id
,
String
pkgName
,
String
typeName
,
String
keyName
)
{
this
.
id
=
id
;
this
.
pkgName
=
pkgName
;
this
.
typeName
=
typeName
;
this
.
keyName
=
keyName
;
}
public
ResourceEntry
(
int
id
)
{
this
(
id
,
""
,
""
,
""
);
}
public
int
getId
()
{
return
id
;
}
public
String
getPkgName
()
{
return
pkgName
;
}
public
String
getTypeName
()
{
return
typeName
;
}
public
String
getKeyName
()
{
return
keyName
;
}
public
void
setParentRef
(
int
parentRef
)
{
this
.
parentRef
=
parentRef
;
}
public
int
getParentRef
()
{
return
parentRef
;
}
public
RawValue
getSimpleValue
()
{
return
simpleValue
;
}
public
void
setSimpleValue
(
RawValue
simpleValue
)
{
this
.
simpleValue
=
simpleValue
;
}
public
void
setNamedValues
(
List
<
RawNamedValue
>
namedValues
)
{
this
.
namedValues
=
namedValues
;
}
public
List
<
RawNamedValue
>
getNamedValues
()
{
return
namedValues
;
}
public
void
setConfig
(
EntryConfig
config
)
{
this
.
config
=
config
;
}
public
EntryConfig
getConfig
()
{
return
config
;
}
public
String
formatAsRef
()
{
return
"@"
+
typeName
+
"/"
+
keyName
;
}
public
String
formatAsAttribute
()
{
return
"?"
+
typeName
+
"/"
+
keyName
;
}
@Override
public
String
toString
()
{
return
" 0x"
+
Integer
.
toHexString
(
id
)
+
" ("
+
id
+
")"
+
config
+
" = "
+
typeName
+
"."
+
keyName
;
}
}
jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java
0 → 100644
View file @
e0ffb018
package
jadx
.
core
.
xmlgen
.
entry
;
import
jadx.core.xmlgen.ParserConstants
;
import
jadx.core.xmlgen.ResourceStorage
;
import
java.text.NumberFormat
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
public
class
ValuesParser
extends
ParserConstants
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
ValuesParser
.
class
);
private
final
String
[]
strings
;
private
final
ResourceStorage
resStorage
;
public
ValuesParser
(
String
[]
strings
,
ResourceStorage
resourceStorage
)
{
this
.
strings
=
strings
;
this
.
resStorage
=
resourceStorage
;
}
public
String
getValueString
(
ResourceEntry
ri
)
{
RawValue
simpleValue
=
ri
.
getSimpleValue
();
if
(
simpleValue
!=
null
)
{
return
decodeValue
(
simpleValue
);
}
List
<
RawNamedValue
>
namedValues
=
ri
.
getNamedValues
();
List
<
String
>
strList
=
new
ArrayList
<
String
>(
namedValues
.
size
());
for
(
RawNamedValue
value
:
namedValues
)
{
String
nameStr
=
decodeNameRef
(
value
.
getNameRef
());
String
valueStr
=
decodeValue
(
value
.
getRawValue
());
if
(
nameStr
==
null
)
{
strList
.
add
(
valueStr
);
}
else
{
strList
.
add
(
nameStr
+
"="
+
valueStr
);
}
}
return
strList
.
toString
();
}
public
String
decodeValue
(
RawValue
value
)
{
int
dataType
=
value
.
getDataType
();
int
data
=
value
.
getData
();
switch
(
dataType
)
{
case
TYPE_NULL:
return
null
;
case
TYPE_STRING:
return
strings
[
data
];
case
TYPE_INT_DEC:
return
Integer
.
toString
(
data
);
case
TYPE_INT_HEX:
return
Integer
.
toHexString
(
data
);
case
TYPE_INT_BOOLEAN:
return
data
==
0
?
"false"
:
"true"
;
case
TYPE_FLOAT:
return
Float
.
toString
(
Float
.
intBitsToFloat
(
data
));
case
TYPE_INT_COLOR_ARGB8:
return
String
.
format
(
"#%08x"
,
data
);
case
TYPE_INT_COLOR_RGB8:
return
String
.
format
(
"#%06x"
,
data
&
0xFFFFFF
);
case
TYPE_INT_COLOR_ARGB4:
return
String
.
format
(
"#%04x"
,
data
&
0xFFFF
);
case
TYPE_INT_COLOR_RGB4:
return
String
.
format
(
"#%03x"
,
data
&
0xFFF
);
case
TYPE_REFERENCE:
{
ResourceEntry
ri
=
resStorage
.
getByRef
(
data
);
if
(
ri
==
null
)
{
return
"?unknown_ref: "
+
Integer
.
toHexString
(
data
);
}
return
ri
.
formatAsRef
();
}
case
TYPE_ATTRIBUTE:
{
ResourceEntry
ri
=
resStorage
.
getByRef
(
data
);
if
(
ri
==
null
)
{
return
"?unknown_ref: "
+
Integer
.
toHexString
(
data
);
}
return
ri
.
formatAsAttribute
();
}
case
TYPE_DIMENSION:
return
decodeComplex
(
data
,
false
);
case
TYPE_FRACTION:
return
decodeComplex
(
data
,
true
);
default
:
LOG
.
warn
(
"Unknown data type: 0x"
+
Integer
.
toHexString
(
dataType
)
+
" "
+
data
);
return
" ?0x"
+
Integer
.
toHexString
(
dataType
)
+
" "
+
data
;
}
}
private
String
decodeNameRef
(
int
nameRef
)
{
int
ref
=
nameRef
;
if
(
isResInternalId
(
nameRef
))
{
ref
=
nameRef
&
ATTR_TYPE_ANY
;
if
(
ref
==
0
)
{
return
null
;
}
}
ResourceEntry
ri
=
resStorage
.
getByRef
(
ref
);
if
(
ri
!=
null
)
{
return
ri
.
getTypeName
()
+
"."
+
ri
.
getKeyName
();
}
return
"?0x"
+
Integer
.
toHexString
(
nameRef
);
}
private
String
decodeComplex
(
int
data
,
boolean
isFraction
)
{
double
value
=
(
data
&
(
COMPLEX_MANTISSA_MASK
<<
COMPLEX_MANTISSA_SHIFT
))
*
RADIX_MULTS
[(
data
>>
COMPLEX_RADIX_SHIFT
)
&
COMPLEX_RADIX_MASK
];
int
unitType
=
data
&
COMPLEX_UNIT_MASK
;
String
unit
;
if
(
isFraction
)
{
value
*=
100
;
switch
(
unitType
)
{
case
COMPLEX_UNIT_FRACTION:
unit
=
"%"
;
break
;
case
COMPLEX_UNIT_FRACTION_PARENT:
unit
=
"%p"
;
break
;
default
:
unit
=
"?f"
+
Integer
.
toHexString
(
unitType
);
}
}
else
{
switch
(
unitType
)
{
case
COMPLEX_UNIT_PX:
unit
=
"px"
;
break
;
case
COMPLEX_UNIT_DIP:
unit
=
"dp"
;
break
;
case
COMPLEX_UNIT_SP:
unit
=
"sp"
;
break
;
case
COMPLEX_UNIT_PT:
unit
=
"pt"
;
break
;
case
COMPLEX_UNIT_IN:
unit
=
"in"
;
break
;
case
COMPLEX_UNIT_MM:
unit
=
"mm"
;
break
;
default
:
unit
=
"?d"
+
Integer
.
toHexString
(
unitType
);
}
}
return
doubleToString
(
value
)
+
unit
;
}
private
static
String
doubleToString
(
double
value
)
{
if
(
value
==
Math
.
ceil
(
value
))
{
return
Integer
.
toString
((
int
)
value
);
}
else
{
// remove trailing zeroes
NumberFormat
f
=
NumberFormat
.
getInstance
();
f
.
setMaximumFractionDigits
(
4
);
f
.
setMinimumIntegerDigits
(
1
);
return
f
.
format
(
value
);
}
}
}
jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java
View file @
e0ffb018
...
...
@@ -144,7 +144,6 @@ public class JResource extends JNode implements Comparable<JResource> {
private
boolean
isSupportedForView
(
ResourceType
type
)
{
switch
(
type
)
{
case
CODE:
case
ARSC:
case
FONT:
case
IMG:
case
LIB:
...
...
@@ -152,6 +151,7 @@ public class JResource extends JNode implements Comparable<JResource> {
case
MANIFEST:
case
XML:
case
ARSC:
case
UNKNOWN:
return
true
;
}
...
...
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