Last active
February 14, 2026 09:14
-
-
Save mq1n/95b059eddcb6110e381b74fb6a9790f0 to your computer and use it in GitHub Desktop.
fuzzer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "../include/PacketFuzzer.hpp" | |
| #include "../include/desc.h" | |
| #include "../include/char.h" | |
| #include "../include/log.h" | |
| #include <net/Type.hpp> | |
| #include <base/Serialization.hpp> | |
| #include <corelib/boost_compat.hpp> | |
| #include <xxhash.h> | |
| #include <cstring> | |
| #include <random> | |
| #include <algorithm> | |
| #include <limits> | |
| #include <fstream> | |
| #include <sstream> | |
| using asio::const_buffer; | |
| namespace | |
| { | |
| // Build a valid PacketHeader with a payload, computing the hash | |
| PacketHeader MakeHeader(uint16_t id, uint32_t payloadSize) | |
| { | |
| PacketHeader h{}; | |
| h.magic = PacketHeader::kMagic; | |
| h.version = PacketHeader::kVersion; | |
| h.length = static_cast<uint32_t>(sizeof(PacketHeader) + payloadSize); | |
| h.id = id; | |
| h.order_security_id = 0; | |
| h.flags = 0; | |
| // hash computed by caller with actual payload bytes | |
| return h; | |
| } | |
| // Writes header (little-endian) and payload into a contiguous buffer matching Socket::ProcessData expectations | |
| std::vector<uint8_t> BuildWirePacket(const PacketHeader& header, const std::vector<uint8_t>& payload) | |
| { | |
| std::vector<uint8_t> buf; | |
| buf.resize(sizeof(PacketHeader) + payload.size()); | |
| // Write header in little endian as on the wire | |
| auto* out = reinterpret_cast<PacketHeader*>(buf.data()); | |
| out->magic = corelib::boost_compat::endian::native_to_little(header.magic); | |
| out->version = header.version; | |
| out->length = corelib::boost_compat::endian::native_to_little(header.length); | |
| out->id = corelib::boost_compat::endian::native_to_little(header.id); | |
| out->hash = corelib::boost_compat::endian::native_to_little(header.hash); | |
| out->order_security_id = corelib::boost_compat::endian::native_to_little(header.order_security_id); | |
| out->flags = header.flags; | |
| if (!payload.empty()) | |
| { | |
| std::memcpy(buf.data() + sizeof(PacketHeader), payload.data(), payload.size()); | |
| } | |
| return buf; | |
| } | |
| std::vector<uint8_t> RandomPayload(std::mt19937& rng, uint32_t maxSize) | |
| { | |
| std::uniform_int_distribution<uint32_t> sizeDist(0, std::min<uint32_t>(maxSize, PacketHeader::kMaxDataSize)); | |
| const uint32_t size = sizeDist(rng); | |
| std::vector<uint8_t> data(size); | |
| std::uniform_int_distribution<uint32_t> byteDist(0, 255); | |
| for (auto& b : data) b = static_cast<uint8_t>(byteDist(rng)); | |
| return data; | |
| } | |
| // Mutate a payload using several strategies | |
| std::vector<std::vector<uint8_t>> MutatePayloads(const std::vector<uint8_t>& base, | |
| std::mt19937& rng, | |
| uint32_t maxVariants) | |
| { | |
| std::vector<std::vector<uint8_t>> out; | |
| out.reserve(8 + maxVariants); | |
| // 1) Bit flip single bits across bytes | |
| for (size_t i = 0; i < std::min<size_t>(base.size(), 16); ++i) | |
| { | |
| for (int bit = 0; bit < 8; ++bit) | |
| { | |
| auto m = base; | |
| m[i] ^= static_cast<uint8_t>(1u << bit); | |
| out.emplace_back(std::move(m)); | |
| if (out.size() >= maxVariants) return out; | |
| } | |
| } | |
| // 2) Insert random byte at random position | |
| { | |
| std::uniform_int_distribution<size_t> posDist(0, base.size()); | |
| for (uint32_t k = 0; k < 8 && out.size() < maxVariants; ++k) | |
| { | |
| auto m = base; | |
| const auto pos = posDist(rng); | |
| m.insert(m.begin() + static_cast<std::ptrdiff_t>(pos), static_cast<uint8_t>(rng() & 0xFF)); | |
| out.emplace_back(std::move(m)); | |
| } | |
| } | |
| // 3) Truncate to random shorter length | |
| if (!base.empty()) | |
| { | |
| std::uniform_int_distribution<size_t> cutDist(0, base.size() - 1); | |
| for (uint32_t k = 0; k < 8 && out.size() < maxVariants; ++k) | |
| { | |
| auto m = base; | |
| m.resize(cutDist(rng)); | |
| out.emplace_back(std::move(m)); | |
| } | |
| } | |
| // 4) Over-extend certain leading length fields (first 2/4 bytes) | |
| if (base.size() >= 2) | |
| { | |
| auto m = base; | |
| m[0] = 0xFF; m[1] = 0xFF; | |
| out.emplace_back(std::move(m)); | |
| } | |
| if (base.size() >= 4) | |
| { | |
| auto m = base; | |
| m[0] = 0xFF; m[1] = 0xFF; m[2] = 0xFF; m[3] = 0xFF; | |
| out.emplace_back(std::move(m)); | |
| } | |
| return out; | |
| } | |
| // Crafted edge cases for strings/optionals/vector counts, etc. | |
| std::vector<std::vector<uint8_t>> EdgeCasePayloads(uint32_t maxSize) | |
| { | |
| std::vector<std::vector<uint8_t>> cases; | |
| // Empty payload | |
| cases.emplace_back(); | |
| // 1 byte | |
| cases.emplace_back(1, 0x00); | |
| // Max size payload (capped by maxSize) | |
| cases.emplace_back(std::min<uint32_t>(maxSize, PacketHeader::kMaxDataSize), 0xFF); | |
| // String length overrun (len says large, but body small) | |
| { | |
| std::vector<uint8_t> p; | |
| // Write uint16_t length = 0xFFFF, no bytes following | |
| p.resize(2); | |
| p[0] = 0xFF; p[1] = 0xFF; | |
| cases.emplace_back(std::move(p)); | |
| } | |
| // OptionalFields bitmask huge but no data | |
| { | |
| std::vector<uint8_t> p; | |
| // OptionalFields bits are uint64_t (implementation detail: see Serialization OptionalFields::BitsType) | |
| p.resize(8, 0xFF); // all bits set, but nothing else | |
| cases.emplace_back(std::move(p)); | |
| } | |
| return cases; | |
| } | |
| // Meaningless arbitrary payloads with common test patterns and varied sizes | |
| std::vector<std::vector<uint8_t>> BuildArbitraryPayloads(uint32_t maxSize, std::mt19937& rng) | |
| { | |
| const uint32_t cap = std::min<uint32_t>(maxSize, PacketHeader::kMaxDataSize); | |
| std::vector<uint32_t> lens = { | |
| 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 31, 32, 63, 64, | |
| 127, 128, 255, 256, 512, 768, 1024, 2048, 4096 | |
| }; | |
| // Trim to cap | |
| for (auto& L : lens) if (L > cap) L = cap; | |
| lens.erase(std::remove_if(lens.begin(), lens.end(), [&](uint32_t L){ return L > cap; }), lens.end()); | |
| std::vector<std::vector<uint8_t>> out; | |
| out.reserve(lens.size() * 6); | |
| auto makePattern = [&](uint32_t len, uint8_t v){ std::vector<uint8_t> p(len, v); return p; }; | |
| for (uint32_t len : lens) | |
| { | |
| // All zeros | |
| out.emplace_back(makePattern(len, 0x00)); | |
| // All 0xFF | |
| out.emplace_back(makePattern(len, 0xFF)); | |
| // 0xAA pattern | |
| out.emplace_back(makePattern(len, 0xAA)); | |
| // 0x55 pattern | |
| out.emplace_back(makePattern(len, 0x55)); | |
| // Incrementing bytes | |
| { | |
| std::vector<uint8_t> inc(len); | |
| for (uint32_t i = 0; i < len; ++i) inc[i] = static_cast<uint8_t>(i); | |
| out.emplace_back(std::move(inc)); | |
| } | |
| // Random bytes | |
| { | |
| std::vector<uint8_t> rnd(len); | |
| std::uniform_int_distribution<uint32_t> byteDist(0, 255); | |
| for (auto& b : rnd) b = static_cast<uint8_t>(byteDist(rng)); | |
| out.emplace_back(std::move(rnd)); | |
| } | |
| } | |
| // A few large cases near cap | |
| if (cap >= 1) | |
| { | |
| auto addNear = [&](uint32_t L){ if (L <= cap) out.emplace_back(makePattern(L, 0xEE)); }; | |
| addNear(cap); | |
| addNear(cap > 1 ? cap - 1 : cap); | |
| addNear(cap > 64 ? cap - 64 : cap); | |
| } | |
| return out; | |
| } | |
| template <typename TPacket> | |
| std::vector<uint8_t> SerializePacket(const TPacket& p) | |
| { | |
| const auto objSize = GetObjectSize(p); | |
| std::vector<uint8_t> payload(objSize); | |
| asio::mutable_buffer buf(payload.data(), payload.size()); | |
| Write(buf, p); | |
| return payload; | |
| } | |
| // Create a long unicode string (UTF-8) with mixed plane chars | |
| std::string BuildUnicodeString() | |
| { | |
| // Mixed ASCII, BMP, and some 4-byte UTF-8 sequences | |
| std::string s = "ASCII äöü ĄŻźć中華人民共和國 "; | |
| // Add emojis / surrogate pairs in UTF-8 | |
| const char* emojis[] = {"\xF0\x9F\x98\x80", "\xF0\x9F\x9A\x80", "\xF0\x9F\x8E\x89"}; | |
| for (int i = 0; i < 128; ++i) | |
| { | |
| s += emojis[i % 3]; | |
| } | |
| return s; | |
| } | |
| // Load BLNS (Big List of Naughty Strings) from disk if provided; otherwise return a built-in subset | |
| std::vector<std::string> LoadNaughtyStrings(const std::string& path) | |
| { | |
| std::vector<std::string> out; | |
| if (!path.empty()) | |
| { | |
| std::ifstream f(path); | |
| if (f) | |
| { | |
| std::string line; | |
| while (std::getline(f, line)) | |
| { | |
| if (line.empty() || line[0] == '#') continue; | |
| out.push_back(line); | |
| } | |
| } | |
| } | |
| if (out.empty()) | |
| { | |
| // Minimal embedded subset if file unavailable | |
| out = { | |
| "\t\n\r", // control chars | |
| "\x00", // null byte | |
| "'\"<>/&", // HTML special | |
| "..\\..\\..\\", // path traversal | |
| "😀🚀🎉", // emojis | |
| "零一二三四五六七八九十", // CJK numerals | |
| "%s %x %n %p", // printf tokens | |
| "${7*7}", // template injection | |
| "DROP TABLE users;--", // SQLi | |
| }; | |
| } | |
| return out; | |
| } | |
| // Python-centric injection strings commonly seen in client script contexts | |
| std::vector<std::string> BuildPythonInjectionStrings() | |
| { | |
| return { | |
| "__import__('os').system('calc')", | |
| "__import__('subprocess').Popen(['calc'])", | |
| "(lambda: __import__('os').system('echo PY'))()", | |
| "__class__.__mro__", | |
| "globals()", | |
| "locals()", | |
| "__import__('builtins').eval('__import\\x28\\'os\\'\\x29.system(\'echo X\')')", | |
| """'; __import__('os').system('echo INJ'); '""", | |
| "{.__class__.__mro__[1].__subclasses__()}", | |
| "__import__('sys').modules", | |
| "().__class__.__base__.__subclasses__()", | |
| "print.__class__.__mro__", | |
| "open('/etc/passwd')", | |
| "__import__('sys').setrecursionlimit(10**6)", | |
| }; | |
| } | |
| // Structured payload builders for commonly used packets | |
| std::vector<std::vector<uint8_t>> BuildStructuredPayloads(uint16_t id, bool unicodeHeavy) | |
| { | |
| using namespace std; | |
| vector<vector<uint8_t>> out; | |
| switch (id) | |
| { | |
| case HEADER_CG_KEY_LOGIN: | |
| { | |
| CgKeyLoginPacket k{}; | |
| k.login = "testlogin"; k.sessionId = 1; k.ac_sessionId = "ac"; k.hwid_1 = "HW1"; k.hwid_2 = "HW2"; k.hwid_3 = "HW3"; k.discord_id = "0"; k.client_version = 0; out.emplace_back(SerializePacket(k)); | |
| k.login.assign(32, 'L'); k.sessionId = 0xFFFFFFFFFFFFFFFFull; k.ac_sessionId.assign(64, 'A'); k.hwid_1.assign(64, '1'); k.hwid_2.assign(64, '2'); k.hwid_3.assign(64, '3'); k.discord_id.assign(64, 'D'); k.client_version = 0xFFFFFFFF; out.emplace_back(SerializePacket(k)); | |
| break; | |
| } | |
| case HEADER_CG_CHARACTER_SELECT: | |
| { | |
| TPacketCGPlayerSelect sel{}; sel.index = 0; sel.localeName = "en"; out.emplace_back(SerializePacket(sel)); sel.index = 3; sel.localeName = "tr"; out.emplace_back(SerializePacket(sel)); | |
| break; | |
| } | |
| case HEADER_CG_CHARACTER_CREATE: | |
| { | |
| TPacketCGPlayerCreate cr{}; cr.index = 0; cr.name = "Hero"; cr.job = 0; cr.shape = 0; cr.Con = 3; cr.Int = 3; cr.Str = 3; cr.Dex = 3; cr.empire = 1; out.emplace_back(SerializePacket(cr)); | |
| cr.name.assign(20, 'N'); cr.job = 3; cr.shape = 1; cr.empire = 3; out.emplace_back(SerializePacket(cr)); | |
| break; | |
| } | |
| case HEADER_CG_CHARACTER_DELETE: | |
| { | |
| TPacketCGPlayerDelete del{}; del.index = 0; del.private_code = "000000"; out.emplace_back(SerializePacket(del)); | |
| del.index = 3; del.private_code = std::string(14, '9'); out.emplace_back(SerializePacket(del)); | |
| break; | |
| } | |
| case HEADER_CG_ENTERGAME: | |
| { | |
| TPacketCGEnterGame eg{}; eg.client_version = 0; eg.file_hash = 0; eg.mem_hash = 0; eg.mem_corrupted = 0; eg.ac_init = 1; out.emplace_back(SerializePacket(eg)); | |
| eg.client_version = 0xFFFFFFFF; eg.file_hash = 0xFFFFFFFF; eg.mem_hash = 0xFFFFFFFF; eg.mem_corrupted = 1; eg.ac_init = 0; out.emplace_back(SerializePacket(eg)); | |
| break; | |
| } | |
| case HEADER_CG_MARK_CRCLIST: | |
| { | |
| TPacketCGMarkCRCList ml{}; ml.imgIdx = 0; memset(ml.crclist, 0, sizeof(ml.crclist)); out.emplace_back(SerializePacket(ml)); | |
| break; | |
| } | |
| case HEADER_CG_MARK_IDXLIST: | |
| { | |
| TPacketCGMarkIDXList mid{}; out.emplace_back(SerializePacket(mid)); | |
| break; | |
| } | |
| case HEADER_CG_MARK_UPLOAD: | |
| { | |
| TPacketCGMarkUpload mu{}; mu.gid = 1; // fill some bytes in image | |
| for (size_t i = 0; i < sizeof(mu.image); i += 4) { mu.image[i] = 0xFF; } | |
| out.emplace_back(SerializePacket(mu)); | |
| break; | |
| } | |
| case HEADER_CG_GUILD_SYMBOL_UPLOAD: | |
| { | |
| TPacketCGGuildSymbolUpload gs{}; gs.guild_id = 1; gs.data.assign(256, 0x7F); out.emplace_back(SerializePacket(gs)); | |
| gs.data.assign(64 * 1024, 0xFF); out.emplace_back(SerializePacket(gs)); | |
| break; | |
| } | |
| case HEADER_CG_SYMBOL_CRC: | |
| { | |
| TPacketCGSymbolCRC sc{}; sc.guild_id = 1; sc.crc = 0; sc.size = 0; sc.lastRequest = 1; out.emplace_back(SerializePacket(sc)); | |
| sc.crc = 0xDEADBEEF; sc.size = 4096; sc.lastRequest = 0; out.emplace_back(SerializePacket(sc)); | |
| break; | |
| } | |
| case HEADER_CG_CHARACTER_MOVE: | |
| { | |
| TPacketCGMove m{}; | |
| // minimally valid and boundary variations | |
| m.bFunc = FUNC_MOVE; m.lX = 0; m.lY = 0; m.dwTime = 1; out.emplace_back(SerializePacket(m)); | |
| m.lX = numeric_limits<int32_t>::max(); m.lY = numeric_limits<int32_t>::max(); out.emplace_back(SerializePacket(m)); | |
| m.lX = numeric_limits<int32_t>::min(); m.lY = numeric_limits<int32_t>::min(); out.emplace_back(SerializePacket(m)); | |
| m.lX = -1; m.lY = -1; out.emplace_back(SerializePacket(m)); | |
| m.lZ = numeric_limits<int32_t>::max(); out.emplace_back(SerializePacket(m)); | |
| m.lZ = numeric_limits<int32_t>::min(); out.emplace_back(SerializePacket(m)); | |
| m.bRot = 255; m.color = 0xFFFFFFFF; m.motionKey = 0xFFFFFFFF; out.emplace_back(SerializePacket(m)); | |
| m.loopCount = 255; m.isMovingSkill = 1; out.emplace_back(SerializePacket(m)); | |
| break; | |
| } | |
| case HEADER_CG_ATTACK: | |
| { | |
| TPacketCGAttack a{}; | |
| a.bType = 0; a.dwVictimVID = 1; out.emplace_back(SerializePacket(a)); | |
| a.dwVictimVID = 0; a.attackTime = 0; out.emplace_back(SerializePacket(a)); | |
| a.attackTime = 0xFFFFFFFF; out.emplace_back(SerializePacket(a)); | |
| a.motionKey = 0; out.emplace_back(SerializePacket(a)); | |
| a.motionKey = 0xFFFFFFFF; out.emplace_back(SerializePacket(a)); | |
| a.x = std::numeric_limits<uint32_t>::max(); a.y = 0; out.emplace_back(SerializePacket(a)); | |
| a.pushX = 0xFFFFFFFF; a.pushY = 0xFFFFFFFF; out.emplace_back(SerializePacket(a)); | |
| break; | |
| } | |
| case HEADER_CG_CHAT: | |
| { | |
| TPacketCGChat c{}; | |
| c.type = 0; c.lang = 0; c.message = "hi"; out.emplace_back(SerializePacket(c)); | |
| c.message.assign(1024, 'A'); out.emplace_back(SerializePacket(c)); | |
| c.message.clear(); out.emplace_back(SerializePacket(c)); | |
| if (unicodeHeavy) | |
| { | |
| c.message = BuildUnicodeString(); out.emplace_back(SerializePacket(c)); | |
| } | |
| break; | |
| } | |
| case HEADER_CG_ITEM_MOVE: | |
| { | |
| TPacketCGItemMove im{}; | |
| out.emplace_back(SerializePacket(im)); | |
| im.count = static_cast<CountType>(0); out.emplace_back(SerializePacket(im)); | |
| im.count = std::numeric_limits<CountType>::max(); out.emplace_back(SerializePacket(im)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_DROP: | |
| { | |
| TPacketCGItemDrop d{}; | |
| d.gold = 0; out.emplace_back(SerializePacket(d)); | |
| d.gold = static_cast<Gold>(-1); out.emplace_back(SerializePacket(d)); | |
| d.gold = std::numeric_limits<Gold>::max(); out.emplace_back(SerializePacket(d)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_DROP2: | |
| { | |
| TPacketCGItemDrop2 d{}; d.Cell = TItemPos{0,0}; d.gold = 0; d.count = 0; out.emplace_back(SerializePacket(d)); | |
| d.gold = std::numeric_limits<Gold>::max(); d.count = std::numeric_limits<CountType>::max(); out.emplace_back(SerializePacket(d)); | |
| break; | |
| } | |
| case HEADER_CG_EXCHANGE: | |
| { | |
| TPacketCGExchange e{}; | |
| // Try all subheaders minimally | |
| for (uint8_t sh = EXCHANGE_SUBHEADER_CG_START; sh <= EXCHANGE_SUBHEADER_CG_CANCEL; ++sh) | |
| { | |
| e.sub_header = sh; | |
| out.emplace_back(SerializePacket(e)); | |
| } | |
| break; | |
| } | |
| case HEADER_CG_USE_SKILL: | |
| { | |
| TPacketCGUseSkill us{}; | |
| us.dwVnum = 0; us.dwVID = 0; out.emplace_back(SerializePacket(us)); | |
| us.dwVnum = 0xFFFFFFFF; us.dwVID = 0xFFFFFFFF; out.emplace_back(SerializePacket(us)); | |
| break; | |
| } | |
| case HEADER_CG_CHOOSE_SKILL_GROUP: | |
| { | |
| TPacketCGChooseSkillGroup csg{}; csg.skillGroup = 0; out.emplace_back(SerializePacket(csg)); csg.skillGroup = 2; out.emplace_back(SerializePacket(csg)); | |
| break; | |
| } | |
| case HEADER_CG_ON_CLICK: | |
| { | |
| TPacketCGOnClick oc{}; oc.vid = 0; out.emplace_back(SerializePacket(oc)); oc.vid = 0xFFFFFFFF; out.emplace_back(SerializePacket(oc)); | |
| break; | |
| } | |
| case HEADER_CG_TARGET: | |
| { | |
| TPacketCGTarget t{}; t.dwVID = 0; out.emplace_back(SerializePacket(t)); t.dwVID = 0xFFFFFFFF; out.emplace_back(SerializePacket(t)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_USE: | |
| { | |
| TPacketCGItemUse u{}; u.Cell = TItemPos{0, 0}; out.emplace_back(SerializePacket(u)); | |
| u.Cell = TItemPos{1, 200}; out.emplace_back(SerializePacket(u)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_USE_TO_ITEM: | |
| { | |
| TPacketCGItemUseToItem u{}; u.Cell = TItemPos{0,0}; u.TargetCell = TItemPos{1,0}; out.emplace_back(SerializePacket(u)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_SPLIT: | |
| { | |
| TPacketCGItemSplit sp{}; sp.Cell = TItemPos{0,0}; sp.count = 0; out.emplace_back(SerializePacket(sp)); | |
| sp.count = std::numeric_limits<CountType>::max(); out.emplace_back(SerializePacket(sp)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_PICKUP: | |
| { | |
| TPacketCGItemPickup pu{}; pu.vid = 0; pu.time = 0; out.emplace_back(SerializePacket(pu)); | |
| pu.vid = 0xFFFFFFFF; pu.time = 0xFFFFFFFF; out.emplace_back(SerializePacket(pu)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_GIVE: | |
| { | |
| TPacketCGGiveItem gi{}; gi.dwTargetVID = 0; gi.ItemPos = TItemPos{0,0}; gi.byItemCount = 0; out.emplace_back(SerializePacket(gi)); | |
| gi.dwTargetVID = 0xFFFFFFFF; gi.byItemCount = std::numeric_limits<CountType>::max(); out.emplace_back(SerializePacket(gi)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_DESTROY: | |
| { | |
| TPacketCGItemDestroy d{}; d.Cell = TItemPos{0,0}; out.emplace_back(SerializePacket(d)); | |
| break; | |
| } | |
| case HEADER_CG_ITEM_COMBINATION: | |
| { | |
| TPacketCGItemCombiation ic{}; ic.mediumPos = TItemPos{0,0}; ic.basePos = TItemPos{1,0}; ic.materialPos = TItemPos{2,0}; out.emplace_back(SerializePacket(ic)); | |
| break; | |
| } | |
| case HEADER_CG_REFINE: | |
| { | |
| TPacketCGRefine r{}; r.pos = 0; r.type = 0; out.emplace_back(SerializePacket(r)); r.pos = 0xFFFF; r.type = 0xFF; out.emplace_back(SerializePacket(r)); | |
| break; | |
| } | |
| case HEADER_CG_FISHING: | |
| { | |
| TPacketCGFishing f{}; f.dir = 0; out.emplace_back(SerializePacket(f)); f.dir = 255; out.emplace_back(SerializePacket(f)); | |
| break; | |
| } | |
| case HEADER_CG_FISHING_GAME: | |
| { | |
| TPacketCGFishingGame fg{}; fg.hitCount = 0; fg.time = 0.f; out.emplace_back(SerializePacket(fg)); fg.hitCount = 255; fg.time = 9999.9f; out.emplace_back(SerializePacket(fg)); | |
| break; | |
| } | |
| case HEADER_CG_FLY_TARGETING: | |
| { | |
| TPacketCGFlyTargeting ft{}; ft.dwTargetVID = 0; ft.x = 0; ft.y = 0; out.emplace_back(SerializePacket(ft)); | |
| ft.dwTargetVID = 0xFFFFFFFF; ft.x = std::numeric_limits<int32_t>::max(); ft.y = std::numeric_limits<int32_t>::min(); out.emplace_back(SerializePacket(ft)); | |
| break; | |
| } | |
| case HEADER_CG_ADD_FLY_TARGETING: | |
| { | |
| TPacketCGFlyTargeting ft{}; ft.dwTargetVID = 0; ft.x = 1; ft.y = 1; out.emplace_back(SerializePacket(ft)); | |
| break; | |
| } | |
| case HEADER_CG_SHOOT: | |
| { | |
| TPacketCGShoot s{}; s.type = 0; s.motionKey = 0; out.emplace_back(SerializePacket(s)); s.type = 0xFF; s.motionKey = 0xFFFFFFFF; out.emplace_back(SerializePacket(s)); | |
| break; | |
| } | |
| case HEADER_CG_QUICKSLOT_ADD: | |
| { | |
| TPacketCGQuickslotAdd qa{}; qa.pos = 0; qa.slot = TQuickslot{}; out.emplace_back(SerializePacket(qa)); qa.pos = 255; out.emplace_back(SerializePacket(qa)); | |
| break; | |
| } | |
| case HEADER_CG_QUICKSLOT_DEL: | |
| { | |
| TPacketCGQuickslotDel qd{}; qd.pos = 0; out.emplace_back(SerializePacket(qd)); qd.pos = 255; out.emplace_back(SerializePacket(qd)); | |
| break; | |
| } | |
| case HEADER_CG_QUICKSLOT_SWAP: | |
| { | |
| TPacketCGQuickslotSwap qs{}; qs.pos = 0; qs.change_pos = 1; out.emplace_back(SerializePacket(qs)); qs.pos = 255; qs.change_pos = 254; out.emplace_back(SerializePacket(qs)); | |
| break; | |
| } | |
| case HEADER_CG_CHAT_FILTER: | |
| { | |
| TPacketCGChatFilter cf{}; cf.bFilterList = {0,1,0,1,1,0,0,1}; out.emplace_back(SerializePacket(cf)); | |
| cf.bFilterList.assign(32, 1); out.emplace_back(SerializePacket(cf)); | |
| break; | |
| } | |
| case HEADER_CG_SYNC_POSITION: | |
| { | |
| TPacketCGSyncPosition sp{}; out.emplace_back(SerializePacket(sp)); | |
| sp.elems.push_back({0, 0, 0}); out.emplace_back(SerializePacket(sp)); | |
| sp.elems.push_back({0xFFFFFFFF, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min()}); out.emplace_back(SerializePacket(sp)); | |
| break; | |
| } | |
| case HEADER_CG_SAFEBOX_ITEM_MOVE: | |
| { | |
| TPacketCGItemMove mv{}; mv.Cell = TItemPos{0,0}; mv.CellTo = TItemPos{1,0}; mv.count = 1; out.emplace_back(SerializePacket(mv)); | |
| break; | |
| } | |
| case HEADER_CG_SAFEBOX_CHECKOUT: | |
| { | |
| TPacketCGSafeboxCheckout so{}; so.bSafePos = 0; so.ItemPos = TItemPos{0,0}; out.emplace_back(SerializePacket(so)); | |
| break; | |
| } | |
| case HEADER_CG_SAFEBOX_CHECKIN: | |
| { | |
| TPacketCGSafeboxCheckin si{}; si.bSafePos = 0; si.ItemPos = TItemPos{0,0}; out.emplace_back(SerializePacket(si)); | |
| break; | |
| } | |
| case HEADER_CG_TARGET_LOAD: | |
| { | |
| TPacketCGTargetLoad tl{}; tl.dwVID = 0; out.emplace_back(SerializePacket(tl)); tl.dwVID = 0xFFFFFFFF; out.emplace_back(SerializePacket(tl)); | |
| break; | |
| } | |
| case HEADER_CG_CHANGE_SKILL_COLOR: | |
| { | |
| TPacketCGChangeSkillColorPacket cscp{}; cscp.vnum = 0; cscp.color = 0; out.emplace_back(SerializePacket(cscp)); cscp.vnum = 0xFFFFFFFF; cscp.color = 0xFFFFFFFF; out.emplace_back(SerializePacket(cscp)); | |
| break; | |
| } | |
| case HEADER_CG_CHANGE_EMPIRE: | |
| { | |
| CgChangeEmpirePacket cep{}; cep.slot = 0; cep.empire = 0; out.emplace_back(SerializePacket(cep)); cep.slot = 3; cep.empire = 3; out.emplace_back(SerializePacket(cep)); | |
| break; | |
| } | |
| case HEADER_CG_BLOCK_MODE: | |
| { | |
| CgBlockModePacket bm{}; bm.blockMode = 0; out.emplace_back(SerializePacket(bm)); bm.blockMode = 0xFFFFFFFF; out.emplace_back(SerializePacket(bm)); | |
| break; | |
| } | |
| case HEADER_CG_SET_TITLE: | |
| { | |
| CgSetTitlePacket st{}; st.color = 0; st.szTitle = "Title"; out.emplace_back(SerializePacket(st)); | |
| if (unicodeHeavy) { st.szTitle = BuildUnicodeString(); out.emplace_back(SerializePacket(st)); } | |
| break; | |
| } | |
| case HEADER_CG_CHANGE_NAME: | |
| { | |
| CgChangeNamePacket chn{}; chn.index = 0; chn.name = "NewName"; out.emplace_back(SerializePacket(chn)); | |
| break; | |
| } | |
| case HEADER_CG_DUNGEON_WARP: | |
| { | |
| TPacketCGDungeonWarp dw{}; dw.bDungeonID = 0; out.emplace_back(SerializePacket(dw)); dw.bDungeonID = 255; out.emplace_back(SerializePacket(dw)); | |
| break; | |
| } | |
| case HEADER_CG_DUNGEON_RANKING: | |
| { | |
| TPacketCGDungeonRanking dr{}; dr.bDungeonID = 0; dr.bRankingType = 0; out.emplace_back(SerializePacket(dr)); dr.bDungeonID = 255; dr.bRankingType = 255; out.emplace_back(SerializePacket(dr)); | |
| break; | |
| } | |
| case HEADER_CG_SWITCHBOT: | |
| { | |
| CgSwitchbotPacket sb{}; sb.subheader = SWITCHBOT_SUBHEADER_CG_SET_ATTRIBUTE; sb.updateAttr = CgSwitchbotAttributeUpdatePacket{0,0,0,{0,0}}; out.emplace_back(SerializePacket(sb)); | |
| sb = CgSwitchbotPacket{}; sb.subheader = SWITCHBOT_SUBHEADER_CG_START_SLOT; sb.status = CgSwitchbotStatusPacket{0}; out.emplace_back(SerializePacket(sb)); | |
| break; | |
| } | |
| case HEADER_CG_BATTLE_PASS: | |
| { | |
| TPacketCGBattlePassAction ba{}; ba.bAction = 0; out.emplace_back(SerializePacket(ba)); ba.bAction = 1; out.emplace_back(SerializePacket(ba)); | |
| break; | |
| } | |
| case HEADER_CG_SCRIPT_BUTTON: | |
| { | |
| TPacketCGScriptButton sbn{}; sbn.idx = 0; out.emplace_back(SerializePacket(sbn)); sbn.idx = 0xFFFFFFFF; out.emplace_back(SerializePacket(sbn)); | |
| break; | |
| } | |
| case HEADER_CG_SCRIPT_SELECT_ITEM: | |
| { | |
| TPacketCGScriptSelectItem ssel{}; ssel.selection = 0; out.emplace_back(SerializePacket(ssel)); ssel.selection = 0xFFFFFFFF; out.emplace_back(SerializePacket(ssel)); | |
| break; | |
| } | |
| case HEADER_CG_SCRIPT_ANSWER: | |
| { | |
| TPacketCGScriptAnswer sans{}; sans.answer = 0; sans.qIndex = 0; out.emplace_back(SerializePacket(sans)); sans.answer = 1; sans.qIndex = -1; out.emplace_back(SerializePacket(sans)); | |
| break; | |
| } | |
| case HEADER_CG_QUEST_INPUT_STRING: | |
| { | |
| TPacketCGQuestInputString qis{}; qis.msg = ""; qis.qIndex = 0; out.emplace_back(SerializePacket(qis)); qis.msg.assign(256, 'Q'); qis.qIndex = -1; out.emplace_back(SerializePacket(qis)); | |
| break; | |
| } | |
| case HEADER_CG_QUEST_RECEIVE: | |
| { | |
| TPacketCGQuestRcv qrcv{}; qrcv.questID = 0; qrcv.msg = "hi"; out.emplace_back(SerializePacket(qrcv)); qrcv.questID = 0xFFFFFFFF; qrcv.msg.clear(); out.emplace_back(SerializePacket(qrcv)); | |
| break; | |
| } | |
| case HEADER_CG_QUEST_CONFIRM: | |
| { | |
| TPacketCGQuestConfirm qc{}; qc.answer = 0; qc.requestPID = 0; out.emplace_back(SerializePacket(qc)); qc.answer = 1; qc.requestPID = 0xFFFFFFFF; out.emplace_back(SerializePacket(qc)); | |
| break; | |
| } | |
| case HEADER_CG_PARTY_REMOVE: | |
| { | |
| TPacketCGPartyRemove pr{}; pr.pid = 0; out.emplace_back(SerializePacket(pr)); pr.pid = 0xFFFFFFFF; out.emplace_back(SerializePacket(pr)); | |
| break; | |
| } | |
| case HEADER_CG_PARTY_PARAMETER: | |
| { | |
| TPacketCGPartyParameter pp{}; pp.bDistributeMode = 0; out.emplace_back(SerializePacket(pp)); pp.bDistributeMode = 2; out.emplace_back(SerializePacket(pp)); | |
| break; | |
| } | |
| case HEADER_CG_ANSWER_MAKE_GUILD: | |
| { | |
| TPacketCGAnswerMakeGuild amg{}; amg.guild_name = "Guild"; out.emplace_back(SerializePacket(amg)); | |
| break; | |
| } | |
| case HEADER_CG_HACK: | |
| { | |
| TPacketCGHack hk{}; hk.szBuf = ""; hk.szInfo = ""; out.emplace_back(SerializePacket(hk)); hk.szBuf.assign(64, 'H'); hk.szInfo.assign(64, 'I'); out.emplace_back(SerializePacket(hk)); | |
| break; | |
| } | |
| case HEADER_CG_MYSHOP: | |
| { | |
| CgMyShopOpen ms{}; ms.sign = "Shop"; ms.bundleItem = TItemPos{0,0}; ms.count = 0; out.emplace_back(SerializePacket(ms)); | |
| break; | |
| } | |
| case HEADER_CG_MESSENGER: | |
| { | |
| // Simple TPacketCGMessenger add_by_name/remove coverage | |
| TPacketCGMessenger m{}; m.subheader = MESSENGER_SUBHEADER_CG_ADD_BY_NAME; m.name = std::string("Friend"); out.emplace_back(SerializePacket(m)); | |
| m = TPacketCGMessenger{}; m.subheader = MESSENGER_SUBHEADER_CG_REMOVE; m.name = std::string("Friend"); out.emplace_back(SerializePacket(m)); | |
| break; | |
| } | |
| case HEADER_CG_WIKI_REQUEST: | |
| { | |
| CgRecvWikiPacket wk{}; wk.ret_id = 0; wk.vnum = 0; wk.is_mob = 0; out.emplace_back(SerializePacket(wk)); wk.ret_id = 1; wk.vnum = 12345; wk.is_mob = 1; out.emplace_back(SerializePacket(wk)); | |
| break; | |
| } | |
| case HEADER_CG_SHOP: | |
| { | |
| // Cover shop subheaders with minimal optional fields | |
| TPacketCGShop sp{}; | |
| // BUY | |
| sp = TPacketCGShop{}; sp.subheader = SHOP_SUBHEADER_CG_BUY; sp.buyAction = CgShopActionBuy{0, 1}; out.emplace_back(SerializePacket(sp)); | |
| // SELL | |
| sp = TPacketCGShop{}; sp.subheader = SHOP_SUBHEADER_CG_SELL; sp.sellAction = CgShopActionSell{TItemPos{0, 0}, 1}; out.emplace_back(SerializePacket(sp)); | |
| // SELL2 | |
| sp = TPacketCGShop{}; sp.subheader = SHOP_SUBHEADER_CG_SELL2; sp.sellAction = CgShopActionSell{TItemPos{1, 2}, 255}; out.emplace_back(SerializePacket(sp)); | |
| // END | |
| sp = TPacketCGShop{}; sp.subheader = SHOP_SUBHEADER_CG_END; out.emplace_back(SerializePacket(sp)); | |
| break; | |
| } | |
| case HEADER_CG_ACCE: | |
| { | |
| // ACCE subheader coverage | |
| TPacketCGAcce ap{}; | |
| // CHECKIN | |
| ap = TPacketCGAcce{}; ap.subheader = ACCE_SUBHEADER_CG_REFINE_CHECKIN; ap.checkin = TPacketCGAcceCheckin{0, TItemPos{0, 0}, 0}; out.emplace_back(SerializePacket(ap)); | |
| // CHECKOUT | |
| ap = TPacketCGAcce{}; ap.subheader = ACCE_SUBHEADER_CG_REFINE_CHECKOUT; ap.checkout = TPacketCGAcceCheckout{0}; out.emplace_back(SerializePacket(ap)); | |
| // ACCEPT | |
| ap = TPacketCGAcce{}; ap.subheader = ACCE_SUBHEADER_CG_REFINE_ACCEPT; ap.accept = TPacketCGAcceRefineAccept{0}; out.emplace_back(SerializePacket(ap)); | |
| // CANCEL | |
| ap = TPacketCGAcce{}; ap.subheader = ACCE_SUBHEADER_CG_REFINE_CANCEL; out.emplace_back(SerializePacket(ap)); | |
| break; | |
| } | |
| case HEADER_CG_CHANGELOOK: | |
| { | |
| // CHANGELOOK subheader coverage | |
| CgChangeLookPacket cl{}; | |
| // CHECKIN | |
| cl = CgChangeLookPacket{}; cl.subheader = CHANGELOOK_SUBHEADER_CG_REFINE_CHECKIN; cl.checkin = CgChangeLookCheckinPacket{TItemPos{0, 0}, 0}; out.emplace_back(SerializePacket(cl)); | |
| // CHECKOUT | |
| cl = CgChangeLookPacket{}; cl.subheader = CHANGELOOK_SUBHEADER_CG_REFINE_CHECKOUT; cl.checkout = static_cast<uint8_t>(0); out.emplace_back(SerializePacket(cl)); | |
| // ACCEPT | |
| cl = CgChangeLookPacket{}; cl.subheader = CHANGELOOK_SUBHEADER_CG_REFINE_ACCEPT; out.emplace_back(SerializePacket(cl)); | |
| // CANCEL | |
| cl = CgChangeLookPacket{}; cl.subheader = CHANGELOOK_SUBHEADER_CG_REFINE_CANCEL; out.emplace_back(SerializePacket(cl)); | |
| break; | |
| } | |
| case HEADER_CG_LEVEL_PET: | |
| { | |
| // LEVEL PET subheader coverage | |
| CgLevelPetPacket lp{}; | |
| // OPEN | |
| lp = CgLevelPetPacket{}; lp.subheader = LEVELPET_SUBHEADER_CG_OPEN; lp.index = static_cast<uint32_t>(0); out.emplace_back(SerializePacket(lp)); | |
| // CLOSE | |
| lp = CgLevelPetPacket{}; lp.subheader = LEVELPET_SUBHEADER_CG_CLOSE; out.emplace_back(SerializePacket(lp)); | |
| // PLUS_ATTR | |
| lp = CgLevelPetPacket{}; lp.subheader = LEVELPET_SUBHEADER_CG_PLUS_ATTR; lp.itemPos = TItemPos{0, 0}; out.emplace_back(SerializePacket(lp)); | |
| break; | |
| } | |
| #ifdef ENABLE_IKASHOP_RENEWAL | |
| case HEADER_CG_NEW_OFFLINESHOP: | |
| { | |
| using namespace std; | |
| // Ikarus Shop: fully cover subheaders | |
| TPacketCGIkarusShop pk{}; | |
| // CREATE_NEW (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_CREATE_NEW; out.emplace_back(SerializePacket(pk)); | |
| // FORCE_CLOSE (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_FORCE_CLOSE; out.emplace_back(SerializePacket(pk)); | |
| // REQUEST_SHOPLIST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_REQUEST_SHOPLIST; out.emplace_back(SerializePacket(pk)); | |
| // OPEN (ownerid) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_OPEN; pk.shopOpen = TSubPacketCGShopOpen{0}; out.emplace_back(SerializePacket(pk)); | |
| // OPEN_OWNER (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_OPEN_OWNER; out.emplace_back(SerializePacket(pk)); | |
| // BUY_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_BUY_ITEM; pk.buyItem = TSubPacketCGShopBuyItem{0u, DEFAULT_ITEM_UUID, false, 0}; out.emplace_back(SerializePacket(pk)); | |
| // ADD_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_ADD_ITEM; pk.addItem = TSubPacketCGAddItem{TItemPos{0,0}, TPriceInfo{0}, 0}; out.emplace_back(SerializePacket(pk)); | |
| // REMOVE_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_REMOVE_ITEM; pk.removeItem = TSubPacketCGRemoveItem{DEFAULT_ITEM_UUID}; out.emplace_back(SerializePacket(pk)); | |
| // EDIT_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_EDIT_ITEM; pk.editItem = TSubPacketCGEditItem{DEFAULT_ITEM_UUID, TPriceInfo{0}}; out.emplace_back(SerializePacket(pk)); | |
| // FILTER_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_FILTER_REQUEST; pk.filterRequest = TSubPacketCGFilterRequest{TFilterInfo{0,0,"", TPriceInfo{0}, TPriceInfo{0}, 0, 0, {}, 0, 0, false}}; out.emplace_back(SerializePacket(pk)); | |
| // SEARCH_FILL_REQUEST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_SEARCH_FILL_REQUEST; out.emplace_back(SerializePacket(pk)); | |
| // OFFER_CREATE | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_OFFER_CREATE; pk.offerCreate = TSubPacketCGOfferCreate{}; out.emplace_back(SerializePacket(pk)); | |
| // OFFER_ACCEPT | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_OFFER_ACCEPT; pk.offerAccept = TSubPacketCGOfferAccept{0}; out.emplace_back(SerializePacket(pk)); | |
| // OFFER_CANCEL | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_OFFER_CANCEL; pk.offerCancel = TSubPacketCGOfferCancel{0,0}; out.emplace_back(SerializePacket(pk)); | |
| // REQUEST_OFFER_LIST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_REQUEST_OFFER_LIST; out.emplace_back(SerializePacket(pk)); | |
| // SAFEBOX_OPEN (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_SAFEBOX_OPEN; out.emplace_back(SerializePacket(pk)); | |
| // SAFEBOX_GET_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_SAFEBOX_GET_ITEM; pk.getItemSafebox = TSubPacketCGShopSafeboxGetItem{DEFAULT_ITEM_UUID}; out.emplace_back(SerializePacket(pk)); | |
| // SAFEBOX_GET_VALUTES (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_SAFEBOX_GET_VALUTES; out.emplace_back(SerializePacket(pk)); | |
| // SAFEBOX_CLOSE (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_SAFEBOX_CLOSE; out.emplace_back(SerializePacket(pk)); | |
| // AUCTION_LIST_REQUEST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_AUCTION_LIST_REQUEST; out.emplace_back(SerializePacket(pk)); | |
| // AUCTION_OPEN_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_AUCTION_OPEN_REQUEST; pk.auctionOpenRequest = TSubPacketCGAuctionOpenRequest{0}; out.emplace_back(SerializePacket(pk)); | |
| // MY_AUCTION_OPEN_REQUEST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_MY_AUCTION_OPEN_REQUEST; out.emplace_back(SerializePacket(pk)); | |
| // MY_AUCTION_CLOSE (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_MY_AUCTION_CLOSE; out.emplace_back(SerializePacket(pk)); | |
| // MY_AUCTION_CANCEL (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_MY_AUCTION_CANCEL; out.emplace_back(SerializePacket(pk)); | |
| // CREATE_AUCTION | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_CREATE_AUCTION; pk.auctionCreate = TSubPacketCGAuctionCreate{TItemPos{0,0}, TPriceInfo{0}}; out.emplace_back(SerializePacket(pk)); | |
| // AUCTION_ADD_OFFER | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_AUCTION_ADD_OFFER; pk.auctionAddOffer = TSubPacketCGAuctionAddOffer{0, TPriceInfo{0}}; out.emplace_back(SerializePacket(pk)); | |
| // EXIT_FROM_AUCTION | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_EXIT_FROM_AUCTION; pk.auctionExitFrom = TSubPacketCGAuctionExitFrom{0}; out.emplace_back(SerializePacket(pk)); | |
| // CLOSE_MY_SHOP_BOARD (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_CLOSE_MY_SHOP_BOARD; out.emplace_back(SerializePacket(pk)); | |
| // CLOSE_OFFER_LIST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_CLOSE_OFFER_LIST; out.emplace_back(SerializePacket(pk)); | |
| // CLOSE_SHOP_GUEST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_CLOSE_SHOP_GUEST; out.emplace_back(SerializePacket(pk)); | |
| #ifdef ENABLE_IKASHOP_ENTITIES | |
| // CLICK_ENTITY | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_CLICK_ENTITY; pk.clickEntity = TSubPacketCGShopClickEntity{0}; out.emplace_back(SerializePacket(pk)); | |
| #ifdef EXTEND_IKASHOP_PRO | |
| // MOVE_SHOP_ENTITY (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_MOVE_SHOP_ENTITY; out.emplace_back(SerializePacket(pk)); | |
| #endif | |
| #endif | |
| #ifdef EXTEND_IKASHOP_PRO | |
| // NOTIFICATION_LIST_REQUEST (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_NOTIFICATION_LIST_REQUEST; out.emplace_back(SerializePacket(pk)); | |
| // NOTIFICATION_LIST_CLOSE (no additional) | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_NOTIFICATION_LIST_CLOSE; out.emplace_back(SerializePacket(pk)); | |
| #endif | |
| #ifdef EXTEND_IKASHOP_ULTIMATE | |
| // PRICE_AVERAGE_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_PRICE_AVERAGE_REQUEST; pk.priceAverageRequest = TSubPacketCGPriceAverageRequest{0, 1}; out.emplace_back(SerializePacket(pk)); | |
| // SHOP_DECORATION_USE | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_DECORATION_USE; pk.decoUse = 0; out.emplace_back(SerializePacket(pk)); | |
| // SHOP_MOVE_ITEM | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_MOVE_ITEM; pk.moveItem = TSubPacketCGShopMoveItem{0, 1}; out.emplace_back(SerializePacket(pk)); | |
| #endif | |
| // CHANGE_NAME | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_CHANGE_NAME; pk.changeName = TSubPacketCGShopChangeName{"Name"}; out.emplace_back(SerializePacket(pk)); | |
| // FILTER_WITH_VNUM_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_FILTER_WITH_VNUM_REQUEST; pk.filterWithVnumRequest = TSubPacketCGFilterWithVnumRequest{0}; out.emplace_back(SerializePacket(pk)); | |
| // FILTER_WITH_SELLER_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_FILTER_WITH_SELLER_REQUEST; pk.filterWithSellerRequest = TSubPacketCGFilterWithSellerRequest{0}; out.emplace_back(SerializePacket(pk)); | |
| // FILTER_WITH_SELLER_NAME_REQUEST | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_FILTER_WITH_SELLER_NAME_REQUEST; pk.filterWithSellerNameRequest = TSubPacketCGFilterWithSellerNameRequest{"Seller"}; out.emplace_back(SerializePacket(pk)); | |
| // BUY_ALL_ITEMS | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_SHOP_BUY_ALL_ITEMS; { | |
| TSubPacketCGShopBuyAllItems b{}; b.itemcount = 0; b.seenprice = 0; pk.buyAllItems = b; out.emplace_back(SerializePacket(pk)); | |
| } | |
| // TELEPORT_TO_SHOP | |
| pk = TPacketCGIkarusShop{}; pk.subheader = SUBHEADER_CG_TELEPORT_TO_SHOP; pk.teleportToShop = TSubPacketCGTeleportToShop{0, 0}; out.emplace_back(SerializePacket(pk)); | |
| break; | |
| } | |
| #endif // ENABLE_IKASHOP_RENEWAL | |
| default: | |
| break; | |
| } | |
| return out; | |
| } | |
| } | |
| namespace fuzz | |
| { | |
| void Run(DESC* desc, const FuzzOptions& opts) | |
| { | |
| if (!desc) | |
| return; | |
| auto* proc = desc->GetInputProcessor(); | |
| if (!proc) | |
| { | |
| SPDLOG_ERROR("Fuzzer: no input processor for desc {}", desc->GetHandle()); | |
| return; | |
| } | |
| auto blns = LoadNaughtyStrings(opts.naughtyStringsPath); | |
| std::mt19937 rng; | |
| if (opts.seed != 0) | |
| rng.seed(opts.seed); | |
| else | |
| rng.seed(static_cast<uint32_t>(std::random_device{}())); | |
| std::vector<uint16_t> ids; | |
| if (!opts.headerIds.empty()) | |
| { | |
| ids = opts.headerIds; | |
| } | |
| else | |
| { | |
| ids.reserve(256); | |
| for (uint16_t i = 0; i < 256; ++i) ids.push_back(i); | |
| } | |
| auto edgeCases = EdgeCasePayloads(opts.maxPayloadSize); | |
| for (auto id : ids) | |
| { | |
| // Try edge cases first | |
| for (const auto& ec : edgeCases) | |
| { | |
| try | |
| { | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(ec.size())); | |
| const auto computedHash = XXH32(ec.data(), ec.size(), PacketHeader::kHashSeed); | |
| h.hash = computedHash; | |
| auto wire = BuildWirePacket(h, ec); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } | |
| catch (const std::exception& ex) | |
| { | |
| SPDLOG_ERROR("Fuzzer edge case id {} failed: {}", id, ex.what()); | |
| } | |
| } | |
| // Structured cases for known packets | |
| if (opts.enableStructured) | |
| { | |
| auto structured = BuildStructuredPayloads(id, opts.unicodeHeavy); | |
| for (auto& sc : structured) | |
| { | |
| try | |
| { | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(sc.size())); | |
| h.hash = XXH32(sc.data(), sc.size(), PacketHeader::kHashSeed); | |
| auto wire = BuildWirePacket(h, sc); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } | |
| catch (const std::exception& ex) | |
| { | |
| SPDLOG_ERROR("Fuzzer structured id {} failed: {}", id, ex.what()); | |
| } | |
| } | |
| // Inject BLNS (naughty strings) into known string-bearing packets | |
| if (opts.enableNaughtyStrings) | |
| { | |
| if (!blns.empty()) | |
| { | |
| uint32_t injected = 0; | |
| // Chat message | |
| if (id == HEADER_CG_CHAT) | |
| { | |
| for (auto& s : blns) | |
| { | |
| if (injected++ >= opts.maxNaughtyPerField) break; | |
| TPacketCGChat c{}; c.type = 0; c.lang = 0; c.message = s; | |
| auto sp = SerializePacket(c); | |
| try { | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(sp.size())); | |
| h.hash = XXH32(sp.data(), sp.size(), PacketHeader::kHashSeed); | |
| auto wire = BuildWirePacket(h, sp); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } catch(const std::exception& ex) { | |
| SPDLOG_ERROR("Fuzzer BLNS chat failed: {}", ex.what()); | |
| } | |
| } | |
| } | |
| // Whisper (name + message) | |
| if (id == HEADER_CG_WHISPER) | |
| { | |
| injected = 0; | |
| for (auto& s : blns) | |
| { | |
| if (injected++ >= opts.maxNaughtyPerField) break; | |
| TPacketCGWhisper w{}; w.szNameTo = s; w.message = s; | |
| auto sp = SerializePacket(w); | |
| try { | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(sp.size())); | |
| h.hash = XXH32(sp.data(), sp.size(), PacketHeader::kHashSeed); | |
| auto wire = BuildWirePacket(h, sp); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } catch(const std::exception& ex) { | |
| SPDLOG_ERROR("Fuzzer BLNS whisper failed: {}", ex.what()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Arbitrary meaningless payloads irrespective of expected struct | |
| { | |
| auto anyPayloads = BuildArbitraryPayloads(opts.maxPayloadSize, rng); | |
| for (auto& ap : anyPayloads) | |
| { | |
| try | |
| { | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(ap.size())); | |
| h.hash = XXH32(ap.data(), ap.size(), PacketHeader::kHashSeed); | |
| auto wire = BuildWirePacket(h, ap); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } | |
| catch (const std::exception& ex) | |
| { | |
| SPDLOG_ERROR("Fuzzer arbitrary id {} failed: {}", id, ex.what()); | |
| } | |
| } | |
| } | |
| // Random iterations | |
| for (uint32_t it = 0; it < opts.iterationsPerId; ++it) | |
| { | |
| try | |
| { | |
| auto payload = RandomPayload(rng, opts.maxPayloadSize); | |
| if (opts.enableMutations) | |
| { | |
| auto variants = MutatePayloads(payload, rng, opts.maxMutationsPerBase); | |
| for (auto& v : variants) | |
| { | |
| PacketHeader h2 = MakeHeader(id, static_cast<uint32_t>(v.size())); | |
| h2.hash = XXH32(v.data(), v.size(), PacketHeader::kHashSeed); | |
| auto wire2 = BuildWirePacket(h2, v); | |
| const_buffer fb(wire2.data(), wire2.size()); | |
| const_buffer db = fb + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h2, db); | |
| } | |
| } | |
| PacketHeader h = MakeHeader(id, static_cast<uint32_t>(payload.size())); | |
| const auto computedHash = XXH32(payload.data(), payload.size(), PacketHeader::kHashSeed); | |
| h.hash = computedHash; | |
| auto wire = BuildWirePacket(h, payload); | |
| const_buffer fullBuf(wire.data(), wire.size()); | |
| const_buffer dataBuf = fullBuf + sizeof(PacketHeader); | |
| (void)proc->Analyze(desc, h, dataBuf); | |
| } | |
| catch (const std::exception& ex) | |
| { | |
| SPDLOG_ERROR("Fuzzer random id {} failed: {}", id, ex.what()); | |
| } | |
| } | |
| } | |
| SPDLOG_INFO("Fuzzer finished."); | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment