// MarkImageSet + the GC_MARK_* body parsers — synthetic packets in, guild-mark // pixels out. No socket: this covers the parse/decompress/blit path that the // MarkClient (mark_client.h) drives over the wire. #include "../src/net/mark_image.h" #include "../src/net/wire.h" #include #include #include #include using namespace mtnet; static int g_fail = 0; #define CHECK(c, msg) \ do { \ if (!(c)) { \ std::fprintf(stderr, "FAIL: %s\n", msg); \ ++g_fail; \ } \ } while (0) static void put_u16(std::vector &b, uint16_t v) { b.push_back((uint8_t)(v & 0xFF)); b.push_back((uint8_t)(v >> 8)); } static void put_u32(std::vector &b, uint32_t v) { for (int i = 0; i < 4; ++i) { b.push_back((uint8_t)((v >> (8 * i)) & 0xFF)); } } // LZO1X-compress one 64x48 RGBA block (MARK_BLOCK_PIXELS words). static std::vector compress_block(const uint32_t *px) { lzo_init(); static std::vector wrk(LZO1X_1_MEM_COMPRESS); std::vector out(MARK_BLOCK_PIXELS * 4 + MARK_BLOCK_PIXELS + 64); lzo_uint out_len = out.size(); int r = lzo1x_1_compress((const uint8_t *)px, MARK_BLOCK_PIXELS * 4, out.data(), &out_len, wrk.data()); if (r != LZO_E_OK) { std::fprintf(stderr, "FAIL: lzo1x_1_compress -> %d\n", r); ++g_fail; } out.resize(out_len); return out; } int main() { // Guild 4242's mark lives at mark_id 1281 -> image 1, position 1 (row 0, // col 1): pixel origin (16, 0). Guild 7 -> mark_id 3 -> image 0, pos 3. const uint32_t kGuildA = 4242, kMarkA = MARK_PER_IMAGE + 1; // 1281 const uint32_t kGuildB = 7, kMarkB = 3; // --- GC_MARK_IDXLIST body: count x {u16 guild_id, u16 mark_id} --- MarkImageSet set; { std::vector body; put_u16(body, (uint16_t)kGuildA); put_u16(body, (uint16_t)kMarkA); put_u16(body, (uint16_t)kGuildB); put_u16(body, (uint16_t)kMarkB); size_t n = parse_mark_idxlist(body.data(), body.size(), 2, set); CHECK(n == 2, "idxlist: 2 entries parsed"); } CHECK(set.has_mark(kGuildA) && set.has_mark(kGuildB), "idxlist: both guilds registered"); CHECK(set.mark_id(kGuildA) == kMarkA, "idxlist: mark id stored"); CHECK(!set.has_mark(999), "idxlist: unknown guild absent"); // needed images = {0, 1} ascending std::vector need = set.needed_images(); CHECK(need.size() == 2 && need[0] == 0 && need[1] == 1, "needed_images = {0,1}"); // rect: guild A at image 1, (16, 0); guild B at image 0, (48, 0) MarkRect ra = set.rect_of(kGuildA); CHECK(ra.found && ra.img_idx == 1 && ra.x == 16 && ra.y == 0 && ra.w == 16 && ra.h == 12, "rect_of(A) = img1 (16,0) 16x12"); MarkRect rb = set.rect_of(kGuildB); CHECK(rb.found && rb.img_idx == 0 && rb.x == 48 && rb.y == 0, "rect_of(B) = img0 (48,0)"); // --- GC_MARK_BLOCK body for image 1, block 0 (covers marks at cols 0..3) --- // Paint guild A's 16x12 cell (origin 16,0 within the block) a solid colour. const uint32_t kColour = 0xFF3399CCu; // AABBGGRR { std::vector block(MARK_BLOCK_PIXELS, 0); for (int j = 0; j < GUILD_MARK_HEIGHT; ++j) { for (int i = 0; i < GUILD_MARK_WIDTH; ++i) { block[(size_t)j * MARK_BLOCK_WIDTH + (16 + i)] = kColour; } } std::vector comp = compress_block(block.data()); std::vector body; body.push_back(0); // block_pos 0 put_u32(body, (uint32_t)comp.size()); body.insert(body.end(), comp.begin(), comp.end()); size_t applied = parse_mark_block(body.data(), body.size(), /*img_idx=*/1, /*count=*/1, set); CHECK(applied == 1, "block: 1 block applied"); } // guild A's mark pixels should now be the solid colour; guild B (no block // for image 0 yet) should come back empty. std::vector pa = set.mark_pixels(kGuildA); CHECK(pa.size() == (size_t)(GUILD_MARK_WIDTH * GUILD_MARK_HEIGHT), "mark_pixels(A): 192 words"); bool all_colour = !pa.empty(); for (uint32_t p : pa) { all_colour = all_colour && (p == kColour); } CHECK(all_colour, "mark_pixels(A): every pixel is the painted colour"); CHECK(set.mark_pixels(kGuildB).empty(), "mark_pixels(B): empty (image 0 not downloaded)"); // corrupt compressed data -> apply_block fails, image untouched { uint8_t junk[8] = {1, 2, 3, 4, 5, 6, 7, 8}; CHECK(!set.apply_block(1, 1, junk, sizeof(junk)), "apply_block: junk rejected"); } // out-of-range indices CHECK(!set.apply_block(-1, 0, (const uint8_t *)"x", 1), "apply_block: bad img idx"); CHECK(!set.apply_block(0, MARK_BLOCK_TOTAL_COUNT, (const uint8_t *)"x", 1), "apply_block: bad block pos"); // truncated block body -> nothing applied, no crash { std::vector body; body.push_back(2); put_u32(body, 9999); // claims 9999 bytes that aren't there size_t applied = parse_mark_block(body.data(), body.size(), 0, 1, set); CHECK(applied == 0, "block: truncated body -> 0 applied"); } if (g_fail) { std::fprintf(stderr, "%d check(s) failed\n", g_fail); return 1; } std::printf("PASS: net_mark_test\n"); return 0; }