HMCL-dev/HMCL · error · JsonParseException

Missing payload or payload array is too small

Error message

Missing payload or payload array is too small

What it means

OBFUSCATED_V1.joinObfuscatedPayload throws this when the envelope's 'payload' member is not a JSON array or has fewer than 4 elements. The obfuscated format stores the Base64 ciphertext in a 256-element null-padded array; anything smaller cannot contain the 4 lanes and is considered malformed.

Solutions

  1. Ensure the protection marker matches the actual payload layout; use 'plain' with a direct payload, or a full obfuscated array.
  2. Restore the original 256-element padded array (or the whole file) from backup and let HMCL re-save.
  3. Regenerate the setting so HMCL rewrites the envelope in the correct format.

Example fix

// before: mismatched marker/layout
{"protection":"hmcl-obfuscated-v1","payload":"abc...","nonce":"..."}
// after: correct pairing
{"protection":"plain","payload":{...}}
Defensive patterns

Strategy: validation

Validate before calling

com.google.gson.JsonElement p = envelope.get("payload");
boolean ok = "hmcl-obfuscated-v1".equals(JsonUtils.getString(envelope, "protection"))
        ? p instanceof com.google.gson.JsonArray && ((com.google.gson.JsonArray) p).size() >= 4
        : p != null;

Type guard

static boolean hasObfuscatedPayloadArray(com.google.gson.JsonObject envelope) {
    return envelope.get("payload") instanceof com.google.gson.JsonArray lanes
        && lanes.size() >= 4;
}

Try / catch

try {
    return ProtectedPayload.read(envelope, JsonObject.class);
} catch (com.google.gson.JsonParseException e) {
    logger.warning("Malformed obfuscated envelope; regenerating", e);
    return recreateSetting();
}

Prevention

When it happens

Trigger: Calling ProtectedPayload.read on an envelope marked 'hmcl-obfuscated-v1' whose payload member is missing, not an array (e.g. a plain value from PLAIN mode), or an array with fewer than OBFUSCATED_LANE_COUNT (4) entries.

Common situations: Mixing envelope formats — using a 'plain'-style payload with an 'hmcl-obfuscated-v1' marker; hand-editing and shortening the padded array; older or third-party writers emitting a different obfuscation layout.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/bc87d8ed3c0e947f. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java:186

                    int start = laneIndex * laneLength;
                    String lane = payload.substring(start, start + laneLength);
                    for (int i = 0; i < LANE_PADDING_COUNT; i++) {
                        result.add(JsonNull.INSTANCE);
                    }
                    result.add(lane);
                }
                return result;
            }

            /// Joins Base64 payload lanes from the envelope.
            ///
            /// @param envelope the envelope object to read from
            /// @return the restored Base64 payload
            /// @throws JsonParseException if the payload lanes are missing or malformed
            private static String joinObfuscatedPayload(JsonObject envelope) {
                if (!(envelope.get(PROPERTY_PAYLOAD) instanceof JsonArray lanes)
                        || lanes.size() < OBFUSCATED_LANE_COUNT) {
                    throw new JsonParseException("Missing payload or payload array is too small");
                }

                int effectivePayloadSize = Integer.highestOneBit(lanes.size());
                String[] laneTexts = new String[OBFUSCATED_LANE_COUNT];
                int totalLength = 0;
                for (int i = 0; i < OBFUSCATED_LANE_COUNT; i++) {
                    int payloadIndex = lanePayloadIndex(i, effectivePayloadSize);
                    JsonElement lane = lanes.get(payloadIndex);
                    if (!lane.isJsonPrimitive() || !lane.getAsJsonPrimitive().isString()) {
                        throw new JsonParseException("Protected payload lane is not a string");
                    }

                    laneTexts[i] = lane.getAsString();
                    totalLength += laneTexts[i].length();
                }

                StringBuilder result = new StringBuilder(totalLength);
                for (String laneText : laneTexts) {

View on GitHub (pinned to 24702dc5a0)