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

  1. Run decode and write as one uninterrupted pipeline on the same ResPackage[] without external mutation.
  2. Verify all resource entries get replacement names during decode (no earlier AndrolibExceptions swallowed).
  3. Rebuild the APK from sources and re-run AndResGuard on a clean artifact.
  4. 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

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


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)