mirror of
https://github.com/HikikoMarmy/Champions-Reborn-Server.git
synced 2026-04-05 08:59:54 -03:00
Network character saving
This commit is contained in:
203
Game/CharacterSaveManager.cpp
Normal file
203
Game/CharacterSaveManager.cpp
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* CharacterSaveManager is responsible for managing character saves while online.
|
||||||
|
* Because Return To Arms can have any user in the party initiate a save for any
|
||||||
|
* other user in the party, we need to keep track of these tasks.
|
||||||
|
*
|
||||||
|
* Tasks keep track of the user that initiated the save (the owner),
|
||||||
|
* and the target user whose character is being saved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "CharacterSaveManager.h"
|
||||||
|
|
||||||
|
#include "RealmUser.h"
|
||||||
|
#include "RealmUserManager.h"
|
||||||
|
#include "../Database/Database.h"
|
||||||
|
#include "../../logging.h"
|
||||||
|
|
||||||
|
CharacterSaveManager::CharacterSaveManager()
|
||||||
|
{
|
||||||
|
m_tasks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterSaveManager::~CharacterSaveManager()
|
||||||
|
{
|
||||||
|
m_tasks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterSaveManager::BeginSaveTask(
|
||||||
|
const sptr_user user,
|
||||||
|
const uint32_t characterId,
|
||||||
|
const CharacterSlotData &metaData,
|
||||||
|
const CharacterSaveType saveType )
|
||||||
|
{
|
||||||
|
return BeginSaveTask( user, user, characterId, metaData, saveType );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterSaveManager::BeginSaveTask(
|
||||||
|
const sptr_user m_owner,
|
||||||
|
const sptr_user m_target,
|
||||||
|
const uint32_t characterId,
|
||||||
|
const CharacterSlotData &metaData,
|
||||||
|
const CharacterSaveType saveType )
|
||||||
|
{
|
||||||
|
if( !m_owner || !m_target )
|
||||||
|
{
|
||||||
|
Log::Error( "CreateSaveTask called with empty session IDs!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &sessionId = m_owner->m_sessionId;
|
||||||
|
|
||||||
|
if( m_tasks.find( sessionId ) != m_tasks.end() )
|
||||||
|
{
|
||||||
|
m_tasks.erase( sessionId );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto task = std::make_shared< CharacterSaveTask >( saveType, characterId );
|
||||||
|
|
||||||
|
task->m_ownerUser = m_owner;
|
||||||
|
task->m_targetUser = m_target;
|
||||||
|
task->m_meta = metaData;
|
||||||
|
m_tasks[ sessionId ] = task;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterSaveManager::AppendSaveData( const std::wstring &sessionId, const std::vector<uint8_t> &data, bool endOfData )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto it = m_tasks.find( sessionId );
|
||||||
|
if( it == m_tasks.end() )
|
||||||
|
{
|
||||||
|
Log::Error( "AddDataToSaveTask: No task found for session ID [{}].", sessionId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &task = it->second;
|
||||||
|
|
||||||
|
if( !task->AppendData( data ) )
|
||||||
|
{
|
||||||
|
Log::Error( "AddDataToSaveTask: Append failed for session ID [{}].", sessionId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !endOfData )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !task->Validate() )
|
||||||
|
{
|
||||||
|
Log::Error( "AddDataToSaveTask: Validation failed for session ID [{}].", sessionId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !CommitSaveTask( sessionId ) )
|
||||||
|
{
|
||||||
|
Log::Error( "AddDataToSaveTask: Commit failed for session ID [{}].", sessionId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Debug( "AddDataToSaveTask: Final chunk committed for session ID [{}].", sessionId );
|
||||||
|
}
|
||||||
|
catch( const std::exception &e )
|
||||||
|
{
|
||||||
|
Log::Error( "AddDataToSaveTask: Exception for session ID [{}]: {}", sessionId, e.what() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterSaveManager::CommitSaveTask( const std::wstring &sessionId )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto node = m_tasks.extract( sessionId );
|
||||||
|
if( !node )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Task for session ID [{}] not found.", sessionId );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &saveTask = node.mapped();
|
||||||
|
|
||||||
|
if( !saveTask->Validate() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &targetUser = saveTask->m_targetUser;
|
||||||
|
|
||||||
|
if( !targetUser )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Target user not found." );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( saveTask->m_saveType == CharacterSaveType::NEW_CHARACTER )
|
||||||
|
{
|
||||||
|
auto result = Database::Get().CreateNewCharacter( targetUser->m_accountId, saveTask->m_meta, saveTask->m_data );
|
||||||
|
if( !result )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Failed to create new character for account ID: {}", targetUser->m_accountId );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Debug( "CommitSaveTask: New character created with ID {} for account ID: {}", result, targetUser->m_accountId );
|
||||||
|
|
||||||
|
targetUser->m_characterId = result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( 0 == saveTask->m_characterId )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Invalid character ID for save task." );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = Database::Get().SaveCharacter( targetUser->m_accountId, targetUser->m_characterId, saveTask->m_meta, saveTask->m_data );
|
||||||
|
if( !result )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Failed to save character for account ID: {}", targetUser->m_accountId );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Debug( "CommitSaveTask: Character saved for account ID: {}", targetUser->m_accountId );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( const std::exception &e )
|
||||||
|
{
|
||||||
|
Log::Error( "CommitSaveTask: Exception occurred while committing task for session ID [{}]: {}", sessionId, e.what() );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Debug( "CommitSaveTask: Task for session ID [{}] committed successfully.", sessionId );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterSaveManager::RemoveSaveTask( const std::wstring &sessionId )
|
||||||
|
{
|
||||||
|
auto it = m_tasks.find( sessionId );
|
||||||
|
if( it != m_tasks.end() )
|
||||||
|
{
|
||||||
|
m_tasks.erase( it );
|
||||||
|
Log::Debug( "RemoveSaveTask: Task for session ID [{}] removed.", sessionId );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::Error( "RemoveSaveTask: Task for session ID [{}] not found.", sessionId );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sptr_character_save_task CharacterSaveManager::FindSaveTask( const std::wstring &sessionId )
|
||||||
|
{
|
||||||
|
auto it = m_tasks.find( sessionId );
|
||||||
|
if( it != m_tasks.end() )
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::Error( "FindSaveTask: Task for session ID [{}] not found.", sessionId );
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Game/CharacterSaveManager.h
Normal file
43
Game/CharacterSaveManager.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "CharacterSaveTask.h"
|
||||||
|
|
||||||
|
class CharacterSaveManager {
|
||||||
|
public:
|
||||||
|
CharacterSaveManager();
|
||||||
|
~CharacterSaveManager();
|
||||||
|
CharacterSaveManager( const CharacterSaveManager & ) = delete;
|
||||||
|
CharacterSaveManager &operator=( const CharacterSaveManager & ) = delete;
|
||||||
|
|
||||||
|
static CharacterSaveManager &Get()
|
||||||
|
{
|
||||||
|
static CharacterSaveManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BeginSaveTask(
|
||||||
|
const sptr_user user,
|
||||||
|
const uint32_t characterId,
|
||||||
|
const CharacterSlotData &metaData,
|
||||||
|
const CharacterSaveType saveType );
|
||||||
|
|
||||||
|
bool BeginSaveTask(
|
||||||
|
const sptr_user m_owner,
|
||||||
|
const sptr_user m_target,
|
||||||
|
const uint32_t characterId,
|
||||||
|
const CharacterSlotData &metaData,
|
||||||
|
const CharacterSaveType saveType );
|
||||||
|
|
||||||
|
void AppendSaveData( const std::wstring &sessionId, const std::vector<uint8_t> &data, bool endOfData );
|
||||||
|
bool CommitSaveTask( const std::wstring &sessionId );
|
||||||
|
void RemoveSaveTask( const std::wstring &sessionId );
|
||||||
|
|
||||||
|
sptr_character_save_task FindSaveTask( const std::wstring &sessionId );
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::unordered_map< std::wstring, sptr_character_save_task > m_tasks;
|
||||||
|
};
|
||||||
43
Game/CharacterSaveTask.h
Normal file
43
Game/CharacterSaveTask.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "../Common/Constant.h"
|
||||||
|
#include "../Common/ByteStream.h"
|
||||||
|
#include "RealmUser.h"
|
||||||
|
#include "RealmCharacterMetaKV.h"
|
||||||
|
|
||||||
|
enum class CharacterSaveType : uint8_t
|
||||||
|
{
|
||||||
|
NEW_CHARACTER,
|
||||||
|
SAVE_CHARACTER
|
||||||
|
};
|
||||||
|
|
||||||
|
class CharacterSaveTask {
|
||||||
|
public:
|
||||||
|
CharacterSaveTask( CharacterSaveType Type, uint32_t characterId = 0 );
|
||||||
|
~CharacterSaveTask();
|
||||||
|
|
||||||
|
void SetMetaData( const CharacterSlotData &metaData );
|
||||||
|
bool AppendData( const std::vector< uint8_t > &data );
|
||||||
|
|
||||||
|
bool Validate();
|
||||||
|
|
||||||
|
const std::vector< uint8_t >& GetData() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CharacterSaveType m_saveType;
|
||||||
|
sptr_user m_ownerUser;
|
||||||
|
sptr_user m_targetUser;
|
||||||
|
|
||||||
|
uint32_t m_characterId;
|
||||||
|
uint32_t m_writePosition;
|
||||||
|
|
||||||
|
CharacterSlotData m_meta;
|
||||||
|
std::vector< uint8_t > m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
using sptr_character_save_task = std::shared_ptr< CharacterSaveTask >;
|
||||||
@@ -1,145 +1,660 @@
|
|||||||
#include "RealmCharacter.h"
|
#include "RealmCharacter.h"
|
||||||
|
|
||||||
|
#include "CharacterSaveTask.h"
|
||||||
|
#include "../Common/ByteBufferReader.hpp"
|
||||||
|
#include "../Common/RLEZ.hpp"
|
||||||
|
|
||||||
#include "../../logging.h"
|
#include "../../logging.h"
|
||||||
|
|
||||||
RealmCharacter::RealmCharacter( std::vector< uint8_t > &data )
|
RealmCharacter::RealmCharacter()
|
||||||
{
|
{
|
||||||
ByteBuffer stream( data );
|
m_characterId = 0;
|
||||||
|
m_data.clear();
|
||||||
|
|
||||||
m_metaData.Deserialize( stream );
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
auto characterDataSize = stream.read_u32();
|
RealmCharacter::RealmCharacter( const int32_t character_id, const CharacterSlotData meta, const std::vector<uint8_t> data )
|
||||||
if( characterDataSize > 0 && characterDataSize < 1024 )
|
{
|
||||||
|
m_characterId = character_id;
|
||||||
|
m_data = data;
|
||||||
|
m_metaData = meta;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
RealmCharacter::~RealmCharacter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RealmCharacter::Initialize()
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
for( auto &val : unknown_000 )
|
||||||
{
|
{
|
||||||
m_characterData = stream.read_bytes( characterDataSize );
|
val = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
RealmCharacter::RealmCharacter(sptr_byte_stream stream)
|
unknown_str = "";
|
||||||
{
|
|
||||||
m_metaData.Deserialize(*stream);
|
|
||||||
|
|
||||||
auto characterDataSize = stream->read_u32();
|
for( auto &val : unknown_004 )
|
||||||
if (characterDataSize > 0 && characterDataSize < 1024)
|
|
||||||
{
|
{
|
||||||
m_characterData = stream->read_bytes(characterDataSize);
|
val = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
character_class = CharacterClass::WARRIOR;
|
||||||
|
character_race = CharacterRace::BARBARIAN_M;
|
||||||
|
|
||||||
|
current_level = 0;
|
||||||
|
pending_level = 0;
|
||||||
|
|
||||||
|
unknown_009 = 0;
|
||||||
|
experience = 0;
|
||||||
|
|
||||||
|
std::memset( unknown_010, 0, sizeof( unknown_010 ) );
|
||||||
|
std::memset( stats, 0, sizeof( stats ) );
|
||||||
|
|
||||||
|
unknown_016 = 0;
|
||||||
|
current_hp = 0.0f;
|
||||||
|
maximum_hp = 0.0f;
|
||||||
|
|
||||||
|
unknown_017 = 0;
|
||||||
|
unknown_018 = 0;
|
||||||
|
unknown_019 = 0;
|
||||||
|
|
||||||
|
current_mana = 0.0f;
|
||||||
|
maximum_mana = 0.0f;
|
||||||
|
|
||||||
|
unknown_020 = 0;
|
||||||
|
|
||||||
|
attack_power = 0;
|
||||||
|
minimum_damage = 0;
|
||||||
|
maximum_damage = 0;
|
||||||
|
|
||||||
|
unknown_021 = 0;
|
||||||
|
unknown_022 = 0;
|
||||||
|
unknown_023 = 0;
|
||||||
|
unknown_024 = 0;
|
||||||
|
unknown_025 = 0;
|
||||||
|
unknown_026 = 0;
|
||||||
|
|
||||||
|
current_gold = 0;
|
||||||
|
current_skill_points = 0;
|
||||||
|
current_ability_points = 0;
|
||||||
|
has_spent_remaining_points = 0;
|
||||||
|
|
||||||
|
unknown_029 = 0;
|
||||||
|
unknown_030 = 0;
|
||||||
|
unknown_031 = 0;
|
||||||
|
unknown_032 = 0;
|
||||||
|
unknown_033 = 0;
|
||||||
|
unknown_034 = 0;
|
||||||
|
unknown_035 = 0;
|
||||||
|
unknown_036 = 0;
|
||||||
|
unknown_037 = 0;
|
||||||
|
unknown_038 = 0;
|
||||||
|
unknown_039 = 0;
|
||||||
|
unknown_040 = 0;
|
||||||
|
|
||||||
|
weight = 0.0f;
|
||||||
|
max_weight = 0.0f;
|
||||||
|
|
||||||
|
unknown_041 = 0;
|
||||||
|
unknown_042 = 0;
|
||||||
|
|
||||||
|
item_data.fill( ItemData() );
|
||||||
|
num_armor_item = 0;
|
||||||
|
unknown_043 = 0;
|
||||||
|
armor_item_data.fill( ItemData() );
|
||||||
|
num_weapon_item = 0;
|
||||||
|
unknown_044 = 0;
|
||||||
|
weapon_item_data.fill( ItemData() );
|
||||||
|
num_consumable_item = 0;
|
||||||
|
|
||||||
|
unknown_045 = 0;
|
||||||
|
unknown_046.fill( 0 );
|
||||||
|
|
||||||
|
quest_string.fill( s_quest() );
|
||||||
|
num_quests = 0;
|
||||||
|
|
||||||
|
unknown_048 = 0;
|
||||||
|
unknown_049 = 0;
|
||||||
|
unknown_050 = 0;
|
||||||
|
unknown_051 = 0;
|
||||||
|
unknown_052 = 0;
|
||||||
|
unknown_053 = 0;
|
||||||
|
unknown_054 = 0;
|
||||||
|
|
||||||
|
equipment.fill( 0 );
|
||||||
|
|
||||||
|
newStyle.fill( s_new_style() );
|
||||||
|
|
||||||
|
unknown_075 = 0;
|
||||||
|
unknown_076 = 0.0f;
|
||||||
|
|
||||||
|
unknown_077 = 0;
|
||||||
|
unknown_078 = 0;
|
||||||
|
unknown_079 = 0;
|
||||||
|
unknown_080 = 0;
|
||||||
|
unknown_081 = 0;
|
||||||
|
unknown_082 = 0;
|
||||||
|
unknown_083 = 0;
|
||||||
|
unknown_084 = 0;
|
||||||
|
unknown_085 = 0;
|
||||||
|
|
||||||
|
skill_slot[ 0 ] = 0;
|
||||||
|
skill_slot[ 1 ] = 0;
|
||||||
|
|
||||||
|
unknown_087 = 0;
|
||||||
|
unknown_088 = 0;
|
||||||
|
|
||||||
|
attackData.fill( AttackData() );
|
||||||
|
|
||||||
|
skills.fill( s_skill() );
|
||||||
|
|
||||||
|
unknown_091 = 0;
|
||||||
|
|
||||||
|
mission_progress.fill( s_difficulty_progress() );
|
||||||
|
mission_medals.fill( 0 );
|
||||||
|
|
||||||
|
evil_bitflag = 0;
|
||||||
|
good_bitflag = 0;
|
||||||
|
unknown_101 = 0;
|
||||||
|
movement_speed = 0.0f;
|
||||||
|
|
||||||
|
unknown_102 = 0;
|
||||||
|
unknown_103 = 0;
|
||||||
|
unknown_104 = 0;
|
||||||
|
unknown_105 = 0;
|
||||||
|
unknown_106 = 0;
|
||||||
|
unknown_107 = 0;
|
||||||
|
unknown_108 = 0;
|
||||||
|
unknown_109 = 0;
|
||||||
|
unknown_110 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector< uint8_t > RealmCharacter::GetCharacterData() const
|
const std::vector< uint8_t > RealmCharacter::Serialize() const
|
||||||
{
|
{
|
||||||
return m_characterData;
|
auto writer = ByteBuffer( CHARACTER_DATA_SIZE );
|
||||||
|
|
||||||
|
/*writer.write_sz_utf8(name, 32);
|
||||||
|
|
||||||
|
for( const auto &val : unknown_000 )
|
||||||
|
{
|
||||||
|
writer.write_u8( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_sz_utf8( unknown_str, 32 );
|
||||||
|
|
||||||
|
for( const auto &val : unknown_004 )
|
||||||
|
{
|
||||||
|
writer.write_i32( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( static_cast< int32_t >( character_class ) );
|
||||||
|
writer.write_i32( static_cast< int32_t >( character_race ) );
|
||||||
|
|
||||||
|
writer.write_u8( current_level );
|
||||||
|
writer.write_u8( pending_level );
|
||||||
|
writer.write_u16( unknown_009 );
|
||||||
|
writer.write_i32( experience );
|
||||||
|
|
||||||
|
for( const auto &val : unknown_010 )
|
||||||
|
{
|
||||||
|
writer.write_i32( val );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( const auto &stat : stats )
|
||||||
|
{
|
||||||
|
writer.write( stat );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( unknown_016 );
|
||||||
|
writer.write_f32( current_hp );
|
||||||
|
writer.write_f32( maximum_hp );
|
||||||
|
writer.write_i32( unknown_017 );
|
||||||
|
writer.write_i32( unknown_018 );
|
||||||
|
writer.write_i32( unknown_019 );
|
||||||
|
writer.write_f32( current_mana );
|
||||||
|
writer.write_f32( maximum_mana );
|
||||||
|
writer.write_i32( unknown_020 );
|
||||||
|
writer.write_i32( attack_power );
|
||||||
|
writer.write_i32( minimum_damage );
|
||||||
|
writer.write_i32( maximum_damage );
|
||||||
|
writer.write_i32( unknown_021 );
|
||||||
|
writer.write_i32( unknown_022 );
|
||||||
|
writer.write_u8( unknown_023 );
|
||||||
|
writer.write_u8( unknown_024 );
|
||||||
|
writer.write_u8( unknown_025 );
|
||||||
|
writer.write_u8( unknown_026 );
|
||||||
|
writer.write_i32( current_gold );
|
||||||
|
writer.write_i32( current_skill_points );
|
||||||
|
writer.write_i16( current_ability_points );
|
||||||
|
writer.write_i16( has_spent_remaining_points );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_029 );
|
||||||
|
writer.write_i32( unknown_030 );
|
||||||
|
writer.write_i32( unknown_031 );
|
||||||
|
writer.write_i32( unknown_032 );
|
||||||
|
writer.write_i32( unknown_033 );
|
||||||
|
writer.write_i32( unknown_034 );
|
||||||
|
writer.write_i32( unknown_035 );
|
||||||
|
writer.write_i32( unknown_036 );
|
||||||
|
writer.write_i32( unknown_037 );
|
||||||
|
writer.write_i32( unknown_038 );
|
||||||
|
writer.write_i32( unknown_039 );
|
||||||
|
writer.write_i32( unknown_040 );
|
||||||
|
|
||||||
|
writer.write_f32( weight );
|
||||||
|
writer.write_f32( max_weight );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_041 );
|
||||||
|
writer.write_i32( unknown_042 );
|
||||||
|
|
||||||
|
for( const auto &item : item_data )
|
||||||
|
{
|
||||||
|
writer.write( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( num_armor_item );
|
||||||
|
writer.write_i32( unknown_043 );
|
||||||
|
for( const auto &item : armor_item_data )
|
||||||
|
{
|
||||||
|
writer.write( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( num_weapon_item );
|
||||||
|
writer.write_i32( unknown_044 );
|
||||||
|
for( const auto &item : weapon_item_data )
|
||||||
|
{
|
||||||
|
writer.write( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( num_consumable_item );
|
||||||
|
writer.write_i32( unknown_045 );
|
||||||
|
|
||||||
|
writer.forward( 2696 );
|
||||||
|
|
||||||
|
for( const auto &quest : quest_string )
|
||||||
|
{
|
||||||
|
writer.write_sz_utf8( quest.name, 32 );
|
||||||
|
writer.write_sz_utf8( quest.description, 32 );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( num_quests );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_048 );
|
||||||
|
writer.write_i32( unknown_049 );
|
||||||
|
writer.write_i32( unknown_050 );
|
||||||
|
writer.write_i32( unknown_051 );
|
||||||
|
writer.write_i32( unknown_052 );
|
||||||
|
writer.write_i32( unknown_053 );
|
||||||
|
writer.write_i32( unknown_054 );
|
||||||
|
|
||||||
|
for( const auto &slot : equipment )
|
||||||
|
{
|
||||||
|
writer.write_i32( slot );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( const auto &style : newStyle )
|
||||||
|
{
|
||||||
|
writer.write< s_new_style >( style );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( unknown_075 );
|
||||||
|
writer.write_f32( unknown_076 );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_077 );
|
||||||
|
writer.write_i32( unknown_078 );
|
||||||
|
writer.write_i32( unknown_079 );
|
||||||
|
writer.write_i32( unknown_080 );
|
||||||
|
writer.write_i32( unknown_081 );
|
||||||
|
writer.write_i32( unknown_082 );
|
||||||
|
writer.write_i32( unknown_083 );
|
||||||
|
writer.write_i32( unknown_084 );
|
||||||
|
writer.write_i32( unknown_085 );
|
||||||
|
|
||||||
|
for( const auto &skill : skill_slot )
|
||||||
|
{
|
||||||
|
writer.write_u8( skill );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8( unknown_087 );
|
||||||
|
writer.write_u8( unknown_088 );
|
||||||
|
|
||||||
|
for( const auto &attack : attackData )
|
||||||
|
{
|
||||||
|
writer.write< AttackData >( attack );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( const auto &skill : skills )
|
||||||
|
{
|
||||||
|
writer.write< s_skill >( skill );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_i32( unknown_091 );
|
||||||
|
|
||||||
|
for( const auto &progress : mission_progress )
|
||||||
|
{
|
||||||
|
writer.write< s_difficulty_progress >( progress );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( const auto &medal : mission_medals )
|
||||||
|
{
|
||||||
|
writer.write_u8( medal );
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8( evil_bitflag );
|
||||||
|
writer.write_u8( good_bitflag );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_101 );
|
||||||
|
|
||||||
|
writer.write_f32( movement_speed );
|
||||||
|
|
||||||
|
writer.write_u8( unknown_102 );
|
||||||
|
writer.write_u8( unknown_103 );
|
||||||
|
writer.write_u8( unknown_104 );
|
||||||
|
writer.write_u8( unknown_105 );
|
||||||
|
writer.write_u8( unknown_106 );
|
||||||
|
writer.write_u8( unknown_107 );
|
||||||
|
writer.write_u8( unknown_108 );
|
||||||
|
writer.write_u8( unknown_109 );
|
||||||
|
|
||||||
|
writer.write_i32( unknown_110 );*/
|
||||||
|
|
||||||
|
return writer.get_buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RealmCharacter::SetCharacterData( const std::vector<uint8_t> &data )
|
void RealmCharacter::Deserialize( const std::vector<uint8_t> &data )
|
||||||
{
|
{
|
||||||
m_characterData = data;
|
ByteBufferReader reader( data );
|
||||||
|
|
||||||
|
/*try
|
||||||
|
{
|
||||||
|
name = reader.readString( 32 );
|
||||||
|
|
||||||
|
for( auto &val : unknown_000 )
|
||||||
|
{
|
||||||
|
val = reader.read< uint8_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
unknown_str = reader.readString( 32 );
|
||||||
|
|
||||||
|
for( auto &val : unknown_004 )
|
||||||
|
{
|
||||||
|
val = reader.read< int32_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
character_class = static_cast< CharacterClass >( reader.read< int32_t >() );
|
||||||
|
character_race = static_cast< CharacterRace >( reader.read< int32_t >() );
|
||||||
|
|
||||||
|
current_level = reader.read< uint8_t >();
|
||||||
|
pending_level = reader.read< uint8_t >();
|
||||||
|
unknown_009 = reader.read< uint16_t >();
|
||||||
|
experience = reader.read< int32_t >();
|
||||||
|
|
||||||
|
for( auto &val : unknown_010 )
|
||||||
|
{
|
||||||
|
val = reader.read< int32_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( auto &stat : stats )
|
||||||
|
{
|
||||||
|
stat = reader.read< s_stats >();
|
||||||
|
}
|
||||||
|
|
||||||
|
unknown_016 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
current_hp = reader.read< float_t >();
|
||||||
|
maximum_hp = reader.read< float_t >();
|
||||||
|
|
||||||
|
unknown_017 = reader.read< int32_t >();
|
||||||
|
unknown_018 = reader.read< int32_t >();
|
||||||
|
unknown_019 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
current_mana = reader.read< float_t >();
|
||||||
|
maximum_mana = reader.read< float_t >();
|
||||||
|
|
||||||
|
unknown_020 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
attack_power = reader.read< int32_t >();
|
||||||
|
minimum_damage = reader.read< int32_t >();
|
||||||
|
maximum_damage = reader.read< int32_t >();
|
||||||
|
|
||||||
|
unknown_021 = reader.read< int32_t >();
|
||||||
|
unknown_022 = reader.read< int32_t >();
|
||||||
|
unknown_023 = reader.read< uint8_t >();
|
||||||
|
unknown_024 = reader.read< uint8_t >();
|
||||||
|
unknown_025 = reader.read< uint8_t >();
|
||||||
|
unknown_026 = reader.read< uint8_t >();
|
||||||
|
|
||||||
|
current_gold = reader.read< int32_t >();
|
||||||
|
current_skill_points = reader.read< int32_t >();
|
||||||
|
current_ability_points = reader.read< int16_t >();
|
||||||
|
has_spent_remaining_points = reader.read< int16_t >();
|
||||||
|
|
||||||
|
//
|
||||||
|
unknown_029 = reader.read< int32_t >();
|
||||||
|
unknown_030 = reader.read< int32_t >();
|
||||||
|
unknown_031 = reader.read< int32_t >();
|
||||||
|
unknown_032 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
//
|
||||||
|
unknown_033 = reader.read< int32_t >();
|
||||||
|
unknown_034 = reader.read< int32_t >();
|
||||||
|
unknown_035 = reader.read< int32_t >();
|
||||||
|
unknown_036 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
//
|
||||||
|
unknown_037 = reader.read< int32_t >();
|
||||||
|
unknown_038 = reader.read< int32_t >();
|
||||||
|
unknown_039 = reader.read< int32_t >();
|
||||||
|
unknown_040 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
weight = reader.read< float_t >();
|
||||||
|
max_weight = reader.read< float_t >();
|
||||||
|
|
||||||
|
unknown_041 = reader.read< int32_t >();
|
||||||
|
unknown_042 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
item_data = reader.readArray< ItemData, 64>();
|
||||||
|
|
||||||
|
num_armor_item = reader.read< int32_t >();
|
||||||
|
unknown_043 = reader.read< int32_t >();
|
||||||
|
armor_item_data = reader.readArray< ItemData, 64>();
|
||||||
|
|
||||||
|
num_weapon_item = reader.read< int32_t >();
|
||||||
|
unknown_044 = reader.read< int32_t >();
|
||||||
|
weapon_item_data = reader.readArray< ItemData, 64>();
|
||||||
|
|
||||||
|
num_consumable_item = reader.read< int32_t >();
|
||||||
|
unknown_045 = reader.read< int32_t >();
|
||||||
|
unknown_046.fill( 0x00 ); // Fill with zeros, size is 2696 bytes
|
||||||
|
|
||||||
|
for( auto &quest : quest_string )
|
||||||
|
{
|
||||||
|
quest = reader.read< s_quest >();
|
||||||
|
}
|
||||||
|
num_quests = reader.read< int32_t >();
|
||||||
|
|
||||||
|
unknown_048 = reader.read< int32_t >();
|
||||||
|
unknown_049 = reader.read< int32_t >();
|
||||||
|
unknown_050 = reader.read< int32_t >();
|
||||||
|
unknown_051 = reader.read< int32_t >();
|
||||||
|
unknown_052 = reader.read< int32_t >();
|
||||||
|
unknown_053 = reader.read< int32_t >();
|
||||||
|
unknown_054 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
for( auto &slot : equipment )
|
||||||
|
{
|
||||||
|
slot = reader.read< int32_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( auto &style : newStyle )
|
||||||
|
{
|
||||||
|
style = reader.read< s_new_style >();
|
||||||
|
}
|
||||||
|
|
||||||
|
unknown_075 = reader.read< int32_t >();
|
||||||
|
unknown_076 = reader.read< float_t >();
|
||||||
|
unknown_077 = reader.read< int32_t >();
|
||||||
|
unknown_078 = reader.read< int32_t >();
|
||||||
|
unknown_079 = reader.read< int32_t >();
|
||||||
|
unknown_080 = reader.read< int32_t >();
|
||||||
|
unknown_081 = reader.read< int32_t >();
|
||||||
|
unknown_082 = reader.read< int32_t >();
|
||||||
|
unknown_083 = reader.read< int32_t >();
|
||||||
|
unknown_084 = reader.read< int32_t >();
|
||||||
|
unknown_085 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
for( auto &skill : skill_slot )
|
||||||
|
{
|
||||||
|
skill = reader.read< uint8_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
unknown_087 = reader.read< uint8_t >();
|
||||||
|
unknown_088 = reader.read< uint8_t >();
|
||||||
|
|
||||||
|
for( auto &attack : attackData )
|
||||||
|
{
|
||||||
|
attack = reader.read< AttackData >();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( auto &skill : skills )
|
||||||
|
{
|
||||||
|
skill.skill_id = reader.read< int16_t >();
|
||||||
|
skill.skill_level = reader.read< int16_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
unknown_091 = reader.read< int32_t >();
|
||||||
|
|
||||||
|
for( auto &val : mission_progress )
|
||||||
|
{
|
||||||
|
val = reader.read< s_difficulty_progress >();
|
||||||
|
}
|
||||||
|
|
||||||
|
for( auto &val : mission_medals )
|
||||||
|
{
|
||||||
|
val = reader.read< uint8_t >();
|
||||||
|
}
|
||||||
|
|
||||||
|
evil_bitflag = reader.read< uint8_t >();
|
||||||
|
good_bitflag = reader.read< uint8_t >();
|
||||||
|
|
||||||
|
unknown_101 = reader.read< int32_t >();
|
||||||
|
movement_speed = reader.read< float_t >();
|
||||||
|
|
||||||
|
unknown_102 = reader.read< uint8_t >();
|
||||||
|
unknown_103 = reader.read< uint8_t >();
|
||||||
|
unknown_104 = reader.read< uint8_t >();
|
||||||
|
unknown_105 = reader.read< uint8_t >();
|
||||||
|
unknown_106 = reader.read< uint8_t >();
|
||||||
|
unknown_107 = reader.read< uint8_t >();
|
||||||
|
unknown_108 = reader.read< uint8_t >();
|
||||||
|
unknown_109 = reader.read< uint8_t >();
|
||||||
|
|
||||||
|
unknown_110 = reader.read< int32_t >();
|
||||||
|
}
|
||||||
|
catch( const std::out_of_range &e )
|
||||||
|
{
|
||||||
|
Log::Error( "Failed to deserialize RealmCharacter: %s", e.what() );
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector< uint8_t > RealmCharacter::GetInventoryData() const
|
bool RealmCharacter::ValidateData()
|
||||||
{
|
{
|
||||||
return m_inventoryData;
|
if( m_data.empty() || m_data.size() < CHARACTER_DATA_SIZE )
|
||||||
|
{
|
||||||
|
Log::Error( "Character data is invalid or too small!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( current_level < 1 || current_level > 80 )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid character level: %d", current_level );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( current_hp < 1.0f || current_hp > maximum_hp )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid HP values: current %f, maximum %f", current_hp, maximum_hp );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( current_mana < 0.0f || current_mana > maximum_mana )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid mana values: current %f, maximum %f", current_mana, maximum_mana );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( weight < 0.0f || weight > max_weight )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid weight values: current %f, maximum %f", weight, max_weight );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( movement_speed <= 0.0f )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid movement speed: %f", movement_speed );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( const auto &mission : mission_progress )
|
||||||
|
{
|
||||||
|
if( mission.mission_na < 0 || mission.mission_na > 3 )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid mission NA value: %d", mission.mission_na );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mission.mission_Disease < 0 || mission.mission_Disease > 3 )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid mission Disease value: %d", mission.mission_Disease );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mission.mission_Fear < 0 || mission.mission_Fear > 3 )
|
||||||
|
{
|
||||||
|
Log::Error( "Invalid mission Fear value: %d", mission.mission_Fear );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RealmCharacter::SetInventoryData( const std::vector<uint8_t> &data )
|
CharacterSlotData RealmCharacter::GetMetaData() const
|
||||||
{
|
|
||||||
m_inventoryData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
RealmCharacterMetaData RealmCharacter::GetMetaData() const
|
|
||||||
{
|
{
|
||||||
return m_metaData;
|
return m_metaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RealmCharacter::SetMetaData( const std::vector<uint8_t> &data )
|
void RealmCharacter::SetMetaData( sptr_byte_stream stream )
|
||||||
{
|
{
|
||||||
ByteBuffer stream( data );
|
|
||||||
m_metaData.Deserialize( stream );
|
m_metaData.Deserialize( stream );
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8_t> RealmCharacter::Unpack()
|
std::vector<uint8_t> RealmCharacter::Unpack()
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> output;
|
std::vector< uint8_t > output;
|
||||||
const int expectedOutputSize = 19504;
|
size_t read = 4;
|
||||||
const size_t inputSize = m_characterData.size();
|
|
||||||
const uint8_t* input = m_characterData.data();
|
|
||||||
output.reserve(expectedOutputSize);
|
|
||||||
|
|
||||||
try
|
while( read < m_data.size() )
|
||||||
{
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < inputSize && output.size() < expectedOutputSize) {
|
|
||||||
uint8_t byte = input[i++];
|
|
||||||
if (byte != 0x00) {
|
|
||||||
output.push_back(byte);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (i >= inputSize) {
|
|
||||||
throw std::runtime_error("Invalid blob format: zero-byte with no length");
|
|
||||||
}
|
|
||||||
uint8_t next = input[i++];
|
|
||||||
if (next == 0x00) {
|
|
||||||
// Escaped literal 0x00 byte
|
|
||||||
output.push_back(0x00);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Insert `next` zero bytes
|
|
||||||
output.insert(output.end(), next, 0x00);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
{
|
||||||
Log::Error("Error unpacking character data: " + std::string(e.what()));
|
uint8_t byte = m_data[ read++ ];
|
||||||
|
output.push_back( byte );
|
||||||
|
|
||||||
|
if( byte == 0x00 )
|
||||||
|
{
|
||||||
|
if( read >= m_data.size() )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t count = m_data[ read++ ];
|
||||||
|
output.insert( output.end(), count, 0x00 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have exactly 19504 bytes
|
return output;
|
||||||
if (output.size() != expectedOutputSize)
|
|
||||||
{
|
|
||||||
Log::Error("Unpacked character data size mismatch: expected " + std::to_string(expectedOutputSize) + ", got " + std::to_string(output.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> RealmCharacter::Pack(const std::vector<uint8_t>& input)
|
|
||||||
{
|
|
||||||
constexpr size_t ExpectedInputSize = 19504;
|
|
||||||
if (input.size() != ExpectedInputSize)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Input to compressor must be exactly 19504 bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> output;
|
|
||||||
|
|
||||||
// Optional: add 4-byte header if game expects it (match decompress behavior)
|
|
||||||
output.insert(output.end(), { 0x00, 0x00, 0x00, 0x00 }); // Placeholder header
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < input.size())
|
|
||||||
{
|
|
||||||
uint8_t byte = input[i];
|
|
||||||
if (byte != 0x00)
|
|
||||||
{
|
|
||||||
// Literal non-zero byte, just write it
|
|
||||||
output.push_back(byte);
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Zero run: count how many consecutive 0x00s (max 255)
|
|
||||||
size_t zeroCount = 0;
|
|
||||||
while (i < input.size() && input[i] == 0x00 && zeroCount < 255)
|
|
||||||
{
|
|
||||||
++zeroCount;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_back(0x00); // Zero marker
|
|
||||||
output.push_back(static_cast<uint8_t>(zeroCount)); // Count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
@@ -1,40 +1,170 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "../Common/Constant.h"
|
||||||
#include "../Common/ByteStream.h"
|
#include "../Common/ByteStream.h"
|
||||||
#include "RealmCharacterMetaKV.h"
|
#include "RealmCharacterMetaKV.h"
|
||||||
|
|
||||||
|
constexpr size_t MAX_NUMBER_OF_CHARACTERS = 12;
|
||||||
|
constexpr size_t CHARACTER_DATA_SIZE = 19504;
|
||||||
|
|
||||||
|
using ItemData = std::array< uint8_t, 72 >;
|
||||||
|
using AttackData = std::array< float_t, 4 >;
|
||||||
|
|
||||||
class RealmCharacter {
|
class RealmCharacter {
|
||||||
public:
|
public:
|
||||||
// Character metadata, such as name, class, etc.
|
RealmCharacter();
|
||||||
// This is used for displaying character information in the character selection screen.
|
RealmCharacter( const int32_t id, const CharacterSlotData meta, const std::vector< uint8_t > data );
|
||||||
RealmCharacterMetaData m_metaData;
|
~RealmCharacter();
|
||||||
|
|
||||||
// Character data blob, such as stats, skills, etc.
|
void Initialize();
|
||||||
std::vector< uint8_t > m_characterData;
|
const std::vector< uint8_t > Serialize() const;
|
||||||
|
void Deserialize( const std::vector< uint8_t > &data );
|
||||||
|
|
||||||
// Inventory data blob
|
bool ValidateData();
|
||||||
std::vector< uint8_t > m_inventoryData;
|
|
||||||
|
CharacterSlotData GetMetaData() const;
|
||||||
|
void SetMetaData( sptr_byte_stream stream );
|
||||||
|
|
||||||
|
std::vector< uint8_t > Unpack();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RealmCharacter() = default;
|
uint32_t m_characterId;
|
||||||
RealmCharacter( std::vector< uint8_t > &data );
|
CharacterSlotData m_metaData;
|
||||||
RealmCharacter(sptr_byte_stream stream);
|
std::vector< uint8_t > m_data;
|
||||||
|
|
||||||
const std::vector< uint8_t > GetCharacterData() const;
|
public:
|
||||||
void SetCharacterData( const std::vector< uint8_t > &data );
|
std::string name;
|
||||||
|
uint8_t unknown_000[ 4 ];
|
||||||
|
std::string unknown_str;
|
||||||
|
int32_t unknown_004[ 3 ];
|
||||||
|
|
||||||
const std::vector< uint8_t > GetInventoryData() const;
|
CharacterClass character_class;
|
||||||
void SetInventoryData( const std::vector< uint8_t > &data );
|
CharacterRace character_race;
|
||||||
|
|
||||||
RealmCharacterMetaData GetMetaData() const;
|
uint8_t current_level;
|
||||||
void SetMetaData( const std::vector< uint8_t > &data );
|
uint8_t pending_level; // Waiting to spend points, basically.
|
||||||
|
uint16_t unknown_009;
|
||||||
|
int32_t experience;
|
||||||
|
|
||||||
const std::vector< uint8_t > Unpack();
|
int32_t unknown_010[ 6 ];
|
||||||
std::vector< uint8_t > Pack(const std::vector<uint8_t>& input);
|
|
||||||
|
struct s_stats {
|
||||||
|
int32_t strength, intelligence, dexterity, stamina, unknown_a, unknown_b;
|
||||||
|
} stats[ 2 ];
|
||||||
|
|
||||||
|
int32_t unknown_016;
|
||||||
|
float_t current_hp, maximum_hp;
|
||||||
|
int32_t unknown_017, unknown_018, unknown_019;
|
||||||
|
float_t current_mana, maximum_mana;
|
||||||
|
int32_t unknown_020;
|
||||||
|
int32_t attack_power, minimum_damage, maximum_damage;
|
||||||
|
int32_t unknown_021, unknown_022;
|
||||||
|
|
||||||
|
uint8_t unknown_023, unknown_024, unknown_025, unknown_026;
|
||||||
|
int32_t current_gold, current_skill_points;
|
||||||
|
int16_t current_ability_points;
|
||||||
|
int16_t has_spent_remaining_points;
|
||||||
|
|
||||||
|
int32_t unknown_029, unknown_030, unknown_031, unknown_032;
|
||||||
|
int32_t unknown_033, unknown_034, unknown_035, unknown_036;
|
||||||
|
int32_t unknown_037, unknown_038, unknown_039, unknown_040;
|
||||||
|
|
||||||
|
float_t weight, max_weight;
|
||||||
|
int32_t unknown_041, unknown_042;
|
||||||
|
|
||||||
|
std::array<ItemData, 64> item_data;
|
||||||
|
int32_t num_armor_item, unknown_043;
|
||||||
|
std::array<ItemData, 64> armor_item_data;
|
||||||
|
|
||||||
|
int32_t num_weapon_item, unknown_044;
|
||||||
|
std::array<ItemData, 64> weapon_item_data;
|
||||||
|
|
||||||
|
int32_t num_consumable_item, unknown_045;
|
||||||
|
std::array< uint8_t, 2696 > unknown_046;
|
||||||
|
|
||||||
|
struct s_quest {
|
||||||
|
char name[ 32 ];
|
||||||
|
char description[ 32 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<s_quest, 8> quest_string;
|
||||||
|
|
||||||
|
int32_t num_quests;
|
||||||
|
|
||||||
|
int32_t unknown_048;
|
||||||
|
int32_t unknown_049;
|
||||||
|
int32_t unknown_050;
|
||||||
|
int32_t unknown_051;
|
||||||
|
int32_t unknown_052;
|
||||||
|
int32_t unknown_053;
|
||||||
|
int32_t unknown_054;
|
||||||
|
|
||||||
|
std::array< int32_t, 20 > equipment;
|
||||||
|
|
||||||
|
struct s_new_style {
|
||||||
|
char name[ 32 ]; // "newstyle"
|
||||||
|
int id;
|
||||||
|
uint8_t active_flag;
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t unknown_a;
|
||||||
|
uint8_t unknown_b;
|
||||||
|
uint8_t reserved[ 64 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<s_new_style, 15> newStyle;
|
||||||
|
|
||||||
|
int32_t unknown_075;
|
||||||
|
float_t unknown_076;
|
||||||
|
|
||||||
|
int32_t
|
||||||
|
unknown_077, unknown_078, unknown_079,
|
||||||
|
unknown_080, unknown_081, unknown_082,
|
||||||
|
unknown_083, unknown_084, unknown_085;
|
||||||
|
|
||||||
|
uint8_t skill_slot[ 2 ];
|
||||||
|
uint8_t unknown_087, unknown_088;
|
||||||
|
|
||||||
|
std::array< AttackData, 8> attackData;
|
||||||
|
|
||||||
|
struct s_skill {
|
||||||
|
int16_t skill_id;
|
||||||
|
int16_t skill_level;
|
||||||
|
};
|
||||||
|
std::array<s_skill, 48> skills;
|
||||||
|
|
||||||
|
int32_t unknown_091;
|
||||||
|
|
||||||
|
struct s_difficulty_progress {
|
||||||
|
uint8_t mission_na;
|
||||||
|
uint8_t mission_War;
|
||||||
|
uint8_t mission_Innovation;
|
||||||
|
uint8_t mission_PitOfIllOmen;
|
||||||
|
uint8_t mission_PlaneOfWater;
|
||||||
|
uint8_t mission_Torment;
|
||||||
|
uint8_t mission_Disease;
|
||||||
|
uint8_t mission_Valor;
|
||||||
|
uint8_t mission_Fire;
|
||||||
|
uint8_t mission_Storms;
|
||||||
|
uint8_t mission_Faydark;
|
||||||
|
uint8_t mission_Nightmares;
|
||||||
|
uint8_t mission_Fear;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<s_difficulty_progress, 5> mission_progress;
|
||||||
|
std::array< uint8_t, 13 > mission_medals;
|
||||||
|
|
||||||
|
uint8_t evil_bitflag, good_bitflag;
|
||||||
|
int32_t unknown_101;
|
||||||
|
float_t movement_speed;
|
||||||
|
|
||||||
|
uint8_t unknown_102, unknown_103, unknown_104, unknown_105;
|
||||||
|
uint8_t unknown_106, unknown_107, unknown_108, unknown_109;
|
||||||
|
int32_t unknown_110;
|
||||||
};
|
};
|
||||||
|
|
||||||
using sptr_realm_character = std::shared_ptr< RealmCharacter >;
|
using sptr_realm_character = std::shared_ptr< RealmCharacter >;
|
||||||
@@ -1,20 +1,28 @@
|
|||||||
#include "RealmCharacterMetaKV.h"
|
#include "RealmCharacterMetaKV.h"
|
||||||
|
|
||||||
|
#include "../Common/ByteBufferReader.hpp"
|
||||||
#include "../Common/ByteStream.h"
|
#include "../Common/ByteStream.h"
|
||||||
#include "../Common/Utility.h"
|
#include "../Common/Utility.h"
|
||||||
#include "../logging.h"
|
#include "../logging.h"
|
||||||
|
|
||||||
RealmCharacterMetaData::RealmCharacterMetaData( const std::vector<uint8_t> &data )
|
CharacterSlotData::CharacterSlotData(const std::vector<uint8_t>& data)
|
||||||
{
|
{
|
||||||
ByteBuffer stream( data );
|
if( !data.empty() )
|
||||||
Deserialize( stream );
|
{
|
||||||
|
Deserialize(data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_metaData.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RealmCharacterMetaData::Deserialize( ByteBuffer &stream )
|
void CharacterSlotData::Deserialize(const std::vector< uint8_t >& data)
|
||||||
{
|
{
|
||||||
auto numberOfKeys = stream.read_u32();
|
ByteBuffer reader(data);
|
||||||
|
|
||||||
if( !Util::IsInRange< int32_t >( numberOfKeys, 0, 4 ) )
|
auto numberOfKeys = reader.read_i32();
|
||||||
|
if( !Util::IsInRange( numberOfKeys, 0, 4 ) )
|
||||||
{
|
{
|
||||||
m_metaData.clear();
|
m_metaData.clear();
|
||||||
return;
|
return;
|
||||||
@@ -25,25 +33,43 @@ void RealmCharacterMetaData::Deserialize( ByteBuffer &stream )
|
|||||||
|
|
||||||
for( auto i = 0; i < numberOfKeys; ++i )
|
for( auto i = 0; i < numberOfKeys; ++i )
|
||||||
{
|
{
|
||||||
std::wstring key = stream.read_utf16();
|
std::wstring key = reader.read_utf16();
|
||||||
m_metaData.emplace_back( key, L"" );
|
m_metaData.emplace_back( key, L"" );
|
||||||
}
|
}
|
||||||
|
|
||||||
auto numberOfValues = stream.read_u32();
|
auto numberOfValues = reader.read_i32();
|
||||||
|
|
||||||
for( auto &pair : m_metaData )
|
for( auto &pair : m_metaData )
|
||||||
{
|
{
|
||||||
pair.second = stream.read_utf16();
|
pair.second = reader.read_utf16();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RealmCharacterMetaData::Deserialize( const std::vector<uint8_t> &data )
|
void CharacterSlotData::Deserialize(const sptr_byte_stream stream)
|
||||||
{
|
{
|
||||||
ByteBuffer stream( data );
|
auto numberOfKeys = stream->read_i32();
|
||||||
Deserialize( stream );
|
if (!Util::IsInRange(numberOfKeys, 0, 4))
|
||||||
|
{
|
||||||
|
m_metaData.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_metaData.clear();
|
||||||
|
m_metaData.reserve(numberOfKeys);
|
||||||
|
|
||||||
|
for (auto i = 0; i < numberOfKeys; ++i)
|
||||||
|
{
|
||||||
|
std::wstring key = stream->read_utf16();
|
||||||
|
m_metaData.emplace_back(key, L"");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto numberOfValues = stream->read_i32();
|
||||||
|
for (auto& pair : m_metaData)
|
||||||
|
{
|
||||||
|
pair.second = stream->read_utf16();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector< uint8_t > RealmCharacterMetaData::Serialize() const
|
std::vector< uint8_t > CharacterSlotData::Serialize() const
|
||||||
{
|
{
|
||||||
ByteBuffer stream;
|
ByteBuffer stream;
|
||||||
|
|
||||||
|
|||||||
@@ -6,32 +6,37 @@
|
|||||||
|
|
||||||
#include "../Common/ByteStream.h"
|
#include "../Common/ByteStream.h"
|
||||||
|
|
||||||
using CharacterMetaKV = std::pair<std::wstring, std::wstring>;
|
using CharacterAttributeKV = std::pair<std::wstring, std::wstring>;
|
||||||
|
|
||||||
class RealmCharacterMetaData {
|
class CharacterSlotData {
|
||||||
private:
|
private:
|
||||||
std::vector<CharacterMetaKV> m_metaData;
|
std::vector<CharacterAttributeKV> m_metaData;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RealmCharacterMetaData() = default;
|
CharacterSlotData() = default;
|
||||||
explicit RealmCharacterMetaData( const std::vector<uint8_t> &data );
|
CharacterSlotData( const std::vector< uint8_t > &data );
|
||||||
|
|
||||||
void Deserialize( ByteBuffer &stream );
|
void Deserialize( const std::vector< uint8_t > &data );
|
||||||
void Deserialize( const std::vector<uint8_t> &data );
|
void Deserialize( const sptr_byte_stream stream );
|
||||||
|
|
||||||
std::vector<uint8_t> Serialize() const;
|
std::vector<uint8_t> Serialize() const;
|
||||||
|
|
||||||
const std::vector<CharacterMetaKV> &GetMetaData() const
|
bool empty() const
|
||||||
{
|
{
|
||||||
return m_metaData;
|
return m_metaData.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMetaData( const std::vector<CharacterMetaKV> &metaData )
|
const std::vector<CharacterAttributeKV> &GetMetaData() const
|
||||||
{
|
{
|
||||||
m_metaData = metaData;
|
return m_metaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring GetValue( std::wstring key )
|
void SetMetaData( const std::vector<CharacterAttributeKV> &metaData )
|
||||||
|
{
|
||||||
|
m_metaData = metaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring GetValue( std::wstring key )
|
||||||
{
|
{
|
||||||
for( const auto &kv : m_metaData )
|
for( const auto &kv : m_metaData )
|
||||||
{
|
{
|
||||||
|
|||||||
105
Game/RealmCharacterSaveTask.cpp
Normal file
105
Game/RealmCharacterSaveTask.cpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include "CharacterSaveTask.h"
|
||||||
|
#include "RealmCharacter.h"
|
||||||
|
|
||||||
|
#include "../Common/ByteBufferReader.hpp"
|
||||||
|
#include "../../logging.h"
|
||||||
|
|
||||||
|
CharacterSaveTask::CharacterSaveTask( CharacterSaveType Type, uint32_t characterId )
|
||||||
|
{
|
||||||
|
m_saveType = Type;
|
||||||
|
m_ownerUser.reset();
|
||||||
|
m_targetUser.reset();
|
||||||
|
|
||||||
|
m_characterId = characterId;
|
||||||
|
m_writePosition = 0;
|
||||||
|
|
||||||
|
m_data.reserve(4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterSaveTask::~CharacterSaveTask()
|
||||||
|
{
|
||||||
|
m_ownerUser.reset();
|
||||||
|
m_targetUser.reset();
|
||||||
|
m_data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterSaveTask::SetMetaData( const CharacterSlotData &metaData )
|
||||||
|
{
|
||||||
|
this->m_meta = metaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterSaveTask::AppendData( const std::vector< uint8_t > &data )
|
||||||
|
{
|
||||||
|
if( data.empty() )
|
||||||
|
{
|
||||||
|
Log::Error( "AppendTailData called with empty data!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_data.insert( m_data.end(), data.begin(), data.end() );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterSaveTask::Validate()
|
||||||
|
{
|
||||||
|
if( m_data.empty() )
|
||||||
|
{
|
||||||
|
Log::Error( "EndUpdate called with empty pending data!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_meta.empty() )
|
||||||
|
{
|
||||||
|
Log::Error( "EndUpdate called with empty metadata!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the size of the decompressed buffer
|
||||||
|
size_t read = 4;
|
||||||
|
size_t decompressedSize = 0;
|
||||||
|
size_t lastUsedByte = read;
|
||||||
|
|
||||||
|
while( read < m_data.size() )
|
||||||
|
{
|
||||||
|
uint8_t byte = m_data[ read++ ];
|
||||||
|
decompressedSize += 1;
|
||||||
|
lastUsedByte = read;
|
||||||
|
|
||||||
|
if( byte == 0x00 )
|
||||||
|
{
|
||||||
|
if( read >= m_data.size() )
|
||||||
|
{
|
||||||
|
Log::Error( "Unexpected end of data during decompression" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t count = m_data[ read++ ];
|
||||||
|
decompressedSize += count;
|
||||||
|
lastUsedByte = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( decompressedSize >= CHARACTER_DATA_SIZE )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( decompressedSize < CHARACTER_DATA_SIZE )
|
||||||
|
{
|
||||||
|
Log::Error( "Failed to decompress pending character data. More data needed!" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Better error checking.
|
||||||
|
|
||||||
|
// Trim Garbage from the compressed buffer.
|
||||||
|
m_data.resize( lastUsedByte );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t> &CharacterSaveTask::GetData() const
|
||||||
|
{
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user