//------------------------------------------------ //--- 010 Editor v3.1.3 Binary Template // // File: DEX.bt // Authors: Jon Larimer, Tim Strazzere // E-mail: jlarimer@gmail.com, strazz@gmail.com // Version: 2.1.1 // Purpose: A template for analyzing Dalvik VM // (Android) DEX files. // Category: Operating System // File Mask: *.dex // ID Bytes: 64 65 78 0A, 64 65 79 0A //dex\n,dey\n // History: // 2.1.1 2020-11-13 an: Updated to latest version. Adds colors, extra checks, many updates. // 1.2 2019-04-24 SweetScape Software: Fixed runtime error with FieldIdItemRead function. // 1.1 2016-02-11 SweetScape Software: Updated header for repository submission. // 1.0 2011-05-10 J Larimer: Initial version. // License: This file is released into the public domain. People may // use it for any purpose, commercial or otherwise. //-------------------------------------- // Version 2.1.1 (2015-10-17) // // FIXED: // - Annotation item's should note be optimized and have a correct // size of 4, not 12. Thanks jcase! // Version 2.1 (2015-9-17) // // FIXED: // - Check bounds on debug information offset found inside code_items. // // Version 2.0 (2015-1-22) // // ADDED: // - Check for data injected inside of dex after the map section. // This was seen ITW; checksum, signature and filesize from the // dex header where set properly to "include" this data. // // Version 1.9 (2015-1-06) // // ADDED: // - Prompt user to check source file oddities // // Version 1.8 (2014-2-11) // // FIXED: // - Small warning about type_item array not being created when the size // is zero. Not a large issue but creating unwanted info in the // output. // // Version 1.7 (2013-9-17) // // ADDED: // - Added Adler32 checksum parsing for odex contents. // - Added reading class descriptor inside odex sections. // FIXED: // - Odex parsing in general, offsets where not being properly // padded, now they are. // - File size attribute is not properly parsed and checked for // odex files. // - Fixed the Alder32 and SHA1 checks when file is odex, significant // speed increases in doing so. // - SHA1 should just not be run against Odex since it should always // fail. // // Version 1.6 (2013-9-13) // // FIXED: // - Warnings where not being output to the "output" box and only being // displayed in the status box, so I wrote a small wrapper hack to make // this data display in both places since more than one warning is likely // to occur at a time. This will prompt you at the end to check the output // box to see what actually went wrong, opposed to only see the last thing // that went wrong. (Normally you want to see the first thing that broke!) // // Version 1.5 (2013-9-12) // // FIXED: // - Added a warning and (red) color change to the following header fields; // - file size vs expected file size // - SHA1 from header vs actual SHA1 // - Alder32 checksum from header vs actual checksum // // Version 1.4 (2013-8-12) // // FIXED: // - Fixed linked section to only color if section is not null or zero // - Color the header_size to red if not expected 0x70 size, indicated // "big ego"/section stuffer attack // // Version 1.3 (2013-2-7) // // FIXED: // - Actually fixed the on-demand structs issue. Template appears to // run much faster (previous update introduced bugs) // - Properly configured the optimize=true|false flags for structs // // Version 1.2 (2012-12-17) // // FIXED: // - Attempting to convert some structs over to on-demand structs, // this should reduce some memory overhead according to 010Editor // docs. // // Version 1.1 (2012-10-08) // // FIXED: // - Same set of known issues below, though I've added some simple // coloring to aid with visual inspection of APKS. // - Linked section is called out in red // - If there is a larger than normal header, it will be colored red // and bundled into the "extra_padding" section of the header for // easier inspection // // NEW ISSUES // - Only color the linked section if it isn't null // // // Version 1.0 (2011-05-10) // // KNOWN ISSUES: // - display of floats and doubles in encoded_value is incorrect // - display of MUTF-8 strings may be incorrect // - doesn't handle big endian (byte swapped) files // - doesn't disassemble DEX instructions // - only decodes partial information from optimized (ODEX/DexOpt) files // - could use a bit of refactoring and removing duplicate code // - not decoding items in the map_list structure - they're already // referenced through other items in the header so adding them // in the map_list would be reduntant #define CHECK_ODDITIES false // set to true for extra checks LittleEndian(); #define NO_INDEX (0xFFFFFFFF) #define ENDIAN_CONSTANT (0x12345678) #define REVERSE_ENDIAN_CONSTANT (0x78563412); // offset used when seeking within a dex file inside of an odex wrapper local int odexpad = 0; // utility type to show the SHA1 hash in the value column typedef ubyte SHA1[20] ; local int warnings = 0; local string temp_warning; // A hack to get warning messages to both "Warn" (show in status) and output to the "output" window void PrintWarning(string message) { Warning(temp_warning); // Ensure new-line, "Warning" statuses should not have them SPrintf(temp_warning, "%s\n", message); Printf(temp_warning); // Hack to trigger a more generic "look at warnings in output" warnings++; } string SHA1Read(SHA1 sig) { string ret; string tmp; int i; for(i = 0; i<20; i++) { SPrintf(tmp, "%.2X", sig[i]); ret += tmp; } return ret; } // utility for reading/checking the magic value typedef struct { char dex[3]; char newline; char ver[3]; char zero; // XXX not checking the version, but it should be 035 if((Strcmp(dex, "dex") && Strcmp(dex, "dey")) || newline != '\n' || zero != 0) { PrintWarning("Invalid DEX file"); return -1; } } dex_magic ; string DexMagicRead(dex_magic &m) { string s; SPrintf(s, "%s %s", m.dex, m.ver); return s; } ////////////////////////////////////////////////// // LEB128 stuff ////////////////////////////////////////////////// // struct to read a uleb128 value. uleb128's are a variable-length encoding for // a 32 bit value. some of the uleb128/sleb128 code was adapted from dalvik's // libdex/Leb128.h typedef struct { ubyte val ; if(val > 0x7f) { ubyte val ; if (val > 0x7f) { ubyte val ; if(val > 0x7f) { ubyte val ; if(val > 0x7f) { ubyte val ; } } } } } uleb128 ; // get the actual uint value of the uleb128 uint uleb128_value(uleb128 &u) { local uint result; local ubyte cur; result = u.val[0]; if(result > 0x7f) { cur = u.val[1]; result = (result & 0x7f) | (uint)((cur & 0x7f) << 7); if(cur > 0x7f) { cur = u.val[2]; result |= (uint)(cur & 0x7f) << 14; if(cur > 0x7f) { cur = u.val[3]; result |= (uint)(cur & 0x7f) << 21; if(cur > 0x7f) { cur = u.val[4]; result |= (uint)cur << 28; } } } } return result; } typedef struct uleb128 uleb128p1; int uleb128p1_value(uleb128 &u) { return (int)uleb128_value(u) - 1; } string ULeb128Read(uleb128 &u) { local string s; s = SPrintf(s, "0x%X", uleb128_value(u)); return s; } // sleb128 typedef struct { ubyte val ; if(val > 0x7f) { ubyte val ; if (val > 0x7f) { ubyte val ; if(val > 0x7f) { ubyte val ; if(val > 0x7f) { ubyte val ; } } } } } sleb128 ; // get the actual uint value of the uleb128 int sleb128_value(sleb128 &u) { local int result; local ubyte cur; result = u.val[0]; if(result <= 0x7f) { result = (result << 25) >> 25; } else { cur = u.val[1]; result = (result & 0x7f) | ((uint)(cur & 0x7f) << 7); if(cur <= 0x7f) { result = (result << 18) >> 18; } else { cur = u.val[2]; result |= (uint)(cur & 0x7f) << 14; if(cur <= 0x7f) { result = (result << 11) >> 11; } else { cur = u.val[3]; result |= (uint)(cur & 0x7f) << 21; if(cur <= 0x7f) { result = (result << 4) >> 4; } else { cur = u.val[4]; result |= (uint)cur << 28; } } } } return result; } string SLeb128Read(sleb128 &u) { local string s; s = SPrintf(s, "%i", sleb128_value(u)); return s; } ////////////////////////////////////////////////// // encoded_value type ////////////////////////////////////////////////// typedef enum { VALUE_BYTE = 0x00, VALUE_SHORT = 0x02, VALUE_CHAR = 0x03, VALUE_INT = 0x04, VALUE_LONG = 0x06, VALUE_FLOAT = 0x10, VALUE_DOUBLE = 0x11, VALUE_STRING = 0x17, VALUE_TYPE = 0x18, VALUE_FIELD = 0x19, VALUE_METHOD = 0x1a, VALUE_ENUM = 0x1b, VALUE_ARRAY = 0x1c, VALUE_ANNOTATION = 0x1d, VALUE_NULL = 0x1e, VALUE_BOOLEAN = 0x1f } VALUE; // variable-width integer used by encoded_value types typedef struct (int size, VALUE type) { local int s = size + 1; local VALUE t = type; local int i; for(i=0; i; } } EncodedValue ; string EncodedValueRead(EncodedValue &v) { local string s = ""; switch(v.t) { case VALUE_BYTE: case VALUE_SHORT: case VALUE_INT: case VALUE_LONG: case VALUE_FLOAT: case VALUE_DOUBLE: SPrintf(s, "0x%X", EncodedValueValue(v)); break; case VALUE_STRING: s = StringIdRead(EncodedValueValue(v)); break; case VALUE_TYPE: s = LongTypeIdRead(EncodedValueValue(v)); break; case VALUE_FIELD: s = FieldIdRead(EncodedValueValue(v)); break; case VALUE_ENUM: s = FieldIdRead(EncodedValueValue(v)); break; case VALUE_ARRAY: case VALUE_ANNOTATION: case VALUE_BOOLEAN: case VALUE_NULL: s = "NULL"; break; } return s; } int64 EncodedValueValue(EncodedValue &v) { local int shift = 0; local int i; local int64 ret; if(v.s == 1) { return v.val; } for(i=0; i; ubyte value_arg:3 ; local string valstr = ""; local string typestr = ""; switch (value_type) { case VALUE_BYTE: ubyte value ; SPrintf(valstr, "0x%.2X", value); typestr = "byte"; break; case VALUE_SHORT: // value_arg has size-1, either 0 or 1 EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "%i", EncodedValueValue(value)); typestr = "short"; break; case VALUE_CHAR: EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "'%c'", EncodedValueValue(value)); typestr = "char"; break; case VALUE_INT: EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "%i", EncodedValueValue(value)); typestr = "int"; break; case VALUE_LONG: EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "%li", EncodedValueValue(value)); typestr = "long"; break; case VALUE_FLOAT: // XXX this doesn't work EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "%f", EncodedValueValue(value)); typestr = "float"; break; case VALUE_DOUBLE: EncodedValue value(value_arg, value_type) ; SPrintf(valstr, "%li", EncodedValueValue(value)); typestr = "double"; break; case VALUE_STRING: EncodedValue value(value_arg, value_type) ; valstr = "\"" + GetStringById(EncodedValueValue(value)) + "\""; typestr = "string"; break; case VALUE_TYPE: EncodedValue value(value_arg, value_type) ; valstr = GetLongTypeById(EncodedValueValue(value)); typestr = "type"; break; case VALUE_FIELD: EncodedValue value(value_arg, value_type) ; valstr = GetFieldById(EncodedValueValue(value)); typestr = "field"; break; case VALUE_METHOD: EncodedValue value(value_arg, value_type) ; valstr = GetMethodById(EncodedValueValue(value)); typestr = "method"; break; case VALUE_ENUM: EncodedValue value(value_arg, value_type) ; valstr = GetFieldById(EncodedValueValue(value)); typestr = "enum"; break; case VALUE_ARRAY: struct encoded_array array ; break; case VALUE_ANNOTATION: struct encoded_annotation annotation ; break; case VALUE_NULL: // no additional bytes used by null typestr = valstr = "NULL"; break; case VALUE_BOOLEAN: // no additional bytes used by boolean typestr = "boolean"; if(value_arg == 0) { valstr = "false"; } else { valstr = "true"; } break; default: SPrintf(temp_warning, "Unknown type for encoded value 0x%X", value_type); PrintWarning(temp_warning); break; } } encoded_value ; string EncodedValueStructRead(encoded_value &v) { return v.typestr + ": " + v.valstr; } typedef struct { uleb128 size ; encoded_value values[uleb128_value(size)] ; } encoded_array ; // show first 5 elements of the array string EncodedArrayRead(encoded_array &a) { local int count = 5; local int dots = 1; local int i; if(uleb128_value(a.size) < 5) { count = uleb128_value(a.size); dots = 0; } string val = "["; for(i=0; i; encoded_value value ; } annotation_element ; string AnnotationElementRead(annotation_element &e) { string name = GetStringById(uleb128_value(e.name_idx)); return name + " = " + e.value.valstr; } typedef struct { uleb128 type_idx ; uleb128 size ; if(uleb128_value(size) > 0) { annotation_element elements[uleb128_value(size)] ; } } encoded_annotation ; string EncodedAnnotationRead(encoded_annotation &a) { string s; SPrintf(s, "%i annotations for %s", uleb128_value(a.size), GetLongTypeById(uleb128_value(a.type_idx))); return s; } ////////////////////////////////////////////////// // dex file header ////////////////////////////////////////////////// typedef struct { dex_magic magic ; local uint file_start = 0; if(odex) { file_start = dexopt_header.dex_offset; } // Temp variable for expected file_length local uint temp_expected_file_size = ReadUInt(file_start + 32); // If the size is less than the actual file - lets just set it to be filesize and let other // things fail nicely than hit out of bound errors if(temp_expected_file_size > FileSize()) { temp_expected_file_size = FileSize(); } local uint temp_checksum = Checksum(CHECKSUM_ADLER32, file_start + 12, temp_expected_file_size - 12, -1, -1); if(temp_checksum != ReadUInt(FTell())) { SetBackColor(cLtRed); SPrintf(temp_warning, "Alder32 checksum did not match, expected %08X but got %08X", ReadUInt(FTell()), temp_checksum); PrintWarning(temp_warning); } uint checksum ; // Ensure we reset color to not bleed SetBackColor(cLtGreen); // Odex files kill the sha1, but the above checksum is changed if(!odex) { // Preemptively read the sha1 in for comparison local string temp_expected_sha1, temp_char; local int i; for(i = 0; i < 20; i++) { SPrintf(temp_char, "%.2X", ReadUByte(FTell()+i)); temp_expected_sha1 += temp_char; } local string temp_prepared_sha1 = ""; ChecksumAlgStr(CHECKSUM_SHA1, temp_prepared_sha1, file_start + 32, temp_expected_file_size - 32, "", -1, -1); if(temp_expected_sha1 != temp_prepared_sha1) { SetBackColor(cLtRed); SPrintf(temp_warning, "Sha1 checksum did not match, expected %s but got %s", temp_expected_sha1, temp_prepared_sha1); PrintWarning(temp_warning); } } SHA1 signature ; // Ensure we reset color to not bleed SetBackColor(cLtGreen); local uint temp_file_size = ReadUInt(FTell()); if(temp_file_size > FileSize()) { PrintWarning("File size appears be to smaller than expected - invalid dex file"); SetBackColor(cLtRed); } else if(odex && temp_file_size != dexopt_header.dex_length) { PrintWarning("File size does not match what odex header says it should be"); SetBackColor(cLtRed); } else if(!odex && temp_file_size < FileSize()) { // This could still technically be a valid dex file according to code paths in the verifier PrintWarning("File size appears be to larger than expected"); SetBackColor(cLtRed); } uint file_size ; // Ensure we reset color to not bleed SetBackColor(cLtGreen); if(ReadUInt(FTell()) != 0x70) { SetBackColor(cLtRed); } uint header_size ; if(header_size > 0x70) { PrintWarning("Header size appears be to larger than expected"); } // Ensure we reset color to not bleed SetBackColor(cLtGreen); uint endian_tag ; if(endian_tag != ENDIAN_CONSTANT) { // XXX we don't handle big endian files SPrintf(temp_warning, "Invalid endian_tag %.8X, should be %.8X", endian_tag, ENDIAN_CONSTANT); PrintWarning(temp_warning); } if(ReadUInt(FTell()) != 0 && ReadUInt(FTell() + 4) != 0) { SetBackColor(cLtRed); } uint link_size ; uint link_off ; if(link_size != 0 || link_off != 0) { PrintWarning("A link section appears to be set, this is not supported"); } // Ensure we reset color to not bleed SetBackColor(cLtGreen); uint map_off ; uint string_ids_size ; uint string_ids_off ; uint type_ids_size ; uint type_ids_off ; uint proto_ids_size ; uint proto_ids_off ; uint field_ids_size ; uint field_ids_off ; uint method_ids_size ; uint method_ids_off ; uint class_defs_size ; uint class_defs_off ; uint data_size ; uint data_off ; // Anything after the rest of this, but before the other sections is essentiall // just "padding", so lets create an array variable to contain it and color it // properly if(header_size > 0x70) { SetBackColor(cLtRed); char extra_padding[header_size - 0x70]; } } header_item ; int readHeaderItemSize(header_item &item) { return ReadUInt(startof(item)+36); } ////////////////////////////////////////////////// // odex file header ////////////////////////////////////////////////// // Keeping this enum, but it shouldn't ever happen that the flag // will be anything other than 0x0 or DEX_OPT_FLAG_BIG // https://android.googlesource.com/platform/dalvik.git/+/57fd399d1265ec627d28a15b3d4b98e5f239ac88%5E!/ typedef enum { DEX_FLAG_VERIFIED = 0x1, DEX_OPT_FLAG_BIG = 0x2, DEX_OPT_FLAG_FIELDS = 0x4, DEX_OPT_FLAG_INVOCATIONS = 0x8 } DEX_OPT_FLAGS; typedef struct { dex_magic magic ; uint dex_offset ; uint dex_length ; uint deps_offset ; uint deps_length ; uint opt_offset ; uint opt_length ; DEX_OPT_FLAGS flags ; // This is a potentially sloppy way of getting the size of the section to be checksum'ed, // though I haven't bothered to look up how the padding is done on the end of the // deps + opt section local uint temp_checksum = Checksum(CHECKSUM_ADLER32, deps_offset, FileSize() - deps_offset, -1, -1); if(temp_checksum != ReadUInt(FTell())) { SetBackColor(cLtRed); SPrintf(temp_warning, "Alder32 checksum for optimized dependancies did not match, expected %08X but got %08X", ReadUInt(FTell()), temp_checksum); PrintWarning(temp_warning); } uint checksum ; } dexopt_header_item ; string DexOptFlagsRead(DEX_OPT_FLAGS f) { string ret = ""; string flags = ""; DEX_OPT_FLAGS i = 1; while(i <= DEX_OPT_FLAG_INVOCATIONS) { if (f & i) { flags += EnumToString(i) + " "; } i = i << 1; } SPrintf(ret, "(0x%X) %s", f, flags); return ret; } ////////////////////////////////////////////////// // strings ////////////////////////////////////////////////// typedef struct { uleb128 utf16_size ; string data ; } string_item; typedef struct { uint string_data_off ; local int64 pos = FTell(); FSeek(odexpad + string_data_off); string_item string_data ; FSeek(pos); } string_id_item ; string StringDataReader(string_id_item &i) { return i.string_data.data; } typedef struct (int size) { local int s = size; string_id_item string_id[size] ; } string_id_list ; string StringIDListRead(string_id_list &l) { string s; s = SPrintf(s, "%d strings", l.s); return s; } ////////////////////////////////////////////////// // type IDs ////////////////////////////////////////////////// typedef struct { uint descriptor_idx ; } type_id_item ; string TypeIDRead(type_id_item &i) { return GetLongTypeDescriptor(GetStringById(i.descriptor_idx)); } typedef struct (int size) { local int s = size; type_id_item type_id[size] ; } type_id_list ; string TypeIDListRead(type_id_list &l) { string s; s = SPrintf(s, "%d types", l.s); return s; } ////////////////////////////////////////////////// // type list ////////////////////////////////////////////////// typedef struct { ushort type_idx ; } type_item ; typedef struct { uint size ; if(size != 0) { type_item list[size] ; } } type_item_list ; string TypeItemRead(type_item &t) { return GetTypeById(t.type_idx); } string TypeItemListRead(type_item_list &l) { string s = ""; string tmp; int i; for(i = 0; i < l.size; i++) { s += GetTypeById(l.list[i].type_idx); if(i+1 < l.size) { s += ", "; } } return s; } string GetLongTypeDescriptor(string descriptor) { local string desc = ""; local string post = ""; local int i = 0; local int len = Strlen(descriptor); // array descriptors while(descriptor[i] == '[') { post += "[]"; i++; if(i >= len) return "ERROR"; } if(descriptor[i] == 'L') { // fully qualified class descriptors i++; while(i < len) { if(descriptor[i] == '/') desc += "."; else if(descriptor[i] == ';') break; else desc += descriptor[i]; i++; } } else { // simple type descriptors switch(descriptor[i]) { case 'V': desc = "void"; break; case 'Z': desc = "boolean"; break; case 'B': desc = "byte"; break; case 'S': desc = "short"; break; case 'C': desc = "char"; break; case 'I': desc = "int"; break; case 'J': desc = "long"; break; case 'F': desc = "float"; break; case 'D': desc = "double"; break; } } return desc + post; } ////////////////////////////////////////////////// // protoypes ////////////////////////////////////////////////// typedef struct { uint shorty_idx ; uint return_type_idx ; uint parameters_off ; if(parameters_off != 0) { local int64 pos = FTell(); FSeek(odexpad + parameters_off); type_item_list parameters ; FSeek(pos); } } proto_id_item ; string ProtoIDItemRead(proto_id_item &i) { return GetPrototypeSignature(i); } typedef struct (int size) { local int s = size; proto_id_item proto_id[size] ; } proto_id_list ; string ProtoIDListRead(proto_id_list &l) { string s; s = SPrintf(s, "%d prototypes", l.s); return s; } string GetParameterListString(type_item_list &l) { local string s = "("; local string tmp; local int i; for(i = 0; i < l.size; i++) { s += GetLongTypeDescriptor(GetTypeById(l.list[i].type_idx)); if(i+1 < l.size) { s += ", "; } } return s + ")"; } string GetPrototypeSignature(proto_id_item &item) { string ret = GetLongTypeDescriptor(GetTypeById(item.return_type_idx)); string params = "()"; if(exists(item.parameters)) { params = GetParameterListString(item.parameters); } return ret + " " + params; } ////////////////////////////////////////////////// // fields ////////////////////////////////////////////////// typedef struct { ushort class_idx ; ushort type_idx ; uint name_idx ; } field_id_item ; string FieldIdItemRead(field_id_item &i) { local string type = GetLongTypeDescriptor(GetTypeById(i.type_idx)); local string class = GetLongTypeDescriptor(GetTypeById(i.class_idx)); local string name = GetStringById(i.name_idx); return type + " " + class + "." + name; } typedef struct (int size) { local int s = size; field_id_item field_id[size] ; } field_id_list ; string FieldIDListRead(field_id_list &l) { string s; s = SPrintf(s, "%d fields", l.s); return s; } ////////////////////////////////////////////////// // methods ////////////////////////////////////////////////// typedef struct { ushort class_idx ; ushort proto_idx ; uint name_idx ; } method_id_item ; string ProtoIdxRead(ushort p) { string s; SPrintf(s, "(0x%X) %s", p, GetPrototypeSignature(dex_proto_ids.proto_id[p])); return s; } string MethodIdItemRead(method_id_item &m) { local string retval = GetLongTypeDescriptor(GetTypeById(dex_proto_ids.proto_id[m.proto_idx].return_type_idx)); local string classname = GetLongTypeDescriptor(GetStringById(dex_type_ids.type_id[m.class_idx].descriptor_idx)); local string methodname = GetStringById(m.name_idx); local string params = "()"; if(exists(dex_proto_ids.proto_id[m.proto_idx].parameters)) { params = GetParameterListString(dex_proto_ids.proto_id[m.proto_idx].parameters); } return retval + " " + classname + "." + methodname + params; } typedef struct (int size) { local int s = size; method_id_item method_id[size] ; } method_id_list ; string MethodIDListRead(method_id_list &l) { string s; s = SPrintf(s, "%d methods", l.s); return s; } ////////////////////////////////////////////////// // annotations ////////////////////////////////////////////////// typedef struct { uint field_idx ; uint annotations_off; if(annotations_off != 0) { local int64 pos = FTell(); FSeek(odexpad + annotations_off); struct annotation_set_item field_annotations; FSeek(pos); } } field_annotation ; string FieldAnnotationRead(field_annotation &f) { return GetFieldById(f.field_idx); } typedef struct { uint method_idx ; uint annotations_off; if(annotations_off != 0) { local int64 pos = FTell(); FSeek(odexpad + annotations_off); struct annotation_set_item method_annotations; FSeek(pos); } } method_annotation ; string MethodAnnotationRead(method_annotation &m) { return GetMethodById(m.method_idx); } typedef struct { uint method_idx ; uint annotations_off; if(annotations_off != 0) { local int64 pos = FTell(); FSeek(odexpad + annotations_off); struct annotation_set_ref_list annotations_list; FSeek(pos); } } parameter_annotation ; string ParameterAnnotationRead(parameter_annotation &p) { return GetParameterListString(dex_proto_ids.proto_id[dex_method_ids.method_id[p.method_idx].proto_idx].parameters); } typedef enum { VISIBILITY_BUILD = 0x0, VISIBILITY_RUNTIME = 0x1, VISIBILITY_SYSTEM = 0x2 } VISIBILITY; typedef struct { VISIBILITY visibility ; encoded_annotation annotation ; } annotation_item ; string AnnotationItemRead(annotation_item &i) { return EncodedAnnotationRead(i.annotation); } typedef struct { uint annotation_off ; if(annotation_off != 0) { local int64 pos = FTell(); FSeek(odexpad + annotation_off); annotation_item item ; FSeek(pos); } } annotation_off_item ; string AnnotationOffItemRead(annotation_off_item &i) { if(exists(i.item)) { return AnnotationItemRead(i.item); } } typedef struct { uint size ; if(size > 0) { annotation_off_item entries[size] ; } } annotation_set_item ; string AnnotationSetItemRead(annotation_set_item &i) { local string s; SPrintf(s, "%i annotation entries", i.size); return s; } typedef struct { uint class_annotations_off ; if(class_annotations_off != 0) { local int64 pos = FTell(); FSeek(odexpad + class_annotations_off); annotation_set_item class_annotations ; FSeek(pos); } uint fields_size ; uint methods_size ; uint parameters_size ; if(fields_size > 0) { field_annotation field_annotations[fields_size] ; } if(methods_size > 0) { method_annotation method_annotations[methods_size] ; } if(parameters_size > 0) { parameter_annotation parameter_annotations[parameters_size] ; } } annotations_directory_item ; string AnnotationsDirectoryItemRead(annotations_directory_item &i) { local string s; local int classes = 0; if(exists(i.class_annotations)) { classes = i.class_annotations.size; } SPrintf(s, "%i class annotations, %i field annotations, %i method annotations, %i parameter annotations", classes, i.fields_size, i.methods_size, i.parameters_size); return s; } typedef struct { uint annotations_off ; if(annotations_off != 0) { local int64 pos = FTell(); FSeek(odexpad + annotations_off); struct annotation_set_item item ; FSeek(pos); } } annotation_set_ref_item ; typedef struct { uint size ; if(size > 0) { annotation_set_ref_item list[size] ; } } annotation_set_ref_list; ////////////////////////////////////////////////// // classes ////////////////////////////////////////////////// // access flags. some of these mean different things for different items (class/field/method) typedef enum { ACC_PUBLIC = 0x1, ACC_PRIVATE = 0x2, ACC_PROTECTED = 0x4, ACC_STATIC = 0x8, ACC_FINAL = 0x10, ACC_SYNCHRONIZED = 0x20, ACC_VOLATILE = 0x40, // field //ACC_BRIDGE = 0x40, // method ACC_TRANSIENT = 0x80, // field //ACC_VARARGS = 0x80, // method ACC_NATIVE = 0x100, ACC_INTERFACE = 0x200, ACC_ABSTRACT = 0x400, ACC_STRICT = 0x800, ACC_SYNTHETIC = 0x1000, ACC_ANNOTATION = 0x2000, ACC_ENUM = 0x4000, ACC_CONSTRUCTOR = 0x10000, ACC_DECLARED_SYNCHRONIZED = 0x20000 } ACCESS_FLAGS ; string AccessFlagsRead(ACCESS_FLAGS f) { string ret = ""; string flags = ""; ACCESS_FLAGS i = 1; while(i <= ACC_DECLARED_SYNCHRONIZED) { if (f & i) { flags += EnumToString(i) + " "; } i = i << 1; } SPrintf(ret, "(0x%X) %s", f, flags); return ret; } string AccessFlagsReadUleb(uleb128 &f) { return AccessFlagsRead(uleb128_value(f)); } typedef enum { AF_CLASS, AF_FIELD, AF_METHOD } AF_TYPE; string GetFriendlyAccessFlag(int flag, AF_TYPE type) { switch (flag) { case ACC_PUBLIC: return "public"; case ACC_PRIVATE: return "private"; case ACC_PROTECTED: return "protected"; case ACC_STATIC: return "static"; case ACC_FINAL: return "final"; case ACC_SYNCHRONIZED: return "synchronized"; case ACC_VOLATILE: if(type == AF_FIELD) return "volatile"; else return "bridge"; // 0x40 is 'bridge' for methods case ACC_TRANSIENT: if(type == AF_FIELD) return "transient"; else return "varargs"; // 0x80 is 'varargs' for methods case ACC_NATIVE: return "native"; case ACC_INTERFACE: return "interface"; case ACC_ABSTRACT: return "abstract"; case ACC_STRICT: return "strict"; case ACC_SYNTHETIC: return "synthetic"; case ACC_ANNOTATION: return "annotation"; case ACC_ENUM: return "enum"; case ACC_CONSTRUCTOR: return "constructor"; case ACC_DECLARED_SYNCHRONIZED: return "declared-synchronized"; } return "ERROR"; } string GetFriendlyAccessFlags(ACCESS_FLAGS f, AF_TYPE type) { string flags = ""; ACCESS_FLAGS i = 1; while(i <= ACC_DECLARED_SYNCHRONIZED) { if (f & i) { flags += GetFriendlyAccessFlag(i, type) + " "; } i = i << 1; } return flags; } // encoded fields typedef struct (int previd) { local int p = previd; uleb128 field_idx_diff ; uleb128 access_flags ; } encoded_field ; string EncodedFieldRead(encoded_field &f) { local int realid = f.p + uleb128_value(f.field_idx_diff); return GetFriendlyAccessFlags(uleb128_value(f.access_flags), AF_FIELD) + GetFieldById(realid); } typedef struct (int size) { local int s = size; local int i; local int fieldid = 0; for(i=0; i; fieldid = fieldid + uleb128_value(field.field_idx_diff); } } encoded_field_list ; string EncodedFieldListRead(encoded_field_list &l) { local string s; SPrintf(s, "%i fields", l.s); return s; } // encoded methods typedef struct (int previd) { local int p = previd; uleb128 method_idx_diff ; uleb128 access_flags ; uleb128 code_off ; if(uleb128_value(code_off) != 0) { local int64 pos = FTell(); FSeek(odexpad + uleb128_value(code_off)); struct code_item code ; FSeek(pos); } } encoded_method ; string EncodedMethodRead(encoded_method &m) { local int realid = m.p + uleb128_value(m.method_idx_diff); return GetFriendlyAccessFlags(uleb128_value(m.access_flags), AF_METHOD) + GetMethodById(realid); } typedef struct (int size) { local int s = size; local int i; local int methodid = 0; for(i=0; i; methodid = methodid + uleb128_value(method.method_idx_diff); } } encoded_method_list ; string EncodedMethodListRead(encoded_method_list &l) { local string s; SPrintf(s, "%i methods", l.s); return s; } typedef struct { uleb128 static_fields_size ; uleb128 instance_fields_size ; uleb128 direct_methods_size ; uleb128 virtual_methods_size ; if(uleb128_value(static_fields_size) > 0) { encoded_field_list static_fields(uleb128_value(static_fields_size)) ; } if(uleb128_value(instance_fields_size) > 0) { encoded_field_list instance_fields(uleb128_value(instance_fields_size)) ; } if(uleb128_value(direct_methods_size) > 0) { encoded_method_list direct_methods(uleb128_value(direct_methods_size)) ; } if(uleb128_value(virtual_methods_size) > 0) { encoded_method_list virtual_methods(uleb128_value(virtual_methods_size)) ; } } class_data_item ; string ClassDataItemRead(class_data_item &i) { local string s; SPrintf(s, "%i static fields, %i instance fields, %i direct methods, %i virtual methods", uleb128_value(i.static_fields_size), uleb128_value(i.instance_fields_size), uleb128_value(i.direct_methods_size), uleb128_value(i.virtual_methods_size)); return s; } typedef struct { local int64 pos; uint class_idx ; ACCESS_FLAGS access_flags ; uint superclass_idx ; uint interfaces_off ; if(interfaces_off != 0) { pos = FTell(); FSeek(odexpad + interfaces_off); type_item_list interfaces ; FSeek(pos); } uint source_file_idx ; uint annotations_off ; if(annotations_off != 0) { pos = FTell(); FSeek(odexpad + annotations_off); annotations_directory_item annotations ; FSeek(pos); } uint class_data_off ; if(class_data_off != 0) { pos = FTell(); FSeek(odexpad + class_data_off); class_data_item class_data ; FSeek(pos); } uint static_values_off ; if(static_values_off != 0) { pos = FTell(); FSeek(odexpad + static_values_off); struct encoded_array_item static_values ; FSeek(pos); } } class_def_item ; string ClassDefItemRead(class_def_item &i) { local string classname = GetLongTypeById(i.class_idx); local string flags = GetFriendlyAccessFlags(i.access_flags, AF_CLASS); return flags + classname; } string InterfacesRead(type_item_list &l) { string s = ""; int i; for(i = 0; i < l.size; i++) { s += GetLongTypeDescriptor(GetTypeById(l.list[i].type_idx)); if(i+1 < l.size) { s += ", "; } } return s; } typedef struct (int size) { local int s = size; class_def_item class_def[size] ; } class_def_item_list ; string ClassDefItemListRead(class_def_item_list &l) { string s; s = SPrintf(s, "%d classes", l.s); return s; } typedef struct { uint start_addr ; ushort insn_count ; ushort handler_off ; } try_item ; typedef struct { sleb128 size ; local int s = sleb128_value(size); local int numhandlers = 0; if(s != 0) { numhandlers = Abs(s); struct encoded_type_addr_pair handlers[numhandlers] ; } if(s <= 0) { uleb128 catch_all_addr ; numhandlers++; } } encoded_catch_handler ; string EncodedCatchHandlerRead(encoded_catch_handler &h) { local string s; SPrintf(s, "%i handlers", h.numhandlers); return s; } typedef struct { uleb128 size ; encoded_catch_handler list[uleb128_value(size)] ; } encoded_catch_handler_list ; string EncodedCatchHandlerListRead(encoded_catch_handler_list &l) { local string s; SPrintf(s, "%i handler lists", uleb128_value(l.size)); return s; } typedef struct { ushort registers_size ; ushort ins_size ; ushort outs_size ; ushort tries_size ; uint debug_info_off ; if(debug_info_off != 0) { if(odexpad + debug_info_off > FileSize()) { PrintWarning("Debug info offset appears to be outside of file bounds - skipping!"); } else if(odexpad + debug_info_off < 0) { PrintWarning("Debug info offset appears to be before file bounds - skipping!"); } else { local int64 pos = FTell(); FSeek(odexpad + debug_info_off); struct debug_info_item debug_info ; FSeek(pos); } } uint insns_size ; if(insns_size != 0) { ushort insns[insns_size] ; } if(tries_size != 0) { if (insns_size & 1 == 1) { ushort padding ; } try_item tries[tries_size] ; encoded_catch_handler_list handlers ; } } code_item ; string CodeItemRead(code_item &i) { local string s; SPrintf(s, "%i registers, %i in arguments, %i out arguments, %i tries, %i instructions", i.registers_size, i.ins_size, i.outs_size, i.tries_size, i.insns_size); return s; } typedef struct { uleb128 type_idx ; uleb128 addr ; } encoded_type_addr_pair ; string EncodedTypeAddrPairRead(encoded_type_addr_pair &p) { string s; SPrintf(s, "%s at 0x%X", GetLongTypeById(uleb128_value(p.type_idx)), uleb128_value(p.addr)); return s; } typedef struct { encoded_array value ; } encoded_array_item ; string EncodedArrayItemRead(encoded_array_item &i) { local string s; SPrintf(s, "%i items: %s", uleb128_value(i.value.size), EncodedArrayRead(i.value)); return s; } enum TYPE_CODES { TYPE_HEADER_ITEM = 0x0000, TYPE_STRING_ID_ITEM = 0x0001, TYPE_TYPE_ID_ITEM = 0x0002, TYPE_PROTO_ID_ITEM = 0x0003, TYPE_FIELD_ID_ITEM = 0x0004, TYPE_METHOD_ID_ITEM = 0x0005, TYPE_CLASS_DEF_ITEM = 0x0006, TYPE_MAP_LIST = 0x1000, TYPE_TYPE_LIST = 0x1001, TYPE_ANNOTATION_SET_REF_LIST = 0x1002, TYPE_ANNOTATION_SET_ITEM = 0x1003, TYPE_CLASS_DATA_ITEM = 0x2000, TYPE_CODE_ITEM = 0x2001, TYPE_STRING_DATA_ITEM = 0x2002, TYPE_DEBUG_INFO_ITEM = 0x2003, TYPE_ANNOTATION_ITEM = 0x2004, TYPE_ENCODED_ARRAY_ITEM = 0x2005, TYPE_ANNOTATIONS_DIRECTORY_ITEM = 0x2006 }; ////////////////////////////////////////////////// // debug info ////////////////////////////////////////////////// typedef enum { DBG_END_SEQUENCE = 0x00, DBG_ADVANCE_PC = 0x01, DBG_ADVANCE_LINE = 0x02, DBG_START_LOCAL = 0x03, DBG_START_LOCAL_EXTENDED = 0x04, DBG_END_LOCAL = 0x05, DBG_RESTART_LOCAL = 0x06, DBG_SET_PROLOGUE_END = 0x07, DBG_SET_EPILOGUE_BEGIN = 0x08, DBG_SET_FILE = 0x09 } DBG_OPCODE; typedef struct { DBG_OPCODE opcode ; local string args = ""; switch (opcode) { case DBG_END_SEQUENCE: break; case DBG_ADVANCE_PC: uleb128 addr_diff ; SPrintf(args, "%i", uleb128_value(addr_diff)); break; case DBG_ADVANCE_LINE: sleb128 line_diff ; SPrintf(args, "%i", sleb128_value(line_diff)); break; case DBG_START_LOCAL: uleb128 register_num ; uleb128p1 name_idx ; uleb128p1 type_idx ; SPrintf(args, "%i, %s, %s", uleb128_value(register_num), StringIdReadUlebp1(name_idx), LongTypeIdReadUlebp1(type_idx)); break; case DBG_START_LOCAL_EXTENDED: uleb128 register_num ; uleb128p1 name_idx ; uleb128p1 type_idx ; uleb128p1 sig_idx ; SPrintf(args, "%i, %s, %s, %s", uleb128_value(register_num), StringIdReadUlebp1(name_idx), LongTypeIdReadUlebp1(type_idx), StringIdReadUlebp1(sig_idx)); break; case DBG_END_LOCAL: uleb128 register_num ; SPrintf(args, "%i", uleb128_value(register_num)); break; case DBG_RESTART_LOCAL: uleb128 register_num ; SPrintf(args, "%i", uleb128_value(register_num)); break; case DBG_SET_PROLOGUE_END: case DBG_SET_EPILOGUE_BEGIN: break; case DBG_SET_FILE: uleb128p1 name_idx ; SPrintf(args, "%s", StringIdReadUlebp1(name_idx)); } } debug_opcode ; #define DBG_FIRST_SPECIAL 0x0a #define DBG_LINE_BASE -4 #define DBG_LINE_RANGE 15 string DebugOpcodeRead(debug_opcode &opcode) { local string s; if(opcode.opcode >= DBG_FIRST_SPECIAL) { local ubyte adjusted = opcode.opcode - DBG_FIRST_SPECIAL; SPrintf(s, "Special opcode: line + %i, address + %i", DBG_LINE_BASE + (adjusted % DBG_LINE_RANGE), (adjusted / DBG_LINE_RANGE)); } else { s = EnumToString(opcode.opcode); } if(opcode.args != "") { s += " (" + opcode.args + ")"; } return s; } typedef struct { uleb128 line_start ; uleb128 parameters_size ; if(uleb128_value(parameters_size) > 0) { uleb128p1 parameter_names[uleb128_value(parameters_size)] ; // actually uleb128p1 } do { debug_opcode opcode ; } while (opcode.opcode != DBG_END_SEQUENCE); } debug_info_item; ////////////////////////////////////////////////// // map list ////////////////////////////////////////////////// typedef struct { TYPE_CODES type; ushort unused; uint size; uint offset; } map_item ; string MapItemRead(map_item &m) { string s; SPrintf(s, "%s", EnumToString(m.type)); return s; } typedef struct { uint size; map_item list[size]; } map_list_type ; string MapListTypeRead(map_list_type &t) { local string s; SPrintf(s, "%i items", t.size); return s; } ////////////////////////////////////////////////// // utility functions for reading various strings // note: strings are stored in a format called MUTF-8, and its // possible they won't always display correctly in the 010 UI ////////////////////////////////////////////////// // read a value from the string table string StringIdRead(int id) { if(id == NO_INDEX) { return "NO_INDEX"; } local string s; SPrintf(s, "(0x%.X) \"%s\"", id, GetStringById(id)); return s; } string StringIdReadUleb(uleb128 &id) { return StringIdRead(uleb128_value(id)); } string StringIdReadUlebp1(uleb128p1 &id) { return StringIdRead(uleb128p1_value(id)); } // read a value from the type table, return short form string TypeIdRead(int id) { return GetIdAndNameString(id, GetTypeById(id)); } // read a value from the type table, return the long form string LongTypeIdRead(int id) { return GetIdAndNameString(id, GetLongTypeById(id)); } string LongTypeIdReadUleb(uleb128 &id) { return LongTypeIdRead(uleb128_value(id)); } string LongTypeIdReadUlebp1(uleb128p1 &id) { return LongTypeIdRead(uleb128p1_value(id)); } string FieldIdRead(int id) { return GetIdAndNameString(id, GetFieldById(id)); } string MethodIdRead(int id) { return GetIdAndNameString(id, GetMethodById(id)); } string GetIdAndNameString(int id, string name) { local string s; SPrintf(s, "(0x%X) %s", id, name); return s; } // read a string from the string table string GetStringById(int id) { if(id == NO_INDEX) { return "NO_INDEX"; } if(exists(dex_string_ids.string_id[id])) { return dex_string_ids.string_id[id].string_data.data; } else { return "*** NO STRING"; } } string GetTypeById(int id) { if(id == NO_INDEX) { return "NO_INDEX"; } if(exists(dex_type_ids.type_id[id])) { return GetStringById(dex_type_ids.type_id[id].descriptor_idx); } else { return "*** NO TYPE"; } } string GetLongTypeById(int id) { return GetLongTypeDescriptor(GetTypeById(id)); } string GetMethodById(int id) { if(id == NO_INDEX) { return "NO_INDEX"; } if(exists(dex_method_ids.method_id[id])) { return MethodIdItemRead(dex_method_ids.method_id[id]); } else { return "*** NO METHOD"; } } string GetFieldById(int id) { if(id == NO_INDEX) { return "NO_INDEX"; } if(exists(dex_field_ids.field_id[id])) { return FieldIdItemRead(dex_field_ids.field_id[id]); } else { return "*** NO FIELD"; } } ////////////////////////////////////////////////// // dexopt stuff ////////////////////////////////////////////////// typedef enum { DEX_CHUNK_CLASS_LOOKUP = 0x434c4b50, DEX_CHUNK_REGISTER_MAPS = 0x524d4150, DEX_CHUNK_END = 0x41454e44 } DEX_CHUNK_TYPE; typedef struct { DEX_CHUNK_TYPE type ; uint size ; local int realsize = (size + 7) & ~7; if(type == DEX_CHUNK_CLASS_LOOKUP) { struct dex_class_lookup class_lookup_table ; } else if (type == DEX_CHUNK_REGISTER_MAPS) { ubyte chunkbytes[realsize]; } else if (type == DEX_CHUNK_END) { //ubyte chunkbytes[realsize]; } else { SPrintf(temp_warning, "Unknown chunk type 0x%X", type); PrintWarning(temp_warning); return; } } dexopt_opt_chunk ; typedef struct { local int count = 0; do { dexopt_opt_chunk chunk; count++; } while(chunk.type != DEX_CHUNK_END); } dexopt_opt_table ; string DexoptOptTableRead(dexopt_opt_table &t) { local string s; SPrintf(s, "%i items", t.count); return s; } string DexOptChunkRead(dexopt_opt_chunk &c) { local string s; SPrintf(s, "%s chunk: %i bytes", EnumToString(c.type), c.size); return s; } typedef struct { int offset; } class_descriptor ; string readClassDescriptor(class_descriptor &item) { if(item.offset != 0) return ReadString(sizeof(dexopt_header) + item.offset); return ""; } typedef struct { uint class_descriptor_hash ; class_descriptor class_descriptor_item ; int class_definition_offset ; } dex_class_lookup_entry ; string DexClassLookupEntryRead(dex_class_lookup_entry &e) { local string s; SPrintf(s, "0x%X: (descriptor 0x%X, definition 0x%X)", e.class_descriptor_hash, e.class_descriptor_item.offset, e.class_definition_offset); return s; } typedef struct { int size ; int num_entries ; if(num_entries > 0) { dex_class_lookup_entry table[num_entries] ; } } dex_class_lookup; ////////////////////////////////////////////////// // main stuff ////////////////////////////////////////////////// // first check file type - dex files start with 'dex', odex files start with 'dey' local int odex = 0; local char tmp[3]; ReadBytes(tmp, 0, 3); FSeek(0); if(!Strcmp(tmp, "dey")) { odex = 1; } // dexopt files start with a dexopt header if(odex) { dexopt_header_item dexopt_header ; odexpad = dexopt_header.dex_offset; FSeek(odexpad); } // main dex header and structs SetBackColor(cLtGreen); header_item dex_header ; FSeek(dex_header.string_ids_off + odexpad); SetBackColor(cLtYellow); string_id_list dex_string_ids(dex_header.string_ids_size) ; FSeek(dex_header.type_ids_off + odexpad); SetBackColor(cLtPurple); type_id_list dex_type_ids(dex_header.type_ids_size) ; FSeek(dex_header.proto_ids_off + odexpad); SetBackColor(cLtBlue); proto_id_list dex_proto_ids(dex_header.proto_ids_size) ; FSeek(dex_header.field_ids_off + odexpad); SetBackColor(cYellow); field_id_list dex_field_ids(dex_header.field_ids_size) ; FSeek(dex_header.method_ids_off + odexpad); SetBackColor(cDkYellow); method_id_list dex_method_ids(dex_header.method_ids_size) ; FSeek(dex_header.class_defs_off + odexpad); SetBackColor(cLtGray); class_def_item_list dex_class_defs(dex_header.class_defs_size) ; // map list, we don't really do anything with it though SetBackColor(cLtAqua); if(dex_header.map_off != 0) { FSeek(odexpad + dex_header.map_off); map_list_type dex_map_list ; } if(odex) { if(dexopt_header.deps_offset != 0) { FSeek(dexopt_header.deps_offset); ubyte dexopt_deps[dexopt_header.deps_length] ; } if(dexopt_header.opt_offset != 0) { FSeek(dexopt_header.opt_offset); dexopt_opt_table opt_table ; } } ////////////////////////////////////////////////// // Post template completion checks and functions ////////////////////////////////////////////////// // Check for code injected before the "end" (according to header) of dex file; hidden after the map section, which must be last local int injection_location = dex_header.map_off + (dex_map_list.size * 12) + 4; local int injection_size = dex_header.file_size - injection_location; if(injection_size > 0) { SPrintf(temp_warning, "There appears to be extra data injected after the map list @ [ %d ] with a size of [ %d ] that is before the end of the dex file.", injection_location, injection_size); PrintWarning(temp_warning); SetBackColor(cLtRed); FSeek(injection_location); ubyte injected_data[injection_size]; } // Check for source code name removal/obfuscation int CheckSourceFileName(int source_file_index) { local string temp_str = StringIdRead(source_file_index); if(sizeof(temp_str) <= 6) { return -1; } temp_str = SubStr(temp_str, sizeof(temp_str) - 7, -1); return Strcmp(temp_str, ".java\""); } //if( MessageBox( idYes | idNo, // "Check for oddities?", // "Check for source file issues?") // == idYes ) { if( CHECK_ODDITIES ) { local int class_idx = 0; for(class_idx = 0; class_idx < sizeof(dex_class_defs) / sizeof(dex_class_defs.class_def); class_idx++) { if(dex_class_defs.class_def[class_idx].source_file_idx != NO_INDEX && CheckSourceFileName(dex_class_defs.class_def[class_idx].source_file_idx) != 0) { SPrintf(temp_warning, "Source file name for class [ %s ] does not appear to be a \".java\" file!", LongTypeIdRead(dex_class_defs.class_def[class_idx].class_idx)); PrintWarning(temp_warning); } } } ////////////////////////////////////////////////// // Output error stuff ////////////////////////////////////////////////// // It's not really useful to see just the last warning, so inform us how many warnings we should see in output if(warnings > 1) { Warning("%d warnings have occured and logged to the output box!", warnings); } // This will make the template show "Template executed successfully." if(warnings != 0) { SPrintf(temp_warning, "%d warnings found, template may not have run successfully!", warnings); return temp_warning; }