This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
MineConvert/Xbox to Anvil/source/Main.ExpandX.cpp
Keith @ Killa Network edeb684560 Added XBox support
2015-11-05 18:54:13 +11:00

877 lines
24 KiB
C++

// ExpandX.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
// Chunk-specific variables
int iol, xpos, zpos, xstart, zstart, n;
int i, blockstart, datastart, blocklightstart, skylightstart, biomestart;
int y_plane, h;
int local_biome_list[256];
// Variables for file I/O
FILE *input_file_f, *output_file_f, *log_file_f;
char *afile_path, *aoutput_path, *status;
unsigned long file_size, bytes_read, template_length, section_template_length;
// All our memory handling
unsigned char *nbuffer, *xbuffer, *rbuffer, *ebuffer, *sbuffer, s_num;
unsigned char *biome_list;
int broken = 0, missing = -295;
// Contained in the Biomes.cpp file
void populate_biomes(unsigned char *biome_list);
// Takes an input int and flips it from Big to Little Endian or vice versa.
int EndianFlip(int _input_int)
{
int retval;
unsigned char *_input_ptr = (unsigned char *)&_input_int;
unsigned char *_p_retval = (unsigned char *)&retval;
for (int _i = 0; _i < 4; _i ++)
{
_p_retval[3 - _i] = _input_ptr[_i];
}
return retval;
}
// Find the offset of the Blocks section of the XBox file, in bytes
int BlockStart(unsigned char *_in_buffer, int _size)
{
int _i = 0;
// Loop through looking for the Blocks section
do
{
if (_in_buffer[_i] == 'B')
if (_in_buffer[_i + 1] == 'l')
if (_in_buffer[_i + 5] == 's')
return _i + 10;
_i ++;
} while (_i < _size);
return -1;
}
// Find the offset of the Biomes section of the XBox file, in bytes
int BiomeStart(unsigned char *_in_buffer, int _size)
{
int _i = 0;
// Loop through looking for the Blocks section
do
{
if (_in_buffer[_i] == 'B')
if (_in_buffer[_i + 1] == 'i')
if (_in_buffer[_i + 5] == 's')
return _i + 10;
_i ++;
} while (_i < _size);
return -1;
}
// Gets the higher order nybble of a byte
unsigned char HiNybble(unsigned char _in_byte)
{
return _in_byte >> 4;
}
// Gets the lower order nybble of a byte
unsigned char LoNybble(unsigned char _in_byte)
{
return _in_byte & 0xF;
}
// Gets the offset of the BlockLight section in the XBox file, in bytes
int BlockLight(unsigned char *_in_buffer, int _size)
{
int _i = 0;
// Loop through looking for the BlockLight section
do
{
if (_in_buffer[_i] == 'B')
if (_in_buffer[_i + 1] == 'l')
if (_in_buffer[_i + 7] == 'g')
if (_in_buffer[_i + 8] == 'h')
return _i + 14;
_i ++;
} while (_i < _size);
return -1;
}
// Gets the offset of the SkyLight section in the XBox file, in bytes
int SkyLight(unsigned char *_in_buffer, int _size)
{
int _i = 0;
// Loop through looking for the SkyLight section
do
{
if (_in_buffer[_i] == 'S')
if (_in_buffer[_i + 1] == 'k')
if (_in_buffer[_i + 5] == 'g')
if (_in_buffer[_i + 6] == 'h')
return _i + 12;
_i ++;
} while (_i < _size);
return -1;
}
// Takes a short and flips it from Big Endian to Little Endian or vice versa.
short EndianFlip(short _input_short)
{
short retval;
unsigned char *_input_ptr = (unsigned char *)&_input_short;
unsigned char *_p_retval = (unsigned char *)&retval;
for (int _i = 0; _i < 2; _i ++)
{
_p_retval[1 - _i] = _input_ptr[_i];
}
return retval;
}
// Gets the offset of the data section of the XBox file, in bytes
int DataStart(unsigned char *_in_buffer, int _size)
{
int _i = 0;
// Loop through looking for the Blocks section
do
{
if (_in_buffer[_i] == 'D')
if (_in_buffer[_i + 1] == 'a')
if (_in_buffer[_i + 3] == 'a')
return _i + 8;
_i ++;
} while (_i < _size);
return -1;
}
// This analyzes an NBT tag to make sure it is not broken.
// _out_buffer: pointer to the start of the tag in memory.
// _size: length of the buffer
// _tag_type: the tag type as defined by Minecraft
// returns: the total length of the tag, in bytes, or -1 if the tag is invalid.
int ProcessTag(unsigned char *_out_buffer, int _size, unsigned char _tag_type)
{
int _n = 0, _i;
int _tag_num;
short _name_length;
int _result;
// Tag-specific processing now occurs
switch (_tag_type)
{
case 1:
// Bytes should be OK. Skip over this tag.
return _n + 1;
case 2:
// Shorts can be encoded with the strange negative encoding
// We check for this by seeing if there is an alphanumeric character for the next tag earlier than expected
return _n + 2;
case 3:
// Integers can be encoded with the strange negative encoding
// We check for this by seeing if there is an alphanumeric character for the next tag earlier than expected
return _n + 4;
case 4:
// Longs can be encoded with the strange negative encoding
// We check for this by seeing if there is an alphanumeric character for the next tag earlier than expected
return _n + 8;
case 5:
// Floats should be OK. Skip this tag.
return _n + 4;
case 6:
// Doubles should be OK; skip this tag.
return _n + 8;
case 7:
// Bytes arrays should be OK. First, get the size, then skip over this tag.
return _n + 4 + EndianFlip(*((int *)(_out_buffer + _n)));
case 8:
// Strings should be OK; first get the length, then skip this tag
return _n + 2 + EndianFlip(*((short *)(_out_buffer + _n)));
case 9:
// Here are the tricky ones. This list type will require sub-processing.
// First, get the length, then loop through all the entries
_tag_type = _out_buffer[_n];
_n += 5;
_tag_num = EndianFlip(*((int *)(_out_buffer + _n - 4)));
for (_i = 0; _i < _tag_num; _i ++)
{
_result = ProcessTag(_out_buffer + _n, _size, _tag_type);
if (_result == -1) return -1;
_n += _result;
}
return _n;
case 10:
// This is a compound tag. All subtags will be processed in order.
do
{
// Grab the tag type; break if this is the terminating tag (a null byte)
_tag_type = _out_buffer[_n];
if (_tag_type == 0) return _n + 1;
// Grab the name length, then skip over the name
_name_length = EndianFlip(*((short *)(_out_buffer + _n + 1)));
_n += _name_length + 3;
// Process the sub tag.
_result = ProcessTag(_out_buffer + _n, _size - _n, _tag_type);
if (_result == -1) return -1;
_n += _result;
} while (_n <= _size);
// If here, we ran out of buffer space before processing finished.
// This indicates the tag was broken, and we have to bail.
return -1;
case 11:
// This integer array should be OK. Skip this tag.
return _n + 4 + 4 * EndianFlip(*((int *)(_out_buffer + _n)));
default:
// If we are here, there is a serious problem.
// No tag should have an ID value above 11; return -1 to indicate processing failed.
return -1;
}
}
/* This method copies the Entities or TileEntities tag into an empty buffer and checks to make sure it is not broken.
* _in_buffer: Pointer to the tag in the XBox file
* _out_buffer: Pointer to the empty buffer into which to copy this tag.
* _size: size of the tag in bytes.
* returns: the length of the empty buffer used.
*/
int ProcessEntities(unsigned char *_in_buffer, unsigned char *_out_buffer, int _offset, int _size)
{
int _n = 7;
int _i = 0;
// Do the memory copy first
memcpy(_out_buffer, _in_buffer + _offset, _size);
// Now pass the TileEntities tag into the global processor.
return ProcessTag(_out_buffer + _n + 8, _size, 9);
}
/* This looks at the top blocks in every column and makes an attempt at assigning a biome to each column.
* Entire chunks will share ONE biome. Assignments are made based on which biome the chunk probably is.
* The method considers only the TOP NON-AIR blocks in the chunk in making its decisions.
* _in_buffer: pointer to the biomes block to modify.
* _block_buffer: pointer to the blocks section corresponding to the biomes buffer above.
* _data_buffer: pointer to the data section corresponding to the biomes buffer above.
* _flag_no_reset: pointer to a buffer in which to store flags for biome columns that are not to be changed in the second pass.
*/
void ProcessBiomes(unsigned char *_in_buffer, unsigned char *_block_buffer, unsigned char *_data_buffer)
{
int n, _index;
int _c_max, _max_ID;
int max_height = 0;
int min_height = 128;
// First clear the input buffer and set all biomes to plains by default
for (int _i = 0; _i < 256; _i ++)
_in_buffer[_i] = 0xFF;
for (int _i = 0; _i < 256; _i ++)
local_biome_list[_i] = 0;
for (int _i = 0; _i < 256; _i ++)
_flag_no_reset[_i] = 0;
// We want to start at an offset of 128 for this column
_block_buffer += 128;
_data_buffer += 64;
// First pass - get top block and see if it is listed as a known biome
for (int _i = 0; _i < 256; _i ++)
{
// Initial offset for this column
n = -1;
// Loop backwards through the block column until we find the first non-air block
while (_block_buffer[n] == 0 && n > -256)
n --;
// If no non-air blocks were found, bail now
if (n == -256) continue;
// Look at the heights ONLY IF this is a DIRT block.
if (_block_buffer[n] == 2 || _block_buffer[n] == 3)
{
if (128 + n > max_height) max_height = 128 + n;
if (128 + n < min_height) min_height = 128 + n;
}
// Now get the index of this data / block pair
if (n % 2 == 0)
_index = LoNybble(_data_buffer[(n - 1) / 2]);
else
_index = HiNybble(_data_buffer[(n - 1) / 2]);
// Add block to index
_index |= _block_buffer[n] << 4;
// Update the biome if it is known
if (biome_list[_index] != 0xFF)
{
local_biome_list[biome_list[_index]] ++;
_in_buffer[_i] = biome_list[_index];
}
// Reset index
_index = 0;
// Move to next column
_block_buffer += 128;
_data_buffer += 64;
}
// Now get the most common biome in this chunk
_c_max = -1;
for (int _i = 0; _i < 256; _i ++)
{
if (local_biome_list[_i] > _c_max)
{
_c_max = local_biome_list[_i];
_max_ID = _i;
}
}
// Default to plains
if (_max_ID == 0) _max_ID = 1;
// If the maximum height is at least 80, or if the tallest hill is 12 or more blocks higher than than the lowest valley...
// ... this is extreme hills
if (max_height >= 80 || max_height - min_height >= 12)
_max_ID = 3;
// If more than half of this chunk consists of water, we set it to ocean
if (local_biome_list[254] > 128)
_max_ID = 0;
// If at least one hundred blocks in this chunk consist of sand, we set it to desert
// Also, if at least one of the blocks in this chunk is sand and there is little water and dirt, this is desert
if ( (local_biome_list[253] >= 100) || (local_biome_list[253] > 0 && local_biome_list[254] < 64 && local_biome_list[252] == 0 && local_biome_list[1] == 0) )
_max_ID = 2;
// If any evidence was found this should be a swamp biome and there was no evidence for a jungle biome...
// ... call this a swamp. (Ex: vines but no jungle leaves)
if (local_biome_list[6] > 0)
_max_ID = 6;
if (local_biome_list[21] > 0)
_max_ID = 21;
// Second pass - assign all to the most common for this chunk.
for (int _i = 0; _i < 256; _i ++)
{
if (_in_buffer[_i] != 30 && _in_buffer[_i] != 21) _in_buffer[_i] = _max_ID;
}
}
/* Anvil format stores blocks in YZX format instead of XZY format.
* This method takes an input buffer with blocks stored in XZY order and outputs the data in YZX format into an output buffer.
* _in_buffer: the block data stored in XZY format.
* _out_buffer: buffer to write the YZX format data.
* _y_levels: The number of planes in the y direction to convert.
*/
bool xyz_flip(unsigned char *_in_buffer, unsigned char *_out_buffer, unsigned char _y_level)
{
unsigned char x, y, z;
int c_ts = _y_level * 16, c_td;
bool nair_flag = false;
int c_inc = 0x70;
// Loop through all y planes
for (y = 0; y < 16; y ++)
{
// Each new plane begins at an index specified only by the plane's y coordinate.
c_td = y;
// There are 16 rows in this plane
for (z = 0; z < 16; z ++)
{
// There are 16 cols in this plane
for (x = 0; x < 16; x ++)
{
// If we find a non-air block, we set a flag to let us know this section is not empty
if (_in_buffer[c_ts] != 0)
nair_flag = true;
// Move memory, then update coordinates
_out_buffer[c_td] = _in_buffer[c_ts];
c_td += 1 << 8;
c_ts ++;
}
// Increment the z counter in the destination integer and reset the x counter
c_td += 1 << 4;
c_td &= 0xFF;
// Skip to the next column in the source block
c_ts += c_inc;
}
}
return nair_flag;
}
/* Anvil format stores blocks in YZX format instead of XZY format.
* This method takes an input buffer with data nybbles stored in XZY order and outputs the data in YZX format into an output buffer.
* _in_buffer: the block data stored in XZY format.
* _out_buffer: buffer to write the YZX format data.
* _y_levels: The number of planes in the y direction to convert.
*/
void xyz_nybbleflip(unsigned char *_in_buffer, unsigned char *_out_buffer, unsigned char _y_level)
{
unsigned char x, y, z;
unsigned char st;
int c_ts, c_td;
int c_inc = 0x70;
// Zero memory first for easier bitwise operation later
for (c_ts = 0; c_ts < 0x800; c_ts ++)
_out_buffer[c_ts] = 0;
// Get the source pointer set
c_ts = 16 * _y_level;
// Loop through all y planes
for (y = 0; y < 16; y ++)
{
// Each new plane begins at an index specified only by the plane's y coordinate.
c_td = y;
// There are 16 rows in this plane
for (z = 0; z < 16; z ++)
{
// There are 16 cols in this plane
for (x = 0; x < 16; x ++)
{
// Get the nybble we're interested in
if (c_ts % 2 == 0)
st = LoNybble(_in_buffer[c_ts / 2]);
else
st = HiNybble(_in_buffer[c_ts / 2]);
// Update the target nybble
if (c_td % 2 == 0)
_out_buffer[c_td / 2] |= st;
else
_out_buffer[c_td / 2] |= st << 4;
// Increment the pointer and repeat
c_td += 1 << 8;
c_ts ++;
}
// Increment the z counter in the destination integer and reset the x counter
c_td += 1 << 4;
c_td &= 0xFF;
// Skip to next column in source block
c_ts += c_inc;
}
}
}
/* XBox files are packed a little more tightly than simple compression.
* The signal 'FF' followed by a number n and a byte B indicates that B is repeated n + 1 times.
* If n is less than 3, the signal indicates that the byte 'FF' shall be repeated n + 1 times.
* This method unpacks a given input buffer in this format to the output buffer given.
* Return value is the amount of the output buffer used.
* _input_buffer: the data to expand
* _output_buffer: buffer in which to output the expanded data
* _input_size: length of the input buffer to expand
*/
unsigned long ExpandX(unsigned char *_input_buffer, unsigned char *_output_buffer, unsigned long _input_size)
{
unsigned long retval = 0;
unsigned short _limit;
unsigned char _repeat;
// Begin looping through the input buffer
for (unsigned long _i = 0; _i < _input_size; _i ++)
{
// Check to see if the character at this place is FF
if (_input_buffer[_i] == 0xFF)
{
// If here, increment outer counter and add the repeated bytes.
// Note that the (n + 1)th repeat is taken care of after this if statement.
_i ++;
_repeat = _input_buffer[_i + 1];
_limit = (unsigned short)_input_buffer[_i];
if (_limit >= 3)
{
for (int _n = 0; _n <= _limit; _n ++)
{
_output_buffer[retval] = _repeat;
retval ++;
}
}
else
{
_i --;
for (int _n = 0; _n <= _limit; _n ++)
{
_output_buffer[retval] = 0xFF;
retval ++;
}
}
// Increment the outer counter again to skip the number of repeats and the repeated byte
_i ++;
}
else
{
_output_buffer[retval] = _input_buffer[_i];
retval ++;
}
}
// Return the length of outputted content.
return retval;
}
/* The entry point for the console application.
* Call this application with the following:
* ExpandX _input_directory _output_directory _x_pos _z_pos [Nether|End]
* _input_directory: path to the directory containing the packed XBox files.
* _output_directory: path to the directory in which to output the converted PC files.
* _x_pos: use '-' if this region is negative in the x direction; any other string otherwise.
* _z_pos: use '-' if this region is negative in the z direction; any other string otherwise.
* [Nether|End]: Optional; use Nether for a Nether chunk and End for an End chunk. DO NOT SET for an overworld chunk.
*/
int _tmain(int argc, _TCHAR* argv[])
{
// Allocate memory
xbuffer = new unsigned char[409600];
nbuffer = new unsigned char[409600];
rbuffer = new unsigned char[409600];
ebuffer = new unsigned char[409600];
sbuffer = new unsigned char[409600];
afile_path = new char[200];
aoutput_path = new char[200];
status = new char[300];
biome_list = new unsigned char[4096];
// Load xstart and zstart
if (argc < 5)
{
xstart = 0;
zstart = 0;
}
else
{
xstart = (*argv[3] == '-' ? 32 : 0);
zstart = (*argv[4] == '-' ? 32 : 0);
}
// If this is the Nether or End, there are less chunks expected
if (argc > 5) missing = -943;
// Load the Anvil template file into memory
input_file_f = fopen("anvil_template.dat", "r");
fseek(input_file_f, 0L, SEEK_END);
template_length = ftell(input_file_f);
fseek(input_file_f, 0L, SEEK_SET);
fread(rbuffer, sizeof(char), template_length, input_file_f);
fclose(input_file_f);
// Load the section template file into memory
input_file_f = fopen("section_template.dat", "r");
fseek(input_file_f, 0L, SEEK_END);
section_template_length = ftell(input_file_f);
fseek(input_file_f, 0L, SEEK_SET);
fread(sbuffer, sizeof(char), section_template_length, input_file_f);
fclose(input_file_f);
// This assigns biome IDs to specific block types for later reference.
populate_biomes(biome_list);
// Open the log file
log_file_f = fopen("log.txt", "w+b");
// Loop through input directory
for (iol = 0; iol < 1024; iol ++)
{
// Get the input and output paths
sprintf(afile_path, "%ls\\%d.dat", argv[1], iol);
sprintf(aoutput_path, "%ls\\%d.dat", argv[2], iol);
// Next input file
input_file_f = fopen(afile_path, "rb");
if (input_file_f != NULL)
{
// Get the size of the file
fseek(input_file_f, 0L, SEEK_END);
file_size = ftell(input_file_f);
// Read in all data
fseek(input_file_f, 0L, SEEK_SET);
bytes_read = fread(nbuffer, sizeof(char), file_size, input_file_f);
if (bytes_read != file_size) printf("Warning: could not read %d bytes from file %d.\n", file_size, iol);
fclose(input_file_f);
// Perform the actual expansion
file_size = ExpandX(nbuffer, xbuffer, file_size);
// Get the start points for the blocks, data, skylight, and blocklight blocks
blockstart = BlockStart(xbuffer, file_size);
datastart = DataStart(xbuffer, file_size);
blocklightstart = BlockLight(xbuffer, file_size);
skylightstart = SkyLight(xbuffer, file_size);
// If there is no block or data section, we delete this corrupted file and abort conversion of this chunk
if (blockstart == -1 || datastart == -1)
{
remove(aoutput_path);
remove(afile_path);
sprintf(status, "Warning: chunk missing blocks and/or data; not converted at x=%d, z=%d.\r\n\0", (iol % 32) - xstart, (int)(iol / 32) - zstart);
fwrite(status, 1, strlen(status), log_file_f);
continue;
}
// If here, there was no issue with the chunk file, so we open the output file
output_file_f = fopen(aoutput_path, "w+b");
// Attempt to correct the biomes
ProcessBiomes(rbuffer + 0x28, xbuffer + blockstart, xbuffer + datastart);
// Output xpos and zpos into the template
xpos = EndianFlip((iol % 32) - xstart);
zpos = EndianFlip(iol / 32 - zstart);
memcpy(rbuffer + 0x15B, &xpos, 4);
memcpy(rbuffer + 0x166, &zpos, 4);
// If this is the nether, we replace all bedrock with air and set biome to "hell"
// For the end, set biome to "Sky"
if (argc > 5)
{
if (wcscmp(argv[5], L"Nether") == 0)
{
for (i = blockstart; i < blockstart + 0x8000; i ++) if ((i - blockstart) % 128 >= 3) if (xbuffer[i] == 7) xbuffer[i] = 87;
for (i = 0x28; i < 0x128; i ++) rbuffer[i] = 8;
}
else if (wcscmp(argv[5], L"End") == 0)
for (i = 0x28; i < 0x128; i ++) rbuffer[i] = 9;
}
// Find and process the Entities section
zpos = -1;
for (int n = file_size - 1; n > 0; n --)
{
if (*(xbuffer + n) == 's' && *(xbuffer + n - 1) == 'e' && *(xbuffer + n - 2) == 'i' && *(xbuffer + n - 3) == 't' && *(xbuffer + n - 8) == 8)
{
zpos = 11 + ProcessEntities(xbuffer, ebuffer, n - 14, file_size - n + 14);
break;
}
}
// Begin assembling the final file
memcpy(nbuffer, rbuffer, 0xB);
if (zpos != 10)
{
memcpy(nbuffer + 0xB, ebuffer + 4, zpos);
memcpy(nbuffer + 0xB + zpos, rbuffer + 0x1B, 0x14F);
}
else
{
memcpy(nbuffer + 0xB, nbuffer + 0xB, 0x15F);
zpos = 0x10;
}
// Find the tile entity section and process
for (int n = file_size - 1; n > 0; n --)
{
if (*(xbuffer + n) == 's' && *(xbuffer + n - 1) == 'e' && *(xbuffer + n - 2) == 'i' && *(xbuffer + n - 3) == 't' && *(xbuffer + n - 12) == 12)
{
xpos = 15 + ProcessEntities(xbuffer, ebuffer, n - 14, file_size - n + 14);
break;
}
}
// Add the TileEntities section
if (xpos != 14)
{
memcpy(nbuffer + 0x15A + zpos, ebuffer, xpos);
memcpy(nbuffer + 0x15A + zpos + xpos, rbuffer + 0x17E, 0x44C);
}
else
{
memcpy(nbuffer + 0x15A + zpos, rbuffer + 0x16A, 0x460);
xpos = 0x14;
}
// Use zpos as the total length of the upper template file for now
zpos += 0x5A6 + xpos;
// Get the number of y planes in the blocks segment
y_plane = EndianFlip(*(int *)(xbuffer + blockstart - 4)) / 0x1000;
// Reset section count to zero.
s_num = 0;
h = 0;
// Now, flip the XYZ order of all blocks in each segment
for (xpos = 0; xpos < y_plane; xpos ++)
{
// If this is the 8th plane up from the bottom, we advance to the second half of the XBox blocks section.
if (xpos == 8) h = 0x8000;
// Perform the flip of blocks. If this if statement returns false, the section was empty.
// Empty sections are not stored, so if this section is empty we skip it now.
if (!xyz_flip(xbuffer + blockstart + h, sbuffer + 0x183D, xpos % 8))
continue;
// If here, this section is not empty, so now we flip the data, blocklight, and skylight sections.
xyz_nybbleflip(xbuffer + datastart + (h / 2), sbuffer + 0xB, xpos % 8);
xyz_nybbleflip(xbuffer + blocklightstart + (h / 2), sbuffer + 0x102B, xpos % 8);
xyz_nybbleflip(xbuffer + skylightstart + (h / 2), sbuffer + 0x81A, xpos % 8);
// This assigns the Y coordinate of the section.
*(sbuffer + 0x182F) = (unsigned char)xpos;
// Move this section to the end of the template file
memcpy(nbuffer + zpos + 0x283E * s_num, sbuffer, 0x283E);
s_num ++;
}
// Set the final two bytes to zero and set the number of sections correctly
for (xpos = 0; xpos < 3; xpos ++) *(nbuffer + zpos + 0x283E * s_num + xpos) = 0;
*(nbuffer + zpos - 1) = s_num;
// Here we double check the status of this chunk; if it is broken, we remove it
if (ProcessTag(nbuffer + 3, zpos + 0x283E * s_num - 1, 10) != zpos + 0x283E * s_num - 1)
{
remove(aoutput_path);
remove(afile_path);
sprintf(status, "Warning: broken chunk not converted at x=%d, z=%d.\r\n\0", (iol % 32) - xstart, (int)(iol / 32) - zstart);
fwrite(status, 1, strlen(status), log_file_f);
broken ++;
}
else
{
fwrite(nbuffer, 1, zpos + 0x283E * s_num + 2, output_file_f);
}
// Reset buffer for next chunk
memcpy(nbuffer, rbuffer, template_length);
// Close handle to this output file
fclose(output_file_f);
}
else
missing ++;
}
// Write final info and close log file
if (missing < 0) missing = 0;
sprintf(status, "Chunk conversion complete for region x=%d, z=%d. Chunk details:\r\n\0", xstart == 32 ? -1 : 0, zstart == 32 ? -1 : 0);
fwrite(status, 1, strlen(status), log_file_f);
sprintf(status, "\tOK: %d\r\n\tMissing: %d\r\n\tBroken: %d\r\n\0", 1024 - missing - broken - (argc > 5 ? 943 : 295), missing, broken);
fwrite(status, 1, strlen(status), log_file_f);
fclose(log_file_f);
// Free buffers
delete [409600] xbuffer;
delete [409600] nbuffer;
delete [409600] rbuffer;
delete [409600] ebuffer;
delete [409600] sbuffer;
delete [200] aoutput_path;
delete [200] afile_path;
delete [300] status;
delete [4096] biome_list;
return 0;
}