SHARE
TWEET

Untitled

a guest Feb 16th, 2011 12,220 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  ** 2011 January 5
  3.  **
  4.  ** The author disclaims copyright to this source code.  In place of
  5.  ** a legal notice, here is a blessing:
  6.  **
  7.  **    May you do good and not evil.
  8.  **    May you find forgiveness for yourself and forgive others.
  9.  **    May you share freely, never taking more than you give.
  10.  **/
  11.  
  12. /*
  13.  * 2011 February 16
  14.  *
  15.  * This source code is based on the work of Scaevolus (see notice above).
  16.  * It has been slightly modified by Mojang AB (constants instead of magic
  17.  * numbers, a chunk timestamp header, and auto-formatted according to our
  18.  * formatter template).
  19.  *
  20.  */
  21.  
  22. // Interfaces with region files on the disk
  23.  
  24. /*
  25.  
  26.  Region File Format
  27.  
  28.  Concept: The minimum unit of storage on hard drives is 4KB. 90% of Minecraft
  29.  chunks are smaller than 4KB. 99% are smaller than 8KB. Write a simple
  30.  container to store chunks in single files in runs of 4KB sectors.
  31.  
  32.  Each region file represents a 32x32 group of chunks. The conversion from
  33.  chunk number to region number is floor(coord / 32): a chunk at (30, -3)
  34.  would be in region (0, -1), and one at (70, -30) would be at (3, -1).
  35.  Region files are named "r.x.z.data", where x and z are the region coordinates.
  36.  
  37.  A region file begins with a 4KB header that describes where chunks are stored
  38.  in the file. A 4-byte big-endian integer represents sector offsets and sector
  39.  counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the
  40.  file. The bottom byte of the chunk offset indicates the number of sectors the
  41.  chunk takes up, and the top 3 bytes represent the sector number of the chunk.
  42.  Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up
  43.  at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk
  44.  offset is 0, the corresponding chunk is not stored in the region file.
  45.  
  46.  Chunk data begins with a 4-byte big-endian integer representing the chunk data
  47.  length in bytes, not counting the length field. The length must be smaller than
  48.  4096 times the number of sectors. The next byte is a version field, to allow
  49.  backwards-compatible updates to how chunks are encoded.
  50.  
  51.  A version of 1 represents a gzipped NBT file. The gzipped data is the chunk
  52.  length - 1.
  53.  
  54.  A version of 2 represents a deflated (zlib compressed) NBT file. The deflated
  55.  data is the chunk length - 1.
  56.  
  57.  */
  58.  
  59. import java.io.*;
  60. import java.util.ArrayList;
  61. import java.util.zip.*;
  62.  
  63. public class RegionFile {
  64.  
  65.     private static final int VERSION_GZIP = 1;
  66.     private static final int VERSION_DEFLATE = 2;
  67.  
  68.     private static final int SECTOR_BYTES = 4096;
  69.     private static final int SECTOR_INTS = SECTOR_BYTES / 4;
  70.  
  71.     static final int CHUNK_HEADER_SIZE = 5;
  72.     private static final byte emptySector[] = new byte[4096];
  73.  
  74.     private final File fileName;
  75.     private RandomAccessFile file;
  76.     private final int offsets[];
  77.     private final int chunkTimestamps[];
  78.     private ArrayList<Boolean> sectorFree;
  79.     private int sizeDelta;
  80.     private long lastModified = 0;
  81.  
  82.     public RegionFile(File path) {
  83.         offsets = new int[SECTOR_INTS];
  84.         chunkTimestamps = new int[SECTOR_INTS];
  85.  
  86.         fileName = path;
  87.         debugln("REGION LOAD " + fileName);
  88.  
  89.         sizeDelta = 0;
  90.  
  91.         try {
  92.             if (path.exists()) {
  93.                 lastModified = path.lastModified();
  94.             }
  95.  
  96.             file = new RandomAccessFile(path, "rw");
  97.  
  98.             if (file.length() < SECTOR_BYTES) {
  99.                 /* we need to write the chunk offset table */
  100.                 for (int i = 0; i < SECTOR_INTS; ++i) {
  101.                     file.writeInt(0);
  102.                 }
  103.                 // write another sector for the timestamp info
  104.                 for (int i = 0; i < SECTOR_INTS; ++i) {
  105.                     file.writeInt(0);
  106.                 }
  107.  
  108.                 sizeDelta += SECTOR_BYTES * 2;
  109.             }
  110.  
  111.             if ((file.length() & 0xfff) != 0) {
  112.                 /* the file size is not a multiple of 4KB, grow it */
  113.                 for (int i = 0; i < (file.length() & 0xfff); ++i) {
  114.                     file.write((byte) 0);
  115.                 }
  116.             }
  117.  
  118.             /* set up the available sector map */
  119.             int nSectors = (int) file.length() / SECTOR_BYTES;
  120.             sectorFree = new ArrayList<Boolean>(nSectors);
  121.  
  122.             for (int i = 0; i < nSectors; ++i) {
  123.                 sectorFree.add(true);
  124.             }
  125.  
  126.             sectorFree.set(0, false); // chunk offset table
  127.             sectorFree.set(1, false); // for the last modified info
  128.  
  129.             file.seek(0);
  130.             for (int i = 0; i < SECTOR_INTS; ++i) {
  131.                 int offset = file.readInt();
  132.                 offsets[i] = offset;
  133.                 if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.size()) {
  134.                     for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) {
  135.                         sectorFree.set((offset >> 8) + sectorNum, false);
  136.                     }
  137.                 }
  138.             }
  139.             for (int i = 0; i < SECTOR_INTS; ++i) {
  140.                 int lastModValue = file.readInt();
  141.                 chunkTimestamps[i] = lastModValue;
  142.             }
  143.         } catch (IOException e) {
  144.             e.printStackTrace();
  145.         }
  146.     }
  147.  
  148.     /* the modification date of the region file when it was first opened */
  149.     public long lastModified() {
  150.         return lastModified;
  151.     }
  152.  
  153.     /* gets how much the region file has grown since it was last checked */
  154.     public synchronized int getSizeDelta() {
  155.         int ret = sizeDelta;
  156.         sizeDelta = 0;
  157.         return ret;
  158.     }
  159.  
  160.     // various small debug printing helpers
  161.     private void debug(String in) {
  162. //        System.out.print(in);
  163.     }
  164.  
  165.     private void debugln(String in) {
  166.         debug(in + "\n");
  167.     }
  168.  
  169.     private void debug(String mode, int x, int z, String in) {
  170.         debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] = " + in);
  171.     }
  172.  
  173.     private void debug(String mode, int x, int z, int count, String in) {
  174.         debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] " + count + "B = " + in);
  175.     }
  176.  
  177.     private void debugln(String mode, int x, int z, String in) {
  178.         debug(mode, x, z, in + "\n");
  179.     }
  180.  
  181.     /*
  182.      * gets an (uncompressed) stream representing the chunk data returns null if
  183.      * the chunk is not found or an error occurs
  184.      */
  185.     public synchronized DataInputStream getChunkDataInputStream(int x, int z) {
  186.         if (outOfBounds(x, z)) {
  187.             debugln("READ", x, z, "out of bounds");
  188.             return null;
  189.         }
  190.  
  191.         try {
  192.             int offset = getOffset(x, z);
  193.             if (offset == 0) {
  194.                 // debugln("READ", x, z, "miss");
  195.                 return null;
  196.             }
  197.  
  198.             int sectorNumber = offset >> 8;
  199.             int numSectors = offset & 0xFF;
  200.  
  201.             if (sectorNumber + numSectors > sectorFree.size()) {
  202.                 debugln("READ", x, z, "invalid sector");
  203.                 return null;
  204.             }
  205.  
  206.             file.seek(sectorNumber * SECTOR_BYTES);
  207.             int length = file.readInt();
  208.  
  209.             if (length > SECTOR_BYTES * numSectors) {
  210.                 debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
  211.                 return null;
  212.             }
  213.  
  214.             byte version = file.readByte();
  215.             if (version == VERSION_GZIP) {
  216.                 byte[] data = new byte[length - 1];
  217.                 file.read(data);
  218.                 DataInputStream ret = new DataInputStream(new GZIPInputStream(new ByteArrayInputStream(data)));
  219.                 // debug("READ", x, z, " = found");
  220.                 return ret;
  221.             } else if (version == VERSION_DEFLATE) {
  222.                 byte[] data = new byte[length - 1];
  223.                 file.read(data);
  224.                 DataInputStream ret = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
  225.                 // debug("READ", x, z, " = found");
  226.                 return ret;
  227.             }
  228.  
  229.             debugln("READ", x, z, "unknown version " + version);
  230.             return null;
  231.         } catch (IOException e) {
  232.             debugln("READ", x, z, "exception");
  233.             return null;
  234.         }
  235.     }
  236.  
  237.     public DataOutputStream getChunkDataOutputStream(int x, int z) {
  238.         if (outOfBounds(x, z)) return null;
  239.  
  240.         return new DataOutputStream(new DeflaterOutputStream(new ChunkBuffer(x, z)));
  241.     }
  242.  
  243.     /*
  244.      * lets chunk writing be multithreaded by not locking the whole file as a
  245.      * chunk is serializing -- only writes when serialization is over
  246.      */
  247.     class ChunkBuffer extends ByteArrayOutputStream {
  248.         private int x, z;
  249.  
  250.         public ChunkBuffer(int x, int z) {
  251.             super(8096); // initialize to 8KB
  252.             this.x = x;
  253.             this.z = z;
  254.         }
  255.  
  256.         public void close() {
  257.             RegionFile.this.write(x, z, buf, count);
  258.         }
  259.     }
  260.  
  261.     /* write a chunk at (x,z) with length bytes of data to disk */
  262.     protected synchronized void write(int x, int z, byte[] data, int length) {
  263.         try {
  264.             int offset = getOffset(x, z);
  265.             int sectorNumber = offset >> 8;
  266.             int sectorsAllocated = offset & 0xFF;
  267.             int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1;
  268.  
  269.             // maximum chunk size is 1MB
  270.             if (sectorsNeeded >= 256) {
  271.                 return;
  272.             }
  273.  
  274.             if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded) {
  275.                 /* we can simply overwrite the old sectors */
  276.                 debug("SAVE", x, z, length, "rewrite");
  277.                 write(sectorNumber, data, length);
  278.             } else {
  279.                 /* we need to allocate new sectors */
  280.  
  281.                 /* mark the sectors previously used for this chunk as free */
  282.                 for (int i = 0; i < sectorsAllocated; ++i) {
  283.                     sectorFree.set(sectorNumber + i, true);
  284.                 }
  285.  
  286.                 /* scan for a free space large enough to store this chunk */
  287.                 int runStart = sectorFree.indexOf(true);
  288.                 int runLength = 0;
  289.                 if (runStart != -1) {
  290.                     for (int i = runStart; i < sectorFree.size(); ++i) {
  291.                         if (runLength != 0) {
  292.                             if (sectorFree.get(i)) runLength++;
  293.                             else runLength = 0;
  294.                         } else if (sectorFree.get(i)) {
  295.                             runStart = i;
  296.                             runLength = 1;
  297.                         }
  298.                         if (runLength >= sectorsNeeded) {
  299.                             break;
  300.                         }
  301.                     }
  302.                 }
  303.  
  304.                 if (runLength >= sectorsNeeded) {
  305.                     /* we found a free space large enough */
  306.                     debug("SAVE", x, z, length, "reuse");
  307.                     sectorNumber = runStart;
  308.                     setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
  309.                     for (int i = 0; i < sectorsNeeded; ++i) {
  310.                         sectorFree.set(sectorNumber + i, false);
  311.                     }
  312.                     write(sectorNumber, data, length);
  313.                 } else {
  314.                     /*
  315.                      * no free space large enough found -- we need to grow the
  316.                      * file
  317.                      */
  318.                     debug("SAVE", x, z, length, "grow");
  319.                     file.seek(file.length());
  320.                     sectorNumber = sectorFree.size();
  321.                     for (int i = 0; i < sectorsNeeded; ++i) {
  322.                         file.write(emptySector);
  323.                         sectorFree.add(false);
  324.                     }
  325.                     sizeDelta += SECTOR_BYTES * sectorsNeeded;
  326.  
  327.                     write(sectorNumber, data, length);
  328.                     setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
  329.                 }
  330.             }
  331.             setTimestamp(x, z, (int) (System.currentTimeMillis() / 1000L));
  332.         } catch (IOException e) {
  333.             e.printStackTrace();
  334.         }
  335.     }
  336.  
  337.     /* write a chunk data to the region file at specified sector number */
  338.     private void write(int sectorNumber, byte[] data, int length) throws IOException {
  339.         debugln(" " + sectorNumber);
  340.         file.seek(sectorNumber * SECTOR_BYTES);
  341.         file.writeInt(length + 1); // chunk length
  342.         file.writeByte(VERSION_DEFLATE); // chunk version number
  343.         file.write(data, 0, length); // chunk data
  344.     }
  345.  
  346.     /* is this an invalid chunk coordinate? */
  347.     private boolean outOfBounds(int x, int z) {
  348.         return x < 0 || x >= 32 || z < 0 || z >= 32;
  349.     }
  350.  
  351.     private int getOffset(int x, int z) {
  352.         return offsets[x + z * 32];
  353.     }
  354.  
  355.     public boolean hasChunk(int x, int z) {
  356.         return getOffset(x, z) != 0;
  357.     }
  358.  
  359.     private void setOffset(int x, int z, int offset) throws IOException {
  360.         offsets[x + z * 32] = offset;
  361.         file.seek((x + z * 32) * 4);
  362.         file.writeInt(offset);
  363.     }
  364.  
  365.     private void setTimestamp(int x, int z, int value) throws IOException {
  366.         chunkTimestamps[x + z * 32] = value;
  367.         file.seek(SECTOR_BYTES + (x + z * 32) * 4);
  368.         file.writeInt(value);
  369.     }
  370.  
  371.     public void close() throws IOException {
  372.         file.close();
  373.     }
  374. }
RAW Paste Data
Top