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/savegamedat.cpp
Keith @ Killa Network edeb684560 Added XBox support
2015-11-05 18:54:13 +11:00

478 lines
13 KiB
C++

// savegamedat.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
FILE *sgame_dat, *xbox_sgame;
char *sgame_path, *NBT_path;
const char *s_s = ".\0";
unsigned char *lbuffer, *xbuffer, *obuffer;
int template_length, xbox_length;
short string_length;
int inventory_length, inventory_offset;
// This structure represents an entry in the level.dat file.
struct entry
{
public:
const char *path;
int length;
int offset;
} player_entries[28], savegame_entries[8];
// Flips an integer from little to big 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;
}
// Flips a short from little to big 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;
}
/* This is a slight variation of the method in ExpandX.cpp.
* Here we not only check the tag to make sure it is not broken, but check its name to see if we want to copy it to the PC file.
* If the name matches a known name, we copy it over.
* _out_buffer: buffer pointing to the tag to process.
* _size: length of the buffer to process.
* _tag_type: integer specifying the tag type.
* _path: current path to the tag.
* _search: array of entries to search for known values.
* _entry_length: number of entries in the array.
*/
int ProcessTag(unsigned char *_out_buffer, int _size, unsigned char _tag_type, char *_path, entry *_search, int _entry_length)
{
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, _path, _search, _entry_length);
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, now
_name_length = EndianFlip(*((short *)(_out_buffer + _n + 1)));
memcpy(_path, _out_buffer + _n + 3, _name_length);
memcpy(_path + _name_length, s_s, 2);
_n += _name_length + 3;
// Look through the structs to see if there are any matches
for (_i = 0; _i < _entry_length; _i ++)
{
if (strcmp(_search[_i].path, NBT_path) == 0)
{
memcpy(lbuffer + _search[_i].offset, _out_buffer + _n, _search[_i].length);
}
}
// If this is the inventory tag, store the length for later.
if (strcmp("Inventory.", NBT_path) == 0)
{
_result = ProcessTag(_out_buffer + _n, _size - _n, _tag_type, _path + _name_length + 1, _search, _entry_length);
inventory_length = _result;
inventory_offset = _n + 3;
}
else
_result = ProcessTag(_out_buffer + _n, _size - _n, _tag_type, _path + _name_length + 1, _search, _entry_length);
// Process the sub tag.
_result = ProcessTag(_out_buffer + _n, _size - _n, _tag_type, _path + _name_length + 1, _search, _entry_length);
// If processing failed, break now.
// Otherwise, add the result length to the pointer and go again.
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;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
// Allocate memory
sgame_path = new char[300];
lbuffer = new unsigned char[496000];
xbuffer = new unsigned char[496000];
obuffer = new unsigned char[496000];
NBT_path = new char[1000];
// Emptry entry values to begin
for (int i = 0; i < 28; i ++)
player_entries[i] = entry();
for (int i = 0; i < 8; i ++)
savegame_entries[i] = entry();
// Populate the entries
// Player position
player_entries[0].path = "Pos.";
player_entries[0].length = 0x1D;
player_entries[0].offset = 0x2C7;
// Player HurtTime
player_entries[1].path = "HurtTime.";
player_entries[1].length = 2;
player_entries[1].offset = 0x294;
// Player OnGround
player_entries[2].path = "OnGround.";
player_entries[2].length = 1;
player_entries[2].offset = 0x288;
// Player motion
player_entries[3].path = "Motion.";
player_entries[3].length = 0x1D;
player_entries[3].offset = 0x147;
// Player food exhaustion level
player_entries[4].path = "foodExhaustionLevel.";
player_entries[4].length = 4;
player_entries[4].offset = 0x17A;
// Player current dimension location
player_entries[5].path = "Dimension.";
player_entries[5].length = 4;
player_entries[5].offset = 0x2B5;
// Player current dimension location
player_entries[6].path = "Health.";
player_entries[6].length = 2;
player_entries[6].offset = 0x1A9;
// Player Air
player_entries[7].path = "Air.";
player_entries[7].length = 2;
player_entries[7].offset = 0x2BF;
// Player camera rotation
player_entries[8].path = "Rotation.";
player_entries[8].length = 0xD;
player_entries[8].offset = 0x3A9;
// Player current fall distance
player_entries[9].path = "FallDistance.";
player_entries[9].length = 4;
player_entries[9].offset = 0x38C;
// Player current time on fire
player_entries[10].path = "Fire.";
player_entries[10].length = 2;
player_entries[10].offset = 0x1EA;
// Player current time since death
player_entries[11].path = "DeathTime.";
player_entries[11].length = 2;
player_entries[11].offset = 0x239;
// Player spawn location Z coordinate
player_entries[12].path = "SpawnZ.";
player_entries[12].length = 4;
player_entries[12].offset = 0x48A;
// Player current time since last attack
player_entries[13].path = "AttackTime.";
player_entries[13].length = 2;
player_entries[13].offset = 0x1D5;
// Player sleeping
player_entries[14].path = "Sleeping.";
player_entries[14].length = 1;
player_entries[14].offset = 0x1E2;
// Player SleepTimer
player_entries[15].path = "SleepTimer.";
player_entries[15].length = 2;
player_entries[15].offset = 0x27B;
// Player XpP
player_entries[16].path = "XpP.";
player_entries[16].length = 4;
player_entries[16].offset = 0x253;
// Player Experience Level
player_entries[17].path = "XpLevel.";
player_entries[17].length = 4;
player_entries[17].offset = 0x19C;
// Player Experience Level
player_entries[18].path = "XpTotal.";
player_entries[18].length = 4;
player_entries[18].offset = 0x39A;
// Player Spawn location X coordinate
player_entries[19].path = "SpawnX.";
player_entries[19].length = 4;
player_entries[19].offset = 0x44D;
// Player Spawn location Y coordinate
player_entries[20].path = "SpawnY.";
player_entries[20].length = 4;
player_entries[20].offset = 0x466;
// Player foodLevel
player_entries[21].path = "foodLevel.";
player_entries[21].length = 4;
player_entries[21].offset = 0x20D;
// Player foodTickTimer
player_entries[22].path = "foodTickTimer.";
player_entries[22].length = 4;
player_entries[22].offset = 0x18E;
// Player foodSaturationLevel
player_entries[23].path = "foodSaturationLevel.";
player_entries[23].length = 4;
player_entries[23].offset = 0x2FA;
// Player currently flying
player_entries[24].path = "abilities.flying.";
player_entries[24].length = 1;
player_entries[24].offset = 0x328;
// Player able to build instantly
player_entries[25].path = "abilities.instabuild.";
player_entries[25].length = 1;
player_entries[25].offset = 0x336;
// Player invulnerable
player_entries[26].path = "abilities.invulnerable.";
player_entries[26].length = 1;
player_entries[26].offset = 0x350;
// Player able to fly
player_entries[27].path = "abilities.mayfly.";
player_entries[27].length = 1;
player_entries[27].offset = 0x340;
// Savegame is raining
savegame_entries[0].path = "Data.raining.";
savegame_entries[0].length = 1;
savegame_entries[0].offset = 0x431;
// Savegame is thundering
savegame_entries[1].path = "Data.thundering.";
savegame_entries[1].length = 1;
savegame_entries[1].offset = 0x17;
// Savegame duration of rainstorm
savegame_entries[2].path = "Data.rainTime.";
savegame_entries[2].length = 4;
savegame_entries[2].offset = 0x5B2;
// Savegame duration of rainstorm
savegame_entries[3].path = "Data.thunderTime.";
savegame_entries[3].length = 4;
savegame_entries[3].offset = 0x440;
// Savegame version
// Note: the typo is intentional. We do not currently want to copy this value.
savegame_entries[4].path = "Data.versoon.";
savegame_entries[4].length = 4;
savegame_entries[4].offset = 0x403;
// Savegame timestamp LastPlayed
savegame_entries[5].path = "Data.LastPlayed.";
savegame_entries[5].length = 8;
savegame_entries[5].offset = 0x25;
// Savegame seed
savegame_entries[6].path = "Data.RandomSeed.";
savegame_entries[6].length = 8;
savegame_entries[6].offset = 0x3D3;
// Savegame time
savegame_entries[7].path = "Data.Time.";
savegame_entries[7].length = 8;
savegame_entries[7].offset = 0x41F;
// Open the output file
sprintf(sgame_path, "%ls", argv[1]);
sgame_dat = fopen(sgame_path, "r+b");
// Open the XBox savegame.dat
sprintf(sgame_path, "%ls", argv[2]);
xbox_sgame = fopen(sgame_path, "r+b");
// Load the template into memory
fseek(sgame_dat, 0, SEEK_END);
template_length = ftell(sgame_dat);
rewind(sgame_dat);
fread(lbuffer, 1, template_length, sgame_dat);
// Load the XBox file into memory
fseek(xbox_sgame, 0, SEEK_END);
xbox_length = ftell(xbox_sgame);
rewind(xbox_sgame);
fread(xbuffer, 1, xbox_length, xbox_sgame);
// This method browses through the NBT tags in the XBox file
// Any that match the template file are written out there
ProcessTag(xbuffer + 3, xbox_length, 10, NBT_path, savegame_entries, 8);
// Now do the same for the player file
// Open the XBox savegame.dat
fclose(xbox_sgame);
sprintf(sgame_path, "%ls", argv[3]);
xbox_sgame = fopen(sgame_path, "r+b");
// Load the XBox file into memory
fseek(xbox_sgame, 0, SEEK_END);
xbox_length = ftell(xbox_sgame);
rewind(xbox_sgame);
fread(xbuffer, 1, xbox_length, xbox_sgame);
// This method browses through the NBT tags in the XBox file
// Any that match the template file are written out there
ProcessTag(xbuffer + 3, xbox_length, 10, NBT_path, player_entries, 28);
// Generate world name
sprintf(NBT_path + 2, "%ls\0", argv[4]);
*((short *)(NBT_path)) = EndianFlip((short)strlen(NBT_path + 2));
// Now begin assembling the final file
// The inventory is professed differently here.
memcpy(obuffer, lbuffer, 0x1C3);
memcpy(obuffer + 0x1C3, xbuffer + inventory_offset, inventory_length);
memcpy(obuffer + 0x1C3 + inventory_length, lbuffer + 0x1C8, 0x2AE);
memcpy(obuffer + 0x1C3 + inventory_length + 0x2AE, NBT_path, strlen(NBT_path + 2) + 2);
memcpy(obuffer + 0x1C3 + inventory_length + 0x2AE + strlen(NBT_path + 2) + 2, lbuffer + 0x481, 0x14E);
// Now discard the level.dat file and repoen
fclose(xbox_sgame);
sprintf(sgame_path, "%ls", argv[2]);
xbox_sgame = fopen(sgame_path, "w+b");
// Go to the beginning of the file and write
rewind(xbox_sgame);
fwrite(obuffer, 1, 0x1C3 + inventory_length + 0x2AE + strlen(NBT_path + 2) + 2 + 0x14E, xbox_sgame);
// Close files
fclose(sgame_dat);
fclose(xbox_sgame);
// Free memory
delete [300] sgame_path;
delete [496000] lbuffer;
delete [496000] xbuffer;
delete [496000] obuffer;
delete [1000] NBT_path;
return 0;
}