// Public M2Client argument bounds shared by the Classic and m2dev backends. #include "../src/net/net_bounds.h" #include "../src/net/item_action_rules.h" #include static int g_fail = 0; #define CHECK(c, msg) \ do { \ if (!(c)) { \ std::fprintf(stderr, "FAIL: %s\n", msg); \ ++g_fail; \ } \ } while (0) int main() { using namespace mtnet::bounds; CHECK(u8(0) && u8(255), "u8 accepts wire range"); CHECK(!u8(-1) && !u8(256), "u8 rejects outside range"); CHECK(u16(65535) && !u16(-1) && !u16(65536), "u16 range"); CHECK(u32(0) && u32(0xffffffffLL) && !u32(-1), "u32 range"); CHECK(i32(-2147483648LL) && i32(2147483647LL), "i32 accepts signed range"); CHECK(!i32(-2147483649LL) && !i32(2147483648LL), "i32 rejects outside range"); // ClientVS22 uicharacter.py sends quest-log buttons as // (INT32_MIN + questIndex), then the packet stores the bit pattern in a // uint32. The public GDScript API must allow that sentinel range while // rejecting arbitrary negative values. CHECK(script_button_index(0) && script_button_index(65535), "script button accepts ordinary and quest-index endpoints"); CHECK(script_button_index(-2147483648LL) && script_button_index(-2147418113LL), "script button accepts INT32_MIN quest sentinel range"); CHECK(!script_button_index(-2147483649LL) && !script_button_index(-2147418112LL), "script button rejects values outside quest sentinel range"); CHECK(quick_slot(0) && quick_slot(35), "quick slot endpoints"); CHECK(!quick_slot(-1) && !quick_slot(36), "quick slot outside range"); CHECK(private_shop_slot(0) && private_shop_slot(39), "private shop endpoints"); CHECK(!private_shop_slot(40), "private shop outside range"); CHECK(exchange_slot(0) && exchange_slot(11) && !exchange_slot(12), "exchange range"); CHECK(cube_slot(0) && cube_slot(23) && !cube_slot(24), "cube range"); // ยง3.8 mod 3: use_skill skill index. 1..254 accepted, 0 / >=255 / negative rejected. CHECK(SKILL_MAX_NUM == 255, "SKILL_MAX_NUM matches Packet.h"); CHECK(skill_index(1) && skill_index(254), "skill index endpoints"); CHECK(!skill_index(0) && !skill_index(255) && !skill_index(-1), "skill index outside range"); CHECK(!skill_index(0x7fffffffLL + 1), "skill index rejects large value"); CHECK(refine_request(0, 0, 90) && refine_request(89, 3, 90), "refine accepts inventory positions"); CHECK(refine_request(255, 255, 90), "refine accepts the 40250 cancel sentinel"); CHECK(!refine_request(90, 0, 90) && !refine_request(255, 0, 90) && !refine_request(0, 255, 90), "refine rejects invalid non-sentinel requests"); // ClientVS22 TItemPos::IsValidCell() parity: a wire-sized integer is not // automatically a valid inventory position. CHECK(item_cell(1, 0) && item_cell(1, 167), "inventory cell endpoints"); CHECK(!item_cell(1, 168) && !item_cell(1, 65535), "inventory cell outside range"); CHECK(item_cell(2, 0) && item_cell(2, 133), "equipment window endpoints"); CHECK(!item_cell(2, 134), "equipment window outside range"); CHECK(item_cell(5, 179) && !item_cell(5, 180), "dragon soul cell range"); CHECK(item_cell(7, 15) && !item_cell(7, 16), "belt cell range"); CHECK(!item_cell(3, 0) && !item_cell(0, 0), "unsupported item windows rejected"); using namespace mtnet::item_action; CHECK(is_equipment_type(1) && is_equipment_type(2), "CItemData::IsEquipment accepts weapon and armor"); CHECK(!is_equipment_type(3) && !is_equipment_type(28) && !is_equipment_type(33), "CItemData::IsEquipment rejects use, costume and ring types"); CHECK(use_sound_type(1, 2) == ItemSoundType::Bow, "weapon bow uses the bow sound"); CHECK(use_sound_type(1, 6) == ItemSoundType::Default, "weapon arrow uses the default sound"); CHECK(use_sound_type(2, 0) == ItemSoundType::Armor && use_sound_type(2, 5) == ItemSoundType::Accessory, "body and accessory armor use their reference sounds"); CHECK(use_sound_type(3, 7) == ItemSoundType::Potion && use_sound_type(3, 0) == ItemSoundType::None && use_sound_type(3, 1) == ItemSoundType::Portal, "use-item sound branches match ability-up, potion, talisman"); CHECK(drop_sound_type(1, 2) == ItemSoundType::Bow && drop_sound_type(2, 0) == ItemSoundType::Armor && drop_sound_type(3, 7) == ItemSoundType::Default, "drop sound branches match weapon, armor and default items"); Context action{}; action.has_main = true; CHECK(can_act_main(action), "item action allowed for a live main actor"); action.dead = true; CHECK(!can_act_main(action), "item action blocked for a dead main actor"); action.dead = false; action.exchange_active = true; CHECK(!can_touch_equipment(action, true), "equipment action blocked during exchange"); CHECK(can_touch_equipment(action, false), "non-equipment action remains allowed during exchange"); action.exchange_active = false; action.attacking = true; CHECK(!can_touch_equipment(action, true), "equipment action blocked while attacking"); if (g_fail) { std::fprintf(stderr, "%d checks FAILED\n", g_fail); return 1; } std::puts("net_bounds_test: OK"); return 0; }