From 6c2557dec8f687dccf7ea5772bd9e2f143e04217 Mon Sep 17 00:00:00 2001 From: josch Date: Tue, 18 Mar 2014 14:48:14 +0100 Subject: [PATCH] Added README, definfo.py, shred.py - added README.md - added definfo.py to retrieve information of DEF files - remove shredding option from defextract and lodextract - added shred.py which modifies PNG images or directories of them - support DEF files with different full width and height in individual frames (only ovslot.def) - removed common.py --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ defextract.py | 64 ++++++++++++++++++++++++++------------------------- definfo.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ lodextract.py | 38 ++++++------------------------ shred.py | 50 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 62 deletions(-) create mode 100644 README.md create mode 100644 definfo.py create mode 100644 shred.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..9090cc2 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +This is a set of scripts which shows how to unpack all bitmaps and animations +of Heroes of Might and Magic 3 into PNG images and then back into the formats +understood by VCMI. + +These scripts are probably the first open source implementation of a writer for +the Heroes of Might and Magic 3 animation format called DEF. They are meant to +make it possible for artists to create a free replacement for the proprietary +assets VCMI currently needs. + +Install VCMI and then install original game files via any of the following methods: + + vcmibuilder --cd1 /path/to/iso/or/cd --cd2 /path/to/second/cd --download + vcmibuilder --gog /path/to/gog.com/installer --download + vcmibuilder --data /path/to/h3/data --download + +Symlink sprites to Data directory + + ln -s Data ~/.vcmi/Sprites + +Backup original archives: + + mkdir ~/lods + for f in H3ab_bmp.lod H3ab_spr.lod H3bitmap.lod H3sprite.lod; do mv ~/.vcmi/Data/$f ~/lods; done + for f in hmm35wog.pac "wog - animated objects.pac" "wog - animated trees.pac" "wog - battle decorations.pac"; do mv ~/.vcmi/Mods/WoG/Data/"$f" ~/lods; done + +Extract archives: + + for f in H3bitmap.lod H3sprite.lod H3ab_bmp.lod H3ab_spr.lod hmm35wog.pac "wog - animated objects.pac" "wog - animated trees.pac" "wog - battle decorations.pac"; do python lodextract.py ~/lods/"$f" ~/.vcmi/Data/; done + +Backup original DEFs: + + mkdir ~/defs + mv ~/.vcmi/Data/*.def ~/defs + rm ~/defs/sgtwmta.def ~/defs/sgtwmtb.def # these are having higher offsets than size + +Extract all DEFs into JSON files and directories with PNG images: + + for f in ~/defs/*; do python defextract.py $f ~/.vcmi/Data || break; done + +(optional) modify all frames: + + for d in ~/.vcmi/Data/*.dir; do python shred.py $d || break; done + +(optional) modify all bitmaps: + for f in ~/.vcmi/Data/*.png; do python shred.py $f || break; done + +Repack all JSON: + + for f in ~/.vcmi/Data/*.json; do python makedef.py $f ~/.vcmi/Data || break; done + +In case you followed the optional steps, enjoy your LSD infused game now :) + +After above steps you will have a mixture of DEF files as well as JSON +files and their *.dir data directories. All parts of vcmi that support it will +read the animations from the JSON files now. All others will fall back to +reading the DEF files. + +You can now make changes to either the PNG images in the Data directory or in +the *.dir subdirectories. If you make changes to PNG images in *.dir +subdirectories you might have to repack them into DEF files for all animations +which do not support JSON animations yet. + +I only tested these scripts on Linux because I do not own a license for Windows +or MacOS. Patches welcome. diff --git a/defextract.py b/defextract.py index 6028408..5c29614 100644 --- a/defextract.py +++ b/defextract.py @@ -33,7 +33,7 @@ def get_color(fname): # so we are left with 248 values return 8+crc%248 -def extract_def(infile,outdir,shred=True): +def extract_def(infile,outdir): f = open(infile) bn = os.path.basename(infile) bn = os.path.splitext(bn)[0].lower() @@ -63,10 +63,17 @@ def extract_def(infile,outdir,shred=True): offs, = struct.unpack(" fw or tm > fh: + print "margins (%dx%d) are higher than dimensions (%dx%d)"%(lm,tm,fw,fh) + return False + + if firstfw==-1 and firstfh == -1: + firstfw = fw + firstfh = fh + else: + if firstfw > fw: + print "must enlarge width" + fw = firstfw + if firstfw < fw: + print "first with smaller than latter one" + return False + if firstfh > fh: + print "must enlarge height" + fh = firstfh + if firstfh < fh: + print "first height smaller than latter one" + return False + if out_json["format"] == -1: out_json["format"] = fmt elif out_json["format"] != fmt: @@ -93,12 +124,6 @@ def extract_def(infile,outdir,shred=True): if fmt == 0: pixeldata = f.read(w*h) elif fmt == 1: - # SGTWMTA.def and SGTWMTB.def fail here - # they have inconsistent left and top margins - # they seem to be unused - if lm > fw or tm > fh: - print "margins (%dx%d) are higher than dimensions (%dx%d)"%(lm,tm,fw,fh) - return False lineoffs = struct.unpack("<"+"I"*h, f.read(4*h)) for lineoff in lineoffs: f.seek(offs+32+lineoff) @@ -120,7 +145,6 @@ def extract_def(infile,outdir,shred=True): f.seek(offs+32+lineoff) totalrowlength=0 while totalrowlength>5 length = (segment&0x1f)+1 @@ -155,32 +179,10 @@ def extract_def(infile,outdir,shred=True): im = Image.fromstring('P', (w,h),pixeldata) else: # either width or height is zero im = None - if im and shred: - #im = Image.new("P", (w*3,h*3), get_color(bn)) - #draw = ImageDraw.Draw(im) - #tw,th = draw.textsize("%d%s"%(j,bn),font=font) - #draw.text(((w*3-tw)/2,(h*3-th)/2),"%d%s"%(j,bn),font=font) - #im = im.resize((w,h),Image.ANTIALIAS) - - #pixels = im.load() - #color = get_color(bn) - #for i in range(w): - # for j in range(h): - # if pixels[i,j] > 7: - # pixels[i,j] = color - - color = get_color(bn) - import numpy as np - pixels = np.array(im) - pixels[pixels > 7] = color - im = Image.fromarray(pixels) imo = Image.new('P', (fw,fh)) imo.putpalette(palette) if im: imo.paste(im,(lm,tm)) - #draw = ImageDraw.Draw(imo) - #tw,th = draw.textsize(bn,font=font) - #draw.text(((fw-tw)/2,(fh-th)/2),bn,255,font=font) imo.save(outname) out_json["sequences"].append({"group":bid,"frames":frames}) with open(os.path.join(outdir,"%s.json"%bn),"w+") as o: diff --git a/definfo.py b/definfo.py new file mode 100644 index 0000000..fa283f3 --- /dev/null +++ b/definfo.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright (C) 2014 Johannes Schauer +# +# 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. + +import struct +from collections import defaultdict + +from common import sanitize_filename + +def main(infile): + f = open(infile) + t,_x,_y,blocks = struct.unpack(">16 - g = (crc&0xff00)>>8 - b = crc&0xff - w,h = im.size - pixels = im.load() - for i in range(w): - for j in range(h): - if pixels[i,j] > 7: - if im.mode == 'P': - pixels[i,j] = 8+crc%248 - else: - pixels[i,j] = (r,g,b) - im.resize((w*3,h*3)) - draw = ImageDraw.Draw(im) - tw,th = draw.textsize(os.path.basename(filename),font=font) - tpos = ((w*3-tw)/2,(h*3-th)/2) - if im.mode == 'P': - # we can't really have a complement in palette mode, so just get some color - draw.text(tpos,os.path.basename(filename),255,font=font) - else: - draw.text(tpos,os.path.basename(filename),get_complement(r,g,b),font=font) - im = im.resize((w,h),Image.ANTIALIAS) - im.save(filename, "PNG") - else: + if not im: return False + filename = os.path.splitext(filename)[0] + filename = filename+".png" + im.save(filename) else: - o = open(filename,"w+") - o.write(data) - o.close() + with open(filename,"w+") as o: + o.write(data) return True diff --git a/shred.py b/shred.py new file mode 100644 index 0000000..4a222d4 --- /dev/null +++ b/shred.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +import numpy as np +from PIL import Image +import crcmod +import os + +crc24_func = crcmod.mkCrcFun(0x1864CFBL) # polynomial from libgcrypt + +def handle_img(inf, color): + with open(inf) as f: + im = Image.open(f) + pal = im.getpalette() + pixels = np.array(im) + if pal: + pal[765], pal[766], pal[767] = color + pixels[pixels > 7] = 255 + im = Image.fromarray(pixels) + im.putpalette(pal) + else: + # non-palette pictures have no transparency + im = Image.new('RGB', im.size, color) + # in case we ever want to replace colors in rgb images: + #rc, gc, bc = pixels[:,:,0], pixels[:,:,1], pixels[:,:,2] + #mask = (rc == 0) & (gc == 255) & (bc == 255) + #pixels[:,:,:3][mask] = color + im.save(inf) + +def main(inf): + print "processing %s"%inf + crc = crc24_func(inf) + r = crc>>16 + g = (crc&0xff00)>>8 + b = crc&0xff + color = r%255,g%255,b%255 # avoid hitting special values + if os.path.isdir(inf): + for fname in os.listdir(inf): + fname = os.path.join(inf,fname) + handle_img(fname, color) + else: + handle_img(inf, color) + return True + +if __name__ == '__main__': + import sys + if len(sys.argv) != 2: + print "usage: %s indir/infile" + exit(0) + ret = main(sys.argv[1]) + exit(0 if ret else 1)