    001package net.minecraft.world.chunk.storage;
    002
    003import java.io.BufferedInputStream;
    004import java.io.ByteArrayInputStream;
    005import java.io.DataInputStream;
    006import java.io.DataOutputStream;
    007import java.io.File;
    008import java.io.IOException;
    009import java.io.RandomAccessFile;
    010import java.util.ArrayList;
    011import java.util.zip.DeflaterOutputStream;
    012import java.util.zip.GZIPInputStream;
    013import java.util.zip.InflaterInputStream;
    014
    015public class RegionFile
    016{
    017    private static final byte[] emptySector = new byte[4096];
    018    private final File fileName;
    019    private RandomAccessFile dataFile;
    020    private final int[] offsets = new int[1024];
    021    private final int[] chunkTimestamps = new int[1024];
    022    private ArrayList sectorFree;
    023
    024    /** McRegion sizeDelta */
    025    private int sizeDelta;
    026    private long lastModified = 0L;
    027
    028    public RegionFile(File par1File)
    029    {
    030        this.fileName = par1File;
    031        this.sizeDelta = 0;
    032
    033        try
    034        {
    035            if (par1File.exists())
    036            {
    037                this.lastModified = par1File.lastModified();
    038            }
    039
    040            this.dataFile = new RandomAccessFile(par1File, "rw");
    041            int i;
    042
    043            if (this.dataFile.length() < 4096L)
    044            {
    045                for (i = 0; i < 1024; ++i)
    046                {
    047                    this.dataFile.writeInt(0);
    048                }
    049
    050                for (i = 0; i < 1024; ++i)
    051                {
    052                    this.dataFile.writeInt(0);
    053                }
    054
    055                this.sizeDelta += 8192;
    056            }
    057
    058            if ((this.dataFile.length() & 4095L) != 0L)
    059            {
    060                for (i = 0; (long)i < (this.dataFile.length() & 4095L); ++i)
    061                {
    062                    this.dataFile.write(0);
    063                }
    064            }
    065
    066            i = (int)this.dataFile.length() / 4096;
    067            this.sectorFree = new ArrayList(i);
    068            int j;
    069
    070            for (j = 0; j < i; ++j)
    071            {
    072                this.sectorFree.add(Boolean.valueOf(true));
    073            }
    074
    075            this.sectorFree.set(0, Boolean.valueOf(false));
    076            this.sectorFree.set(1, Boolean.valueOf(false));
    077            this.dataFile.seek(0L);
    078            int k;
    079
    080            for (j = 0; j < 1024; ++j)
    081            {
    082                k = this.dataFile.readInt();
    083                this.offsets[j] = k;
    084
    085                if (k != 0 && (k >> 8) + (k & 255) <= this.sectorFree.size())
    086                {
    087                    for (int l = 0; l < (k & 255); ++l)
    088                    {
    089                        this.sectorFree.set((k >> 8) + l, Boolean.valueOf(false));
    090                    }
    091                }
    092            }
    093
    094            for (j = 0; j < 1024; ++j)
    095            {
    096                k = this.dataFile.readInt();
    097                this.chunkTimestamps[j] = k;
    098            }
    099        }
    100        catch (IOException ioexception)
    101        {
    102            ioexception.printStackTrace();
    103        }
    104    }
    105
    106    /**
    107     * args: x, y - get uncompressed chunk stream from the region file
    108     */
    109    public synchronized DataInputStream getChunkDataInputStream(int par1, int par2)
    110    {
    111        if (this.outOfBounds(par1, par2))
    112        {
    113            return null;
    114        }
    115        else
    116        {
    117            try
    118            {
    119                int k = this.getOffset(par1, par2);
    120
    121                if (k == 0)
    122                {
    123                    return null;
    124                }
    125                else
    126                {
    127                    int l = k >> 8;
    128                    int i1 = k & 255;
    129
    130                    if (l + i1 > this.sectorFree.size())
    131                    {
    132                        return null;
    133                    }
    134                    else
    135                    {
    136                        this.dataFile.seek((long)(l * 4096));
    137                        int j1 = this.dataFile.readInt();
    138
    139                        if (j1 > 4096 * i1)
    140                        {
    141                            return null;
    142                        }
    143                        else if (j1 <= 0)
    144                        {
    145                            return null;
    146                        }
    147                        else
    148                        {
    149                            byte b0 = this.dataFile.readByte();
    150                            byte[] abyte;
    151
    152                            if (b0 == 1)
    153                            {
    154                                abyte = new byte[j1 - 1];
    155                                this.dataFile.read(abyte);
    156                                return new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(abyte))));
    157                            }
    158                            else if (b0 == 2)
    159                            {
    160                                abyte = new byte[j1 - 1];
    161                                this.dataFile.read(abyte);
    162                                return new DataInputStream(new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(abyte))));
    163                            }
    164                            else
    165                            {
    166                                return null;
    167                            }
    168                        }
    169                    }
    170                }
    171            }
    172            catch (IOException ioexception)
    173            {
    174                return null;
    175            }
    176        }
    177    }
    178
    179    /**
    180     * args: x, z - get an output stream used to write chunk data, data is on disk when the returned stream is closed
    181     */
    182    public DataOutputStream getChunkDataOutputStream(int par1, int par2)
    183    {
    184        return this.outOfBounds(par1, par2) ? null : new DataOutputStream(new DeflaterOutputStream(new RegionFileChunkBuffer(this, par1, par2)));
    185    }
    186
    187    /**
    188     * args: x, z, data, length - write chunk data at (x, z) to disk
    189     */
    190    protected synchronized void write(int par1, int par2, byte[] par3ArrayOfByte, int par4)
    191    {
    192        try
    193        {
    194            int l = this.getOffset(par1, par2);
    195            int i1 = l >> 8;
    196            int j1 = l & 255;
    197            int k1 = (par4 + 5) / 4096 + 1;
    198
    199            if (k1 >= 256)
    200            {
    201                return;
    202            }
    203
    204            if (i1 != 0 && j1 == k1)
    205            {
    206                this.write(i1, par3ArrayOfByte, par4);
    207            }
    208            else
    209            {
    210                int l1;
    211
    212                for (l1 = 0; l1 < j1; ++l1)
    213                {
    214                    this.sectorFree.set(i1 + l1, Boolean.valueOf(true));
    215                }
    216
    217                l1 = this.sectorFree.indexOf(Boolean.valueOf(true));
    218                int i2 = 0;
    219                int j2;
    220
    221                if (l1 != -1)
    222                {
    223                    for (j2 = l1; j2 < this.sectorFree.size(); ++j2)
    224                    {
    225                        if (i2 != 0)
    226                        {
    227                            if (((Boolean)this.sectorFree.get(j2)).booleanValue())
    228                            {
    229                                ++i2;
    230                            }
    231                            else
    232                            {
    233                                i2 = 0;
    234                            }
    235                        }
    236                        else if (((Boolean)this.sectorFree.get(j2)).booleanValue())
    237                        {
    238                            l1 = j2;
    239                            i2 = 1;
    240                        }
    241
    242                        if (i2 >= k1)
    243                        {
    244                            break;
    245                        }
    246                    }
    247                }
    248
    249                if (i2 >= k1)
    250                {
    251                    i1 = l1;
    252                    this.setOffset(par1, par2, l1 << 8 | k1);
    253
    254                    for (j2 = 0; j2 < k1; ++j2)
    255                    {
    256                        this.sectorFree.set(i1 + j2, Boolean.valueOf(false));
    257                    }
    258
    259                    this.write(i1, par3ArrayOfByte, par4);
    260                }
    261                else
    262                {
    263                    this.dataFile.seek(this.dataFile.length());
    264                    i1 = this.sectorFree.size();
    265
    266                    for (j2 = 0; j2 < k1; ++j2)
    267                    {
    268                        this.dataFile.write(emptySector);
    269                        this.sectorFree.add(Boolean.valueOf(false));
    270                    }
    271
    272                    this.sizeDelta += 4096 * k1;
    273                    this.write(i1, par3ArrayOfByte, par4);
    274                    this.setOffset(par1, par2, i1 << 8 | k1);
    275                }
    276            }
    277
    278            this.setChunkTimestamp(par1, par2, (int)(System.currentTimeMillis() / 1000L));
    279        }
    280        catch (IOException ioexception)
    281        {
    282            ioexception.printStackTrace();
    283        }
    284    }
    285
    286    /**
    287     * args: sectorNumber, data, length - write the chunk data to this RegionFile
    288     */
    289    private void write(int par1, byte[] par2ArrayOfByte, int par3) throws IOException
    290    {
    291        this.dataFile.seek((long)(par1 * 4096));
    292        this.dataFile.writeInt(par3 + 1);
    293        this.dataFile.writeByte(2);
    294        this.dataFile.write(par2ArrayOfByte, 0, par3);
    295    }
    296
    297    /**
    298     * args: x, z - check region bounds
    299     */
    300    private boolean outOfBounds(int par1, int par2)
    301    {
    302        return par1 < 0 || par1 >= 32 || par2 < 0 || par2 >= 32;
    303    }
    304
    305    /**
    306     * args: x, y - get chunk's offset in region file
    307     */
    308    private int getOffset(int par1, int par2)
    309    {
    310        return this.offsets[par1 + par2 * 32];
    311    }
    312
    313    /**
    314     * args: x, z, - true if chunk has been saved / converted
    315     */
    316    public boolean isChunkSaved(int par1, int par2)
    317    {
    318        return this.getOffset(par1, par2) != 0;
    319    }
    320
    321    /**
    322     * args: x, z, offset - sets the chunk's offset in the region file
    323     */
    324    private void setOffset(int par1, int par2, int par3) throws IOException
    325    {
    326        this.offsets[par1 + par2 * 32] = par3;
    327        this.dataFile.seek((long)((par1 + par2 * 32) * 4));
    328        this.dataFile.writeInt(par3);
    329    }
    330
    331    /**
    332     * args: x, z, timestamp - sets the chunk's write timestamp
    333     */
    334    private void setChunkTimestamp(int par1, int par2, int par3) throws IOException
    335    {
    336        this.chunkTimestamps[par1 + par2 * 32] = par3;
    337        this.dataFile.seek((long)(4096 + (par1 + par2 * 32) * 4));
    338        this.dataFile.writeInt(par3);
    339    }
    340
    341    /**
    342     * close this RegionFile and prevent further writes
    343     */
    344    public void close() throws IOException
    345    {
    346        if (this.dataFile != null)
    347        {
    348            this.dataFile.close();
    349        }
    350    }
    351}