#!/usr/bin/python ###########################[ icedragon@dolphinwave.org ]### # chkfsh.py # Furcadia Shape Format [FSH] Diagnostic Tool # ########################################################### # This script is designed to perform various kinds of # # integrity checks on single or multiple FSH files. The # # checks include: # # - File underloading (reg_shapes > real_shapes). # # - Misreported chunk sizes (header vs. W*H+4). # # - File overloading (junk at end of last shape). # # - Footer presense at EOF (FSH1.002). # # - Use of unused color IDs (1-9 & 246-255) in shapes. # # - Misreported shape dimensions (0xN / Nx0). # ########################################################### ##################### ###--# Imports #--### ##################### from glob import glob from struct import * from sys import exit,argv from time import ctime ########################### ###--# Configuration #--### ########################### config = {} counter = {} fd = '' # Should we dump the script output to a logfile? config['create_log'] = True # What should the logfile be called? (Ignore if previous is FALSE) config['log_filename'] = 'chkfsh.log' def PutLog( text ): global fd print text if config['create_log']: if fd == '': fd = open( config['log_filename'], 'w' ) fd.write( text + '\n' ) ############################# ###--# File Info Class #--### ############################# class FSHFile: ###--# Variables #--### filename = '' # Filename of the entry. reg_shapes = 0 # Registered amount of shapes. real_shapes = 0 # Existing amount of shapes. junk_bytes = 0 # Amount of junk bytes at the end. has_footer = False # Footer at the end of file. underloaded = False # File is underloaded. chunk_sizes = [] # Array of chunk sizes registered in the header. bad_col_shapes = {} # Shapes with bad colors. [shp_num: [col1,col2,col3,...]] bad_chnk_shapes = {} # Shapes with bad chunk data. [shp_num: (regged,real)] bad_dim_shapes = {} # Shapes with bad dimensions. [shp_num: (width,height)] ###--# Functions #--### def __init__( self, filename = '' ): self.filename = '' self.reg_shapes = 0 self.real_shapes = 0 self.junk_bytes = 0 self.has_footer = 0 self.underloaded = 0 self.chunk_sizes = [] self.bad_col_shapes = {} self.bad_chnk_shapes = {} self.bad_dim_shapes = {} if filename != '': self.ReadFile( filename ) else: pass def ReadFile( self, filename ): fsh = open( filename, 'rb' ) self.filename = filename.split('\\')[-1] # Getting registered shape amount. self.reg_shapes = unpack( 'H', fsh.read(2) )[0] # Getting chunk sizes registered in the header. for i in range( self.reg_shapes ): self.chunk_sizes.append( unpack( 'H', fsh.read(2) )[0] ) # Getting shapes. while True: buffer = fsh.read(4) if len( buffer ) == 4: width,height,offset_x,offset_y = unpack( 'BBbb', buffer ) else: print "WARNING: %d bytes read, %d bytes expected! Shape %d" % ( len(buffer), 4, self.real_shapes ) self.underloaded = True break # Checking chunk size vs. registered one. if (width*height+4) != self.chunk_sizes[self.real_shapes]: self.bad_chnk_shapes[self.real_shapes] = ( self.chunk_sizes[self.real_shapes], (width*height+4) ) # Checking bad dimensions. if (height*width) == 0: self.bad_dim_shapes[self.real_shapes] = ( width,height ) # Scanning data. buffer = fsh.read( height * width ) for c in buffer: color = ord(c) # Checking for illegal colors. if (color > 0) and (color < 10) or (color > 245): if self.real_shapes not in self.bad_col_shapes: self.bad_col_shapes[self.real_shapes] = [color] else: bcs = self.bad_col_shapes[self.real_shapes] if color not in bcs: self.bad_col_shapes[self.real_shapes].append( color ) self.real_shapes += 1 if len(buffer) < height*width: print "WARNING: %d bytes read, %d bytes expected!" % (len(buffer), (height*width)) self.underloaded = True break # Is it the last shape we passed? if self.real_shapes == self.reg_shapes: marker = fsh.tell() # Checking footer. if fsh.read(8) == "FSH1.002": self.has_footer = True marker += 8 fsh.seek( 0,2 ) bytes_left = fsh.tell() - marker if bytes_left > 0: self.junk_bytes = bytes_left break fsh.close() def GenReport ( self ): faults = 0 buffer = '---[ %s ]---------------\n' % self.filename buffer += 'Shape QTY: %d (Real: %d)\n' % ( self.reg_shapes, self.real_shapes ) buffer += 'Footer: %s\n' % self.has_footer buffer += 'Structure: ' if self.underloaded: buffer += 'UNDERLOADED\n' elif self.junk_bytes > 0: buffer += 'OVERLOADED\n' else: buffer += 'OK\n' buffer += 'Misc. faults:\n' ### Shape QTY Mismatch ### if self.reg_shapes != self.real_shapes: buffer += ' - Shape quantity mismatch: %d registered, %d found.\n' % ( self.reg_shapes, self.real_shapes ) faults += 1 ### Misreported Chunk Sizes ### if len( self.bad_chnk_shapes ) > 0: buffer += ' - Badly reported chunk shapes (%d found):\n' % len( self.bad_chnk_shapes ) faults += 1 for x in self.bad_chnk_shapes.keys(): buffer += ' + Shape %d: %d bytes registered, %d bytes detected.\n' % ( x, self.bad_chnk_shapes[x][0], self.bad_chnk_shapes[x][1] ) ### Junk at end of file ### if self.junk_bytes > 0: buffer += ' - Junk at end of file: %d bytes.\n' % self.junk_bytes faults += 1 ### Illegal color usage ### if len( self.bad_col_shapes ) > 0: buffer += ' - Illegal color use in shapes (%d shapes affected):\n' % len( self.bad_col_shapes ) faults += 1 for x in self.bad_col_shapes.keys(): buffer += ' + Shape %d: %s\n' % ( x, self.bad_col_shapes[x] ) ### Null dimensions ### if len( self.bad_dim_shapes ) > 0: buffer += ' - Bad dimension specifications (%d shapes affected):\n' % len( self.bad_dim_shapes ) faults += 1 for x in self.bad_dim_shapes.keys(): buffer += ' + Shape %d: %dx%d\n' % ( x, self.bad_dim_shapes[x][0], self.bad_dim_shapes[x][1] ) if faults > 0: pass buffer += ' > TOTAL: %d\n' % faults else: buffer += ' None\n' # if ( len( argv ) < 3 ) or ( faults > 0 ): PutLog( buffer ) ################################ ###--# Checking Arguments #--### ################################ if len( argv ) < 2: print "Syntax: %s - Scan an *.fsh filename." print " %s [p] - Scan a directory with *.fsh files." exit(0) buffer = '### Begin FSH Check Procedure ###\n\n' buffer += 'Timestamp: %s\n' % ctime() if len( argv ) < 3: buffer += 'Filename: %s\n' % argv[1] PutLog( buffer ) fsh = FSHFile( argv[1] ) fsh.GenReport() del fsh else: buffer += 'Path: %s\n' % argv[2] PutLog( buffer ) for filename in glob( argv[2] + '*.fsh' ): fsh = FSHFile( filename ) fsh.GenReport() del fsh PutLog( '### End of FSH Check Procedure ###' ) fd.close() exit(0)