2026-03-01 12:16:08 +08:00
# include "stdafx.h"
# include "System.h"
# include "InputOutputStream.h"
# include "File.h"
# include "RegionFile.h"
# include "ConsoleSaveFile.h"
byteArray RegionFile : : emptySector ( SECTOR_BYTES ) ;
RegionFile : : RegionFile ( ConsoleSaveFile * saveFile , File * path )
{
_lastModified = 0 ;
m_saveFile = saveFile ;
offsets = new int [ SECTOR_INTS ] ;
memset ( offsets , 0 , SECTOR_BYTES ) ;
chunkTimestamps = new int [ SECTOR_INTS ] ;
memset ( chunkTimestamps , 0 , SECTOR_BYTES ) ;
2026-03-02 17:39:35 +07:00
/* 4J Jev, using files instead of strings:
2026-03-01 12:16:08 +08:00
strncpy ( fileName , path , MAX_PATH_SIZE ) ; */
fileName = path ;
// debugln("REGION LOAD " + fileName);
sizeDelta = 0 ;
// 4J - removed try/catch
// try {
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
/* 4J - Removed as _lastModifed not used and this is always failing as checking wrong thing
if ( path - > exists ( ) )
{
_lastModified = path - > lastModified ( ) ;
}
*/
fileEntry = m_saveFile - > createFile ( fileName - > getName ( ) ) ;
m_saveFile - > setFilePointer ( fileEntry , 0 , NULL , FILE_END ) ;
if ( fileEntry - > getFileSize ( ) < SECTOR_BYTES )
{
// 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.
// This now happens in insertInitialSectors when we do a first write to the region
2026-03-02 17:39:35 +07:00
m_bIsEmpty = true ;
2026-03-01 12:16:08 +08:00
sizeDelta + = SECTOR_BYTES * 2 ;
}
else
{
m_bIsEmpty = false ;
}
//if ((GetFileSize(file,NULL) & 0xfff) != 0)
if ( ( fileEntry - > getFileSize ( ) & 0xfff ) ! = 0 )
{
//byte zero = 0;
DWORD numberOfBytesWritten = 0 ;
DWORD bytesToWrite = 0x1000 - ( fileEntry - > getFileSize ( ) & 0xfff ) ;
byte * zeroBytes = new byte [ bytesToWrite ] ;
ZeroMemory ( zeroBytes , bytesToWrite ) ;
/* the file size is not a multiple of 4KB, grow it */
m_saveFile - > writeFile ( fileEntry , zeroBytes , bytesToWrite , & numberOfBytesWritten ) ;
delete [ ] zeroBytes ;
}
/* set up the available sector map */
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
int nSectors ;
if ( m_bIsEmpty ) // 4J - added this case for our empty files that we now don't create
{
nSectors = 2 ;
}
else
{
nSectors = ( int ) fileEntry - > getFileSize ( ) / SECTOR_BYTES ;
}
sectorFree = new vector < bool > ;
sectorFree - > reserve ( nSectors ) ;
for ( int i = 0 ; i < nSectors ; + + i )
{
sectorFree - > push_back ( true ) ;
}
sectorFree - > at ( 0 ) = false ; // chunk offset table
sectorFree - > at ( 1 ) = false ; // for the last modified info
m_saveFile - > setFilePointer ( fileEntry , 0 , NULL , FILE_BEGIN ) ;
for ( int i = 0 ; i < SECTOR_INTS ; + + i )
{
unsigned int offset = 0 ;
DWORD numberOfBytesRead = 0 ;
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
{
m_saveFile - > readFile ( fileEntry , & offset , 4 , & numberOfBytesRead ) ;
if ( saveFile - > isSaveEndianDifferent ( ) ) System : : ReverseULONG ( & offset ) ;
}
offsets [ i ] = offset ;
if ( offset ! = 0 & & ( offset > > 8 ) + ( offset & 0xFF ) < = sectorFree - > size ( ) )
{
for ( unsigned int sectorNum = 0 ; sectorNum < ( offset & 0xFF ) ; + + sectorNum )
{
sectorFree - > at ( ( offset > > 8 ) + sectorNum ) = false ;
}
}
}
for ( int i = 0 ; i < SECTOR_INTS ; + + i )
{
int lastModValue = 0 ;
DWORD numberOfBytesRead = 0 ;
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
{
m_saveFile - > readFile ( fileEntry , & lastModValue , 4 , & numberOfBytesRead ) ;
if ( saveFile - > isSaveEndianDifferent ( ) ) System : : ReverseINT ( & lastModValue ) ;
}
chunkTimestamps [ i ] = lastModValue ;
}
// } catch (IOException e) {
// e.printStackTrace();
// }
}
void RegionFile : : writeAllOffsets ( ) // used for the file ConsoleSaveFile conversion between platforms
{
2026-03-02 17:39:35 +07:00
if ( m_bIsEmpty = = false )
2026-03-01 12:16:08 +08:00
{
// save all the offsets and timestamps
m_saveFile - > LockSaveAccess ( ) ;
DWORD numberOfBytesWritten = 0 ;
m_saveFile - > setFilePointer ( fileEntry , 0 , NULL , FILE_BEGIN ) ;
m_saveFile - > writeFile ( fileEntry , offsets , SECTOR_BYTES , & numberOfBytesWritten ) ;
numberOfBytesWritten = 0 ;
m_saveFile - > setFilePointer ( fileEntry , SECTOR_BYTES , NULL , FILE_BEGIN ) ;
m_saveFile - > writeFile ( fileEntry , chunkTimestamps , SECTOR_BYTES , & numberOfBytesWritten ) ;
m_saveFile - > ReleaseSaveAccess ( ) ;
}
}
RegionFile : : ~ RegionFile ( )
{
delete [ ] offsets ;
delete [ ] chunkTimestamps ;
delete sectorFree ;
m_saveFile - > closeHandle ( fileEntry ) ;
}
2026-03-02 17:39:35 +07:00
__int64 RegionFile : : lastModified ( )
2026-03-01 12:16:08 +08:00
{
return _lastModified ;
}
int RegionFile : : getSizeDelta ( ) // TODO - was synchronized
{
int ret = sizeDelta ;
sizeDelta = 0 ;
return ret ;
}
DataInputStream * RegionFile : : getChunkDataInputStream ( int x , int z ) // TODO - was synchronized
{
if ( outOfBounds ( x , z ) )
{
// debugln("READ", x, z, "out of bounds");
return NULL ;
}
// 4J - removed try/catch
// try {
int offset = getOffset ( x , z ) ;
if ( offset = = 0 )
{
// debugln("READ", x, z, "miss");
return NULL ;
}
unsigned int sectorNumber = offset > > 8 ;
unsigned int numSectors = offset & 0xFF ;
if ( sectorNumber + numSectors > sectorFree - > size ( ) )
{
// debugln("READ", x, z, "invalid sector");
return NULL ;
}
m_saveFile - > LockSaveAccess ( ) ;
2026-03-02 17:39:35 +07:00
//SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
2026-03-01 12:16:08 +08:00
m_saveFile - > setFilePointer ( fileEntry , sectorNumber * SECTOR_BYTES , NULL , FILE_BEGIN ) ;
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
unsigned int length ;
unsigned int decompLength ;
unsigned int readDecompLength ;
DWORD numberOfBytesRead = 0 ;
// 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
// We store length and decompression length as ints, then length bytes of xbox LZX compressed data
m_saveFile - > readFile ( fileEntry , & length , 4 , & numberOfBytesRead ) ;
if ( m_saveFile - > isSaveEndianDifferent ( ) ) System : : ReverseULONG ( & length ) ;
// Using to bit of length to signify that this data was compressed with RLE method
bool useRLE = false ;
if ( length & 0x80000000 )
{
useRLE = true ;
length & = 0x7fffffff ;
}
m_saveFile - > readFile ( fileEntry , & decompLength , 4 , & numberOfBytesRead ) ;
if ( m_saveFile - > isSaveEndianDifferent ( ) ) System : : ReverseULONG ( & decompLength ) ;
if ( length > SECTOR_BYTES * numSectors )
{
// debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
m_saveFile - > ReleaseSaveAccess ( ) ;
return NULL ;
}
MemSect ( 50 ) ;
byte * data = new byte [ length ] ;
byte * decomp = new byte [ decompLength ] ;
MemSect ( 0 ) ;
readDecompLength = decompLength ;
m_saveFile - > readFile ( fileEntry , data , length , & numberOfBytesRead ) ;
m_saveFile - > ReleaseSaveAccess ( ) ;
Compression : : getCompression ( ) - > SetDecompressionType ( m_saveFile - > getSavePlatform ( ) ) ; // if this save is from another platform, set the correct decompression type
if ( useRLE )
{
Compression : : getCompression ( ) - > DecompressLZXRLE ( decomp , & readDecompLength , data , length ) ;
}
else
{
Compression : : getCompression ( ) - > Decompress ( decomp , & readDecompLength , data , length ) ;
}
Compression : : getCompression ( ) - > SetDecompressionType ( SAVE_FILE_PLATFORM_LOCAL ) ; // and then set the decompression back to the local machine's standard type
delete [ ] data ;
// 4J - was InflaterInputStream in here too, but we've already decompressed
2026-03-02 17:39:35 +07:00
DataInputStream * ret = new DataInputStream ( new ByteArrayInputStream ( byteArray ( decomp , readDecompLength ) ) ) ;
2026-03-01 12:16:08 +08:00
return ret ;
// } catch (IOException e) {
// debugln("READ", x, z, "exception");
// return null;
// }
}
DataOutputStream * RegionFile : : getChunkDataOutputStream ( int x , int z )
{
// 4J - was DeflatorOutputStream in here too, but we've already compressed
2026-03-02 17:39:35 +07:00
return new DataOutputStream ( new ChunkBuffer ( this , x , z ) ) ;
2026-03-01 12:16:08 +08:00
}
/* write a chunk at (x,z) with length bytes of data to disk */
void RegionFile : : write ( int x , int z , byte * data , int length ) // TODO - was synchronized
{
// 4J Stu - Do the compression here so that we know how much space we need to store the compressed data
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.
unsigned int compLength = length ;
Compression : : getCompression ( ) - > CompressLZXRLE ( compData , & compLength , data , length ) ;
int sectorsNeeded = ( compLength + CHUNK_HEADER_SIZE ) / SECTOR_BYTES + 1 ;
// app.DebugPrintf(">>>>>>>>>>>>>> writing compressed data for 0x%.8x, %d %d\n",fileEntry->data.regionIndex,x,z);
// maximum chunk size is 1MB
if ( sectorsNeeded > = 256 )
{
return ;
}
m_saveFile - > LockSaveAccess ( ) ;
{
int offset = getOffset ( x , z ) ;
int sectorNumber = offset > > 8 ;
int sectorsAllocated = offset & 0xFF ;
# ifndef _CONTENT_PACKAGE
if ( sectorNumber < 0 )
{
__debugbreak ( ) ;
}
# endif
if ( sectorNumber ! = 0 & & sectorsAllocated = = sectorsNeeded )
{
/* we can simply overwrite the old sectors */
// debug("SAVE", x, z, length, "rewrite");
# ifndef _CONTENT_PACKAGE
//wprintf(L"Writing chunk (%d,%d) in %ls from current sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
# endif
write ( sectorNumber , compData , length , compLength ) ;
}
else
{
/* we need to allocate new sectors */
/* mark the sectors previously used for this chunk as free */
for ( int i = 0 ; i < sectorsAllocated ; + + i )
{
sectorFree - > at ( sectorNumber + i ) = true ;
}
// 4J added - zero this now unused region of the file, so it can be better compressed until it is reused
zero ( sectorNumber , SECTOR_BYTES * sectorsAllocated ) ;
PIXBeginNamedEvent ( 0 , " Scanning for free space \n " ) ;
/* scan for a free space large enough to store this chunk */
int runStart = ( int ) ( find ( sectorFree - > begin ( ) , sectorFree - > end ( ) , true ) - sectorFree - > begin ( ) ) ; // 4J - was sectorFree.indexOf(true)
int runLength = 0 ;
if ( runStart ! = - 1 )
{
for ( unsigned int i = runStart ; i < sectorFree - > size ( ) ; + + i )
{
if ( runLength ! = 0 )
{
if ( sectorFree - > at ( i ) ) runLength + + ;
else runLength = 0 ;
} else if ( sectorFree - > at ( i ) )
{
runStart = i ;
runLength = 1 ;
}
if ( runLength > = sectorsNeeded )
{
break ;
}
}
}
PIXEndNamedEvent ( ) ;
if ( runLength > = sectorsNeeded )
{
/* we found a free space large enough */
// debug("SAVE", x, z, length, "reuse");
sectorNumber = runStart ;
setOffset ( x , z , ( sectorNumber < < 8 ) | sectorsNeeded ) ;
# ifndef _CONTENT_PACKAGE
//wprintf(L"Writing chunk (%d,%d) in %ls from old sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
# endif
for ( int i = 0 ; i < sectorsNeeded ; + + i )
{
sectorFree - > at ( sectorNumber + i ) = false ;
}
write ( sectorNumber , compData , length , compLength ) ;
}
else
{
PIXBeginNamedEvent ( 0 , " Expanding storage for %d sectors \n " , sectorsNeeded ) ;
/*
* no free space large enough found - - we need to grow the
* file
*/
// debug("SAVE", x, z, length, "grow");
2026-03-02 17:39:35 +07:00
//SetFilePointer(file,0,0,FILE_END);
2026-03-01 12:16:08 +08:00
m_saveFile - > setFilePointer ( fileEntry , 0 , NULL , FILE_END ) ;
sectorNumber = ( int ) sectorFree - > size ( ) ;
# ifndef _CONTENT_PACAKGE
//wprintf(L"Writing chunk (%d,%d) in %ls from new sector %d to %d\n", x,z, fileEntry->data.filename, sectorNumber, sectorNumber + sectorsNeeded - 1);
# endif
DWORD numberOfBytesWritten = 0 ;
for ( int i = 0 ; i < sectorsNeeded ; + + i )
{
//WriteFile(file,emptySector.data,SECTOR_BYTES,&numberOfBytesWritten,NULL);
m_saveFile - > writeFile ( fileEntry , emptySector . data , SECTOR_BYTES , & numberOfBytesWritten ) ;
sectorFree - > push_back ( false ) ;
}
sizeDelta + = SECTOR_BYTES * sectorsNeeded ;
write ( sectorNumber , compData , length , compLength ) ;
setOffset ( x , z , ( sectorNumber < < 8 ) | sectorsNeeded ) ;
PIXEndNamedEvent ( ) ;
}
}
setTimestamp ( x , z , ( int ) ( System : : currentTimeMillis ( ) / 1000L ) ) ;
}
m_saveFile - > ReleaseSaveAccess ( ) ;
// } catch (IOException e) {
// e.printStackTrace();
// }
}
/* write a chunk data to the region file at specified sector number */
void RegionFile : : write ( int sectorNumber , byte * data , int length , unsigned int compLength )
{
DWORD numberOfBytesWritten = 0 ;
2026-03-02 17:39:35 +07:00
//SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
2026-03-01 12:16:08 +08:00
m_saveFile - > setFilePointer ( fileEntry , sectorNumber * SECTOR_BYTES , NULL , FILE_BEGIN ) ;
// 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
// We store length and decompression length as ints, then length bytes of xbox LZX compressed data
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
// 4J Stu - We need to do the compression at a level above this, where it is checking for free space
compLength | = 0x80000000 ; // 4J - signify that this has been encoded with RLE method ( see code in getChunkDataInputStream() for matching detection of this)
m_saveFile - > writeFile ( fileEntry , & compLength , 4 , & numberOfBytesWritten ) ;
compLength & = 0x7fffffff ;
m_saveFile - > writeFile ( fileEntry , & length , 4 , & numberOfBytesWritten ) ;
m_saveFile - > writeFile ( fileEntry , data , compLength , & numberOfBytesWritten ) ;
delete [ ] data ;
}
void RegionFile : : zero ( int sectorNumber , int length )
{
DWORD numberOfBytesWritten = 0 ;
2026-03-02 17:39:35 +07:00
//SetFilePointer(file,sectorNumber * SECTOR_BYTES,0,FILE_BEGIN);
2026-03-01 12:16:08 +08:00
m_saveFile - > setFilePointer ( fileEntry , sectorNumber * SECTOR_BYTES , NULL , FILE_BEGIN ) ;
m_saveFile - > zeroFile ( fileEntry , length , & numberOfBytesWritten ) ;
}
/* is this an invalid chunk coordinate? */
bool RegionFile : : outOfBounds ( int x , int z )
{
return x < 0 | | x > = 32 | | z < 0 | | z > = 32 ;
}
int RegionFile : : getOffset ( int x , int z )
{
return offsets [ x + z * 32 ] ;
}
bool RegionFile : : hasChunk ( int x , int z )
{
return getOffset ( x , z ) ! = 0 ;
}
// 4J added - write the initial two sectors that used to be written in the ctor when the file was empty
void RegionFile : : insertInitialSectors ( )
{
m_saveFile - > setFilePointer ( fileEntry , 0 , NULL , FILE_BEGIN ) ;
DWORD numberOfBytesWritten = 0 ;
byte zeroBytes [ SECTOR_BYTES ] ;
ZeroMemory ( zeroBytes , SECTOR_BYTES ) ;
/* we need to write the chunk offset table */
m_saveFile - > writeFile ( fileEntry , zeroBytes , SECTOR_BYTES , & numberOfBytesWritten ) ;
// write another sector for the timestamp info
m_saveFile - > writeFile ( fileEntry , zeroBytes , SECTOR_BYTES , & numberOfBytesWritten ) ;
m_bIsEmpty = false ;
}
void RegionFile : : setOffset ( int x , int z , int offset )
{
if ( m_bIsEmpty )
{
insertInitialSectors ( ) ; // 4J added
}
DWORD numberOfBytesWritten = 0 ;
offsets [ x + z * 32 ] = offset ;
m_saveFile - > setFilePointer ( fileEntry , ( x + z * 32 ) * 4 , NULL , FILE_BEGIN ) ;
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
m_saveFile - > writeFile ( fileEntry , & offset , 4 , & numberOfBytesWritten ) ;
}
void RegionFile : : setTimestamp ( int x , int z , int value )
{
if ( m_bIsEmpty )
{
insertInitialSectors ( ) ; // 4J added
}
DWORD numberOfBytesWritten = 0 ;
chunkTimestamps [ x + z * 32 ] = value ;
m_saveFile - > setFilePointer ( fileEntry , SECTOR_BYTES + ( x + z * 32 ) * 4 , NULL , FILE_BEGIN ) ;
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
m_saveFile - > writeFile ( fileEntry , & value , 4 , & numberOfBytesWritten ) ;
}
void RegionFile : : close ( )
{
m_saveFile - > closeHandle ( fileEntry ) ;
}