From c4e5f6b3191498e273965224eb827bf83966d3ff Mon Sep 17 00:00:00 2001
From: Colin Noga <Tempus@Spectrum-Song.local>
Date: Fri, 4 Nov 2011 04:17:20 -0500
Subject: Added Puzzle+, it probably correctly makes and saves map tilesets,
 but something is broken on opening. Also added a group feature for my own
 sake

---
 Puzzle+/.DS_Store        |  Bin 0 -> 6148 bytes
 Puzzle+/archive.py       |  205 +++++
 Puzzle+/common.py        |  364 +++++++++
 Puzzle+/license.txt      |  339 +++++++++
 Puzzle+/nsmblib-0.5a.zip |  Bin 0 -> 9056 bytes
 Puzzle+/puzzle+.py       | 1858 ++++++++++++++++++++++++++++++++++++++++++++++
 Puzzle+/windows_build.py |   81 ++
 7 files changed, 2847 insertions(+)
 create mode 100644 Puzzle+/.DS_Store
 create mode 100755 Puzzle+/archive.py
 create mode 100755 Puzzle+/common.py
 create mode 100755 Puzzle+/license.txt
 create mode 100644 Puzzle+/nsmblib-0.5a.zip
 create mode 100755 Puzzle+/puzzle+.py
 create mode 100755 Puzzle+/windows_build.py

diff --git a/Puzzle+/.DS_Store b/Puzzle+/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/Puzzle+/.DS_Store differ
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.zip
new file mode 100644
index 0000000..bca75e2
Binary files /dev/null and b/Puzzle+/nsmblib-0.5a.zip differ
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
-- 
cgit v1.2.3