Skip to content

Instantly share code, notes, and snippets.

@jsoctocat
Created May 24, 2019 22:37
Show Gist options
  • Save jsoctocat/e10eb4cd8b9bf049f5fe9875af26c0cc to your computer and use it in GitHub Desktop.
Save jsoctocat/e10eb4cd8b9bf049f5fe9875af26c0cc to your computer and use it in GitHub Desktop.
Market Bot for Black Desert Online
#include <windows.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <map>
#include <iostream>
#include <ctime>
#include <vector>
#include <fstream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#include "third_party/minhook/source/minhook.h"
#define APPLICATION_NAME "Mark"
#define ATA_LUA_GETTOP 0x14165EDC0 // 795 0x00000001414FE620
#define ATA_REQUEST_ITEM_MARKET_SELL_INFO 0x140447100 // 795 0x0000000140434640
#define ATA_GET_ITEM_MARKET_SELL_INFO_IN_CLIENT_COUNT 0x140446F70 // 795 0x00000001404344B0
#define ATA_GET_ITEM_MARKET_SELL_INFO_IN_CLIENT_BY_INDEX 0x140446FA0 // 795 0x00000001404344E0
#define ATA_REQUEST_BUY_ITEM_FOR_ITEM_MARKET 0x1404474D0 // 795 0x0000000140434A10
#define ATA_IS_BIDDING_ITEM 0x1403A4E70 // 795 0x0000000140392420
#define ATA_IS_BIDDING_JOIN_TIME 0x1403A4EB0 // 795 0x0000000140392460
#define ATA_GET_ITEM_MARKET_NO 0x1401D4040 // 795 0x00000001401BB5A0
#define ATA_IS_BIDDING_JOIN_ITEM 0x1404481B0 // 795 0x00000001404356F0
#define ATA_GET_ONE_PRICE 0x1402E1BB0 // 795 0x00000001402C78E0
#define ATA_WAREHOUSE_MONEY_FROM_NPC_SHOP 0x14064ED10 // 795 0x000000014063F9C0
#define ADA_LOCAL_PLAYER 0x1432764D0 // 795 0x0000000142108158
#define EXECUTION_INTERVAL 500
#define DEFAULT_WALLET_TYPE 2 // Warehouse
#define DEFAULT_ANIMATION_SPEED_MODIFIER 0.25f
typedef uint64_t(__fastcall* ft_lua_gettop)(void* lua_state);
typedef void(__fastcall* ft_request_item_market_sell_info)(uint32_t territory_key, uint32_t item_key, bool unk_1);
typedef int32_t(__fastcall* ft_get_item_market_sell_info_in_client_count)(uint32_t territory_key, uint32_t item_key);
typedef uint64_t(__fastcall* ft_get_item_market_sell_info_in_client_by_index)(uint32_t territory_key, uint32_t item_key, uint32_t slot_index);
typedef void(__fastcall* ft_request_buy_item_for_item_market)(uint32_t wallet_type, uint32_t item_key, uint32_t slot_index, uint32_t amount, uint64_t unk_1);
typedef bool(__fastcall* ft_is_bidding_item)(uint64_t sell_info_address);
typedef bool(__fastcall* ft_is_bidding_join_time)(uint64_t sell_info_address);
typedef uint64_t(__fastcall* ft_get_item_market_no)(uint64_t sell_info_address);
typedef bool(__fastcall* ft_is_bidding_join_item)(uint64_t item_market_no);
typedef uint64_t(__fastcall* ft_get_one_price)(uint64_t sell_info_address);
typedef uint64_t(__fastcall* ft_warehouse_money_from_npc_shop)();
static ft_lua_gettop g_fp_lua_gettop = nullptr;
static ft_request_item_market_sell_info g_fp_request_item_market_sell_info = reinterpret_cast<ft_request_item_market_sell_info>(ATA_REQUEST_ITEM_MARKET_SELL_INFO);
static ft_get_item_market_sell_info_in_client_count g_fp_get_item_market_sell_info_in_client_count = reinterpret_cast<ft_get_item_market_sell_info_in_client_count>(ATA_GET_ITEM_MARKET_SELL_INFO_IN_CLIENT_COUNT);
static ft_get_item_market_sell_info_in_client_by_index g_fp_get_item_market_sell_info_in_client_by_index = reinterpret_cast<ft_get_item_market_sell_info_in_client_by_index>(ATA_GET_ITEM_MARKET_SELL_INFO_IN_CLIENT_BY_INDEX);
static ft_request_buy_item_for_item_market g_fp_request_buy_item_for_item_market = reinterpret_cast<ft_request_buy_item_for_item_market>(ATA_REQUEST_BUY_ITEM_FOR_ITEM_MARKET);
static ft_is_bidding_item g_fp_is_bidding_item = reinterpret_cast<ft_is_bidding_item>(ATA_IS_BIDDING_ITEM);
static ft_is_bidding_join_time g_fp_is_bidding_join_time = reinterpret_cast<ft_is_bidding_join_time>(ATA_IS_BIDDING_JOIN_TIME);
static ft_get_item_market_no g_fp_get_item_market_no = reinterpret_cast<ft_get_item_market_no>(ATA_GET_ITEM_MARKET_NO);
static ft_is_bidding_join_item g_fp_is_bidding_join_item = reinterpret_cast<ft_is_bidding_join_item>(ATA_IS_BIDDING_JOIN_ITEM);
static ft_get_one_price g_fp_get_one_price = reinterpret_cast<ft_get_one_price>(ATA_GET_ONE_PRICE);
static ft_warehouse_money_from_npc_shop g_fp_warehouse_money_from_npc_shop = reinterpret_cast<ft_warehouse_money_from_npc_shop>(ATA_WAREHOUSE_MONEY_FROM_NPC_SHOP);
static bool g_market_activated = false;
static bool g_hacks_activated = false;
static std::map<std::string, uint32_t> g_targets = {
{ "Blessing of Kamasylve (15 Days)", 17081 },
{ "Sealed Book of Combat (15 Days)", 17646 },
{ "Equipment Tailoring Coupon", 17611 },
{ "Value Pack (30 Days)", 17354 },
{ "Young Griffon", 18429 },
{ "Shaggy Dog", 17939 },
{ "Calpheon Chubby Dog", 17966 },
{ "Desert Fox", 17976 },
{ "Red Panda", 18098 },
{ "Snowball Rosefinch", 18426 },
{ "Crow", 18439 },
{ "Snowflake Reindeer Box", 290006 },
{ "Winter Snowflake Box", 18946 },
{ "Horse Flute Box (Permanent)", 17973 },
{ "Trina Knight Horse Gear Set", 21012 },
};
static uint64_t g_last_execution = 0;
static float g_animation_speed = 1.0f;
static char g_c_dlldir[300];
static int DEFAULT_TERRITORY_KEY = 0;
class BuyMarketItemFile
{
public:
std::string first;
uint32_t second;
BuyMarketItemFile(std::string item_name, int item_id)
: first(item_name), second(item_id)
{
}
};
std::vector<BuyMarketItemFile> g_v_market_items;
std::string get_formatted_datetime()
{
std::time_t raw_time;
std::tm* time;
char time_buffer[20] = { 0 };
std::time(&raw_time);
time = std::localtime(&raw_time);
std::strftime(time_buffer, sizeof(time_buffer), "%d.%m.%Y %H:%M:%S", time);
return std::string(::_strdup(time_buffer));
}
char *GetDirectoryFile(char *filename)
{
static char path[300];
strcpy_s(path, g_c_dlldir);
strcat_s(path, filename);
return path;
}
uint64_t __fastcall hf_lua_gettop(void* lua_state)
{
if (::GetAsyncKeyState(VK_NUMPAD1) & 0x01)
{
g_v_market_items.clear();
std::string itemName;
int itemId;
const char *marketfile = "market.txt";
char *file = const_cast<char*>(marketfile);
std::string endReading = "<end>";
std::ifstream marketfin;
marketfin.open(GetDirectoryFile(file), std::ifstream::in);
while (marketfin.good())
{
std::getline(marketfin, itemName, ',');
if (itemName.find(endReading) != std::string::npos)
break;
marketfin >> itemId;
g_v_market_items.emplace_back(itemName, itemId);
}
marketfin.close();
std::cout << "Buy From Market Items File Loaded" << std::endl;
}
if (::GetAsyncKeyState(VK_NUMPAD2) & 0x01)
{
if (DEFAULT_TERRITORY_KEY < 6)
{
DEFAULT_TERRITORY_KEY++;
std::cout << "Territory: " << DEFAULT_TERRITORY_KEY << std::endl;
}
else
{
DEFAULT_TERRITORY_KEY = 0;
std::cout << "Territory: " << DEFAULT_TERRITORY_KEY << std::endl;
}
}
if (::GetAsyncKeyState(VK_NUMPAD6) & 0x01)
{
g_market_activated = !g_market_activated;
if (g_market_activated)
{
std::ostringstream warehouse_money_string_stream;
warehouse_money_string_stream.imbue(std::locale("de"));
warehouse_money_string_stream << g_fp_warehouse_money_from_npc_shop();
std::cout << "Warehouse Money: " << warehouse_money_string_stream.str() << std::endl;
}
std::cout << APPLICATION_NAME " Market state is now `" << g_market_activated << "`." << std::endl;
}
if (::GetAsyncKeyState(VK_NUMPAD7) & 0x01)
{
g_animation_speed -= DEFAULT_ANIMATION_SPEED_MODIFIER;
std::cout << "<< Animation Speed Modifier decreased to `" << g_animation_speed << "`." << std::endl;
}
if (::GetAsyncKeyState(VK_NUMPAD8) & 0x01)
{
g_animation_speed += DEFAULT_ANIMATION_SPEED_MODIFIER;
std::cout << ">> Animation Speed Modifier increased to `" << g_animation_speed << "`." << std::endl;
}
if (::GetAsyncKeyState(VK_NUMPAD9) & 0x01)
{
g_hacks_activated = !g_hacks_activated;
if (!g_hacks_activated)
{
uint64_t local_player_address = *((uint64_t*) ADA_LOCAL_PLAYER);
uint64_t character_control_address = *((uint64_t*) (local_player_address + 0x0380));
uint64_t character_scene_address = *((uint64_t*) (character_control_address + 0x10));
*((float*) (character_scene_address + 0x04C8)) = 1.0f;
}
std::cout << APPLICATION_NAME " Hack state is now `" << g_hacks_activated << "`." << std::endl;
}
if (g_hacks_activated)
{
uint64_t local_player_address = *((uint64_t*) ADA_LOCAL_PLAYER);
uint64_t character_control_address = *((uint64_t*) (local_player_address + 0x0380));
uint64_t character_scene_address = *((uint64_t*) (character_control_address + 0x10));
*((float*) (character_scene_address + 0x04C8)) = g_animation_speed;
}
if (g_market_activated && ((g_last_execution + EXECUTION_INTERVAL) <= ::GetTickCount64()))
{
g_last_execution = ::GetTickCount64();
for (auto const& target : g_v_market_items)
{
g_fp_request_item_market_sell_info(DEFAULT_TERRITORY_KEY, target.second, false);
int32_t target_slot_count = g_fp_get_item_market_sell_info_in_client_count(DEFAULT_TERRITORY_KEY, target.second);
if (target_slot_count == 0)
continue;
for (int32_t slot_index = 0; slot_index < target_slot_count; slot_index++)
{
uint64_t sell_info = g_fp_get_item_market_sell_info_in_client_by_index(DEFAULT_TERRITORY_KEY, target.second, slot_index);
if (sell_info == 0x00)
continue;
uint64_t sell_info_count = *((uint64_t*) (sell_info + 0x18));
if (sell_info_count > 0)
{
bool is_bidding_item = g_fp_is_bidding_item(sell_info);
bool is_bidding_join_time = g_fp_is_bidding_join_time(sell_info);
uint64_t item_market_no = g_fp_get_item_market_no(sell_info);
bool is_bidding_join_item = g_fp_is_bidding_join_item(item_market_no);
uint64_t one_price = g_fp_get_one_price(sell_info);
uint64_t total_price = (one_price * sell_info_count);
if (g_fp_warehouse_money_from_npc_shop() < total_price)
continue;
std::ostringstream one_price_string_stream;
std::ostringstream total_price_string_stream;
one_price_string_stream.imbue(std::locale("de"));
one_price_string_stream << one_price;
total_price_string_stream.imbue(std::locale("de"));
total_price_string_stream << total_price;
if (is_bidding_item)
{
if (is_bidding_join_time)
{
if (!is_bidding_join_item)
{
std::cout << "[" << get_formatted_datetime() << "] Bidding on " << target.first << " (" << slot_index << "." << sell_info_count << "-" << item_market_no << ") [T " << total_price_string_stream.str() << " | U " << one_price_string_stream.str() << "]" << std::endl;
g_fp_request_buy_item_for_item_market(DEFAULT_WALLET_TYPE, target.second, slot_index, sell_info_count, 0);
}
}
else if (is_bidding_join_item)
{
std::cout << "[" << get_formatted_datetime() << "] Confirming bid on " << target.first << " (" << slot_index << "." << sell_info_count << "-" << item_market_no << ") [T " << total_price_string_stream.str() << " | U " << one_price_string_stream.str() << "]" << std::endl;
g_fp_request_buy_item_for_item_market(DEFAULT_WALLET_TYPE, target.second, slot_index, sell_info_count, 0);
}
}
else
{
std::cout << "[" << get_formatted_datetime() << "] Buying " << target.first << " (" << slot_index << "." << sell_info_count << "-" << item_market_no << ") [T " << total_price_string_stream.str() << " | U " << one_price_string_stream.str() << "]" << std::endl;
g_fp_request_buy_item_for_item_market(DEFAULT_WALLET_TYPE, target.second, slot_index, sell_info_count, 0);
}
}
}
}
}
return g_fp_lua_gettop(lua_state);
}
BOOL APIENTRY DllMain(HMODULE module, DWORD reason, void* reserved)
{
if (reason != DLL_PROCESS_ATTACH)
return FALSE;
DisableThreadLibraryCalls(module);
GetModuleFileName(module, g_c_dlldir, 512);
for (size_t i = strlen(g_c_dlldir); i > 0; i--) { if (g_c_dlldir[i] == '\\') { g_c_dlldir[i + 1] = 0; break; } }
FILE* console_stream = nullptr;
::AllocConsole();
::SetConsoleTitle(APPLICATION_NAME);
::freopen_s(&console_stream, "conout$", "w", stdout);
std::cout << "Hello, world!" << std::endl;
if (::MH_Initialize() != MH_OK)
{
std::cout << "Failed to initialize hook library." << std::endl;
return 0;
}
if (::MH_CreateHook(((void*) ATA_LUA_GETTOP), &hf_lua_gettop, reinterpret_cast<void**>(&g_fp_lua_gettop)) != MH_OK)
{
std::cout << "Failed to create `lua_gettop` hook." << std::endl;
return 0;
}
if (::MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
{
std::cout << "Failed to enable hooks." << std::endl;
return 0;
}
return TRUE;
}
@jsoctocat
Copy link
Author

jsoctocat commented May 24, 2019

This allows for the manipulation of the in-game marketplace system for black desert online

This was originally written by Ustonovic from EC, some modifications were made so that the end-users can dynamically update the item list at runtime for autonomous purchase while accessing the marketplace system

optimization is needed(old unused function from new implementations were not deleted)! and a hook is required for this to work, the original hook used was the minHook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment