the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2#include "System.h"
3#include "InputOutputStream.h"
4#include "File.h"
5#include "RegionFile.h"
6
7#include "ConsoleSaveFile.h"
8
9byteArray RegionFile::emptySector(SECTOR_BYTES);
10
11RegionFile::RegionFile(ConsoleSaveFile *saveFile, File *path)
12{
13 _lastModified = 0;
14
15 m_saveFile = saveFile;
16
17 offsets = new int[SECTOR_INTS];
18 memset(offsets,0,SECTOR_BYTES);
19 chunkTimestamps = new int[SECTOR_INTS];
20 memset(chunkTimestamps,0,SECTOR_BYTES);
21
22 /* 4J Jev, using files instead of strings:
23 strncpy(fileName,path,MAX_PATH_SIZE); */
24
25 fileName = path;
26
27// debugln("REGION LOAD " + fileName);
28
29 sizeDelta = 0;
30
31 // 4J - removed try/catch
32// try {
33
34 /* 4J - Removed as _lastModifed not used and this is always failing as checking wrong thing
35 if( path->exists() )
36 {
37 _lastModified = path->lastModified();
38 }
39 */
40
41 fileEntry = m_saveFile->createFile( fileName->getName() );
42 m_saveFile->setFilePointer( fileEntry, 0, NULL, FILE_END );
43
44 if ( fileEntry->getFileSize() < SECTOR_BYTES)
45 {
46 // 4J altered - the original code used to write out 2 empty sectors here, which we don't want to do as we might be at a point where we shouldn't be touching the save file.
47 // This now happens in insertInitialSectors when we do a first write to the region
48 m_bIsEmpty = true;
49
50 sizeDelta += SECTOR_BYTES * 2;
51 }
52 else
53 {
54 m_bIsEmpty = false;
55 }
56
57 //if ((GetFileSize(file,NULL) & 0xfff) != 0)
58 if ((fileEntry->getFileSize() & 0xfff) != 0)
59 {
60 //byte zero = 0;
61 DWORD numberOfBytesWritten = 0;
62 DWORD bytesToWrite = 0x1000 - (fileEntry->getFileSize() & 0xfff);
63 byte *zeroBytes = new byte[ bytesToWrite ];
64 ZeroMemory(zeroBytes, bytesToWrite);
65
66 /* the file size is not a multiple of 4KB, grow it */
67 m_saveFile->writeFile(fileEntry,zeroBytes,bytesToWrite,&numberOfBytesWritten);
68
69 delete [] zeroBytes;
70 }
71
72 /* set up the available sector map */
73
74 int nSectors;
75 if( m_bIsEmpty ) // 4J - added this case for our empty files that we now don't create
76 {
77 nSectors = 2;
78 }
79 else
80 {
81 nSectors = (int) fileEntry->getFileSize() / SECTOR_BYTES;
82 }
83 sectorFree = new vector<bool>;
84 sectorFree->reserve(nSectors);
85
86 for (int i = 0; i < nSectors; ++i)
87 {
88 sectorFree->push_back(true);
89 }
90
91 sectorFree->at(0) = false; // chunk offset table
92 sectorFree->at(1) = false; // for the last modified info
93
94 m_saveFile->setFilePointer( fileEntry, 0, NULL, FILE_BEGIN );
95 for (int i = 0; i < SECTOR_INTS; ++i)
96 {
97 unsigned int offset = 0;
98 DWORD numberOfBytesRead = 0;
99 if( !m_bIsEmpty ) // 4J added condition, don't read back if we've just created an empty file as we don't immediately write this anymore
100 {
101 m_saveFile->readFile(fileEntry, &offset, 4, &numberOfBytesRead);
102
103 if(saveFile->isSaveEndianDifferent()) System::ReverseULONG(&offset);
104
105 }
106 offsets[i] = offset;
107 if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree->size())
108 {
109 for (unsigned int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum)
110 {
111 sectorFree->at((offset >> 8) + sectorNum) = false;
112 }
113 }
114 }
115 for (int i = 0; i < SECTOR_INTS; ++i)
116 {
117 int lastModValue = 0;
118 DWORD numberOfBytesRead = 0;
119 if( !m_bIsEmpty ) // 4J added condition, don't read back if we've just created an empty file as we don't immediately write this anymore
120 {
121 m_saveFile->readFile(fileEntry, &lastModValue, 4, &numberOfBytesRead);
122
123 if(saveFile->isSaveEndianDifferent()) System::ReverseINT(&lastModValue);
124 }
125 chunkTimestamps[i] = lastModValue;
126 }
127
128
129// } catch (IOException e) {
130// e.printStackTrace();
131// }
132}
133
134void RegionFile::writeAllOffsets() // used for the file ConsoleSaveFile conversion between platforms
135{
136 if(m_bIsEmpty == false)
137 {
138 // save all the offsets and timestamps
139 m_saveFile->LockSaveAccess();
140
141 DWORD numberOfBytesWritten = 0;
142 m_saveFile->setFilePointer( fileEntry, 0, NULL, FILE_BEGIN );
143 m_saveFile->writeFile(fileEntry,offsets, SECTOR_BYTES ,&numberOfBytesWritten);
144
145 numberOfBytesWritten = 0;
146 m_saveFile->setFilePointer( fileEntry, SECTOR_BYTES, NULL, FILE_BEGIN );
147 m_saveFile->writeFile(fileEntry, chunkTimestamps, SECTOR_BYTES, &numberOfBytesWritten);
148
149 m_saveFile->ReleaseSaveAccess();
150 }
151
152}
153RegionFile::~RegionFile()
154{
155 delete[] offsets;
156 delete[] chunkTimestamps;
157 delete sectorFree;
158 m_saveFile->closeHandle( fileEntry );
159}
160
161__int64 RegionFile::lastModified()
162{
163 return _lastModified;
164}
165
166int RegionFile::getSizeDelta() // TODO - was synchronized
167{
168 int ret = sizeDelta;
169 sizeDelta = 0;
170 return ret;
171}
172
173DataInputStream *RegionFile::getChunkDataInputStream(int x, int z) // TODO - was synchronized
174{
175 if (outOfBounds(x, z))
176 {
177// debugln("READ", x, z, "out of bounds");
178 return NULL;
179 }
180
181 // 4J - removed try/catch
182// try {
183 int offset = getOffset(x, z);
184 if (offset == 0)
185 {
186 // debugln("READ", x, z, "miss");
187 return NULL;
188 }
189
190 unsigned int sectorNumber = offset >> 8;
191 unsigned int numSectors = offset & 0xFF;
192
193 if (sectorNumber + numSectors > sectorFree->size())
194 {
195// debugln("READ", x, z, "invalid sector");
196 return NULL;
197 }
198
199 m_saveFile->LockSaveAccess();
200
201 //SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
202 m_saveFile->setFilePointer( fileEntry, sectorNumber * SECTOR_BYTES, NULL, FILE_BEGIN);
203
204 unsigned int length;
205 unsigned int decompLength;
206 unsigned int readDecompLength;
207
208 DWORD numberOfBytesRead = 0;
209
210 // 4J - this differs a bit from the java file format. Java has length stored as an int, then a type as a byte, then length-1 bytes of data
211 // We store length and decompression length as ints, then length bytes of xbox LZX compressed data
212 m_saveFile->readFile(fileEntry,&length,4,&numberOfBytesRead);
213
214 if(m_saveFile->isSaveEndianDifferent()) System::ReverseULONG(&length);
215
216 // Using to bit of length to signify that this data was compressed with RLE method
217 bool useRLE = false;
218 if( length & 0x80000000 )
219 {
220 useRLE = true;
221 length &= 0x7fffffff;
222 }
223 m_saveFile->readFile(fileEntry,&decompLength,4,&numberOfBytesRead);
224
225 if(m_saveFile->isSaveEndianDifferent()) System::ReverseULONG(&decompLength);
226
227 if (length > SECTOR_BYTES * numSectors)
228 {
229// debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
230
231 m_saveFile->ReleaseSaveAccess();
232 return NULL;
233 }
234
235 MemSect(50);
236 byte *data = new byte[length];
237 byte *decomp = new byte[decompLength];
238 MemSect(0);
239 readDecompLength = decompLength;
240 m_saveFile->readFile(fileEntry,data,length,&numberOfBytesRead);
241
242 m_saveFile->ReleaseSaveAccess();
243
244 Compression::getCompression()->SetDecompressionType(m_saveFile->getSavePlatform()); // if this save is from another platform, set the correct decompression type
245
246 if( useRLE )
247 {
248 Compression::getCompression()->DecompressLZXRLE(decomp, &readDecompLength, data, length );
249 }
250 else
251 {
252 Compression::getCompression()->Decompress(decomp, &readDecompLength, data, length );
253 }
254
255 Compression::getCompression()->SetDecompressionType(SAVE_FILE_PLATFORM_LOCAL); // and then set the decompression back to the local machine's standard type
256
257 delete [] data;
258
259 // 4J - was InflaterInputStream in here too, but we've already decompressed
260 DataInputStream *ret = new DataInputStream(new ByteArrayInputStream( byteArray( decomp, readDecompLength) ));
261 return ret;
262
263// } catch (IOException e) {
264// debugln("READ", x, z, "exception");
265// return null;
266// }
267}
268
269DataOutputStream *RegionFile::getChunkDataOutputStream(int x, int z)
270{
271 // 4J - was DeflatorOutputStream in here too, but we've already compressed
272 return new DataOutputStream( new ChunkBuffer(this, x, z));
273}
274
275/* write a chunk at (x,z) with length bytes of data to disk */
276void RegionFile::write(int x, int z, byte *data, int length) // TODO - was synchronized
277{
278 // 4J Stu - Do the compression here so that we know how much space we need to store the compressed data
279 byte *compData = new byte[length + 2048]; // presuming compression is going to make this smaller... UPDATE - for some really small things this isn't the case. Added 2K on here to cover those.
280 unsigned int compLength = length;
281 Compression::getCompression()->CompressLZXRLE(compData,&compLength,data,length);
282
283 int sectorsNeeded = (compLength + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1;
284
285// app.DebugPrintf(">>>>>>>>>>>>>> writing compressed data for 0x%.8x, %d %d\n",fileEntry->data.regionIndex,x,z);
286
287 // maximum chunk size is 1MB
288 if (sectorsNeeded >= 256)
289 {
290 return;
291 }
292
293 m_saveFile->LockSaveAccess();
294 {
295 int offset = getOffset(x, z);
296 int sectorNumber = offset >> 8;
297 int sectorsAllocated = offset & 0xFF;
298
299#ifndef _CONTENT_PACKAGE
300 if(sectorNumber < 0)
301 {
302 __debugbreak();
303 }
304#endif
305
306 if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded)
307 {
308 /* we can simply overwrite the old sectors */
309 // debug("SAVE", x, z, length, "rewrite");
310 #ifndef _CONTENT_PACKAGE
311 //wprintf(L"Writing chunk (%d,%d) in %ls from current sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
312 #endif
313 write(sectorNumber, compData, length, compLength);
314 }
315 else
316 {
317 /* we need to allocate new sectors */
318
319 /* mark the sectors previously used for this chunk as free */
320 for (int i = 0; i < sectorsAllocated; ++i)
321 {
322 sectorFree->at(sectorNumber + i) = true;
323 }
324 // 4J added - zero this now unused region of the file, so it can be better compressed until it is reused
325 zero(sectorNumber, SECTOR_BYTES * sectorsAllocated);
326
327 PIXBeginNamedEvent(0,"Scanning for free space\n");
328 /* scan for a free space large enough to store this chunk */
329 int runStart = (int)(find(sectorFree->begin(),sectorFree->end(),true) - sectorFree->begin()); // 4J - was sectorFree.indexOf(true)
330 int runLength = 0;
331 if (runStart != -1)
332 {
333 for (unsigned int i = runStart; i < sectorFree->size(); ++i)
334 {
335 if (runLength != 0)
336 {
337 if (sectorFree->at(i)) runLength++;
338 else runLength = 0;
339 } else if (sectorFree->at(i))
340 {
341 runStart = i;
342 runLength = 1;
343 }
344 if (runLength >= sectorsNeeded)
345 {
346 break;
347 }
348 }
349 }
350 PIXEndNamedEvent();
351
352 if (runLength >= sectorsNeeded)
353 {
354 /* we found a free space large enough */
355 // debug("SAVE", x, z, length, "reuse");
356 sectorNumber = runStart;
357 setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
358 #ifndef _CONTENT_PACKAGE
359 //wprintf(L"Writing chunk (%d,%d) in %ls from old sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
360 #endif
361 for (int i = 0; i < sectorsNeeded; ++i)
362 {
363 sectorFree->at(sectorNumber + i) = false;
364 }
365 write(sectorNumber, compData, length, compLength);
366 }
367 else
368 {
369 PIXBeginNamedEvent(0,"Expanding storage for %d sectors\n", sectorsNeeded);
370 /*
371 * no free space large enough found -- we need to grow the
372 * file
373 */
374 // debug("SAVE", x, z, length, "grow");
375 //SetFilePointer(file,0,0,FILE_END);
376 m_saveFile->setFilePointer( fileEntry, 0, NULL, FILE_END );
377
378 sectorNumber = (int)sectorFree->size();
379 #ifndef _CONTENT_PACAKGE
380 //wprintf(L"Writing chunk (%d,%d) in %ls from new sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
381 #endif
382 DWORD numberOfBytesWritten = 0;
383 for (int i = 0; i < sectorsNeeded; ++i)
384 {
385 //WriteFile(file,emptySector.data,SECTOR_BYTES,&numberOfBytesWritten,NULL);
386 m_saveFile->writeFile(fileEntry,emptySector.data,SECTOR_BYTES,&numberOfBytesWritten);
387 sectorFree->push_back(false);
388 }
389 sizeDelta += SECTOR_BYTES * sectorsNeeded;
390
391 write(sectorNumber, compData, length, compLength);
392 setOffset(x, z, (sectorNumber << 8) | sectorsNeeded);
393 PIXEndNamedEvent();
394 }
395 }
396 setTimestamp(x, z, (int) (System::currentTimeMillis() / 1000L));
397 }
398 m_saveFile->ReleaseSaveAccess();
399
400// } catch (IOException e) {
401// e.printStackTrace();
402// }
403}
404
405/* write a chunk data to the region file at specified sector number */
406void RegionFile::write(int sectorNumber, byte *data, int length, unsigned int compLength)
407{
408 DWORD numberOfBytesWritten = 0;
409 //SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
410 m_saveFile->setFilePointer( fileEntry, sectorNumber * SECTOR_BYTES, NULL, FILE_BEGIN );
411
412 // 4J - this differs a bit from the java file format. Java has length stored as an int, then a type as a byte, then length-1 bytes of data
413 // We store length and decompression length as ints, then length bytes of xbox LZX compressed data
414
415 // 4J Stu - We need to do the compression at a level above this, where it is checking for free space
416
417 compLength |= 0x80000000; // 4J - signify that this has been encoded with RLE method ( see code in getChunkDataInputStream() for matching detection of this)
418 m_saveFile->writeFile(fileEntry,&compLength,4,&numberOfBytesWritten);
419 compLength &= 0x7fffffff;
420 m_saveFile->writeFile(fileEntry,&length,4,&numberOfBytesWritten);
421 m_saveFile->writeFile(fileEntry,data,compLength,&numberOfBytesWritten);
422 delete[] data;
423}
424
425void RegionFile::zero(int sectorNumber, int length)
426{
427 DWORD numberOfBytesWritten = 0;
428 //SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
429 m_saveFile->setFilePointer( fileEntry, sectorNumber * SECTOR_BYTES, NULL, FILE_BEGIN );
430 m_saveFile->zeroFile( fileEntry, length, &numberOfBytesWritten );
431}
432
433/* is this an invalid chunk coordinate? */
434bool RegionFile::outOfBounds(int x, int z)
435{
436 return x < 0 || x >= 32 || z < 0 || z >= 32;
437}
438
439int RegionFile::getOffset(int x, int z)
440{
441 return offsets[x + z * 32];
442}
443
444bool RegionFile::hasChunk(int x, int z)
445{
446 return getOffset(x, z) != 0;
447}
448
449// 4J added - write the initial two sectors that used to be written in the ctor when the file was empty
450void RegionFile::insertInitialSectors()
451{
452 m_saveFile->setFilePointer( fileEntry, 0, NULL, FILE_BEGIN );
453 DWORD numberOfBytesWritten = 0;
454 byte zeroBytes[ SECTOR_BYTES ];
455 ZeroMemory(zeroBytes, SECTOR_BYTES);
456
457 /* we need to write the chunk offset table */
458 m_saveFile->writeFile(fileEntry,zeroBytes,SECTOR_BYTES,&numberOfBytesWritten);
459
460 // write another sector for the timestamp info
461 m_saveFile->writeFile(fileEntry,zeroBytes,SECTOR_BYTES,&numberOfBytesWritten);
462
463 m_bIsEmpty = false;
464}
465
466void RegionFile::setOffset(int x, int z, int offset)
467{
468 if( m_bIsEmpty )
469 {
470 insertInitialSectors(); // 4J added
471 }
472
473 DWORD numberOfBytesWritten = 0;
474 offsets[x + z * 32] = offset;
475 m_saveFile->setFilePointer( fileEntry, (x + z * 32) * 4, NULL, FILE_BEGIN );
476
477 m_saveFile->writeFile(fileEntry,&offset,4,&numberOfBytesWritten);
478}
479
480void RegionFile::setTimestamp(int x, int z, int value)
481{
482 if( m_bIsEmpty )
483 {
484 insertInitialSectors(); // 4J added
485 }
486
487 DWORD numberOfBytesWritten = 0;
488 chunkTimestamps[x + z * 32] = value;
489 m_saveFile->setFilePointer( fileEntry, SECTOR_BYTES + (x + z * 32) * 4, NULL, FILE_BEGIN );
490
491 m_saveFile->writeFile(fileEntry,&value,4,&numberOfBytesWritten);
492}
493
494void RegionFile::close()
495{
496 m_saveFile->closeHandle( fileEntry );
497}