A C# library for reading and writing Named Binary Tag (NBT) files and streams. A continuation of libnbt project, originally by Erik Davidson (aphistic).
Hi, I've been trying to create a region (.mca) file from scratch, but I just can't get it to work.
I'm using "NBTExplorer" to read the file I'm creating for testing.
When I load the file in NBTExplorer, I can see the chunk that I'm creating but not any tags inside it.
So it seems that everything works fine, except the NBT part.
I'd appreciate any help with this issue.
Here's the class to create a region file:
public class RegionFile
{
private const int SECTOR_BYTES = 4096;
private const int SECTOR_INTS = SECTOR_BYTES / 4;
private const int CHUNK_HEADER_SIZE = 5;
private const int VERSION_DEFLATE = 2;
private static byte[] emptySector = new byte[SECTOR_BYTES];
private static int[] offsets;
private static int[] timestamps;
private List<bool> sectorFree;
private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public RegionFile(string path)
{
offsets = new int[SECTOR_INTS];
timestamps = new int[SECTOR_INTS];
try
{
using (BinaryWriter file = new BinaryWriter(File.Open(path, FileMode.Create)))
{
if (file.BaseStream.Length < SECTOR_BYTES)
{
/* we need to write the chunk offset table */
for (int i = 0; i < SECTOR_INTS; ++i)
{
file.Write((int)0);
}
// write another sector for the timestamp info
for (int i = 0; i < SECTOR_INTS; ++i)
{
file.Write((int)0);
}
}
if ((file.BaseStream.Length & 0xfff) != 0)
{
/* the file size is not a multiple of 4KB, grow it */
for (int i = 0; i < (file.BaseStream.Length & 0xfff); ++i)
{
file.Write((byte)0);
}
}
}
using (BinaryReader file = new BinaryReader(File.Open(path, FileMode.Open)))
{
/* set up the available sector map */
int nSectors = (int)file.BaseStream.Length / SECTOR_BYTES;
sectorFree = new List<bool>(nSectors);
for (int i = 0; i < nSectors; ++i)
{
sectorFree.Add(true);
}
sectorFree[0] = false; // chunk offset table
sectorFree[1] = false; // for the last modified info
file.BaseStream.Position = 0;
for (int i = 0; i < SECTOR_INTS; ++i)
{
int offset = file.ReadInt32();
offsets[i] = offset;
if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.Count())
{
for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum)
{
sectorFree[(offset >> 8) + sectorNum] = false;
}
}
}
for (int i = 0; i < SECTOR_INTS; ++i)
{
int lastModValue = file.ReadInt32();
timestamps[i] = lastModValue;
}
}
}
catch (IOException e)
{
MessageBox.Show(e.ToString());
}
// Nbt Arrays
byte[] biomeBytes = new byte[256];
for (int i = 0; i < 256; ++i)
{
biomeBytes[i] = 0;
}
int[] heightMapInts = new int[256];
for (int i = 0; i < 256; ++i)
{
heightMapInts[i] = 0;
}
byte[] blockBytes = new byte[4096];
for (int i = 0; i < 4096; ++i)
{
blockBytes[i] = 56;
}
byte[] dataBytes = new byte[2048];
for (int i = 0; i < 2048; ++i)
{
dataBytes[i] = 0;
}
byte[] blockLightBytes = new byte[2048];
for (int i = 0; i < 2048; ++i)
{
blockLightBytes[i] = 0;
}
byte[] skyLightBytes = new byte[2048];
for (int i = 0; i < 2048; ++i)
{
skyLightBytes[i] = 0;
}
NbtCompound chunk = new NbtCompound("Chunk") {
new NbtInt("DataVersion", 176),
new NbtCompound("Level") {
new NbtInt("xPos", 0),
new NbtInt("zPos", 0),
new NbtLong("LastUpdate", 0),
new NbtByte("LightPopulated", 0),
new NbtByte("TerrainPopulated", 1),
new NbtByte("V", 1),
new NbtLong("InhabitedTime", 0),
new NbtByteArray("Biomes", biomeBytes),
new NbtIntArray("HeightMap", heightMapInts),
new NbtList("Sections", NbtTagType.Compound)
{
new NbtCompound()
{
new NbtByte("Y", 0),
new NbtByteArray("Blocks", blockBytes),
new NbtByteArray("Data", dataBytes),
new NbtByteArray("BlockLight", blockLightBytes),
new NbtByteArray("SkyLight", skyLightBytes)
},
new NbtCompound()
{
new NbtByte("Y", 1),
new NbtByteArray("Blocks", blockBytes),
new NbtByteArray("Data", dataBytes),
new NbtByteArray("BlockLight", blockLightBytes),
new NbtByteArray("SkyLight", skyLightBytes)
},
new NbtCompound()
{
new NbtByte("Y", 2),
new NbtByteArray("Blocks", blockBytes),
new NbtByteArray("Data", dataBytes),
new NbtByteArray("BlockLight", blockLightBytes),
new NbtByteArray("SkyLight", skyLightBytes)
}
},
new NbtList("Entities", NbtTagType.Compound) {
},
new NbtList("TileEntities", NbtTagType.Compound) {
}
}
};
NbtFile nbtFile = new NbtFile(chunk);
byte[] data = nbtFile.SaveToBuffer(NbtCompression.ZLib);
using (BinaryWriter file = new BinaryWriter(new FileStream(path, FileMode.Open)))
{
WriteChunk(file, 0, 0, data, data.Length);
}
}
void WriteChunk(BinaryWriter file, int x, int z, byte[] data, int length)
{
try
{
int offset = getOffset(x, z);
int sectorNumber = offset >> 8;
int sectorsAllocated = offset & 0xFF;
int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1;
// Maximum chunk size is 1MB
if (sectorsNeeded >= 256)
{
return;
}
if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded)
{
WriteChunk(file, sectorNumber, data, length);
}
else
{
for (int i = 0; i < sectorsAllocated; ++i)
{
sectorFree[sectorNumber + i] = true;
}
int runStart = sectorFree.IndexOf(true);
int runLength = 0;
if (runStart != -1)
{
for (int i = runStart; i < sectorFree.Count(); ++i)
{
if (runLength != 0)
{
if (sectorFree[i])
runLength++;
else
runLength = 0;
}
else if (sectorFree[i])
{
runStart = i;
runLength = 1;
}
if (runLength >= sectorsNeeded)
{
break;
}
}
}
if (runLength >= sectorsNeeded)
{
/* We found a free space large enough */
sectorNumber = runStart;
SetOffset(file, x, z, (sectorNumber << 8) | sectorsNeeded);
for (int i = 0; i < sectorsNeeded; ++i)
{
sectorFree[sectorNumber + i] = false;
}
WriteChunk(file, sectorNumber, data, length);
}
else {
// No free space large enough found; we need to grow the file
file.BaseStream.Position = (int)file.BaseStream.Length;
sectorNumber = sectorFree.Count;
for (int i = 0; i < sectorsNeeded; ++i)
{
file.Write(emptySector);
sectorFree.Add(false);
}
WriteChunk(file, sectorNumber, data, length);
SetOffset(file, x, z, (sectorNumber << 8) | sectorsNeeded);
}
SetTimestamp(file, x, z, CurrentTimeMillis());
}
}
catch (IOException e)
{
MessageBox.Show(e.ToString());
}
}
private void WriteChunk(BinaryWriter file, int sectorNumber, byte[] data, int length)
{
file.BaseStream.Position = sectorNumber * SECTOR_BYTES;
file.Write(length + 1); // Chunk length
file.Write((byte)VERSION_DEFLATE); // Chunk version number
file.Write(data, 0, length);
}
int getOffset(int x, int z)
{
return offsets[x + z * 32];
}
void SetOffset(BinaryWriter file, int x, int z, int offset)
{
offsets[x + z * 32] = offset;
file.BaseStream.Position = (x + z * 32) * 4;
file.Write(offset);
}
void SetTimestamp(BinaryWriter file, int x, int z, int value)
{
timestamps[x + z * 32] = value;
file.BaseStream.Position = SECTOR_BYTES + (x + z * 32) * 4;
file.Write(value);
}
public static int CurrentTimeMillis()
{
return (int)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
}
}
Hi, I've been trying to create a region (.mca) file from scratch, but I just can't get it to work. I'm using "NBTExplorer" to read the file I'm creating for testing.
When I load the file in NBTExplorer, I can see the chunk that I'm creating but not any tags inside it. So it seems that everything works fine, except the NBT part.
I'd appreciate any help with this issue. Here's the class to create a region file: