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}