shwenzhang/AndResGuard · error · brut.androlib.AndrolibException
writeEntry new specNamesId < 0
Error message
writeEntry new specNamesId < 0 %d
What it means
When writing a resource entry back into resources.arsc, AndResGuard looks up the obfuscated spec-name position via pkg.getSpecRepplace(mResId). If the resulting index is negative, the resource ID has no recorded replacement name — the write phase cannot map the entry back to its obfuscated name — so it throws to avoid emitting a corrupt arsc.
Solutions
- Run decode and write as one uninterrupted pipeline on the same ResPackage[] without external mutation.
- Verify all resource entries get replacement names during decode (no earlier AndrolibExceptions swallowed).
- Rebuild the APK from sources and re-run AndResGuard on a clean artifact.
- Upgrade AndResGuard if the arsc comes from a newer AAPT2 with entry layouts the old decoder mishandles.
Example fix
// before ResPackage[] pkgs = ARSCDecoder.decode(in, decoder); pkgs[0].removeSpecNamesReplace(someResId); // custom mutation ARSCDecoder.write(in, decoder, pkgs); // specNamesId < 0 // after ResPackage[] pkgs = ARSCDecoder.decode(in, decoder); ARSCDecoder.write(in, decoder, pkgs);
Defensive patterns
Strategy: validation
Validate before calling
for (ResPackage pkg : pkgs) {
for (int resId : allEntryResIds) {
if (pkg.isCanResguard() && pkg.getSpecRepplace(resId) == null) {
throw new IllegalStateException("no obfuscated name for resId 0x" + Integer.toHexString(resId));
}
}
} Try / catch
try {
ARSCDecoder.write(in, decoder, pkgs);
} catch (AndrolibException e) {
if (e.getMessage().contains("specNamesId < 0")) {
// decode/write desync; rerun the full decode->write pipeline
}
} Prevention
- Run decode and write back-to-back on the same ResPackage[] with no mutation.
- Never swallow earlier AndrolibExceptions during decode.
- Rebuild from sources when processing fails halfway.
When it happens
Trigger: writeEntry encounters a resId for which pkg.getSpecRepplace returns null/unknown during the write pass — i.e. an entry present in the arsc that was never assigned an obfuscated name during decode (decode/write desync, or packages mutated between phases).
Common situations: Custom code altering ResPackage/spec-name maps between decode and write; partial decode failures swallowed upstream; arsc entries referencing IDs AndResGuard's decode pass skipped; version-mismatched APK processing (decode from one build, write into another).
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- writeTable package count is different before
- Could not decode arsc file
- the old mapping res file path should be like r/a, yours
- Neither KeyStore key alias nor private key file available
- Failed to determine APK's minimum supported platform…
AI-assisted analysis of shwenzhang/AndResGuard@e4df245d82 (2026-09-12).
Data as JSON: /api/errors/e466b8a21c2589de.
Report an issue: GitHub.
Appendix: source
Thrown at AndResGuard-core/src/main/java/com/tencent/mm/androlib/res/decoder/ARSCDecoder.java:669
mPkg.putSpecNamesReplace(mResId, replaceString);
// arsc name列混淆成固定名字, 减少string pool大小
boolean useFixedName = config.mFixedResName != null && config.mFixedResName.length() > 0;
String fixedName = useFixedName ? config.mFixedResName : replaceString;
mPkg.putSpecNamesblock(fixedName, replaceString);
mType.putSpecResguardName(replaceString);
}
private void writeEntry() throws IOException, AndrolibException {
/* size */
mOut.writeBytes(mIn, 2);
short flags = mIn.readShort();
mOut.writeShort(flags);
int specNamesId = mIn.readInt();
ResPackage pkg = mPkgs[mCurPackageID];
if (pkg.isCanResguard()) {
specNamesId = mCurSpecNameToPos.get(pkg.getSpecRepplace(mResId));
if (specNamesId < 0) {
throw new AndrolibException(String.format("writeEntry new specNamesId < 0 %d", specNamesId));
}
}
mOut.writeInt(specNamesId);
if ((flags & ENTRY_FLAG_COMPLEX) == 0) {
writeValue();
} else {
writeComplexEntry();
}
}
/**
* @param flags whether read direct
*/
private void readComplexEntry(boolean flags, int specNamesId) throws IOException, AndrolibException {
int parent = mIn.readInt();
int count = mIn.readInt();
for (int i = 0; i < count; i++) {View on GitHub (pinned to e4df245d82)