{"record":{"id":"783c714edc0c6f2d","repo":"iBotPeaches/Apktool","slug":"cannot-disassemble-an-odex-file-without-deodexing","errorCode":null,"errorMessage":"Cannot disassemble an odex file without deodexing it: {dexName}","messagePattern":"Cannot disassemble an odex file without deodexing it: (.+?)","errorType":"exception","errorClass":"AndrolibException","httpStatus":null,"severity":"error","filePath":"brut.apktool/apktool-lib/src/main/java/brut/androlib/smali/SmaliDecoder.java","lineNumber":103,"sourceCode":"\n                int dexNum;\n                try {\n                    dexNum = Integer.parseInt(dexEntryName.substring(prefix.length()));\n                } catch (NumberFormatException ignored) {\n                    continue;\n                }\n                if (dexNum > 1) {\n                    dexFiles.put(dexNum, mDexContainer.getEntry(dexEntryName).getDexFile());\n                }\n            }\n\n            // Decode the dex files into separate folders.\n            for (Map.Entry<Integer, DexBackedDexFile> entry : dexFiles.entrySet()) {\n                int dexNum = entry.getKey();\n                DexBackedDexFile dexFile = entry.getValue();\n\n                if (dexFile.supportsOptimizedOpcodes()) {\n                    throw new AndrolibException(\"Cannot disassemble an odex file without deodexing it: \" + dexName);\n                }\n\n                String dirName = \"smali\";\n                if (dexNum > 1 || !dexName.equals(\"classes.dex\")) {\n                    dirName += \"_\" + dexName.substring(0, dexName.lastIndexOf('.')).replace('/', '@');\n                    if (dexNum > 1) {\n                        dirName += dexNum;\n                    }\n                }\n\n                decodeFile(dexFile, new File(outDir, dirName));\n            }\n\n            mDexFiles.add(dexName);\n        } catch (IOException ex) {\n            throw new AndrolibException(\"Could not baksmali file: \" + dexName, ex);\n        }\n    }","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/iBotPeaches/Apktool/blob/79b63384d7d7e22917e6ea8b453272da7012515b/brut.apktool/apktool-lib/src/main/java/brut/androlib/smali/SmaliDecoder.java#L85-L121","documentation":"During baksmali decoding, a dex file in the container reports supportsOptimizedOpcodes(), i.e. it is an optimized dex (odex/oat artifact from a device's dalvik-art cache), not plain dex. Baksmali cannot disassemble optimized opcodes without a deodexing context, so apktool refuses.","triggerScenarios":"Calling SmaliDecoder.decode on an APK or container that contains an odex entry (e.g. classes pulled from /data/dalvik-cache, or an .odex file), where the dex uses quickened/optimized opcodes.","commonSituations":"Pulling optimized dex from a rooted device's ART cache instead of the original APK; handling OEM .odex files; trying to decode with a tool version whose deodex path is not enabled.","solutions":["Decode the original APK (base.apk from the device/Play store) rather than dalvik-cache artifacts","For real odex files, deodex first using baksmali with a matching boot.oat/boot.art (`baksmali deodex -b boot.oat file.odex`), then work with the plain dex","Ensure the odex's boot image comes from the exact same ROM build as the odex, otherwise deodex fails","Alternatively skip the odex entry: decode only known plain-dex entries by name"],"exampleFix":"# before\n# pulled /data/dalvik-cache/arm64/data@app@@com.example*==/base.apk@classes.dex\napktool d cached.apk   # Cannot disassemble an odex file without deodexing it\n\n# after\n# get the original, non-optimized artifact and deodex properly\nbaksmali deodex -b boot.oat app.odex -o out\napktool d original-base.apk","handlingStrategy":"validation","validationCode":"// Detect optimized dex before decode\n// DexBackedDexFile.supportsOptimizedOpcodes() is the check apktool uses;\n// from outside, screen inputs by origin: reject files pulled from dalvik-cache\nif (path.contains(\"dalvik-cache\") || path.endsWith(\".odex\")) {\n    throw new IllegalArgumentException(\"Optimized dex requires deodexing first: \" + path);\n}","typeGuard":null,"tryCatchPattern":"try {\n    decoder.decode(dexName, outDir);\n} catch (AndrolibException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Cannot disassemble an odex\")) {\n        // deodex with matching boot.oat via baksmali, or decode the original APK instead\n    } else { throw e; }\n}","preventionTips":["Decode original APKs from the store/device, never dalvik-cache artifacts","For odex workflows, always fetch boot.oat from the same ROM build","Screen input files by extension and origin before invoking the decoder"],"tags":["apktool","odex","dex","smali","deodex"],"backgroundTag":null,"analyzedSha":"79b63384d7d7e22917e6ea8b453272da7012515b","analyzedAt":"2026-08-14T10:43:28.812Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}