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 var2;
042
043            if (this.dataFile.length() < 4096L)
044            {
045                for (var2 = 0; var2 < 1024; ++var2)
046                {
047                    this.dataFile.writeInt(0);
048                }
049
050                for (var2 = 0; var2 < 1024; ++var2)
051                {
052                    this.dataFile.writeInt(0);
053                }
054
055                this.sizeDelta += 8192;
056            }
057
058            if ((this.dataFile.length() & 4095L) != 0L)
059            {
060                for (var2 = 0; (long)var2 < (this.dataFile.length() & 4095L); ++var2)
061                {
062                    this.dataFile.write(0);
063                }
064            }
065
066            var2 = (int)this.dataFile.length() / 4096;
067            this.sectorFree = new ArrayList(var2);
068            int var3;
069
070            for (var3 = 0; var3 < var2; ++var3)
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 var4;
079
080            for (var3 = 0; var3 < 1024; ++var3)
081            {
082                var4 = this.dataFile.readInt();
083                this.offsets[var3] = var4;
084
085                if (var4 != 0 && (var4 >> 8) + (var4 & 255) <= this.sectorFree.size())
086                {
087                    for (int var5 = 0; var5 < (var4 & 255); ++var5)
088                    {
089                        this.sectorFree.set((var4 >> 8) + var5, Boolean.valueOf(false));
090                    }
091                }
092            }
093
094            for (var3 = 0; var3 < 1024; ++var3)
095            {
096                var4 = this.dataFile.readInt();
097                this.chunkTimestamps[var3] = var4;
098            }
099        }
100        catch (IOException var6)
101        {
102            var6.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 var3 = this.getOffset(par1, par2);
120
121                if (var3 == 0)
122                {
123                    return null;
124                }
125                else
126                {
127                    int var4 = var3 >> 8;
128                    int var5 = var3 & 255;
129
130                    if (var4 + var5 > this.sectorFree.size())
131                    {
132                        return null;
133                    }
134                    else
135                    {
136                        this.dataFile.seek((long)(var4 * 4096));
137                        int var6 = this.dataFile.readInt();
138
139                        if (var6 > 4096 * var5)
140                        {
141                            return null;
142                        }
143                        else if (var6 <= 0)
144                        {
145                            return null;
146                        }
147                        else
148                        {
149                            byte var7 = this.dataFile.readByte();
150                            byte[] var8;
151
152                            if (var7 == 1)
153                            {
154                                var8 = new byte[var6 - 1];
155                                this.dataFile.read(var8);
156                                return new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(var8))));
157                            }
158                            else if (var7 == 2)
159                            {
160                                var8 = new byte[var6 - 1];
161                                this.dataFile.read(var8);
162                                return new DataInputStream(new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(var8))));
163                            }
164                            else
165                            {
166                                return null;
167                            }
168                        }
169                    }
170                }
171            }
172            catch (IOException var9)
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 var5 = this.getOffset(par1, par2);
195            int var6 = var5 >> 8;
196            int var7 = var5 & 255;
197            int var8 = (par4 + 5) / 4096 + 1;
198
199            if (var8 >= 256)
200            {
201                return;
202            }
203
204            if (var6 != 0 && var7 == var8)
205            {
206                this.write(var6, par3ArrayOfByte, par4);
207            }
208            else
209            {
210                int var9;
211
212                for (var9 = 0; var9 < var7; ++var9)
213                {
214                    this.sectorFree.set(var6 + var9, Boolean.valueOf(true));
215                }
216
217                var9 = this.sectorFree.indexOf(Boolean.valueOf(true));
218                int var10 = 0;
219                int var11;
220
221                if (var9 != -1)
222                {
223                    for (var11 = var9; var11 < this.sectorFree.size(); ++var11)
224                    {
225                        if (var10 != 0)
226                        {
227                            if (((Boolean)this.sectorFree.get(var11)).booleanValue())
228                            {
229                                ++var10;
230                            }
231                            else
232                            {
233                                var10 = 0;
234                            }
235                        }
236                        else if (((Boolean)this.sectorFree.get(var11)).booleanValue())
237                        {
238                            var9 = var11;
239                            var10 = 1;
240                        }
241
242                        if (var10 >= var8)
243                        {
244                            break;
245                        }
246                    }
247                }
248
249                if (var10 >= var8)
250                {
251                    var6 = var9;
252                    this.setOffset(par1, par2, var9 << 8 | var8);
253
254                    for (var11 = 0; var11 < var8; ++var11)
255                    {
256                        this.sectorFree.set(var6 + var11, Boolean.valueOf(false));
257                    }
258
259                    this.write(var6, par3ArrayOfByte, par4);
260                }
261                else
262                {
263                    this.dataFile.seek(this.dataFile.length());
264                    var6 = this.sectorFree.size();
265
266                    for (var11 = 0; var11 < var8; ++var11)
267                    {
268                        this.dataFile.write(emptySector);
269                        this.sectorFree.add(Boolean.valueOf(false));
270                    }
271
272                    this.sizeDelta += 4096 * var8;
273                    this.write(var6, par3ArrayOfByte, par4);
274                    this.setOffset(par1, par2, var6 << 8 | var8);
275                }
276            }
277
278            this.setChunkTimestamp(par1, par2, (int)(System.currentTimeMillis() / 1000L));
279        }
280        catch (IOException var12)
281        {
282            var12.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}