hostile-takeover/AniMax/AnimDoc.cs
2014-07-06 17:47:28 -07:00

691 lines
17 KiB
C#

using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
using System.IO;
using System.Collections;
using System.Windows.Forms;
using System.Drawing;
using System.Text; // For ASCIIEncoding
using SpiffLib;
using ICSharpCode.SharpZipLib.Zip;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Diagnostics;
namespace SpiffCode
{
/// <summary>
/// Summary description for AnimDoc.
/// </summary>
[Serializable]
public class AnimDoc : ISerializable
{
// Persistable state
private int m_nTileSize;
private XBitmapSet m_xbms;
private StripSet m_stps;
private int m_msFrameRate;
//
private bool m_fDirty = false;
private bool m_fHires = false;
private string m_strFileName = "untitled.amx";
private Strip m_stpActive;
// Public properties
public XBitmapSet XBitmapSet {
get {
return m_xbms;
}
}
public StripSet StripSet {
get {
return m_stps;
}
}
public bool Hires {
get {
return m_fHires;
}
set {
m_fHires = value;
}
}
// Exposed for anyone who wants to keep track of this AnimDoc's ActiveStrip
public event EventHandler ActiveStripChanged;
public Strip ActiveStrip {
get {
return m_stpActive;
}
set {
m_stpActive = value;
if (ActiveStripChanged != null)
ActiveStripChanged(this, EventArgs.Empty);
}
}
public bool Dirty {
get {
return m_fDirty;
}
set {
m_fDirty = value;
}
}
public string FileName {
get {
return m_strFileName;
}
set {
m_strFileName = value;
Dirty = true;
}
}
public int FrameRate {
get {
return m_msFrameRate;
}
set {
m_msFrameRate = value;
}
}
public int TileSize {
get {
return m_nTileSize;
}
set {
m_nTileSize = value;
Dirty = true;
}
}
//
public AnimDoc(int nTileSize, int cmsFrameRate) {
m_nTileSize = nTileSize;
m_msFrameRate = cmsFrameRate;
m_xbms = new XBitmapSet();
m_stps = new StripSet();
m_fDirty = true;
}
public static AnimDoc Load(string strFileName) {
if (!File.Exists(strFileName))
throw new FileNotFoundException("File Not found", strFileName);
return Load(strFileName, null);
}
public static AnimDoc Load(string strFileName, Stream stmZamx) {
string strFileNameOrig = strFileName;
string strExt = Path.GetExtension(strFileName).ToLower();
bool fZip = strExt == ".zip" || strExt == ".zamx";
string strCurrentDirSav = null;
string strTempDir = null;
// Remember current dir
strCurrentDirSav = Directory.GetCurrentDirectory();
if (fZip) {
// Change current dir to temp dir
strTempDir = Path.Combine(Path.GetTempPath(), "AniMax_temp_extract_dir");
Directory.CreateDirectory(strTempDir);
Directory.SetCurrentDirectory(strTempDir);
// Extract the .zip to the temp dir
ZipInputStream zipi = new ZipInputStream(stmZamx != null ? stmZamx : File.OpenRead(strFileName));
ZipEntry zipe;
while ((zipe = zipi.GetNextEntry()) != null) {
string strDir = Path.GetDirectoryName(zipe.Name);
if (Path.GetExtension(zipe.Name).ToLower() == ".amx")
strFileName = zipe.Name;
if (strDir != null && strDir != "") {
if (!Directory.Exists(strDir))
Directory.CreateDirectory(strDir);
}
FileStream stm = File.Create(zipe.Name);
byte[] abT = new byte[zipe.Size];
// For some reason due to its implementation, ZipInputStream.Read can return
// fewer than the requested number of bytes. Loop until we have them all.
while (true) {
int cbRead = zipi.Read(abT, 0, abT.Length);
if (cbRead <= 0)
break;
stm.Write(abT, 0, cbRead);
}
stm.Close();
}
zipi.Close();
// Convert filename from, say, c:\ht\data\foo.zip or foo.zamx to foo.amx
strFileName = Path.GetFileNameWithoutExtension(strFileName) + ".amx";
}
FileStream stmAmx = File.Open(strFileName, FileMode.Open, FileAccess.Read);
SoapFormatter spfmt = new SoapFormatter();
spfmt.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
spfmt.Binder = new RelaxedSerializationBinder();
if (!fZip) {
// If .amx being loaded is in a directory other than the current one,
// change to it so deserialization will find the contained bitmaps in
// their proper place.
string strPath = Path.GetDirectoryName(strFileName);
if (strPath != null && strPath != "")
Directory.SetCurrentDirectory(strPath);
}
AnimDoc doc = null;
try {
doc = (AnimDoc)spfmt.Deserialize(stmAmx);
} catch (Exception ex) {
MessageBox.Show(ex.ToString());
Console.WriteLine(ex);
}
stmAmx.Close();
// Restore current dir (NOTE: can't delete temp dir until it isn't current)
Directory.SetCurrentDirectory(strCurrentDirSav);
if (fZip) {
// Delete temp extraction dir and its contents
Directory.Delete(strTempDir, true);
}
if (doc == null)
return null;
doc.m_strFileName = strFileNameOrig;
return doc;
}
public void Save(string strFileName) {
string strExt = Path.GetExtension(strFileName).ToLower();
bool fZip = strExt == ".zip" || strExt == ".zamx";
// Update the XBitmaps to have paths relative to the specified file
// in a subdirectory named after the file.
m_strFileName = strFileName;
// Create a sub-directory for all the Bitmaps
// UNDONE: clean it out?
string strName = Path.GetFileNameWithoutExtension(strFileName);
string strBitmapDir = Path.Combine(Path.GetDirectoryName(strFileName), strName);
foreach (XBitmap xbm in XBitmapSet)
xbm.FileName = Path.Combine(strName, Path.GetFileName(xbm.FileName));
string strAmxFileName = fZip ? strFileName + ".temporary_file" : strFileName;
FileStream stm = File.Open(strAmxFileName, FileMode.Create, FileAccess.Write);
SoapFormatter spfmt = new SoapFormatter();
// This cuts down on the verbosity of the generated XML file
spfmt.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
spfmt.Serialize(stm, this);
stm.Close();
if (fZip) {
ZipOutputStream zipo = null;
zipo = new ZipOutputStream(File.Create(strFileName));
zipo.SetLevel(9); // maximum compression
// Read temporary .amx file
stm = File.OpenRead(strAmxFileName);
byte[] abT = new byte[stm.Length];
stm.Read(abT, 0, abT.Length);
stm.Close();
// Delete temporary .amx file
File.Delete(strAmxFileName);
// Write .amx file to .zip
ZipEntry zipe = new ZipEntry(strName + ".amx");
zipo.PutNextEntry(zipe);
zipo.Write(abT, 0, abT.Length);
// Write the bitmaps too
foreach (XBitmap xbm in m_xbms) {
// Write temporary bitmap file
xbm.Save(strAmxFileName);
// Read temporary bitmap file
stm = File.OpenRead(strAmxFileName);
abT = new byte[stm.Length];
stm.Read(abT, 0, abT.Length);
stm.Close();
// Delete the temporary bitmap file
File.Delete(strAmxFileName);
// Write bitmap to .zip
zipe = new ZipEntry(Path.Combine(strName, Path.GetFileName(xbm.FileName)));
zipo.PutNextEntry(zipe);
zipo.Write(abT, 0, abT.Length);
}
zipo.Finish();
zipo.Close();
} else {
// Save the Bitmaps too
Directory.CreateDirectory(strBitmapDir);
foreach (XBitmap xbm in m_xbms)
xbm.Save(Path.Combine(strBitmapDir, Path.GetFileName(xbm.FileName)));
}
m_fDirty = false;
}
public static bool ParseNameValueString(string str, out string strName, out string strValue) {
int ichEquals = str.IndexOf('=');
if (ichEquals == -1) {
strName = null;
strValue = null;
return false;
}
strName = str.Substring(0, ichEquals).Trim();
strValue = str.Substring(ichEquals + 1);
return true;
}
public bool Import(string[] astrFileNames) {
// Is this a SideWinder framedata.txt file?
if (astrFileNames.Length == 1 && Path.GetFileName(astrFileNames[0]).ToLower() == "framedata.txt") {
// Yep, open it up and parse it
StreamReader stmr = new StreamReader(astrFileNames[0]);
int iLine = 0;
string str;
do {
iLine++;
str = stmr.ReadLine();
} while (str == ""); // skip blank lines
if (str == null) {
MessageBox.Show(null, "Reached the end of the file before it was expected", "Error");
return false;
}
string strName, strValue;
if (!ParseNameValueString(str, out strName, out strValue)) {
MessageBox.Show(null, String.Format("Syntax error on line %d: %s", iLine, str), "Error");
return false;
}
if (strName != "cfrm") {
MessageBox.Show(null, "Expected a 'cfrm =' statement but didn't find it", "Error");
return false;
}
// Find a unique name for this strip
int iStrip = 0;
while (StripSet["strip" + iStrip] != null)
iStrip++;
Strip stp = new Strip("strip" + iStrip);
StripSet.Add(stp);
int cfr = int.Parse(strValue);
for (int ifr = 0; ifr < cfr; ifr++) {
// 1. Read the bitmap from it and add it to the Document's XBitmapSet
XBitmap xbm;
string strBitmap = "frame" + ifr + ".bmp";
try {
xbm = new XBitmap(strBitmap, true);
} catch {
MessageBox.Show(null, String.Format("Can't load \"{0}\"", strBitmap), "Error");
return false;
}
XBitmapSet.Add(xbm);
// 2. Create a Frame to go with the Bitmap and add it to the appropriate
// Strip. If no strip exists, create one.
Frame fr = new Frame();
fr.BitmapPlacers.Add(new BitmapPlacer());
fr.BitmapPlacers[0].XBitmap = xbm;
stp[ifr] = fr;
bool fDone = false;
while (!fDone) {
do {
iLine++;
str = stmr.ReadLine();
} while (str == ""); // skip blank lines
if (!ParseNameValueString(str, out strName, out strValue)) {
MessageBox.Show(null, String.Format("Syntax error on line %d: %s", iLine, str), "Error");
return false;
}
switch (strName) {
case "flags":
Debug.Assert(strValue.Trim() == "0");
break;
case "xCenter":
fr.BitmapPlacers[0].X = int.Parse(strValue);
break;
case "yCenter":
fr.BitmapPlacers[0].Y = int.Parse(strValue);
break;
case "xGrab":
fr.SpecialPoint = new Point(int.Parse(strValue) - fr.BitmapPlacers[0].X, fr.SpecialPoint.Y);
break;
case "yGrab":
fr.SpecialPoint = new Point(fr.SpecialPoint.X, int.Parse(strValue) - fr.BitmapPlacers[0].Y);
break;
case "xWidth":
Debug.Assert(int.Parse(strValue.Trim()) == xbm.Width);
break;
case "yHeight":
Debug.Assert(int.Parse(strValue.Trim()) == xbm.Height);
fDone = true;
break;
}
}
}
} else {
// XBitmap encapsulates special filename rules
astrFileNames = XBitmap.FilterFileNames(astrFileNames);
// By sorting the filenames we introduce a useful bit of
// determinism.
Array.Sort(astrFileNames);
// Enumerate all the filenames and for each one:
foreach (string strFile in astrFileNames) {
// 0. Verify the filename fits the pattern we're expecting
string[] astr = strFile.Substring(strFile.LastIndexOf('\\') + 1).Split('_', '.');
if (astr.Length != 5) {
MessageBox.Show(null, String.Format("File {0} does not match the requisite naming pattern. Skipping and continuing.",
strFile), "Error");
continue;
}
string strAnimDoc = astr[0];
string strStripA = astr[1];
string strStripB = astr[2];
int ifr = Convert.ToInt32(astr[3]);
// 1. Read the bitmap from it and add it to the Document's XBitmapSet
XBitmap xbm;
try {
xbm = new XBitmap(strFile);
} catch {
MessageBox.Show(null, String.Format("Can't load \"{0}\"", strFile), "Error");
return false;
}
XBitmapSet.Add(xbm);
// 2. Create a Frame to go with the Bitmap and add it to the appropriate
// Strip. If no strip exists, create one.
Frame fr = new Frame();
fr.BitmapPlacers.Add(new BitmapPlacer());
fr.BitmapPlacers[0].XBitmap = xbm;
fr.BitmapPlacers[0].X = xbm.Width / 2;
fr.BitmapPlacers[0].Y = xbm.Height / 2;
string strStripName = strStripA + " " + strStripB;
Strip stp = StripSet[strStripName];
if (stp == null) {
stp = new Strip(strStripName);
StripSet.Add(stp);
}
stp[ifr] = fr;
}
}
Dirty = true;
return true;
}
public bool WriteAnir(Palette pal, string strExportPath, string strAnimName) {
Color clrTransparent = Color.FromArgb(0xff, 0, 0xff);
SolidBrush brTransparent = new SolidBrush(clrTransparent);
ASCIIEncoding enc = new ASCIIEncoding();
FileStream stm = new FileStream(strExportPath + Path.DirectorySeparatorChar + strAnimName + ".anir", FileMode.Create, FileAccess.Write);
BinaryWriter stmw = new BinaryWriter(stm);
// Count the number of Strips
ushort cstpd = (ushort)StripSet.Count;
// Write AnimationFileHeader.cstpd
stmw.Write(Misc.SwapUShort(cstpd));
// Write array of offsets to StripDatas (AnimationFileHeader.aoffStpd)
ushort offStpd = (ushort)(2 + (2 * cstpd));
ArrayList albm = new ArrayList();
foreach (Strip stp in StripSet) {
stmw.Write(Misc.SwapUShort(offStpd));
// Advance offset to where the next StripData will be
offStpd += (ushort)((26+1+1+2) /* sizeof(StripData) - sizeof(FrameData) */ +
((1+1+1+1+1+1+1+1+1) /* sizeof(FrameData) */ * stp.Count));
// Force word alignment of StripDatas
if ((offStpd & 1) == 1)
offStpd++;
}
// Write array of StripDatas
foreach (Strip stp in StripSet) {
// Write StripData.Name
byte[] abT = new byte[26];
enc.GetBytes(stp.Name, 0, Math.Min(stp.Name.Length, 25), abT, 0);
abT[25] = 0;
stmw.Write(abT);
// Write StripData.cHold
stmw.Write((byte)stp.DefHoldCount);
// Write StripData.bfFlags
stmw.Write((byte)0);
// Write StripData.cfrmd
ushort cfrmd = (ushort)stp.Count;
stmw.Write(Misc.SwapUShort(cfrmd));
// Write array of FrameDatas
foreach (Frame fr in stp) {
// Add the Frame's Bitmap for output
int ibm = -1;
Bitmap bm;
if (fr.BitmapPlacers.Count > 0) {
bm = fr.BitmapPlacers[0].XBitmap.Bitmap;
ibm = albm.IndexOf(bm);
if (ibm == -1)
ibm = albm.Add(bm);
}
// Write FrameData.ibm (the index of the Bitmap as it will be in the Bitmap array)
stmw.Write((byte)ibm);
ibm = -1;
if (fr.BitmapPlacers.Count > 1) {
// Add the Frame's Bitmap for output
bm = fr.BitmapPlacers[1].XBitmap.Bitmap;
ibm = albm.IndexOf(bm);
if (ibm == -1)
ibm = albm.Add(bm);
}
// Write FrameData.ibm2 (the index of the Bitmap as it will be in the Bitmap array)
stmw.Write((byte)ibm);
// Write FrameData.cHold
stmw.Write((byte)fr.HoldCount);
// Write FrameData.xOrigin, FrameData.yOrigin
if (fr.BitmapPlacers.Count > 0) {
stmw.Write((byte)fr.BitmapPlacers[0].X);
stmw.Write((byte)fr.BitmapPlacers[0].Y);
} else {
stmw.Write((byte)0);
stmw.Write((byte)0);
}
if (fr.BitmapPlacers.Count > 1) {
stmw.Write((byte)fr.BitmapPlacers[1].X);
stmw.Write((byte)fr.BitmapPlacers[1].Y);
} else {
stmw.Write((byte)0);
stmw.Write((byte)0);
}
// Write FrameData.bCustomData1, FrameData.bCustomData2
stmw.Write((byte)fr.SpecialPoint.X);
stmw.Write((byte)fr.SpecialPoint.Y);
#if false
// Write FrameData.bCustomData3
stmw.Write((byte)0);
#endif
}
// Force word alignment of StripDatas given that FrameDatas are an odd
// number of bytes long and there may be an odd number of frames.
if ((cfrmd & 1) == 1)
stmw.Write((byte)0);
}
stmw.Close();
// Write out .tbm
if (albm.Count != 0) {
string strFileName = strExportPath + Path.DirectorySeparatorChar + strAnimName + ".tbm";
// if (gfSuperVerbose)
// Console.WriteLine("Crunching and writing " + strFileName);
TBitmap.Save((Bitmap[])albm.ToArray(typeof(Bitmap)), pal, strFileName);
}
return true;
}
// ISerializable interface implementation
private AnimDoc(SerializationInfo seri, StreamingContext stmc) {
m_xbms = (XBitmapSet)seri.GetValue("Bitmaps", typeof(XBitmapSet));
m_stps = (StripSet)seri.GetValue("Strips", typeof(StripSet));
m_msFrameRate = seri.GetInt32("FrameRate");
try {
bool fHires = seri.GetBoolean("Hires");
if (fHires) {
m_nTileSize = 24;
} else {
m_nTileSize = 16;
}
} catch {
m_nTileSize = -1;
}
if (m_nTileSize == -1) {
m_nTileSize = seri.GetInt32("TileSize");
}
}
void ISerializable.GetObjectData(SerializationInfo seri, StreamingContext stmc) {
seri.AddValue("Bitmaps", m_xbms);
seri.AddValue("Strips", m_stps);
seri.AddValue("FrameRate", m_msFrameRate);
seri.AddValue("TileSize", m_nTileSize);
}
}
// This class is implemented to allow one Assembly read a .amx file written by a
// different Assembly -- what a concept!
public class RelaxedSerializationBinder : SerializationBinder {
public override Type BindToType(string strAssemblyName, string strTypeName) {
return Type.GetType(strTypeName);
}
}
}