# .------------------------------------------[ icedragon@quickfox.org ]--. # # | libfs2.py | FS2 File Format Manipulation Module (c) 2006 - IceDragon | # # |-[1.0]----------------------------------------------------------------| # # | This module grants you the ability to manipulate an FS2 file format | # # | used in Furcadia on a high level. The module can load data from an | # # | FS2 file into memory and save the data stored in classes back into | # # | an FS2 file. | # # | | # # | Note on encryption: | # # | The algorithm is not included in this module by default, so you will | # # | have to code it by yourself! The function designated to this will do | # # | nothing. | # # `----------------------------------------------------------------------' # ###--# Imports #--### import struct from array import * ###--# Constants #--### FLAG_ENCODED = 0x00000001 # The FS2 file data is encoded. ###--# Classes #--### class FS2_File: """This class contains information about the FS2 file itself. Variables: fileName = '' - File name/path of the file we've last loaded or saved. fileSize = 0 - File size in bytes, last acquired during a load/save. regShapes = 0 - Amount of shapes registered in the file header. flags = 0 - Flags for this file: FLAG_ENCODED - Encoded file. isEncoded = False - Indicates whether the file is/should be encoded. shapes = [] - List of shapes within this file. tail = '' - Binary remainder after theoretical end of FS2 file (should be empty). Functions: loadFile ( filename ) - Parse an FS2 file and load its contents into memory. saveFile ( filename ) - Save the data in memory into an FS2 file (filename is optional).""" ## Variables ## fileName = '' # Filename of the patch we've loaded/saving. fileSize = 0 # Size of the file we're representing. regShapes = 0 # Amount of shapes registered in the header. flags = 0 # Flags set on this file (only one available so far) isEncoded = False # File is encoded. shapes = [] # Array that will contain the shapes. tail = '' # Remaining data after theoretical End of File. ## Constructor ## def __init__ ( self, filename = '' ): """This function initializes the class.""" if filename != '': self.loadFile( filename ) ## Functions ## def loadFile ( self, filename ): """Load data from a specific file into memory, filling the class.""" fd = open( filename, 'rb' ) self.fileName = filename # Reading file header & initializing data. buffer = fd.read( 16 ) if buffer[:8] != "FSH2.002": raise Exception( "The file `" + filename + "` is not of an FS2 format." ) self.regShapes = struct.unpack( 'H', buffer[8:10] )[0] self.flags = struct.unpack( 'I', buffer[12:16] )[0] self.isEncoded = bool( self.flags & FLAG_ENCODED ) # Starting to read shapes. eof = False for i in range( self.regShapes ): # Reading shape header. header = fd.read(6) if len(header) < 6: print "[libfs2] WARNING: Unable to read shape header - only %d/6 bytes read!" % len(header) break else: (width, height, offset_x, offset_y, shpReplace) = struct.unpack( 'BBbbH', header ) # Reading shape data. size = width * height data = '' while size > 0: buffer = fd.read(size) # EOF Check # if len(buffer) == 0: print "[libfs2] WARNING: Unexpected EOF at shape %d!", i eof = True break size -= len( buffer ) data += buffer # Forming a shape from the collected data. shape = FS2_Shape() shape.width = width shape.height = height shape.offset = (offset_x, offset_y) shape.shpReplace = shpReplace shape.data.fromstring( data ) # Decrypting if necessary. if self.isEncoded: shape.cryptData() self.shapes.append( shape ) if eof: break # Saving data remains, filesize and closing file. self.tail = fd.read() self.fileSize = fd.tell() fd.close() def saveFile ( self, filename = '' ): """Save all the data into an FS2 file.""" # If no filename specified and we have one in memory, use it instead. if filename == '': if self.fileName != '': filename = self.fileName else: raise Exception( "Unable to save FS2 file - no destination specified." ) # Open file. fd = open( filename, 'wb' ) # Storing header. self.regShapes = len( self.shapes ) if self.isEncoded: self.flags |= FLAG_ENCODED elif bool(self.flags & FLAG_ENCODED): self.flags ^= FLAG_ENCODED fd.write( 'FSH2.002' + struct.pack( 'HHI', self.regShapes, 0, self.flags ) ) # Storing images. for shape in self.shapes: if self.isEncoded: shape.cryptData() fd.write( shape.getChunk() ) # Storing tail, saving new filesize and closing file. fd.write( self.tail ) self.fileSize = fd.tell() fd.close() class FS2_Shape: """This class is responsible for an individual shape within an FS2 file. Variables: width = 0 - Image width - byte height = 0 - Image height - byte offset = (0,0) - Image offset (X,Y) - signed byte. shpReplace = 0 - Shape ID to replace - unsigned word. data = array('B') - Graphical data - array of bytes. Functions: initData ( dimensions ) - Initialize the data array to specific dimensions - (W,H). getHeader () - Returns the binary header of 6 bytes for the FS2 shape. getChunk () - Returns the binary chunk including header and data for the FS2 shape. getPixel ( coords ) - Returns the color ID at specific coordinates (X,Y). setPixel ( coords, color) - Sets the color ID at specific coordiantes (X,Y). cryptData () - Performs data encryption/decryption on this shape.""" ## Variables ## width = 0 # Image width. height = 0 # Image height. offset = (0,0) # Image offset (X,Y). shpReplace = 0 # Shape ID to replace. data = False # Data array to contain graphical data. ## Constructor ## def __init__ ( self, dimensions = (0,0) ): """This function will initialize the data and dimensions, should they be specified.""" self.data = array('B') size = dimensions[0] * dimensions[1] if size > 0: self.initData( dimensions ) ## Functions ## def initData ( self, dimensions = (0,0) ): """This function initializes the Width/Height variables and initializes the data array.""" (self.width, self.height) = dimensions size = self.width * self.height self.data = array('B') if size > 0: self.data.fromlist( [0]*size ) def getHeader ( self ): """This function forms a shape header out of the information it currently has.""" return struct.pack( 'BBbbH', self.width, self.height, self.offset[0], self.offset[1], self.shpReplace ) def getChunk ( self ): """This function returns the entire binary chunk (header + data) of the shape.""" return self.getHeader() + self.data.tostring() def getPixel ( self, coords ): """Returns the color ID at specific coordinates in the image data.""" (x,y) = ( coords[0], self.width - 1 - coords[1] ) return self.data[ self.width * y + x ] def setPixel ( self, coords, color ): """Sets the color ID at specific coordinates in the image data.""" (x,y) = ( coords[0], self.width - 1 - coords[1] ) self.data[ self.width * y + x ] = color def cryptData ( self ): """Performs data encryption/decryption on the image data.""" ### TODO: Code the encryption algo here ### ###--# End of File #--###