Sonic Mania Sprite Ripper
# Written by Skyth | |
import struct | |
import os | |
import sys | |
from PIL import Image | |
import images2gif | |
def mkdir(path): | |
if not os.path.exists(path): | |
os.mkdir(path) | |
def readUInt(f): | |
return struct.unpack("<I", f.read(4))[0] | |
def readInt(f): | |
return struct.unpack("<i", f.read(4))[0] | |
def readUShort(f): | |
return struct.unpack("<H", f.read(2))[0] | |
def readShort(f): | |
return struct.unpack("<h", f.read(2))[0] | |
def readByte(f): | |
return struct.unpack("B", f.read(1))[0] | |
def readSByte(f): | |
return struct.unpack("b", f.read(1))[0] | |
def readString(f): | |
length = readByte(f) | |
return f.read(length)[:-1] | |
def makeBox(width, height): | |
return ((0, 0), (width, height)) | |
def combineBox(box1, box2): | |
return ( | |
(min(box1[0][0], box2[0][0]), | |
min(box1[0][1], box2[0][1])), | |
(max(box1[1][0], box2[1][0]), | |
max(box1[1][1], box2[1][1]))) | |
def moveBox(box, val): | |
return ( | |
(box[0][0] + val[0], | |
box[0][1] + val[1]), | |
(box[1][0] + val[0], | |
box[1][1] + val[1])) | |
def boxWidth(box): | |
return box[1][0] - box[0][0] | |
def boxHeight(box): | |
return box[1][1] - box[0][1] | |
def convert(path): | |
rootDir = os.path.dirname(path) | |
outDir = os.path.splitext(path)[0] | |
mkdir(outDir) | |
f = open(path, "rb") | |
signature = readUInt(f) | |
totalFrameCount = readUInt(f) | |
spriteSheetsCount = readByte(f) | |
spriteSheets = [] | |
for i in xrange(spriteSheetsCount): | |
spriteSheet = readString(f) | |
spriteSheetPath = os.path.join(os.path.dirname(path), "..", spriteSheet) | |
if not os.path.exists(spriteSheetPath): | |
spriteSheetPath = os.path.join(rootDir, os.path.basename(spriteSheet)) | |
if not os.path.exists(spriteSheetPath): | |
spriteSheets.append(None) | |
else: | |
spriteSheets.append(Image.open(spriteSheetPath)) | |
print("Sprite Sheet: {}".format(spriteSheet)) | |
hitboxesCount = readByte(f) | |
for i in xrange(hitboxesCount): | |
print("Hitbox: {}".format(readString(f))) | |
animationsCount = readUShort(f) | |
for i in xrange(animationsCount): | |
name = readString(f) | |
print("Animation: {}".format(name)) | |
if "/" in name: | |
name = name.replace("/", "&") | |
frameCount = readUShort(f) | |
speed = readShort(f) | |
loopIndex = readByte(f) | |
flags = readByte(f) | |
outputGifFile = os.path.join(outDir, name) | |
frames = [] | |
box = makeBox(0, 0) | |
for j in xrange(frameCount): | |
spriteSheetIndex = readByte(f) | |
if spriteSheetIndex >= spriteSheetsCount or spriteSheets[spriteSheetIndex] == None: | |
print "Unhandled case! No conversion will be done." | |
return | |
duration = readShort(f) | |
identifier = readShort(f) | |
x = readUShort(f) | |
y = readUShort(f) | |
width = readUShort(f) | |
height = readUShort(f) | |
originX = readShort(f) | |
originY = readShort(f) | |
frameBox = makeBox(width, height) | |
frameBox = moveBox(frameBox, (originX, originY)) | |
frameSpeed = 0 | |
if duration > 0: | |
frameSpeed = (speed * 64) / duration | |
if frameSpeed > 0: | |
frameSpeed = 1024 / frameSpeed | |
frames.append(( | |
spriteSheetIndex, | |
x, y, width, height, | |
originX, originY, | |
frameBox, frameSpeed / 960.0)) | |
box = combineBox(box, frameBox) | |
for h in xrange(hitboxesCount): | |
left = readShort(f) | |
top = readShort(f) | |
right = readShort(f) | |
bottom = readShort(f) | |
if len(frames): | |
imgWidth = boxWidth(box) | |
imgHeight = boxHeight(box) | |
images = [] | |
for frame in frames: | |
imgCrop = spriteSheets[frame[0]].crop(( | |
frame[1], | |
frame[2], | |
frame[1] + frame[3], | |
frame[2] + frame[4])) | |
img = Image.new("P", (imgWidth, imgHeight)) | |
img.putpalette(imgCrop.getpalette()) | |
img.paste(imgCrop, (frame[7][0][0] - box[0][0], frame[7][0][1] - box[0][1])) | |
images.append(img) | |
images2gif.writeGif(outputGifFile + ".gif", images, duration=[x[8] for x in frames], subRectangles=False) | |
f.close() | |
if __name__ == "__main__": | |
if len(sys.argv) < 2: | |
print("maniaAnim.py [*.bin]") | |
else: | |
convert(sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment