Skip to content

Commit 5c97591

Browse files
committed
Implement region file parsing and chunk loading
1 parent efb40fa commit 5c97591

7 files changed

Lines changed: 110 additions & 10 deletions

File tree

src/main/java/org/glavo/nbt/chunk/Chunk.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,28 @@
1616
package org.glavo.nbt.chunk;
1717

1818
import org.glavo.nbt.NBTElement;
19+
import org.glavo.nbt.internal.ChunkMetadata;
1920
import org.glavo.nbt.internal.ChunkUtils;
21+
import org.glavo.nbt.tag.CompoundTag;
2022
import org.jetbrains.annotations.Nullable;
2123

2224
public final class Chunk implements NBTElement {
2325
@Nullable Region region;
2426
int localIndex = -1;
2527

28+
CompoundTag rootTag;
29+
30+
Chunk(Region region, int localIndex) {
31+
this.region = region;
32+
this.localIndex = localIndex;
33+
}
34+
35+
Chunk(Region region, ChunkMetadata metadata, CompoundTag rootTag) {
36+
this.region = region;
37+
this.localIndex = metadata.localIndex();
38+
this.rootTag = rootTag;
39+
}
40+
2641
public Chunk() {
2742
}
2843

@@ -40,4 +55,8 @@ public int getLocalX() {
4055
public int getLocalZ() {
4156
return localIndex >= 0 ? localIndex / ChunkUtils.CHUNKS_PER_REGION_SIDE : -1;
4257
}
58+
59+
public CompoundTag getRootTag() {
60+
return rootTag;
61+
}
4362
}

src/main/java/org/glavo/nbt/chunk/Region.java

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
import org.glavo.nbt.internal.ChunkMetadata;
2121
import org.glavo.nbt.internal.ChunkMetadataTable;
2222
import org.glavo.nbt.internal.ChunkUtils;
23+
import org.glavo.nbt.internal.input.DataReader;
2324
import org.glavo.nbt.internal.input.InputContext;
25+
import org.glavo.nbt.internal.input.RawDataReader;
26+
import org.glavo.nbt.internal.input.ZlibDataReader;
27+
import org.glavo.nbt.tag.CompoundTag;
28+
import org.glavo.nbt.tag.Tag;
2429

2530
import java.io.IOException;
2631
import java.util.Arrays;
@@ -57,22 +62,72 @@ static Region readRegion(InputContext context) throws IOException {
5762

5863
List<ChunkMetadata> sortedBySectorOffset = table.getSortedBySectorOffset();
5964

60-
long contentStart = context.source.position();
65+
var region = new Region();
66+
67+
long contentStart = context.position();
6168
for (ChunkMetadata chunkMetadata : sortedBySectorOffset) {
6269
long sectorStart = contentStart + (long) chunkMetadata.sectorOffset() * ChunkUtils.SECTOR_BYTES;
70+
long position = context.position();
71+
if (position != sectorStart) {
72+
if (position < sectorStart) {
73+
context.skip(sectorStart - position);
74+
} else {
75+
throw new IOException("Invalid chunk metadata: sector offset points to a position before the current position");
76+
}
77+
}
78+
79+
long chunkRawDataLength = context.rawReader.readUnsignedInt();
80+
if (chunkRawDataLength < 1) {
81+
throw new IOException("Invalid chunk data length: " + chunkRawDataLength);
82+
}
83+
84+
int compressType = context.rawReader.readUnsignedByte();
85+
if (compressType > 128) {
86+
if (chunkRawDataLength != 1) {
87+
throw new IOException("Invalid chunk data length: %d (expected 1 for compression type %d)".formatted(chunkRawDataLength, compressType));
88+
}
89+
90+
throw new IOException("The chunk data is stored externally, and reading this data is not currently supported.");
91+
}
92+
93+
DataReader reader = switch (compressType) {
94+
case 1 -> throw new IOException("GZip compression is not supported yet.");
95+
case 2 -> new ZlibDataReader(context, chunkRawDataLength - 1L);
96+
case 3 -> new RawDataReader(context, chunkRawDataLength - 1L);
97+
case 4 -> throw new IOException("LZ4 compression is not supported yet.");
98+
default -> throw new IOException("Unsupported compression type: " + compressType);
99+
};
63100

101+
var tag = Tag.readTag(reader);
102+
if (tag instanceof CompoundTag rootTag) {
103+
region.getChunk(chunkMetadata.localIndex()).rootTag = rootTag;
104+
} else {
105+
throw new IOException("Unexpected tag type: " + tag);
106+
}
64107
}
65108

66-
// TODO
67-
throw new AssertionError("Not implemented yet");
109+
return region;
68110
}
69111

70-
private final Chunk[] chunks = new Chunk[ChunkUtils.CHUNKS_PRE_REGION];
112+
private final Chunk[] chunks;
113+
114+
public Region() {
115+
this.chunks = new Chunk[ChunkUtils.CHUNKS_PRE_REGION];
116+
for (int i = 0; i < ChunkUtils.CHUNKS_PRE_REGION; i++) {
117+
chunks[i] = new Chunk(this, i);
118+
}
119+
}
120+
121+
public Chunk getChunk(int localIndex) {
122+
Objects.checkIndex(localIndex, ChunkUtils.CHUNKS_PRE_REGION);
123+
124+
return chunks[localIndex];
125+
}
71126

72127
public Chunk getChunk(int x, int z) {
73128
Objects.checkIndex(x, ChunkUtils.CHUNKS_PER_REGION_SIDE);
74129
Objects.checkIndex(z, ChunkUtils.CHUNKS_PER_REGION_SIDE);
75130

76-
return chunks[x + z * ChunkUtils.CHUNKS_PER_REGION_SIDE];
131+
return chunks[ChunkUtils.toLocalIndex(x, z)];
77132
}
78133
}

src/main/java/org/glavo/nbt/internal/input/DataReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public byte[] readByteArray() throws IOException {
5252
throw new IOException("Array length too large");
5353
}
5454

55-
return readBYteArray(len);
55+
return readByteArray(len);
5656
}
5757

5858
public byte[] readByteArray(int len) throws IOException {

src/main/java/org/glavo/nbt/internal/input/InputContext.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public final class InputContext implements Closeable {
3737
public final MinecraftEdition edition;
3838

3939
public final RawDataReader rawReader;
40+
private final long startPosition;
4041

4142
public final StringCache stringCache = DEFAULT_CACHE;
4243
@Nullable StringBuilder charsBuffer;
@@ -49,6 +50,31 @@ public InputContext(InputSource source, MinecraftEdition edition) {
4950
this.rawReader = new RawDataReader(
5051
this,
5152
InputBuffer.allocate(IOUtils.DEFAULT_BUFFER_SIZE, source.supportDirectBuffer(), edition.byteOrder()));
53+
this.startPosition = source.position();
54+
}
55+
56+
public long position() {
57+
long position = source.position() - startPosition - rawReader.buffer.remaining();
58+
assert position >= 0;
59+
return position;
60+
}
61+
62+
public void skip(long bytes) throws IOException {
63+
if (bytes < 0) {
64+
throw new IllegalArgumentException("bytes must be non-negative");
65+
}
66+
67+
if (bytes == 0L) {
68+
return;
69+
}
70+
71+
int bytesDrop = (int) Math.min(rawReader.buffer.remaining(), bytes);
72+
rawReader.buffer.drop(bytesDrop);
73+
74+
bytes -= bytesDrop;
75+
if (bytes > 0) {
76+
source.skip(bytes);
77+
}
5278
}
5379

5480
private @Nullable InputBuffer decompressBuffer;

src/main/java/org/glavo/nbt/internal/input/InputSource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public boolean supportDirectBuffer() {
2828
return false;
2929
}
3030

31-
public abstract long position() throws IOException;
31+
public abstract long position();
3232

3333
@Override
3434
public final void close() throws IOException {
@@ -72,7 +72,7 @@ public OfInputStream(InputStream inputStream, boolean closeInputStream) {
7272
}
7373

7474
@Override
75-
public long position() throws IOException {
75+
public long position() {
7676
return position;
7777
}
7878

src/main/java/org/glavo/nbt/internal/input/ZlibDataReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void close(Inflater value) {
3737

3838
private final Inflater inflater;
3939

40-
ZlibDataReader(InputContext context, long compressedSize) {
40+
public ZlibDataReader(InputContext context, long compressedSize) {
4141
super(context, context.getDecompressBuffer());
4242
this.inflater = INFLATER_CACHE_KEY.get(context);
4343
this.remainingInput = compressedSize;

src/main/java/org/glavo/nbt/tag/Tag.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
public sealed abstract class Tag implements NBTElement
3737
permits ValueTag, ArrayTag, ParentTag {
3838

39-
static @Nullable Tag readTag(DataReader reader) throws IOException {
39+
public static @Nullable Tag readTag(DataReader reader) throws IOException {
4040
byte tagByte = reader.readByte();
4141
if (tagByte == 0) {
4242
return null;

0 commit comments

Comments
 (0)