Fix filename bug that Xavier identified, add field comments, general cleanup
authorMichael Petch <mpetch@capp-sysware.com>
Tue, 31 Dec 2013 16:22:09 +0000 (09:22 -0700)
committerMichael Petch <mpetch@capp-sysware.com>
Tue, 31 Dec 2013 16:25:42 +0000 (09:25 -0700)
extractxgdata.py
xgimport.py
xgstruct.py

index 003ae0e..92ba12f 100755 (executable)
@@ -80,7 +80,7 @@ if __name__ == '__main__':
             for segment in xgobj.getfilesegment():
                 segment.copyto(os.path.abspath(
                         os.path.join(xgbasepath,
-                        xgbasefile[:-(len(xgext) + 1)] + segment.ext)))
+                        xgbasefile[:-len(xgext[1])] + segment.ext)))
 
                 if segment.type == xgimport.Import.Segment.XG_GAMEFILE:
                     segment.fd.seek(os.SEEK_SET, 0)
@@ -100,7 +100,7 @@ if __name__ == '__main__':
                         rec = xgstruct.RolloutFileRecord().fromstream(segment.fd)
                         if rec is None:
                             break
-                        pprint.pprint (rec.__dict__,width=160)
+                        pprint.pprint (rec,width=160)
 
         except (xgimport.Error, xgzarc.Error) as e:
             print (e.value)
index f0ae771..99a5a3c 100644 (file)
@@ -33,7 +33,7 @@ class Import(object):
         GDF_HDR, GDF_IMAGE, XG_GAMEHDR, XG_GAMEFILE, XG_ROLLOUTS, XG_COMMENT, \
             ZLIBARC_IDX, XG_UNKNOWN = range(8)
         EXTENSIONS = ['_gdh.bin', '.jpg', '_gamehdr.bin', '_gamefile.bin',
-                      '_rollouts.bin', '_comments.rtf', '_idx.bin', None]
+                      '_rollouts.bin', '_comments.bin', '_idx.bin', None]
         GDF_HDR_EXT, GDF_IMAGE_EXT, XG_GAMEHDR_EXT, XG_GAMEFILE_EXT, \
             XG_ROLLOUTS_EXT, XG_COMMENTS_EXT, \
             XG_IDX_EXT, XG_UNKNOWN = EXTENSIONS
index 50be494..1d16ffd 100755 (executable)
@@ -19,7 +19,8 @@
 #
 #   This code is based upon Delphi data structures provided by
 #   Xavier Dufaure de Citres <contact@extremegammon.com> for purposes
-#   of interacting with the ExtremeGammon XG file formats.
+#   of interacting with the ExtremeGammon XG file formats. Field
+#   descriptions derived from xg_format.pas
 #
 
 import xgutils as _xgutils
@@ -33,15 +34,15 @@ class TimeSettingRecord(dict):
 
     def __init__(self, **kw):
         defaults = {
-            'ClockType': 0,
-            'PerGame': False,
-            'Time1': 0,
-            'Time2': 0,
-            'Penalty': 0,
-            'TimeLeft1': 0,
-            'TimeLeft2': 0,
-            'PenaltyMoney': 0            
-            }
+            'ClockType': 0,                 # 0=None,0=Fischer,0=Bronstein
+            'PerGame': False,               # time is for session reset after each game
+            'Time1': 0,                     # initial time in sec
+            'Time2': 0,                     # time added (fisher) or reverved (bronstrein) per move in sec
+            'Penalty': 0,                   # point penalty when running our of time (in point)
+            'TimeLeft1': 0,                 # current time left
+            'TimeLeft2': 0,                 # current time left
+            'PenaltyMoney': 0               # point penalty when running our of time (in point)
+            }                              
         super(TimeSettingRecord, self).__init__(defaults, **kw)
 
     def __setattr__(self, key, value):
@@ -71,8 +72,8 @@ class EvalLevelRecord(dict):
 
     def __init__(self, **kw):
         defaults = {
-            'Level': 0,
-            'isDouble': False
+            'Level': 0,                     # Level used see PLAYERLEVEL table
+            'isDouble': False               # The analyze assume double for the very next move
             }
         super(EvalLevelRecord, self).__init__(defaults, **kw)
 
@@ -98,23 +99,23 @@ class EngineStructBestMoveRecord(dict):
 
     def __init__(self, **kw):
         defaults = {
-            'Pos': None,
-            'Dice': None,
-            'Level': 0,
-            'Score': None,
-            'Cube': 0,
-            'CubePos': 0,
-            'Crawford': 0,
-            'Jacoby': 0,
-            'Nmoves': 0,
-            'PosPlayed': None,
-            'Moves': None,
-            'EvalLevel': None,
-            'Eval': None,
-            'Unused': 0,
-            'met': 0,
-            'Choice0': 0,
-            'Choice3': 0
+            'Pos': None,                    # Current position
+            'Dice': None,                   # Dice
+            'Level': 0,                     # analyze level requested
+            'Score': None,                  # current score
+            'Cube': 0,                      # cube value 1,2,4, etcc.
+            'CubePos': 0,                   # 0: Center 1: Player owns cube -1 Opponent owns cube
+            'Crawford': 0,                  # 1 = Crawford   0 = No Crawford
+            'Jacoby': 0,                    # 1 = Jacoby   0 = No Jacoby
+            'NMoves': 0,                    # number of move (max 32)
+            'PosPlayed': None,              # position played
+            'Moves': None,                  # move list as From1,dice1, from2,dice2 etc.. -1 show termination of list
+            'EvalLevel': None,              # evaluation level of each move
+            'Eval': None,                   # eval value of each move
+            'Unused': 0,                    # if 1 does not count as a decision
+            'met': 0,                       # unused
+            'Choice0': 0,                   # 1-ply choice (index to PosPlayed)
+            'Choice3': 0                    # 3-ply choice (index to PosPlayed)
             }
         super(EngineStructBestMoveRecord, self).__init__(defaults, **kw)
 
@@ -136,7 +137,7 @@ class EngineStructBestMoveRecord(dict):
         self.Cubepos = unpacked_data[32]
         self.Crawford = unpacked_data[33]
         self.Jacoby = unpacked_data[34]
-        self.Nmoves = unpacked_data[35]
+        self.NMoves = unpacked_data[35]
 
         self.PosPlayed = ()
         for row in range(32):
@@ -171,26 +172,26 @@ class EngineStructDoubleAction(dict):
 
     def __init__(self, **kw):
         defaults = {
-            'Pos': None,
-            'Level': 0,
-            'Score': None,
-            'Cube': 0,
-            'CubePos': 0,
-            'Jacoby': 0,
-            'Crawford': 0,
-            'met': 0,
-            'FlagDouble': 0,
-            'isBeaver': 0,
-            'Eval': None,
-            'equB': 0.0,
-            'equDouble': 0.0,
-            'equDrop': 0.0,
-            'LevelRequest': 0,
-            'DoubleChoice3': 0,
-            'EvalDouble': None
+            'Pos': None,                    # Current position
+            'Level': 0,                     # analyze level performed
+            'Score': None,                  # current score
+            'Cube': 0,                      # cube value 1,2,4, etcc.
+            'CubePos': 0,                   # 0: Center 1: Player owns cube -1 Opponent owns cube
+            'Jacoby': 0,                    # 1 = Jacoby   0 = No Jacoby
+            'Crawford': 0,                  # 1 = Crawford   0 = No Crawford
+            'met': 0,                       # unused
+            'FlagDouble': 0,                # 0: Dont double 1: Double
+            'isBeaver': 0,                  # is it a beaver if doubled
+            'Eval': None,                   # eval value for No double
+            'equB': 0.0,                    # equity No Double
+            'equDouble': 0.0,               # equity Double/take
+            'equDrop': 0.0,                 # equity double/drop (-1)
+            'LevelRequest': 0,              # analyze level requested
+            'DoubleChoice3': 0,             # 3-ply choice as double+take*2
+            'EvalDouble': None              # eval value for Double/Take
             }
         super(EngineStructDoubleAction, self).__init__(defaults, **kw)
-        
+
     def __setattr__(self, key, value):
         self[key] = value
 
@@ -229,61 +230,61 @@ class HeaderMatchEntry(dict):
         defaults = {
             'Name': 'MatchInfo',
             'EntryType': GameFileRecord.ENTRYTYPE_HEADERMATCH,
-            'SPlayer1': None,
+            'SPlayer1': None,              # player name in ANSI string for XG1 compatbility see "Player1" and "Player2" below for unicode
             'SPlayer2': None,
-            'MatchLength': 0,
-            'Variation': 0,
-            'Crawford': False,
-            'Jacoby': False,
-            'Beaver': False,
-            'AutoDouble': False,
-            'Elo1': 0.0,
-            'Elo2': 0.0,
-            'Exp1': 0,
-            'Exp2': 0,
-            'Date': 0,
-            'SEvent': None,
-            'GameId': 0,
-            'CompLevel1': -1,
+            'MatchLength': 0,              # Match length, 99999 for unlimited
+            'Variation': 0,                # 0:backgammon, 1: Nack, 2: Hyper, 3: Longgammon
+            'Crawford': False,             # Crawford in use
+            'Jacoby': False,               # Jacoby in use
+            'Beaver': False,               # Beaver in use
+            'AutoDouble': False,           # Automatic double in use
+            'Elo1': 0.0,                   # player1 elo
+            'Elo2': 0.0,                   # player2 experience
+            'Exp1': 0,                     # player1 elo
+            'Exp2': 0,                     # player2 experience
+            'Date': 0,                     # game date
+            'SEvent': None,                # event name, in ANSI string for XG1 compatbility see "event" below for unicode
+            'GameId': 0,                   # game ID, if player are swap make gameid:=-GameID
+            'CompLevel1': -1,              # Player level: see table at the end (PLAYERLEVEL TABLE)
             'CompLevel2': -1,
-            'CountForElo': False,
-            'AddtoProfile1': False,
-            'AddtoProfile2': False,
-            'SLocation': None,
-            'GameMode': 0,
-            'Imported': False,
-            'SRound': None,
-            'Invert': 0,
-            'Version': version,
-            'Magic': 0x494C4D44,
-            'MoneyInitG': 0,
-            'MoneyInitScore': [0, 0],
-            'Entered': False,
-            'Counted': False,
-            'UnratedImp': False,
-            'CommentHeaderMatch': -1,
-            'CommentFooterMatch': -1,
-            'isMoneyMatch': False,
-            'WinMoney': 0.0,
-            'LoseMoney': 0.0,
-            'Currency': 0,
-            'FeeMoney': 0.0,
-            'TableStake': 0,
-            'SiteId': -1,
-            'CubeLimit': 0,
-            'AutoDoubleMax': 0,
-            'Transcribed': False,
-            'Event': None,
-            'Player1': None,
-            'Player2': None,
-            'Location': None,
-            'Round': None,
-            'TimeSetting': None,
-            'TotTimeDelayMove': 0,
-            'TotTimeDelayCube': 0,
-            'TotTimeDelayMoveDone': 0,
-            'TotTimeDelayCubeDone': 0,
-            'Transcriber': None
+            'CountForElo': False,          # outcome of the session will affect elo
+            'AddtoProfile1': False,        # outcome of the session will affect player 1 profile
+            'AddtoProfile2': False,        # outcome of the session will affect player 2 profile
+            'SLocation': None,             # location name, in ANSI string for XG1 compatbility see "location" below for unicode
+            'GameMode': 0,                 # game mode : see table at the end (GAMEMODE TABLE)
+            'Imported': False,             # game was imported from an site (MAT, CBG etc..)
+            'SRound': None,                # round name, in ANSI string for XG1 compatbility see "round" below for unicode
+            'Invert': 0,                   # If the board is swap then invert=-invert and MatchID=-MatchID
+            'Version': version,            # file version, currently SaveFileVersion
+            'Magic': 0x494C4D44,           # must be MagicNumber = $494C4D44;
+            'MoneyInitG': 0,               # initial game played from the profile against that opp in money
+            'MoneyInitScore': [0, 0],      # initial score from the profile against that opp in money
+            'Entered': False,              # entered in profile
+            'Counted': False,              # already accounted in the profile elo
+            'UnratedImp': False,           # game was unrated on the site it was imported from
+            'CommentHeaderMatch': -1,      # index of the match comment header in temp.xgc
+            'CommentFooterMatch': -1,      # index of the match comment footer in temp.xgc
+            'isMoneyMatch': False,         # was player for real money
+            'WinMoney': 0.0,               # amount of money for the winner
+            'LoseMoney': 0.0,              # amount of money for the looser
+            'Currency': 0,                 # currency code from Currency.ini
+            'FeeMoney': 0.0,               # amount of rake
+            'TableStake': 0,               # max amount that can be lost -- NOT IMPLEMENTED
+            'SiteId': -1,                  # site id from siteinfo.ini
+            'CubeLimit': 0,                # v8: maximum cube value
+            'AutoDoubleMax': 0,            # v8: maximum c# of time the autodouble can be used
+            'Transcribed': False,          # v24: game was transcribed
+            'Event': None,                 # v24: Event name (unicode)
+            'Player1': None,               # v24: Player1 name (unicode)
+            'Player2': None,               # v24: Player2 name (unicode)
+            'Location': None,              # v24: Location (unicode)
+            'Round': None,                 # v24: Round (unicode)
+            'TimeSetting': None,           # v25: Time setting for the game
+            'TotTimeDelayMove': 0,         # v26: # of checker play marked for delayed RO
+            'TotTimeDelayCube': 0,         # v26: # of checker play marked for delayed RO done
+            'TotTimeDelayMoveDone': 0,     # v26: # of checker Cube action marked for delayed RO
+            'TotTimeDelayCubeDone': 0,     # v26: # of checker Cube action marked for delayed RO Done
+            'Transcriber': None            # v30: Name of the Transcriber (unicode)
             }
         super(HeaderMatchEntry, self).__init__(defaults, **kw)
 
@@ -376,19 +377,20 @@ class FooterGameEntry(dict):
         defaults = {
             'Name': 'GameFooter',
             'EntryType': GameFileRecord.ENTRYTYPE_FOOTERGAME,
-            'Score1g': 0,
-            'Score2g': 0,
-            'CrawfordApplyg': False,
-            'Winner': 0,
-            'PointsWon': 0,
-            'Termination': 0,
-            'ErrResign': 0.0,
-            'ErrTakeResign': 0.0,
-            'Eval': None,
+            'Score1g': 0,                   # Final score
+            'Score2g': 0,                   # Final score
+            'CrawfordApplyg': False,        # will crawford apply next game
+            'Winner': 0,                    # who win +1=player1, -1 player 2
+            'PointsWon': 0,                 # point scored
+            'Termination': 0,               # 0=Drop 1=single 2=gammon 3=Backgamon 
+                                            # (0,1,2) +100=Resign  (0,1,2)+1000 settle
+            'ErrResign': 0.0,               # error made by resigning (-1000 if not analyze)
+            'ErrTakeResign': 0.0,           # error made by accepting the resign (-1000 if not analyze)
+            'Eval': None,                   # evaluation of the final position
             'EvalLevel': 0
             }
         super(FooterGameEntry, self).__init__(defaults, **kw)
-        
+
     def __setattr__(self, key, value):
         self[key] = value
 
@@ -396,7 +398,8 @@ class FooterGameEntry(dict):
        return self[key]
 
     def fromstream(self, stream):
-        unpacked_data = _struct.unpack('<9xxxxllBxxxlllxxxxdd7dl', stream.read(116))
+        unpacked_data = _struct.unpack('<9xxxxllBxxxlllxxxxdd7dl',
+                stream.read(116))
         self.Score1g = unpacked_data[0]
         self.Score2g = unpacked_data[1]
         self.CrawfordApplyg = bool(unpacked_data[2])
@@ -408,7 +411,7 @@ class FooterGameEntry(dict):
         self.Eval = unpacked_data[8:15]
         self.EvalLevel = unpacked_data[15]
         return self
-         
+
 
 class MissingEntry(dict):
 
@@ -423,7 +426,7 @@ class MissingEntry(dict):
             'MissingPoints': 0
             }
         super(MissingEntry, self).__init__(defaults, **kw)
-        
+
     def __setattr__(self, key, value):
         self[key] = value
 
@@ -446,17 +449,17 @@ class FooterMatchEntry(dict):
         defaults = {
             'Name': 'MatchFooter',
             'EntryType': GameFileRecord.ENTRYTYPE_FOOTERMATCH,
-            'Score1m': 0,
-            'Score2m': 0,
-            'WinnerM': 0,
-            'Elo1m': 0.0,
-            'Elo2m': 0.0,
-            'Exp1m': 0,
-            'Exp2m': 0,
-            'Datem': 0.0
+            'Score1m': 0,                   # Final score of the match
+            'Score2m': 0,                   # Final score of the match
+            'WinnerM': 0,                   # who win +1=player1, -1 player 2
+            'Elo1m': 0.0,                   # resulting elo, player1
+            'Elo2m': 0.0,                   # resulting elo, player2
+            'Exp1m': 0,                     # resulting exp, player1
+            'Exp2m': 0,                     # resulting exp, player2
+            'Datem': 0.0                    # Date time of the match end
             }
         super(FooterMatchEntry, self).__init__(defaults, **kw)
-        
+
     def __setattr__(self, key, value):
         self[key] = value
 
@@ -485,18 +488,20 @@ class HeaderGameEntry(dict):
         defaults = {
             'Name': 'GameHeader',
             'EntryType': GameFileRecord.ENTRYTYPE_HEADERGAME,
-            'Score1': 0,
-            'Score2': 0,
-            'CrawfordApply': False,
-            'PosInit': (0,) * 26,    # Tuple of 26 integers
-            'GameNumber': 0,
-            'InProgress': False,
-            'CommentHeaderGame': -1,
-            'CommentFooterGame': -1,
-            'NumberOfAutoDoubles': 0
+            'Score1': 0,                    # initial score player1
+            'Score2': 0,                    # initial score player1
+            'CrawfordApply': False,         # iDoes Crawford apply on that game
+            'PosInit': (0,) * 26,           # initial position
+            'GameNumber': 0,                # Game number (start at 1)
+            'InProgress': False,            # Game is still in progress
+            'CommentHeaderGame': -1,        # index of the game comment header in temp.xgc
+            'CommentFooterGame': -1,        # index of the game comment footer in temp.xgc
+            'NumberOfAutoDoubles': 0        # v26: Number of Autodouble that happen in that game
+                                            # note that in the rest of the game the cube still start at 1.
+                                            # For display purpose or point calculation add the 2^NumberOfAutoDouble
             }
         super(HeaderGameEntry, self).__init__(defaults, **kw)
-        
+
     def __setattr__(self, key, value):
         self[key] = value
 
@@ -504,7 +509,8 @@ class HeaderGameEntry(dict):
        return self[key]
 
     def fromstream(self, stream):
-        unpacked_data = _struct.unpack('<9xxxxllB26bxlBxxxlll', stream.read(68))
+        unpacked_data = _struct.unpack('<9xxxxllB26bxlBxxxlll',
+                stream.read(68))
         self.Score1 = unpacked_data[0]
         self.Score2 = unpacked_data[1]
         self.CrawfordApply = bool(unpacked_data[2])
@@ -519,234 +525,206 @@ class HeaderGameEntry(dict):
         return self
 
 
-class CubeEntry(object):
+class CubeEntry(dict):
 
     SIZEOFREC = 2560
 
-    def __init__(self):
-        self.name = 'Cube'
-        self.entrytype = GameFileRecord.ENTRYTYPE_HEADERMATCH
-        self.actifp = 0
-        self.double = 0
-        self.take = 0
-        self.beaverr = 0
-        self.raccoonr = 0
-        self.cubeb = 0
-        self.position = None
-        self.doubled = None
-        self.errcube = 0.0
-        self.dicerolled = None
-        self.errtake = 0.0
-        self.rolloutindexd = 0
-        self.compchoiced = 0
-        self.analyzec = 0
-        self.errbeaver = 0.0
-        self.errraccoon = 0.0
-        self.analyzecr = 0
-        self.isvalid = 0
-        self.tutorcube = 0
-        self.tutortake = 0
-        self.errtutorcube = 0.0
-        self.errtutortake = 0.0
-        self.flaggeddouble = False
-        self.commentcube = -1
-        self.editedcube = False
-        self.timedelaycube = False
-        self.timedelaycubedone = False
-        self.numberofautodoublecube = 0
-        self.timebot = 0
-        self.timetop = 0
-
-    def __str__(self):
-        return str(self.todict())
-
-    def __repr__(self):
-        return str(self)
+    def __init__(self, **kw):
+        defaults = {
+            'Name': 'Cube',
+            'EntryType': GameFileRecord.ENTRYTYPE_HEADERMATCH,
+            'ActiveP': 0,                   # Active player (1 or 2)
+            'Double': 0,                    # player double (0= no, 1=yes)
+            'Take': 0,                      # opp take (0= no, 1=yes, 2=beaver )
+            'BeaverR': 0,                   # player accept beaver (0= no, 1=yes, 2=raccoon)
+            'RaccoonR': 0,                  # player accept raccoon (0= no, 1=yes)
+            'CubeB': 0,                     # Cube value 0=center, +1=2 own, +2=4 own ... -1=2 opp, -2=4 opp
+            'Position': None,               # initial position
+            'Doubled': None,                # Analyze result
+            'ErrCube': 0.0,                 # error made on doubling (-1000 if not analyze)
+            'DiceRolled': None,             # dice rolled
+            'ErrTake': 0.0,                 # error made on taking (-1000 if not analyze)
+            'RolloutIndexD': 0,             # index of the Rollout in temp.xgr
+            'CompChoiceD': 0,               # 3-ply choice as Double+2*take
+            'AnalyzeC': 0,                  # Level of the analyze
+            'ErrBeaver': 0.0,               # error made on beavering (-1000 if not analyze)
+            'ErrRaccoon': 0.0,              # error made on racconning (-1000 if not analyze)
+            'AnalyzeCR': 0,                 # requested Level of the analyze (sometime a XGR+ request will stop at 4-ply when obivous)
+            'isValid': 0,                   # invalid decision 0=Ok, 1=error, 2=invalid
+            'TutorCube': 0,                 # player initial double in tutor mode (0= no, 1=yes)
+            'TutorTake': 0,                 # player initial take in tutor mode (0= no, 1=yes)
+            'ErrTutorCube': 0.0,            # error initialy made on doubling (-1000 if not analyze)
+            'ErrTutorTake': 0.0,            # error initialy made on taking (-1000 if not analyze)
+            'FlaggedDouble': False,         # cube has been flagged
+            'CommentCube': -1,              # index of the cube comment in temp.xgc
+            'EditedCube': False,            # v24: Position was edited at this point
+            'TimeDelayCube': False,         # v26: position is marked for later RO
+            'TimeDelayCubeDone': False,     # v26: position later RO has been done
+            'NumberOfAutoDoubleCube': 0,    # v27: Number of Autodouble that happen in that game
+            'TimeBot': 0,                   # v28: time left for both players
+            'TimeTop': 0
+            }
+        super(CubeEntry, self).__init__(defaults, **kw)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+    def __getattr__(self, key):
+       return self[key]
 
     def fromstream(self, stream):
         unpacked_data = _struct.unpack('<9xxxxllllll26bxx',
                                        stream.read(64))
-        self.actifp = unpacked_data[0]
-        self.double = unpacked_data[1]
-        self.take = unpacked_data[2]
-        self.beaverr = unpacked_data[3]
-        self.raccoonr = unpacked_data[4]
-        self.cubeb = unpacked_data[5]
-        self.position = unpacked_data[6:32]
-        self.doubled = EngineStructDoubleAction().fromstream(stream)
+        self.ActiveP = unpacked_data[0]
+        self.Double = unpacked_data[1]
+        self.Take = unpacked_data[2]
+        self.BeaverR = unpacked_data[3]
+        self.RaccoonR = unpacked_data[4]
+        self.CubeB = unpacked_data[5]
+        self.Position = unpacked_data[6:32]
+        self.Doubled = EngineStructDoubleAction().fromstream(stream)
         unpacked_data = _struct.unpack('<xxxxd3Bxxxxxdlllxxxx' \
                                        'ddllbbxxxxxxddBxxxlBBBxlll',
                                        stream.read(116))
-        self.errcube = unpacked_data[0]
-        self.dicerolled = _xgutils.delphishortstrtostr(unpacked_data[1:4]) 
-        self.errtake = unpacked_data[4]
-        self.rolloutindexd = unpacked_data[5]
-        self.compchoiced = unpacked_data[6]
-        self.analyzec = unpacked_data[7]
-        self.errbeaver = unpacked_data[8]
-        self.errraccoon = unpacked_data[9]
-        self.analyzecr = unpacked_data[10]
-        self.isvalid = unpacked_data[11]
-        self.tutorcube = unpacked_data[12]
-        self.tutortake = unpacked_data[13]
-        self.errtutorcube = unpacked_data[14]
-        self.errtutortake = unpacked_data[15]
-        self.flaggeddouble = bool(unpacked_data[16])
-        self.commentcube = unpacked_data[17]
+        self.ErrCube = unpacked_data[0]
+        self.DiceRolled = _xgutils.delphishortstrtostr(unpacked_data[1:4])
+        self.ErrTake = unpacked_data[4]
+        self.RolloutIndexD = unpacked_data[5]
+        self.CompChoiceD = unpacked_data[6]
+        self.AnalyzeC = unpacked_data[7]
+        self.ErrBeaver = unpacked_data[8]
+        self.ErrRaccoon = unpacked_data[9]
+        self.AnalyzeCR = unpacked_data[10]
+        self.isValid = unpacked_data[11]
+        self.TutorCube = unpacked_data[12]
+        self.TutorTake = unpacked_data[13]
+        self.ErrTutorCube = unpacked_data[14]
+        self.ErrTutorTake = unpacked_data[15]
+        self.FlaggedDouble = bool(unpacked_data[16])
+        self.CommentCube = unpacked_data[17]
         if self.Version >= 24:
-            self.editedcube = bool(unpacked_data[18])
+            self.EditedCube = bool(unpacked_data[18])
         if self.Version >= 26:
-            self.timedelaycube = bool(unpacked_data[19])
-            self.timedelaycubedone = bool(unpacked_data[20])
+            self.TimeDelayCube = bool(unpacked_data[19])
+            self.TimeDelayCubeDone = bool(unpacked_data[20])
         if self.Version >= 27:
-            self.numberofautodoublecube = unpacked_data[21]
+            self.NumberOfAutoDoubleCube = unpacked_data[21]
         if self.Version >= 28:
-            self.timebot = unpacked_data[22]
-            self.timetop = unpacked_data[23]
+            self.TimeBot = unpacked_data[22]
+            self.TimeTop = unpacked_data[23]
         return self
 
 
-    def todict(self):
-        return {'actifp': self.actifp, 'double': self.double,
-                'take': self.take, 'beaverr': self.beaverr,
-                'raccoonr': self.raccoonr, 'cubeb': self.cubeb,
-                'position': self.position, 'doubled': self.doubled,
-                'errcube': self.errcube, 'dicerolled': self.dicerolled,
-                'errtake': self.errtake, 'rolloutindexd': self.rolloutindexd,
-                'compchoiced': self.compchoiced, 'analyzec': self.analyzec,
-                'errbeaver': self.errbeaver, 'errraccoon': self.errraccoon,
-                'analyzecr': self.analyzecr, 'isvalid': self.isvalid,
-                'tutorcube': self.tutorcube, 'tutortake': self.tutortake,
-                'errtutorcube': self.errtutorcube,
-                'errtutortake': self.errtutortake, 
-                'flaggeddouble': self.flaggeddouble, 
-                'commentcube': self.commentcube, 'editedcube': self.editedcube,
-                'timedelaycube': self.timedelaycube,
-                'timedalaycubedone:': self.timedelaycubedone,
-                'numberofautodoublecube': self.numberofautodoublecube,
-                'timebot': self.timebot, 'timetop': self.timetop}
-                
-
-class MoveEntry(object):
+class MoveEntry(dict):
 
     SIZEOFREC = 2560
 
-    def __init__(self):
-        self.name = 'Move'
-        self.entrytype = GameFileRecord.ENTRYTYPE_MOVE
-        self.positioni = None
-        self.positionend = None
-        self.actifp = 0
-        self.moves = None
-        self.dice = None
-        self.cubea = 0
-        self.errorm = 0 # Not used
-        self.nmoveeval = 0
-        self.datamoves = None
-        self.played = False
-        self.errmove = 0.0
-        self.errluck = 0.0
-        self.compchoice = 0
-        self.initeq = 0.0
-        self.rolloutindexm = None
-        self.analyzem = 0
-        self.analyzel = 0
-        self.invalidm = 0
-        self.positiontutor = None
-        self.tutor = 0
-        self.errtutormove = 0.0
-        self.flagged = False
-        self.commentmove = -1
-        self.editedmove = False
-        self.timedelaymove = 0
-        self.timedelaymovedone = 0
-        self.numberofautodoublemove = 0
-        self.filler = (0,)*4
-
-    def __str__(self):
-        return str(self.todict())
-
-    def __repr__(self):
-        return str(self)
+    def __init__(self, **kw):
+        defaults = {
+            'Name:': 'Move',
+            'EntryType': GameFileRecord.ENTRYTYPE_MOVE,
+            'PositionI': None,              # Initial position
+            'PositionEnd': None,            # Final Position
+            'ActiveP': 0,                   # active player (1,2)
+            'Moves': None,                  # list of move as From1,dice1, from2,dice2 etc.. -1 show termination of list
+            'Dice': None,                   # dice rolled
+            'CubeA': 0,                     # Cube value 0=center, +1=2 own, +2=4 own ... -1=2 opp, -2=4 opp
+            'ErrorM': 0,                    # Not used anymore (not sure)
+            'NMoveEval': 0,                 # Number of candidate (max 32)
+            'DataMoves': None,              # analyze
+            'Played': False,                # move was played
+            'ErrMove': 0.0,                 # error made (-1000 if not analyze)
+            'ErrLuck': 0.0,                 # luck of the roll
+            'CompChoice': 0,                # computer choice (index to DataMoves.moveplayed)
+            'InitEq': 0.0,                  # Equity before the roll (for luck purposes)
+            'RolloutIndexM': None,          # index of the Rollout in temp.xgr
+            'AnalyzeM': 0,                  # level of analyze of the move
+            'AnalyzeL': 0,                  # level of analyze for the luck
+            'InvalidM': 0,                  # invalid decision 0=Ok, 1=error, 2=invalid
+            'PositionTutor': None,          # Position resulting of the initial move
+            'Tutor': 0,                     # index of the move played dataMoves.moveplayed
+            'ErrTutorMove': 0.0,            # error initialy made (-1000 if not analyze)
+            'Flagged': False,               # move has been flagged
+            'CommentMove': -1,              # index of the move comment in temp.xgc
+            'EditedMove': False,            # v24: Position was edited at this point
+            'TimeDelayMove': 0,             # v26: Bit list: position is marked for later RO
+            'TimeDelayMoveDone': 0,         # v26: Bit list: position later RO has been done
+            'NumberOfAutoDoubleMove': 0     # v27: Number of Autodouble that happen in that game
+                                            # filler, ignore, should be set to 0
+            }
+        super(MoveEntry, self).__init__(defaults, **kw)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+    def __getattr__(self, key):
+       return self[key]
 
     def fromstream(self, stream):
         unpacked_data = _struct.unpack('<9x26b26bxxxl8l2lldl',
                                        stream.read(124))
-        self.positioni = unpacked_data[0:26]
-        self.positionend = unpacked_data[26:52]
-        self.actifp = unpacked_data[52]
-        self.moves = unpacked_data[53:61]
-        self.dice = unpacked_data[61:63]
-        self.cubea = unpacked_data[63]
-        self.errorm = unpacked_data[64] # Not used
-        self.nmoveeval = unpacked_data[65]
-        self.datamoves = EngineStructBestMoveRecord().fromstream(stream)
+        self.PositionI = unpacked_data[0:26]
+        self.PositionEnd = unpacked_data[26:52]
+        self.ActiveP = unpacked_data[52]
+        self.Moves = unpacked_data[53:61]
+        self.Dice = unpacked_data[61:63]
+        self.CubeA = unpacked_data[63]
+        self.ErrorM = unpacked_data[64] # Not used
+        self.NMoveEval = unpacked_data[65]
+        self.DataMoves = EngineStructBestMoveRecord().fromstream(stream)
 
         unpacked_data = _struct.unpack('<Bxxxddlxxxxd32llll26bbxdBxxxl',
                                        stream.read(220))
-        self.played = bool(unpacked_data[0])
-        self.errmove = unpacked_data[1]
-        self.errluck = unpacked_data[2]
-        self.compchoice = unpacked_data[3]
-        self.initeq = unpacked_data[4]
-        self.rolloutindexm = unpacked_data[5:37]
-        self.analyzem = unpacked_data[37]
-        self.analyzel = unpacked_data[38]
-        self.invalidm = unpacked_data[39]
-        self.positiontutor = unpacked_data[40:66]
-        self.tutor = unpacked_data[66]
-        self.errtutormove = unpacked_data[67]
-        self.flagged = bool(unpacked_data[68])
-        self.commentmove = unpacked_data[69]
+        self.Played = bool(unpacked_data[0])
+        self.ErrMove = unpacked_data[1]
+        self.ErrLuck = unpacked_data[2]
+        self.CompChoice = unpacked_data[3]
+        self.InitEq = unpacked_data[4]
+        self.RolloutIndexM = unpacked_data[5:37]
+        self.AnalyzeM = unpacked_data[37]
+        self.AnalyzeL = unpacked_data[38]
+        self.InvalidM = unpacked_data[39]
+        self.PositionTutor = unpacked_data[40:66]
+        self.Tutor = unpacked_data[66]
+        self.ErrTutorMove = unpacked_data[67]
+        self.Flagged = bool(unpacked_data[68])
+        self.CommentMove = unpacked_data[69]
         if self.Version >= 24:
             unpacked_data = _struct.unpack('<B', stream.read(1))
-            self.editedmove = bool(unpacked_data[0])
+            self.EditedMove = bool(unpacked_data[0])
         if self.Version >= 26:
             unpacked_data = _struct.unpack('<xxxLL', stream.read(11))
-            self.timedelaymove = unpacked_data[0]
-            self.timedelaymovedone = unpacked_data[1]
+            self.TimeDelayMove = unpacked_data[0]
+            self.TimeDelayMoveDone = unpacked_data[1]
         if self.Version >= 27:
             unpacked_data = _struct.unpack('<l', stream.read(4))
-            self.numberofautodoublemove = unpacked_data[0]
-        unpacked_data = _struct.unpack('<4l', stream.read(16))
-        self.filler = unpacked_data
-        
+            self.NumberOfAutoDoubleMove = unpacked_data[0]
+
         return self
 
-    def todict(self):
-        return {'positioni': self.positioni, 'positionend': self.positionend,
-                'actifp': self.actifp, 'moves': self.moves,
-                'dice': self.dice, 'cubea': self.cubea,
-                'errorm': self.errorm, 'nmoveeval': self.nmoveeval,
-                'datamoves': self.datamoves, 'played': self.played,
-                'errmove': self.errmove, 'errluck': self.errluck,
-                'compchoice': self.compchoice, 'initeq': self.initeq,
-                'rolloutindexm': self.rolloutindexm,
-                'analyzem': self.analyzem, 'analyzel': self.analyzel,
-                'invalidm': self.invalidm, 
-                'positiontutor': self.positiontutor, 'tutor': self.tutor,
-                'errtutormove': self.errtutormove, 'flagged': self.flagged,
-                'commentmove': self.commentmove,
-                'editedmove': self.editedmove, 
-                'timedelaymove': self.timedelaymove,
-                'timedelaymovedone': self.timedelaymovedone,
-                'numberofautodoublemove': self.numberofautodoublemove}
-
-
-class UnimplementedEntry(object):
+
+class UnimplementedEntry(dict):
 
     """ Class for record types we have yet to implement
     """
 
     SIZEOFREC = 2560
 
+    def __init__(self, **kw):
+        defaults = {
+            'Name': 'Unimplemented'
+            }
+        super(UnimplementedEntry, self).__init__(defaults, **kw)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+    def __getattr__(self, key):
+       return self[key]
+
     def fromstream(self, stream):
-        self.name = 'Unimplemented'
         return self
 
-    def todict(self):
-        return {}
-
 
 class GameFileRecord(dict):
 
@@ -759,22 +737,26 @@ class GameFileRecord(dict):
     ENTRYTYPE_HEADERMATCH, ENTRYTYPE_HEADERGAME, ENTRYTYPE_CUBE, \
             ENTRYTYPE_MOVE, ENTRYTYPE_FOOTERGAME, ENTRYTYPE_FOOTERMATCH, \
             ENTRYTYPE_MISSING, ENTRYTYPE_UNIMPLEMENTED = range(8)
-            
-    def __init__(self, version=-1):
+
+    def __init__(self, version=-1, **kw):
         """ Create a game file record based upon the given file version
         number. The file version is first found in a HeaderMatchEntry
-        object. The version needs to be propogated to all other game 
+        object. The version needs to be propogated to all other game
         file objects within the same archive.
         """
-        self.EntryType = -1
-        self.Record = None
-        self.Version = version
+        defaults = {
+            'Name': 'GameFileRecord',
+            'EntryType': -1,
+            'Record': None,
+            'Version': version
+            }
+        super(GameFileRecord, self).__init__(defaults, **kw)
 
-#    def __str__(self):
-#        return str(self.todict())
+    def __setattr__(self, key, value):
+        self[key] = value
 
-#    def __repr__(self):
-#        return str(self.todict())
+    def __getattr__(self, key):
+       return self[key]
 
     def fromstream(self, stream):
         # Read the header. First 8 bytes are unused. 9th byte is record type
@@ -782,18 +764,18 @@ class GameFileRecord(dict):
         # If we catch a struct.error we have hit the EOF.
         startpos = stream.tell()
         try:
-            unpacked_data = _struct.unpack('<8xB', 
+            unpacked_data = _struct.unpack('<8xB',
                                            stream.read(self.__SIZEOFSRHDR))
         except _struct.error:
             return None
         self.EntryType = unpacked_data[0]
 
-        # Back up to the beginning of the record after getting the record 
-        # type and feed the entire stream back into the corresponding 
-        # record object. 
+        # Back up to the beginning of the record after getting the record
+        # type and feed the entire stream back into the corresponding
+        # record object.
         stream.seek(-self.__SIZEOFSRHDR, _os.SEEK_CUR)
 
-        # Using the appropriate class, read the data stream 
+        # Using the appropriate class, read the data stream
         self.Record = self.__REC_CLASSES[self.EntryType]()
         self.Record.Version = self.Version
         self.Record.fromstream(stream)
@@ -805,79 +787,90 @@ class GameFileRecord(dict):
 
         return self.Record
 
-    def todict(self):
-        return {'entrytype': self.entrytype, 'record': self.record}
 
-
-class RolloutContextEntry(object):
+class RolloutContextEntry(dict):
 
     SIZEOFREC = 2184
 
-    def __init__(self):
-        self.name = 'Rollout'
-        self.entrytype = RolloutFileRecord.ROLLOUTCONTEXT
-        self.truncated = False
-        self.errorlimited = False
-        self.truncate = 0
-        self.minroll = 0
-        self.errorlimit = 0.0
-        self.maxroll = 0
-        self.level1 = 0
-        self.level2 = 0
-        self.levelcut = 0
-        self.variance = False
-        self.cubeless = False
-        self.time = False
-        self.level1c = 0
-        self.level2c = 0
-        self.timelimit = 0
-        self.truncatebo = 0
-        self.randomseed = 0
-        self.randomseedi = 0
-        self.rollboth = False
-        self.searchinterval = 0.0
-        self.met = 0
-        self.firstroll = False
-        self.dodouble = False
-        self.extent = False
-        self.rolled = 0
-        self.doublefirst = False
-        self.sum1 = None
-        self.sumsquare1 = None
-        self.sum2 = None
-        self.sumsquare2 = None
-        self.stdev1 = None
-        self.stdev2 = None
-        self.rolledd = None
-        self.error1 = 0.0
-        self.error2 = 0.0
-        self.result1 = None
-        self.result2 = None
-        self.mwc1 = 0.0 
-        self.mwc2 = 0.0 
-        self.prevlevel = 0
-        self.preveval = None
-        self.prevnd = 0.0
-        self.prevd = 0.0
-        self.duration = 0.0
-        self.leveltrunc = 0
-        self.rolled2 = 0
-        self.multiplemin = 0
-        self.multiplestopall = False
-        self.multiplestopone = False
-        self.multiplestopallvalue = 0.0
-        self.multiplestoponevalue = 0.0
-        self.astake = False
-        self.rotation = 0
-        self.userinterrupted = False
-        self.vermaj = 0
-        self.vermin = 0
-        
-    def __str__(self):
-        return str(self.todict())
-
-    def __repr__(self):
-        return str(self)
+    def __init__(self, **kw):
+        defaults = {
+            'Name': 'Rollout',
+            'EntryType': RolloutFileRecord.ROLLOUTCONTEXT,
+            'Truncated': False,             # is truncated
+            'ErrorLimited': False,          # stop when CI under "ErrorLimit"
+            'Truncate': 0,                  # truncation level
+            'MinRoll': 0,                   # minimum games to roll
+            'ErrorLimit': 0.0,              # CI to stop the RO
+            'MaxRoll': 0,                   # maximum games to roll
+            'Level1': 0,                    # checker play Level used before "LevelCut"
+            'Level2': 0,                    # checker play Level used on and after "LevelCut"
+            'LevelCut': 0,                  # Cutoff for level1 and level2
+            'Variance': False,              # use variance reduction
+            'Cubeless': False,              # is a cubeless ro
+            'Time': False,                  # is time limited
+            'Level1C': 0,                   # cube Level used before "LevelCut"
+            'Level2C': 0,                   # cube Level used on and after "LevelCut"
+            'TimeLimit': 0,                 # limit in time (min)
+            'TruncateBO': 0,                # what do do when reaching BO db: 0=nothing; 1=?
+            'RandomSeed': 0,                # caculated seed=RandomSeedI+hashpos
+            'RandomSeedI': 0,               # used entered seed
+            'RollBoth': False,              # roll both line (ND and D/T)
+            'SearchInterval': 0.0,          # Search interval used (1=normal, 1.5=large, 2=huge, 4=gigantic)
+            'met': 0,                       # unused
+            'FirstRoll': False,             # is it a first roll rollout
+            'DoDouble': False,              # roll both line (ND and D/T) in multiple rollout
+            'Extent': False,                # if the ro is extended
+                                            
+                                            
+            'Rolled': 0,                    # game rolled
+            'DoubleFirst': False,           # a double happens immediatly.
+                                            
+            'Sum1': None,                   # sum of equities for all 36 1st roll
+            'SumSquare1': None,             # sum of square equities for all 36 1st roll
+            'Sum2': None,                   # D/T sum of equities for all 36 1st roll
+            'SumSquare2': None,             # D/T sum of square equities for all 36 1st roll
+            'Stdev1': None,                 # Standard deviation for all 36 1st roll
+            'Stdev2': None,                 # D/T Stand deviation for all 36 1st roll
+            'RolledD': None,                # number of game rolled for all 36 1st roll
+            'Error1': 0.0,                  # 95% CI
+            'Error2': 0.0,                  # D/T 95% CI
+                                            
+            'Result1': None,                # evaluation of the position
+            'Result2': None,                # D/T evaluation of the position
+            'Mwc1': 0.0,                    # ND  mwc equivalent of result1[1,6]
+            'Mwc2': 0.0,                    # D/T mwc equivalent of result2[1,6]
+                                            
+            'PrevLevel': 0,                 # store the previous analyze level (for deleting RO)
+            'PrevEval': None,               # store the previous analyze result (for deleting RO)
+            'PrevND': 0.0,                  # store the previous analyze equities (for deleting RO)
+            'PrevD': 0.0,                   
+                                            
+            'Duration': 0.0,                # duration in seconds
+                                            
+                                            
+            'LevelTrunc': 0,                # level used at truncation
+                                            
+            'Rolled2': 0,                   # D/T number of game rolled
+                                            
+            'MultipleMin': 0,               # Multiple RO minimum # of game
+            'MultipleStopAll': False,       # Multiple RO stop all if one move reach MultipleStopAllValue
+            'MultipleStopOne': False,       # Multiple RO stop one move is reach under MultipleStopOneValue
+            'MultipleStopAllValue': 0.0,    # value to stop all RO (for instance 99.9%)
+            'MultipleStopOneValue': 0.0,    # value to stop one move(for instance 0.01%)
+                                            
+            'AsTake': False,                # when running ND and D/T if AsTake is true, checker decision are made using the cube position in the D/T line
+            'Rotation': 0,                  # 0=36 dice, 1=21 dice (XG1), 2=30 dice (for 1st pos)
+            'UserInterrupted': False,       # RO was interrupted by user
+            'VerMaj': 0,                    # Major version use for the RO, currently (2.20): 2
+            'VerMin': 0                     # Minor version use for the RO, currently (2.10): 10 (no change in RO or engine between 2.10 and 2.20)
+            }
+        super(RolloutContextEntry, self).__init__(defaults, **kw)
+
+    def __setattr__(self, key, value):
+        self[key] = value
+
+    def __getattr__(self, key):
+       return self[key]
 
     def fromstream(self, stream):
         unpacked_data = _struct.unpack('<BBxxllxxxxdllllBBBxllLlllBxxx' \
@@ -885,140 +878,108 @@ class RolloutContextEntry(object):
                                        'ff7f7fffl7fllllllBBxxffBxxxlBxHH',
                                        stream.read(2174))
 
-        self.truncated = unpacked_data[0] # bool
-        self.errorlimited = unpacked_data[1] # bool
-        self.truncate = unpacked_data[2]
-        self.minroll = unpacked_data[3]
-        self.errorlimit = unpacked_data[4]
-        self.maxroll = unpacked_data[5]
-        self.level1 = unpacked_data[6]
-        self.level2 = unpacked_data[7]
-        self.levelcut = unpacked_data[8]
-        self.variance = unpacked_data[9] #bool
-        self.cubeless = unpacked_data[10] #bool
-        self.time = unpacked_data[11] #bool
-        self.level1c = unpacked_data[12]
-        self.level2c = unpacked_data[13]
-        self.timelimit = unpacked_data[14]
-        self.truncatebo = unpacked_data[15]
-        self.randomseed = unpacked_data[16]
-        self.randomseedi = unpacked_data[17]
-        self.rollboth = unpacked_data[18] #bool
-        self.searchinterval = unpacked_data[19]
+        self.Truncated = bool(unpacked_data[0])
+        self.ErrorLimited = bool(unpacked_data[1])
+        self.Truncate = unpacked_data[2]
+        self.MinRoll = unpacked_data[3]
+        self.ErrorLimit = unpacked_data[4]
+        self.MaxRoll = unpacked_data[5]
+        self.Level1 = unpacked_data[6]
+        self.Level2 = unpacked_data[7]
+        self.LevelCut = unpacked_data[8]
+        self.Variance = bool(unpacked_data[9])
+        self.Cubeless = bool(unpacked_data[10])
+        self.Time = bool(unpacked_data[11])
+        self.Level1C = unpacked_data[12]
+        self.Level2C = unpacked_data[13]
+        self.TimeLimit = unpacked_data[14]
+        self.TruncateBO = unpacked_data[15]
+        self.RandomSeed = unpacked_data[16]
+        self.RandomSeedI = unpacked_data[17]
+        self.RollBoth = bool(unpacked_data[18])
+        self.SearchInterval = unpacked_data[19]
         self.met = unpacked_data[20]
-        self.firstroll = unpacked_data[21] #bool
-        self.dodouble = unpacked_data[22] #bool
-        self.extent = unpacked_data[23] #bool
-        self.rolled = unpacked_data[24]
-        self.doublefirst = unpacked_data[25] #bool
-        self.sum1 = unpacked_data[26:63]
-        self.sumsquare1 = unpacked_data[63:100]
-        self.sum2 = unpacked_data[100:137]
-        self.sumsquare2 = unpacked_data[137:174]
-        self.stdev1 = unpacked_data[174:211]
-        self.stdev2 = unpacked_data[211:248]
-        self.rolledd = unpacked_data[248:285]
-        self.error1 = unpacked_data[285]
-        self.error2 = unpacked_data[286]
-        self.result1 = unpacked_data[287:294]
-        self.result2 = unpacked_data[294:301]
-        self.mwc1 = unpacked_data[301]
-        self.mwc2 = unpacked_data[302]
-        self.prevlevel = unpacked_data[303]
-        self.preveval = unpacked_data[304:311]
-        self.prevnd = unpacked_data[311]
-        self.prevd = unpacked_data[312]
-        self.duration = unpacked_data[313]
-        self.leveltrunc = unpacked_data[314]
-        self.rolled2 = unpacked_data[315]
-        self.multiplemin = unpacked_data[316]
-        self.multiplestopall = bool(unpacked_data[317])
-        self.multiplestopone = bool(unpacked_data[318])
-        self.multiplestopallvalue = unpacked_data[319]
-        self.multiplestoponevalue = unpacked_data[320]
-        self.astake = bool(unpacked_data[321])
-        self.rotation = unpacked_data[322]
-        self.userinterrupted = bool(unpacked_data[323])
-        self.vermaj = unpacked_data[324]
-        self.vermin = unpacked_data[325]
+        self.FirstRoll = bool(unpacked_data[21])
+        self.DoDouble = bool(unpacked_data[22])
+        self.Extent = bool(unpacked_data[23])
+        self.Rolled = unpacked_data[24]
+        self.DoubleFirst = bool(unpacked_data[25])
+        self.Sum1 = unpacked_data[26:63]
+        self.SumSquare1 = unpacked_data[63:100]
+        self.Sum2 = unpacked_data[100:137]
+        self.SumSquare2 = unpacked_data[137:174]
+        self.Stdev1 = unpacked_data[174:211]
+        self.Stdev2 = unpacked_data[211:248]
+        self.RolledD = unpacked_data[248:285]
+        self.Error1 = unpacked_data[285]
+        self.Error2 = unpacked_data[286]
+        self.Result1 = unpacked_data[287:294]
+        self.Result2 = unpacked_data[294:301]
+        self.Mwc1 = unpacked_data[301]
+        self.Mwc2 = unpacked_data[302]
+        self.PrevLevel = unpacked_data[303]
+        self.PrevEval = unpacked_data[304:311]
+        self.PrevND = unpacked_data[311]
+        self.PrevD = unpacked_data[312]
+        self.Duration = unpacked_data[313]
+        self.LevelTrunc = unpacked_data[314]
+        self.Rolled2 = unpacked_data[315]
+        self.MultipleMin = unpacked_data[316]
+        self.MultipleStopAll = bool(unpacked_data[317])
+        self.MultipleStopOne = bool(unpacked_data[318])
+        self.MultipleStopAllValue = unpacked_data[319]
+        self.MultipleStopOneValue = unpacked_data[320]
+        self.AsTake = bool(unpacked_data[321])
+        self.Rotation = unpacked_data[322]
+        self.UserInterrupted = bool(unpacked_data[323])
+        self.VerMaj = unpacked_data[324]
+        self.VerMin = unpacked_data[325]
 
         return self
 
-    def todict(self):
-        return {'truncated': self.truncated, 'errorlimited': self.errorlimited,
-                'truncate': self.truncate, 'minroll': self.minroll,
-                'errorlimit': self.errorlimit, 'maxroll': self.maxroll,
-                'level1': self.level1, 'level2': self.level2,
-                'levelcut': self.levelcut, 'variance': self.variance,
-                'cubeless': self.cubeless, 'time': self.time,
-                'level1c': self.level1c, 'level2c': self.level2c,
-                'timelimit': self.timelimit, 'truncatebo': self.truncatebo,
-                'randomseed': self.randomseed, 'randomseedi': self.randomseedi,
-                'rollboth': self.rollboth, 
-                'searchinterval': self.searchinterval, 'met': self.met,
-                'firstroll': self.firstroll, 'dodouble': self.dodouble,
-                'extent': self.extent, 'rolled': self.rolled,
-                'doublefirst': self.doublefirst, 'sum1': self.sum1,
-                'sumsquare1': self.sumsquare1, 'sum2': self.sum2,
-                'sumsquare2': self.sumsquare2, 'stdev1': self.stdev1,
-                'stdev2': self.stdev2, 'rolledd': self.rolledd,
-                'error1': self.error1, 'error2': self.error2,
-                'result1': self.result1, 'result2': self.result2,
-                'mwc1': self.mwc1, 'mwc2': self.mwc2,
-                'prevlevel': self.prevlevel, 'preveval': self.preveval,
-                'prevnd': self.prevnd, 'prevd': self.prevd,
-                'duration': self.duration, 'leveltrunc': self.leveltrunc,
-                'rolled2': self.rolled2, 'multiplemin': self.multiplemin,
-                'multiplestopall': self.multiplestopall,
-                'multiplestopone': self.multiplestopone,
-                'multiplestopallvalue': self.multiplestopallvalue,
-                'multiplestoponevalue': self.multiplestoponevalue,
-                'astake': self.astake, 'rotation': self.rotation,
-                'userinterrupted': self.userinterrupted,
-                'vermaj': self.vermaj, 'vermin': self.vermin}
-
-
-class RolloutFileRecord(object):
+
+class RolloutFileRecord(dict):
 
     ROLLOUTCONTEXT = 0
-    
-    def __init__(self, version=-1):
+
+    def __init__(self, version=-1, **kw):
         """ Create a game file record based upon the given file version
         number. The file version is first found in a HeaderMatchEntry
-        object. The version needs to be propogated to all other game 
+        object. The version needs to be propogated to all other game
         file objects within the same archive.
         """
-        self.entrytype = 0
-        self.record = None
-        self.version = version
+        defaults = {
+            'Name': 'RolloutFileRecord',
+            'EntryType': 0,
+            'Record': None,
+            'Version': version
+            }
+        super(RolloutFileRecord, self).__init__(defaults, **kw)
 
-    def __str__(self):
-        return str(self.todict())
+    def __setattr__(self, key, value):
+        self[key] = value
 
-    def __repr__(self):
-        return str(self.todict())
+    def __getattr__(self, key):
+       return self[key]
 
     def fromstream(self, stream):
         # If we are at EOF then return
-        if len(stream.read(1)) < 1:
+        if len(stream.read(1)) <= 0:
             return None
 
         stream.seek(-1, _os.SEEK_CUR)
         startpos = stream.tell()
 
-        # Using the appropriate class, read the data stream 
-        self.record = RolloutContextEntry()
-        self.record.version = self.version
-        self.record.fromstream(stream)
+        # Using the appropriate class, read the data stream
+        self.Record = RolloutContextEntry()
+        self.Record.Version = self.Version
+        self.Record.fromstream(stream)
         realrecsize = stream.tell() - startpos
         # Each record is actually 2184 bytes long. We need to advance past
         # the unused filler data to be at the start of the next record
-        stream.seek(self.record.SIZEOFREC - realrecsize, _os.SEEK_CUR)
-
-        return self.record
+        stream.seek(self.Record.SIZEOFREC - realrecsize, _os.SEEK_CUR)
 
-    def todict(self):
-        return {'entrytype': self.entrytype, 'record': self.record}
+        return self.Record
 
 
 if __name__ == '__main__':