Skip to content

Instantly share code, notes, and snippets.

@Epicpkmn11
Last active March 4, 2022 09:05
Show Gist options
  • Save Epicpkmn11/988b2a3cac73a907cdd8e483209aae4a to your computer and use it in GitHub Desktop.
Save Epicpkmn11/988b2a3cac73a907cdd8e483209aae4a to your computer and use it in GitHub Desktop.
DS FIFA image conversion scripts
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from PIL import Image
from sys import exit
def dst2png(args):
for input in args.inputs:
print(input.name)
if(input.read(4) != b"DST1"):
print("Error: Is this really a DST file?")
exit()
if args.output:
output = args.output
else:
output = input.name[:input.name.rfind(".")] + ".png"
# First two are currently unknown what they mean
_, _, width, height, colors = struct.unpack("<LLHHH", input.read(0xE))
bitmapSize = width * height // (2 if colors == 16 else 1)
print(f"{width}x{height}, {colors} colors") # , {'alpha' if alpha else 'no alpha'}")
if colors > 0:
# Convert from DS style to normal RGB palette
palette = [0] * colors * 3
for i in range(colors):
color = struct.unpack("<H", input.read(2))[0]
palette[i * 3] = round((color & 0x1F) * 255 / 31)
palette[i * 3 + 1] = round(((color >> 5) & 0x1F) * 255 / 31)
palette[i * 3 + 2] = round(((color >> 10) & 0x1F) * 255 / 31)
# Get bitmap data, if 16 color then extract the two nibbles
data = b""
if colors == 16:
for byte in input.read(bitmapSize):
data += struct.pack("BB", byte & 0xF, byte >> 4)
else:
data = input.read(bitmapSize)
img = Image.frombytes("P", (width, height), data)
img.putpalette(palette)
else:
print("Error: No colors??")
exit()
# Save the image
img.save(output)
if __name__ == "__main__":
dst2pngarg = argparse.ArgumentParser(description="Converts a DST file to image(s)")
dst2pngarg.add_argument("inputs", metavar="in.dst", nargs="*", type=argparse.FileType("rb"), help="input file(s)")
dst2pngarg.add_argument("--output", "-o", metavar="out.png", type=str, help="output name")
exit(dst2png(dst2pngarg.parse_args()))
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from os.path import basename
from PIL import Image
from sys import exit
def png2dst(args):
for input in args.inputs:
print(basename(input))
if args.output:
output = args.output
else:
output = open(input[:input.rfind(".")] + ".dst", "wb")
with Image.open(input) as img:
if img.mode != "P":
img = img.convert("RGB").quantize()
# Write header
# First 8 bytes after magic are unknown atm
colors = len(img.palette.palette) // 3
output.write(b"DST1" + struct.pack("<LLHHH", 0x01010102, 3, *img.size, colors))
print(f"{img.width}x{img.height}, {colors} colors")
# Palette data
if img.palette:
pal = b""
for i in range(len(img.palette.palette) // 3):
r, g, b = [round(x * 31 / 255) & 0x1F for x in img.palette.palette[i * 3:i * 3 + 3]]
pal += struct.pack("<H", 1 << 15 | b << 10 | g << 5 | r)
output.write(pal)
# Bitmap data
if colors == 16: # 16 color
data = b""
bytes = img.tobytes()
for i in range(len(bytes) // 2):
lower, upper = bytes[i * 2:i * 2 + 2]
data += struct.pack("B", (lower & 0xF) | ((upper & 0xF) << 4))
output.write(data)
elif colors > 0: # 256 color
output.write(img.tobytes())
else:
print("Error: Invalid color count??")
exit()
if __name__ == "__main__":
png2dstarg = argparse.ArgumentParser(description="Converts an image to a DST")
png2dstarg.add_argument("inputs", metavar="in.png", nargs="*", type=str, help="input image(s)")
png2dstarg.add_argument("--output", "-o", metavar="out.dst", type=argparse.FileType("wb"), help="output file")
exit(png2dst(png2dstarg.parse_args()))
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from os.path import basename
from PIL import Image
from sys import exit
def png2tbf(args):
for input in args.inputs:
print(basename(input))
if args.output:
output = args.output
else:
output = open(input[:input.rfind(".")] + ".tbf", "wb")
with Image.open(input) as img:
if img.mode != "P":
img = img.convert("RGB").quantize(256)
# Get palette, converted to DS format
for i in range(len(img.palette.palette) // 3):
r, g, b = [round(x * 31 / 255) & 0x1F for x in img.palette.palette[i * 3:i * 3 + 3]]
output.write(struct.pack("<H", 1 << 15 | b << 10 | g << 5 | r))
# If the image is 128x128, write the image data
if img.size == (128, 128):
output.write(img.tobytes())
if __name__ == "__main__":
png2tbfarg = argparse.ArgumentParser(description="Converts an image to a TBF")
png2tbfarg.add_argument("inputs", metavar="in.png", nargs="*",type=str, help="input image(s)")
png2tbfarg.add_argument("--output", "-o", metavar="out.tbf", type=argparse.FileType("wb"), help="output file")
exit(png2tbf(png2tbfarg.parse_args()))
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from os.path import basename
from PIL import Image
from sys import exit
def png2tlb(args):
print(" ".join([basename(x) for x in args.inputs]))
if args.output:
output = args.output
else:
output = open(args.inputs[0][:args.inputs[0].rfind(".")] + ".tlb", "wb")
# Image count
output.write(struct.pack("<H", len(args.inputs)))
for i, image in enumerate(args.inputs):
img = Image.open(image)
# Quantize if given an RGB(A) image unless alpha only (solid white + alpha)
alpha = img.mode == "RGBA"
if img.mode != "P" and img.convert("RGB").tobytes() != b"\xFF" * img.width * img.height * 3:
img = img.convert("RGB").quantize(colors=args.colors)
if img.palette and (0xFF, 0x00, 0xFF) in img.palette.colors and img.palette.colors[(0xFF, 0x00, 0xFF)] == 0:
alpha = True
# Image header
type = 0x00
colors = 0
if img.mode == "RGBA":
type = 0x04
else:
if len(img.palette.colors) <= 16:
type = 0x10
colors = 16
else:
colors = 256
if alpha:
type |= 0x02
widthShift = 0
while (8 << widthShift) < img.width:
widthShift += 1
if 8 << widthShift > img.width:
print(f"Error: Invalid width ({img.width})")
exit()
heightShift = 0
while (8 << heightShift) < img.height:
heightShift += 1
if 8 << heightShift > img.height:
print(f"Error: Invalid height ({img.height})")
exit()
bitmapSize = img.width * img.height
if colors == 16:
bitmapSize //= 2
print(f"Image {i}, type 0x{type:X}, shift size {widthShift}x{heightShift}, bitmap 0x{bitmapSize:X} bytes")
output.write(struct.pack("<LLLL", type, widthShift, heightShift, bitmapSize))
# Bitmap data
if colors == 16: # 16 color
data = b""
bytes = img.tobytes()
for i in range(len(bytes) // 2):
lower, upper = bytes[i * 2:i * 2 + 2]
data += struct.pack("B", (lower & 0xF) | ((upper & 0xF) << 4))
output.write(data)
elif colors > 0: # 256 color
output.write(img.tobytes())
else: # alpha
output.write(img.getchannel("A").tobytes())
# Palette data
if img.palette:
pal = b""
for i in range(len(img.palette.palette) // 3):
r, g, b = [round(x * 31 / 255) & 0x1F for x in img.palette.palette[i * 3:i * 3 + 3]]
pal += struct.pack("<H", 1 << 15 | b << 10 | g << 5 | r)
output.write(pal)
if __name__ == "__main__":
png2tlbarg = argparse.ArgumentParser(description="Converts image(s) to a TLB")
png2tlbarg.add_argument("inputs", metavar="in.png", nargs="*", type=str, help="input image(s)")
png2tlbarg.add_argument("--output", "-o", metavar="out.tlb", type=argparse.FileType("wb"), help="output file")
png2tlbarg.add_argument("--colors", "-c", type=int, help="number of colors in the palette (only used if input is RGB(A))")
exit(png2tlb(png2tlbarg.parse_args()))
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from os import SEEK_END, SEEK_SET
from PIL import Image
from sys import exit
def tbf2png(args):
for input in args.inputs:
print(input.name)
if args.output:
output = args.output
else:
output = input.name[:input.name.rfind(".")] + ".png"
# Check file size
input.seek(0, SEEK_END)
size = input.tell()
input.seek(0, SEEK_SET)
# Either a 16 or 256 color palette, sometimes bitmap data sometimes not
if (size % 0x4000) == 0x20:
colors = 16
elif (size % 0x4000) == 0x200:
colors = 256
# Get palette
palette = [0] * colors * 3 # 256 color palette with RGB separated
for i in range(colors):
color = struct.unpack("<H", input.read(2))[0]
palette[i * 3] = round((color & 0x1F) * 255 / 31)
palette[i * 3 + 1] = round(((color >> 5) & 0x1F) * 255 / 31)
palette[i * 3 + 2] = round(((color >> 10) & 0x1F) * 255 / 31)
# If there's bitmap data it's a 128x128 image
if size > 0x4000:
size = (128, 128)
data = input.read()
else: # Otherwise just put the colors
size = (colors, 1)
data = bytes([i for i in range(colors)])
img = Image.frombytes("P", size, data)
img.putpalette(palette)
img.save(output)
if __name__ == "__main__":
tbf2pngarg = argparse.ArgumentParser(description="Converts a TBF file to an image")
tbf2pngarg.add_argument("inputs", metavar="in.tbf", nargs="*", type=argparse.FileType("rb"), help="input file(s)")
tbf2pngarg.add_argument("--output", "-o", metavar="out.png", type=str, help="output name")
exit(tbf2png(tbf2pngarg.parse_args()))
#!/usr/bin/env python3
# Requirements:
# pip3 install pillow
"""
Copyright © 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
import argparse
import struct
from PIL import Image
from sys import exit
def tlb2png(args):
for input in args.inputs:
print(input.name)
if args.output:
output = args.output
else:
output = input.name[:input.name.rfind(".")] + ".png"
imageCount = struct.unpack("<H", input.read(2))[0]
for image in range(imageCount):
type, widthShift, heightShift, bitmapSize = struct.unpack("<LLLL", input.read(0x10))
# Get colors and if there's alpha from the 'type'
colors = 0
alpha = False
if type & 0x04: # Alpha only
alpha = True
elif type & 0x10: # 16 color
colors = 16
elif (type & ~0x02) == 0: # 256 color
colors = 256
else:
print(f"Error: supported type ({type})")
exit()
if type & 0x02:
alpha = True
# For some reason the size is stored so you need to shift 8 to it
width = 8 << widthShift
height = 8 << heightShift
print(f"Image {image}, {width}x{height}, {colors} colors, {'alpha' if alpha else 'no alpha'}")
# Get bitmap data, if 16 color then extract the two nibbles
data = b""
if colors == 16:
for byte in input.read(bitmapSize):
data += struct.pack("BB", byte & 0xF, byte >> 4)
else:
data = input.read(bitmapSize)
if colors > 0:
img = Image.frombytes("P", (width, height), data)
# Convert from DS style to normal RGB palette
palette = [0] * colors * 3
for i in range(colors):
color = struct.unpack("<H", input.read(2))[0]
palette[i * 3] = round((color & 0x1F) * 255 / 31)
palette[i * 3 + 1] = round(((color >> 5) & 0x1F) * 255 / 31)
palette[i * 3 + 2] = round(((color >> 10) & 0x1F) * 255 / 31)
img.putpalette(palette)
if alpha and args.alpha: # set color 0 to transparent
img = img.convert("RGBA")
alpha = Image.frombytes("L", (width, height), bytes([0 if x == 0 else 0xFF for x in data]))
img.putalpha(alpha)
elif alpha: # alpha only
img = Image.frombytes("RGB", (width, height), b"\xFF" * width * height * 3)
alpha = Image.frombytes("L", (width, height), data)
img.putalpha(alpha)
else:
print("Error: No colors or alpha??")
exit()
# Save the image, if more than one image in the TBL append a number
if(imageCount > 1):
img.save(f"{output[:output.rfind('.')]}-{image}{output[output.rfind('.'):]}")
else:
img.save(output)
if __name__ == "__main__":
tlb2pngarg = argparse.ArgumentParser(description="Converts a TLB file to image(s)")
tlb2pngarg.add_argument("inputs", metavar="in.tlb", nargs="*", type=argparse.FileType("rb"), help="input file(s)")
tlb2pngarg.add_argument("--output", "-o", metavar="out.png", type=str, help="output name")
tlb2pngarg.add_argument("--alpha", "-a", action="store_true", help="make transparent pixel transparent instead of #FF00FF (may break reverse conversion)")
exit(tlb2png(tlb2pngarg.parse_args()))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment