Anuken/Mindustry · error · IOException
Could not skip bytes. Expected length: {}; Actual length: {}
Error message
Could not skip bytes. Expected length: {}; Actual length: {} What it means
Thrown by SaveFileReader.skipChunk after reading a 4-byte chunk length and calling DataInput.skipBytes(length). The Java DataInput contract allows skipBytes to skip fewer bytes than requested (it is best-effort and returns the number actually skipped); this guard fires when the returned count does not match the declared chunk length. The mismatch almost always means the underlying stream is truncated or the chunk header is corrupt rather than a transient skip failure.
Source
Thrown at core/src/mindustry/io/SaveFileReader.java:154
int length = input.readInt();
runner.accept(input, length);
return length;
}
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
public int readChunkReads(DataInput input, IORunnerLength<Reads> runner) throws IOException{
return readChunk(input, (in, length) -> {
chunkReads.input = in;
runner.accept(chunkReads, length);
});
}
/** Skip a chunk completely, discarding the bytes. */
public void skipChunk(DataInput input) throws IOException{
int length = readChunk(input, (t, len) -> {});
int skipped = input.skipBytes(length);
if(length != skipped){
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
}
}
/** Reads a legacy chunk where the length is only 2 bytes. */
public int readLegacyShortChunk(DataInput input, IORunnerLength<Reads> runner) throws IOException{
int length = input.readUnsignedShort();
chunkReads.input = input;
runner.accept(chunkReads, length);
return length;
}
/** Skip a legacy chunk completely, discarding the bytes. */
public void skipLegacyShortChunk(DataInput input) throws IOException{
int length = readLegacyShortChunk(input, (t, len) -> {});
int skipped = input.skipBytes(length);
if(length != skipped){
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
}View on GitHub (pinned to f695ad7e60)
Solutions
- Treat the save as corrupt: call SaveIO.isSaveValid(file) first, and on failure fall back to the auto-generated backup via SaveIO.getBackupStream / SaveIO.load(file) which already retries the -backup file.
- If loading a custom DataInput, verify the source stream has at least length bytes remaining before invoking skipChunk (use a CounterInputStream or available()/mark).
- Re-save the game in the current version so the chunk layout matches, instead of loading an older/cross-version file.
- If you control the writer, ensure writeChunk always writes a length prefix that exactly equals the buffered output size (it does in SaveFileReader.writeChunk).
Example fix
// before
try {
SaveIO.load(file);
} catch (SaveException e) {
// surface raw error
}
// after
if (SaveIO.isSaveValid(file)) {
SaveIO.load(file);
} else {
ui.showInfo("This save file is corrupt or from an incompatible version.");
} Defensive patterns
Strategy: validation
Validate before calling
// Before loading, confirm the save parses end-to-end.
if (!SaveIO.isSaveValid(file)) {
throw new IllegalArgumentException("Save file is corrupt or truncated: " + file);
}
// SaveIO.load(Fi) already retries the -backup file on SaveException. Type guard
null
Try / catch
try {
SaveIO.load(file);
} catch (SaveException e) {
// both primary and backup failed; report to user
ui.showInfo(bundle.get("save.corrupt"));
} Prevention
- Always load via SaveIO.load(Fi) so the auto-backup retry applies.
- Call SaveIO.isSaveValid before offering a Load button on a slot.
- Never hand-edit chunk length prefixes in save files.
When it happens
Trigger: Called from SaveVersion.readMap (SaveVersion.java:373) when a tile had an entity in the save but the block no longer has Building IO code, so the entity region must be discarded. Also fires anywhere skipChunk is invoked on a stream whose actual remaining bytes are fewer than the length prefix indicates.
Common situations: Loading a save from an incompatible game version where chunk layout shifted; a truncated .msav (disk write interrupted / partial download); a corrupt deflated stream that decompressed to fewer bytes than the length prefix claimed; manual editing of a save file that left the chunk length stale.
Related errors
- Failed to read tile entity of block: {}
- Unknown save version: {}. Are you trying to load a save from
- Incorrect header! Expecting: {}; Actual: {}
- Invalid asset type ID: {}. You are likely loading trying to
- Nested arrays are not allowed
AI-assisted analysis of Anuken/Mindustry@f695ad7e60 (2026-08-14).
Data as JSON: /api/errors/054468e74c613363.
Report an issue: GitHub.