From 38e50fb9870ff1b253a28ba6143339727a5b476f Mon Sep 17 00:00:00 2001 From: Colin Noga Date: Fri, 4 Nov 2011 22:02:51 -0500 Subject: Koopuzzle now seems to be working 100%..., included a testing tileset called Test5.arc that covers all group use cases. --- Koopuzzle/.DS_Store | Bin 0 -> 6148 bytes Koopuzzle/Koopuzzle.py | 1878 ++++++++++++++++++++++++++++++++++++++++++++ Koopuzzle/archive.py | 205 +++++ Koopuzzle/common.py | 364 +++++++++ Koopuzzle/license.txt | 339 ++++++++ Koopuzzle/windows_build.py | 81 ++ Puzzle+/.DS_Store | Bin 6148 -> 0 bytes Puzzle+/archive.py | 205 ----- Puzzle+/common.py | 364 --------- Puzzle+/license.txt | 339 -------- Puzzle+/nsmblib-0.5a.zip | Bin 9056 -> 0 bytes Puzzle+/puzzle+.py | 1858 ------------------------------------------- Puzzle+/windows_build.py | 81 -- Test5.arc | Bin 0 -> 1049184 bytes 14 files changed, 2867 insertions(+), 2847 deletions(-) create mode 100644 Koopuzzle/.DS_Store create mode 100755 Koopuzzle/Koopuzzle.py create mode 100755 Koopuzzle/archive.py create mode 100755 Koopuzzle/common.py create mode 100755 Koopuzzle/license.txt create mode 100755 Koopuzzle/windows_build.py delete mode 100644 Puzzle+/.DS_Store delete mode 100755 Puzzle+/archive.py delete mode 100755 Puzzle+/common.py delete mode 100755 Puzzle+/license.txt delete mode 100644 Puzzle+/nsmblib-0.5a.zip delete mode 100755 Puzzle+/puzzle+.py delete mode 100755 Puzzle+/windows_build.py create mode 100644 Test5.arc diff --git a/Koopuzzle/.DS_Store b/Koopuzzle/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Koopuzzle/.DS_Store differ diff --git a/Koopuzzle/Koopuzzle.py b/Koopuzzle/Koopuzzle.py new file mode 100755 index 0000000..4962e5c --- /dev/null +++ b/Koopuzzle/Koopuzzle.py @@ -0,0 +1,1878 @@ +#!/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 + + + +######################################################## +# 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): + '''Tile Constructor''' + + self.image = image + + + 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): + '''Adds an tile class to the tile list with the passed image or parameters''' + + self.tiles.append(self.Tile(image)) + + + 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, treecko, group): + 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 (tile[1] != 0xFFFF): + 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 + + if group != None: + grovyle = cPickle.loads(group) + makeTree(grovyle, treecko) + + +def makeTree(grovyle, treecko): + + for razorLeaf in grovyle: + + if (type(razorLeaf) is str) and (razorLeaf[:6] == "Object"): + + pix = QtGui.QPixmap(24, 24) + pix.fill(QtCore.Qt.transparent) + painter = QtGui.QPainter(pix) + painter.drawPixmap(0, 0, pix) + painter.end() + + a = QtGui.QTreeWidgetItem(treecko) + a.setText(0, razorLeaf) + a.setFlags(QtCore.Qt.ItemFlags(0x25)) + a.setIcon(1, QtGui.QIcon(pix)) + + else: + + a = QtGui.QTreeWidgetItem(treecko) + a.setText(0, razorLeaf[0]) + a.setFlags(QtCore.Qt.ItemFlags(0x2F)) + a.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ShowIndicator) + a.setExpanded(True) + + + makeTree(razorLeaf[1], a) + + + + +@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() + + + # 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 = struct.unpack_from('>H', tex, i)[0] + # newpixel = (int(tex[i]) << 8) | int(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() + self.treeki.clear() + self.objmodel.clear() + + Tileset = TilesetClass() + + EmptyPix = QtGui.QPixmap(24, 24) + EmptyPix.fill(QtCore.Qt.black) + + for i in range(512): + Tileset.addTile(EmptyPix) + + self.setuptile() + self.setWindowTitle('New Tileset') + + + def openTileset(self): + '''Opens a Koopatlas tileset arc and parses the heck out of it.''' + + global Tileset + + path = str(QtGui.QFileDialog.getOpenFileName(self, "Open Koopatlas Tileset", '', + "Image Files (*.arc)")) + + if path: + self.setWindowTitle(os.path.basename(path)) + Tileset.clear() + self.treeki.clear() + self.objmodel.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'): + group = 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 + + dest = RGB4A3Decode(Image) + + self.tileImage = QtGui.QPixmap.fromImage(dest) + + # Makes us some nice Tile Classes! + Xoffset = 4 + Yoffset = 4 + for i in range(512): + Tileset.addTile(self.tileImage.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: + untile = struct.unpack_from('>BH', objstrings, offset) + retile = (untile[0], untile[1], 0) + + tilelist[len(tilelist)-1].append(retile) + + 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] + + self.setuptile() + SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles, self.treeki, group) + + self.tileWidget.tiles.updateList() + + 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(unicode(self.name))[:-4]) + + fn = unicode(self.name) + f = open(fn, 'wb') + f.write(outdata) + f.close() + + reply = QtGui.QMessageBox.information(self, "Save Complete", "Tileset saved as {0}.".format(os.path.basename(unicode(self.name))[:-4])) + + 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() + + reply = QtGui.QMessageBox.information(self, "Save Complete", "Tileset saved as {0}.".format(os.path.basename(unicode(self.name))[:-4])) + + + 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 = [] + + # Walk through all top level items + for kiddy in xrange(self.treeki.topLevelItemCount()): + groupList.append(self.walkTree(self.treeki.topLevelItem(kiddy))) + + return cPickle.dumps(groupList) + + + def walkTree(self, treeItem): + + # If item has kids, walk through each kid. If not, just add the string. + + name = str(treeItem.text(0)) + + alist = [] + + if treeItem.childCount() > 0: + + for kiddy in xrange(treeItem.childCount()): + alist.append(self.walkTree(treeItem.child(kiddy))) + + return (name, alist) + + else: + return name + + + 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("Clear Object Data", Tileset.clearObjects, QtGui.QKeySequence('Ctrl+Alt+Backspace')) + + + 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() + 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) + + + SetupObjectModel(self.objmodel, Tileset.objects, Tileset.tiles, self.treeki, None) + + + 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/Koopuzzle/archive.py b/Koopuzzle/archive.py new file mode 100755 index 0000000..b0495f5 --- /dev/null +++ b/Koopuzzle/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/Koopuzzle/common.py b/Koopuzzle/common.py new file mode 100755 index 0000000..792acec --- /dev/null +++ b/Koopuzzle/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/Koopuzzle/license.txt b/Koopuzzle/license.txt new file mode 100755 index 0000000..63e41a4 --- /dev/null +++ b/Koopuzzle/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. + + + Copyright (C) + + 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. + + , 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/Koopuzzle/windows_build.py b/Koopuzzle/windows_build.py new file mode 100755 index 0000000..10d710c --- /dev/null +++ b/Koopuzzle/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 diff --git a/Puzzle+/.DS_Store b/Puzzle+/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Puzzle+/.DS_Store and /dev/null differ diff --git a/Puzzle+/archive.py b/Puzzle+/archive.py deleted file mode 100755 index b0495f5..0000000 --- a/Puzzle+/archive.py +++ /dev/null @@ -1,205 +0,0 @@ -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 deleted file mode 100755 index 792acec..0000000 --- a/Puzzle+/common.py +++ /dev/null @@ -1,364 +0,0 @@ -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 deleted file mode 100755 index 63e41a4..0000000 --- a/Puzzle+/license.txt +++ /dev/null @@ -1,339 +0,0 @@ - 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. - - - Copyright (C) - - 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. - - , 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 deleted file mode 100644 index bca75e2..0000000 Binary files a/Puzzle+/nsmblib-0.5a.zip and /dev/null differ diff --git a/Puzzle+/puzzle+.py b/Puzzle+/puzzle+.py deleted file mode 100755 index 886a80a..0000000 --- a/Puzzle+/puzzle+.py +++ /dev/null @@ -1,1858 +0,0 @@ -#!/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 deleted file mode 100755 index 10d710c..0000000 --- a/Puzzle+/windows_build.py +++ /dev/null @@ -1,81 +0,0 @@ -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 diff --git a/Test5.arc b/Test5.arc new file mode 100644 index 0000000..2050257 Binary files /dev/null and b/Test5.arc differ -- cgit v1.2.3