Skip to content

Instantly share code, notes, and snippets.

@viliml
Last active May 24, 2019 20:02
Show Gist options
  • Save viliml/94b88cbe9c2cb33a201e20832aaa744c to your computer and use it in GitHub Desktop.
Save viliml/94b88cbe9c2cb33a201e20832aaa744c to your computer and use it in GitHub Desktop.
With much code copying from Nimms <nimms@ya.ru> and anon
#!/usr/bin/env python3
import sys
import glob
import os
import struct
import codecs
from PIL import Image, ImageTk
from itertools import product
class State:
def __init__(self, id, count, start):
self.id = id
self.count = count
self.start = start
self.overlays = {}
class Overlay:
def __init__(self, count, start):
self.count = count
self.start = start
class Sprite:
def __init__(self, path, width, height, states, records, table):
self.path = path
self.width = width
self.height = height
self.states = states
self.records = records
self.table = table
# endianness, depends on target platform (> = big, < = little)
BYTE_ORDER = '<'
BLOCK_SIZE = 32
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
NAMES = {'ama': 'Amanda', 'ayu': 'Ayumi', 'chi': 'Child Yu-no', 'eri': 'Eriko', 'hoj': 'Houjou', 'kan': 'Kanna', 'kao': 'Kaori', 'kon': 'Imperial Guard', 'kou': 'Koudai', 'mio': 'Mio', 'mit': 'Mitsuki', 'osy': 'Marina (Female Guard)', 'rou': 'Ume', 'ryu': 'Ryuuzouji', 'saa': 'Some miner guy', 'sab': 'Some other miner guy', 'ser': 'Sayless', 'sin': 'God-Empress', 'syo': 'Quarry Chief', 'tak': 'Takuya', 'toy': 'Toyotomi', 'yuk': 'Yuuki', 'yun': 'Yu-no'}
OVERLAYS = {'L': 'Lips', 'E': 'Eyelids'}
VARIANTS = {'': 'Base',
'_2': 'Night', '_2b': 'Night B', '_2bc': 'Night B Corpse', '_2bc2': 'Night B Corpse 2', '_2c': 'Night C', '_2d': 'Night D', '_2t': 'Night Telephone',
'_3': 'Evening', '_4': 'Rain',
'r': 'Reverse', 'rc': 'Reverse C', 'rd': 'Reverse D', 're': 'Reverse E', 't': 'Telephone', 'y': 'Yu-no',
'b': 'B', 'b2': 'B Shadow', 'b_2': 'B Night', 'bc': 'B+C',
'c': 'C', 'c2': 'C Item', 'c_2': 'C Night',
'd': 'D', 'd_2': 'D Night', 'dt': 'D Telephone',
'e': 'E', 'e_2': 'E Night',
'f': 'F', 'f_2': 'F Night',
'g': 'G', 'g_2': 'G Night',
'h': 'H', 'h_2': 'H Night',
'i': 'I',
'j': 'J'}
INVERSE = {val: key for key, val in VARIANTS.items()}
args = sys.argv[1:]
if len(sys.argv) <= 1:
args = glob.glob('*.mvl')
else:
for i, arg in enumerate(args):
if '*' in arg:
args = args[:i] + glob.glob(arg) + args[i+1:]
sprites = {}
for arg in args:
arg = os.path.abspath(os.path.splitext(arg)[0])
if arg[-1] == '_':
arg = arg[:-1]
arg_basename = os.path.basename(arg)
# only do BGs
if not any(c.isdigit() for c in arg_basename):
continue
if arg_basename[-3] != 'h':
continue
#print(arg_basename)
name = arg_basename[4:7]
num = arg_basename[-2:]
records_count = 0
table_count = 0
states = []
records = []
table = []
table_start = None
with open(arg + '_.mvl', 'rb') as f:
# 4 chars: MVL1
f.read(4)
# uint32: count of states
states_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(states_count)
# uint32: input width
input_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(input_width)
# 20 zeros
f.read(20)
# 64 characters
shit = str(f.read(64), encoding='ascii').strip('\0')
#print(shit)
for i in range(states_count):
# uint32: output width
output_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: output height
output_height = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: idk
idk = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 4 zeros
f.read(4)
# uint32: count of coordinates records
records_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of coordinates records
records_start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: count of records to read
count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of records to read
start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 32 chars: state name
id = str(f.read(32), encoding='ascii').strip('\0')
if table_start == None:
table_start = start
start = (start - table_start) // 2
table_count += count
if len(id) == 9:
states.append(State(id, count, start))
elif len(id) == 11:
states[-1].overlays.setdefault(id[-2], []).append(Overlay(count, start))
else:
print("weird!")
#print(id, count, start, sep='\t')
#print()
#print(output_width, output_height)
#print()
#print(records_count)
for i in range(records_count):
# float32: block position on the screen, X
x1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# float32: block position on the screen, Y
y1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# 4 zeros
f.read(4)
# float32: block position in the image, X
x2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
# float32: block position in the image, Y
y2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
#print(x1, y1, x2, y2)
records.append((x1, y1, x2, y2))
#print()
#print(map_count)
for i in range(table_count):
(idx,) = struct.unpack(BYTE_ORDER + 'H', f.read(2))
table.append(idx)
sprites.setdefault(num, {})[name] = Sprite(arg + '.png', output_width, output_height, states, records, table)
bgs = {}
for path in glob.glob('bgs/yng_h*.png'):
path = os.path.abspath(os.path.splitext(path)[0])
if path[-1] == '_':
path = path[:-1]
basename = os.path.basename(path)
num = basename[5:7]
ext = basename[7:]
bgs.setdefault(num, []).append(ext)
sprites.setdefault(num, {})
import tkinter as tk
from tkinter import ttk
class Application(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
self.base_im = None
self.display = None
self.variant_label = None
self.variant_picker = None
self.labels = {}
self.pickers = {}
self.compressed_ims = {}
self.labels2s = {}
self.scaless = {}
def select_bg(self, dummy_event=None):
if self.variant_label:
self.variant_label.grid_forget()
if self.variant_picker:
self.variant_picker.grid_forget()
for label in self.labels.values():
label.grid_forget()
for picker in self.pickers.values():
picker.grid_forget()
for compressed_im in self.compressed_ims.values():
compressed_im.close()
for labels2 in self.labels2s:
for label in labels:
label.grid_forget()
for scales in self.scaless:
for scale in scales:
scale.grid_forget()
self.variant_label = ttk.Label(self, text='Variant')
self.variant_label.grid(row=0,column=1)
self.variant_picker = ttk.Combobox(self, state='readonly', values=[VARIANTS[x] for x in bgs[self.number_picker.get()]])
self.variant_picker.bind('<<ComboboxSelected>>', self.redraw)
self.variant_picker.set(VARIANTS[''])
self.variant_picker.grid(row=1, column=1)
self.labels.clear()
self.pickers.clear()
for i, (name, sprite) in enumerate(sprites[self.number_picker.get()].items()):
label = ttk.Label(self, text=NAMES[name])
label.grid(row=0,column=2+2*i, columnspan=2)
self.labels[name] = label
picker = ttk.Combobox(self, state='readonly', values=[x for x in range(len(sprite.states)+1)])
picker.bind('<<ComboboxSelected>>', self.pick_face)
picker.set('0')
picker.grid(row=1, column=2+2*i, columnspan=2)
self.pickers[name] = picker
self.compressed_ims[name] = Image.open(sprite.path)
self.labels2s[name] = {}
self.scaless[name] = {}
self.base_im = Image.open('bgs/yng_h' + self.number_picker.get() + '.png').convert(mode='RGBA')
self.redraw()
def pick_face(self, dummy_event=None):
for i, (name, sprite) in enumerate(sprites[self.number_picker.get()].items()):
labels2 = self.labels2s[name]
scales = self.scaless[name]
for label in labels2.values():
label.grid_forget()
for scale in scales.values():
scale.grid_forget()
labels2.clear()
scales.clear()
compressed_im = self.compressed_ims[name]
picker = self.pickers[name]
x = int(picker.get()) - 1
if x != -1:
state = sprite.states[x]
for j, (o, l) in enumerate(state.overlays.items()):
label = ttk.Label(self, text = OVERLAYS[o])
label.grid(row=2,column=2+2*i+j, columnspan=2//len(state.overlays))
labels2[o] = label
scale = ttk.Scale(self, to=len(l)-1, command=self.redraw, length=200)
scale.grid(row=3, column=2+2*i+j, columnspan=2//len(state.overlays))
scales[o] = scale
self.redraw()
def redraw(self, dummy_event=None, *idk):
if not self.base_im:
return
for scales in self.scaless.values():
for scale in scales.values():
if scale.get() != round(scale.get()):
scale.set(round(scale.get()))
return
if self.display:
self.display.grid_forget()
s_im = Image.alpha_composite(self.base_im, Image.open('bgs/yng_h' + self.number_picker.get() + INVERSE[self.variant_picker.get()] + '.png').convert(mode='RGBA'))
for name, sprite in sprites[self.number_picker.get()].items():
compressed_im = self.compressed_ims[name]
picker = self.pickers[name]
scales = self.scaless[name]
x = int(picker.get()) - 1
if x != -1:
image = compressed_im
s = sprite.states[x]
for i in range(s.start, s.start + s.count, 6):
c = sprite.records[sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box, region)
for o, l in s.overlays.items():
overlay = l[round(scales[o].get())]
for i in range(overlay.start, overlay.start + overlay.count, 6):
c = sprite.records[sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box, region)
if self.height.get():
render = ImageTk.PhotoImage(s_im.resize(((SCREEN_WIDTH * self.height.get()) // SCREEN_HEIGHT, self.height.get())))
else:
render = ImageTk.PhotoImage(s_im.copy())
self.display = ttk.Label(self, image = render)
self.display.image = render
self.display.grid(row=4,column=1,columnspan=1+2*len(sprites[self.number_picker.get()]))
s_im.close()
def createWidgets(self):
ttk.Label(self, text='Height').grid(row=0,column=0)
self.height = tk.IntVar()
self.height.trace('w', self.redraw)
e = ttk.Entry(self, textvariable=self.height)
e.grid(row=1,column=0)
self.number_picker = ttk.Combobox(self, height=20, state='readonly', values=list(bgs))
self.number_picker.bind('<<ComboboxSelected>>', self.select_bg)
self.number_picker.grid(row=2, column=0, rowspan=2, sticky='N')
app = Application()
app.master.title('YUNO BG Viewer')
app.mainloop()
#!/usr/bin/env python3
import sys
import glob
import os
import struct
import codecs
from PIL import Image, ImageTk
from itertools import product
class State:
def __init__(self, id, count, start):
self.id = id
self.count = count
self.start = start
self.overlays = {}
class Overlay:
def __init__(self, count, start):
self.count = count
self.start = start
class Sprite:
def __init__(self, path, width, height, states, records, table):
self.path = path
self.width = width
self.height = height
self.states = states
self.records = records
self.table = table
# endianness, depends on target platform (> = big, < = little)
BYTE_ORDER = '<'
BLOCK_SIZE = 32
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
NAMES = {'ama': 'Amanda', 'ayu': 'Ayumi', 'chi': 'Child Yu-no', 'eri': 'Eriko', 'hoj': 'Houjou', 'kan': 'Kanna', 'kao': 'Kaori', 'kon': 'Imperial Guard', 'kou': 'Koudai', 'mio': 'Mio', 'mit': 'Mitsuki', 'osy': 'Marina (Female Guard)', 'rou': 'Ume', 'ryu': 'Ryuuzouji', 'saa': 'Some miner guy', 'sab': 'Some other miner guy', 'ser': 'Sayless', 'sin': 'God-Empress', 'syo': 'Quarry Chief', 'tak': 'Takuya', 'toy': 'Toyotomi', 'yuk': 'Yuuki', 'yun': 'Yu-no'}
OVERLAYS = {'L': 'Lips', 'E': 'Eyelids'}
VARIANTS = {'': 'Base',
'_2': 'Alternate', '_2b': 'Alternate B', '_2c': 'Alternate C', '_2d': 'Alternate D',
's': 'Sepia',
'a': 'A',
'b': 'B', 'b2': 'B Shadow', 'b_2': 'B Alternate', 'bl': 'B Light', 'bn': 'B Night',
'c': 'C', 'c2': 'C Shadow', 'c_b': 'C Black', 'c_w': 'C White',
'd': 'D', 'd_2': 'D Alternate', 'd_b': 'D Black', 'd_w': 'D White',
'e': 'E', 'e_2': 'E Alternate',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J'}
INVERSE = {val: key for key, val in VARIANTS.items()}
args = sys.argv[1:]
if len(sys.argv) <= 1:
args = glob.glob('*.mvl')
else:
for i, arg in enumerate(args):
if '*' in arg:
args = args[:i] + glob.glob(arg) + args[i+1:]
sprites = {}
for arg in args:
arg = os.path.abspath(os.path.splitext(arg)[0])
if arg[-1] == '_':
arg = arg[:-1]
arg_basename = os.path.basename(arg)
# only do CGs
if not any(c.isdigit() for c in arg_basename):
continue
if arg_basename[-4] == '_':
continue
#print(arg_basename)
name = arg_basename[4:7]
num = arg_basename[-4:]
records_count = 0
table_count = 0
states = []
records = []
table = []
table_start = None
with open(arg + '_.mvl', 'rb') as f:
# 4 chars: MVL1
f.read(4)
# uint32: count of states
states_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(states_count)
# uint32: input width
input_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(input_width)
# 20 zeros
f.read(20)
# 64 characters
shit = str(f.read(64), encoding='ascii').strip('\0')
#print(shit)
for i in range(states_count):
# uint32: output width
output_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: output height
output_height = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: idk
idk = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 4 zeros
f.read(4)
# uint32: count of coordinates records
records_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of coordinates records
records_start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: count of records to read
count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of records to read
start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 32 chars: state name
id = str(f.read(32), encoding='ascii').strip('\0')
if table_start == None:
table_start = start
start = (start - table_start) // 2
table_count += count
if len(id) == 9:
states.append(State(id, count, start))
elif len(id) == 11:
states[-1].overlays.setdefault(id[-2], []).append(Overlay(count, start))
else:
print("weird!")
#print(id, count, start, sep='\t')
#print()
#print(output_width, output_height)
#print()
#print(records_count)
for i in range(records_count):
# float32: block position on the screen, X
x1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# float32: block position on the screen, Y
y1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# 4 zeros
f.read(4)
# float32: block position in the image, X
x2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
# float32: block position in the image, Y
y2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
#print(x1, y1, x2, y2)
records.append((x1, y1, x2, y2))
#print()
#print(map_count)
for i in range(table_count):
(idx,) = struct.unpack(BYTE_ORDER + 'H', f.read(2))
table.append(idx)
sprites.setdefault(num, {})[name] = Sprite(arg + '.png', output_width, output_height, states, records, table)
cgs = {}
for path in glob.glob('cgs/yng_*.png'):
path = os.path.abspath(os.path.splitext(path)[0])
if path[-1] == '_':
path = path[:-1]
basename = os.path.basename(path)
num = basename[4:8]
ext = basename[8:]
cgs.setdefault(num, []).append(ext)
sprites.setdefault(num, {})
import tkinter as tk
from tkinter import ttk
class Application(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
self.base_im = None
self.display = None
self.variant_label = None
self.variant_picker = None
self.labels = {}
self.pickers = {}
self.compressed_ims = {}
self.labels2s = {}
self.scaless = {}
def select_cg(self, dummy_event=None):
if self.variant_label:
self.variant_label.grid_forget()
if self.variant_picker:
self.variant_picker.grid_forget()
for label in self.labels.values():
label.grid_forget()
for picker in self.pickers.values():
picker.grid_forget()
for compressed_im in self.compressed_ims.values():
compressed_im.close()
for labels2 in self.labels2s:
for label in labels:
label.grid_forget()
for scales in self.scaless:
for scale in scales:
scale.grid_forget()
self.variant_label = ttk.Label(self, text='Variant')
self.variant_label.grid(row=0,column=1)
self.variant_picker = ttk.Combobox(self, state='readonly', values=[VARIANTS[x] for x in cgs[self.number_picker.get()]])
self.variant_picker.bind('<<ComboboxSelected>>', self.redraw)
self.variant_picker.set(VARIANTS[''])
self.variant_picker.grid(row=1, column=1)
self.labels.clear()
self.pickers.clear()
for i, (name, sprite) in enumerate(sprites[self.number_picker.get()].items()):
label = ttk.Label(self, text=NAMES[name])
label.grid(row=0,column=2+2*i, columnspan=2)
self.labels[name] = label
picker = ttk.Combobox(self, state='readonly', values=[x for x in range(len(sprite.states)+1)])
picker.bind('<<ComboboxSelected>>', self.pick_face)
picker.set('0')
picker.grid(row=1, column=2+2*i, columnspan=2)
self.pickers[name] = picker
self.compressed_ims[name] = Image.open(sprite.path)
self.labels2s[name] = {}
self.scaless[name] = {}
self.base_im = Image.open('cgs/yng_' + self.number_picker.get() + '.png').convert(mode='RGBA')
self.redraw()
def pick_face(self, dummy_event=None):
for i, (name, sprite) in enumerate(sprites[self.number_picker.get()].items()):
labels2 = self.labels2s[name]
scales = self.scaless[name]
for label in labels2.values():
label.grid_forget()
for scale in scales.values():
scale.grid_forget()
labels2.clear()
scales.clear()
compressed_im = self.compressed_ims[name]
picker = self.pickers[name]
x = int(picker.get()) - 1
if x != -1:
state = sprite.states[x]
for j, (o, l) in enumerate(state.overlays.items()):
label = ttk.Label(self, text = OVERLAYS[o])
label.grid(row=2,column=2+2*i+j, columnspan=2//len(state.overlays))
labels2[o] = label
scale = ttk.Scale(self, to=len(l)-1, command=self.redraw, length=200)
scale.grid(row=3, column=2+2*i+j, columnspan=2//len(state.overlays))
scales[o] = scale
self.redraw()
def redraw(self, dummy_event=None, *idk):
if not self.base_im:
return
for scales in self.scaless.values():
for scale in scales.values():
if scale.get() != round(scale.get()):
scale.set(round(scale.get()))
return
if self.display:
self.display.grid_forget()
s_im = Image.alpha_composite(self.base_im, Image.open('cgs/yng_' + self.number_picker.get() + INVERSE[self.variant_picker.get()] + '.png').convert(mode='RGBA'))
for name, sprite in sprites[self.number_picker.get()].items():
compressed_im = self.compressed_ims[name]
picker = self.pickers[name]
scales = self.scaless[name]
x = int(picker.get()) - 1
if x != -1:
image = compressed_im
s = sprite.states[x]
for i in range(s.start, s.start + s.count, 6):
c = sprite.records[sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box, region)
for o, l in s.overlays.items():
overlay = l[round(scales[o].get())]
for i in range(overlay.start, overlay.start + overlay.count, 6):
c = sprite.records[sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box, region)
if self.height.get():
render = ImageTk.PhotoImage(s_im.resize(((SCREEN_WIDTH * self.height.get()) // SCREEN_HEIGHT, self.height.get())))
else:
render = ImageTk.PhotoImage(s_im.copy())
self.display = ttk.Label(self, image = render)
self.display.image = render
self.display.grid(row=4,column=1,columnspan=1+2*len(sprites[self.number_picker.get()]))
s_im.close()
def createWidgets(self):
ttk.Label(self, text='Height').grid(row=0,column=0)
self.height = tk.IntVar()
self.height.trace('w', self.redraw)
e = ttk.Entry(self, textvariable=self.height)
e.grid(row=1,column=0)
self.number_picker = ttk.Combobox(self, height=20, state='readonly', values=list(cgs))
self.number_picker.bind('<<ComboboxSelected>>', self.select_cg)
self.number_picker.grid(row=2, column=0, rowspan=2, sticky='N')
app = Application()
app.master.title('YUNO CG Viewer')
app.mainloop()
#!/usr/bin/env python3
import sys
import glob
import os
import struct
import codecs
from PIL import Image
from itertools import product
class State:
def __init__(self, id, count, start):
self.id = id
self.count = count
self.start = start
self.overlays = {}
class Overlay:
def __init__(self, count, start):
self.count = count
self.start = start
# endianness, depends on target platform (> = big, < = little)
BYTE_ORDER = '<'
BLOCK_SIZE = 32
# 0 - skip CGS
# 1 - assemble partial overlays
# 2 - assemble full CGs (not working)
ASSEMBLE_CGS = 1
if len(sys.argv) == 1:
print("No files given.", file=sys.stderr)
exit(1)
args = sys.argv[1:]
if len(sys.argv) == 0:
args = glob.glob('*.mvl')
else:
for i, arg in enumerate(args):
if '*' in arg:
args = args[:i] + glob.glob(arg) + args[i+1:]
for arg in args:
arg = os.path.abspath(os.path.splitext(arg)[0])
if arg[-1] == '_':
arg = arg[:-1]
arg_basename = os.path.basename(arg)
if any(c.isdigit() for c in arg_basename):
if ASSEMBLE_CGS == 0:
continue
opf = 'output_cgs'
else:
opf = 'output_sprites'
if not os.path.exists(opf):
os.mkdir(opf)
os.chdir(opf)
if os.path.exists(arg_basename):
os.chdir('..')
continue
os.mkdir(arg_basename)
os.chdir(arg_basename)
print(arg_basename)
records_count = 0
table_count = 0
states = []
records = []
table = []
table_start = None
with open(arg + '_.mvl', 'rb') as f:
# 4 chars: MVL1
f.read(4)
# uint32: count of states
states_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(states_count)
# uint32: input width
INPUT_WIDTH = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(INPUT_WIDTH)
# 20 zeros
f.read(20)
# 64 characters
shit = str(f.read(64), encoding='ascii').strip('\0')
#print(shit)
for i in range(states_count):
# uint32: output width
SCREEN_WIDTH = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: output height
SCREEN_HEIGHT = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: idk
idk = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 4 zeros
f.read(4)
# uint32: count of coordinates records
records_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of coordinates records
records_start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: count of records to read
count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of records to read
start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 32 chars: state name
id = str(f.read(32), encoding='ascii').strip('\0')
if table_start == None:
table_start = start
start = (start - table_start) // 2
table_count += count
if len(id) == 9:
states.append(State(id, count, start))
elif len(id) == 11:
states[-1].overlays.setdefault(id[-2], []).append(Overlay(count, start))
else:
print("weird!")
#print(id, count, start, sep='\t')
#print()
#print(output_width, output_height)
#print()
#print(records_count)
for i in range(records_count):
# float32: block position on the screen, X
x1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# float32: block position on the screen, Y
y1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# 4 zeros
f.read(4)
# float32: block position in the image, X
x2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
# float32: block position in the image, Y
y2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
#print(x1, y1, x2, y2)
records.append((x1, y1, x2, y2))
#print()
#print(table_count)
for i in range(table_count):
(idx,) = struct.unpack(BYTE_ORDER + 'H', f.read(2))
table.append(idx)
with Image.open(arg + '.png') as image:
# empty image
empty_im = Image.new('RGBA', (SCREEN_WIDTH, SCREEN_HEIGHT), (0, 0, 0, 0))
for s in states:
print(s.id)
if ASSEMBLE_CGS == 2 and any(c.isdigit() for c in arg_basename):
cg_id = arg_basename[-4:]
if cg_id[0] == '_':
cg_id = cg_id[1:]
x = int(s.id[-2:])
if x > 1:
cg_id += chr(ord('a') + x - 1)
s_im = Image.open('../../cgs/yng_' + cg_id + '.png')
else:
s_im = empty_im.copy()
for i in range(s.start, s.start + s.count, 6):
c = records[table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box)
for overlays in product(*map(enumerate, s.overlays.values())):
s_fname = s.id
for o, (num, overlay) in zip(s.overlays.keys(), overlays):
s_fname += o + str(num + 1)
for i in range(overlay.start, overlay.start + overlay.count, 6):
c = records[table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (SCREEN_WIDTH//2 + c[0] - 1, SCREEN_HEIGHT//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box)
s_im.save(s_fname + '.png')
s_im.close()
print()
os.chdir('..')
os.chdir('..')
#!/usr/bin/env python3
import sys
import glob
import os
import struct
import codecs
from PIL import Image, ImageTk
from itertools import product
class State:
def __init__(self, id, count, start):
self.id = id
self.count = count
self.start = start
self.overlays = {}
class Overlay:
def __init__(self, count, start):
self.count = count
self.start = start
class Sprite:
def __init__(self, path, width, height, states, records, table):
self.path = path
self.width = width
self.height = height
self.states = states
self.records = records
self.table = table
# endianness, depends on target platform (> = big, < = little)
BYTE_ORDER = '<'
BLOCK_SIZE = 32
NAMES = {'ama': 'Amanda', 'ayu': 'Ayumi', 'chi': 'Child Yu-no', 'eri': 'Eriko', 'hoj': 'Houjou', 'kan': 'Kanna', 'kao': 'Kaori', 'kon': 'Imperial Guard', 'kou': 'Koudai', 'mio': 'Mio', 'mit': 'Mitsuki', 'osy': 'Marina (Female Guard)', 'rou': 'Ume', 'ryu': 'Ryuuzouji', 'saa': 'Some miner guy', 'sab': 'Some other miner guy', 'ser': 'Sayless', 'sin': 'God-Empress', 'syo': 'Quarry Chief', 'tak': 'Takuya', 'toy': 'Toyotomi', 'yuk': 'Yuuki', 'yun': 'Yu-no'}
OVERLAYS = {'L': 'Lips', 'E': 'Eyelids'}
args = sys.argv[1:]
if len(sys.argv) <= 1:
args = glob.glob('*.mvl')
else:
for i, arg in enumerate(args):
if '*' in arg:
args = args[:i] + glob.glob(arg) + args[i+1:]
sprites = {}
for arg in args:
arg = os.path.abspath(os.path.splitext(arg)[0])
if arg[-1] == '_':
arg = arg[:-1]
arg_basename = os.path.basename(arg)
# don't do CGs
if any(c.isdigit() for c in arg_basename):
continue
#print(arg_basename)
(_, name, pos) = arg_basename.split('_')
# skip Takuya's closeup
if pos[0] != 's':
continue
bodies = sprites.setdefault(name, [])
if len(bodies) == ord(pos[1]) - ord('a'):
bodies.append([])
arms = bodies[-1]
records_count = 0
table_count = 0
states = []
records = []
table = []
table_start = None
with open(arg + '_.mvl', 'rb') as f:
# 4 chars: MVL1
f.read(4)
# uint32: count of states
states_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(states_count)
# uint32: input width
input_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
#print(input_width)
# 20 zeros
f.read(20)
# 64 characters
shit = str(f.read(64), encoding='ascii').strip('\0')
#print(shit)
for i in range(states_count):
# uint32: output width
output_width = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: output height
output_height = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: idk
idk = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 4 zeros
f.read(4)
# uint32: count of coordinates records
records_count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of coordinates records
records_start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: count of records to read
count = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# uint32: beginning of records to read
start = struct.unpack(BYTE_ORDER + 'I', f.read(4))[0]
# 32 chars: state name
id = str(f.read(32), encoding='ascii').strip('\0')
if table_start == None:
table_start = start
start = (start - table_start) // 2
table_count += count
if len(id) == 9:
states.append(State(id, count, start))
elif len(id) == 11:
states[-1].overlays.setdefault(id[-2], []).append(Overlay(count, start))
else:
print("weird!")
#print(id, count, start, sep='\t')
#print()
#print(output_width, output_height)
#print()
#print(records_count)
for i in range(records_count):
# float32: block position on the screen, X
x1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# float32: block position on the screen, Y
y1 = round(struct.unpack(BYTE_ORDER + 'f', f.read(4))[0])# + 1
# 4 zeros
f.read(4)
# float32: block position in the image, X
x2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
# float32: block position in the image, Y
y2 = struct.unpack(BYTE_ORDER + 'f', f.read(4))[0]# - 1
#print(x1, y1, x2, y2)
records.append((x1, y1, x2, y2))
#print()
#print(map_count)
for i in range(table_count):
(idx,) = struct.unpack(BYTE_ORDER + 'H', f.read(2))
table.append(idx)
arms.append(Sprite(arg + '.png', output_width, output_height, states, records, table))
import tkinter as tk
from tkinter import ttk
class Application(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
self.scales = {}
self.labels = {}
self.empty_im = None
self.compressed_im = None
self.base_im = None
self.display = None
self.sprite_id = None
def select_sprite(self, dummy_event=None):
if len(self.tree.focus()) != 8:
return
for scale in self.scales.values():
scale.grid_forget()
for label in self.labels.values():
label.grid_forget()
if self.sprite_id != self.tree.focus()[:6]:
self.sprite_id = self.tree.focus()[:6]
char = self.tree.focus()[:3]
outfit = ord(self.tree.focus()[4]) - ord('a')
pose = ord(self.tree.focus()[5]) - ord('a')
self.sprite = sprites[char][outfit][pose]
if self.empty_im:
self.empty_im.close()
if self.base_im:
self.base_im.close()
self.empty_im = Image.new('RGBA', (self.sprite.width, self.sprite.height), (0, 0, 0, 0))
self.base_im = self.empty_im.copy()
if self.compressed_im != None:
self.compressed_im.close()
self.compressed_im = Image.open(self.sprite.path)
face = int(self.tree.focus()[6:8])
s = self.state = self.sprite.states[face]
self.scales = {}
self.labels = {}
for i, (key, val) in enumerate(self.state.overlays.items()):
label = ttk.Label(self, text = OVERLAYS[key])
label.grid(row=0, column=1+i)
self.labels[key] = label
scale = ttk.Scale(self, to=len(val)-1, command=self.redraw, length=200)
scale.grid(row=1, column=1+i)
self.scales[key] = scale
s_im = self.base_im
image = self.compressed_im
for i in range(s.start, s.start + s.count, 6):
c = self.sprite.records[self.sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (self.sprite.width//2 + c[0] - 1, self.sprite.height//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box)
self.redraw()
def redraw(self, dummy_event=None, *idk):
if not self.base_im:
return
for scale in self.scales.values():
if scale.get() != round(scale.get()):
scale.set(round(scale.get()))
return
if self.display:
self.display.grid_forget()
s_im = self.base_im
image = self.compressed_im
for o, l in self.state.overlays.items():
x = l[int(self.scales[o].get())]
for i in range(x.start, x.start + x.count, 6):
c = self.sprite.records[self.sprite.table[i]]
in_box = (round(c[2] * image.width) - 1, round(c[3] * image.height) - 1, round(c[2] * image.width) + BLOCK_SIZE - 1, round(c[3] * image.height) + BLOCK_SIZE - 1)
out_box = (self.sprite.width//2 + c[0] - 1, self.sprite.height//2 + c[1] - 1)
region = image.crop(in_box)
s_im.paste(region, out_box)
if self.height.get():
render = ImageTk.PhotoImage(s_im.resize(((self.sprite.width * self.height.get()) // self.sprite.height, self.height.get())))
else:
render = ImageTk.PhotoImage(s_im.copy())
self.display = ttk.Label(self, image = render)
self.display.image = render
self.display.grid(row=2,column=1,columnspan=max(1, len(self.state.overlays)))
def createWidgets(self):
ttk.Label(self, text='Height').grid(row=0,column=0)
self.height = tk.IntVar()
self.height.trace('w', self.redraw)
e = ttk.Entry(self, textvariable=self.height)
e.grid(row=1,column=0)
ttk.Style().configure('Treeview', rowheight=40)
ttk.Style().configure('Horizontal.TScale', sliderthickness=40)
self.tree = ttk.Treeview(self, show='tree', selectmode='browse', height=20)
for char, outfit in sprites.items():
self.tree.insert('', 'end', char, text = NAMES[char])
for i, poses in enumerate(outfit):
id1 = char + '_' + chr(ord('a') + i)
self.tree.insert(char, 'end', id1, text = "Outfit " + str(i + 1))
for j, sprite in enumerate(poses):
id2 = id1 + chr(ord('a') + j)
self.tree.insert(id1, 'end', id2, text = "Pose " + str(j + 1))
for k, face in enumerate(sprite.states):
id3 = id2 + '{:02}'.format(k)
self.tree.insert(id2, 'end', id3, text = "Face " + str(k + 1))
self.tree.column('#0', width=300)
self.tree.bind('<<TreeviewSelect>>', self.select_sprite)
self.tree.grid(row=2,column=0)
app = Application()
app.master.title('YUNO Sprite Viewer')
app.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment