minecraft-source/src/net/minecraft/world/level/chunk/storage/RegionFile.java

342 lines
13 KiB
Java

package net.minecraft.world.level.chunk.storage;
import java.io.ByteArrayOutputStream;
import org.apache.logging.log4j.LogManager;
import java.nio.file.StandardCopyOption;
import java.nio.file.CopyOption;
import java.nio.file.attribute.FileAttribute;
import net.minecraft.Util;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.BufferedInputStream;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.io.DataInputStream;
import net.minecraft.world.level.ChunkPos;
import java.nio.file.StandardOpenOption;
import java.nio.file.OpenOption;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.io.IOException;
import java.io.File;
import java.nio.IntBuffer;
import java.nio.file.Path;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import org.apache.logging.log4j.Logger;
public class RegionFile implements AutoCloseable {
private static final Logger LOGGER;
private static final ByteBuffer PADDING_BUFFER;
private final FileChannel file;
private final Path externalFileDir;
private final RegionFileVersion version;
private final ByteBuffer header;
private final IntBuffer offsets;
private final IntBuffer timestamps;
private final RegionBitmap usedSectors;
public RegionFile(final File file1, final File file2) throws IOException {
this(file1.toPath(), file2.toPath(), RegionFileVersion.VERSION_DEFLATE);
}
public RegionFile(final Path path1, final Path path2, final RegionFileVersion cbb) throws IOException {
this.header = ByteBuffer.allocateDirect(8192);
this.usedSectors = new RegionBitmap();
this.version = cbb;
if (!Files.isDirectory(path2)) {
throw new IllegalArgumentException("Expected directory, got " + path2.toAbsolutePath());
}
this.externalFileDir = path2;
(this.offsets = this.header.asIntBuffer()).limit(1024);
this.header.position(4096);
this.timestamps = this.header.asIntBuffer();
this.file = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
this.usedSectors.force(0, 2);
this.header.position(0);
final int integer5 = this.file.read(this.header, 0L);
if (integer5 != -1) {
if (integer5 != 8192) {
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", path1, integer5);
}
for (int integer6 = 0; integer6 < 1024; ++integer6) {
final int integer7 = this.offsets.get(integer6);
if (integer7 != 0) {
final int integer8 = getSectorNumber(integer7);
final int integer9 = getNumSectors(integer7);
this.usedSectors.force(integer8, integer9);
}
}
}
}
private Path getExternalChunkPath(final ChunkPos bje) {
final String string3 = "c." + bje.x + "." + bje.z + ".mcc";
return this.externalFileDir.resolve(string3);
}
@Nullable
public synchronized DataInputStream getChunkDataInputStream(final ChunkPos bje) throws IOException {
final int integer3 = this.getOffset(bje);
if (integer3 == 0) {
return null;
}
final int integer4 = getSectorNumber(integer3);
final int integer5 = getNumSectors(integer3);
final int integer6 = integer5 * 4096;
final ByteBuffer byteBuffer7 = ByteBuffer.allocate(integer6);
this.file.read(byteBuffer7, integer4 * 4096);
byteBuffer7.flip();
if (byteBuffer7.remaining() < 5) {
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", bje, integer6, byteBuffer7.remaining());
return null;
}
final int integer7 = byteBuffer7.getInt();
final byte byte9 = byteBuffer7.get();
if (integer7 == 0) {
RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", bje);
return null;
}
final int integer8 = integer7 - 1;
if (isExternalStreamChunk(byte9)) {
if (integer8 != 0) {
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
}
return this.createExternalChunkInputStream(bje, getExternalChunkVersion(byte9));
}
if (integer8 > byteBuffer7.remaining()) {
RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", bje, integer8, byteBuffer7.remaining());
return null;
}
if (integer8 < 0) {
RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", integer7, bje);
return null;
}
return this.createChunkInputStream(bje, byte9, createStream(byteBuffer7, integer8));
}
private static boolean isExternalStreamChunk(final byte byte1) {
return (byte1 & 0x80) != 0x0;
}
private static byte getExternalChunkVersion(final byte byte1) {
return (byte)(byte1 & 0xFFFFFF7F);
}
@Nullable
private DataInputStream createChunkInputStream(final ChunkPos bje, final byte byte2, final InputStream inputStream) throws IOException {
final RegionFileVersion cbb5 = RegionFileVersion.fromId(byte2);
if (cbb5 == null) {
RegionFile.LOGGER.error("Chunk {} has invalid chunk stream version {}", bje, byte2);
return null;
}
return new DataInputStream(new BufferedInputStream(cbb5.wrap(inputStream)));
}
@Nullable
private DataInputStream createExternalChunkInputStream(final ChunkPos bje, final byte byte2) throws IOException {
final Path path4 = this.getExternalChunkPath(bje);
if (!Files.isRegularFile(path4)) {
RegionFile.LOGGER.error("External chunk path {} is not file", path4);
return null;
}
return this.createChunkInputStream(bje, byte2, Files.newInputStream(path4));
}
private static ByteArrayInputStream createStream(final ByteBuffer byteBuffer, final int integer) {
return new ByteArrayInputStream(byteBuffer.array(), byteBuffer.position(), integer);
}
private int packSectorOffset(final int integer1, final int integer2) {
return integer1 << 8 | integer2;
}
private static int getNumSectors(final int integer) {
return integer & 0xFF;
}
private static int getSectorNumber(final int integer) {
return integer >> 8;
}
private static int sizeToSectors(final int integer) {
return (integer + 4096 - 1) / 4096;
}
public boolean doesChunkExist(final ChunkPos bje) {
final int integer3 = this.getOffset(bje);
if (integer3 == 0) {
return false;
}
final int integer4 = getSectorNumber(integer3);
final int integer5 = getNumSectors(integer3);
final ByteBuffer byteBuffer6 = ByteBuffer.allocate(5);
try {
this.file.read(byteBuffer6, integer4 * 4096);
byteBuffer6.flip();
if (byteBuffer6.remaining() != 5) {
return false;
}
final int integer6 = byteBuffer6.getInt();
final byte byte8 = byteBuffer6.get();
if (isExternalStreamChunk(byte8)) {
if (!RegionFileVersion.isValidVersion(getExternalChunkVersion(byte8))) {
return false;
}
if (!Files.isRegularFile(this.getExternalChunkPath(bje))) {
return false;
}
}
else {
if (!RegionFileVersion.isValidVersion(byte8)) {
return false;
}
if (integer6 == 0) {
return false;
}
final int integer7 = integer6 - 1;
if (integer7 < 0 || integer7 > 4096 * integer5) {
return false;
}
}
}
catch (IOException iOException7) {
return false;
}
return true;
}
public DataOutputStream getChunkDataOutputStream(final ChunkPos bje) throws IOException {
return new DataOutputStream(new BufferedOutputStream(this.version.wrap(new ChunkBuffer(bje))));
}
protected synchronized void write(final ChunkPos bje, final ByteBuffer byteBuffer) throws IOException {
final int integer4 = getOffsetIndex(bje);
final int integer5 = this.offsets.get(integer4);
final int integer6 = getSectorNumber(integer5);
final int integer7 = getNumSectors(integer5);
final int integer8 = byteBuffer.remaining();
int integer9 = sizeToSectors(integer8);
int integer10;
CommitOp b11;
if (integer9 >= 256) {
final Path path12 = this.getExternalChunkPath(bje);
RegionFile.LOGGER.warn("Saving oversized chunk {} ({} bytes} to external file {}", bje, integer8, path12);
integer9 = 1;
integer10 = this.usedSectors.allocate(integer9);
b11 = this.writeToExternalFile(path12, byteBuffer);
final ByteBuffer byteBuffer2 = this.createExternalStub();
this.file.write(byteBuffer2, integer10 * 4096);
}
else {
integer10 = this.usedSectors.allocate(integer9);
b11 = (() -> Files.deleteIfExists(this.getExternalChunkPath(bje)));
this.file.write(byteBuffer, integer10 * 4096);
}
final int integer11 = (int)(Util.getEpochMillis() / 1000L);
this.offsets.put(integer4, this.packSectorOffset(integer10, integer9));
this.timestamps.put(integer4, integer11);
this.writeHeader();
b11.run();
if (integer6 != 0) {
this.usedSectors.free(integer6, integer7);
}
}
private ByteBuffer createExternalStub() {
final ByteBuffer byteBuffer2 = ByteBuffer.allocate(5);
byteBuffer2.putInt(1);
byteBuffer2.put((byte)(this.version.getId() | 0x80));
byteBuffer2.flip();
return byteBuffer2;
}
private CommitOp writeToExternalFile(final Path path, final ByteBuffer byteBuffer) throws IOException {
final Path path2 = Files.createTempFile(this.externalFileDir, "tmp", null, new FileAttribute[0]);
try (final FileChannel fileChannel5 = FileChannel.open(path2, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
byteBuffer.position(5);
fileChannel5.write(byteBuffer);
}
return () -> Files.move(path2, path, StandardCopyOption.REPLACE_EXISTING);
}
private void writeHeader() throws IOException {
this.header.position(0);
this.file.write(this.header, 0L);
}
private int getOffset(final ChunkPos bje) {
return this.offsets.get(getOffsetIndex(bje));
}
public boolean hasChunk(final ChunkPos bje) {
return this.getOffset(bje) != 0;
}
private static int getOffsetIndex(final ChunkPos bje) {
return bje.getRegionLocalX() + bje.getRegionLocalZ() * 32;
}
@Override
public void close() throws IOException {
try {
this.padToFullSector();
}
finally {
try {
this.writeHeader();
}
finally {
try {
this.file.force(true);
}
finally {
this.file.close();
}
}
}
}
private void padToFullSector() throws IOException {
final int integer2 = (int)this.file.size();
final int integer3 = sizeToSectors(integer2) * 4096;
if (integer2 != integer3) {
final ByteBuffer byteBuffer4 = RegionFile.PADDING_BUFFER.duplicate();
byteBuffer4.position(0);
this.file.write(byteBuffer4, integer3 - 1);
}
}
static {
LOGGER = LogManager.getLogger();
PADDING_BUFFER = ByteBuffer.allocateDirect(1);
}
class ChunkBuffer extends ByteArrayOutputStream {
private final ChunkPos pos;
public ChunkBuffer(final ChunkPos bje) {
super(8096);
super.write(0);
super.write(0);
super.write(0);
super.write(0);
super.write(RegionFile.this.version.getId());
this.pos = bje;
}
@Override
public void close() throws IOException {
final ByteBuffer byteBuffer2 = ByteBuffer.wrap(this.buf, 0, this.count);
byteBuffer2.putInt(0, this.count - 5 + 1);
RegionFile.this.write(this.pos, byteBuffer2);
}
}
interface CommitOp {
void run() throws IOException;
}
}