Download dex-parser.js from Hosdroid/Aplood: direct link, hf CLI and curl.
- Browser
- Download file 20.4 kB
-
https://huggingface.co/spaces/Hosdroid/Aplood/resolve/main/dex-parser.js
- Command line
-
hf download hf://spaces/Hosdroid/Aplood/dex-parser.js
-
curl -L -o dex-parser.js https://huggingface.co/spaces/Hosdroid/Aplood/resolve/main/dex-parser.js
20.4 kB
| /** | |
| * Advanced DEX Parser and Transpiler | |
| * Converts Android DEX bytecode to JavaScript | |
| * | |
| * This module provides deep analysis and conversion of DEX files | |
| */ | |
| class DEXParser { | |
| constructor() { | |
| this.dexData = null; | |
| this.header = null; | |
| this.stringIds = []; | |
| this.typeIds = []; | |
| this.protoIds = []; | |
| this.fieldIds = []; | |
| this.methodIds = []; | |
| this.classDefs = []; | |
| this.codeItems = []; | |
| } | |
| async parse(data) { | |
| if (data instanceof ArrayBuffer) { | |
| this.dexData = new Uint8Array(data); | |
| } else if (data instanceof Uint8Array) { | |
| this.dexData = data; | |
| } else { | |
| throw new Error('Invalid DEX data format'); | |
| } | |
| await this.parseHeader(); | |
| await this.parseStringIds(); | |
| await this.parseTypeIds(); | |
| await this.parseProtoIds(); | |
| await this.parseFieldIds(); | |
| await this.parseMethodIds(); | |
| await this.parseClassDefs(); | |
| return this.generateJavaScript(); | |
| } | |
| async parseHeader() { | |
| const view = new DataView(this.dexData.buffer); | |
| // Verify magic number | |
| const magic = new TextDecoder().decode(this.dexData.slice(0, 8)); | |
| if (!magic.startsWith('dex\n')) { | |
| throw new Error('Invalid DEX file: bad magic number'); | |
| } | |
| this.header = { | |
| magic: magic, | |
| version: view.getUint32(4, true), | |
| fileSize: view.getUint32(32, true), | |
| endianTag: view.getUint32(40, true), | |
| linkSize: view.getUint32(44, true), | |
| linkOff: view.getUint32(48, true), | |
| mapOff: view.getUint32(52, true), | |
| stringIdsSize: view.getUint32(56, true), | |
| stringIdsOff: view.getUint32(60, true), | |
| typeIdsSize: view.getUint32(64, true), | |
| typeIdsOff: view.getUint32(68, true), | |
| protoIdsSize: view.getUint32(72, true), | |
| protoIdsOff: view.getUint32(76, true), | |
| fieldIdsSize: view.getUint32(80, true), | |
| fieldIdsOff: view.getUint32(84, true), | |
| methodIdsSize: view.getUint32(88, true), | |
| methodIdsOff: view.getUint32(92, true), | |
| classDefsSize: view.getUint32(96, true), | |
| classDefsOff: view.getUint32(100, true), | |
| dataSize: view.getUint32(104, true), | |
| dataOff: view.getUint32(108, true) | |
| }; | |
| console.log('DEX Header parsed:', this.header); | |
| } | |
| async parseStringIds() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.stringIdsOff; | |
| const size = this.header.stringIdsSize; | |
| for (let i = 0; i < size; i++) { | |
| const stringOffset = view.getUint32(offset + (i * 4), true); | |
| const string = this.readString(stringOffset); | |
| this.stringIds.push(string); | |
| } | |
| console.log(`Parsed ${this.stringIds.length} strings`); | |
| } | |
| readString(offset) { | |
| // Read MUTF-8 encoded string | |
| let index = offset; | |
| const len = this.readULEB128(index); | |
| index += this.getULEB128Size(len); | |
| const bytes = []; | |
| for (let i = 0; i < len; i++) { | |
| bytes.push(this.dexData[index + i]); | |
| } | |
| try { | |
| return new TextDecoder().decode(new Uint8Array(bytes)); | |
| } catch (e) { | |
| return ''; | |
| } | |
| } | |
| readULEB128(offset) { | |
| let result = 0; | |
| let shift = 0; | |
| let byte; | |
| do { | |
| byte = this.dexData[offset++]; | |
| result |= (byte & 0x7f) << shift; | |
| shift += 7; | |
| } while (byte & 0x80); | |
| return result; | |
| } | |
| getULEB128Size(value) { | |
| let size = 0; | |
| do { | |
| value >>= 7; | |
| size++; | |
| } while (value !== 0); | |
| return size; | |
| } | |
| async parseTypeIds() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.typeIdsOff; | |
| const size = this.header.typeIdsSize; | |
| for (let i = 0; i < size; i++) { | |
| const descriptorIdx = view.getUint32(offset + (i * 4), true); | |
| const descriptor = this.stringIds[descriptorIdx]; | |
| this.typeIds.push({ | |
| descriptorIdx, | |
| descriptor, | |
| className: this.descriptorToClassName(descriptor) | |
| }); | |
| } | |
| console.log(`Parsed ${this.typeIds.length} types`); | |
| } | |
| descriptorToClassName(descriptor) { | |
| if (!descriptor) return 'unknown'; | |
| // Convert Ljava/lang/Object; to java.lang.Object | |
| if (descriptor.startsWith('L') && descriptor.endsWith(';')) { | |
| return descriptor.substring(1, descriptor.length - 1).replace(/\//g, '.'); | |
| } | |
| // Array types | |
| if (descriptor.startsWith('[')) { | |
| return descriptor.replace(/\//g, '.'); | |
| } | |
| // Primitive types | |
| const primitives = { | |
| 'V': 'void', | |
| 'Z': 'boolean', | |
| 'B': 'byte', | |
| 'S': 'short', | |
| 'C': 'char', | |
| 'I': 'int', | |
| 'J': 'long', | |
| 'F': 'float', | |
| 'D': 'double' | |
| }; | |
| return primitives[descriptor] || descriptor; | |
| } | |
| async parseProtoIds() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.protoIdsOff; | |
| const size = this.header.protoIdsSize; | |
| for (let i = 0; i < size; i++) { | |
| const baseOffset = offset + (i * 12); | |
| const shortyIdx = view.getUint32(baseOffset, true); | |
| const returnTypeIdx = view.getUint32(baseOffset + 4, true); | |
| const parametersOff = view.getUint32(baseOffset + 8, true); | |
| const returnType = this.typeIds[returnTypeIdx]; | |
| const shorty = this.stringIds[shortyIdx]; | |
| this.protoIds.push({ | |
| shorty, | |
| returnType, | |
| parametersOff, | |
| parameters: [] | |
| }); | |
| } | |
| console.log(`Parsed ${this.protoIds.length} prototypes`); | |
| } | |
| async parseFieldIds() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.fieldIdsOff; | |
| const size = this.header.fieldIdsSize; | |
| for (let i = 0; i < size; i++) { | |
| const baseOffset = offset + (i * 6); | |
| const classIdx = view.getUint16(baseOffset, true); | |
| const typeIdx = view.getUint16(baseOffset + 2, true); | |
| const nameIdx = view.getUint16(baseOffset + 4, true); | |
| this.fieldIds.push({ | |
| classIdx, | |
| typeIdx, | |
| nameIdx, | |
| className: this.typeIds[classIdx]?.className || 'unknown', | |
| typeName: this.typeIds[typeIdx]?.className || 'unknown', | |
| name: this.stringIds[nameIdx] || 'unknown' | |
| }); | |
| } | |
| console.log(`Parsed ${this.fieldIds.length} fields`); | |
| } | |
| async parseMethodIds() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.methodIdsOff; | |
| const size = this.header.methodIdsSize; | |
| for (let i = 0; i < size; i++) { | |
| const baseOffset = offset + (i * 8); | |
| const classIdx = view.getUint16(baseOffset, true); | |
| const protoIdx = view.getUint16(baseOffset + 2, true); | |
| const nameIdx = view.getUint32(baseOffset + 4, true); | |
| this.methodIds.push({ | |
| classIdx, | |
| protoIdx, | |
| nameIdx, | |
| className: this.typeIds[classIdx]?.className || 'unknown', | |
| proto: this.protoIds[protoIdx], | |
| name: this.stringIds[nameIdx] || 'unknown' | |
| }); | |
| } | |
| console.log(`Parsed ${this.methodIds.length} methods`); | |
| } | |
| async parseClassDefs() { | |
| const view = new DataView(this.dexData.buffer); | |
| const offset = this.header.classDefsOff; | |
| const size = this.header.classDefsSize; | |
| for (let i = 0; i < size; i++) { | |
| const baseOffset = offset + (i * 32); | |
| const classIdx = view.getUint32(baseOffset, true); | |
| const accessFlags = view.getUint32(baseOffset + 4, true); | |
| const superclassIdx = view.getUint32(baseOffset + 8, true); | |
| const interfacesOff = view.getUint32(baseOffset + 12, true); | |
| const sourceFileIdx = view.getUint32(baseOffset + 16, true); | |
| const annotationsOff = view.getUint32(baseOffset + 20, true); | |
| const classDataOff = view.getUint32(baseOffset + 24, true); | |
| const staticValuesOff = view.getUint32(baseOffset + 28, true); | |
| this.classDefs.push({ | |
| classIdx, | |
| accessFlags, | |
| superclassIdx, | |
| interfacesOff, | |
| sourceFileIdx, | |
| annotationsOff, | |
| classDataOff, | |
| staticValuesOff, | |
| className: this.typeIds[classIdx]?.className || 'unknown', | |
| superClass: this.typeIds[superclassIdx]?.className || 'unknown' | |
| }); | |
| } | |
| console.log(`Parsed ${this.classDefs.length} class definitions`); | |
| } | |
| generateJavaScript() { | |
| let jsCode = '// Auto-generated JavaScript from DEX bytecode\n\n'; | |
| jsCode += '// DEX Information\n'; | |
| jsCode += `// Version: ${this.header.version}\n`; | |
| jsCode += `// File Size: ${this.header.fileSize} bytes\n`; | |
| jsCode += `// Strings: ${this.header.stringIdsSize}\n`; | |
| jsCode += `// Types: ${this.header.typeIdsSize}\n`; | |
| jsCode += `// Methods: ${this.header.methodIdsSize}\n`; | |
| jsCode += `// Classes: ${this.header.classDefsSize}\n\n`; | |
| // Generate class stubs | |
| jsCode += '// Class Definitions\n'; | |
| for (const classDef of this.classDefs) { | |
| jsCode += this.generateClassStub(classDef); | |
| } | |
| // Generate method stubs | |
| jsCode += '\n// Method Stubs\n'; | |
| for (const method of this.methodIds.slice(0, 100)) { // Limit to first 100 methods | |
| jsCode += this.generateMethodStub(method); | |
| } | |
| // Generate Android framework simulation | |
| jsCode += this.generateAndroidFramework(); | |
| console.log('JavaScript code generated successfully'); | |
| return jsCode; | |
| } | |
| generateClassStub(classDef) { | |
| const className = classDef.className.split('.').pop() || 'UnknownClass'; | |
| const packageName = classDef.className.substring(0, classDef.className.lastIndexOf('.')) || 'default'; | |
| let code = `// Class: ${classDef.className}\n`; | |
| code += `class ${className} {\n`; | |
| code += ` constructor() {\n`; | |
| code += ` // Access flags: ${classDef.accessFlags}\n`; | |
| code += ` // Superclass: ${classDef.superClass}\n`; | |
| code += ` console.log('${className} instantiated');\n`; | |
| code += ` }\n`; | |
| code += `}\n\n`; | |
| return code; | |
| } | |
| generateMethodStub(method) { | |
| const methodName = method.name; | |
| const returnType = method.proto?.returnType?.className || 'void'; | |
| let code = `// Method: ${method.className}.${methodName}()\n`; | |
| code += `${methodName}: function() {\n`; | |
| code += ` // Return type: ${returnType}\n`; | |
| code += ` console.log('Method ${methodName} called');\n`; | |
| code += ` return null;\n`; | |
| code += `},\n\n`; | |
| return code; | |
| } | |
| generateAndroidFramework() { | |
| return ` | |
| // Android Framework Simulation | |
| const android = { | |
| // Core classes | |
| Context: class { | |
| constructor() { | |
| this.resources = {}; | |
| this.preferences = {}; | |
| } | |
| getSystemService(name) { | |
| console.log('Getting system service:', name); | |
| return null; | |
| } | |
| startActivity(intent) { | |
| console.log('Starting activity:', intent); | |
| } | |
| getResources() { | |
| return this.resources; | |
| } | |
| getSharedPreferences(name, mode) { | |
| return this.preferences; | |
| } | |
| }, | |
| Activity: class extends android.Context { | |
| constructor() { | |
| super(); | |
| this.intent = null; | |
| this.view = null; | |
| } | |
| onCreate(savedInstanceState) { | |
| console.log('Activity created'); | |
| } | |
| onStart() { | |
| console.log('Activity started'); | |
| } | |
| onResume() { | |
| console.log('Activity resumed'); | |
| } | |
| onPause() { | |
| console.log('Activity paused'); | |
| } | |
| onStop() { | |
| console.log('Activity stopped'); | |
| } | |
| onDestroy() { | |
| console.log('Activity destroyed'); | |
| } | |
| setContentView(layoutId) { | |
| console.log('Setting content view:', layoutId); | |
| } | |
| findViewById(id) { | |
| console.log('Finding view by ID:', id); | |
| return null; | |
| } | |
| }, | |
| Service: class extends android.Context { | |
| onCreate() { | |
| console.log('Service created'); | |
| } | |
| onStartCommand(intent, flags, startId) { | |
| console.log('Service started'); | |
| return this.START_NOT_STICKY; | |
| } | |
| onDestroy() { | |
| console.log('Service destroyed'); | |
| } | |
| }, | |
| BroadcastReceiver: class { | |
| onReceive(context, intent) { | |
| console.log('Broadcast received:', intent); | |
| } | |
| }, | |
| ContentProvider: class { | |
| onCreate() { | |
| console.log('Content provider created'); | |
| return true; | |
| } | |
| query(uri, projection, selection, selectionArgs, sortOrder) { | |
| console.log('Querying:', uri); | |
| return null; | |
| } | |
| insert(uri, values) { | |
| console.log('Inserting into:', uri); | |
| return null; | |
| } | |
| update(uri, values, selection, selectionArgs) { | |
| console.log('Updating:', uri); | |
| return 0; | |
| } | |
| delete(uri, selection, selectionArgs) { | |
| console.log('Deleting from:', uri); | |
| return 0; | |
| } | |
| }, | |
| // UI Components | |
| View: class { | |
| constructor(context) { | |
| this.context = context; | |
| this.visibility = 0; // VISIBLE | |
| this.alpha = 1.0; | |
| } | |
| setVisibility(visibility) { | |
| this.visibility = visibility; | |
| } | |
| setAlpha(alpha) { | |
| this.alpha = alpha; | |
| } | |
| invalidate() { | |
| console.log('View invalidated'); | |
| } | |
| }, | |
| TextView: class extends android.View { | |
| constructor(context) { | |
| super(context); | |
| this.text = ''; | |
| } | |
| setText(text) { | |
| this.text = text; | |
| console.log('TextView set to:', text); | |
| } | |
| getText() { | |
| return this.text; | |
| } | |
| }, | |
| Button: class extends android.TextView { | |
| constructor(context) { | |
| super(context); | |
| this.onClick = null; | |
| } | |
| setOnClickListener(listener) { | |
| this.onClick = listener; | |
| } | |
| performClick() { | |
| if (this.onClick) { | |
| this.onClick(this); | |
| } | |
| } | |
| }, | |
| ImageView: class extends android.View { | |
| constructor(context) { | |
| super(context); | |
| this.imageResource = 0; | |
| } | |
| setImageResource(resId) { | |
| this.imageResource = resId; | |
| console.log('ImageView set to resource:', resId); | |
| } | |
| setImageBitmap(bitmap) { | |
| console.log('ImageView set to bitmap'); | |
| } | |
| }, | |
| LinearLayout: class extends android.View { | |
| constructor(context) { | |
| super(context); | |
| this.orientation = 1; // VERTICAL | |
| this.children = []; | |
| } | |
| setOrientation(orientation) { | |
| this.orientation = orientation; | |
| } | |
| addView(view) { | |
| this.children.push(view); | |
| console.log('View added to LinearLayout'); | |
| } | |
| removeView(view) { | |
| const index = this.children.indexOf(view); | |
| if (index > -1) { | |
| this.children.splice(index, 1); | |
| } | |
| } | |
| }, | |
| RelativeLayout: class extends android.View { | |
| constructor(context) { | |
| super(context); | |
| this.children = []; | |
| } | |
| addView(view, params) { | |
| this.children.push(view); | |
| console.log('View added to RelativeLayout'); | |
| } | |
| }, | |
| // Common utilities | |
| Intent: class { | |
| constructor(context, component) { | |
| this.context = context; | |
| this.component = component; | |
| this.extras = {}; | |
| this.action = null; | |
| this.data = null; | |
| } | |
| putExtra(key, value) { | |
| this.extras[key] = value; | |
| } | |
| getExtra(key) { | |
| return this.extras[key]; | |
| } | |
| setAction(action) { | |
| this.action = action; | |
| } | |
| setData(uri) { | |
| this.data = uri; | |
| } | |
| }, | |
| Bundle: class { | |
| constructor() { | |
| this.data = {}; | |
| } | |
| putString(key, value) { | |
| this.data[key] = value; | |
| } | |
| getString(key, defaultValue) { | |
| return this.data[key] || defaultValue; | |
| } | |
| putInt(key, value) { | |
| this.data[key] = value; | |
| } | |
| getInt(key, defaultValue) { | |
| return this.data[key] || defaultValue; | |
| } | |
| putBoolean(key, value) { | |
| this.data[key] = value; | |
| } | |
| getBoolean(key, defaultValue) { | |
| return this.data[key] !== undefined ? this.data[key] : defaultValue; | |
| } | |
| }, | |
| SharedPreferences: class { | |
| constructor() { | |
| this.data = {}; | |
| } | |
| edit() { | |
| return { | |
| putString: (key, value) => { this.data[key] = value; return this; }, | |
| putInt: (key, value) => { this.data[key] = value; return this; }, | |
| putBoolean: (key, value) => { this.data[key] = value; return this; }, | |
| commit: () => { console.log('Preferences committed'); return true; }, | |
| apply: () => { console.log('Preferences applied'); } | |
| }; | |
| } | |
| getString(key, defValue) { | |
| return this.data[key] || defValue; | |
| } | |
| getInt(key, defValue) { | |
| return this.data[key] !== undefined ? this.data[key] : defValue; | |
| } | |
| getBoolean(key, defValue) { | |
| return this.data[key] !== undefined ? this.data[key] : defValue; | |
| } | |
| }, | |
| // Constants | |
| View: { | |
| VISIBLE: 0, | |
| INVISIBLE: 1, | |
| GONE: 8 | |
| }, | |
| LinearLayout: { | |
| VERTICAL: 1, | |
| HORIZONTAL: 0 | |
| }, | |
| Service: { | |
| START_NOT_STICKY: 2, | |
| START_STICKY: 1, | |
| START_REDELIVER_INTENT: 3 | |
| }, | |
| // Logging | |
| Log: { | |
| d: (tag, msg) => console.log('[DEBUG]', tag, ':', msg), | |
| i: (tag, msg) => console.log('[INFO]', tag, ':', msg), | |
| w: (tag, msg) => console.warn('[WARN]', tag, ':', msg), | |
| e: (tag, msg) => console.error('[ERROR]', tag, ':', msg), | |
| v: (tag, msg) => console.log('[VERBOSE]', tag, ':', msg) | |
| } | |
| }; | |
| // Initialize application | |
| console.log('Android framework initialized'); | |
| console.log('Ready to run transpiled DEX code'); | |
| `; | |
| } | |
| } | |
| // Export for usage | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = DEXParser; | |
| } | |