SpongePowered / nbt

Named Binary Tag (NBT) library for Java based on Graham Edgecombe's JNBT library. NBT is a tag based binary format designed to carry large amounts of binary data with smaller amounts of additional data.
https://flow.github.io/nbt
MIT License
51 stars 33 forks source link

Error parsing region files? #8

Open TealNerd opened 6 years ago

TealNerd commented 6 years ago

Tried using this to parse region files and I'm getting an error right away.

new NBTInputStream(file, false).readTag()

causes an IOException with the message "TAG_End found without a TAG_Compound/TAG_List tag preceding it."

Not really sure what's happening there

TealNerd commented 6 years ago

Okay you literally have a region file reading class im an idiot

TealNerd commented 6 years ago

Okay reopening because the SimpleRegionFileReader readFile method is returning null

natemort commented 6 years ago

The region parser doesn't work at all. I'm not really sure what format it's intended to parse. The first thing the SimpleRegionFileReader does is read an int to check the version, but the Region file format doesn't have a version field. It doesn't seem to match the old file format or the schematic file format either.

Here's the version I created, since everything else in the library works fine. I'm using this to do some offline computations on Minecraft worlds, and can verify that it works correctly.

import com.flowpowered.nbt.Tag;
import com.flowpowered.nbt.stream.NBTInputStream;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;

public class RegionReader {
    private static final int SECTOR_BYTES = 4096;

    public static List<Tag<?>> readFile(File f) {
        try (RandomAccessFile raf = new RandomAccessFile(f, "r")) {
            // If there isn't a header and at least one chunk sector, the region is empty
            if (raf.length() < SECTOR_BYTES * 3) {
                return Collections.emptyList();
            }
            // Each chunk can use 1 or more sectors, and the first two sectors
            // are the header, so this is the maximum number of chunks
            int maxChunks = ((int) raf.length() / SECTOR_BYTES) - 2;
            int[] chunkLocation = new int[maxChunks];
            int entries = 0;
            for (int i = 0; i < (SECTOR_BYTES / 4); i++) {
                int offset = raf.readInt();
                if (offset != 0) {
                    // The rest of the offset is the number of sectors that the chunk
                    // occupies.  We don't care about that as each chunk stores its length
                    chunkLocation[entries++] = (offset >> 8) * SECTOR_BYTES;
                }
            }
            List<Tag<?>> list = new ArrayList<>();

            for (int i = 0; i < entries; i++) {
                list.add(readChunk(raf, chunkLocation[i]));
            }

            return list;
        } catch (IOException ioe) {
            ioe.printStackTrace();
            return null;
        }
    }

    private static Tag<?> readChunk(RandomAccessFile raf, int location) throws IOException {
        raf.seek(location);
        int length = raf.readInt();
        byte compressionType = raf.readByte();
        byte[] data = new byte[length-1];
        raf.readFully(data);
        return readTag(decompress(compressionType, data));
    }

    private static InputStream decompress(int type, byte[] data) throws IOException {
        switch (type) {
            case 1: return new GZIPInputStream(new ByteArrayInputStream(data));
            case 2: return new InflaterInputStream(new ByteArrayInputStream(data));
            default: throw new IllegalArgumentException("Unknown type");
        }
    }

    private static Tag<?> readTag(InputStream in) throws IOException {
        return new NBTInputStream(in, false).readTag();
    }
}

EDIT: I've modified the file slightly since I originally uploaded it to account for empty sectors. My revised unit tests pass.