0x1 Genshin Impact Leaks Story
So someone in mihoyo leaks significantly amount of data from official production server, rumors said this is his revenge for being fired, but I heard he got caught already. RIP.
Leaked data consists of all server side configuration data( some shell scripts ), compiled game server binary(3.2 dev ~ 3.4 dev) and game related data( all game characters skill/talent till 4.0 dev etc…).
This leak is pretty huge, we can see all the characters skills till 4.0 dev, even some of them aren’t really useful because it is unfinished. Many people already publish what they found like Alhatham skillset. This part is easy because they are all just plain text json files.
As a junior reverse engineer, I’d like to challenge myself with the real big guy, like the real game server binary(2.4 Gb).
I’m reversing the 3.4 dev game server binary, which is newest in leaked data.
Loading such a large binary in IDA is pain in the ass, I spent almost 2 days just waiting IDA finish it, and the final idb was 11.3 Gb, largest idb I have seen in my life.
IDA recognizes 1598472 functions, thank god this file has dwarf debug information with full symbols and structure definitions, or I’ve given up already lol.
0x2 Artifact Generation Logic
The first thing I want to inspect was how the game server generate artifacts, or does it has bias for some properties.
BTW, artifacts and weapons has almost same logic, seems they all called reliquary in code.
Let’s see how reliquary initializes, this function seems like handle the reliquary initializations:
int32_t __cdecl Reliquary::init(Reliquary *const this, Player *player, bool is_first_create)
{
....
if ( is_first_create )
{
ItemComp = Player::getItemComp(player);
ItemId = Item::getItemId(this);
main_prop_id = PlayerItemComp::generateReliquaryMainPropId(ItemComp, ItemId);
if ( !main_prop_id )
{
common::milog::MiLogStream::create(
&v25,
&common::milog::MiLogDefault::default_log_obj_,
4u,
"./src/player/item/reliquary.cpp",
"init",
165);
v12 = common::milog::MiLogStream::operator<<<char [45],(char *[45])0>(
&v25,
(const char (*)[45])"generateReliquaryMainPropId failed, item_id:");
val = Item::getItemId(this);
common::milog::MiLogStream::operator<<<unsigned int,(unsigned int *)0>(v12, &val);
common::milog::MiLogStream::~MiLogStream(&v25);
result = -1;
goto LABEL_29;
}
Reliquary::setMainPropId(this, main_prop_id);
std::vector<unsigned int>::clear(&this->append_prop_id_vec_);
for ( idx = 0; ; ++idx )
{
if ( *(_BYTE *)(((unsigned __int64)&reliquary_config_ptr->append_prop_num >> 3) + 0x7FFF8000) != 0
&& (char)((((_BYTE)reliquary_config_ptr + 92) & 7) + 3) >= *(_BYTE *)(((unsigned __int64)&reliquary_config_ptr->append_prop_num >> 3)
+ 0x7FFF8000) )
{
__asan_report_load4(&reliquary_config_ptr->append_prop_num);
}
if ( idx >= reliquary_config_ptr->append_prop_num )
break;
ServiceBox::findService<GameserverService>();
GameserverService::getConfig((GameserverService *const)&v24);
p_reliquary_config_mgr = &std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false>::operator->((const std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false> *const)&v24)->design_config.txt_config_mgr.reliquary_config_mgr;
MainPropId = Reliquary::getMainPropId(this);
v14 = Item::getItemId(this);
*(_DWORD *)(v3 + 32) = ReliquaryExcelConfigMgr::generateAppendPropId(
p_reliquary_config_mgr,
v14,
MainPropId,
&this->append_prop_id_vec_);
....
}
As you can see, it generates main property first, and then generates all sub properties(append props).
Seems legit, let’s see how it generate main property.
0x3 Artifact Main Property Generation
Looks like main property generation is handled by PlayerItemComp::generateReliquaryMainPropId, let’s inspect it.
....
v99 = std::__detail::_Node_const_iterator<std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>,false,false>::operator*(&__for_begin);
main_prop_id = std::get<0ul,unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>(v99);
guarantee_config = (std::tuple_element<1,const std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig> >::type *)std::get<1ul,unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>(v99);
guarantee_data = std::map<unsigned int,ReliquaryMainPropGuaranteeData>::operator[](
(std::map<unsigned int,ReliquaryMainPropGuaranteeData> *const)(v2 + 176),
main_prop_id);
old_guarantee_data_ptr = common::tools::MiscUtils::findMapValuePointer<std::map<unsigned int,ReliquaryMainPropGuaranteeData>>(
&guarantee_depot->main_prop_guarantee_data_map,
main_prop_id);
if ( old_guarantee_data_ptr )
I smelled something unusual by just scrolling the code, what the heck is guarantee_config ?
Looks like our artifact isn’t really fully random, they implemented some sort of pity function.
At very first of the function, it calls another function to generate a main property:
ServiceBox::findService<GameserverService>();
GameserverService::getConfig((GameserverService *const)(v2 + 144));
reliquary_config_mgr = &std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false>::operator->((const std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false> *const)(v2 + 144))->design_config.txt_config_mgr.reliquary_config_mgr;
std::shared_ptr<Config>::~shared_ptr((std::shared_ptr<Config> *const)(v2 + 144));
*(_DWORD *)(v2 + 32) = ReliquaryExcelConfigMgr::generateMainPropId(reliquary_config_mgr, *(_DWORD *)(v2 + 96));
reliquary_config_ptr = data::ReliquaryExcelConfigMgrBase::findReliquaryExcelConfig(
reliquary_config_mgr,
*(unsigned int *)(v2 + 96));
Let’s jump into ReliquaryExcelConfigMgr::generateMainPropId .
....
*(_DWORD *)(v2 + 80) = item_id;
reliquary_config_ptr = data::ReliquaryExcelConfigMgrBase::findReliquaryExcelConfig(this, *(unsigned int *)(v2 + 80));
....
if ( common::tools::RandomUtils::weightSelectOne<data::ReliquaryMainPropExcelConfig,unsigned int data::ReliquaryMainPropExcelConfig::*>(
main_prop_vec,
(uint32_t *)(v2 + 64),
(unsigned int *)0x14,
0) )
{
....
v15 = *(unsigned int *)(v2 + 64);
if ( v15 < std::vector<data::ReliquaryMainPropExcelConfig>::size(main_prop_vec) )
{
if ( *(_BYTE *)(((v2 + 64) >> 3) + 0x7FFF8000) != 0 && *(_BYTE *)(((v2 + 64) >> 3) + 0x7FFF8000) <= 3 )
__asan_report_load4(v2 + 64);
v20 = std::vector<data::ReliquaryMainPropExcelConfig>::operator[](main_prop_vec, *(unsigned int *)(v2 + 64));
if ( *(_BYTE *)(((unsigned __int64)&v20->id >> 3) + 0x7FFF8000) != 0
&& *(_BYTE *)(((unsigned __int64)&v20->id >> 3) + 0x7FFF8000) <= 3 )
{
__asan_report_load4(&v20->id);
}
result = v20->id;
....
I remove some asan stuff to simplify the procedure, as you can see, the server called common::tools::RandomUtils::weightSelectOne to select the main property, this function name implies the main property is selected via some weights, not really pure random.
In weightSelectOne:
for ( idx = 0LL; idx < std::vector<data::ReliquaryMainPropExcelConfig>::size(input_vec); ++idx )
{
v7 = (unsigned __int64)member + (_QWORD)std::vector<data::ReliquaryMainPropExcelConfig>::operator[](input_vec, idx);
new_sum_weight = *(_DWORD *)v7 + sum_weight;
if ( new_sum_weight < sum_weight )
goto LABEL_12;
v8 = (unsigned int *)((char *)member
+ (_QWORD)std::vector<data::ReliquaryMainPropExcelConfig>::operator[](input_vec, idx));
if ( new_sum_weight >= *v8 )
v9 = 0;
else
LABEL_12:
v9 = 1;
if ( v9 )
{
result = -1;
goto LABEL_40;
}
sum_weight = new_sum_weight;
}
.....
*select_idx = 0;
if ( random_seed )
rand_weight = common::tools::RandomUtils::rand<unsigned int>(random_seed) % sum_weight;
else
rand_weight = common::tools::RandomUtils::rand<unsigned int>() % sum_weight;
for ( idx_0 = 0LL; idx_0 < std::vector<data::ReliquaryMainPropExcelConfig>::size(input_vec); ++idx_0 )
{
v12 = (unsigned int *)((char *)member
+ (_QWORD)std::vector<data::ReliquaryMainPropExcelConfig>::operator[](input_vec, idx_0));
if ( rand_weight < *v12 )
{
v13 = idx_0;
*select_idx = v13;
break;
}
v14 = (unsigned __int64)member
+ (_QWORD)std::vector<data::ReliquaryMainPropExcelConfig>::operator[](input_vec, idx_0);
rand_weight -= *(_DWORD *)v14;
}
result = 0;
Looks like a standard random bias selection algorithm, the member parameter(0x14 here) indicates which column contains weight data, the seed is 0 means no seed.
Since this data in configured in server side, we cannot know server config without access to the prod server, but the leaker actually dumped the server config so here’s the table!
Main property ID(Parsed by me) | Main depot ID(Parsed) | Type | Weights |
FIGHT_PROP_HP | Sand-1000 | 2 | 0 |
FIGHT_PROP_HP_PERCENT | Sand-1000 | 3 | 1334 |
FIGHT_PROP_ATTACK | Sand-1000 | 5 | 0 |
FIGHT_PROP_ATTACK_PERCENT | Sand-1000 | 6 | 1333 |
FIGHT_PROP_DEFENSE | Sand-1000 | 8 | 0 |
FIGHT_PROP_DEFENSE_PERCENT | Sand-1000 | 9 | 1333 |
FIGHT_PROP_CHARGE_EFFICIENCY | Sand-1000 | 23 | 500 |
FIGHT_PROP_ELEMENT_MASTERY | Sand-1000 | 28 | 500 |
FIGHT_PROP_FIRE_SUB_HURT | Sand-1000 | 50 | 0 |
FIGHT_PROP_ELEC_SUB_HURT | Sand-1000 | 51 | 0 |
FIGHT_PROP_ICE_SUB_HURT | Sand-1000 | 56 | 0 |
FIGHT_PROP_WATER_SUB_HURT | Sand-1000 | 52 | 0 |
FIGHT_PROP_WIND_SUB_HURT | Sand-1000 | 54 | 0 |
FIGHT_PROP_ROCK_SUB_HURT | Sand-1000 | 55 | 0 |
FIGHT_PROP_GRASS_SUB_HURT | Sand-1000 | 53 | 0 |
FIGHT_PROP_ATTACK | Feather-2000 | 5 | 1000 |
FIGHT_PROP_HP | Circlet-3000 | 2 | 0 |
FIGHT_PROP_HP_PERCENT | Circlet-3000 | 3 | 1100 |
FIGHT_PROP_ATTACK | Circlet-3000 | 5 | 0 |
FIGHT_PROP_ATTACK_PERCENT | Circlet-3000 | 6 | 1100 |
FIGHT_PROP_DEFENSE | Circlet-3000 | 8 | 0 |
FIGHT_PROP_DEFENSE_PERCENT | Circlet-3000 | 9 | 1100 |
FIGHT_PROP_CRITICAL | Circlet-3000 | 20 | 500 |
FIGHT_PROP_CRITICAL_HURT | Circlet-3000 | 22 | 500 |
FIGHT_PROP_HEAL_ADD | Circlet-3000 | 26 | 500 |
FIGHT_PROP_ELEMENT_MASTERY | Circlet-3000 | 28 | 200 |
FIGHT_PROP_HP | Flower-4000 | 2 | 1000 |
FIGHT_PROP_HP | Goblet-5000 | 2 | 0 |
FIGHT_PROP_HP_PERCENT | Goblet-5000 | 3 | 770 |
FIGHT_PROP_ATTACK | Goblet-5000 | 5 | 0 |
FIGHT_PROP_ATTACK_PERCENT | Goblet-5000 | 6 | 770 |
FIGHT_PROP_DEFENSE | Goblet-5000 | 8 | 0 |
FIGHT_PROP_DEFENSE_PERCENT | Goblet-5000 | 9 | 760 |
FIGHT_PROP_ELEMENT_MASTERY | Goblet-5000 | 28 | 100 |
FIGHT_PROP_FIRE_ADD_HURT | Goblet-5000 | 40 | 200 |
FIGHT_PROP_ELEC_ADD_HURT | Goblet-5000 | 41 | 200 |
FIGHT_PROP_ICE_ADD_HURT | Goblet-5000 | 46 | 200 |
FIGHT_PROP_WATER_ADD_HURT | Goblet-5000 | 42 | 200 |
FIGHT_PROP_WIND_ADD_HURT | Goblet-5000 | 44 | 200 |
FIGHT_PROP_ROCK_ADD_HURT | Goblet-5000 | 45 | 200 |
FIGHT_PROP_GRASS_ADD_HURT | Goblet-5000 | 43 | 200 |
FIGHT_PROP_PHYSICAL_ADD_HURT | Goblet-5000 | 30 | 200 |
Wait, what about the guarantee thing?
Let’s go back to PlayerItemComp::generateReliquaryMainPropId. I removed all the asan stuff and log stuff.
// Generate main property
*(_DWORD *)(v2 + 96) = reliquary_id;
ServiceBox::findService<GameserverService>();
GameserverService::getConfig((GameserverService *const)(v2 + 144));
reliquary_config_mgr = &std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false>::operator->((const std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false> *const)(v2 + 144))->design_config.txt_config_mgr.reliquary_config_mgr;
std::shared_ptr<Config>::~shared_ptr((std::shared_ptr<Config> *const)(v2 + 144));
*(_DWORD *)(v2 + 32) = ReliquaryExcelConfigMgr::generateMainPropId(reliquary_config_mgr, *(_DWORD *)(v2 + 96));
reliquary_config_ptr = data::ReliquaryExcelConfigMgrBase::findReliquaryExcelConfig(
reliquary_config_mgr,
*(unsigned int *)(v2 + 96));
......
if ( !common::tools::MiscUtils::isContains<std::unordered_set<unsigned int> const,unsigned int>(
&reliquary_config_mgr->guaranteed_main_prop_depot_id_set,
&reliquary_config_ptr->main_prop_depot_id) )
{
v10 = *(_DWORD *)(v2 + 32);
goto LABEL_112;
}
if ( reliquary_config_ptr->rank_level <= 4 )
{
v10 = *(_DWORD *)(v2 + 32);
goto LABEL_112;
}
*(_DWORD *)(v2 + 48) = reliquary_config_ptr->set_id;
if ( !*(_DWORD *)(v2 + 48) )
{
v10 = *(_DWORD *)(v2 + 32);
goto LABEL_112;
}
reliquary_set_config_ptr = data::ReliquaryExcelConfigMgrBase::findReliquarySetExcelConfig(
reliquary_config_mgr,
*(_DWORD *)(v2 + 48));
....
*(_DWORD *)(v2 + 64) = reliquary_set_config_ptr->guarantee_depot_id;
if ( !*(_DWORD *)(v2 + 64) )
{
v10 = *(_DWORD *)(v2 + 32);
goto LABEL_112;
}
...
ServiceBox::findService<GameserverService>();
GameserverService::getConfig((GameserverService *const)(v2 + 144));
v23 = std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false>::operator->((const std::__shared_ptr_access<Config,(__gnu_cxx::_Lock_policy)2,false,false> *const)(v2 + 144));
isReliquaryGuaranteeClosed = FeatureSwitchMgr::isReliquaryGuaranteeClosed(&v23->feature_switch_mgr);
std::shared_ptr<Config>::~shared_ptr((std::shared_ptr<Config> *const)(v2 + 144));
if ( isReliquaryGuaranteeClosed )
{
...
v10 = *(_DWORD *)(v2 + 32);
goto LABEL_112;
}
...
v28 = (char *)(v2 + 64);
guarantee_depot = std::map<unsigned int,ReliquaryGuaranteeDepot>::operator[](
&this->reliquary_guarantee_depot_map_,
(const std::map<unsigned int,ReliquaryGuaranteeDepot>::key_type *)(v2 + 64));
v29 = *(_DWORD *)(v2 + 64);
guarantee_depot->guarantee_depot_id = v29;
std::map<unsigned int,ReliquaryMainPropGuaranteeData>::map((std::map<unsigned int,ReliquaryMainPropGuaranteeData> *const)(v2 + 176));
*(_QWORD *)(v2 + 144) = 0LL;
*(_QWORD *)(v2 + 152) = 0LL;
__for_range = &reliquary_config_mgr->reliquary_main_prop_guarantee_excel_config_map;
__for_begin._M_cur = std::unordered_map<unsigned int,data::ReliquaryMainPropGuaranteeExcelConfig>::begin(&reliquary_config_mgr->reliquary_main_prop_guarantee_excel_config_map)._M_cur;
__for_end._M_cur = std::unordered_map<unsigned int,data::ReliquaryMainPropGuaranteeExcelConfig>::end(&reliquary_config_mgr->reliquary_main_prop_guarantee_excel_config_map)._M_cur;
while ( std::__detail::operator!=<std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>,false>(
&__for_begin,
&__for_end) )
{
v99 = std::__detail::_Node_const_iterator<std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>,false,false>::operator*(&__for_begin);
main_prop_id = std::get<0ul,unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>(v99);
guarantee_config = (std::tuple_element<1,const std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig> >::type *)std::get<1ul,unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>(v99);
guarantee_data = std::map<unsigned int,ReliquaryMainPropGuaranteeData>::operator[](
(std::map<unsigned int,ReliquaryMainPropGuaranteeData> *const)(v2 + 176),
main_prop_id);
old_guarantee_data_ptr = common::tools::MiscUtils::findMapValuePointer<std::map<unsigned int,ReliquaryMainPropGuaranteeData>>(
&guarantee_depot->main_prop_guarantee_data_map,
main_prop_id);
if ( old_guarantee_data_ptr )
{
guarantee_count = old_guarantee_data_ptr->guarantee_count;
if ( guarantee_count < guarantee_config->min_guarantee_count )
goto LABEL_54;
if ( v36 <= guarantee_config->max_guarantee_count )
{
v56 = old_guarantee_data_ptr->miss_count + 1;
guarantee_data->miss_count = v56;
main_prop_depot_id = guarantee_config->main_prop_depot_id;
if ( main_prop_depot_id == reliquary_config_ptr->main_prop_depot_id
&& old_guarantee_data_ptr->miss_count >= old_guarantee_data_ptr->guarantee_count )
{
*(_DWORD *)(v2 + 112) = *main_prop_id;
*(_DWORD *)(v2 + 116) = old_guarantee_data_ptr->guarantee_count;
*(_DWORD *)(v2 + 120) = old_guarantee_data_ptr->miss_count - old_guarantee_data_ptr->guarantee_count;
if ( !std::optional<PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData>::has_value((const std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144))
|| (v62 = std::optional<PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData>::value((std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144)),
PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData::isBetterThan(
(const PlayerItemComp::generateReliquaryMainPropId::TriggeredMainPropData *const)(v2 + 112),
v62)) )
{
std::optional_PlayerItemComp::generateReliquaryMainPropId_uint32_t_::TriggeredMainPropData_::operator__PlayerItemComp::generateReliquaryMainPropId_uint32_t_::TriggeredMainPropData__(
(std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144),
(PlayerItemComp::generateReliquaryMainPropId::TriggeredMainPropData *)(v2 + 112));
}
}
}
else
{
guarantee_data->miss_count = 1;
max_guarantee_count = guarantee_config->max_guarantee_count;
v39 = common::tools::RandomUtils::rand<unsigned int>(guarantee_config->min_guarantee_count, max_guarantee_count);
guarantee_data->guarantee_count = v39;
}
}
else
{
guarantee_data->miss_count = 1;
v31 = guarantee_config->max_guarantee_count;
v32 = v31;
v33 = common::tools::RandomUtils::rand<unsigned int>(guarantee_config->min_guarantee_count, v31);
guarantee_data->guarantee_count = v33;
}
std::__detail::_Node_const_iterator<std::pair<unsigned int const,data::ReliquaryMainPropGuaranteeExcelConfig>,false,false>::operator++(&__for_begin);
}
*(_DWORD *)(v2 + 80) = *(_DWORD *)(v2 + 32);
if ( std::optional<PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData>::has_value((const std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144)) )
{
v64 = std::optional<PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData>::value((std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144));
v65 = v64;
*(_DWORD *)(v2 + 80) = v65->main_prop_id;
}
guarantee_data_ptr = common::tools::MiscUtils::findMapValuePointer<std::map<unsigned int,ReliquaryMainPropGuaranteeData>>(
(std::map<unsigned int,ReliquaryMainPropGuaranteeData> *)(v2 + 176),
(const std::map<unsigned int,ReliquaryMainPropGuaranteeData>::key_type *)(v2 + 80));
if ( guarantee_data_ptr )
{
guarantee_data_ptr->miss_count = 0;
if ( std::optional<PlayerItemComp::generateReliquaryMainPropId(unsigned int)::TriggeredMainPropData>::has_value((const std::optional<PlayerItemComp::generateReliquaryMainPropId(uint32_t)::TriggeredMainPropData> *const)(v2 + 144)) )
{
guarantee_config_ptr = data::ReliquaryExcelConfigMgrBase::findReliquaryMainPropGuaranteeExcelConfig(
reliquary_config_mgr,
*(_DWORD *)(v2 + 80));
v79 = guarantee_config_ptr->max_guarantee_count;
v80 = v79;
v81 = common::tools::RandomUtils::rand<unsigned int>(guarantee_config_ptr->min_guarantee_count, v79);
guarantee_data_ptr->guarantee_count = v81;
}
v10 = *(_DWORD *)(v2 + 80);
goto LABEL_111;
}
v10 = *(_DWORD *)(v2 + 32);
LABEL_111:
std::map<unsigned int,ReliquaryMainPropGuaranteeData>::~map((std::map<unsigned int,ReliquaryMainPropGuaranteeData> *const)(v2 + 176));
LABEL_112:
result = v10;
return result;
In line 19, we can see that pity isn’t activated if artifact rank <=4.
In line 46, server can switch pity on/off at anytime. But it seems this is on for public official server.
In line 100, server checks all property pity count, add all property that matches pity count into a list.
In line 129, server selects first pity property that matches the selected depot.
So in short, the main property generation is weight biased and have a pity system for unlucky people like me.
Another question, what’s the probability for pity?
Fortunately, the leaker dumped that too, here’s the table.
Main property ID(Parsed) | Depot(Parsed) | Type | Pity count | Min/Max |
FIGHT_PROP_HP_PERCENT | Sand-1000 | 3 | 19 | 1;2 |
FIGHT_PROP_ATTACK_PERCENT | Sand-1000 | 6 | 19 | 1;2 |
FIGHT_PROP_DEFENSE_PERCENT | Sand-1000 | 9 | 19 | 1;2 |
FIGHT_PROP_CHARGE_EFFICIENCY | Sand-1000 | 23 | 50 | 1;2 |
FIGHT_PROP_ELEMENT_MASTERY | Sand-1000 | 28 | 50 | 1;2 |
FIGHT_PROP_ATTACK | Feather-2000 | 5 | 5 | 1;2 |
FIGHT_PROP_HP_PERCENT | Circlet-3000 | 3 | 23 | 1;2 |
FIGHT_PROP_ATTACK_PERCENT | Circlet-3000 | 6 | 23 | 1;2 |
FIGHT_PROP_DEFENSE_PERCENT | Circlet-3000 | 9 | 23 | 1;2 |
FIGHT_PROP_CRITICAL | Circlet-3000 | 20 | 50 | 1;2 |
FIGHT_PROP_CRITICAL_HURT | Circlet-3000 | 22 | 50 | 1;2 |
FIGHT_PROP_HEAL_ADD | Circlet-3000 | 26 | 50 | 1;2 |
FIGHT_PROP_ELEMENT_MASTERY | Circlet-3000 | 28 | 125 | 1;2 |
FIGHT_PROP_HP | Flower-4000 | 2 | 5 | 1;2 |
FIGHT_PROP_HP_PERCENT | Goblet-5000 | 3 | 26 | 1;2 |
FIGHT_PROP_ATTACK_PERCENT | Goblet-5000 | 6 | 26 | 1;2 |
FIGHT_PROP_DEFENSE_PERCENT | Goblet-5000 | 9 | 27 | 1;2 |
FIGHT_PROP_ELEMENT_MASTERY | Goblet-5000 | 28 | 200 | 1;2 |
FIGHT_PROP_FIRE_ADD_HURT | Goblet-5000 | 40 | 100 | 1;2 |
FIGHT_PROP_ELEC_ADD_HURT | Goblet-5000 | 41 | 100 | 1;2 |
FIGHT_PROP_ICE_ADD_HURT | Goblet-5000 | 46 | 100 | 1;2 |
FIGHT_PROP_WATER_ADD_HURT | Goblet-5000 | 42 | 100 | 1;2 |
FIGHT_PROP_WIND_ADD_HURT | Goblet-5000 | 44 | 100 | 1;2 |
FIGHT_PROP_ROCK_ADD_HURT | Goblet-5000 | 45 | 100 | 1;2 |
FIGHT_PROP_GRASS_ADD_HURT | Goblet-5000 | 43 | 100 | 1;2 |
FIGHT_PROP_PHYSICAL_ADD_HURT | Goblet-5000 | 30 | 100 | 1;2 |
So you are guaranteed to get a element mastery goblet in 200 artifacts generation? Bruh…
0x4 Artifact Sub Properties Generation
OK, how about sub properties?
After main property was generated, server will generate some sub properties for this new artifact, via ReliquaryExcelConfigMgr::generateAppendPropId:
....
is_upgrade = 0;
if ( std::vector<unsigned int>::size(append_prop_id_vec) > 3 )
{
*(std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::const_iterator *)(v5 + 368) = std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::begin(group_prop_config_map);
*(std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::const_iterator *)(v5 + 400) = std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::end(group_prop_config_map);
while ( 1 )
{
v28 = (char *)(v5 + 400);
if ( !std::operator!=(
(const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::_Self *)(v5 + 368),
(const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::_Self *)(v5 + 400)) )
break;
v51 = std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>>::operator*((const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > > *const)(v5 + 368));
group_id_0 = std::get<0ul,unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>(v51);
append_prop_config_vec_0 = (std::tuple_element<1,const std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::type *)std::get<1ul,unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>(v51);
if ( std::set<unsigned int>::count((const std::set<unsigned int> *const)(v5 + 1104), group_id_0) )
{
__lasta = std::vector<data::ReliquaryAffixExcelConfig>::end(append_prop_config_vec_0)._M_current;
M_current = std::vector<data::ReliquaryAffixExcelConfig>::begin(append_prop_config_vec_0)._M_current;
*(std::vector<data::ReliquaryAffixExcelConfig>::iterator *)(v5 + 432) = std::vector<data::ReliquaryAffixExcelConfig>::end((std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 656));
__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig const*,std::vector<data::ReliquaryAffixExcelConfig>>::__normal_iterator<data::ReliquaryAffixExcelConfig*>(
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *const)(v5 + 464),
(const __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *)(v5 + 432));
std::vector<data::ReliquaryAffixExcelConfig>::insert<__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig const*,std::vector<data::ReliquaryAffixExcelConfig>>,void>(
(std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 656),
*(std::vector<data::ReliquaryAffixExcelConfig>::const_iterator *)(v5 + 464),
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> >)M_current,
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> >)__lasta);
}
std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>>::operator++((std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > > *const)(v5 + 368));
}
is_upgrade = 1;
}
else
{
*(std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::const_iterator *)(v5 + 240) = std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::begin(group_prop_config_map);
*(std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::const_iterator *)(v5 + 272) = std::map<unsigned int,std::vector<data::ReliquaryAffixExcelConfig>>::end(group_prop_config_map);
while ( 1 )
{
v28 = (char *)(v5 + 272);
if ( !std::operator!=(
(const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::_Self *)(v5 + 240),
(const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::_Self *)(v5 + 272)) )
break;
v54 = std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>>::operator*((const std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > > *const)(v5 + 240));
group_id = std::get<0ul,unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>(v54);
append_prop_config_vec = (std::tuple_element<1,const std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > >::type *)std::get<1ul,unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>(v54);
if ( !std::set<unsigned int>::count((const std::set<unsigned int> *const)(v5 + 1104), group_id) )
{
__last = std::vector<data::ReliquaryAffixExcelConfig>::end(append_prop_config_vec)._M_current;
v29 = std::vector<data::ReliquaryAffixExcelConfig>::begin(append_prop_config_vec)._M_current;
*(std::vector<data::ReliquaryAffixExcelConfig>::iterator *)(v5 + 304) = std::vector<data::ReliquaryAffixExcelConfig>::end((std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 656));
__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig const*,std::vector<data::ReliquaryAffixExcelConfig>>::__normal_iterator<data::ReliquaryAffixExcelConfig*>(
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *const)(v5 + 336),
(const __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *)(v5 + 304));
std::vector<data::ReliquaryAffixExcelConfig>::insert<__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig const*,std::vector<data::ReliquaryAffixExcelConfig>>,void>(
(std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 656),
*(std::vector<data::ReliquaryAffixExcelConfig>::const_iterator *)(v5 + 336),
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> >)v29,
(__gnu_cxx::__normal_iterator<const data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> >)__last);
}
std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig>>>::operator++((std::_Rb_tree_const_iterator<std::pair<unsigned int const,std::vector<data::ReliquaryAffixExcelConfig> > > *const)(v5 + 240));
}
}
__for_range_2 = (std::vector<data::ReliquaryAffixExcelConfig> *)(v5 + 656);
*(std::vector<data::ReliquaryAffixExcelConfig>::iterator *)(v5 + 496) = std::vector<data::ReliquaryAffixExcelConfig>::begin(__for_range_2);
*(std::vector<data::ReliquaryAffixExcelConfig>::iterator *)(v5 + 528) = std::vector<data::ReliquaryAffixExcelConfig>::end(__for_range_2);
while ( __gnu_cxx::operator!=<data::ReliquaryAffixExcelConfig *,std::vector<data::ReliquaryAffixExcelConfig>>(
(const __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *)(v5 + 496),
(const __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *)(v5 + 528)) )
{
append_prop_config = __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig *,std::vector<data::ReliquaryAffixExcelConfig>>::operator*((const __gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *const)(v5 + 496));
p_prop_type = &append_prop_config->prop_type;
prop_type = append_prop_config->prop_type;
if ( prop_type != main_prop_config_ptr->prop_type )
std::vector<data::ReliquaryAffixExcelConfig>::emplace_back<data::ReliquaryAffixExcelConfig const&>(
(std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 592),
append_prop_config,
append_prop_config);
__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig *,std::vector<data::ReliquaryAffixExcelConfig>>::operator++((__gnu_cxx::__normal_iterator<data::ReliquaryAffixExcelConfig*,std::vector<data::ReliquaryAffixExcelConfig> > *const)(v5 + 496));
}
*(_DWORD *)(v5 + 64) = 0;
if ( is_upgrade )
v33 = 32LL;
else
v33 = 28LL;
if ( common::tools::RandomUtils::weightSelectOne<data::ReliquaryAffixExcelConfig,unsigned int data::ReliquaryAffixExcelConfig::*>(
(const std::vector<data::ReliquaryAffixExcelConfig> *)(v5 + 592),
(uint32_t *)(v5 + 64),
(unsigned int *)v33,
0) )
....
v35 = *(unsigned int *)(v5 + 64);
if ( v35 < std::vector<data::ReliquaryAffixExcelConfig>::size((const std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 592)) )
{
v40 = std::vector<data::ReliquaryAffixExcelConfig>::operator[](
(std::vector<data::ReliquaryAffixExcelConfig> *const)(v5 + 592),
*(unsigned int *)(v5 + 64));
id = v40->id;
}
....
Interesting parameter, is_upgrade, hmm.
Does this function also implements artifact upgrade? Yes. xref shows this function also being called from Reliquary::upgradeLevel !
Also, do you guys see the familiar weightSelectOne, LOL.
Apparently, the sub properties generation and upgrade also has some sort of weights.
And, guess what, the leaker also leaks this configuration, so here’s the table:
For the sake of simplicity, I’ll only show the 5 star artifact related data.
Append Property | Affix | Group | Type | Property Value | Random weights | Upgrade weights |
FIGHT_PROP_HP | 501 | 2 | 2 | 209.13 | 150 | 1000 |
FIGHT_PROP_HP | 501 | 2 | 2 | 239 | 150 | 1000 |
FIGHT_PROP_HP | 501 | 2 | 2 | 268.88 | 150 | 1000 |
FIGHT_PROP_HP | 501 | 2 | 2 | 298.75 | 150 | 1000 |
FIGHT_PROP_HP_PERCENT | 501 | 3 | 3 | 0.0408 | 100 | 1000 |
FIGHT_PROP_HP_PERCENT | 501 | 3 | 3 | 0.0466 | 100 | 1000 |
FIGHT_PROP_HP_PERCENT | 501 | 3 | 3 | 0.0525 | 100 | 1000 |
FIGHT_PROP_HP_PERCENT | 501 | 3 | 3 | 0.0583 | 100 | 1000 |
FIGHT_PROP_ATTACK | 501 | 5 | 5 | 13.62 | 150 | 1000 |
FIGHT_PROP_ATTACK | 501 | 5 | 5 | 15.56 | 150 | 1000 |
FIGHT_PROP_ATTACK | 501 | 5 | 5 | 17.51 | 150 | 1000 |
FIGHT_PROP_ATTACK | 501 | 5 | 5 | 19.45 | 150 | 1000 |
FIGHT_PROP_ATTACK_PERCENT | 501 | 6 | 6 | 0.0408 | 100 | 1000 |
FIGHT_PROP_ATTACK_PERCENT | 501 | 6 | 6 | 0.0466 | 100 | 1000 |
FIGHT_PROP_ATTACK_PERCENT | 501 | 6 | 6 | 0.0525 | 100 | 1000 |
FIGHT_PROP_ATTACK_PERCENT | 501 | 6 | 6 | 0.0583 | 100 | 1000 |
FIGHT_PROP_DEFENSE | 501 | 8 | 8 | 16.2 | 150 | 1000 |
FIGHT_PROP_DEFENSE | 501 | 8 | 8 | 18.52 | 150 | 1000 |
FIGHT_PROP_DEFENSE | 501 | 8 | 8 | 20.83 | 150 | 1000 |
FIGHT_PROP_DEFENSE | 501 | 8 | 8 | 23.15 | 150 | 1000 |
FIGHT_PROP_DEFENSE_PERCENT | 501 | 9 | 9 | 0.051 | 100 | 1000 |
FIGHT_PROP_DEFENSE_PERCENT | 501 | 9 | 9 | 0.0583 | 100 | 1000 |
FIGHT_PROP_DEFENSE_PERCENT | 501 | 9 | 9 | 0.0656 | 100 | 1000 |
FIGHT_PROP_DEFENSE_PERCENT | 501 | 9 | 9 | 0.0729 | 100 | 1000 |
FIGHT_PROP_CHARGE_EFFICIENCY | 501 | 23 | 23 | 0.0453 | 100 | 1000 |
FIGHT_PROP_CHARGE_EFFICIENCY | 501 | 23 | 23 | 0.0518 | 100 | 1000 |
FIGHT_PROP_CHARGE_EFFICIENCY | 501 | 23 | 23 | 0.0583 | 100 | 1000 |
FIGHT_PROP_CHARGE_EFFICIENCY | 501 | 23 | 23 | 0.0648 | 100 | 1000 |
FIGHT_PROP_ELEMENT_MASTERY | 501 | 24 | 28 | 16.32 | 100 | 1000 |
FIGHT_PROP_ELEMENT_MASTERY | 501 | 24 | 28 | 18.65 | 100 | 1000 |
FIGHT_PROP_ELEMENT_MASTERY | 501 | 24 | 28 | 20.98 | 100 | 1000 |
FIGHT_PROP_ELEMENT_MASTERY | 501 | 24 | 28 | 23.31 | 100 | 1000 |
FIGHT_PROP_CRITICAL | 501 | 20 | 20 | 0.0272 | 75 | 1000 |
FIGHT_PROP_CRITICAL | 501 | 20 | 20 | 0.0311 | 75 | 1000 |
FIGHT_PROP_CRITICAL | 501 | 20 | 20 | 0.035 | 75 | 1000 |
FIGHT_PROP_CRITICAL | 501 | 20 | 20 | 0.0389 | 75 | 1000 |
FIGHT_PROP_CRITICAL_HURT | 501 | 22 | 22 | 0.0544 | 75 | 1000 |
FIGHT_PROP_CRITICAL_HURT | 501 | 22 | 22 | 0.0622 | 75 | 1000 |
FIGHT_PROP_CRITICAL_HURT | 501 | 22 | 22 | 0.0699 | 75 | 1000 |
FIGHT_PROP_CRITICAL_HURT | 501 | 22 | 22 | 0.0777 | 75 | 1000 |
Yes, as you can see, the critical rate/damage properties has lowest weights as expected, and trash properties(Flat ATK/HP/DEF) has highest weights, twice more than critical rate/damage, bruh….
To my surprise, the upgrade weights are all the same, means once the artifact has 4 sub properties, you have even chance to upgrade any of them!
0x5 Final words
No wonder you cannot get a perfect artifact, the weights are just unfair.
I’ll publish more server side reverse engineering interesting findings, stay tuned!
Also, feel free to comment if you have any questions!