This repository has been archived on 2024-05-15. You can view files and clone it, but cannot push or open issues or pull requests.
fugg/fugg-client/src/Client.cpp

871 lines
31 KiB
C++

#include "Client.h"
#include <flyff.h>
#include <iostream>
#include <memory>
#include <sstream>
template<typename T>
void packet_push(std::vector<uint8_t> &packet, const T &value, std::vector<uint8_t>::iterator offset) {
union ToBytes {
T value;
uint8_t bytes[sizeof(T)];
};
ToBytes ref = {value};
packet.insert(offset, std::begin(ref.bytes), std::end(ref.bytes));
}
template<typename T>
void packet_push(std::vector<uint8_t> &packet, const T &value) {
return packet_push(packet, value, std::end(packet));
}
namespace fugg {
Client::Client() : flyff::Neuz(flyff::Neuz::instance()) {
}
Client &Client::instance() {
// Constructor will be called.
static Client instance;
// Whole module is initialised now.
return instance;
}
void Client::send_packet(const uint32_t &id, std::vector<uint8_t> payload) const {
const uint32_t header = id < 0x400 ? id : id ^ flyff::PACKET_HEADER_HASH;
// Add header
packet_push(payload, header, std::begin(payload));
struct Vector {
uintptr_t begin;
uintptr_t end;
uintptr_t capacity;
};
auto raw_payload = reinterpret_cast<const Vector *>(&payload);
// Copy the message into a new ptr
auto payload_ = std::make_unique<Vector>(Vector{
.begin = fugg::RuntimePointer<uintptr_t>(raw_payload->begin).as_raw(),
.end = fugg::RuntimePointer<uintptr_t>(raw_payload->end).as_raw(),
.capacity = fugg::RuntimePointer<uintptr_t>(raw_payload->capacity).as_raw()
});
flyff::api::finalize_packet(
fugg::RuntimePointer<Vector>(payload_.get()).as_raw()
);
flyff::api::websocket_send(
payload_->begin,
payload_->end - payload_->begin
);
}
void Client::send_logout() const {
send_packet(0x403, {});
}
void Client::send_move_item_in_inventory(const uint32_t &from_slot, const uint32_t &to_slot) const {
std::vector<uint8_t> packet;
packet_push<uint32_t>(packet, from_slot);
packet_push<uint32_t>(packet, to_slot);
send_packet(0x2001, packet);
}
void Client::send_use_item_in_inventory(const uint32_t &slot) const {
std::vector<uint8_t> packet;
packet_push<uint32_t>(packet, slot);
// Unknown yet
packet_push<uint32_t>(packet, 0);
send_packet(0x410, packet);
}
void Client::send_motion_packet(const uint32_t &action, unsigned long long param0, unsigned long long param1,
unsigned long long param2, unsigned long long param3) const {
const int32_t motion_packet_id = 0x404;
const auto &neuz = flyff::Neuz::instance();
if (!neuz.player) return;
std::vector<uint8_t> packet;
uint8_t param_flags = 0;
if (param0 != 0) param_flags |= 1;
if (param1 != 0) param_flags |= 2;
if (param2 != 0) param_flags |= 4;
if (param3 != 0) param_flags |= 8;
packet_push<flyff::Vector3>(packet, neuz.player->position);
packet_push<uint8_t>(packet, action);
packet_push<uint8_t>(packet, param_flags);
packet_push(packet, neuz.player->server_tick);
if (param0 != 0) packet_push<unsigned long long>(packet, param0);
if (param1 != 0) packet_push<unsigned long long>(packet, param1);
if (param2 != 0) packet_push<unsigned long long>(packet, param2);
if (param3 != 0) packet_push<unsigned long long>(packet, param3);
send_packet(motion_packet_id, packet);
}
void Client::set_target(const unsigned long long &id) const {
send_motion_packet(0x04, id);
}
void Client::clear_target() const {
send_motion_packet(0x04, {});
}
void Client::interact_target(const unsigned long long &index) const {
send_motion_packet(0x11, index);
}
void Client::use_skill(const flyff::SkillID &id, unsigned long long target) const {
send_motion_packet(0x5, static_cast<unsigned long long>(id), target);
}
void Client::send_move_to(const double &x, const double &y, const double &z) const {
// Mouse click
send_motion_packet(
0,
*reinterpret_cast<const unsigned long long *>(&x),
*reinterpret_cast<const unsigned long long *>(&y),
*reinterpret_cast<const unsigned long long *>(&z)
);
}
void Client::send_chat_message(const std::string &message) const {
std::vector<uint8_t> packet;
// Message length
packet_push<uint32_t>(packet, message.length());
// Write message
for (const auto &character: message) {
packet_push<uint8_t>(packet, character);
}
// Unknown 1
packet_push<uint32_t>(packet, 0);
// Unknown 2
packet_push<uint32_t>(packet, 0);
send_packet(0x2000, packet);
}
}
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <string>
#include <memory>
#include <emscripten/html5.h>
#include <cstddef>
flyff::Pointer<flyff::Mover::Buff>
find_skill_buff(const flyff::Pointer<flyff::Mover> &mover, const flyff::SkillID &skill_index) {
for (const auto &buff: mover->buffs) {
if (buff->get_type() == flyff::Mover::Buff::Type::Skill) {
auto skill_property = reinterpret_cast<const flyff::SkillProperty *>(
static_cast<const flyff::ObjectProperty *>(buff->property)
);
if (!buff->is_removed && skill_property->id == skill_index)
return buff;
}
}
return flyff::Pointer<flyff::Mover::Buff>(nullptr);
}
template<size_t size>
std::vector<flyff::SkillID>
check_rebuff(const flyff::Pointer<flyff::Mover> &player, const flyff::SkillID (&buffs_to_check)[size]) {
const auto &neuz = flyff::Neuz::instance();
auto result = std::vector<flyff::SkillID>();
for (const auto &skill_index: buffs_to_check) {
const auto &buff = find_skill_buff(player, skill_index);
const auto &skill_property = flyff::Neuz::get_skill_property(skill_index);
// If a buff is active
if (buff) {
auto player_skill_level = player->get_skill_level(skill_index);
// For some reason it's one off...
auto current_skill_level = buff->level + 1;
auto buff_property = reinterpret_cast<const flyff::SkillProperty *>(
static_cast<const flyff::ObjectProperty *>(buff->property)
);
if (player_skill_level > current_skill_level) {
// Always apply a higher buff.
result.push_back(skill_index);
} else if (buff->get_time_remaining() < 30 * 1000 && player_skill_level >= current_skill_level) {
// Refresh the buff.
result.push_back(skill_index);
}
} else {
// Apply the buff.
result.push_back(skill_index);
}
}
return result;
}
flyff::Pointer<const flyff::InventoryItem> find_first_equippable_item(const flyff::ItemKind3 &item_kind) {
const auto &neuz = flyff::Neuz::instance();
const auto &player = neuz.player;
uint32_t highest_level = 0;
flyff::InventoryItem const *highest = nullptr;
for (const auto &item: player->inventory) {
const auto &prop = item.property;
if (item.index < 168 && prop && prop->item_kind3 == item_kind && prop->required_level <= player->get_level() &&
prop->required_level > highest_level) {
highest = &item;
highest_level = prop->required_level;
}
}
return flyff::Pointer<const flyff::InventoryItem>(highest);
}
bool is_running = false;
flyff::Vector3 start_position = {0};
// The object property id of the monster to grind.
int object_property_index = 0;
void on_keyup_hook(int event_type, const EmscriptenKeyboardEvent *event, void *user_data) {
const auto &client = fugg::Client::instance();
if (event->keyCode == 220) {
const auto &player = client.player;
auto items = player->inventory.begin();
std::cout << "Player has " << player->get_inventory_slots() << " slots" << std::endl;
std::cout << "Player has " << player->get_hp_percent() << "% HP" << std::endl;
for (auto i = 0; i < player->get_inventory_slots(); i++) {
if (items[i].property && items[i].property->is_food()) {
std::cout << client.get_text(items[i].property->name) << ": "
<< "Is food? " << (items[i].property->is_food() ? "Yes" : "No") << ", "
<< "Can use? " << (player->can_use_item(items[i].property) ? "Yes" : "No")
<< std::endl;
}
// if (items[i].property &&
// items[i].property->is_food() &&
// player->can_use_item(items[i].property)) {
// std::string str = "Eating ";
// str.append(client.get_text(items[i].property->name));
// str.append(" because HP is below 75%");
//
// client.show_message(str);
//
// client.send_use_item_in_inventory(i);
//
// break;
// }
}
// if (!main_hand || main_hand->item_kind3 != flyff::ItemProperty::CheerStick) {
// const auto &stick = find_first_equippable_item(flyff::ItemProperty::CheerStick);
//
// if (stick) {
// std::string str = "Found valid cheerstick: ";
// str.append(client.get_text(stick->property->name));
// client.show_message(str, {0, 0xFF, 0});
//
// use_item_in_inventory(stick->id);
// }
// }
// std::cout << reinterpret_cast<void*>(static_cast<u32>(main_hand)) << " Weapon is required level: " << main_hand->required_level << std::endl;
// auto length = ((uintptr_t)player->buffs.end() - (uintptr_t)player->buffs.begin()) / 4;
// std::cout << length << " buffs!" << std::endl;;
// for(const auto& test : player->buffs) {
// std::cout << "Buff with type " << test->get_type() << " and id " << reinterpret_cast<void*>(test->get_id()) << std::endl;
// if(test->get_type() == flyff::Mover::Buff::BUFF_SKILL) {
// auto skill_property = reinterpret_cast<flyff::SkillProperty*>(
// static_cast<flyff::ObjectProperty*>(test->property)
// );
// std::cout << "BuffSkill "
// << client.get_text(skill_property->name)
// << " ReqLv. " << skill_property->required_level
// << " Lv." << skill_property->level
// << " Id: " << reinterpret_cast<void*>(skill_property->id)
// << " Remaining: " << (test->get_time_remaining() / 1000)
// << std::endl;
// }
// }
//
// clear_target();
// auto items = player->inventory.begin();
// auto length = ((uintptr_t)player->inventory.end() - (uintptr_t)player->inventory.begin()) / 0x60;
// static constexpr int max_inventory_size = 168;
// static constexpr int slot_weapon = max_inventory_size + 10;
// int stick_index = -1;
// if(items[slot_weapon].id != -1 &&
// items[slot_weapon].property &&
// items[slot_weapon].property->item_kind3 == flyff::ItemProperty::CheerStick) {
// stick_index = slot_weapon;
// } else {
// for(auto i = 0; i < length; i++) {
// if(items[i].id != -1 && items[i].property) {
// // if(items[i].id < 168) InventoryItem
// // else Equipped
// // std::cout << items[i].id << std::endl;
// std::cout << client.get_text(items[i].property->name) << " at " << items[i].id << ": "
// << "IK2=" << items[i].property->item_kind2 << ", "
// << "IK3=" << items[i].property->item_kind3
// << std::endl;
// if(items[i].property->item_kind3 == flyff::ItemProperty::CheerStick) {
// stick_index = i;
// break;
// }
// }
// }
// if(stick_index != -1) {
// // Switch main_hand
// use_item_in_inventory(stick_index);
// }
// }
// if(stick_index != -1) {
// // Use skill Heal
// use_skill(0x2c, 10);
// } else {
// client.show_message("Could not find a stick to heal myself.", { 0xFF, 0, 0 });
// }
}
// BracketLeft
if (event->keyCode == 219 && !is_running) {
if (!client.player)
return;
if (client.player->selected_target == 0) {
client.show_message("No monster selected, unable to start bot.", {0xFF, 0, 0});
return;
}
auto target = client.get_mover(client.player->selected_target);
if (!target) {
client.show_message("No monster selected, unable to start bot.", {0xFF, 0xFF, 0});
return;
}
std::string str = "I'm only going to kill ";
str.append(client.get_text(target->name));
str.append("<Lv. ");
str.append(std::to_string(target->get_level()));
str.append(">");
client.show_message(str);
object_property_index = target->property_id;
start_position = {client.player->position.x, client.player->position.y, client.player->position.z};
std::cout << "Start botting at " << start_position << std::endl;
is_running = true;
client.show_message("Bot started");
}
if (event->keyCode == 221) {
if (is_running) {
is_running = false;
client.show_message("Bot stopped");
} else {
if (!client.player) return;
std::cout << "My job is " << client.player->get_job() << std::endl;
// client.show_announcement("Hello world!", {0xFF, 0, 0});
// auto current = client.movers.first;
// do {
// const auto &mover = current->mover;
//
// if (mover->type == flyff::ObjectType::Mover) {
// // int level = mover->get_level();
//
// std::cout << client.get_text(mover->name) << " <Lv. " << mover->get_level() << ">): "
// << (mover->is_dead() ? "Dead" : "Not dead") << ", "
// << (mover->is_fly() ? "Flying" : "Not flying") << ", "
// << (mover->is_jumping() ? "Jumping" : "Not jumping") << ", "
// << (mover->is_moving() ? "Moving" : "Not moving") << ", "
// << (mover->is_grounded ? "On ground" : "Flying") << ", "
// << std::endl;
// }
//
// current = current->next;
// } while (current);
// auto items = player.inventory.begin();
// auto length = ((uintptr_t)player.inventory.end() - (uintptr_t)player.inventory.begin()) / 0x60;
// std::cout << length << " items!" << std::endl;
// for(auto i = 0; i < length; i++) {
// if(items[i].id != -1) {
// // std::cout << items[i].id << std::endl;
// std::cout << "(Property address: " << static_cast<flyff::ItemProperty*>(items[i].property) << ", "
// << "Item address: " << &items[i] << ") "
// << client.get_text(items[i].property->name) << ": "
// << "Cooldown=" << items[i].property->cooldown_time << ", "
// << "Maybe category=" << items[i].property->category << ", "
// << "Maybe subcategory=" << items[i].property->sub_category << ", "
// << "Is this food ? " << (items[i].property->is_food_item() ? "Yes" : "No")
// << std::endl;
// }
// }
}
}
}
flyff::MoverID attack_next_target(flyff::Vector3 const &start_position) {
auto &neuz = flyff::Neuz::instance();
auto &client = fugg::Client::instance();
if (!neuz.player) return 0;
// Found in the code somewhere.
static constexpr float max_distance = 144.;
float lowest_distance = std::numeric_limits<float>::max();
auto closest = flyff::Pointer<flyff::Mover>(nullptr);
auto current = neuz.movers.first;
do {
const auto &mover = current->mover;
auto dx = std::abs(mover->position.x - neuz.player->position.x);
auto dy = std::abs(mover->position.y - neuz.player->position.y);
auto dz = std::abs(mover->position.z - neuz.player->position.z);
auto squared = (dx * dx) + (dy * dy) + (dz * dz);
auto d = std::sqrt(squared);
if (mover->type == flyff::ObjectType::Mover &&
current->hash != neuz.player_object_id &&
!mover->is_dead() &&
// Only attack ground mobs.
mover->is_grounded &&
// Only attack same mobs.
mover->property_id == object_property_index &&
d <= max_distance) {
if (d < lowest_distance) {
closest = mover;
lowest_distance = d;
}
}
current = current->next;
} while (current);
if (closest) {
neuz.player->selected_target = closest->object_id;
client.set_target(closest->object_id);
neuz.player->move_toward_target = closest->object_id;
client.interact_target(1);
return closest->object_id;
}
return 0;
}
flyff::MoverID find_next_item() {
const auto &neuz = flyff::Neuz::instance();
if (!neuz.player) return 0;
float lowest_distance = 32.f;
auto closest = flyff::Pointer<flyff::Mover>(nullptr);
auto current = neuz.movers.first;
do {
const auto &mover = current->mover;
auto dx = std::abs(mover->position.x - neuz.player->position.x);
auto dz = std::abs(mover->position.z - neuz.player->position.z);
auto squared = (dx * dx) + (dz * dz);
auto d = std::sqrt(squared);
if (mover->world != 0 &&
!mover->is_despawned &&
mover->type == flyff::ObjectType::Item &&
d <= lowest_distance) {
auto *prop = reinterpret_cast<const flyff::ItemProperty *>(
static_cast<const flyff::ObjectProperty *>(mover->property)
);
if (!prop->is_quest()) {
closest = mover;
lowest_distance = d;
}
}
current = current->next;
} while (current);
if (closest)
return closest->object_id;
else
return 0;
}
void interact(flyff::MoverID id) {
const auto &neuz = flyff::Neuz::instance();
const auto &client = fugg::Client::instance();
if (!neuz.player)
return;
neuz.player->selected_target = id;
client.set_target(id);
neuz.player->move_toward_target = id;
client.interact_target(1);
}
flyff::MoverID current_target = 0;
flyff::MoverID current_pickup_item = 0;
void bot_tick() {
const auto &neuz = flyff::Neuz::instance();
const auto &client = fugg::Client::instance();
if (!neuz.player)
return;
// std::cout << "Max Max HP: "
// << neuz.player->get_max_hp()
// << ", Adjust HP:"
// << neuz.player->get_adjust_param(0x2c)
// << ", Player level: "
// << neuz.player->get_level()
// << std::endl;
// printf("%#010x\n", neuz.player->get_state());
if (is_running) {
if (neuz.player->is_dead()) {
neuz.show_message("Player is dead, stopping...", {0xFF, 0x00, 0x00});
is_running = false;
return;
}
const auto &player = neuz.player;
// if is_idle:
// if item_target:
// pickup_item()
// elif attacK_target:
// attack_target()
if (neuz.player->get_hp_percent() < 75) {
// neuz.show_message("Fooding because HP is under 75%");W
auto items = player->inventory.begin();
std::cout << "Player has " << player->get_inventory_slots() << " slots" << std::endl;
for (auto i = 0; i < player->get_inventory_slots(); i++) {
if (items[i].property &&
items[i].property->is_food() &&
player->can_use_item(items[i].property)) {
std::string str = "Eating ";
str.append(neuz.get_text(items[i].property->name));
str.append(" because HP is below 75%");
neuz.show_message(str);
client.send_use_item_in_inventory(i);
break;
}
}
}
if (neuz.player->get_move_state() == 1 &&
!neuz.player->is_attacking()) {
// Check buffs.
static constexpr flyff::SkillID buffs_to_check[] = {
flyff::SkillID::HeapUp,
flyff::SkillID::Haste,
flyff::SkillID::Patience,
flyff::SkillID::BeefUp,
};
auto item = neuz.get_mover(current_pickup_item);
auto monster = neuz.get_mover(current_target);
if (neuz.player->get_job() == flyff::JobID::Assist) {
const auto &main_hand = player->get_equipped_item(flyff::Part::MainHand);
const auto &off_hand = player->get_equipped_item(flyff::Part::Shield);
auto buffs = check_rebuff(neuz.player, buffs_to_check);
if (!buffs.empty() && (!item || item->is_despawned) && (!monster || monster->is_dead())) {
const auto skill_index = buffs.back();
if (!main_hand || main_hand->item_kind3 != flyff::ItemKind3::CheerStick) {
const auto &stick = find_first_equippable_item(flyff::ItemKind3::CheerStick);
if (stick) {
buffs.pop_back();
client.send_use_item_in_inventory(stick->index);
client.use_skill(skill_index);
return;
} else {
neuz.show_message("Unable to find a stick", {0xFF, 0, 0});
}
} else {
buffs.pop_back();
client.use_skill(skill_index);
return;
}
}
if (buffs.empty()) {
if (!main_hand || main_hand->item_kind3 != flyff::ItemKind3::KnuckleHammer) {
const auto &knuckle = find_first_equippable_item(flyff::ItemKind3::KnuckleHammer);
if (knuckle) {
client.send_use_item_in_inventory(knuckle->index);
} else {
neuz.show_message("Unable to find a knuckle", {0xFF, 0, 0});
}
}
if (!off_hand || off_hand->item_kind3 != flyff::ItemKind3::Shield) {
const auto &shield = find_first_equippable_item(flyff::ItemKind3::Shield);
if (shield) {
client.send_use_item_in_inventory(shield->index);
} else {
neuz.show_message("Unable to find a shield", {0xFF, 0, 0});
}
}
}
}
if (item && !item->is_despawned && neuz.player->move_toward_target == 0) {
// Pickup
interact(item->object_id);
} else if (monster && !monster->is_dead() && neuz.player->move_toward_target == 0) {
// Attack
interact(monster->object_id);
// neuz.player->attack_target = monster->object_id;
} else if ((!item || item->is_despawned) &&
(!monster || monster->is_dead())) {
std::cout << "No item, no target. Searching..." << std::endl;
// First check all mobs if they attacked me (aggro ones).
auto current = neuz.movers.first;
do {
const auto &mover = current->mover;
if (mover->type == flyff::ObjectType::Mover &&
current->hash != neuz.player_object_id &&
!mover->is_dead() &&
// If it's a monster that has me targetted...
mover->last_attacked_target == neuz.player_object_id &&
// ...and is tracking me
mover->follow_target == neuz.player_object_id) {
current_target = attack_next_target(start_position);
monster = neuz.get_mover(current_target);
std::string str = "Attacking ";
str.append(monster->name);
str.append(" because it attacked me.");
interact(monster->object_id);
neuz.show_message(str);
return;
}
current = current->next;
} while (current);
// No item target and no attack target
current_pickup_item = find_next_item();
item = neuz.get_mover(current_pickup_item);
if (item) {
auto *prop = reinterpret_cast<const flyff::ItemProperty *>(
static_cast<const flyff::ObjectProperty *>(item->property)
);
std::string str = "Picking up ";
str.append(neuz.get_text(prop->name));
interact(item->object_id);
neuz.show_message(str);
} else {
current_target = attack_next_target(start_position);
monster = neuz.get_mover(current_target);
if (monster) {
std::string str = "Attacking ";
str.append(monster->name);
std::cout << str << std::endl;
interact(monster->object_id);
// neuz.player->attack_target = monster->object_id;
neuz.show_message(str);
} else {
neuz.show_message("Unable to find target.");
}
}
}
}
} else {
// Reset current target is not running.
current_target = 0;
current_pickup_item = 0;
}
}
void before_main_loop() {
bot_tick();
}
void after_main_loop() {
// std::string test;
// auto& music_properties = fugg::module_ref<fugg::Vector<flyff::MusicProperty>>(0x00034f7c);
// auto& item_properties = fugg::module_ref<fugg::Vector<flyff::ItemProperty>>(0x00035194);
// for(auto& item : item_properties) {
// std::cout << item.some_string->c_str() << std::endl;
// }
// auto& player_pointer = fugg::module_ref<uintptr_t>(0x0003546c);
// if(player_pointer != 0) {
// auto& player = fugg::module_ref<flyff::Mover>(player_pointer);
// std::cout << "Player pos: "
// << player.position.x << ", "
// << player.position.y << ", "
// << player.position.z << std::endl;
// // std::cout << "Current target: " << player.selected_target << std::endl;
// std::cout << "Current animation: " << player.current_animation << std::endl;
// std::cout << "Name: " << player.name << std::endl;
// const int bla = offsetof(Mover, name);
// const int test = offsetof(flyff::Mover, name);
// static_assert(bla == test, "OMG");
// // static_assert(offsetof(Mover, name) == 2020, "ASD");
// if(player.selected_target != 0) {
// struct ObjectHolder {
// uintptr_t next;
// unsigned int list_index;
// unsigned long long hash;
// uintptr_t mover;
// };
// auto& selected_target_holder = fugg::module_ref<ObjectHolder>(
// flyff::api::w2c_f208(player.selected_target)
// );
// auto& selected_target = fugg::module_ref<Mover>(
// selected_target_holder.mover
// );
// std::cout << "(" << reinterpret_cast<void*>(selected_target_holder.mover) << ") Target: "
// << selected_target.name->c_str() << " at "
// << selected_target.world_position.x << ", "
// << selected_target.world_position.y << ", "
// << selected_target.world_position.z << std::endl;
// } else {
// std::cout << "Got no target." << std::endl;
// }
// }
// auto& item_properties = fugg::module_ref<fugg::Vector<flyff::ItemProperty>>(0x00035194);
// std::cout << "Expecting " << reinterpret_cast<void*>(fugg::wasm::kMemory.address_of(0x00035194)) << std::endl;
// std::cout << "Item has " << item_properties->size() << std::endl;
// std::cout << "There are " << item_properties->size() << " item properties." << std::endl;
// for(const auto& item : *item_properties) {
// std::cout << "Item with id " << item.translation_tag.tag << std::endl;
// }
// auto test = reinterpret_cast<Pointer<int>*>(0x00035194);
// std::cout << "Test is at " << reinterpret_cast<void*>(test->to_intptr_t()) << std::endl;
// if((void*)test == (void*)0x00035194) {
// std::cout << "Test is at the right address!" << std::endl;
// }
// auto vector_location = fugg::ModulePointer<uintptr_t> { 0x00035194 };
// auto ptr = fugg::ModulePointer<ThisPointerTest> { 0x00035194 };
// std::cout << "vector_location is at " << reinterpret_cast<void*>(0x00035194) << std::endl;
// std::cout << "ptr is at " <<
// auto raw = new RawVector {
// .begin = **fugg::ModulePointer<fugg::ModulePointer<uintptr_t>> { 0x00035194 + 0 },
// .end = **fugg::ModulePointer<fugg::ModulePointer<uintptr_t>> { 0x00035194 + 4 },
// .end_capacity = **fugg::ModulePointer<fugg::ModulePointer<uintptr_t>> { 0x00035194 + 8 },
// };
// auto unique = std::unique_ptr<RawVector>(raw);
// auto vec = reinterpret_cast<std::vector<flyff::ItemProperty>*>(raw);
// auto unique = std::unique_ptr<std::vector<flyff::ItemProperty>>(vec);
// std::cout << "There are " << unique->size() << " items!" << std::endl;
// Probably deconstructed here.
// std::cout << "Constructed raw. Call raw->temp..." << std::endl;
// std::cout << raw->temp << std::endl;
// std::cout << "Done calling raw->temp" << std::endl;
}