Skip to content

Instantly share code, notes, and snippets.

@lkolbly
Last active January 3, 2017 18:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lkolbly/115ae348c3f148eedde19c15a0951d60 to your computer and use it in GitHub Desktop.
Save lkolbly/115ae348c3f148eedde19c15a0951d60 to your computer and use it in GitHub Desktop.
Python parser for the Star Wars: X-Wing (Collector's CD) .XWI mission file format
import json
import struct
class XwiParser:
def __init__(self, f):
self.f = f
res = self._parseHeader()
res["groups"] = []
res["objects"] = []
for i in range(res["num_fgs"]):
fg = self._parseFlightGroup()
#if fg["fg_type"] == "xwing" or fg["fg_type"] == "tief":
res["groups"].append(fg)
for i in range(res["num_objs"]):
obj = self._parseObject()
res["objects"].append(obj)
print(json.dumps(res, indent=3))
pass
def _read(self, fmt):
data = self.f.read(struct.calcsize(fmt))
return struct.unpack(fmt, data)
def _readFields(self, fields):
fmt = ""
for field in fields:
if field[1] == "fxp":
fmt += "H"
elif field[1] == "bool":
fmt += "H"
else:
fmt += field[1]
vals = self._read(fmt)
res = {}
for field,value in zip(fields,vals):
if field[1][-1] == "s":
value = value.split(b"\x00")[0].decode('utf-8')
o = res
name = field[0]
if isinstance(field[0], tuple):
name = field[0]
for n in name[:-1]:
if n not in o:
o[n] = {}
o = o[n]
name = field[0][-1]
pass
if len(field) == 3:
if field[1] == "fxp":
if value > 32768:
value = -(65536 - value)
#print(field[0], value)
o[name] = float(value) / float(field[2]) # TODO: Is this times or divided by?
else:
# An enum
enum = field[2]
if isinstance(field[2], tuple):
# An offset!
value = value - field[2][0]
enum = field[2][1]
print(value, field)
if value >= len(enum):
o[name] = "UNKNOWN_%s"%value
else:
o[name] = enum[value]
else:
if field[1] == "bool":
if value:
o[name] = True
else:
o[name] = False
else:
o[name] = value
return res
def _parseHeader(self):
fields = [
("version", "H"),
("timelimit", "H"),
("end_event", "H", ["RESCUED", "CAPTURED", None, None, "HIT_EXHAUST_PORT"]),
("?", "H"),
("mission_location", "H", ["DEEP_SPACE", "DEATH_STAR"]),
("completion_msg_1", "64s"),
("completion_msg_2", "64s"),
("completion_msg_3", "64s"),
("num_fgs", "H"),
("num_objs", "H")
]
return self._readFields(fields)
def _parseFlightGroup(self):
SHIPTYPES = [
None,
"xwing",
"ywing",
"awing",
"tief",
"tiei",
"tieb",
"gunboat",
"transport",
"shuttle",
"tug",
"container",
"freighter",
"calamari",
"nebulon",
"corellian",
"star_destroyer",
"tiea",
"bwing"
]
FORMATIONS = [
"VIC",
"FINGER_FOUR",
"LINE_ASTERN",
"LINE_ABREAST",
"ECHELON_RIGHT",
"ECHELON_LEFT",
"DOUBLE_ASTERN",
"DIAMOND",
"STACKED",
"SPREAD",
"HI_LO",
"SPIRAL"
]
ORDERS = [
"HOLD_STEADY",
"FLY_HOME",
"CIRCLE_IGNORE",
"FLY_ONCE_IGNORE",
"CIRCLE_EVADE",
"FLY_ONCE_EVADE",
"CLOSE_ESCORT",
"LOOSE_ESCORT",
"ATTACK_ESCORTS",
"ATTACK_TARGETS", # Primary and secondary targets
"ATTACK_ENEMIES",
"RENDEZVOUS",
"DISABLED",
"BOARD_DELIVER",
"BOARD_TAKE",
"BOARD_CAPTURE",
"BOARD_DESTROY",
"DISABLE_TARGETS",
"DISABLE_ENEMIES",
"ATTACK_TRANSPORTS",
"ATTACK_FREIGHTERS", # Incl. CRVs
"ATTACK_STARSHIPS",
"ATTACK_SATS_MINES",
"DISABLE_FREIGHTERS",
"DISABLE_STARSHIPS",
"STARSHIP_SIT_AND_FIRE",
"STARSHIP_FLY_DANCE",
"STARSHIP_CIRCLE",
"STARSHIP_AWAIT_RETURN",
"STARSHIP_AWAIT_LAUNCH",
"STARSHIP_AWAIT_BOARDING"
]
OBJECTIVES = [
None,
"ALL_DESTROYED",
"ALL_SURVIVE",
"ALL_CAPTURED",
"ALL_DOCKED",
"SPECIAL_DESTROYED",
"SPECIAL_SURVIVE",
"SPECIAL_CAPTURED",
"SPECIAL_DOCKED",
"HALF_DESTROYED",
"HALF_SURVIVE",
"HALF_CAPTURED",
"HALF_DOCKED",
"ALL_IDENTIFIED",
"SPECIAL_IDENTIFIED",
"HALF_IDENTIFIED",
"ARRIVE"
]
fields = [
("designation", "16s"),
("cargo", "16s"),
("special_cargo", "16s"),
("special_number", "H"),
("fg_type", "H", SHIPTYPES),
("iff", "H", ["DEFAULT", "REBEL", "IMPERIAL", "NEUTRAL"]),
("craft_status", "H", ["NORMAL", "NO_MISSILES", "HALF_MISSILES", "NO_SHIELDS"]),
("wave_size", "H"),
("num_waves", "H"),
# Group's arrival is based on a denoted "arrival FG"
("arrival_event", "H", ["MISSION_START", "ARRIVES", "DESTROYED", "ATTACKED", "BOARDED", "IDENTIFIED", "DISABLED"]),
("arrival_delay", "H"), # Minutes
("arrival_fg", "H"),
("mothership", "H"),
("arrive_by_hyperspace", "bool"),
("depart_by_hyperspace", "bool"),
(("start", 1, "x"), "fxp", 160),
(("waypoint", 1, "x"), "fxp", 160),
(("waypoint", 2, "x"), "fxp", 160),
(("waypoint", 3, "x"), "fxp", 160),
(("start", 2, "x"), "fxp", 160),
(("start", 3, "x"), "fxp", 160),
(("hyperspace", "x"), "fxp", 160),
(("start", 1, "y"), "fxp", 160),
(("waypoint", 1, "y"), "fxp", 160),
(("waypoint", 2, "y"), "fxp", 160),
(("waypoint", 3, "y"), "fxp", 160),
(("start", 2, "y"), "fxp", 160),
(("start", 3, "y"), "fxp", 160),
(("hyperspace", "y"), "fxp", 160),
(("start", 1, "z"), "fxp", 160),
(("waypoint", 1, "z"), "fxp", 160),
(("waypoint", 2, "z"), "fxp", 160),
(("waypoint", 3, "z"), "fxp", 160),
(("start", 2, "z"), "fxp", 160),
(("start", 3, "z"), "fxp", 160),
(("hyperspace", "z"), "fxp", 160),
(("start", 1, "en"), "bool"),
(("waypoint", 1, "en"), "bool"),
(("waypoint", 2, "en"), "bool"),
(("waypoint", 3, "en"), "bool"),
(("start", 2, "en"), "bool"),
(("start", 3, "en"), "bool"),
(("hyperspace", "en"), "bool"),
("formation", "H", FORMATIONS),
("player_pos", "H"),
("ai", "H", ["ROOKIE", "OFFICER", "VETERAN", "ACE", "TOP_ACE"]),
("order", "H", ORDERS),
("dock_time_or_throttle", "H"), # Depends on Order (dock time minutes)
("color", "H", ["RED", "GOLD", "BLUE"]),
("?", "H"),
("objective", "H", OBJECTIVES),
("primary_target", "H"), # 0xffff for no target
("secondary_target", "H")
]
res = self._readFields(fields)
# Change start and waypoint to be lists
start = []
wp = []
for i in [1, 2, 3]:
if res["start"][i]["en"]:
start.append(res["start"][i])
if res["waypoint"][i]["en"]:
wp.append(res["waypoint"][i])
res["start"] = start
res["waypoint"] = wp
return res
def _parseObject(self):
OBJTYPES = [
"MINE_1",
"MINE_2",
"MINE_3",
"MINE_4",
"SATELLITE",
"NAV_BUOY",
"PROBE",
"?",
"ASTEROID_1",
"ASTEROID_2",
"ASTEROID_3",
"ASTEROID_4",
"ASTEROID_5",
"ASTEROID_6",
"ASTEROID_7",
"ASTEROID_8",
"ROCK_WORLD",
"GRAY_RING_WORLD",
"GRAY_WORLD",
"BROWN_WORLD",
"GRAY_WORLD_2",
"PLANET_AND_MOON",
"GRAY_CRESCENT",
"ORANGE_CRESCENT_1",
"ORANGE_CRESCENT_2",
"ORANGE_CRESCENT_3",
"ORANGE_CRESCENT_4",
"ORANGE_CRESCENT_5",
"ORANGE_CRESCENT_6",
"ORANGE_CRESCENT_7",
"ORANGE_CRESCENT_8",
"DEATH_STAR"
]
IFF = [
None,
"REBEL",
"IMPERIAL",
"NEUTRAL"
]
FORM_OR_OBJECTIVE = [
"FLAT",
"ON_EDGE",
"BROADSIDE",
"SCATTERED",
"ALL_DESTROYED"
]
fields = [
("designation", "16s"),
("?", "16s"),
("?", "16s"),
("?", "H"),
("obj_type", "H", (18, OBJTYPES)),
("iff", "H", IFF),
("form_or_objective", "H", FORM_OR_OBJECTIVE),
("num_objs", "H"),
("x", "fxp"),
("y", "fxp"),
("z", "fxp"),
("?", "H"),
("?", "H"),
("?", "H")
]
res = self._readFields(fields)
return res
if __name__ == "__main__":
import sys
XwiParser(open(sys.argv[1], "rb"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment