add non-functional c1 Hatchery
[openc2e.git] / tools / sfcdumper.py
blob98551844f6d233053c9a37a8896f4aa578ed1ba4
1 #!/usr/bin/python
2 # sfcdumper
3 # a utility to extract information from Creatures 1/2 SFC (world save)/EXP (creature export) files
4 # reverse-engineered by fuzzie and nornagon
6 # TODO: a lot of reads probably need to be changed to signed reads
7 # eg, room CA sources
8 # TODO: lots of unknown values :)
10 identifiers = {}
11 try:
12 for i in open("identifiers.txt").readlines():
13 x = i.strip().split("\t")
14 identifiers[x[0]] = x[1]
15 except IOError:
16 pass
18 def getidentifier(family, genus, species):
19 x = "%d%02d%03d" % (family, genus, species)
20 if x in identifiers:
21 return identifiers[x]
22 else:
23 return None
25 # utility functions to read values from a stream
26 import struct
28 def reads32(f):
29 x = f.read(4)
30 return struct.unpack("<i", x)[0]
32 def read32(f):
33 x = f.read(4)
34 return struct.unpack("<I", x)[0]
36 def read16(f):
37 x = f.read(2)
38 return struct.unpack("<H", x)[0]
40 def read8(f):
41 x = f.read(1)
42 return struct.unpack("<B", x)[0]
44 def readstring(f):
45 length = read8(f)
46 if length == 255:
47 length = read16(f)
48 if length == 65535:
49 length = read32(f)
50 return f.read(length)
52 # read an MFC class from file object f
53 # if you pass reqclass, the code will assert it got an object of that class
54 existingobjects = []
56 def slurpMFC(f, reqclass = None):
57 global existingobjects
58 # read the pid (this only works up to 0x7ffe, but we'll cope)
59 x = read16(f)
60 if x == 0:
61 # null object
62 return None
63 elif x == 65535:
64 # completely new class
65 schemaid = read16(f)
66 strlen = read16(f)
67 name = f.read(strlen)
68 newclass = sys.modules['__main__'].__dict__[name]
69 existingobjects.append(newclass)
70 elif x & 0x8000 != 0x8000:
71 # return an existing object which we already read
72 assert x - 1 < len(existingobjects)
73 o = existingobjects[x - 1]
74 #if reqclass:
75 # assert o.__class__ == reqclass
76 #print "* Returning a " + newclass.__name__ + " (id #" + str(x) + ")"
77 return o
78 #return None
79 else:
80 # create an instance of an already-existing class
81 x = x ^ 0x8000
82 newclass = existingobjects[x - 1]
84 if reqclass:
85 assert newclass == reqclass, "slurpMFC wanted " + reqclass.__name__ + " but got " + newclass.__name__
86 #print "* creating: " + newclass.__name__
87 n = newclass()
88 existingobjects.append(n)
89 n.read(f)
90 return n
92 # creatures classes
93 # this is where all the real logic is - read() slurps up the contents
95 class CDoor:
96 def read(self, f):
97 self.openness = read8(f)
98 self.otherroom = read16(f)
99 x = read16(f)
100 assert x == 0, "CDoor zero wasn't zero"
102 # TODO:
103 # we don't know where 'visited flag' is...
105 nextroom = 0
106 class CRoom:
107 def read(self, f):
108 # room id
109 self.roomid = read32(f)
111 # check rooms are in order, as they should be, maybe?
112 global nextroom
113 assert nextroom == self.roomid
114 nextroom = self.roomid + 1
116 # read magic constant
117 x = read16(f)
118 assert x == 2, "magic constant for CRoom wasn't 2"
120 # read room bounding box
121 self.left = read32(f)
122 self.top = read32(f)
123 self.right = read32(f)
124 self.bottom = read32(f)
126 # read doors from four directions
127 self.doordirections = []
128 for i in range(4):
129 nodoors = read16(f)
130 self.doordirections.append([])
131 for j in range(nodoors):
132 x = slurpMFC(f, CDoor)
133 assert x
134 self.doordirections[i].append(x)
136 # read roomtype, 0-3 in C2
137 self.roomtype = read32(f)
138 assert self.roomtype < 4, "CRoom found weird roomtype: " + str(self.roomtype)
140 # slurp up some data
141 self.floorvalue = read8(f)
142 self.inorganicnutrients = read8(f)
143 self.organicnutrients = read8(f)
144 self.temperature = read8(f)
145 self.heatsource = reads32(f)
146 self.pressure = read8(f)
147 self.pressuresource = reads32(f)
149 # wind x/y direction
150 self.windx = reads32(f)
151 self.windy = reads32(f)
153 # slurp up some more data
154 self.lightlevel = read8(f)
155 self.lightsource = reads32(f)
157 # slurp up some more data
158 self.radiation = read8(f)
159 self.radiationsource = reads32(f)
161 # TODO: slurp up 800 unknown bytes! disease data?
162 self.randombytes = f.read(800)
164 # read floor points
165 nopoints = read16(f)
166 assert nopoints < 100, "sanity check on nopoints"
167 self.floorpoints = []
168 for i in range(nopoints):
169 pointx = read32(f)
170 pointy = read32(f)
171 self.floorpoints.append((pointx, pointy))
173 # TODO: slurp up unknown value
174 x = read32(f)
175 assert x == 0, "first footer of CRoom was " + str(x)
177 self.music = readstring(f)
178 self.dropstatus = read32(f)
179 assert self.dropstatus < 3, "dropstatus was " + str(self.dropstatus)
181 class CGallery:
182 def read(self, f):
183 # number of sprites
184 self.noframes = read32(f)
185 # four chars of filename
186 self.filename = f.read(4)
187 self.firstsprite = read32(f)
188 # TODO: number of references?
189 self.refs = read32(f)
191 # read each piece of framedata in
192 self.framedata = []
193 for i in range(self.noframes):
194 data = {}
195 x = read8(f) # TODO
196 # i suspect this may be 'no of references' - fuzzie
197 #assert x == 1 or x == 4, "CGallery framedata 1st byte was " + str(x)
199 x = read8(f) # TODO
200 # apparently this is '6' in the first eden SimpleObject o.O
201 #assert x == 0, "CGallery framedata 2nd byte was " + str(x)
203 x = read8(f) # TODO
204 # apparently this is 4 inside eden obj #370, a blackboard
205 # apparently this is 5 inside eden obj #552, a blackboard
206 assert x == 0 or x == 1 or x == 4 or x == 5, "CGallery framedata 3rd byte was " + str(x)
208 data['width'] = read32(f)
209 data['height'] = read32(f)
210 data['offset'] = read32(f)
212 print "gallery read file '" + self.filename + "' with " + str(self.noframes) + " frame(s) starting at " + str(self.firstsprite)
214 class MapData:
215 def read(self, f):
216 global version
217 version = read32(f)
218 if version == 0:
219 print "this is a Creatures 1 SFC file"
220 elif version == 1:
221 print "this is a Creatures 2 SFC file"
222 else:
223 assert False, "didn't understand version " + str(version)
224 self.savecount = read16(f) # TODO: prbly not actually savecount
225 x = read16(f) # <- this is prbly uint32 bit
226 assert x == 0, "third bytes of MapData were not zero but " + str(x)
227 if version == 1:
228 x = read32(f) # TODO who knows? 0 (common) or 4, so far
229 assert x == 0 or x == 4, "fourth bytes of MapData were not zero but " + str(x)
230 x = read32(f) # TODO
231 assert x == 0, "fifth bytes of MapData were not zero but " + str(x)
233 # sprite for background tiles
234 self.gallery = slurpMFC(f, CGallery)
236 # rooms
237 self.roomcount = read32(f)
238 print "trying to read " + str(self.roomcount) + " rooms"
239 self.rooms = []
240 for i in xrange(self.roomcount):
241 if version == 0:
242 left = reads32(f)
243 top = read32(f)
244 right = reads32(f)
245 bottom = read32(f)
246 roomtype = read32(f)
247 assert roomtype < 3
248 roomtypes = ["Indoors", "Surface", "Undersea"]
249 print "room at (" + str(left) + ", " + str(top) + "), (" + str(right) + ", " + str(bottom) + ") - type " + roomtypes[roomtype]
250 # TODO
251 self.rooms.append(None)
252 else:
253 assert version == 1
254 self.rooms.append(slurpMFC(f, CRoom))
256 if version == 0:
257 # ground level data
258 self.groundlevel = []
259 for x in xrange(261):
260 self.groundlevel.append(read32(f))
262 # mysterious random bytes! like the in-room ones of C2
263 # disease data? TODO
264 self.randombytes = f.read(800)
266 readingcompound = False
267 readingscenery = False
269 class Entity: # like a compound part?
270 def read(self, f):
271 print "--- start part ---"
273 # sprite
274 self.sprite = slurpMFC(f)
275 print "sprite file: " + self.sprite.filename
277 # currently displayed sprite details
278 self.currsprite = read8(f)
279 self.imageoffset = read8(f)
280 print "curr sprite#: " + str(self.currsprite) + ", img offset: " + str(self.imageoffset)
282 # zorder
283 # TODO: not sure if this should be signed, it makes pointer weird
284 self.zorder = reads32(f)
285 print "zorder: " + str(self.zorder)
287 # location
288 self.x = read32(f)
289 self.y = read32(f)
290 print "loc: " + str(self.x) + ", " + str(self.y)
292 self.haveanim = read8(f)
293 assert self.haveanim == 0 or self.haveanim == 1
294 if self.haveanim == 1:
295 self.animframe = read8(f)
296 if version == 0:
297 self.animstring = f.read(32)
298 else:
299 self.animstring = f.read(99)
300 x = self.animstring.find("\0")
301 if x != -1:
302 self.animstring = self.animstring[:x]
303 print "on frame " + str(self.animframe) + " of animation '" + self.animstring + "'"
305 if readingscenery:
306 return
308 if readingcompound:
309 self.relx = read32(f)
310 self.rely = read32(f)
311 print "part offset: " + str(self.relx) + ", " + str(self.rely)
312 return
314 self.zorder2 = reads32(f)
315 if self.zorder != self.zorder2:
316 # kaelis's Eden.sfc has this differing for obj#816!
317 # TODO: work out what the heck
318 print "strange zorder: " + str(self.zorder2)
320 # TODO: finish decoding this
321 self.clickbhvr = f.read(3)
322 self.touchbhvr = read8(f)
323 print "* touch bhvr: " + str(self.touchbhvr) + ", click bhvr:",
324 for z in self.clickbhvr: print "%02X" % ord(z),
325 print
327 if version == 0: return
329 num_pickup_handles = read16(f)
330 self.pickup_handles = []
331 for i in xrange(num_pickup_handles):
332 self.pickup_handles.append((read32(f), read32(f)))
334 num_pickup_points = read16(f)
335 self.pickup_points = []
336 for i in xrange(num_pickup_points):
337 self.pickup_points.append((read32(f), read32(f)))
339 print "read " + str(len(self.pickup_handles)) + " pickup handles and " + str(len(self.pickup_points)) + " pickup points"
341 class Object:
342 def partialread(self, f):
343 if version == 0:
344 # TODO
345 # we should make sure this value doesn't actually matter.
346 # for now fuzzie is assuming it doesn't, it's presumably the unused portion
347 # of CLAS - it's not always zero, some Terra Nornia scenery has it as 0xff
348 x = read8(f)
349 #assert x == 0, "Object lacking nulls at start, instead has " + str(x)
350 # genus/family/species
351 if version == 0:
352 self.species = read8(f)
353 self.genus = read8(f)
354 self.family = read8(f)
355 else:
356 self.genus = read8(f)
357 self.family = read8(f)
358 x = read16(f)
359 assert x == 0, "Object lacking nulls at start, instead has " + str(x)
360 self.species = read16(f)
362 # print nice stuff!
363 identifier = getidentifier(self.family, self.genus, self.species)
364 if not identifier:
365 identifier = ""
366 else:
367 identifier = " - '" + identifier + "'"
368 print "agent " + self.__class__.__name__ + ": (" + str(self.family) + ", " + str(self.genus) + ", " + str(self.species) + ")" + identifier + ","
370 if version == 0:
371 x = read8(f)
372 # TODO: should make sure this is really only set for pointer?
373 # might be mysteriousness byte from c2..
374 if x: print "pointer-only byte was" + str(x)
375 else:
376 # unid
377 self.unid = read32(f)
378 print "unid: " + str(self.unid)
380 # TODO
381 x = read8(f)
382 # TODO: 0 or 1 normally, but 'Hook', unid: 56314, at least, has it at 4
383 assert x == 0 or x == 1 or x == 4
384 print "* mysteriousness (0/1/4): " + str(x)
386 # attributes
387 if version == 0:
388 # TODO: verify this sometime :)
389 self.attr = read8(f)
390 else:
391 self.attr = read16(f)
392 print "attr: " + str(self.attr)
394 if version == 1:
395 zarros = read16(f)
396 assert zarros == 0
398 one = read32(f)
399 two = read32(f)
400 three = read32(f)
401 four = read32(f)
403 zarros = read16(f)
404 if version == 0:
405 assert zarros == 0, "zarros: " + str(zarros)
406 else:
407 # drat, PointerTool in eden has this as 1803
408 if zarros != 0:
409 print "zarros: " + str(zarros)
411 self.actv = read8(f)
412 print "coords? " + str(one) + ", " + str(two) + ", " + str(three) + ", " + str(four) + ", actv: " + str(self.actv)
414 # our sprite
415 self.sprite = slurpMFC(f, CGallery)
417 # tick data
418 self.tickreset = read32(f)
419 self.tickstate = read32(f)
420 assert self.tickreset >= self.tickstate
421 print "* tick time: " + str(self.tickreset) + ", state: " + str(self.tickstate)
423 self.objp = slurpMFC(f)
425 self.currentsound = f.read(4)
426 if self.currentsound[0] != chr(0):
427 print "current sound: " + self.currentsound
429 if version == 0: numvariables = 3
430 else: numvariables = 100
432 # OBVx variables
433 self.variables = []
434 for i in xrange(numvariables):
435 self.variables.append(read32(f))
437 if version == 1:
438 # misc physics-ish data
439 self.size = read8(f)
440 self.range = read32(f)
441 x = read32(f)
442 if x != 0xffffffff:
443 print "* mysterious physicsish value (probably GRAV related): " + str(x)
444 self.accg = read32(f)
445 self.velx = reads32(f)
446 self.vely = reads32(f)
447 print "velx: " + str(self.velx) + ", vely: " + str(self.vely)
448 self.rest = read32(f)
449 self.aero = read32(f)
450 x = f.read(6) # TODO: unknown [for pointer: 0000 0400 0000]
451 # [for eden #1: d101 0400 0000]
452 print "* post-physics bytes:",
453 for z in x: print "%02X" % ord(z),
454 print
456 self.threat = read8(f)
458 print "accg: " + str(self.accg) + ", rest: " + str(self.rest) + ", aero: " + str(self.aero) + ", size: " + str(self.size) + ", range: " + str(self.range) + ", threat: " + str(self.threat)
460 # TODO: 01 normally, 03 when frozen, 00 for scenery?
461 self.flags = read8(f)
462 assert self.flags == 0 or self.flags == 1 or self.flags == 3, str(self.flags)
464 # TODO: sane scriptness
465 self.scripts = {}
466 numscripts = read32(f)
467 for i in range(numscripts):
468 if version == 0:
469 eventno = read8(f)
470 species = read8(f)
471 genus = read8(f)
472 family = read8(f)
473 else:
474 genus = read8(f)
475 family = read8(f)
476 eventno = read16(f)
477 species = read16(f)
478 script = readstring(f)
479 print "event #" + str(eventno) + " for " + str(family) + ", " + str(genus) + ", " + str(species) + ": " + script
480 self.scripts[eventno] = script
482 class SimpleObject(Object):
483 def read(self, f):
484 Object.partialread(self, f)
486 self.entity = slurpMFC(f, Entity)
487 assert self.entity
489 class PointerTool(SimpleObject):
490 def read(self, f):
491 SimpleObject.read(self, f)
493 # TODO: data from C2 eden is shown below
494 # 02 00 00 00 02 00 00 00
495 # 00 00 <- bubble
496 # 00 00 00 00
497 # 67 65 74 00
498 # 63 72 69 74
499 # 74 65 72 00
500 # 66 67 00 00
501 # 00 00 00 00 00 00 00 00 00
502 # CD CD CD CD CD CD CD CD CD CD CD CD
503 # bear in mind CD CD CD CD is prbly unallocated memory from microsoft's CRT
504 x = f.read(8)
505 print "pointer bytes: ",
506 for z in x: print "%02X" % ord(z),
507 print
509 self.bubble = slurpMFC(f, Bubble)
511 if version == 0:
512 x = f.read(25)
513 print "pointer bytes: ",
514 for z in x: print "%02X" % ord(z),
515 print
516 else:
517 x = f.read(4 + 16 + 9 + 12)
518 print "pointer bytes: ",
519 for z in x: print "%02X" % ord(z),
520 print
522 class Bubble(SimpleObject):
523 def read(self, f):
524 SimpleObject.read(self, f)
526 if version == 0:
527 x = f.read(1)
528 else:
529 x = f.read(5)
530 print "bubble bytes: ",
531 for z in x: print "%02X" % ord(z),
532 print
534 if version == 0:
535 bubblelen = read8(f)
536 assert read8(f) == 0
537 self.bubblestring = f.read(bubblelen)
538 else:
539 self.bubblestring = f.read(36)
540 x = self.bubblestring.find("\0")
541 if x != -1:
542 self.bubblestring = self.bubblestring[:x]
543 print "bubble string: " + self.bubblestring
545 if version == 0:
546 x = f.read(11 + 8) # this is probably terribly wrong
547 else:
548 x = f.read(11)
549 print "more bubble bytes: ",
550 for z in x: print "%02X" % ord(z),
551 print
553 class CompoundObject(Object):
554 def read(self, f):
555 Object.partialread(self, f)
557 global readingcompound
558 readingcompound = True
560 num_parts = read32(f)
561 self.parts = []
562 print "reading " + str(num_parts) + " parts.."
563 for i in xrange(num_parts):
564 print "reading part #" + str(i)
565 e = slurpMFC(f, Entity)
566 if not e:
567 # TODO: hackery?
568 x = f.read(8)
569 print "part bytes:",
570 for z in x: print "%02X" % ord(z),
571 print
572 if i == 0:
573 assert e, "part 0 was null"
574 assert e.relx == 0 and e.rely == 0
575 self.parts.append(e)
576 readingcompound = False
578 self.hotspots = []
579 for i in range(6):
580 hotspotinfo = {}
581 hotspotinfo['left'] = reads32(f)
582 hotspotinfo['top'] = reads32(f)
583 hotspotinfo['right'] = reads32(f)
584 hotspotinfo['bottom'] = reads32(f)
585 self.hotspots.append(hotspotinfo)
587 self.functions = []
588 for i in range(6):
589 functioninfo = {}
590 functioninfo['hotspot'] = reads32(f)
591 self.functions.append(functioninfo)
593 if version == 1:
594 for i in range(6):
595 self.functions[i]['message'] = read16(f)
596 assert read16(f) == 0
597 for i in range(6):
598 self.functions[i]['mask'] = read8(f)
600 print self.hotspots
601 print self.functions
603 class Vehicle(CompoundObject):
604 def read(self, f):
605 CompoundObject.read(self, f)
607 # TODO: correct?
608 self.xvec = reads32(f)
609 self.yvec = reads32(f)
610 self.bump = read8(f)
611 print "xvec: " + str(self.xvec) + ", yvec: " + str(self.yvec) + ", bump flags: " + str(self.bump)
613 self.coordx = read16(f)
614 if version == 0:
615 x = f.read(2)
616 print "vehicl bytes:",
617 for z in x: print "%02X" % ord(z),
618 print
619 else:
620 x = read16(f)
621 assert x == 0
622 self.coordy = read16(f)
623 x = read8(f)
624 assert x == 0
625 print "vehicle coords: " + str(self.coordx) + ", " + str(self.coordy)
627 # TODO: this could all be nonsense, really
628 self.cabinleft = read32(f)
629 self.cabintop = read32(f)
630 self.cabinright = read32(f)
631 self.cabinbottom = read32(f)
633 print "cabin coords: " + str(self.cabinleft) + ", " + str(self.cabintop) + ", " + str(self.cabinright) + ", " + str(self.cabinbottom)
635 x = read32(f)
636 assert x == 0
638 # TODO: is it Vehicle subclass?
639 class Lift(Vehicle):
640 def read(self, f):
641 Vehicle.read(self, f)
643 # if version == 0:
644 # x = f.read() # TODO
645 self.numcallbuttons = read32(f)
646 self.curcallbutton = read32(f)
647 self.mysterybytes = f.read(5)
648 assert self.mysterybytes == "\xff\xff\xff\xff\x00"
649 self.callbuttonys = []
650 for i in range(8):
651 self.callbuttonys.append(read32(f))
652 assert read16(f) == 0
653 print "call buttons defined: " + str(self.numcallbuttons)
654 print "current floor: " + str(self.curcallbutton)
655 print "call button y-vals: " + str(self.callbuttonys)
656 if version == 1:
657 x = f.read(4) # TODO
658 print "lift bytes: ",
659 for z in x: print "%02X" % ord(z),
660 print
662 class CallButton(SimpleObject):
663 def read(self, f):
664 SimpleObject.read(self, f)
666 self.lift = slurpMFC(f, Lift)
667 assert self.lift
668 self.buttonid = read8(f)
669 print "button #" + str(self.buttonid) + " for lift " + str(self.lift)
671 class Blackboard(CompoundObject):
672 def read(self, f):
673 CompoundObject.read(self, f)
675 # TODO: none of this is verified
676 if version == 0:
677 self.backgroundcolour = read8(f)
678 self.chalkcolour = read8(f)
679 self.aliascolour = read8(f)
680 self.textx = read8(f)
681 self.texty = read8(f)
682 else:
683 self.backgroundcolour = read32(f)
684 self.chalkcolour = read32(f)
685 self.aliascolour = read32(f)
686 self.textx = read8(f)
687 self.texty = read8(f)
688 print "text at (" + str(self.textx) + ", " + str(self.texty) + ")"
689 print "colours: " + str(self.backgroundcolour) + ", " + str(self.chalkcolour) + ", " + str(self.aliascolour)
691 if version == 0:
692 totalwords = 16
693 else:
694 totalwords = 48
696 for i in xrange(totalwords):
697 num = read32(f)
698 x = f.read(11)
699 d = x.find("\0")
700 if d != -1:
701 x = x[:d]
702 if len(x) > 0:
703 print "blackboard string #" + str(num) + ": " + x
705 class Scenery(SimpleObject):
706 def read(self, f):
707 global readingscenery
708 readingscenery = True
709 SimpleObject.read(self, f)
710 readingscenery = False
712 class Creature:
713 def read(self, f):
714 global version
715 x = read8(f)
716 if x == 0:
717 print "probably a C1 creature?"
718 version = 0
719 elif x == 1 or x == 2:
720 print "probably a C2 creature?"
721 version = 1
722 else:
723 assert False, "didn't understand Creature first byte " + str(x)
725 if version == 0:
726 x = f.read(24)
727 else:
728 x = f.read(33)
729 print "creature bytes:",
730 for z in x: print "%02X" % ord(z),
731 print
733 self.gallery = slurpMFC(f, CGallery)
735 if version == 0:
736 x = f.read(30)
737 else:
738 x = f.read(455) # 255 + 200?
739 print "creature bytes:",
740 for z in x: print "%02X" % ord(z),
741 print
743 self.moniker = f.read(4)
744 self.mommoniker = f.read(4)
745 self.dadmoniker = f.read(4)
747 print "reading creature with moniker " + self.moniker
749 if version == 1:
750 self.text1 = readstring(f)
751 self.text2 = readstring(f)
753 self.body = slurpMFC(f, Body)
755 if version == 1:
756 x = f.read(32)
757 print "creature bytes:",
758 for z in x: print "%02X" % ord(z),
759 print
761 print "vocab:",
762 if version == 0:
763 vocabsize = 80
764 else:
765 vocabsize = 82
766 for i in range(vocabsize):
767 print readstring(f) + " " + readstring(f),
768 f.read(4)
769 print
771 if version == 0:
772 x = f.read(752)
773 else:
774 x = f.read(896)
775 print "creature bytes:",
776 for z in x: print "%02X" % ord(z),
777 print
779 self.brain = slurpMFC(f, CBrain)
781 if version == 1:
782 x = f.read(68)
783 print "creature (brain?) bytes:",
784 for z in x: print "%02X" % ord(z),
785 print
787 self.biochem = slurpMFC(f, CBiochemistry)
789 # TODO: read instincts (CInstinct) here
790 # TODO: read random data [voice files, birth data, history?] here
791 # TODO: read genome (CGenome) here
793 class Body:
794 def read(self, f):
795 x = f.read(145)
796 print "body bytes:",
797 for z in x: print "%02X" % ord(z),
798 print
800 # fuzzie has little idea what's going on here, so this is probably wrong
801 # (but limb #18 is present in some C2 files, and there's no null there in some C1 files)
802 self.limbs = []
803 if version == 0:
804 nolimbs = 17
805 else:
806 nolimbs = 18
807 for i in range(nolimbs):
808 print "trying to read limb.."
809 limb = slurpMFC(f, Limb)
810 self.limbs.append(limb)
812 if version == 0:
813 x = f.read(14)
814 else:
815 x = f.read(16)
816 print "body bytes:",
817 for z in x: print "%02X" % ord(z),
818 print
820 somepose = readstring(f)
821 print "maaaaybe current pose: " + somepose
823 if version == 0:
824 x = f.read(3)
825 else:
826 x = f.read(7)
827 print "body bytes:",
828 for z in x: print "%02X" % ord(z),
829 print
831 print "poses:",
832 if version == 0:
833 noposestrings = 100
834 else:
835 noposestrings = 256
836 for i in range(noposestrings):
837 print readstring(f),
838 print
840 print "animations:",
841 if version == 0:
842 noanims = 8
843 else:
844 noanims = 15
845 for i in range(noanims):
846 print readstring(f),
847 print
849 class Limb:
850 def read(self, f):
851 x = f.read(65)
852 print "limb bytes:",
853 for z in x: print "%02X" % ord(z),
854 print
856 class CBiochemistry:
857 def read(self, f):
858 print "reading biochem"
860 class CBrain:
861 # thanks to Chris Double for working out some of this structure, years ago
862 # http://www.double.co.nz/creatures/creatures2/expbrain.htm
864 def read(self, f):
865 if version == 1:
866 x = f.read(54)
867 print "brain bytes:",
868 for z in x: print "%02X" % ord(z),
869 print
871 nolobes = read32(f)
872 print str(nolobes) + " lobes:"
874 self.lobes = []
875 for i in range(nolobes):
876 l = Lobe()
877 l.read(f)
878 self.lobes.append(l)
880 for i in self.lobes:
881 i.readNeurons(f)
883 # not an MFC class
884 class Neuron:
885 def read(self, f):
886 self.x = read8(f)
887 self.y = read8(f)
888 self.output = read8(f)
889 self.state = read8(f)
891 if version == 0:
892 x = f.read(2)
893 else:
894 x = f.read(4)
895 #print "neuron bytes:",
896 #for z in x: print "%02X" % ord(z),
897 #print
899 for i in range(2):
900 nodendrites = read8(f)
901 dendritetype = read8(f)
902 x = f.read(3)
903 #print "neuron bytes:",
904 #for z in x: print "%02X" % ord(z),
905 #print
907 for j in range(nodendrites):
908 id = read32(f)
909 x = read8(f)
910 y = read8(f)
911 read8(f)
912 stw = read8(f)
913 ltw = read8(f)
914 strength = read8(f)
916 # not an MFC class
917 class Lobe:
918 def read(self, f):
919 self.x = read32(f)
920 self.y = read32(f)
921 self.width = read32(f)
922 self.height = read32(f)
923 print "reading lobe at (" + str(self.x) + ", " + str(self.y) + "), size " + str(self.width) + "x" + str(self.height)
925 x = f.read(9)
926 print "lobe bytes:",
927 for z in x: print "%02X" % ord(z),
928 print
930 self.nominalthreshold = read8(f)
931 read16(f) # TODO
932 self.leakagerate = read8(f)
933 self.reststate = read8(f)
934 self.inputgain = read8(f)
936 print "lobe has nominal threshold " + str(self.nominalthreshold) + ", leakage rate " + str(self.leakagerate) + ", rest state " + str(self.reststate) + " and input gain " + str(self.inputgain)
938 if version == 0:
939 x = f.read(9)
940 else:
941 x = f.read(16)
942 print "lobe bytes:",
943 for z in x: print "%02X" % ord(z),
944 print
946 self.dendrite0details = DendriteDetails()
947 self.dendrite0details.read(f)
948 self.dendrite1details = DendriteDetails()
949 self.dendrite1details.read(f)
951 self.nocells = read32(f)
952 assert self.nocells == self.width * self.height
953 self.nodendrites = read32(f)
955 def readNeurons(self, f):
956 self.neurons = []
957 for i in range(self.nocells):
958 n = Neuron()
959 n.read(f)
960 self.neurons.append(f)
962 # not an MFC class
963 class DendriteDetails:
964 def read(self, f):
965 self.sourcelobe = read32(f)
966 self.minimum = read8(f)
967 self.maximum = read8(f)
968 self.spread = read8(f)
969 self.fanout = read8(f)
970 self.minltw = read8(f)
971 self.maxltw = read8(f)
972 self.minstr = read8(f)
973 self.maxstr = read8(f)
975 print "src lobe %d, min %d, max %d, spread %d, fanout %d, minltw %d, maxltw %d, minstr %d, maxstr %d" % (self.sourcelobe, self.minimum, self.maximum, self.spread, self.fanout, self.minltw, self.maxltw, self.minstr, self.maxstr)
977 self.migrationrule = read8(f)
978 self.relaxsuscept = read8(f)
979 self.relaxstw = read8(f)
980 self.ltwgainrate = read8(f)
982 print "migration rule %d, relax suscept %d, relax STW %d, LTW gain rate %d" % (self.migrationrule, self.relaxsuscept, self.relaxstw, self.ltwgainrate)
984 # the first bit of this, at least, seems to still be meaningful (ie: not all zeros)
985 if version == 0:
986 x = f.read(42)
987 else:
988 x = f.read(92)
989 print "dendrite bytes:",
990 for z in x: print "%02X" % ord(z),
991 print
993 # -------------------------------------------------------------------
995 import sys
997 assert len(sys.argv) == 2
998 f = open(sys.argv[1], "r")
1000 data = slurpMFC(f)
1002 # first thing in an EXP file is Creature
1003 if isinstance(data, Creature):
1004 sys.exit(0)
1006 # first thing in an SFC file is MapData
1007 assert isinstance(data, MapData)
1009 print "successfully read " + str(len(data.rooms)) + " rooms"
1011 # seek through nulls to find the number of objects
1012 x = 0
1013 while x == 0:
1014 x = read8(f)
1015 f.seek(-1, 1)
1017 # read number of objects
1018 objects = []
1019 numobjects = read32(f)
1020 print "reading " + str(numobjects) + " objects.."
1021 print
1023 # read all the objects
1024 for i in range(numobjects):
1025 print "object #" + str(i + 1) + ":"
1026 x = slurpMFC(f)
1027 if x == None:
1028 print "made a mistake somewhere :("
1029 sys.exit(0)
1030 print
1031 objects.append(x)
1033 scenery = []
1034 numscenery = read32(f)
1035 print "reading " + str(numscenery) + " scenery objects.."
1036 print
1038 # read all the scenery objects
1039 for i in range(numscenery):
1040 print "scenery object #" + str(i + 1) + ":"
1041 x = slurpMFC(f, Scenery)
1042 if x == None:
1043 print "made a mistake somewhere :("
1044 sys.exit(0)
1045 print
1046 scenery.append(x)
1048 # TODO: sane scriptness
1049 scripts = {}
1050 numscripts = read32(f)
1051 print "reading " + str(numscripts) + " scripts.."
1052 for i in range(numscripts):
1053 if version == 0:
1054 eventno = read8(f)
1055 species = read8(f)
1056 genus = read8(f)
1057 family = read8(f)
1058 else:
1059 genus = read8(f)
1060 family = read8(f)
1061 eventno = read16(f)
1062 species = read16(f)
1063 script = readstring(f)
1064 print "event #" + str(eventno) + " for " + str(family) + ", " + str(genus) + ", " + str(species) + " (global): " + script
1065 scripts[eventno] = script
1067 print
1069 # TODO: not sure about these, but they change when i scroll :)
1070 scrollx = read32(f)
1071 scrolly = read32(f)
1072 print "scrolled to " + str(scrollx) + ", " + str(scrolly)
1073 # TODO: definitely not sure about these!
1074 zeros = read16(f)
1075 assert zeros == 0
1076 favplacename = readstring(f)
1077 print "aaaand, to finish off, our favourite place is: " + favplacename
1079 if version == 0:
1080 sys.exit(0)
1082 print
1083 print "and now, for " + str(len(data.rooms)) + " rooms.."
1084 print
1086 roomtypes = ["In-Doors", "Surface", "Underwater", "Atmosphere"]
1087 dropstatuses = ["Never", "Above-floor", "Always"]
1088 doordirs = ["Left", "Right", "Up", "Down"]
1090 for i in data.rooms:
1091 print "room # " + str(i.roomid) + " at (" + str(i.left) + ", " + str(i.top) + "), to (" + str(i.right) + ", " + str(i.bottom) + ")"
1092 for j in range(4):
1093 print "doors in direction " + doordirs[j] + ":",
1094 if len(i.doordirections[j]) == 0:
1095 print "None.",
1096 for k in i.doordirections[j]:
1097 if k != i.doordirections[j][0]: print ",",
1098 print "openness " + str(k.openness) + " to room #" + str(k.otherroom),
1099 print
1100 print "wind: (" + str(i.windx) + ", " + str(i.windy) + ")"
1101 print "room type: " + roomtypes[i.roomtype] + " (" + str(i.roomtype) + ")"
1102 print "floor value: " + str(i.floorvalue)
1103 print "inorganic nutrients: " + str(i.inorganicnutrients) + ", organic nutrients: " + str(i.organicnutrients)
1104 print "temperature: " + str(i.temperature) + ", heat source: " + str(i.heatsource)
1105 print "pressure: " + str(i.pressure) + ", pressure source: " + str(i.pressuresource)
1106 print "radiation: " + str(i.radiation) + ", radiation source: " + str(i.radiationsource)
1107 if len(i.music) > 0:
1108 print "music: " + str(i.music)
1109 if len(i.floorpoints) > 0:
1110 print "Surface points:",
1111 for x in i.floorpoints:
1112 print "(" + str(x[0]) + ", " + str(x[1]) + ")",
1113 print
1114 print "drop status: " + dropstatuses[i.dropstatus] + " (" + str(i.dropstatus) + ")"
1115 print