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 description for AnimDoc. /// [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); } } }