diff options
Diffstat (limited to '')
| -rw-r--r-- | Puzzle+/.DS_Store | bin | 0 -> 6148 bytes | |||
| -rwxr-xr-x | Puzzle+/archive.py | 205 | ||||
| -rwxr-xr-x | Puzzle+/common.py | 364 | ||||
| -rwxr-xr-x | Puzzle+/license.txt | 339 | ||||
| -rw-r--r-- | Puzzle+/nsmblib-0.5a.zip | bin | 0 -> 9056 bytes | |||
| -rwxr-xr-x | Puzzle+/puzzle+.py | 1858 | ||||
| -rwxr-xr-x | Puzzle+/windows_build.py | 81 | 
7 files changed, 2847 insertions, 0 deletions
| diff --git a/Puzzle+/.DS_Store b/Puzzle+/.DS_StoreBinary files differ new file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/Puzzle+/.DS_Store diff --git a/Puzzle+/archive.py b/Puzzle+/archive.py new file mode 100755 index 0000000..b0495f5 --- /dev/null +++ b/Puzzle+/archive.py @@ -0,0 +1,205 @@ +from common import *
 +
 +
 +class U8(WiiArchive):
 +	class U8Header(Struct):
 +		__endian__ = Struct.BE
 +		def __format__(self):
 +			self.tag = Struct.string(4)
 +			self.rootnode_offset = Struct.uint32
 +			self.header_size = Struct.uint32
 +			self.data_offset = Struct.uint32
 +			self.zeroes = Struct.string(16)
 +	class U8Node(Struct):
 +		__endian__ = Struct.BE
 +		def __format__(self):
 +			self.type = Struct.uint16
 +			self.name_offset = Struct.uint16
 +			self.data_offset = Struct.uint32
 +			self.size = Struct.uint32
 +	def __init__(self):
 +		self.files = []
 +	def _dump(self):
 +		header = self.U8Header()
 +		rootnode = self.U8Node()
 +		
 +		# constants
 +		header.tag = "U\xAA8-"
 +		header.rootnode_offset = 0x20
 +		header.zeroes = "\x00" * 16
 +		rootnode.type = 0x0100
 +		
 +		nodes = []
 +		strings = '\x00'
 +		data = ''
 +		
 +		for item, value in self.files:
 +			node = self.U8Node()
 +			
 +			recursion = item.count('/')
 +			if(recursion < 0):
 +				recursion = 0
 +			name = item[item.rfind('/') + 1:]
 +			
 +			node.name_offset = len(strings)
 +			strings += name + '\x00'
 +		
 +			if(value == None): # directory
 +				node.type = 0x0100
 +				node.data_offset = recursion
 +				
 +				node.size = len(nodes) + 1
 +				for one, two in self.files:
 +					if(one[:len(item)] == item): # find nodes in the folder
 +						node.size += 1
 +			else: # file
 +				node.type = 0x0000
 +				node.data_offset = len(data)
 +				#print "before: " + str(len(data))
 +				data += value + ('\x00' * (align(len(value), 32) - len(value))) # 32 seems to work best for fuzzyness? I'm still really not sure
 +				#print "after: " + str(len(data))
 +				node.size = len(value)
 +				#print "sz: " + str(len(value))
 +			nodes.append(node)
 +			
 +		header.header_size = ((len(nodes) + 1) * len(rootnode)) + len(strings)
 +		header.data_offset = align(header.header_size + header.rootnode_offset, 64)
 +		rootnode.size = len(nodes) + 1
 +		
 +		for i in range(len(nodes)):
 +			if(nodes[i].type == 0x0000):
 +				nodes[i].data_offset += header.data_offset
 +						
 +		fd = ''
 +		fd += header.pack()
 +		fd += rootnode.pack()
 +		for node in nodes:
 +			fd += node.pack()
 +		fd += strings
 +		fd += "\x00" * (header.data_offset - header.rootnode_offset - header.header_size)
 +		fd += data
 +		
 +		return fd
 +	def _dumpDir(self, dir):
 +		if(not os.path.isdir(dir)):
 +			os.mkdir(dir)
 +		old = os.getcwd()
 +		os.chdir(dir)
 +		for item, data in self.files:
 +			if(data == None):
 +				if(not os.path.isdir(item)):
 +					os.mkdir(item)
 +			else:
 +				open(item, "wb").write(data)
 +		os.chdir(old)
 +	def _loadDir(self, dir):
 +		try:
 +			self._tmpPath += ''
 +		except:
 +			self._tmpPath = ''
 +		old = os.getcwd()
 +		os.chdir(dir)
 +		entries = os.listdir(".")
 +		for entry in entries:
 +			if(os.path.isdir(entry)):
 +				self.files.append((self._tmpPath + entry, None))
 +				self._tmpPath += entry + '/'
 +				self._loadDir(entry)
 +			elif(os.path.isfile(entry)):
 +				data = open(entry, "rb").read()
 +				self.files.append((self._tmpPath + entry, data))
 +		os.chdir(old)
 +		self._tmpPath = self._tmpPath[:self._tmpPath.find('/') + 1]
 +	def _load(self, data):
 +		offset = 0
 +		
 +		for i in range(len(data)):
 +			header = self.U8Header()
 +			header.unpack(data[offset:offset + len(header)])
 +			if(header.tag == "U\xAA8-"):
 +				break
 +			data = data[1:]
 +		offset += len(header)
 +		offset = header.rootnode_offset
 +		
 +		#print header.rootnode_offset
 +		#print header.header_size
 +		#print header.data_offset
 +		
 +		rootnode = self.U8Node()
 +		rootnode.unpack(data[offset:offset + len(rootnode)])
 +		offset += len(rootnode)
 +		
 +		nodes = []
 +		for i in range(rootnode.size - 1):
 +			node = self.U8Node()
 +			node.unpack(data[offset:offset + len(node)])
 +			offset += len(node)
 +			nodes.append(node)
 +		
 +		strings = data[offset:offset + header.data_offset - len(header) - (len(rootnode) * rootnode.size)]
 +		offset += len(strings)
 +		
 +		recursion = [rootnode.size]
 +		recursiondir = []
 +		counter = 0
 +		for node in nodes:
 +			counter += 1
 +			name = strings[node.name_offset:].split('\0', 1)[0]
 +			
 +			if(node.type == 0x0100): # folder
 +				recursion.append(node.size)
 +				recursiondir.append(name)
 +				#assert len(recursion) == node.data_offset + 2 # haxx
 +				self.files.append(('/'.join(recursiondir), None))
 +				
 +				#print "Dir: " + name
 +			elif(node.type == 0): # file
 +				self.files.append(('/'.join(recursiondir) + '/' + name, data[node.data_offset:node.data_offset + node.size]))
 +				offset += node.size
 +				
 +				#print "File: " + name
 +			else: # unknown type -- wtf?
 +				pass
 +			
 +			#print "Data Offset: " + str(node.data_offset)
 +			#print "Size: " + str(node.size)	
 +			#print "Name Offset: " + str(node.name_offset)
 +			#print ""
 +			
 +			sz = recursion.pop()
 +			if(sz != counter + 1):
 +				recursion.append(sz)
 +			else:
 +				recursiondir.pop()
 +	def __str__(self):
 +		ret = ''
 +		for key, value in self.files:
 +			name = key[key.rfind('/') + 1:]
 +			recursion = key.count('/')
 +			ret += '  ' * recursion
 +			if(value == None):
 +				ret += '[' + name + ']'
 +			else:
 +				ret += name
 +			ret += '\n'
 +		return ret
 +	def __getitem__(self, key):
 +		for item, val in self.files:
 +			if(item == key):
 +				if(val != None):
 +					return val
 +				else:
 +					ret = []
 +					for item2, val2 in self.files:
 +						if(item2.find(item) == 0):
 +							ret.append(item2[len(item) + 1:])
 +					return ret[1:]
 +		raise KeyError
 +	def __setitem__(self, key, val):
 +		for i in range(len(self.files)):
 +			if(self.files[i][0] == key):
 +				self.files[i] = (self.files[i][0], val)
 +				return
 +		self.files.append((key, val))
 +		
 diff --git a/Puzzle+/common.py b/Puzzle+/common.py new file mode 100755 index 0000000..792acec --- /dev/null +++ b/Puzzle+/common.py @@ -0,0 +1,364 @@ +import os.path, struct, sys
 +
 +
 +class StructType(tuple):
 +	def __getitem__(self, value):
 +		return [self] * value
 +	def __call__(self, value, endian='<'):
 +		if isinstance(value, str):
 +			return struct.unpack(endian + tuple.__getitem__(self, 0), value[:tuple.__getitem__(self, 1)])[0]
 +		else:
 +			return struct.pack(endian + tuple.__getitem__(self, 0), value)
 +
 +class StructException(Exception):
 +	pass
 +
 +class Struct(object):
 +	__slots__ = ('__attrs__', '__baked__', '__defs__', '__endian__', '__next__', '__sizes__', '__values__')
 +	int8 = StructType(('b', 1))
 +	uint8 = StructType(('B', 1))
 +	
 +	int16 = StructType(('h', 2))
 +	uint16 = StructType(('H', 2))
 +	
 +	int32 = StructType(('l', 4))
 +	uint32 = StructType(('L', 4))
 +	
 +	int64 = StructType(('q', 8))
 +	uint64 = StructType(('Q', 8))
 +	
 +	float = StructType(('f', 4))
 +
 +	def string(cls, len, offset=0, encoding=None, stripNulls=False, value=''):
 +		return StructType(('string', (len, offset, encoding, stripNulls, value)))
 +	string = classmethod(string)
 +	
 +	LE = '<'
 +	BE = '>'
 +	__endian__ = '<'
 +	
 +	def __init__(self, func=None, unpack=None, **kwargs):
 +		self.__defs__ = []
 +		self.__sizes__ = []
 +		self.__attrs__ = []
 +		self.__values__ = {}
 +		self.__next__ = True
 +		self.__baked__ = False
 +		
 +		if func == None:
 +			self.__format__()
 +		else:
 +			sys.settrace(self.__trace__)
 +			func()
 +			for name in func.func_code.co_varnames:
 +				value = self.__frame__.f_locals[name]
 +				self.__setattr__(name, value)
 +		
 +		self.__baked__ = True
 +		
 +		if unpack != None:
 +			if isinstance(unpack, tuple):
 +				self.unpack(*unpack)
 +			else:
 +				self.unpack(unpack)
 +		
 +		if len(kwargs):
 +			for name in kwargs:
 +				self.__values__[name] = kwargs[name]
 +	
 +	def __trace__(self, frame, event, arg):
 +		self.__frame__ = frame
 +		sys.settrace(None)
 +	
 +	def __setattr__(self, name, value):
 +		if name in self.__slots__:
 +			return object.__setattr__(self, name, value)
 +		
 +		if self.__baked__ == False:
 +			if not isinstance(value, list):
 +				value = [value]
 +				attrname = name
 +			else:
 +				attrname = '*' + name
 +			
 +			self.__values__[name] = None
 +			
 +			for sub in value:
 +				if isinstance(sub, Struct):
 +					sub = sub.__class__
 +				try:
 +					if issubclass(sub, Struct):
 +						sub = ('struct', sub)
 +				except TypeError:
 +					pass
 +				type_, size = tuple(sub)
 +				if type_ == 'string':
 +					self.__defs__.append(Struct.string)
 +					self.__sizes__.append(size)
 +					self.__attrs__.append(attrname)
 +					self.__next__ = True
 +					
 +					if attrname[0] != '*':
 +						self.__values__[name] = size[3]
 +					elif self.__values__[name] == None:
 +						self.__values__[name] = [size[3] for val in value]
 +				elif type_ == 'struct':
 +					self.__defs__.append(Struct)
 +					self.__sizes__.append(size)
 +					self.__attrs__.append(attrname)
 +					self.__next__ = True
 +					
 +					if attrname[0] != '*':
 +						self.__values__[name] = size()
 +					elif self.__values__[name] == None:
 +						self.__values__[name] = [size() for val in value]
 +				else:
 +					if self.__next__:
 +						self.__defs__.append('')
 +						self.__sizes__.append(0)
 +						self.__attrs__.append([])
 +						self.__next__ = False
 +					
 +					self.__defs__[-1] += type_
 +					self.__sizes__[-1] += size
 +					self.__attrs__[-1].append(attrname)
 +					
 +					if attrname[0] != '*':
 +						self.__values__[name] = 0
 +					elif self.__values__[name] == None:
 +						self.__values__[name] = [0 for val in value]
 +		else:
 +			try:
 +				self.__values__[name] = value
 +			except KeyError:
 +				raise AttributeError(name)
 +	
 +	def __getattr__(self, name):
 +		if self.__baked__ == False:
 +			return name
 +		else:
 +			try:
 +				return self.__values__[name]
 +			except KeyError:
 +				raise AttributeError(name)
 +	
 +	def __len__(self):
 +		ret = 0
 +		arraypos, arrayname = None, None
 +		
 +		for i in range(len(self.__defs__)):
 +			sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
 +			
 +			if sdef == Struct.string:
 +				size, offset, encoding, stripNulls, value = size
 +				if isinstance(size, str):
 +					size = self.__values__[size] + offset
 +			elif sdef == Struct:
 +				if attrs[0] == '*':
 +					if arrayname != attrs:
 +						arrayname = attrs
 +						arraypos = 0
 +					size = len(self.__values__[attrs[1:]][arraypos])
 +				size = len(self.__values__[attrs])
 +			
 +			ret += size
 +		
 +		return ret
 +	
 +	def unpack(self, data, pos=0):
 +		for name in self.__values__:
 +			if not isinstance(self.__values__[name], Struct):
 +				self.__values__[name] = None
 +			elif self.__values__[name].__class__ == list and len(self.__values__[name]) != 0:
 +				if not isinstance(self.__values__[name][0], Struct):
 +					self.__values__[name] = None
 +		
 +		arraypos, arrayname = None, None
 +		
 +		for i in range(len(self.__defs__)):
 +			sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
 +			
 +			if sdef == Struct.string:
 +				size, offset, encoding, stripNulls, value = size
 +				if isinstance(size, str):
 +					size = self.__values__[size] + offset
 +				
 +				temp = data[pos:pos+size]
 +				if len(temp) != size:
 +					raise StructException('Expected %i byte string, got %i' % (size, len(temp)))
 +				
 +				if encoding != None:
 +					temp = temp.decode(encoding)
 +				
 +				if stripNulls:
 +					temp = temp.rstrip('\0')
 +				
 +				if attrs[0] == '*':
 +					name = attrs[1:]
 +					if self.__values__[name] == None:
 +						self.__values__[name] = []
 +					self.__values__[name].append(temp)
 +				else:
 +					self.__values__[attrs] = temp
 +				pos += size
 +			elif sdef == Struct:
 +				if attrs[0] == '*':
 +					if arrayname != attrs:
 +						arrayname = attrs
 +						arraypos = 0
 +					name = attrs[1:]
 +					self.__values__[attrs][arraypos].unpack(data, pos)
 +					pos += len(self.__values__[attrs][arraypos])
 +					arraypos += 1
 +				else:
 +					self.__values__[attrs].unpack(data, pos)
 +					pos += len(self.__values__[attrs])
 +			else:
 +				values = struct.unpack(self.__endian__+sdef, data[pos:pos+size])
 +				pos += size
 +				j = 0
 +				for name in attrs:
 +					if name[0] == '*':
 +						name = name[1:]
 +						if self.__values__[name] == None:
 +							self.__values__[name] = []
 +						self.__values__[name].append(values[j])
 +					else:
 +						self.__values__[name] = values[j]
 +					j += 1
 +		
 +		return self
 +	
 +	def pack(self):
 +		arraypos, arrayname = None, None
 +		
 +		ret = ''
 +		for i in range(len(self.__defs__)):
 +			sdef, size, attrs = self.__defs__[i], self.__sizes__[i], self.__attrs__[i]
 +			
 +			if sdef == Struct.string:
 +				size, offset, encoding, stripNulls, value = size
 +				if isinstance(size, str):
 +					size = self.__values__[size]+offset
 +				
 +				if attrs[0] == '*':
 +					if arrayname != attrs:
 +						arraypos = 0
 +						arrayname = attrs
 +					temp = self.__values__[attrs[1:]][arraypos]
 +					arraypos += 1
 +				else:
 +					temp = self.__values__[attrs]
 +				
 +				if encoding != None:
 +					temp = temp.encode(encoding)
 +				
 +				temp = temp[:size]
 +				ret += temp + ('\0' * (size - len(temp)))
 +			elif sdef == Struct:
 +				if attrs[0] == '*':
 +					if arrayname != attrs:
 +						arraypos = 0
 +						arrayname = attrs
 +					ret += self.__values__[attrs[1:]][arraypos].pack()
 +					arraypos += 1
 +				else:
 +					ret += self.__values__[attrs].pack()
 +			else:
 +				values = []
 +				for name in attrs:
 +					if name[0] == '*':
 +						if arrayname != name:
 +							arraypos = 0
 +							arrayname = name
 +						values.append(self.__values__[name[1:]][arraypos])
 +						arraypos += 1
 +					else:
 +						values.append(self.__values__[name])
 +				
 +				ret += struct.pack(self.__endian__+sdef, *values)
 +		return ret
 +	
 +	def __getitem__(self, value):
 +		return [('struct', self.__class__)] * value
 +
 +
 +class WiiObject(object):
 +	def load(cls, data, *args, **kwargs):
 +		self = cls()
 +		self._load(data, *args, **kwargs)
 +		return self
 +	load = classmethod(load)
 +
 +	def loadFile(cls, filename, *args, **kwargs):
 +		return cls.load(open(filename, "rb").read(), *args, **kwargs)
 +	loadFile = classmethod(loadFile)
 +
 +	def dump(self, *args, **kwargs):
 +		return self._dump(*args, **kwargs)
 +	def dumpFile(self, filename, *args, **kwargs):
 +		open(filename, "wb").write(self.dump(*args, **kwargs))
 +		return filename
 +
 +
 +class WiiArchive(WiiObject):
 +	def loadDir(cls, dirname):
 +		self = cls()
 +		self._loadDir(dirname)
 +		return self
 +	loadDir = classmethod(loadDir)
 +		
 +	def dumpDir(self, dirname):
 +		if(not os.path.isdir(dirname)):
 +			os.mkdir(dirname)
 +		self._dumpDir(dirname)
 +		return dirname
 +
 +
 +class WiiHeader(object):
 +	def __init__(self, data):
 +		self.data = data
 +	def addFile(self, filename):
 +		open(filename, "wb").write(self.add())
 +	def removeFile(self, filename):
 +		open(filename, "wb").write(self.remove())
 +	def loadFile(cls, filename, *args, **kwargs):
 +		return cls(open(filename, "rb").read(), *args, **kwargs)
 +	loadFile = classmethod(loadFile)
 +
 +
 +
 +def align(x, boundary):
 +	while x % boundary != 0:
 +		x += 1
 +	return x
 +	
 +def clamp(var, min, max):
 +	if var < min: var = min
 +	if var > max: var = max
 +	return var
 +
 +def abs(var):
 +	if var < 0:
 +		var = var + (2 * var)
 +	return var
 +
 +def hexdump(s, sep=" "): # just dumps hex values
 +	return sep.join(map(lambda x: "%02x" % ord(x), s))
 +		
 +def hexdump2(src, length = 16): # dumps to a "hex editor" style output
 +	result = []
 +	for i in xrange(0, len(src), length):
 +		s = src[i:i + length]
 +		if(len(s) % 4 == 0):
 +			mod = 0 
 +		else: 
 +			mod = 1 
 +		hexa = ''
 +		for j in range((len(s) / 4) + mod):
 +			hexa += ' '.join(["%02X" % ord(x) for x in s[j * 4:j * 4 + 4]])
 +			if(j != ((len(s) / 4) + mod) - 1):
 +				hexa += '  '
 +		printable = s.translate(''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]))
 +		result.append("0x%04X   %-*s   %s\n" % (i, (length * 3) + 2, hexa, printable))
 +	return ''.join(result)
 diff --git a/Puzzle+/license.txt b/Puzzle+/license.txt new file mode 100755 index 0000000..63e41a4 --- /dev/null +++ b/Puzzle+/license.txt @@ -0,0 +1,339 @@ +		    GNU GENERAL PUBLIC LICENSE
 +		       Version 2, June 1991
 +
 + Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 + Everyone is permitted to copy and distribute verbatim copies
 + of this license document, but changing it is not allowed.
 +
 +			    Preamble
 +
 +  The licenses for most software are designed to take away your
 +freedom to share and change it.  By contrast, the GNU General Public
 +License is intended to guarantee your freedom to share and change free
 +software--to make sure the software is free for all its users.  This
 +General Public License applies to most of the Free Software
 +Foundation's software and to any other program whose authors commit to
 +using it.  (Some other Free Software Foundation software is covered by
 +the GNU Lesser General Public License instead.)  You can apply it to
 +your programs, too.
 +
 +  When we speak of free software, we are referring to freedom, not
 +price.  Our General Public Licenses are designed to make sure that you
 +have the freedom to distribute copies of free software (and charge for
 +this service if you wish), that you receive source code or can get it
 +if you want it, that you can change the software or use pieces of it
 +in new free programs; and that you know you can do these things.
 +
 +  To protect your rights, we need to make restrictions that forbid
 +anyone to deny you these rights or to ask you to surrender the rights.
 +These restrictions translate to certain responsibilities for you if you
 +distribute copies of the software, or if you modify it.
 +
 +  For example, if you distribute copies of such a program, whether
 +gratis or for a fee, you must give the recipients all the rights that
 +you have.  You must make sure that they, too, receive or can get the
 +source code.  And you must show them these terms so they know their
 +rights.
 +
 +  We protect your rights with two steps: (1) copyright the software, and
 +(2) offer you this license which gives you legal permission to copy,
 +distribute and/or modify the software.
 +
 +  Also, for each author's protection and ours, we want to make certain
 +that everyone understands that there is no warranty for this free
 +software.  If the software is modified by someone else and passed on, we
 +want its recipients to know that what they have is not the original, so
 +that any problems introduced by others will not reflect on the original
 +authors' reputations.
 +
 +  Finally, any free program is threatened constantly by software
 +patents.  We wish to avoid the danger that redistributors of a free
 +program will individually obtain patent licenses, in effect making the
 +program proprietary.  To prevent this, we have made it clear that any
 +patent must be licensed for everyone's free use or not licensed at all.
 +
 +  The precise terms and conditions for copying, distribution and
 +modification follow.
 +
 +		    GNU GENERAL PUBLIC LICENSE
 +   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 +
 +  0. This License applies to any program or other work which contains
 +a notice placed by the copyright holder saying it may be distributed
 +under the terms of this General Public License.  The "Program", below,
 +refers to any such program or work, and a "work based on the Program"
 +means either the Program or any derivative work under copyright law:
 +that is to say, a work containing the Program or a portion of it,
 +either verbatim or with modifications and/or translated into another
 +language.  (Hereinafter, translation is included without limitation in
 +the term "modification".)  Each licensee is addressed as "you".
 +
 +Activities other than copying, distribution and modification are not
 +covered by this License; they are outside its scope.  The act of
 +running the Program is not restricted, and the output from the Program
 +is covered only if its contents constitute a work based on the
 +Program (independent of having been made by running the Program).
 +Whether that is true depends on what the Program does.
 +
 +  1. You may copy and distribute verbatim copies of the Program's
 +source code as you receive it, in any medium, provided that you
 +conspicuously and appropriately publish on each copy an appropriate
 +copyright notice and disclaimer of warranty; keep intact all the
 +notices that refer to this License and to the absence of any warranty;
 +and give any other recipients of the Program a copy of this License
 +along with the Program.
 +
 +You may charge a fee for the physical act of transferring a copy, and
 +you may at your option offer warranty protection in exchange for a fee.
 +
 +  2. You may modify your copy or copies of the Program or any portion
 +of it, thus forming a work based on the Program, and copy and
 +distribute such modifications or work under the terms of Section 1
 +above, provided that you also meet all of these conditions:
 +
 +    a) You must cause the modified files to carry prominent notices
 +    stating that you changed the files and the date of any change.
 +
 +    b) You must cause any work that you distribute or publish, that in
 +    whole or in part contains or is derived from the Program or any
 +    part thereof, to be licensed as a whole at no charge to all third
 +    parties under the terms of this License.
 +
 +    c) If the modified program normally reads commands interactively
 +    when run, you must cause it, when started running for such
 +    interactive use in the most ordinary way, to print or display an
 +    announcement including an appropriate copyright notice and a
 +    notice that there is no warranty (or else, saying that you provide
 +    a warranty) and that users may redistribute the program under
 +    these conditions, and telling the user how to view a copy of this
 +    License.  (Exception: if the Program itself is interactive but
 +    does not normally print such an announcement, your work based on
 +    the Program is not required to print an announcement.)
 +
 +These requirements apply to the modified work as a whole.  If
 +identifiable sections of that work are not derived from the Program,
 +and can be reasonably considered independent and separate works in
 +themselves, then this License, and its terms, do not apply to those
 +sections when you distribute them as separate works.  But when you
 +distribute the same sections as part of a whole which is a work based
 +on the Program, the distribution of the whole must be on the terms of
 +this License, whose permissions for other licensees extend to the
 +entire whole, and thus to each and every part regardless of who wrote it.
 +
 +Thus, it is not the intent of this section to claim rights or contest
 +your rights to work written entirely by you; rather, the intent is to
 +exercise the right to control the distribution of derivative or
 +collective works based on the Program.
 +
 +In addition, mere aggregation of another work not based on the Program
 +with the Program (or with a work based on the Program) on a volume of
 +a storage or distribution medium does not bring the other work under
 +the scope of this License.
 +
 +  3. You may copy and distribute the Program (or a work based on it,
 +under Section 2) in object code or executable form under the terms of
 +Sections 1 and 2 above provided that you also do one of the following:
 +
 +    a) Accompany it with the complete corresponding machine-readable
 +    source code, which must be distributed under the terms of Sections
 +    1 and 2 above on a medium customarily used for software interchange; or,
 +
 +    b) Accompany it with a written offer, valid for at least three
 +    years, to give any third party, for a charge no more than your
 +    cost of physically performing source distribution, a complete
 +    machine-readable copy of the corresponding source code, to be
 +    distributed under the terms of Sections 1 and 2 above on a medium
 +    customarily used for software interchange; or,
 +
 +    c) Accompany it with the information you received as to the offer
 +    to distribute corresponding source code.  (This alternative is
 +    allowed only for noncommercial distribution and only if you
 +    received the program in object code or executable form with such
 +    an offer, in accord with Subsection b above.)
 +
 +The source code for a work means the preferred form of the work for
 +making modifications to it.  For an executable work, complete source
 +code means all the source code for all modules it contains, plus any
 +associated interface definition files, plus the scripts used to
 +control compilation and installation of the executable.  However, as a
 +special exception, the source code distributed need not include
 +anything that is normally distributed (in either source or binary
 +form) with the major components (compiler, kernel, and so on) of the
 +operating system on which the executable runs, unless that component
 +itself accompanies the executable.
 +
 +If distribution of executable or object code is made by offering
 +access to copy from a designated place, then offering equivalent
 +access to copy the source code from the same place counts as
 +distribution of the source code, even though third parties are not
 +compelled to copy the source along with the object code.
 +
 +  4. You may not copy, modify, sublicense, or distribute the Program
 +except as expressly provided under this License.  Any attempt
 +otherwise to copy, modify, sublicense or distribute the Program is
 +void, and will automatically terminate your rights under this License.
 +However, parties who have received copies, or rights, from you under
 +this License will not have their licenses terminated so long as such
 +parties remain in full compliance.
 +
 +  5. You are not required to accept this License, since you have not
 +signed it.  However, nothing else grants you permission to modify or
 +distribute the Program or its derivative works.  These actions are
 +prohibited by law if you do not accept this License.  Therefore, by
 +modifying or distributing the Program (or any work based on the
 +Program), you indicate your acceptance of this License to do so, and
 +all its terms and conditions for copying, distributing or modifying
 +the Program or works based on it.
 +
 +  6. Each time you redistribute the Program (or any work based on the
 +Program), the recipient automatically receives a license from the
 +original licensor to copy, distribute or modify the Program subject to
 +these terms and conditions.  You may not impose any further
 +restrictions on the recipients' exercise of the rights granted herein.
 +You are not responsible for enforcing compliance by third parties to
 +this License.
 +
 +  7. If, as a consequence of a court judgment or allegation of patent
 +infringement or for any other reason (not limited to patent issues),
 +conditions are imposed on you (whether by court order, agreement or
 +otherwise) that contradict the conditions of this License, they do not
 +excuse you from the conditions of this License.  If you cannot
 +distribute so as to satisfy simultaneously your obligations under this
 +License and any other pertinent obligations, then as a consequence you
 +may not distribute the Program at all.  For example, if a patent
 +license would not permit royalty-free redistribution of the Program by
 +all those who receive copies directly or indirectly through you, then
 +the only way you could satisfy both it and this License would be to
 +refrain entirely from distribution of the Program.
 +
 +If any portion of this section is held invalid or unenforceable under
 +any particular circumstance, the balance of the section is intended to
 +apply and the section as a whole is intended to apply in other
 +circumstances.
 +
 +It is not the purpose of this section to induce you to infringe any
 +patents or other property right claims or to contest validity of any
 +such claims; this section has the sole purpose of protecting the
 +integrity of the free software distribution system, which is
 +implemented by public license practices.  Many people have made
 +generous contributions to the wide range of software distributed
 +through that system in reliance on consistent application of that
 +system; it is up to the author/donor to decide if he or she is willing
 +to distribute software through any other system and a licensee cannot
 +impose that choice.
 +
 +This section is intended to make thoroughly clear what is believed to
 +be a consequence of the rest of this License.
 +
 +  8. If the distribution and/or use of the Program is restricted in
 +certain countries either by patents or by copyrighted interfaces, the
 +original copyright holder who places the Program under this License
 +may add an explicit geographical distribution limitation excluding
 +those countries, so that distribution is permitted only in or among
 +countries not thus excluded.  In such case, this License incorporates
 +the limitation as if written in the body of this License.
 +
 +  9. The Free Software Foundation may publish revised and/or new versions
 +of the General Public License from time to time.  Such new versions will
 +be similar in spirit to the present version, but may differ in detail to
 +address new problems or concerns.
 +
 +Each version is given a distinguishing version number.  If the Program
 +specifies a version number of this License which applies to it and "any
 +later version", you have the option of following the terms and conditions
 +either of that version or of any later version published by the Free
 +Software Foundation.  If the Program does not specify a version number of
 +this License, you may choose any version ever published by the Free Software
 +Foundation.
 +
 +  10. If you wish to incorporate parts of the Program into other free
 +programs whose distribution conditions are different, write to the author
 +to ask for permission.  For software which is copyrighted by the Free
 +Software Foundation, write to the Free Software Foundation; we sometimes
 +make exceptions for this.  Our decision will be guided by the two goals
 +of preserving the free status of all derivatives of our free software and
 +of promoting the sharing and reuse of software generally.
 +
 +			    NO WARRANTY
 +
 +  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 +REPAIR OR CORRECTION.
 +
 +  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 +POSSIBILITY OF SUCH DAMAGES.
 +
 +		     END OF TERMS AND CONDITIONS
 +
 +	    How to Apply These Terms to Your New Programs
 +
 +  If you develop a new program, and you want it to be of the greatest
 +possible use to the public, the best way to achieve this is to make it
 +free software which everyone can redistribute and change under these terms.
 +
 +  To do so, attach the following notices to the program.  It is safest
 +to attach them to the start of each source file to most effectively
 +convey the exclusion of warranty; and each file should have at least
 +the "copyright" line and a pointer to where the full notice is found.
 +
 +    <one line to give the program's name and a brief idea of what it does.>
 +    Copyright (C) <year>  <name of author>
 +
 +    This program is free software; you can redistribute it and/or modify
 +    it under the terms of the GNU General Public License as published by
 +    the Free Software Foundation; either version 2 of the License, or
 +    (at your option) any later version.
 +
 +    This program is distributed in the hope that it will be useful,
 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +    GNU General Public License for more details.
 +
 +    You should have received a copy of the GNU General Public License along
 +    with this program; if not, write to the Free Software Foundation, Inc.,
 +    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 +
 +Also add information on how to contact you by electronic and paper mail.
 +
 +If the program is interactive, make it output a short notice like this
 +when it starts in an interactive mode:
 +
 +    Gnomovision version 69, Copyright (C) year name of author
 +    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 +    This is free software, and you are welcome to redistribute it
 +    under certain conditions; type `show c' for details.
 +
 +The hypothetical commands `show w' and `show c' should show the appropriate
 +parts of the General Public License.  Of course, the commands you use may
 +be called something other than `show w' and `show c'; they could even be
 +mouse-clicks or menu items--whatever suits your program.
 +
 +You should also get your employer (if you work as a programmer) or your
 +school, if any, to sign a "copyright disclaimer" for the program, if
 +necessary.  Here is a sample; alter the names:
 +
 +  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 +  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 +
 +  <signature of Ty Coon>, 1 April 1989
 +  Ty Coon, President of Vice
 +
 +This General Public License does not permit incorporating your program into
 +proprietary programs.  If your program is a subroutine library, you may
 +consider it more useful to permit linking proprietary applications with the
 +library.  If this is what you want to do, use the GNU Lesser General
 +Public License instead of this License.
\ No newline at end of file diff --git a/Puzzle+/nsmblib-0.5a.zip b/Puzzle+/nsmblib-0.5a.zipBinary files differ new file mode 100644 index 0000000..bca75e2 --- /dev/null +++ b/Puzzle+/nsmblib-0.5a.zip diff --git a/Puzzle+/puzzle+.py b/Puzzle+/puzzle+.py new file mode 100755 index 0000000..886a80a --- /dev/null +++ b/Puzzle+/puzzle+.py @@ -0,0 +1,1858 @@ +#!/usr/bin/env python + +import archive +import os.path +import struct +import sys +import cPickle + +from ctypes import create_string_buffer +from PyQt4 import QtCore, QtGui + + +try: +    import nsmblib +    HaveNSMBLib = True +except ImportError: +    HaveNSMBLib = False + + +######################################################## +# To Do: +# +#   - Object Editor +#       - Moving objects around +# +#   - Make UI simpler for Pop +#   - C speed saving +# +######################################################## + + +Tileset = None + +############################################################################################# +########################## Tileset Class and Tile/Object Subclasses ######################### + +class TilesetClass(): +    '''Contains Tileset data. Inits itself to a blank tileset. +    Methods: addTile, removeTile, addObject, removeObject, clear''' + +    class Tile(): +        def __init__(self, image, noalpha): +            '''Tile Constructor''' +                         +            self.image = image +            self.noalpha = noalpha + + +    class Object(): +     +        def __init__(self, height, width, uslope, lslope, tilelist): +            '''Tile Constructor''' +             +            self.height = height +            self.width = width +             +            self.upperslope = uslope +            self.lowerslope = lslope +             +            self.tiles = tilelist +                         + +    def __init__(self): +        '''Constructor''' +         +        self.tiles = [] +        self.objects = [] +         +        self.slot = 0 + + +    def addTile(self, image, noalpha): +        '''Adds an tile class to the tile list with the passed image or parameters''' + +        self.tiles.append(self.Tile(image, noalpha)) +         + +    def addObject(self, height = 1, width = 1,  uslope = [0, 0], lslope = [0, 0], tilelist = [[(0, 0xFFFF, 0)]]): +        '''Adds a new object''' +         +        global Tileset +         + +        # Initialize trusim power! This is required to work, due to python's single default parameter initialization +        if tilelist == [[(0, 0xFFFF, 0)]]: +            tilelist = [[(0, 0xFFFF, 0)]] +             +        self.objects.append(self.Object(height, width, uslope, lslope, tilelist)) +         +         +    def removeObject(self, index): +        '''Removes an Object by Index number. Don't use this much, because we want objects to preserve their ID.''' +         +        self.objects.pop(index) +        +     +    def clear(self): +        '''Clears the tileset for a new file''' +         +        self.tiles = [] +        self.objects = [] +         +         +    def clearObjects(self): +        '''Clears the object data''' +         +        self.objects = [] +                  + +############################################################################################# +##################### Object List Widget and Model Setup with Painter ####################### + + +class objectList(QtGui.QListView): +         +    def __init__(self, parent=None): +        super(objectList, self).__init__(parent) + + +        self.setFlow(QtGui.QListView.TopToBottom) +        # self.setViewMode(QtGui.QListView.ListMode) +        self.setIconSize(QtCore.QSize(96,96)) +        self.setGridSize(QtCore.QSize(100,100)) +        self.setMovement(QtGui.QListView.Static) +        self.setBackgroundRole(QtGui.QPalette.BrightText) +        self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) +        self.setWrapping(False) + +         + +def SetupObjectModel(self, objects, tiles): +    global Tileset +    self.clear() +     +    count = 0 +    for object in objects: +        tex = QtGui.QPixmap(object.width * 24, object.height * 24) +        tex.fill(QtCore.Qt.transparent) +        painter = QtGui.QPainter(tex) +         +        Xoffset = 0 +        Yoffset = 0 +         +        for i in range(len(object.tiles)): +            for tile in object.tiles[i]: +                if (Tileset.slot == 0): +                    painter.drawPixmap(Xoffset, Yoffset, tiles[tile[1]].image) +                Xoffset += 24 +            Xoffset = 0 +            Yoffset += 24 +                         +        painter.end() + +        self.appendRow(QtGui.QStandardItem(QtGui.QIcon(tex), 'Object {0}'.format(count))) +     +        count += 1 + + +@QtCore.pyqtSlot(QtGui.QTreeWidgetItem, int) +def connectToTileWidget(tree, column): + +    row = tree.text(0) +    if row[:7] == "Object ": + +        newrow = int(row[7:]) +        index = window.objmodel.index(newrow, 0) +         +        window.objectList.setCurrentIndex(index)  + +        window.tileWidget.setObject(index) + + + +############################################################################################# +######################## List Widget with custom painter/MouseEvent ######################### + + +class displayWidget(QtGui.QListView): +         +    def __init__(self, parent=None): +        super(displayWidget, self).__init__(parent) + +        self.setMinimumWidth(818) +        self.setMaximumWidth(818) +        self.setMinimumHeight(404) +        self.setMaximumHeight(404) +        self.setDragEnabled(True) +        self.setViewMode(QtGui.QListView.IconMode) +        self.setIconSize(QtCore.QSize(24,24)) +        self.setGridSize(QtCore.QSize(25,25)) +        self.setMovement(QtGui.QListView.Static) +        self.setAcceptDrops(False) +        self.setDropIndicatorShown(True) +        self.setResizeMode(QtGui.QListView.Adjust) +        self.setUniformItemSizes(True) +        self.setBackgroundRole(QtGui.QPalette.BrightText) +        self.setMouseTracking(True) +        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + +        self.setItemDelegate(self.TileItemDelegate()) + +                 +                 +    class TileItemDelegate(QtGui.QAbstractItemDelegate): +        """Handles tiles and their rendering""" +         +        def __init__(self): +            """Initialises the delegate""" +            QtGui.QAbstractItemDelegate.__init__(self) +         +        def paint(self, painter, option, index): +            """Paints an object""" + +            global Tileset +            p = index.model().data(index, QtCore.Qt.DecorationRole) +            painter.drawPixmap(option.rect.x(), option.rect.y(), p.pixmap(24,24)) + +            x = option.rect.x() +            y = option.rect.y() + + +            # Collision Overlays +            curTile = Tileset.tiles[index.row()] +             + +            # Highlight stuff.  +            colour = QtGui.QColor(option.palette.highlight()) +            colour.setAlpha(80) + +            if option.state & QtGui.QStyle.State_Selected: +                painter.fillRect(option.rect, colour) +             +         +        def sizeHint(self, option, index): +            """Returns the size for the object""" +            return QtCore.QSize(24,24) +         +         +         +############################################################################################# +############################ Tile widget for drag n'drop Objects ############################ + + +class tileOverlord(QtGui.QWidget): + +    def __init__(self): +        super(tileOverlord, self).__init__() + +        # Setup Widgets +        self.tiles = tileWidget() + +        self.addObject = QtGui.QPushButton('Add') +        self.removeObject = QtGui.QPushButton('Remove') + +        self.addRow = QtGui.QPushButton('+') +        self.removeRow = QtGui.QPushButton('-') +     +        self.addColumn = QtGui.QPushButton('+') +        self.removeColumn = QtGui.QPushButton('-') + +        self.tilingMethod = QtGui.QComboBox() + +        self.tilingMethod.addItems(['Repeat',  +                                    'Stretch Center', +                                    'Stretch X', +                                    'Stretch Y', +                                    'Repeat Bottom', +                                    'Repeat Top', +                                    'Repeat Left', +                                    'Repeat Right', +                                    'Upward slope', +                                    'Downward slope', +                                    'Downward reverse slope', +                                    'Upward reverse slope']) + + +        # Connections +        self.addObject.released.connect(self.addObj) +        self.removeObject.released.connect(self.removeObj) +        self.addRow.released.connect(self.tiles.addRow) +        self.removeRow.released.connect(self.tiles.removeRow) +        self.addColumn.released.connect(self.tiles.addColumn) +        self.removeColumn.released.connect(self.tiles.removeColumn) + +        self.tilingMethod.activated.connect(self.setTiling) + + +        # Layout +        layout = QtGui.QGridLayout()         +         +        layout.addWidget(self.tilingMethod, 0, 0, 1, 3) + +        layout.addWidget(self.addObject, 0, 6, 1, 1) +        layout.addWidget(self.removeObject, 0, 7, 1, 1) +         +        layout.setRowMinimumHeight(1, 40) +         +        layout.setRowStretch(1, 1) +        layout.setRowStretch(2, 5) +        layout.setRowStretch(5, 5) +        layout.addWidget(self.tiles, 2, 1, 4, 6) +         +        layout.addWidget(self.addColumn, 3, 7, 1, 1) +        layout.addWidget(self.removeColumn, 4, 7, 1, 1) +        layout.addWidget(self.addRow, 6, 3, 1, 1) +        layout.addWidget(self.removeRow, 6, 4, 1, 1) +         +        self.setLayout(layout) +        + +    def addObj(self): +        global Tileset +         +        Tileset.addObject() +         +        pix = QtGui.QPixmap(24, 24) +        pix.fill(QtCore.Qt.transparent) +        painter = QtGui.QPainter(pix) +        painter.drawPixmap(0, 0, pix) +        painter.end() +                     +        count = len(Tileset.objects) +        window.objmodel.appendRow(QtGui.QStandardItem(QtGui.QIcon(pix), 'Object {0}'.format(count-1))) +        a = QtGui.QTreeWidgetItem(window.treeki) +        a.setText(0, 'Object {0}'.format(count-1)) +        a.setFlags(QtCore.Qt.ItemFlags(0x25)) +        a.setIcon(1, QtGui.QIcon(pix)) + +        index = window.objectList.currentIndex() +        window.objectList.setCurrentIndex(index) +        self.setObject(index) + +        window.objectList.update() +        self.update() +         + +    def removeObj(self): +        global Tileset + +        index = window.objectList.currentIndex() + +        Tileset.removeObject(index.row()) +        window.objmodel.removeRow(index.row()) +        self.tiles.clear() + +        matchList = window.treeki.findItems("Object {0}".format(index.row()), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive | QtCore.Qt.MatchWrap) +        for x in matchList: +            index = window.treeki.indexFromItem(x, 0) +            realx = index.row() +            if x.parent(): +                y = x.parent().takeChild(realx) +                del y +            else: +                y =window.treeki.takeTopLevelItem(realx) +                del y + + +        window.objectList.update() +        self.update() + + +    def setObject(self, index): +        global Tileset +        object = Tileset.objects[index.row()] +                 +        width = len(object.tiles[0])-1 +        height = len(object.tiles)-1 +        Xuniform = True +        Yuniform = True +        Xstretch = False +        Ystretch = False + +        for tile in object.tiles[0]: +            if tile[0] != object.tiles[0][0][1]: +                Xuniform = False +                 +        for tile in object.tiles: +            if tile[0][0] != object.tiles[0][0][1]: +                Yuniform = False + +        if object.tiles[0][0][1] == object.tiles[0][width][0] and Xuniform == False: +            Xstretch = True + +        if object.tiles[0][0][1] == object.tiles[height][0][0] and Xuniform == False: +            Ystretch = True + + + +        if object.upperslope[0] != 0: +            if object.upperslope[0] == 0x90: +                self.tilingMethod.setCurrentIndex(8) +            elif object.upperslope[0] == 0x91: +                self.tilingMethod.setCurrentIndex(9) +            elif object.upperslope[0] == 0x92: +                self.tilingMethod.setCurrentIndex(10) +            elif object.upperslope[0] == 0x93: +                self.tilingMethod.setCurrentIndex(11) +             +        else: +            if Xuniform and Yuniform: +                self.tilingMethod.setCurrentIndex(0) +            elif Xstretch and Ystretch: +                self.tilingMethod.setCurrentIndex(1) +            elif Xstretch: +                self.tilingMethod.setCurrentIndex(2) +            elif Ystretch: +                self.tilingMethod.setCurrentIndex(3) +            elif Xuniform and Yuniform == False and object.tiles[0][0][0] == 0: +                self.tilingMethod.setCurrentIndex(4) +            elif Xuniform and Yuniform == False and object.tiles[height][0][0] == 0: +                self.tilingMethod.setCurrentIndex(5) +            elif Xuniform == False and Yuniform and object.tiles[0][0][0] == 0: +                self.tilingMethod.setCurrentIndex(6) +            elif Xuniform == False and Yuniform and object.tiles[0][width][0] == 0: +                self.tilingMethod.setCurrentIndex(7) + +                 +        self.tiles.setObject(object) + +        # print 'Object {0}, Width: {1} / Height: {2}, Slope {3}/{4}'.format(index.row(), object.width, object.height, object.upperslope, object.lowerslope) +        # for row in object.tiles: +        #     print 'Row: {0}'.format(row) +        # print '' +     +    @QtCore.pyqtSlot(int) +    def setTiling(self, listindex): +        global Tileset +         +        index = window.objectList.currentIndex() +        object = Tileset.objects[index.row()] +         +         +        if listindex == 0: # Repeat +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +        if listindex == 1: # Stretch Center + +            if object.width < 3 and object.height < 3: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\nwide and 3 tiles tall to apply stretch center.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if crow == 0 and ctile == 0: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif crow == 0 and ctile == object.width-1: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif crow == object.height-1 and ctile == object.width-1: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif crow == object.height-1 and ctile == 0: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif crow == 0 or crow == object.height-1: +                        object.tiles[crow][ctile] = (1, tile[1], tile[2]) +                    elif ctile == 0 or ctile == object.width-1: +                        object.tiles[crow][ctile] = (2, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (3, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] + +        if listindex == 2: # Stretch X + +            if object.width < 3: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\nwide to apply stretch X.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if ctile == 0: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif ctile == object.width-1: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (1, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                  +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] +                +        if listindex == 3: # Stretch Y + +            if object.height < 3: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 3 tiles\ntall to apply stretch Y.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if crow == 0: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    elif crow == object.height-1: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (2, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] +                 +        if listindex == 4: # Repeat Bottom + +            if object.height < 2: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\ntall to apply repeat bottom.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if crow == object.height-1: +                        object.tiles[crow][ctile] = (2, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] + +        if listindex == 5: # Repeat Top + +            if object.height < 2: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\ntall to apply repeat top.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if crow == 0: +                        object.tiles[crow][ctile] = (2, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] + +        if listindex == 6: # Repeat Left + +            if object.width < 2: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\nwide to apply repeat left.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if ctile == 0: +                        object.tiles[crow][ctile] = (1, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] + +        if listindex == 7: # Repeat Right + +            if object.width < 2: +                reply = QtGui.QMessageBox.information(self, "Warning", "An object must be at least 2 tiles\nwide to apply repeat right.") +                self.setObject(index) +                return +                 +            ctile = 0 +            crow = 0 + +            for row in object.tiles: +                for tile in row: +                    if ctile == object.width-1: +                        object.tiles[crow][ctile] = (1, tile[1], tile[2]) +                    else: +                        object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 +                 +            object.upperslope = [0, 0] +            object.lowerslope = [0, 0] + + +        if listindex == 8: # Upward Slope +            ctile = 0 +            crow = 0 +            for row in object.tiles: +                for tile in row: +                    object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 + +            object.upperslope = [0x90, 1] +            object.lowerslope = [0x84, object.height - 1] +            self.tiles.slope = 1 +             +            self.tiles.update() +             +        if listindex == 9: # Downward Slope +            ctile = 0 +            crow = 0 +            for row in object.tiles: +                for tile in row: +                    object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 + +            object.upperslope = [0x91, 1] +            object.lowerslope = [0x84, object.height - 1] +            self.tiles.slope = 1 +             +            self.tiles.update() + +        if listindex == 10: # Upward Reverse Slope +            ctile = 0 +            crow = 0 +            for row in object.tiles: +                for tile in row: +                    object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 + +            object.upperslope = [0x92, object.height - 1] +            object.lowerslope = [0x84, 1] +            self.tiles.slope = 0-(object.height-1) +             +            self.tiles.update() + +        if listindex == 11: # Downward Reverse Slope +            ctile = 0 +            crow = 0 +            for row in object.tiles: +                for tile in row: +                    object.tiles[crow][ctile] = (0, tile[1], tile[2]) +                    ctile += 1 +                crow += 1 +                ctile = 0 + +            object.upperslope = [0x93, object.height - 1] +            object.lowerslope = [0x84, 1] +            self.tiles.slope = 0-(object.height-1) +             +            self.tiles.update() +        + +class tileWidget(QtGui.QWidget): +     +    def __init__(self): +        super(tileWidget, self).__init__() + +        self.tiles = [] + +        self.size = [1, 1] +        self.setMinimumSize(120, 120) + +        self.slope = 0 + +        self.highlightedRect = QtCore.QRect() + +        self.setAcceptDrops(True) +        self.object = 0 + + +    def clear(self): +        self.tiles = [] +        self.size = [1, 1] # [width, height] +         +        self.slope = 0 +        self.highlightedRect = QtCore.QRect() + +        self.update() + +        return + + +    def addColumn(self): +        global Tileset +         +        if self.size[0] >= 24: +            return +             +        if len(Tileset.objects) == 0: +            window.tileWidget.addObj() + +        self.size[0] += 1 +        self.setMinimumSize(self.size[0]*24, self.size[1]*24) + +        pix = QtGui.QPixmap(24,24) +        pix.fill(QtGui.QColor(205, 205, 255)) + +        for y in xrange(self.size[1]): +            self.tiles.insert(((y+1) * self.size[0]) -1, [self.size[0]-1, y, pix]) + +  +        curObj = Tileset.objects[self.object] +        curObj.width += 1 + +        for row in curObj.tiles: +            row.append((0, 0xFFFF, 0)) +             +        self.update() +        self.updateList() + +    +    def removeColumn(self): +        global Tileset + +        if self.size[0] == 1: +            return + +        if len(Tileset.objects) == 0: +            window.tileWidget.addObj() + +        for y in xrange(self.size[1]): +            self.tiles.pop(((y+1) * self.size[0])-(y+1)) + +        self.size[0] = self.size[0] - 1 +        self.setMinimumSize(self.size[0]*24, self.size[1]*24) + + +        curObj = Tileset.objects[self.object] +        curObj.width -= 1 + +        for row in curObj.tiles: +            row.pop() + +        self.update() +        self.updateList() + + +    def addRow(self): +        global Tileset + +        if len(Tileset.objects) == 0: +            window.tileWidget.addObj() + +        if self.size[1] >= 24: +            return +         +        self.size[1] += 1 +        self.setMinimumSize(self.size[0]*24, self.size[1]*24) + +        pix = QtGui.QPixmap(24,24) +        pix.fill(QtGui.QColor(205, 205, 255)) + +        for x in xrange(self.size[0]): +            self.tiles.append([x, self.size[1]-1, pix]) + +        curObj = Tileset.objects[self.object] +        curObj.height += 1 + +        curObj.tiles.append([]) +        for i in xrange(0, curObj.width): +            curObj.tiles[len(curObj.tiles)-1].append((0, 0xFFFF, 0)) + +        self.update() +        self.updateList() + +     +    def removeRow(self): +        global Tileset + +        if self.size[1] == 1: +            return + +        if len(Tileset.objects) == 0: +            window.tileWidget.addObj() + +        for x in xrange(self.size[0]): +            self.tiles.pop() +         +        self.size[1] -= 1 +        self.setMinimumSize(self.size[0]*24, self.size[1]*24) + +        curObj = Tileset.objects[self.object] +        curObj.height -= 1 + +        curObj.tiles.pop() + +        self.update() +        self.updateList() + + +    def setObject(self, object): +        self.clear() +             +        global Tileset +             +        self.size = [object.width, object.height] +         +        if not object.upperslope[1] == 0: +            if object.upperslope[0] & 2: +                self.slope = 0 - object.lowerslope[1] +            else: +                self.slope = object.upperslope[1] + +        x = 0 +        y = 0 +        for row in object.tiles: +            for tile in row: + +                if (Tileset.slot == 0) or ((tile[2] & 3) != 0): +                    if (tile[1] == 0xFFFF): +                        pix = QtGui.QPixmap(24,24) +                        pix.fill(QtGui.QColor(205, 205, 255)) +                        self.tiles.append([x, y, pix]) +                    else: +                        self.tiles.append([x, y, Tileset.tiles[tile[1]].image]) +                else: +                    pix = QtGui.QPixmap(24,24) +                    pix.fill(QtGui.QColor(205, 205, 255)) +                    self.tiles.append([x, y, pix]) +                x += 1 +            y += 1 +            x = 0 +            +            +        self.object = window.objectList.currentIndex().row()     +        self.update() +        self.updateList() +                + +    def contextMenuEvent(self, event): +     +        TileMenu = QtGui.QMenu(self) +        self.contX = event.x() +        self.contY = event.y() +         +        TileMenu.addAction('Set tile...', self.setTile) + +        TileMenu.exec_(event.globalPos()) + + +    def mousePressEvent(self, event): +        global Tileset + +        if event.button() == 2: +            return + +        if window.tileDisplay.selectedIndexes() == []: +            return + +        currentSelected = window.tileDisplay.selectedIndexes() +         +        ix = 0 +        iy = 0 +        for modelItem in currentSelected: +            # Update yourself! +            centerPoint = self.contentsRect().center() +     +            tile = modelItem.row() +            upperLeftX = centerPoint.x() - self.size[0]*12 +            upperLeftY = centerPoint.y() - self.size[1]*12 +     +            lowerRightX = centerPoint.x() + self.size[0]*12 +            lowerRightY = centerPoint.y() + self.size[1]*12 +     +     +            x = (event.x() - upperLeftX)/24 + ix +            y = (event.y() - upperLeftY)/24 + iy +     +            if event.x() < upperLeftX or event.y() < upperLeftY or event.x() > lowerRightX or event.y() > lowerRightY: +                return +                     +            self.tiles[(y * self.size[0]) + x][2] = Tileset.tiles[tile].image +                     +            Tileset.objects[self.object].tiles[y][x] = (Tileset.objects[self.object].tiles[y][x][0], tile, Tileset.slot) + +            ix += 1 +            if self.size[0]-1 < ix: +                ix = 0 +                iy += 1 +            if iy > self.size[1]-1: +                break +             +             +        self.update() +         +        self.updateList() +         + +    def updateList(self):         +        # Update the list >.> +        object = window.objmodel.itemFromIndex(window.objectList.currentIndex()) +        matchList = window.treeki.findItems("Object {0}".format(window.objectList.currentIndex().row()), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive | QtCore.Qt.MatchWrap) +         + +        tex = QtGui.QPixmap(self.size[0] * 24, self.size[1] * 24) +        tex.fill(QtCore.Qt.transparent) +        painter = QtGui.QPainter(tex) +         +        Xoffset = 0 +        Yoffset = 0 +         +        for tile in self.tiles: +            painter.drawPixmap(tile[0]*24, tile[1]*24, tile[2]) +                         +        painter.end() + +        try: +            object.setIcon(QtGui.QIcon(tex)) +            matchList[0].setIcon(1, QtGui.QIcon(tex)) +        except: +            pass + +        window.objectList.update() +     +             +         +    def setTile(self): +        global Tileset +         +        dlg = self.setTileDialog() +        if dlg.exec_() == QtGui.QDialog.Accepted: +            # Do stuff +            centerPoint = self.contentsRect().center() + +            upperLeftX = centerPoint.x() - self.size[0]*12 +            upperLeftY = centerPoint.y() - self.size[1]*12 + +            tile = dlg.tile.value() +            tileset = dlg.tileset.currentIndex() +     +            x = (self.contX - upperLeftX)/24 +            y = (self.contY - upperLeftY)/24 + +            if tileset != Tileset.slot: +                tex = QtGui.QPixmap(self.size[0] * 24, self.size[1] * 24) +                tex.fill(QtCore.Qt.transparent) +         +                self.tiles[(y * self.size[0]) + x][2] = tex + +            Tileset.objects[self.object].tiles[y][x] = (Tileset.objects[self.object].tiles[y][x][0], tile, tileset) +             +            self.update() +            self.updateList() + + +    class setTileDialog(QtGui.QDialog): +     +        def __init__(self): +            QtGui.QDialog.__init__(self) +         +            self.setWindowTitle('Set tiles') +                 +            self.tile = QtGui.QSpinBox()                 +            self.tile.setRange(0, 512)              +             +            self.buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) +            self.buttons.accepted.connect(self.accept) +            self.buttons.rejected.connect(self.reject) +             +            self.layout = QtGui.QGridLayout() +            self.layout.addWidget(QtGui.QLabel('Tile:'), 0,3,1,1, QtCore.Qt.AlignLeft) +            self.layout.addWidget(self.tile, 1, 3, 1, 3) +            self.layout.addWidget(self.buttons, 2, 3) +            self.setLayout(self.layout) + +                            + +    def paintEvent(self, event): +        painter = QtGui.QPainter() +        painter.begin(self) +         +        centerPoint = self.contentsRect().center() +        upperLeftX = centerPoint.x() - self.size[0]*12 +        lowerRightX = centerPoint.x() + self.size[0]*12 + +        upperLeftY = centerPoint.y() - self.size[1]*12 +        lowerRightY = centerPoint.y() + self.size[1]*12 + + +        painter.fillRect(upperLeftX, upperLeftY, self.size[0] * 24, self.size[1]*24, QtGui.QColor(205, 205, 255)) + +        for x, y, pix in self.tiles: +            painter.drawPixmap(upperLeftX + (x * 24), upperLeftY + (y * 24), pix) + +        if not self.slope == 0: +            pen = QtGui.QPen() +            # pen.setStyle(QtCore.Qt.QDashLine) +            pen.setWidth(1) +            pen.setColor(QtCore.Qt.blue) +            painter.setPen(QtGui.QPen(pen)) +            painter.drawLine(upperLeftX, upperLeftY + (abs(self.slope) * 24), lowerRightX, upperLeftY + (abs(self.slope) * 24)) +             +            if self.slope > 0: +                main = 'Main' +                sub = 'Sub' +            elif self.slope < 0: +                main = 'Sub' +                sub = 'Main' + +            font = painter.font() +            font.setPixelSize(8) +            font.setFamily('Monaco') +            painter.setFont(font) + +            painter.drawText(upperLeftX+1, upperLeftY+10, main) +            painter.drawText(upperLeftX+1, upperLeftY + (abs(self.slope) * 24) + 9, sub) + +        painter.end() + + + +############################################################################################# +############################ Subclassed one dimension Item Model ############################ + + +class PiecesModel(QtCore.QAbstractListModel): +    def __init__(self, parent=None): +        super(PiecesModel, self).__init__(parent) + +        self.pixmaps = [] +        self.setSupportedDragActions(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction | QtCore.Qt.LinkAction) + +    def data(self, index, role=QtCore.Qt.DisplayRole): +        if not index.isValid(): +            return None + +        if role == QtCore.Qt.DecorationRole: +            return QtGui.QIcon(self.pixmaps[index.row()]) + +        if role == QtCore.Qt.UserRole: +            return self.pixmaps[index.row()] + +        return None + +    def addPieces(self, pixmap): +        row = len(self.pixmaps) + +        self.beginInsertRows(QtCore.QModelIndex(), row, row) +        self.pixmaps.insert(row, pixmap) +        self.endInsertRows() +         +    def flags(self,index): +        if index.isValid(): +            return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | +                    QtCore.Qt.ItemIsDragEnabled) + +    def clear(self): +        row = len(self.pixmaps) + +        del self.pixmaps[:] + + +    def mimeTypes(self): +        return ['image/x-tile-piece'] + + +    def mimeData(self, indexes): +        mimeData = QtCore.QMimeData() +        encodedData = QtCore.QByteArray() + +        stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.WriteOnly) + +        for index in indexes: +            if index.isValid(): +                pixmap = QtGui.QPixmap(self.data(index, QtCore.Qt.UserRole)) +                stream << pixmap + +        mimeData.setData('image/x-tile-piece', encodedData) +        return mimeData + + +    def rowCount(self, parent): +        if parent.isValid(): +            return 0 +        else: +            return len(self.pixmaps) + +    def supportedDragActions(self): +        return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction + + + +############################################################################################# +################## Python-based RGB5a3 Decoding code from my BRFNT program ################## + + +def RGB4A3Decode(tex): +    dest = QtGui.QImage(1024,512,QtGui.QImage.Format_ARGB32) +    dest.fill(QtCore.Qt.transparent) +     +    i = 0 +    for ytile in xrange(0, 512, 4): +    	for xtile in xrange(0, 1024, 4): +    		for ypixel in xrange(ytile, ytile + 4): +    			for xpixel in xrange(xtile, xtile + 4): +    				 +    				if(xpixel >= 1024 or ypixel >= 512): +    					continue +    				 +    				newpixel = (tex[i] << 8) | tex[i+1] +    				 +     +    				if(newpixel >= 0x8000): # Check if it's RGB555 +    					red = ((newpixel >> 10) & 0x1F) * 255 / 0x1F +    					green = ((newpixel >> 5) & 0x1F) * 255 / 0x1F +    					blue = (newpixel & 0x1F) * 255 / 0x1F +    					alpha = 0xFF +     +    				else: # If not, it's RGB4A3 +    					alpha = ((newpixel & 0x7000) >> 12) * 255 / 0x7 +    					blue = ((newpixel & 0xF00) >> 8) * 255 / 0xF +    					green = ((newpixel & 0xF0) >> 4) * 255 / 0xF +    					red = (newpixel & 0xF) * 255 / 0xF +     +    				argb = (blue) | (green << 8) | (red << 16) | (alpha << 24) +    				dest.setPixel(xpixel, ypixel, argb) +    				i += 2 +    return dest + + +def RGB4A3Encode(tex): +    destBuffer = create_string_buffer(1024*512*2) + +    shortstruct = struct.Struct('>H') +    offset = 0 + +    for ytile in xrange(0, 512, 4): +        for xtile in xrange(0, 1024, 4): +            for ypixel in xrange(ytile, ytile + 4): +                for xpixel in xrange(xtile, xtile + 4): +    				 +                    if(xpixel >= 1024 or ypixel >= 512): +                    	continue +                     +                    pixel = tex.pixel(xpixel, ypixel) +                     +                    a = pixel >> 24 +                    r = (pixel >> 16) & 0xFF +                    g = (pixel >> 8) & 0xFF +                    b = pixel & 0xFF +                     +                    if a < 245: #RGB4A3 +                        alpha = a/32 +                        red = r/16 +                        green = g/16 +                        blue = b/16 + +                        rgbDAT = (blue) | (green << 4) | (red << 8) | (alpha << 12) +                 +                    else: # RGB555 +                        red = r/8 +                        green = g/8 +                        blue = b/8 +                         +                        rgbDAT = (blue) | (green << 5) | (red << 10) | (0x8000) # 0rrrrrgggggbbbbb +                                                                                                             +                    shortstruct.pack_into(destBuffer, offset, rgbDAT) +                    offset += 2 +                     +    return destBuffer.raw + + +############################################################################################# +############ Main Window Class. Takes care of menu functions and widget creation ############ + + +class MainWindow(QtGui.QMainWindow): +    def __init__(self, parent=None): +        super(MainWindow, self).__init__(parent) + +        self.tileImage = QtGui.QPixmap() +        self.alpha = True +         +        global Tileset +        Tileset = TilesetClass() + +        self.name = '' + +        self.setupMenus() +        self.setupWidgets() + +        self.setuptile() + +        self.newTileset() + +        self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, +                QtGui.QSizePolicy.Fixed)) +        self.setWindowTitle("New Tileset") + + +    def setuptile(self): +        self.tileWidget.tiles.clear() +        self.model.clear() + +        if self.alpha == True: +            for tile in Tileset.tiles: +                self.model.addPieces(tile.image) +        else: +            for tile in Tileset.tiles: +                self.model.addPieces(tile.noalpha) + + +    def newTileset(self): +        '''Creates a new, blank tileset''' +         +        global Tileset +        Tileset.clear() +        Tileset = TilesetClass() + +        self.treeki.clear() +        # self.objectList.clear() +        self.objmodel.clear() +         +        EmptyPix = QtGui.QPixmap(24, 24) +        EmptyPix.fill(QtCore.Qt.black) +         +        for i in range(512): +            Tileset.addTile(EmptyPix, EmptyPix) + +        self.setuptile() +        self.setWindowTitle('New Tileset') +         +         +    def openTileset(self): +        '''Opens a Koopatlas tileset arc and parses the heck out of it.''' +         +        path = str(QtGui.QFileDialog.getOpenFileName(self, "Open Koopatlas Tileset", '', +                    "Image Files (*.arc)")) +                     +        if path: +            self.setWindowTitle(os.path.basename(path)) +            Tileset.clear() + +            name = path[str(path).rfind('/')+1:-4] +         +            file = open(path,'rb') +            data = file.read() +            file.close() +             +            arc = archive.U8() +            arc._load(data) +             +            Image = None +            behaviourdata = None +            objstrings = None +            metadata = None +             +            for key, value in arc.files: +                if value == None: +                    pass +                if key.startswith('BG_tex/') and key.endswith('_tex.bin'): +                    Image = arc[key] +                if key.startswith('BG_grp/') and key.endswith('_grp.bin'): +                    Image = arc[key] +                if key.startswith('BG_unt/'): +                    if key.endswith('_hd.bin'): +                        metadata = arc[key] +                    elif key.endswith('.bin'): +                        objstrings = arc[key] + + +            if (Image == None) or (group == None) or (objstrings == None) or (metadata == None): +                QtGui.QMessageBox.warning(None, 'Error',  'Error - the necessary files were not found.\n\nNot a valid Koopatlas tileset, sadly.') +                return +             +            # Stolen from Reggie! Loads the Image Data. +            if HaveNSMBLib: +                argbdata = nsmblib.decodeTileset(Image) +                rgbdata = nsmblib.decodeTilesetNoAlpha(Image) +                dest = QtGui.QImage(argbdata, 1024, 512, 4096, QtGui.QImage.Format_ARGB32_Premultiplied) +                noalphadest = QtGui.QImage(rgbdata, 1024, 512, 4096, QtGui.QImage.Format_ARGB32_Premultiplied) +            else: +                dest = RGB4A3Decode(Image) +             +            self.tileImage = QtGui.QPixmap.fromImage(dest) +            noalpha = QtGui.QPixmap.fromImage(noalphadest) +             +             +            # Makes us some nice Tile Classes! +            Xoffset = 4 +            Yoffset = 4 +            for i in range(512): +                Tileset.addTile(self.tileImage.copy(Xoffset,Yoffset,24,24), noalpha.copy(Xoffset,Yoffset,24,24)) +                Xoffset += 32 +                if Xoffset >= 1024: +                    Xoffset = 4 +                    Yoffset += 32                     +             +             +            # Load Objects +             +            meta = [] +            for i in xrange(len(metadata)/4): +                meta.append(struct.unpack_from('>H2B', metadata, i * 4))                                     +                 +            tilelist = [[]] +            upperslope = [0, 0] +            lowerslope = [0, 0] +            byte = 0 +             +            for entry in meta:   +                offset = entry[0] +                byte = struct.unpack_from('>B', objstrings, offset)[0] +                row = 0 +                 +                while byte != 0xFF: + +                    if byte == 0xFE: +                        tilelist.append([]) + +                        if (upperslope[0] != 0) and (lowerslope[0] == 0): +                            upperslope[1] = upperslope[1] + 1 +                             +                        if lowerslope[0] != 0: +                            lowerslope[1] = lowerslope[1] + 1 + +                        offset += 1 +                        byte = struct.unpack_from('>B', objstrings, offset)[0] + +                    elif (byte & 0x80): + +                        if upperslope[0] == 0: +                            upperslope[0] = byte +                        else: +                            lowerslope[0] = byte +                             +                        offset += 1 +                        byte = struct.unpack_from('>B', objstrings, offset)[0] +                         +                    else: +                        tilelist[len(tilelist)-1].append(struct.unpack_from('>BH', objstrings, offset).extend([0])) + +                        offset += 3 +                        byte = struct.unpack_from('>B', objstrings, offset)[0] +     +                tilelist.pop() +     +                if (upperslope[0] & 0x80) and (upperslope[0] & 0x2): +                    for i in range(lowerslope[1]): +                        pop = tilelist.pop() +                        tilelist.insert(0, pop) + +                Tileset.addObject(entry[2], entry[1], upperslope, lowerslope, tilelist) + +                tilelist = [[]] +                upperslope = [0, 0] +                lowerslope = [0, 0] + +            Tileset.slot = Tileset.objects[0].tiles[0][0][2] & 3 +            self.tileWidget.tilesetType.setText('Pa{0}'.format(Tileset.slot)) + +            self.setuptile() +            SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles) + +        self.name = path + + +    def openImage(self): +        '''Opens an Image from png, and creates a new tileset from it.''' + +        path = QtGui.QFileDialog.getOpenFileName(self, "Open Image", '', +                    "Image Files (*.png)") +                     +        if path: +            newImage = QtGui.QPixmap() +            self.tileImage = newImage + +            if not newImage.load(path): +                QtGui.QMessageBox.warning(self, "Open Image", +                        "The image file could not be loaded.", +                        QtGui.QMessageBox.Cancel) +                return + +            if ((newImage.width() == 768) & (newImage.height() == 384)): +                x = 0 +                y = 0 +                for i in range(512): +                    Tileset.tiles[i].image = self.tileImage.copy(x*24,y*24,24,24) +                    x += 1 +                    if (x * 24) >= 768: +                        y += 1 +                        x = 0 + +            else:  +                QtGui.QMessageBox.warning(self, "Open Image", +                        "The image was not the proper dimensions." +                        "Please resize the image to 768x384 pixels.", +                        QtGui.QMessageBox.Cancel) +                return + + +            self.setuptile() + + +    def saveImage(self): +             +        fn = QtGui.QFileDialog.getSaveFileName(self, 'Choose a new filename', '', '.png (*.png)') +        if fn == '': return +         +        tex = QtGui.QPixmap(768, 384) +        tex.fill(QtCore.Qt.transparent) +        painter = QtGui.QPainter(tex) +         +        Xoffset = 0 +        Yoffset = 0 +         +        for tile in Tileset.tiles: +            painter.drawPixmap(Xoffset, Yoffset, tile.image) +            Xoffset += 24 +            if Xoffset >= 768: +                Xoffset = 0 +                Yoffset += 24 +                         +        painter.end() + +        tex.save(fn) +         +         +    def saveTileset(self): +        if self.name == '': +            self.saveTilesetAs() +            return +             +         +        outdata = self.saving(os.path.basename(self.name)[:-4]) +         +        fn = self.name +        f = open(fn, 'wb') +        f.write(outdata) +        f.close() +                 +         +    def saveTilesetAs(self): +         +        fn = QtGui.QFileDialog.getSaveFileName(self, 'Choose a new filename', '', '.arc (*.arc)') +        if fn == '': return + +        self.name = fn +        self.setWindowTitle(os.path.basename(unicode(fn))) +         +        outdata = self.saving(os.path.basename(unicode(fn))[:-4]) +        f = open(fn, 'wb') +        f.write(outdata) +        f.close() + + +    def saving(self, name): + +        # Prepare tiles, objects, object metadata, and textures and stuff into buffers. + +        textureBuffer = self.PackTexture() +        objectBuffers = self.PackObjects() +        objectBuffer = objectBuffers[0] +        objectMetaBuffer = objectBuffers[1] +        groupBuffer = self.PackGroups() + +                 +        # Make an arc and pack up the files! +        arc = archive.U8() +        arc['BG_tex'] = None +        arc['BG_tex/{0}_tex.bin'.format(name)] = textureBuffer + +        arc['BG_unt'] = None +        arc['BG_unt/{0}.bin'.format(name)] = objectBuffer +        arc['BG_unt/{0}_hd.bin'.format(name)] = objectMetaBuffer +         +        arc['BG_grp'] = None +        arc['BG_grp/{0}_grp.bin'.format(name)] = groupBuffer + +        return arc._dump() + + +    def PackTexture(self): + +        tex = QtGui.QImage(1024, 512, QtGui.QImage.Format_ARGB32) +        tex.fill(QtCore.Qt.transparent) +        painter = QtGui.QPainter(tex) +         +        Xoffset = 0 +        Yoffset = 0 + +        for tile in Tileset.tiles: +            minitex = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32) +            minitex.fill(QtCore.Qt.transparent) +            minipainter = QtGui.QPainter(minitex) +             +            minipainter.drawPixmap(4, 4, tile.image) +            minipainter.end() +             +            # Read colours and DESTROY THEM (or copy them to the edges, w/e) +            for i in xrange(4,28): +                 +                # Top Clamp +                colour = minitex.pixel(i, 4) +                for p in xrange(0,4): +                    minitex.setPixel(i, p, colour) +                 +                # Left Clamp +                colour = minitex.pixel(4, i) +                for p in xrange(0,4): +                    minitex.setPixel(p, i, colour) +                 +                # Right Clamp +                colour = minitex.pixel(i, 27) +                for p in xrange(27,31): +                    minitex.setPixel(i, p, colour) +                 +                # Bottom Clamp +                colour = minitex.pixel(27, i) +                for p in xrange(27,31): +                    minitex.setPixel(p, i, colour) + +            # UpperLeft Corner Clamp +            colour = minitex.pixel(4, 4) +            for x in xrange(0,4): +                for y in xrange(0,4): +                    minitex.setPixel(x, y, colour) + +            # UpperRight Corner Clamp +            colour = minitex.pixel(27, 4) +            for x in xrange(27,31): +                for y in xrange(0,4): +                    minitex.setPixel(x, y, colour) + +            # LowerLeft Corner Clamp +            colour = minitex.pixel(4, 27) +            for x in xrange(0,4): +                for y in xrange(27,31): +                    minitex.setPixel(x, y, colour) + +            # LowerRight Corner Clamp +            colour = minitex.pixel(27, 27) +            for x in xrange(27,31): +                for y in xrange(27,31): +                    minitex.setPixel(x, y, colour) + +                     +            painter.drawImage(Xoffset, Yoffset, minitex) +             +            Xoffset += 32 +             +            if Xoffset >= 1024: +                Xoffset = 0 +                Yoffset += 32 +                                     +        painter.end() + +        dest = RGB4A3Encode(tex) +                 +        return dest + + + +    def PackObjects(self): +        objectStrings = [] +         +        o = 0 +        for object in Tileset.objects: +                  +                 +            # Slopes +            if object.upperslope[0] != 0: +                 +                # Reverse Slopes +                if object.upperslope[0] & 0x2: +                    a = struct.pack('>B', object.upperslope[0]) +                     +                    if object.height == 1: +                        iterationsA = 0 +                        iterationsB = 1 +                    else: +                        iterationsA = object.upperslope[1] +                        iterationsB = object.lowerslope[1] + object.upperslope[1] +                         +                    for row in xrange(iterationsA, iterationsB): +                        for tile in object.tiles[row]: +                            a = a + struct.pack('>BH', tile[0], tile[1]) +                        a = a + '\xfe' + +                    if object.height > 1: +                        a = a + struct.pack('>B', object.lowerslope[0]) +                     +                        for row in xrange(0, object.upperslope[1]): +                            for tile in object.tiles[row]: +                                a = a + struct.pack('>BH', tile[0], tile[1]) +                            a = a + '\xfe' +                         +                    a = a + '\xff' +                     +                    objectStrings.append(a) +                     +                     +                # Regular Slopes    +                else: +                    a = struct.pack('>B', object.upperslope[0]) +                     +                    for row in xrange(0, object.upperslope[1]): +                        for tile in object.tiles[row]: +                            a = a + struct.pack('>BH', tile[0], tile[1]) +                        a = a + '\xfe' +                     +                    if object.height > 1: +                        a = a + struct.pack('>B', object.lowerslope[0]) +                     +                        for row in xrange(object.upperslope[1], object.height): +                            for tile in object.tiles[row]: +                                a = a + struct.pack('>BH', tile[0], tile[1]) +                            a = a + '\xfe' +                         +                    a = a + '\xff' +                     +                    objectStrings.append(a) +                     +                     +            # Not slopes!     +            else: +                a = '' +                 +                for tilerow in object.tiles: +                    for tile in tilerow: +                        a = a + struct.pack('>BH', tile[0], tile[1]) +                     +                    a = a + '\xfe' +                     +                a = a + '\xff' +                 +                objectStrings.append(a) +             +            o += 1 +             +        Objbuffer = '' +        Metabuffer = '' +        i = 0 +        for a in objectStrings: +            Metabuffer = Metabuffer + struct.pack('>H2B', len(Objbuffer), Tileset.objects[i].width, Tileset.objects[i].height) +            Objbuffer = Objbuffer + a +             +            i += 1 +         +        return (Objbuffer, Metabuffer) + + +     +    def PackGroups(self): + +        groupList = [] +        for kiddy in xrange(self.treeki.topLevelItemCount()): +            self.walkTree(groupList, self.treeki.topLevelItem(kiddy)); + +        return cPickle.dumps(groupList) + + +    def walkTree(self, stringsList, treeItem): +         +        for kiddy in xrange(treeItem.childCount()): +            newList = [] +            self.walkTree(newList, treeItem.child(kiddy)) + +        if treeItem.childCount() > 0: +            stringsList.append(str(treeItem.text(0)), newList) +        else: +            stringsList.append(str(treeItem.text(0))) + + +    def setupMenus(self): +        fileMenu = self.menuBar().addMenu("&File") + +        pixmap = QtGui.QPixmap(60,60) +        pixmap.fill(QtCore.Qt.black) +        icon = QtGui.QIcon(pixmap) + +        self.action = fileMenu.addAction(icon, "New", self.newTileset, QtGui.QKeySequence.New) +        fileMenu.addAction("Open...", self.openTileset, QtGui.QKeySequence.Open) +        fileMenu.addAction("Import Image...", self.openImage, QtGui.QKeySequence('Ctrl+I')) +        fileMenu.addAction("Export Image...", self.saveImage, QtGui.QKeySequence('Ctrl+E')) +        fileMenu.addAction("Save", self.saveTileset, QtGui.QKeySequence.Save) +        fileMenu.addAction("Save as...", self.saveTilesetAs, QtGui.QKeySequence.SaveAs) +        fileMenu.addAction("Quit", self.close, QtGui.QKeySequence('Ctrl-Q')) + +        taskMenu = self.menuBar().addMenu("&Tasks") + +        taskMenu.addAction("Toggle Alpha", self.toggleAlpha, QtGui.QKeySequence('Ctrl+Shift+A')) +        taskMenu.addAction("Clear Object Data", Tileset.clearObjects, QtGui.QKeySequence('Ctrl+Alt+Backspace')) + + + +    def toggleAlpha(self): +        # Replace Alpha Image with non-Alpha images in model +        if self.alpha == True: +            self.alpha = False +        else: +            self.alpha = True + +        self.setuptile() +         + +    def TriggerNewGroup(self): + +        a = QtGui.QTreeWidgetItem(self.treeki) +        a.setText(0, 'Double Click to Rename') +        a.setFlags(QtCore.Qt.ItemFlags(0x2F)) +        a.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ShowIndicator) +        a.setExpanded(True) + + +    def TriggerDelGroup(self): +         +        treecko = self.treeki.currentItem() +        if str(treecko.text(0)).find("Object"): +            eggs = treecko.takeChildren() + +            self.treeki.addTopLevelItems(eggs) + +            index = window.treeki.indexFromItem(treecko, 0) +            realx = index.row() + +            if treecko.parent(): +                y = treecko.parent().takeChild(realx) +            else: +                y = self.treeki.takeTopLevelItem(realx) + +            del y + + + +    def setupWidgets(self): +        frame = QtGui.QFrame() +        frameLayout = QtGui.QHBoxLayout(frame) + +        # Displays the tiles +        self.tileDisplay = displayWidget() +                 +        # Sets up the model for the tile pieces +        self.model = PiecesModel(self) +        self.tileDisplay.setModel(self.model) + +        # Object List +        self.objectList = objectList() +        self.objmodel = QtGui.QStandardItemModel() +        SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles) +        self.objectList.setModel(self.objmodel) + +        self.tileWidget = tileOverlord() + +        # Vertical Container A +        self.container = QtGui.QWidget() +        layout = QtGui.QVBoxLayout() +        layout.addWidget(self.tileDisplay) +        layout.addWidget(self.tileWidget) +        self.container.setLayout(layout) + + +        # Create the Group Tree +        self.treeki = QtGui.QTreeWidget() +        self.treeki.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) +        self.treeki.setDragEnabled(True) +        self.treeki.setDragDropMode(QtGui.QAbstractItemView.InternalMove) +        self.treeki.setAcceptDrops(True) +        self.treeki.setDropIndicatorShown(True) +        self.treeki.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked) +        self.treeki.setColumnCount(2) +        self.treeki.setColumnWidth(0, 200) + +        self.treeki.setColumnWidth(1, 40) + + +        # Vertical Container B +        self.treeBario = QtGui.QWidget() +        tlayout = QtGui.QVBoxLayout() +        tlayout.addWidget(self.treeki) + +        self.groupbar = QtGui.QToolBar() + +        self.newGroup = QtGui.QAction('New Group', self.groupbar) +        self.newGroup.triggered.connect(self.TriggerNewGroup) +        self.newGroup.setShortcut(QtGui.QKeySequence('Ctrl+Shift+N')) + +        self.delGroup = QtGui.QAction('Delete Group', self.groupbar) +        self.delGroup.triggered.connect(self.TriggerDelGroup) +        self.delGroup.setShortcut(QtGui.QKeySequence('Ctrl+Shift+Del')) + +        self.groupbar.addAction(self.newGroup) +        self.groupbar.addAction(self.delGroup) + +        self.groupbar.setFloatable(False) + +        tlayout.addWidget(self.groupbar) +        self.treeBario.setLayout(tlayout) + + +        # Creates the Tab Widget for behaviours and objects +        self.tabWidget = QtGui.QTabWidget() + +        # Sets the Tabs +        self.tabWidget.addTab(self.objectList, 'Object List') +        self.tabWidget.addTab(self.treeBario, 'Object Groups') +     + + +        # Connections do things! +        self.objectList.clicked.connect(self.tileWidget.setObject) +        self.treeki.itemClicked.connect(connectToTileWidget) + + +        frameLayout.addWidget(self.container) +        frameLayout.addWidget(self.tabWidget) +         + +        self.setCentralWidget(frame) +                 + + + +############################################################################################# +####################################### Main Function ####################################### + + +if __name__ == '__main__': + +    import sys + +    app = QtGui.QApplication(sys.argv) +    window = MainWindow() +    window.show() +    sys.exit(app.exec_()) +    app.deleteLater()
\ No newline at end of file diff --git a/Puzzle+/windows_build.py b/Puzzle+/windows_build.py new file mode 100755 index 0000000..10d710c --- /dev/null +++ b/Puzzle+/windows_build.py @@ -0,0 +1,81 @@ +from distutils.core import setup
 +from py2exe.build_exe import py2exe
 +import os, os.path, shutil, sys
 +
 +upxFlag = False
 +if '-upx' in sys.argv:
 +    sys.argv.remove('-upx')
 +    upxFlag = True
 +
 +dir = 'distrib/windows'
 +
 +print '[[ Running Puzzle Through py2exe! ]]'
 +print 'Note: Puzzle MUST have NSMBlib-0.5a or none at all to function.'
 +print '>> Destination directory: %s' % dir
 +sys.argv.append('py2exe')
 +
 +if os.path.isdir(dir): shutil.rmtree(dir)
 +os.makedirs(dir)
 +
 +# exclude QtWebKit to save space, plus Python stuff we don't use
 +excludes = ['encodings', 'doctest', 'pdb', 'unittest', 'difflib', 'inspect',
 +    'os2emxpath', 'posixpath', 'optpath', 'locale', 'calendar',
 +    'threading', 'select', 'socket', 'hashlib', 'multiprocessing', 'ssl',
 +    'PyQt4.QtWebKit', 'PyQt4.QtNetwork']
 +
 +# set it up
 +setup(
 +    name='Puzzle',
 +    version='1.0',
 +    description='Puzzle - Tileset Editor',
 +    windows=[
 +        {'script': 'puzzle.py',
 +         }
 +    ],
 +    options={'py2exe':{
 +        'includes': ['sip', 'encodings', 'encodings.hex_codec', 'encodings.utf_8'],
 +        'compressed': 1,
 +        'optimize': 2,
 +        'excludes': excludes,
 +        'bundle_files': 3,
 +        'dist_dir': dir
 +    }}
 +)
 +
 +print '>> Built frozen executable!'
 +
 +# now that it's built, configure everything
 +os.unlink(dir + '/w9xpopen.exe') # not needed
 +
 +if upxFlag:
 +    if os.path.isfile('upx.exe'):
 +        print '>> Found UPX, using it to compress the executables!'
 +        files = os.listdir(dir)
 +        upx = []
 +        for f in files:
 +            if f.endswith('.exe') or f.endswith('.dll') or f.endswith('.pyd'):
 +                upx.append('"%s/%s"' % (dir,f))
 +        os.system('upx -9 ' + ' '.join(upx))
 +        print '>> Compression complete.'
 +    else:
 +        print '>> UPX not found, binaries can\'t be compressed.'
 +        print '>> In order to build Reggie! with UPX, place the upx.exe file into '\
 +              'this folder.'
 +
 +if os.path.isdir(dir + '/Icons'): shutil.rmtree(dir + '/Icons') 
 +if os.path.isdir(dir + '/nsmblib-0.5a'): shutil.rmtree(dir + '/nsmblib-0.5a') 
 +shutil.copytree('Icons', dir + '/Icons') 
 +shutil.copytree('nsmblib-0.5a', dir + '/nsmblib-0.5a') 
 +shutil.copy('license.txt', dir)
 +
 +print '>> Attempting to copy VC++2008 libraries...'
 +if os.path.isdir('Microsoft.VC90.CRT'):
 +    shutil.copytree('Microsoft.VC90.CRT', dir + '/Microsoft.VC90.CRT')
 +    print '>> Copied libraries!'
 +else:
 +    print '>> Libraries not found! The frozen executable will require the '\
 +          'Visual C++ 2008 runtimes to be installed in order to work.'
 +    print '>> In order to automatically include the runtimes, place the '\
 +          'Microsoft.VC90.CRT folder into this folder.'
 +
 +print '>> Reggie has been frozen to %s!' % dir
 | 
