← Back to Writeups
CUSTOMMediumMisc

Deadgame2

XESXOR9/10/20264 min read
#misc#custom#medium#chat_fragment_recovery#md5_verification#mpq_extraction#replay_event_decoding#tracker_coordinate_plotting

Deadgame2

Platform: sekai2026 | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-06-27 | Status: Solved Techniques: chat_fragment_recovery, md5_verification, mpq_extraction, replay_event_decoding, tracker_coordinate_plotting

Summary

Task: analyze a StarCraft II SC2Replay MPQ archive with chat fragments and hidden position-based data. Solution: decode replay messages, plot SUnitDiedEvent coordinates from tracker events, and rebuild the flag.

Deadgame2 - sekai2026

Description

The provided archive was misc_deadgame2.tar.gz, containing misc_deadgame2/DeadGame2.SC2Replay. The replay is a StarCraft II MPQ archive. Metadata identified map title Tunguska, GameVersion 5.0.15.96999, and Base96999.

The goal was to recover the complete SeKaiCTF{...} flag from replay data. The visible chat gave most of the flag, while a hint about positions pointed to hidden text encoded in tracker coordinates.

Tools

  • python3.11
  • mpyq for extracting the MPQ replay archive
  • s2protocol for decoding StarCraft II replay streams
  • Pillow for rendering coordinate plots

The local environment used vendor311 packages because the bundled s2protocol code was compatible with Python 3.11.

Extraction and Parsing

After unpacking the challenge archive, treat DeadGame2.SC2Replay as an MPQ archive and extract the replay streams. The important files were:

  • replay.message.events - in-game chat messages
  • replay.tracker.events - unit and positional tracker events

The message stream can be decoded directly with the matching/latest bundled s2protocol protocol module:

python
#!/usr/bin/env python3
from pathlib import Path
from s2protocol.versions import protocol97364 as protocol

data = Path("extract/replay.message.events").read_bytes()

for event in protocol.decode_replay_message_events(data):
    if event.get("_event") != "NNet.Game.SChatMessage":
        continue
    text = event["m_string"].decode("utf-8", "replace")
    if text:
        print(event["_gameloop"], event["_userid"]["m_userId"], text)

Chat Fragment Recovery

The decoded chat stream contained direct flag fragments plus the key hint:

GameloopUserMessage
46620SeKaiCTF{lol
74280Want more? Let me be kind enough to give you a hint - in this interstellar war, everyone's "position" is crucial!
116970Chinese hint, translated: Mysterious Eastern text: you should also send some flag out!
118971_fl2g_
725690Attention!!!
731001_
733121_
735881_
738641GG
739311}

This recovered the skeleton:

text
SeKaiCTF{lol_fl2g_???_???_???_GG}

The word position in the hint is the important clue: the missing chunks are not another chat encoding layer, but text drawn by positions in tracker events near the underscore-only chat messages.

Tracker Coordinate Plotting

In replay.tracker.events, the useful data is in NNet.Replay.Tracker.SUnitDiedEvent events. Filter events where m_killerPlayerId == 1 around the three underscore chat gameloops and plot (m_x, m_y) coordinates.

python
#!/usr/bin/env python3
from pathlib import Path
from s2protocol.versions import protocol97364 as protocol

TARGETS = {
    73086: "EN",
    73290: "TARO",
    73563: "HERO",
}

groups = {g: [] for g in TARGETS}
data = Path("extract/replay.tracker.events").read_bytes()

for event in protocol.decode_replay_tracker_events(data):
    if event.get("_event") != "NNet.Replay.Tracker.SUnitDiedEvent":
        continue
    if event.get("m_killerPlayerId") != 1:
        continue
    gameloop = event.get("_gameloop")
    if gameloop in groups:
        groups[gameloop].append((event["m_x"], event["m_y"]))

for gameloop, label in TARGETS.items():
    pts = groups[gameloop]
    xs = [x for x, _ in pts]
    ys = [y for _, y in pts]
    print(f"{gameloop}: {label} points={len(pts)} bbox=x[{min(xs)},{max(xs)}], y[{min(ys)},{max(ys)}]")

    occupied = set(pts)
    for y in range(max(ys), min(ys) - 1, -1):
        print("".join("#" if (x, y) in occupied else "." for x in range(min(xs), max(xs) + 1)))
    print()

The three bursts immediately before the underscore chat messages produced readable coordinate plots:

GameloopPointsBounding boxText
7308648x[115,129], y[152,161]EN
7329082x[106,134], y[140,148]TARO
7356383x[112,142], y[115,125]HERO

The helper script show_tracker_chunks.py renders these same grids and writes tracker_missing_chunks.png for visual confirmation.

Flag Reconstruction

Combine the visible chat fragments with the tracker text chunks. The underscore-only chat messages mark the separators around the hidden words:

text
SeKaiCTF{lol
_fl2g_
EN
_
TARO
_
HERO
_
GG
}

So the reconstructed flag is:

text
SeKaiCTF{REDACTED}

Verification

The provided MD5 check matches the reconstructed flag:

python
#!/usr/bin/env python3
import hashlib

flag = "SeKaiCTF{REDACTED}"
assert hashlib.md5(flag.encode()).hexdigest() == "0b95495176f49f2dab8a2d9c26a41ecc"
print(flag)