diff --git a/chunk.c b/chunk.c new file mode 100644 index 0000000..7bb2fdd --- /dev/null +++ b/chunk.c @@ -0,0 +1,177 @@ +#include "chunk.h" + +RegionID translateChunkToRegion(int x, int z); +RegionID translateCoordsToRegion(double x, double y, double z); +ChunkID translateCoordsToChunk(double x, double y, double z); +int overwriteChunk(const char* regionFolder, ChunkID chunk, void* chunkData, size_t chunkLength); +ssize_t loadChunk(const char* regionFolder, ChunkID chunk, void** chunkData); + +RegionID translateChunkToRegion(int x, int z) { + RegionID region; + region.x = x/CHUNKS_PER_REGION; + region.z = z/CHUNKS_PER_REGION; + return region; +} + +RegionID translateCoordsToRegion(double x, double y, double z) { + ChunkID chunk = translateCoordsToChunk(x,y,z); + return translateChunkToRegion(chunk.x,chunk.z); +} + +ChunkID translateCoordsToChunk(double x, double y, double z) { + ChunkID chunk; + chunk.x = x/BLOCKS_PER_CHUNK; + chunk.z = z/BLOCKS_PER_CHUNK; + return chunk; +} + +int overwriteChunk(const char* regionFolder, ChunkID chunk, void* chunkData, size_t chunkLength) { + RegionID region = translateChunkToRegion(chunk.x,chunk.z); + ChunkID relativeChunk; + relativeChunk.x = chunk.x % 32; + relativeChunk.z = chunk.z % 32; + + char* regionFilename = calloc(MAX_REGION_FILENAME_LENGTH + strlen(regionFolder),sizeof(char)); + sprintf(regionFilename,"%s/r.%d.%d.mca.new",regionFolder,region.x,region.z); + + if(access(regionFilename,R_OK | W_OK) == -1) { + fprintf(stderr,"Can't access file %s: %s\n",regionFilename,strerror(errno)); + return -1; + } + + int fd = open(regionFilename,O_RDWR); + if(fd == -1) { + fprintf(stderr,"Unable to open region file %s: %s\n",regionFilename,strerror(errno)); + return -2; + } + free(regionFilename); + + uint32_t chunkHeaderOffset; + if(pread(fd,&chunkHeaderOffset,sizeof(uint32_t),(relativeChunk.x + relativeChunk.z * CHUNK_OFFSET_LENGTH) * sizeof(uint32_t)) == -1) { + close(fd); + fprintf(stderr,"Unable to read chunk header offset: %s\n",strerror(errno)); + return -3; + } + uint32_t totalChunkLength = (chunkHeaderOffset >> 24) * 4096; + chunkHeaderOffset = (__bswap_32(chunkHeaderOffset & 0x00FFFFFF) >> 8) * CHUNK_SECTOR_SIZE; + int pos = lseek(fd,chunkHeaderOffset,SEEK_SET); + if(pos == -1) { + close(fd); + fprintf(stderr,"Unable to seek to header offset: %s\n",strerror(errno)); + return -4; + } + + ChunkHeader header; + if(pread(fd,&header,sizeof(ChunkHeader),pos) <= 0) { + close(fd); + fprintf(stderr,"Unable to read chunk header: %s\n",strerror(errno)); + return -5; + } + header.length = __bswap_32(header.length); + + void* compressedChunk; + ssize_t compressedChunkLength = deflateGzip(chunkData,chunkLength,&compressedChunk,(header.compressionType == COMPRESSION_TYPE_ZLIB)); + if(compressedChunkLength > totalChunkLength) { + // insert new 4096 blocks + } + header.length = __bswap_32((uint32_t)compressedChunkLength+1); + if(write(fd,&header,sizeof(ChunkHeader)) <= 0) { + close(fd); + free(compressedChunk); + fprintf(stderr,"Unable to read chunk header: %s\n",strerror(errno)); + return -6; + } + ssize_t nWritten = 0; + size_t totalWritten = 0; + + while((nWritten = write(fd,compressedChunk+totalWritten,compressedChunkLength-totalWritten))) { + if(nWritten == -1) { + if(errno == EINTR) { + continue; + } + fprintf(stderr,"Unable to write chunk: %s\n",strerror(errno)); + close(fd); + free(compressedChunk); + return -7; + } + totalWritten += nWritten; + } + close(fd); + free(compressedChunk); + return 0; +} + +ssize_t loadChunk(const char* regionFolder, ChunkID chunk, void** chunkData) { + RegionID region = translateChunkToRegion(chunk.x,chunk.z); + ChunkID relativeChunk; + relativeChunk.x = chunk.x % 32; + relativeChunk.z = chunk.z % 32; + + char* regionFilename = calloc(MAX_REGION_FILENAME_LENGTH + strlen(regionFolder),sizeof(char)); + sprintf(regionFilename,"%s/r.%d.%d.mca",regionFolder,region.x,region.z); + + if(access(regionFilename,R_OK) == -1) { + fprintf(stderr,"Can't access file %s: %s\n",regionFilename,strerror(errno)); + return -1; + } + + int fd = open(regionFilename,O_RDONLY); + if(fd == -1) { + fprintf(stderr,"Unable to open region file %s: %s\n",regionFilename,strerror(errno)); + return -2; + } + free(regionFilename); + + uint32_t chunkHeaderOffset; + if(pread(fd,&chunkHeaderOffset,sizeof(uint32_t),(relativeChunk.x + relativeChunk.z * CHUNK_OFFSET_LENGTH) * sizeof(uint32_t)) == -1) { + close(fd); + fprintf(stderr,"Unable to read chunk header offset: %s\n",strerror(errno)); + return -3; + } + chunkHeaderOffset = (__bswap_32(chunkHeaderOffset & 0x00FFFFFF) >> 8) * CHUNK_SECTOR_SIZE; + + if(lseek(fd,chunkHeaderOffset,SEEK_SET) == -1) { + close(fd); + fprintf(stderr,"Unable to seek to header offset: %s\n",strerror(errno)); + return -4; + } + + ChunkHeader header; + if(read(fd,&header,sizeof(ChunkHeader)) <= 0) { + close(fd); + fprintf(stderr,"Unable to read chunk header: %s\n",strerror(errno)); + return -5; + } + header.length = __bswap_32(header.length); + ssize_t chunkLength = header.length; + + void* compressedChunk = calloc(chunkLength,sizeof(char)); + ssize_t nRead = 0; + size_t totalRead = 0; + + while((nRead = read(fd,compressedChunk+totalRead,chunkLength-totalRead))) { + if(nRead == -1) { + if(errno == EINTR) { + continue; + } + fprintf(stderr,"Unable to read chunk: %s\n",strerror(errno)); + close(fd); + free(compressedChunk); + return -6; + } + totalRead += nRead; + } + close(fd); + + void *decompressedChunk; + chunkLength = inflateGzip(compressedChunk,chunkLength,&decompressedChunk,(header.compressionType == COMPRESSION_TYPE_ZLIB)); + + if(chunkLength <= 0) { + fprintf(stderr,"Error while decompressing chunk\n"); + return -8; + } + free(compressedChunk); + + *chunkData = decompressedChunk; + return chunkLength; +} \ No newline at end of file diff --git a/chunk.h b/chunk.h new file mode 100644 index 0000000..e369742 --- /dev/null +++ b/chunk.h @@ -0,0 +1,49 @@ +#ifndef _CHUNK_H +#define _CHUNK_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compression.h" + +#define BLOCKS_PER_CHUNK 16 +#define CHUNKS_PER_REGION 32 +#define CHUNK_OFFSET_LENGTH 32 +#define CHUNK_SECTOR_SIZE 4096 + +// 58593 is the maximum number of regions containing 32 chunks in any direction +// Thus, biggest filename is: +// r.-58593.-58593.mca +#define MAX_REGION_FILENAME_LENGTH 32 // Just to round up + +enum COMPRESSION_TYPE { + COMPRESSION_TYPE_GZIP = 1, + COMPRESSION_TYPE_ZLIB = 2 +}; + +typedef struct RegionID { + int x; + int z; +} RegionID; + +typedef struct ChunkID { + int x; + int z; +} ChunkID; + +typedef struct __attribute__((packed)) ChunkHeader { + uint32_t length; + uint8_t compressionType; +} ChunkHeader; + +ChunkID translateCoordsToChunk(double x, double y, double z); +int overwriteChunk(const char* regionFolder, ChunkID chunk, void* chunkData, size_t chunkLength); +ssize_t loadChunk(const char* regionFolder, ChunkID chunk, void** chunkData); + +#endif \ No newline at end of file