diff --git a/AA2CardEditor/AA2Card.cs b/AA2CardEditor/AA2Card.cs
new file mode 100644
index 0000000..c5beefb
--- /dev/null
+++ b/AA2CardEditor/AA2Card.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Drawing;
+using System.IO;
+
+namespace AA2CardEditor
+{
+ public class AA2Card
+ {
+ private string filePath = "";
+ private MemoryStream faceImageStream = null;
+ private MemoryStream editorFileStream = null;
+
+ public AA2Card()
+ {
+ IsDirty = false;
+ }
+
+ public string FileName
+ {
+ get
+ {
+ return Path.GetFileName(filePath);
+ }
+ }
+
+ public Image FaceImage
+ {
+ get
+ {
+ if (faceImageStream == null)
+ {
+ return null;
+ }
+ else
+ {
+ faceImageStream.Seek(0, SeekOrigin.Begin);
+ return Image.FromStream(faceImageStream);
+ }
+ }
+ }
+
+ public bool IsDirty { get; private set; }
+
+ public void ReadCardFile(String filePath)
+ {
+ // Load the file at the path into a memory stream.
+ byte[] cardFile = File.ReadAllBytes(filePath);
+ MemoryStream cardFileStream = new MemoryStream(
+ cardFile, 0, cardFile.Length, false, true);
+
+ // Try to read a PNG from the card file stream.
+ MemoryStream faceImageStream = PngFileReader.Read(cardFileStream);
+
+ // Try to read an editor file from the card file stream.
+ MemoryStream editorFileStream = EditorFileReader.Read(cardFileStream);
+
+ // Success. Update card state and mark card as clean.
+ this.filePath = filePath;
+ this.faceImageStream = faceImageStream;
+ this.editorFileStream = editorFileStream;
+ this.IsDirty = false;
+ }
+
+ public void ReplaceFaceImage(String filePath)
+ {
+ // Load the file at the path into a memory stream.
+ byte[] imageFile = File.ReadAllBytes(filePath);
+ MemoryStream imageFileStream = new MemoryStream(
+ imageFile, 0, imageFile.Length, false, true);
+
+ // Try to read a PNG from the image file stream.
+ MemoryStream faceImageStream = PngFileReader.Read(imageFileStream);
+
+ // Success. Update card state and mark card as dirty.
+ this.faceImageStream = faceImageStream;
+ this.IsDirty = true;
+ }
+
+ public void Save()
+ {
+ Save(filePath);
+ }
+
+ public void Save(String filePath)
+ {
+ using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+ {
+ // Copy the face image to the file stream.
+ faceImageStream.Seek(0, SeekOrigin.Begin);
+ faceImageStream.CopyTo(fs);
+
+ // Copy the editor file to the file stream.
+ editorFileStream.Seek(0, SeekOrigin.Begin);
+ editorFileStream.CopyTo(fs);
+
+ // Update card state and mark card as clean.
+ this.filePath = filePath;
+ IsDirty = false;
+ }
+ }
+ }
+}
diff --git a/AA2CardEditor/AA2CardEditor.csproj b/AA2CardEditor/AA2CardEditor.csproj
new file mode 100644
index 0000000..d6e3891
--- /dev/null
+++ b/AA2CardEditor/AA2CardEditor.csproj
@@ -0,0 +1,135 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {82358432-2268-408F-871D-3664E2B76EEC}
+ Library
+ Properties
+ AA2CardEditor
+ AA2CardEditor
+ v4.0
+ Client
+ 512
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+
+
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+ False
+ Microsoft .NET Framework 4 Client Profile %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AA2CardEditor/EditorFileReader.cs b/AA2CardEditor/EditorFileReader.cs
new file mode 100644
index 0000000..ffa7272
--- /dev/null
+++ b/AA2CardEditor/EditorFileReader.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace AA2CardEditor
+{
+ class EditorFileReader
+ {
+ private static readonly byte[] EditorFileSignature = { 0x81, 0x79, 0x83, 0x47, 0x83, 0x66, 0x83, 0x42,
+ 0x83, 0x62, 0x83, 0x67, 0x81, 0x7a, 0x00, 0x00};
+
+ public static MemoryStream Read(MemoryStream inputStream)
+ {
+ byte[] inputBuffer = inputStream.GetBuffer();
+ MemoryStream outputStream = new MemoryStream();
+
+ using (WrappingStream wrappingStream = new WrappingStream(inputStream))
+ using (BinaryReader reader = new BinaryReader(inputStream))
+ {
+ int editorFileBeginPosition = (int)inputStream.Position;
+
+ // Read the signature and verify that it's equal to the editor signature.
+ byte[] fileSignature = reader.ReadBytes(EditorFileSignature.Length);
+
+ if (!fileSignature.SequenceEqual(EditorFileSignature))
+ {
+ throw new Exception("The file does not appear to contain editor data.");
+ }
+
+ inputStream.Seek(0, SeekOrigin.End);
+ int editorFileEndPosition = (int)inputStream.Position;
+
+ // Copy the rest of the file. We're assuming it's a valid editor file.
+ int editorFileLength = editorFileEndPosition - editorFileBeginPosition;
+ outputStream.Write(inputBuffer, editorFileBeginPosition, editorFileLength);
+ }
+
+ return outputStream;
+ }
+ }
+}
diff --git a/AA2CardEditor/Form1.Designer.cs b/AA2CardEditor/Form1.Designer.cs
new file mode 100644
index 0000000..70f9a2c
--- /dev/null
+++ b/AA2CardEditor/Form1.Designer.cs
@@ -0,0 +1,266 @@
+namespace AA2CardEditor
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
+ this.toolStrip1 = new System.Windows.Forms.ToolStrip();
+ this.openToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.saveToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.saveAsToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+ this.replaceCardFaceToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.cardOpenFileDialog = new System.Windows.Forms.OpenFileDialog();
+ this.cardFacePictureBox = new System.Windows.Forms.PictureBox();
+ this.faceOpenFileDialog = new System.Windows.Forms.OpenFileDialog();
+ this.saveFileDialog = new System.Windows.Forms.SaveFileDialog();
+ this.menuStrip1 = new System.Windows.Forms.MenuStrip();
+ this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
+ this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.replaceCardFaceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStrip1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.cardFacePictureBox)).BeginInit();
+ this.menuStrip1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // toolStrip1
+ //
+ this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.openToolStripButton,
+ this.saveToolStripButton,
+ this.saveAsToolStripButton,
+ this.toolStripSeparator1,
+ this.replaceCardFaceToolStripButton});
+ this.toolStrip1.Location = new System.Drawing.Point(0, 0);
+ this.toolStrip1.Name = "toolStrip1";
+ this.toolStrip1.RenderMode = System.Windows.Forms.ToolStripRenderMode.System;
+ this.toolStrip1.Size = new System.Drawing.Size(584, 25);
+ this.toolStrip1.TabIndex = 0;
+ this.toolStrip1.Text = "toolStrip1";
+ //
+ // openToolStripButton
+ //
+ this.openToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.openToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("openToolStripButton.Image")));
+ this.openToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.openToolStripButton.Name = "openToolStripButton";
+ this.openToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.openToolStripButton.Text = "&Open";
+ this.openToolStripButton.ToolTipText = "Open (Ctrl+O)";
+ this.openToolStripButton.Click += new System.EventHandler(this.openToolStripButton_Click);
+ //
+ // saveToolStripButton
+ //
+ this.saveToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.saveToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripButton.Image")));
+ this.saveToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.saveToolStripButton.Name = "saveToolStripButton";
+ this.saveToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.saveToolStripButton.Text = "&Save";
+ this.saveToolStripButton.ToolTipText = "Save (Ctrl+S)";
+ this.saveToolStripButton.Click += new System.EventHandler(this.saveToolStripButton_Click);
+ //
+ // saveAsToolStripButton
+ //
+ this.saveAsToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.saveAsToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveAsToolStripButton.Image")));
+ this.saveAsToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.saveAsToolStripButton.Name = "saveAsToolStripButton";
+ this.saveAsToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.saveAsToolStripButton.Text = "Save &As";
+ this.saveAsToolStripButton.Click += new System.EventHandler(this.saveAsToolStripButton_Click);
+ //
+ // toolStripSeparator1
+ //
+ this.toolStripSeparator1.Name = "toolStripSeparator1";
+ this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25);
+ //
+ // replaceCardFaceToolStripButton
+ //
+ this.replaceCardFaceToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("replaceCardFaceToolStripButton.Image")));
+ this.replaceCardFaceToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.replaceCardFaceToolStripButton.Name = "replaceCardFaceToolStripButton";
+ this.replaceCardFaceToolStripButton.Size = new System.Drawing.Size(123, 22);
+ this.replaceCardFaceToolStripButton.Text = "Replace Card Face";
+ this.replaceCardFaceToolStripButton.ToolTipText = "Replace the face of the card with an image from a file.";
+ this.replaceCardFaceToolStripButton.Click += new System.EventHandler(this.replaceCardFaceToolStripButton_Click);
+ //
+ // cardOpenFileDialog
+ //
+ this.cardOpenFileDialog.Filter = "AA2 Card files|*.png";
+ //
+ // cardFacePictureBox
+ //
+ this.cardFacePictureBox.BackColor = System.Drawing.SystemColors.AppWorkspace;
+ this.cardFacePictureBox.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
+ this.cardFacePictureBox.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.cardFacePictureBox.Location = new System.Drawing.Point(0, 25);
+ this.cardFacePictureBox.Name = "cardFacePictureBox";
+ this.cardFacePictureBox.Size = new System.Drawing.Size(584, 337);
+ this.cardFacePictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage;
+ this.cardFacePictureBox.TabIndex = 1;
+ this.cardFacePictureBox.TabStop = false;
+ //
+ // faceOpenFileDialog
+ //
+ this.faceOpenFileDialog.Filter = "PNG|*.png";
+ //
+ // saveFileDialog
+ //
+ this.saveFileDialog.Filter = "AA2 Card files|*.png";
+ //
+ // menuStrip1
+ //
+ this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.fileToolStripMenuItem,
+ this.editToolStripMenuItem});
+ this.menuStrip1.Location = new System.Drawing.Point(0, 0);
+ this.menuStrip1.Name = "menuStrip1";
+ this.menuStrip1.Size = new System.Drawing.Size(586, 24);
+ this.menuStrip1.TabIndex = 2;
+ this.menuStrip1.Text = "menuStrip1";
+ this.menuStrip1.Visible = false;
+ //
+ // fileToolStripMenuItem
+ //
+ this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.openToolStripMenuItem,
+ this.saveToolStripMenuItem,
+ this.saveAsToolStripMenuItem,
+ this.toolStripSeparator2,
+ this.exitToolStripMenuItem});
+ this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
+ this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
+ this.fileToolStripMenuItem.Text = "&File";
+ //
+ // openToolStripMenuItem
+ //
+ this.openToolStripMenuItem.Name = "openToolStripMenuItem";
+ this.openToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O)));
+ this.openToolStripMenuItem.Size = new System.Drawing.Size(155, 22);
+ this.openToolStripMenuItem.Text = "&Open...";
+ this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click);
+ //
+ // saveToolStripMenuItem
+ //
+ this.saveToolStripMenuItem.Name = "saveToolStripMenuItem";
+ this.saveToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S)));
+ this.saveToolStripMenuItem.Size = new System.Drawing.Size(155, 22);
+ this.saveToolStripMenuItem.Text = "&Save";
+ this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click);
+ //
+ // saveAsToolStripMenuItem
+ //
+ this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem";
+ this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(155, 22);
+ this.saveAsToolStripMenuItem.Text = "Save &As...";
+ //
+ // toolStripSeparator2
+ //
+ this.toolStripSeparator2.Name = "toolStripSeparator2";
+ this.toolStripSeparator2.Size = new System.Drawing.Size(152, 6);
+ //
+ // exitToolStripMenuItem
+ //
+ this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
+ this.exitToolStripMenuItem.Size = new System.Drawing.Size(155, 22);
+ this.exitToolStripMenuItem.Text = "E&xit";
+ //
+ // editToolStripMenuItem
+ //
+ this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.replaceCardFaceToolStripMenuItem});
+ this.editToolStripMenuItem.Name = "editToolStripMenuItem";
+ this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20);
+ this.editToolStripMenuItem.Text = "&Edit";
+ //
+ // replaceCardFaceToolStripMenuItem
+ //
+ this.replaceCardFaceToolStripMenuItem.Name = "replaceCardFaceToolStripMenuItem";
+ this.replaceCardFaceToolStripMenuItem.Size = new System.Drawing.Size(179, 22);
+ this.replaceCardFaceToolStripMenuItem.Text = "&Replace Card Face...";
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(584, 362);
+ this.Controls.Add(this.cardFacePictureBox);
+ this.Controls.Add(this.toolStrip1);
+ this.Controls.Add(this.menuStrip1);
+ this.MainMenuStrip = this.menuStrip1;
+ this.Name = "Form1";
+ this.Text = "AA2 Card Editor";
+ this.toolStrip1.ResumeLayout(false);
+ this.toolStrip1.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.cardFacePictureBox)).EndInit();
+ this.menuStrip1.ResumeLayout(false);
+ this.menuStrip1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.ToolStrip toolStrip1;
+ private System.Windows.Forms.ToolStripButton openToolStripButton;
+ private System.Windows.Forms.ToolStripButton saveToolStripButton;
+ private System.Windows.Forms.ToolStripButton saveAsToolStripButton;
+ private System.Windows.Forms.OpenFileDialog cardOpenFileDialog;
+ private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
+ private System.Windows.Forms.ToolStripButton replaceCardFaceToolStripButton;
+ private System.Windows.Forms.PictureBox cardFacePictureBox;
+ private System.Windows.Forms.OpenFileDialog faceOpenFileDialog;
+ private System.Windows.Forms.SaveFileDialog saveFileDialog;
+ private System.Windows.Forms.MenuStrip menuStrip1;
+ private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem saveToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem;
+ private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
+ private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem replaceCardFaceToolStripMenuItem;
+
+
+
+
+
+
+
+
+
+ }
+}
+
diff --git a/AA2CardEditor/Form1.cs b/AA2CardEditor/Form1.cs
new file mode 100644
index 0000000..3de7239
--- /dev/null
+++ b/AA2CardEditor/Form1.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Reflection;
+using System.Windows.Forms;
+
+namespace AA2CardEditor
+{
+ public partial class Form1 : Form
+ {
+ private static readonly string ApplicationName = GetApplicationName();
+
+ private AA2Card card = new AA2Card();
+
+ public Form1()
+ {
+ InitializeComponent();
+ UpdateWindowState();
+ }
+
+ private void openToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ OpenCardFile();
+ UpdateWindowState();
+ }
+
+ private void saveToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ SaveCardFile();
+ UpdateWindowState();
+ }
+
+ private void openToolStripButton_Click(object sender, EventArgs e)
+ {
+ OpenCardFile();
+ UpdateWindowState();
+ }
+
+ private void saveToolStripButton_Click(object sender, EventArgs e)
+ {
+ SaveCardFile();
+ UpdateWindowState();
+ }
+
+ private void saveAsToolStripButton_Click(object sender, EventArgs e)
+ {
+ SaveAsCardFile();
+ UpdateWindowState();
+ }
+
+ private void replaceCardFaceToolStripButton_Click(object sender, EventArgs e)
+ {
+ ReplaceCardFace();
+ UpdateWindowState();
+ }
+
+ private static string GetApplicationName()
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ Type type = typeof(AssemblyProductAttribute);
+ object[] customAttributes = assembly.GetCustomAttributes(type, false);
+ return ((AssemblyProductAttribute)customAttributes[0]).Product;
+ }
+
+ private void OpenCardFile()
+ {
+ cardOpenFileDialog.FileName = "";
+ if (cardOpenFileDialog.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.ReadCardFile(cardOpenFileDialog.FileName);
+ }
+ catch (Exception e)
+ {
+ MessageBox.Show("The card file could not be opened.");
+ }
+ }
+ }
+
+ private void SaveCardFile()
+ {
+ try
+ {
+ card.Save();
+ }
+ catch (Exception e)
+ {
+ MessageBox.Show("The card file could not be saved.");
+ }
+ }
+
+ private void SaveAsCardFile()
+ {
+ saveFileDialog.FileName = "";
+ if (saveFileDialog.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.Save(saveFileDialog.FileName);
+ }
+ catch (Exception e)
+ {
+ MessageBox.Show("The card file could not be saved.");
+ }
+ }
+ }
+
+ private void ReplaceCardFace()
+ {
+ faceOpenFileDialog.FileName = "";
+ if (faceOpenFileDialog.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.ReplaceFaceImage(faceOpenFileDialog.FileName);
+ }
+ catch (Exception e)
+ {
+ MessageBox.Show("The card face image could not be replaced.");
+ }
+ }
+ }
+
+ private void UpdateWindowState()
+ {
+ UpdateWindowCaption();
+ UpdateToolstripState();
+ UpdateCardFaceView();
+ }
+
+ private void UpdateWindowCaption()
+ {
+ Text = ApplicationName;
+ if (card.FileName != "")
+ {
+ if (card.IsDirty)
+ {
+ Text = card.FileName + "* - " + Text;
+ }
+ else
+ {
+ Text = card.FileName + " - " + Text;
+ }
+ }
+ }
+
+ private void UpdateToolstripState()
+ {
+ if (card.FileName != "")
+ {
+ saveToolStripButton.Enabled = true;
+ saveAsToolStripButton.Enabled = true;
+ replaceCardFaceToolStripButton.Enabled = true;
+ }
+ else
+ {
+ saveToolStripButton.Enabled = false;
+ saveAsToolStripButton.Enabled = false;
+ replaceCardFaceToolStripButton.Enabled = false;
+ }
+ }
+
+ private void UpdateCardFaceView()
+ {
+ cardFacePictureBox.Image = card.FaceImage;
+ }
+ }
+}
diff --git a/AA2CardEditor/Form1.resx b/AA2CardEditor/Form1.resx
new file mode 100644
index 0000000..fe9b3cd
--- /dev/null
+++ b/AA2CardEditor/Form1.resx
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJQSURBVDhPlZNdSNNRGMb/F110ZZEVhVBgeeHNICiiuggp
+ olAUyyxI0oSaH1QYC3N+tKnp5ubm1JUua5uuqdPKMgr7kApFItTUkWZqVhSVYmao5Nev/xyoQ4k88Nyc
+ 8z6/93nP4QjCfy6lwc4ltZVso4P/tMyXRcmMHqZ0EeY6jZQVInzuf0e1Tb9Ina3P/tkpLD6XkNg8BJe5
+ u93C+HDVrP4M2ZkcMOOw5tLZ9nxJyJE4HSExBoKkBQhVpTrGhso9zNPfiph0JlB+U01ZcRbmwnRMeWlc
+ 08opUCV6QissGsZ+WOY6z4hmuuXglC6pRYBbJSp+fzXNxnaZ66o1s3rkyKHWruJuWRYOcwZ2kxKr8TI3
+ DCkU6+QYNUnuNGWmLEY+5uOK3degoKZcx3SfEvozPfVB3OtNhi4ZvI2nrTIc23U9gtmYwa8eNXzScq8i
+ l6bHWnfRwhHeREJzGFONgYw/CeB9qQSZNNR9FyUGBT87lfQ3plJj1zLTq4COGDegLVo0HmeqKZjx+gOM
+ PNzDYPU2lLF+4jhyN6BIl8pgexK3bRpaXopJuhJEwGloiWDmVSgTLw4xWreXoZrtfK/wp/nKak4E+s6/
+ hDFHTkd9GndsOdCTBq1i3NdHmWgIYvRpAMO1OxlwSPhi2YpT641CuoWzsSfnAfnZiVRZ1Tjvx9GsF+bU
+ pF1BvWolD9JXUZmyDnOiD1cvbCZiYXfXCPrMi+gVZ8hOiiL53DHORwdzKnw/hw/uYt9uCTskfvj7+rBp
+ 41rWr/Fig7fX8j/Tsn/fcgx/ARfG3ml6M3rzAAAAAElFTkSuQmCC
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIsSURBVDhPrZPdS5NxFMf3L3TfTdBFtzU1hmuxGjzlHMqq
+ YVgRvT2RL+XSZZqoWJlGLV8gW+HSScvpJJxU+AamSI2hTCVLM1e0xKGm2EQw+PY7v+j5tTIvoh+cy8/n
+ POec76NS/Y/37GkUVL72ZbR5l/DYvYDGhgjuO2ZQW/MJ9tsh3CifQmnJBAoLXiMvdxQXzgeh9Cawtweo
+ qV7FRm9ldQ3GtF4cTnvCSxF4Wxe5oLLiy195giMLK9htfg61WoblkEcI3I/muaC05PO6gp/w+/Ai4kw+
+ FFyexgFzkxA462e54JLt3R+CX+GRyQi2SV5Yc8aRmuIUgrq7YS7IzhqNEfwODwbD2Kx3Q5YDMJkcQlBd
+ 9ZEL5DMBRbAe3OP/gE2JDThy9AWSkmqF4GblNLq7wE4JHD/5CpZjA3zbtDCamT6bOv+A+3DQ0glJsgvB
+ 1bJJdPjAMgA0ub6xu39F+fU5vlRaGM2cmRFU4OTUdhgMFUJwpXAcnmbgoXONBScKY3pOTJlP2JB+roh3
+ Tk5h8H4P9PoyIbDljTEYqLoT5Z1JwEKCOK2EobezGJuag5x7DXuNbRzW7nFBpysSAoql4x6UzyYBwWfz
+ b+FNaB6hmSVcLLYjXu9icCPidz2ANjFfCDIzhtncy3zmrQYPtuyQ0NLRD1/XILr7/Bh4OYR9JgvUunok
+ MHi7pg4ajVUIKNOnT/XzeFLCKCR0ZzoVbZsWRjNTVyqCdyZkxwr+9a/+Dk60OMVjMFpXAAAAAElFTkSu
+ QmCC
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVDhPpZMhb8MwEIXzU/oTAgcNCw3Dajg2w2qkhoWG
+ ZTMsm+FYDQcNBw0HDcdu712aLtKqkVl6Sir5ffd6dxmG/554rBIWBbxTe6qIp/xVj3hC7qpbXZrzWSQe
+ u/x1+peInQCYsuoXIISm/vxWVKWshN+9dzG2yDg6gNYARGYCv58Bvw4qwyvtEwAYA+5Zm1YJFoCv6g0h
+ SDpnVLGSUlK11hRgXbkDQKM0wRWwJKCRp7xFyclK+6ji2WwUtNMqATtNgHucE+hhw7aj5Ahj9dLbDGFv
+ 0qnh7hqAMRGQTl0hFCeTzmWGnKzU4hQSg0Fzq8RYfnrAOSsAiakYu0TA8rnr5bsQJLmNkcuxAGi2zquc
+ DwAlxEZD7yR5fTkYhXCzlsoBy2QnT7oYg+ita/fLCtLePaAYvXdyOTzZwWE06YTxwcxlImA0BnsRMb4Z
+ kDJTzknCMUg9emkRIEAUwOg0b7ZZNqNZbWOV8s7GehgJwd0HNBbGunf6Pux2F8u/wfXkht20jWIWmSDO
+ RRTJmoRG6vK8G//7MQ/fqYdMddv91Q8AAAAASUVORK5CYII=
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIISURBVDhPpZP7S1NxGMbPPxKaXVUkMEq8IpKUCoY/hGgI
+ ymqkDYYXcCjDZOANURSjCNGFQUTsl4GXVMxKk62YU4fXQpaIlygHQxBRH8/zwvyaIAYe+HLgnPN8nue9
+ HA3nvDTq63oW/jm13XOwvPTB3DYFY5MH+bXfcN8ygfTSMSSXfESicQDxBqdYHwH29g9w2tnZ3UcguIvN
+ rR3417exuBJE5N1n/wfwLgXEOc38Bc6xNRHb+/y4nm49G0Bnit2zf9H6bkliE/jKuYxrd6oVgDWfjB+K
+ TWeKMyrGEVfowITvD9re/9ABVQrAhh0HHK+ZselMMaN/mvwtDb+aVqkA7HYIwIj3ysfluPTorJnP6Ezx
+ oHsD1s5ZXEktUwCOioB5f1CEPR9+wTG6iuiserTo8dkwng7HT/R+XUPF8xlcTjErAOdMcW6NW8STiwG8
+ 7vej8oUPN/PsEv3t8Ao0TZP3T1u8uJRkUgAuSYHtO97oLxmXd5t9Ho8aPTK+GzntqNfrLm2fFoihwYOI
+ xGIF4KjoGBLzY1OrF9k6OOFxnwDC4wxIMX1G0pMhgVyMNyoA13PAtS7OrJk1PrC69LUdQWxuF6IybHrX
+ LRI7JrtZdoDAo1XmbjMyD+tjSXxGcXRmnYg5ttD9QuxDhN0uUgDOmbvNTpPOJaGAo2K36cyaGZvOFIfd
+ KlSA8/zRh9ABIDUG+1JpAAAAAElFTkSuQmCC
+
+
+
+ 122, 17
+
+
+ 281, 17
+
+
+ 439, 17
+
+
+ 568, 17
+
+
\ No newline at end of file
diff --git a/AA2CardEditor/PngFileReader.cs b/AA2CardEditor/PngFileReader.cs
new file mode 100644
index 0000000..ef3a5a2
--- /dev/null
+++ b/AA2CardEditor/PngFileReader.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+
+namespace AA2CardEditor
+{
+ class PngFileReader
+ {
+ private static readonly byte[] PngFileSignature = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
+
+ private static readonly int PngChunkTypeLength = 4;
+ private static readonly int PngChunkOverheadLength = 12;
+
+ private static readonly byte[] PngHeaderChunkType = { 0x49, 0x48, 0x44, 0x52 };
+ private static readonly byte[] PngDataChunkType = { 0x49, 0x44, 0x41, 0x54 };
+ private static readonly byte[] PngEndChunkType = { 0x49, 0x45, 0x4e, 0x44 };
+
+ public static MemoryStream Read(MemoryStream inputStream)
+ {
+ byte[] inputBuffer = inputStream.GetBuffer();
+ MemoryStream outputStream = new MemoryStream();
+
+ using (WrappingStream wrappingStream = new WrappingStream(inputStream))
+ using (BinaryReader reader = new BinaryReader(wrappingStream))
+ {
+ // Read the signature and verify that it's equal to the PNG signature.
+ byte[] fileSignature = reader.ReadBytes(PngFileSignature.Length);
+
+ if (!fileSignature.SequenceEqual(PngFileSignature))
+ {
+ throw new Exception("The file is not a PNG format file.");
+ }
+
+ // Copy the PNG file signature to the card face memory stream.
+ outputStream.Write(fileSignature, 0, fileSignature.Length);
+
+ // Copy all of the critical chunks to the card face memory stream.
+ byte[] chunkType = new byte[PngChunkTypeLength];
+ do
+ {
+ int chunkBeginPosition = (int)reader.BaseStream.Position;
+
+ int chunkLength = IPAddress.NetworkToHostOrder(reader.ReadInt32());
+ chunkType = reader.ReadBytes(PngChunkTypeLength);
+
+ if (chunkType.SequenceEqual(PngHeaderChunkType) ||
+ chunkType.SequenceEqual(PngDataChunkType) ||
+ chunkType.SequenceEqual(PngEndChunkType))
+ {
+ outputStream.Write(inputBuffer, (int)chunkBeginPosition,
+ chunkLength + PngChunkOverheadLength);
+ }
+
+ reader.BaseStream.Seek(chunkLength + 4, SeekOrigin.Current);
+ } while (!chunkType.SequenceEqual(PngEndChunkType));
+ }
+ return outputStream;
+ }
+ }
+}
diff --git a/AA2CardEditor/Program.cs b/AA2CardEditor/Program.cs
new file mode 100644
index 0000000..f442414
--- /dev/null
+++ b/AA2CardEditor/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace AA2CardEditor
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/AA2CardEditor/Properties/AssemblyInfo.cs b/AA2CardEditor/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1930595
--- /dev/null
+++ b/AA2CardEditor/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AA2 Card Editor")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AA2 Card Editor")]
+[assembly: AssemblyCopyright("Copyright © 2014 Anonymous")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c3ce529d-f5d8-4199-92e2-58938f4fc979")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.1")]
+[assembly: AssemblyFileVersion("1.0.0.1")]
diff --git a/AA2CardEditor/Properties/Resources.Designer.cs b/AA2CardEditor/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..d808f21
--- /dev/null
+++ b/AA2CardEditor/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2CardEditor.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AA2CardEditor.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/AA2CardEditor/Properties/Resources.resx b/AA2CardEditor/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/AA2CardEditor/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/AA2CardEditor/Properties/Settings.Designer.cs b/AA2CardEditor/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..163a6a3
--- /dev/null
+++ b/AA2CardEditor/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2CardEditor.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/AA2CardEditor/Properties/Settings.settings b/AA2CardEditor/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/AA2CardEditor/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/AA2CardEditor/WrappingStream.cs b/AA2CardEditor/WrappingStream.cs
new file mode 100644
index 0000000..b992cc0
--- /dev/null
+++ b/AA2CardEditor/WrappingStream.cs
@@ -0,0 +1,211 @@
+// From http://code.logos.com/blog/2009/05/wrappingstream_implementation.html
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace AA2CardEditor
+{
+ ///
+ /// A that wraps another stream. The major feature of is that it does not dispose the
+ /// underlying stream when it is disposed; this is useful when using classes such as and
+ /// that take ownership of the stream passed to their constructors.
+ ///
+ public class WrappingStream : Stream
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The wrapped stream.
+ public WrappingStream(Stream streamBase)
+ {
+ // check parameters
+ if (streamBase == null)
+ throw new ArgumentNullException("streamBase");
+
+ m_streamBase = streamBase;
+ }
+
+ ///
+ /// Gets a value indicating whether the current stream supports reading.
+ ///
+ /// true if the stream supports reading; otherwise, false.
+ public override bool CanRead
+ {
+ get { return m_streamBase == null ? false : m_streamBase.CanRead; }
+ }
+
+ ///
+ /// Gets a value indicating whether the current stream supports seeking.
+ ///
+ /// true if the stream supports seeking; otherwise, false.
+ public override bool CanSeek
+ {
+ get { return m_streamBase == null ? false : m_streamBase.CanSeek; }
+ }
+
+ ///
+ /// Gets a value indicating whether the current stream supports writing.
+ ///
+ /// true if the stream supports writing; otherwise, false.
+ public override bool CanWrite
+ {
+ get { return m_streamBase == null ? false : m_streamBase.CanWrite; }
+ }
+
+ ///
+ /// Gets the length in bytes of the stream.
+ ///
+ public override long Length
+ {
+ get { ThrowIfDisposed(); return m_streamBase.Length; }
+ }
+
+ ///
+ /// Gets or sets the position within the current stream.
+ ///
+ public override long Position
+ {
+ get { ThrowIfDisposed(); return m_streamBase.Position; }
+ set { ThrowIfDisposed(); m_streamBase.Position = value; }
+ }
+
+ ///
+ /// Begins an asynchronous read operation.
+ ///
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ ThrowIfDisposed();
+ return m_streamBase.BeginRead(buffer, offset, count, callback, state);
+ }
+
+ ///
+ /// Begins an asynchronous write operation.
+ ///
+ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ ThrowIfDisposed();
+ return m_streamBase.BeginWrite(buffer, offset, count, callback, state);
+ }
+
+ ///
+ /// Waits for the pending asynchronous read to complete.
+ ///
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ ThrowIfDisposed();
+ return m_streamBase.EndRead(asyncResult);
+ }
+
+ ///
+ /// Ends an asynchronous write operation.
+ ///
+ public override void EndWrite(IAsyncResult asyncResult)
+ {
+ ThrowIfDisposed();
+ m_streamBase.EndWrite(asyncResult);
+ }
+
+ ///
+ /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
+ ///
+ public override void Flush()
+ {
+ ThrowIfDisposed();
+ m_streamBase.Flush();
+ }
+
+ ///
+ /// Reads a sequence of bytes from the current stream and advances the position
+ /// within the stream by the number of bytes read.
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ return m_streamBase.Read(buffer, offset, count);
+ }
+
+ ///
+ /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
+ ///
+ public override int ReadByte()
+ {
+ ThrowIfDisposed();
+ return m_streamBase.ReadByte();
+ }
+
+ ///
+ /// Sets the position within the current stream.
+ ///
+ /// A byte offset relative to the parameter.
+ /// A value of type indicating the reference point used to obtain the new position.
+ /// The new position within the current stream.
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ return m_streamBase.Seek(offset, origin);
+ }
+
+ ///
+ /// Sets the length of the current stream.
+ ///
+ /// The desired length of the current stream in bytes.
+ public override void SetLength(long value)
+ {
+ ThrowIfDisposed();
+ m_streamBase.SetLength(value);
+ }
+
+ ///
+ /// Writes a sequence of bytes to the current stream and advances the current position
+ /// within this stream by the number of bytes written.
+ ///
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ ThrowIfDisposed();
+ m_streamBase.Write(buffer, offset, count);
+ }
+
+ ///
+ /// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
+ ///
+ public override void WriteByte(byte value)
+ {
+ ThrowIfDisposed();
+ m_streamBase.WriteByte(value);
+ }
+
+ ///
+ /// Gets the wrapped stream.
+ ///
+ /// The wrapped stream.
+ protected Stream WrappedStream
+ {
+ get { return m_streamBase; }
+ }
+
+ ///
+ /// Releases the unmanaged resources used by the and optionally releases the managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected override void Dispose(bool disposing)
+ {
+ // doesn't close the base stream, but just prevents access to it through this WrappingStream
+ if (disposing)
+ m_streamBase = null;
+
+ base.Dispose(disposing);
+ }
+
+ private void ThrowIfDisposed()
+ {
+ // throws an ObjectDisposedException if this object has been disposed
+ if (m_streamBase == null)
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
+ Stream m_streamBase;
+ }
+}
diff --git a/AA2Snowflake.sln b/AA2Snowflake.sln
new file mode 100644
index 0000000..fc4affa
--- /dev/null
+++ b/AA2Snowflake.sln
@@ -0,0 +1,48 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.23107.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA2Snowflake", "AA2Snowflake\AA2Snowflake.csproj", "{D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SB3UtilityPP", "SB3UtilityPP\SB3UtilityPP.csproj", "{5647B104-8F58-4867-B849-DAC65D5243BD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA2CardEditor", "AA2CardEditor\AA2CardEditor.csproj", "{82358432-2268-408F-871D-3664E2B76EEC}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Debug|x86.ActiveCfg = Debug|x86
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Debug|x86.Build.0 = Debug|x86
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Release|Any CPU.ActiveCfg = Release|x86
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Release|Any CPU.Build.0 = Release|x86
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Release|x86.ActiveCfg = Release|x86
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}.Release|x86.Build.0 = Release|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Debug|Any CPU.Build.0 = Debug|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Debug|x86.ActiveCfg = Debug|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Debug|x86.Build.0 = Debug|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Release|Any CPU.ActiveCfg = Release|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Release|Any CPU.Build.0 = Release|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Release|x86.ActiveCfg = Release|x86
+ {5647B104-8F58-4867-B849-DAC65D5243BD}.Release|x86.Build.0 = Release|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Debug|Any CPU.Build.0 = Debug|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Debug|x86.ActiveCfg = Debug|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Debug|x86.Build.0 = Debug|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Release|Any CPU.ActiveCfg = Release|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Release|Any CPU.Build.0 = Release|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Release|x86.ActiveCfg = Release|x86
+ {82358432-2268-408F-871D-3664E2B76EEC}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/AA2Snowflake/AA2Snowflake.csproj b/AA2Snowflake/AA2Snowflake.csproj
new file mode 100644
index 0000000..76cb566
--- /dev/null
+++ b/AA2Snowflake/AA2Snowflake.csproj
@@ -0,0 +1,255 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D0BF1F9F-19D4-4E0A-B7CB-779388D7C91D}
+ WinExe
+ Properties
+ AA2Snowflake
+ AA2Snowflake
+ v4.6
+ 512
+ true
+
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ Resources\aa2snowflake.ico
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ MinimumRecommendedRules.ruleset
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ formLoad.cs
+
+
+ Form
+
+
+ formMain.cs
+
+
+
+
+
+
+ formLoad.cs
+
+
+ formMain.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+ True
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+ {82358432-2268-408f-871d-3664e2b76eec}
+ AA2CardEditor
+
+
+ {5647b104-8f58-4867-b849-dac65d5243bd}
+ SB3UtilityPP
+
+
+
+
+
\ No newline at end of file
diff --git a/AA2Snowflake/App.config b/AA2Snowflake/App.config
new file mode 100644
index 0000000..2d2a12d
--- /dev/null
+++ b/AA2Snowflake/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/AA2Snowflake/Changelog.txt b/AA2Snowflake/Changelog.txt
new file mode 100644
index 0000000..81de896
--- /dev/null
+++ b/AA2Snowflake/Changelog.txt
@@ -0,0 +1,2 @@
+v1
++ Initial build
\ No newline at end of file
diff --git a/AA2Snowflake/Paths.cs b/AA2Snowflake/Paths.cs
new file mode 100644
index 0000000..37c412c
--- /dev/null
+++ b/AA2Snowflake/Paths.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AA2Install
+{
+ public static class Paths
+ {
+ ///
+ /// 7Zip standalone location.
+ ///
+ public static string _7Za
+ {
+ get
+ {
+ if (Environment.Is64BitOperatingSystem)
+ {
+ return Environment.CurrentDirectory + @"\tools\7z\x64\7za.exe";
+ }
+ else
+ {
+ return Environment.CurrentDirectory + @"\tools\7z\x86\7za.exe";
+ }
+ }
+ }
+ ///
+ /// AA2Play data install location.
+ ///
+ public static string AA2Play
+ {
+ get
+ {
+ /*if (bool.Parse(Configuration.ReadSetting("AA2PLAY") ?? "False"))
+ {
+ return Configuration.ReadSetting("AA2PLAY_Path");
+ }
+ else
+ {*/
+ object dir = Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\illusion\AA2Play", "INSTALLDIR", "NULL");
+ return dir + @"data";
+ //}
+ }
+ }
+ ///
+ /// AA2Edit data install location.
+ ///
+ public static string AA2Edit
+ {
+ get
+ {
+ /*if (bool.Parse(Configuration.ReadSetting("AA2EDIT") ?? "False"))
+ {
+ return Configuration.ReadSetting("AA2EDIT_Path");
+ }
+ else
+ {*/
+ object dir = Microsoft.Win32.Registry.GetValue(@"HKEY_CURRENT_USER\Software\illusion\AA2Edit", "INSTALLDIR", "NULL");
+ return dir + @"data";
+ //}
+ }
+ }
+ public static string Nature => Environment.CurrentDirectory + @"\nature";
+ public static string BACKUP => Environment.CurrentDirectory + @"\backup";
+
+ }
+}
diff --git a/AA2Snowflake/Program.cs b/AA2Snowflake/Program.cs
new file mode 100644
index 0000000..9d748e4
--- /dev/null
+++ b/AA2Snowflake/Program.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace AA2Snowflake
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new formMain());
+ }
+ }
+}
diff --git a/AA2Snowflake/Properties/AssemblyInfo.cs b/AA2Snowflake/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..572a3e2
--- /dev/null
+++ b/AA2Snowflake/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AA2Snowflake")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AA2Snowflake")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d0bf1f9f-19d4-4e0a-b7cb-779388d7c91d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/AA2Snowflake/Properties/Resources.Designer.cs b/AA2Snowflake/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..46dc313
--- /dev/null
+++ b/AA2Snowflake/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2Snowflake.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AA2Snowflake.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/AA2Snowflake/Properties/Resources.resx b/AA2Snowflake/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/AA2Snowflake/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/AA2Snowflake/Properties/Settings.Designer.cs b/AA2Snowflake/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..84b8f3c
--- /dev/null
+++ b/AA2Snowflake/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2Snowflake.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/AA2Snowflake/Properties/Settings.settings b/AA2Snowflake/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/AA2Snowflake/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/AA2Snowflake/Resources/aa2snowflake.ico b/AA2Snowflake/Resources/aa2snowflake.ico
new file mode 100644
index 0000000..e8af608
Binary files /dev/null and b/AA2Snowflake/Resources/aa2snowflake.ico differ
diff --git a/AA2Snowflake/Tools.cs b/AA2Snowflake/Tools.cs
new file mode 100644
index 0000000..61fa138
--- /dev/null
+++ b/AA2Snowflake/Tools.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using SB3Utility;
+using AA2Install;
+using Microsoft.VisualBasic.FileIO;
+
+namespace AA2Snowflake
+{
+ public static class Tools
+ {
+ public static Encoding ShiftJIS => Encoding.GetEncoding(932);
+
+ public static MemoryStream GetStreamFromSubfile(IWriteFile iw)
+ {
+ MemoryStream mem = new MemoryStream();
+ iw.WriteTo(mem);
+ mem.Position = 0;
+ return mem;
+ }
+
+ public static MemoryStream GetStreamFromFile(string file)
+ {
+ MemoryStream mem = new MemoryStream();
+ using (FileStream b = new FileStream(file, FileMode.Open))
+ b.CopyTo(mem);
+ mem.Position = 0;
+ return mem;
+ }
+
+ public static void BackupFile(string file)
+ {
+ string name = file.Remove(0, file.LastIndexOf('\\'));
+ FileSystem.CopyFile(file, Paths.BACKUP + name, UIOption.AllDialogs);
+ }
+
+ public static void RestoreFile(string file)
+ {
+ string name = file.Remove(0, file.LastIndexOf('\\'));
+ FileSystem.CopyFile(Paths.BACKUP + name, file, UIOption.AllDialogs);
+ PP.jg2e00_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e00_00_00.pp", new ppFormat_AA2());
+ PP.jg2e01_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e01_00_00.pp", new ppFormat_AA2());
+ PP.jg2e06_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e06_00_00.pp", new ppFormat_AA2());
+ PP.jg2p01_00_00 = new ppParser(Paths.AA2Play + @"\jg2p01_00_00.pp", new ppFormat_AA2());
+ }
+
+ public static void DeleteBackup(string file)
+ {
+ string name = file.Remove(0, file.LastIndexOf('\\'));
+ FileSystem.DeleteFile(Paths.BACKUP + name, UIOption.AllDialogs, RecycleOption.DeletePermanently);
+ }
+
+ public static byte[] GetBytesFromStream(Stream str)
+ {
+ str.Position = 0;
+ byte[] buffer = new byte[str.Length];
+ str.Read(buffer, 0, (int)str.Length);
+ return buffer;
+ }
+
+ public static MemSubfile ManipulateLst(IWriteFile lst, int column, string replacement)
+ {
+ byte[] buffer;
+ using (MemoryStream mem = GetStreamFromSubfile(lst))
+ buffer = GetBytesFromStream(mem);
+
+ string slst = ShiftJIS.GetString(buffer);
+
+ StringBuilder str = new StringBuilder();
+ foreach (string line in slst.Split(new string[] { "\r\n" }, StringSplitOptions.None))
+ {
+ if (string.IsNullOrWhiteSpace(line))
+ continue;
+
+ string[] set = line.Split('\t');
+ set[column - 1] = replacement;
+
+ str.Append(set.Aggregate((i, j) => i + "\t" + j) + "\r\n");
+ }
+
+ return new MemSubfile(new MemoryStream(ShiftJIS.GetBytes(str.ToString())), lst.Name);
+ }
+ }
+
+ public static class PP
+ {
+ public static ppParser jg2e00_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e00_00_00.pp", new ppFormat_AA2());
+ public static ppParser jg2e01_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e01_00_00.pp", new ppFormat_AA2());
+ public static ppParser jg2e06_00_00 = new ppParser(Paths.AA2Edit + @"\jg2e06_00_00.pp", new ppFormat_AA2());
+ public static ppParser jg2p01_00_00 = new ppParser(Paths.AA2Play + @"\jg2p01_00_00.pp", new ppFormat_AA2());
+ }
+}
diff --git a/AA2Snowflake/formLoad.Designer.cs b/AA2Snowflake/formLoad.Designer.cs
new file mode 100644
index 0000000..d15a799
--- /dev/null
+++ b/AA2Snowflake/formLoad.Designer.cs
@@ -0,0 +1,64 @@
+namespace AA2Snowflake
+{
+ partial class formLoad
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(formLoad));
+ this.progressBar1 = new System.Windows.Forms.ProgressBar();
+ this.SuspendLayout();
+ //
+ // progressBar1
+ //
+ this.progressBar1.Location = new System.Drawing.Point(12, 12);
+ this.progressBar1.Name = "progressBar1";
+ this.progressBar1.Size = new System.Drawing.Size(329, 28);
+ this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+ this.progressBar1.TabIndex = 0;
+ //
+ // formLoad
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(353, 52);
+ this.ControlBox = false;
+ this.Controls.Add(this.progressBar1);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "formLoad";
+ this.Text = "Loading...";
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.ProgressBar progressBar1;
+ }
+}
\ No newline at end of file
diff --git a/AA2Snowflake/formLoad.cs b/AA2Snowflake/formLoad.cs
new file mode 100644
index 0000000..2f50237
--- /dev/null
+++ b/AA2Snowflake/formLoad.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace AA2Snowflake
+{
+ public partial class formLoad : Form
+ {
+ public formLoad()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/AA2Snowflake/formLoad.resx b/AA2Snowflake/formLoad.resx
new file mode 100644
index 0000000..ffa0a51
--- /dev/null
+++ b/AA2Snowflake/formLoad.resx
@@ -0,0 +1,886 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+
+ 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
+ /v//7P/+/uv+z8/B/mlpY18AAAAAAAAAAJKSiP7+/uv+/v7r/vf35f6fn5T++Pjl/v7+6/7+/uv+hIR8
+ +UpKRi4RERANAAAAAAAAAAAKCgoNPDw5ZMvLvf7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+kZGI
+ /gAAAAAAAAAAYGBbX8/PwP7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+z8/A/mxsZl8AAAAAAAAAAJKSiP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+ysq8/kVFQmQTExMNAAAAAAAAAAAJCQkNS0tHHFdXU8/Fxbf+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+mpqQ/mBgW1AAAAAAWlpVX8/PwP7+/uv+/v7r/vr65/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv++Pjm/v7+6/7+/uv+z8/A/mRkX18AAAAAV1dTUJqakP7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7Fxbf+VlZSz1RUUB0SEhENAAAAAAAAAAAEBAQNLy8tBlpaVQRISEV2bW1n
+ /vX14//+/uv+/v7r/v//7P/+/uv+/f3q/rGxpP5RUU24UlJOj8/Pwf/+/uv+/v7r/r6+sf9sbGb339/O
+ /v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/t/fz/9sbGb6vb2w/v//7P/+/uv+z8/B/lZWUo9RUU25sbGk
+ /v7+6//+/uv+/v7r/v//7P/+/uv+9fXj/m1tZ/9LS0h2YmJdBDY2NAYJCQgNAAAAAAAAAAAUFBMNPz88
+ B0hIRVFYWFTYx8e5/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7y8uD+kJCH/tbWxv7+/uv+/v7r
+ /ry8r/5qamROTExIh4+PhvXy8uD+/v7r/v7+6/7y8uD+j4+G9U5OSodhYVxRvLyv/v7+6/7+/uv+1dXG
+ /pCQh/7y8uD+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/sfHuf5ZWVTYTk5KUUZGQgcbGxoNAAAA
+ AAAAAAAzMzENQUE+qqionf76+uf+/v7r/v7+6/7+/uv+/v7r/u3t3P77++j+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/ry8r/5kZF4/AAAAAGRkXlG7u67+/v7r/v7+6/67u67+bW1nUAAAAABYWFM/vLyv
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/77++j+7e3b/v7+6/7+/uv+/v7r/v7+6/76+uf+qKid
+ /kNDQKpBQT4NAAAAAAAAAAA5OTcNe3tz9f//7P/+/uv+/v7r/v//7P/09OL+lpaM91RUUJxhYVzWzc2/
+ /v7+6//+/uv+/v7r/v//7P/+/uv+/v7r/r+/sv9ZWVRmAAAAAFlZVVe7u67+/v7r/v//7P+7u67+YWFb
+ VQAAAABRUU1mv7+x/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/s3Nv/5hYVzXVFRPnJaWjPf09OL+/v7r
+ /v//7P/+/uv+/v7r/nt7dPVKSkYNAAAAAAAAAAAzMzENQUE+tuzs2/7+/uv+/f3q/rW1qP1eXlm0cHBp
+ KAAAAABkZF4HTExIcnR0bfrn59b+/v7r/v7+6/7+/uv+/v7r/vr65/6oqJ39Tk5KzZGRiPr09OL+/v7r
+ /v7+6/7y8uD+jIyD90xMSMyoqJ39+vrn/v7+6/7+/uv+/v7r/v7+6/7n59b+dnZv+VBQTHJoaGIHAAAA
+ AGpqZChdXVi0tLSo/f396v7+/uv+7Ozb/kREQbZCQj8NAAAAAAAAAAAcHBsNSkpGLXBwavTBwbP+bGxm
+ 2GdnYUp0dG0BAAAAAAAAAABVVVARRUVCkIiIf/3w8N7+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv++vrn
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/vr65/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7w8N7+iIh/
+ /EhIRZBbW1YRAAAAAAAAAABra2UBYmJcSmZmYdrBwbP+cHBq9FJSTi0mJiQNAAAAAAAAAAAGBgYNODg1
+ BldXUiJaWlVDdnZvBQAAAAAAAAAAX19aA09PS2Zra2Xs3t7O/v//7P/+/uv+/v7r/v//7P/8/On+/f3q
+ /v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v396v/8/Or+/v7r
+ /v//7P/+/uv+/v7r/t7ezv9tbWfpUlJOZmdnYQQAAAAAAAAAAHBwagVaWlVDXFxWIkNDPwYKCgoNAAAA
+ AAAAAAAAAAANCgoKBgAAAAAAAAAAAAAAAAAAAABdXVgEUVFOvcLCtP79/er+/v7r/v7+6/7+/uv+/v7r
+ /tTUxf5oaGLZZWVg4NXVxv7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+1dXF
+ /mVlYOBoaGLZ1NTE/v7+6/7+/uv+/v7r/v7+6/79/er+wcG0/lNTT71qamQEAAAAAAAAAAAAAAAAAAAA
+ AA0NDQYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAABRUU1CsrKl/v7+6/7+/uv+/v7r
+ /v7+6/7s7Nv+fHx1+VNTT3lwcGoJbGxmC0tLSLz4+Ob+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/74+Ob+TU1Ku3NzbAttbWcJT09LeX19dfnq6tn+/v7r/v7+6/7+/uv+/v7r/rKypf5dXVhBAAAA
+ AAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAABQUE1CsrKl
+ /v//7P/+/uv+/v7r/v//7P/s7Nv+fX11+VBQTXljY10JX19aC0lJRr/4+Ob+/v7r/v//7P/+/uv+/v7r
+ /v//7P/+/uv+/v7r/v//7P/4+Ob+TExJvmdnYQteXlkJTExJeX19dfrq6tn+/v7r/v//7P/+/uv+/v7r
+ /rKypv5dXVhBAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANBgYGBgAAAAAAAAAAAAAA
+ AAAAAABiYl0EU1NPvMHBtP79/er+/v7r/v7+6/7+/uv+/v7r/tTUxf5nZ2HZZ2dh4NXVxv7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+1dXG/mdnYeBnZ2HZ1NTF/v7+6/7+/uv+/v7r
+ /v7+6/79/er+wcG0/lRUUL1vb2kEAAAAAAAAAAAAAAAAAAAAAAcHBwYAAAANAAAAAAAAAAAEBAQNLy8s
+ BkxMSCJNTUpDZ2diBQAAAAAAAAAAbGxmA1VVUGZtbWfo3t7O/v7+6/7+/uv+/v7r/v7+6/78/On+/Pzq
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v396v78/On+/v7r
+ /v7+6/7+/uv+/v7r/t7ezv5ra2XrV1dSZnNzbAQAAAAAAAAAAGJiXQVOTkpDUFBMIjg4NQYHBwcNAAAA
+ AAAAAAAWFhUNRUVBLXFxavXBwbP+ampk2WBgW0pmZmEBAAAAAAAAAABgYFsRR0dEkIiIgP3w8N7+/v7r
+ /v//7P/+/uv+/v7r/v//7P/+/uv+9fXj/v7+6//+/uv+/v7r/v//7P/+/uv+/f3q/vX14//+/uv+/v7r
+ /v//7P/+/uv+/v7r/v//7P/x8d/+iIiA/EpKR5BlZWARAAAAAAAAAABcXFcCWlpWSmZmYNrBwbP+cHBq
+ 9E1NSS0fHx4NAAAAAAAAAAAxMS8NQEA9tuzs2/7+/uv+/f3q/rW1qP1cXFe0ZmZhKAAAAABYWFMHTk5L
+ cXZ2b/rn59b+/v7r/v7+6/7+/uv+/v7r/vr65/6oqJ39VlZSvHJya+nq6tn+/v7r/v7+6/7o6Nf+bGxm
+ 6FVVUb6oqJ39+vrn/v7+6/7+/uv+/v7r/v7+6/7o6Nf+eHhx+VNTT3NdXVgHAAAAAF9fWihYWFO2t7eq
+ /v396v7+/uv+7Ozb/kBAPbk/PzwNAAAAAAAAAAA5OTcNe3tz9f7+6/7+/uv+/v7r/v7+6/709OL+lpaM
+ 91FRTZxgYFvXzc2+/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/r+/sv5eXllmAAAAAGhoYkm7u67+/v7r
+ /v7+6/66uq3+cHBpSAAAAABWVlJmv7+x/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/s3Nvv5gYFvXUFBM
+ nJaWjPf09OL+/v7r/v7+6/7+/uv+/v7r/nx8dPVJSUYNAAAAAAAAAAA2NjMNQ0NAqqionf76+uf+/v7r
+ /v//7P/+/uv+/v7r/u3t3P/7++j+/v7r/v//7P/+/uv+/v7r/v7+6//+/uv+/v7r/r29r/9kZF8/AAAA
+ AF9fWlG7u67+/v7r/v//7P+7u67+aWljUQAAAABYWFQ/vLyv/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r
+ /v//7P/7++j+7e3c/v//7P/+/uv+/v7r/v//7P/6+uf+qKid/kVFQqpFRUENAAAAAAAAAAAbGxoNTExI
+ B1BQTFBZWVTYx8e5/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7y8uD+kJCH/tbWxv7+/uv+/v7r
+ /ry8r/5iYlxRR0dEh4+PhvXy8uD+/v7r/v7+6/7y8uD+j4+G9UlJRohgYFpPvLyv/v7+6/7+/uv+1dXG
+ /pCQh/7z8+H+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/sfHuf5ZWVXYVVVQUFNTTgcjIyINAAAA
+ AAAAAAAFBQUNKCgnBlRUUARHR0R2bW1n/vX14/7+/uv+/v7r/v7+6/7+/uv+/f3q/rCwpP5UVFC3VlZS
+ j8/Pwf7+/uv+/v7r/r29sP5sbGb639/O/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/t/fz/5sbGb3vb2w
+ /v7+6/7+/uv+z8/A/lhYU49SUk69s7Om/v396v7+/uv+/v7r/v7+6/7+/uv+8/Ph/m1tZv5LS0d2XV1Y
+ BDAwLgYICAgNAAAAAAAAAAAICAgNRkZCHFRUUM/Gxrj+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+mpqQ
+ /mRkX1AAAAAAW1tWX8/Pwf/+/uv+/v7r/vn55v/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r
+ /v//7P/+/uv++fnm/v//7P/+/uv+z8/B/mRkX18AAAAAXFxXUJqakP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7Fxbf+V1dSz1FRTB0QEBANAAAAAAAAAAAKCgoNOzs5ZMrKvP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+kZGI/gAAAAAAAAAAX19aX8/PwP7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+z8/A/mtrZV8AAAAAAAAAAJKSiP7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+y8u9/kZGQmQTExMNAAAAAAAAAAAJCQkNQUE+LoSEfPn+/uv+/v7r
+ /vj45f6fn5T+9/fk/v7+6/7+/uv+kZGI/gAAAAAAAAAAXl5ZX8/Pwf7+/uv+/v7r/v7+6/7+/uv++fnn
+ /pqakP7Gxrj+/v7r/v7+6/7Gxrj+mpqQ/vn55/7+/uv+/v7r/v7+6/7+/uv+z8/A/mpqZF8AAAAAAAAA
+ AJKSiP7+/uv+/v7r/vf35f6fn5T++Pjl/v7+6/7+/uv+hIR8+UxMSC4SEhENAAAAAAAAAAAFBQUNPDw5
+ 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
+ AAAAAAAFBQUNAAAAAAAAAAAfHx4NWlpVj5WVi+RSUk53tLSn/v7+6/62tqn+AAAAAEVFQiTAwLL+/v7r
+ /sfHuf1lZV+gYWFcmf7+6/7+/uv+ZWVfmWNjXqDHx7n9/v7r/r6+sf1UVFAjAAAAALa2qf7+/uv+tLSn
+ /lNTTneUlIrkXV1YjSgoJg0AAAAAAAAAAC4uLDS9vbD8/v7r/uzs2v7f38/+/v7r/ra2qf4AAAAAT09L
+ P9/fz/7+/uv+/v7r/vz86f65uaz+/v7r/v7+6/65uaz+/Pzp/v7+6/7+/uv+39/P/l9fWj8AAAAAtrap
+ /v7+6/7g4M/+7Ozb/v7+6/69vbD8ODg1NAAAAAAAAAAANDQxK5WVi+n4+Ob+/v7r/v7+6/7+/uv+urqt
+ /mVlXyNTU08/39/P/v7+6/78/On+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/Pzp/v7+6/7f38/+X19a
+ P1lZVCO6uq3+/v7r/v7+6/7+/uv++Pjm/pWVi+k9PTosAAAAAAAAAAAiIiENV1dTHmBgW+r6+uf+/v7r
+ /v7+6/7+/uv+uLir9ltbVrXf38/+/v7r/pqakO6bm5Dh9vbk/v7+6/7+/uv+9vbk/pubkeGXl43x/v7r
+ /t/fz/5dXVi2t7eq9v7+6/7+/uv+/v7r/vr65/5gYFvqXl5ZHikpJw0AAAAAAAAAAEBAPVaWlozp9vbk
+ /v7+6/7+/uv+9fXj/v7+6/7+/uv+9vbk/vb25P7+/uv+mJiO1GNjXQptbWet/v7r/v7+6/5ycmutYWFc
+ CpeXjdT+/uv+9vbk/vb25P7+/uv+/v7r/vX14/7+/uv+/v7r/vb25P6WlozpR0dDVgAAAAAAAAAAXl5Z
+ p/7+6/7+/uv+8PDf/pKSidNiYl1ujIyD2O/v3f7+/uv+/v7r/v7+6/6qqp7sV1dSRmpqZLL+/uv+/v7r
+ /mtrZbBTU09Fqamd7P7+6/7+/uv+/v7r/u/v3f6NjYTYYmJcb5GRiNPw8N/+/v7r/v7+6/5kZF6nAAAA
+ AAAAAAA8PDk9ra2h+qysoO5qamRtYmJcBAAAAABRUU1NjIyD/P7+6/7+/uv+/v7r/v7+6/7W1sb+9/fk
+ /v7+6/7+/uv+9vbk/tXVxf7+/uv+/v7r/v7+6/7+/uv+jY2D+1ZWUk0AAAAAW1tWBGZmYG2pqZ7ura2h
+ +kREQT0AAAAAAAAAAB8fHg1JSUUhXV1YDgAAAABWVlIgbW1nstbWx/7+/uv+/v7r/vj45v7T08T+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7T08T++Pjm/v7+6/7+/uv+1dXG/nFxarFdXVggAAAA
+ AFpaVQ5MTEghJiYkDQAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAH5+d8b+/uv+/v7r/v7+6/69va/8aGhi
+ jV9fWj2ZmY/h/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+m5uQ4GFhXD1lZWCNvLyv/P7+6/7+/uv+/v7r
+ /oCAeMUAAAAAAAAAAAAAAAABAQENAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAf393xv7+6/7+/uv+/v7r
+ /r29r/xkZF+NVFRQPZmZj+L+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/6ampDiV1dTPWFhXI28vK/8/v7r
+ /v7+6/7+/uv+gYF5xQAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAABkZGA09PTohT09LDgAAAABfX1ogcnJs
+ sdXVxv7+/uv+/v7r/vj45v7U1MX+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7U1MX++Pjl
+ /v7+6/7+/uv+1tbH/nFxa7JmZmAgAAAAAE1NSQ5AQD0hHx8dDQAAAAAAAAAANzc1Pa2tofqrq5/uZGRe
+ bVhYUwQAAAAAVVVRTI2Ng/z+/uv+/v7r/v7+6/7+/uv+0NDB/uzs2v7+/uv+/v7r/unp2P7Q0MH+/v7r
+ /v7+6/7+/uv+/v7r/o2NhPtaWlZNAAAAAFJSTgReXlluqamd7q2tofo/Pzw9AAAAAAAAAABeXlmn/v7r
+ /v7+6/7w8N/+kJCH01paVW6Li4LY7+/d/v7+6/7+/uv+/v7r/qqqnuxkZF8+aGhipP7+6/7+/uv+a2tl
+ o2BgWz+pqZ7s/v7r/v7+6/7+/uv+8PDe/ouLgthZWVRuj4+G0/Hx3/7+/uv+/v7r/mNjXagAAAAAAAAA
+ AEVFQlaXl43p9vbk/v7+6/7+/uv+9fXj/v7+6/7+/uv+9vbk/vb25P7+/uv+mJiO1F1dWApra2Wt/v7r
+ /v7+6/5wcGmtWlpVCpeXjdT+/uv+9vbk/vb25P7+/uv+/v7r/vX14/7+/uv+/v7r/vb25P6Xl43pTExI
+ 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
+ AHgeAAB4HgAA
+
+
+
\ No newline at end of file
diff --git a/AA2Snowflake/formMain.Designer.cs b/AA2Snowflake/formMain.Designer.cs
new file mode 100644
index 0000000..ce33667
--- /dev/null
+++ b/AA2Snowflake/formMain.Designer.cs
@@ -0,0 +1,831 @@
+namespace AA2Snowflake
+{
+ partial class formMain
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(formMain));
+ this.menuStrip1 = new System.Windows.Forms.MenuStrip();
+ this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.snowflakeGuideToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.tabControl1 = new System.Windows.Forms.TabControl();
+ this.tabPage1 = new System.Windows.Forms.TabPage();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.splitContainer1 = new System.Windows.Forms.SplitContainer();
+ this.imgBackground = new System.Windows.Forms.PictureBox();
+ this.btnRestoreAllBG = new System.Windows.Forms.Button();
+ this.btnRestoreBG = new System.Windows.Forms.Button();
+ this.btnSaveBG = new System.Windows.Forms.Button();
+ this.btnLoadBG = new System.Windows.Forms.Button();
+ this.cmbBackground = new System.Windows.Forms.ComboBox();
+ this.label1 = new System.Windows.Forms.Label();
+ this.tabPage2 = new System.Windows.Forms.TabPage();
+ this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.splitContainer2 = new System.Windows.Forms.SplitContainer();
+ this.imgCharacter = new System.Windows.Forms.PictureBox();
+ this.btnRestoreAllCHR = new System.Windows.Forms.Button();
+ this.btnRestoreCHR = new System.Windows.Forms.Button();
+ this.btnSaveCHR = new System.Windows.Forms.Button();
+ this.btnLoadCHR = new System.Windows.Forms.Button();
+ this.cmbCharacter = new System.Windows.Forms.ComboBox();
+ this.label2 = new System.Windows.Forms.Label();
+ this.tabPage3 = new System.Windows.Forms.TabPage();
+ this.groupBox5 = new System.Windows.Forms.GroupBox();
+ this.btnRestoreAll33 = new System.Windows.Forms.Button();
+ this.btnSet33 = new System.Windows.Forms.Button();
+ this.cmbSecond33 = new System.Windows.Forms.ComboBox();
+ this.label4 = new System.Windows.Forms.Label();
+ this.cmbFirst33 = new System.Windows.Forms.ComboBox();
+ this.label3 = new System.Windows.Forms.Label();
+ this.groupBox4 = new System.Windows.Forms.GroupBox();
+ this.btnRestore32 = new System.Windows.Forms.Button();
+ this.btnSet32 = new System.Windows.Forms.Button();
+ this.numPose32 = new System.Windows.Forms.NumericUpDown();
+ this.groupBox3 = new System.Windows.Forms.GroupBox();
+ this.btnDelete31 = new System.Windows.Forms.Button();
+ this.btnMove31 = new System.Windows.Forms.Button();
+ this.btnRestore31 = new System.Windows.Forms.Button();
+ this.btnBackup31 = new System.Windows.Forms.Button();
+ this.tabPage4 = new System.Windows.Forms.TabPage();
+ this.imgCard = new System.Windows.Forms.PictureBox();
+ this.toolStrip1 = new System.Windows.Forms.ToolStrip();
+ this.openToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.lblDimensions = new System.Windows.Forms.ToolStripLabel();
+ this.saveToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.saveAsToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+ this.replaceCardFaceToolStripButton = new System.Windows.Forms.ToolStripButton();
+ this.menuStrip1.SuspendLayout();
+ this.tabControl1.SuspendLayout();
+ this.tabPage1.SuspendLayout();
+ this.groupBox1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
+ this.splitContainer1.Panel1.SuspendLayout();
+ this.splitContainer1.Panel2.SuspendLayout();
+ this.splitContainer1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.imgBackground)).BeginInit();
+ this.tabPage2.SuspendLayout();
+ this.groupBox2.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit();
+ this.splitContainer2.Panel1.SuspendLayout();
+ this.splitContainer2.Panel2.SuspendLayout();
+ this.splitContainer2.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.imgCharacter)).BeginInit();
+ this.tabPage3.SuspendLayout();
+ this.groupBox5.SuspendLayout();
+ this.groupBox4.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.numPose32)).BeginInit();
+ this.groupBox3.SuspendLayout();
+ this.tabPage4.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.imgCard)).BeginInit();
+ this.toolStrip1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // menuStrip1
+ //
+ this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.fileToolStripMenuItem,
+ this.helpToolStripMenuItem});
+ this.menuStrip1.Location = new System.Drawing.Point(0, 0);
+ this.menuStrip1.Name = "menuStrip1";
+ this.menuStrip1.Size = new System.Drawing.Size(489, 24);
+ this.menuStrip1.TabIndex = 0;
+ this.menuStrip1.Text = "menuStrip1";
+ //
+ // fileToolStripMenuItem
+ //
+ this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.exitToolStripMenuItem});
+ this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
+ this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
+ this.fileToolStripMenuItem.Text = "File";
+ //
+ // exitToolStripMenuItem
+ //
+ this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
+ this.exitToolStripMenuItem.Size = new System.Drawing.Size(92, 22);
+ this.exitToolStripMenuItem.Text = "Exit";
+ this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
+ //
+ // helpToolStripMenuItem
+ //
+ this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.snowflakeGuideToolStripMenuItem});
+ this.helpToolStripMenuItem.Name = "helpToolStripMenuItem";
+ this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
+ this.helpToolStripMenuItem.Text = "Help";
+ //
+ // snowflakeGuideToolStripMenuItem
+ //
+ this.snowflakeGuideToolStripMenuItem.Name = "snowflakeGuideToolStripMenuItem";
+ this.snowflakeGuideToolStripMenuItem.Size = new System.Drawing.Size(162, 22);
+ this.snowflakeGuideToolStripMenuItem.Text = "Snowflake Guide";
+ this.snowflakeGuideToolStripMenuItem.Click += new System.EventHandler(this.snowflakeGuideToolStripMenuItem_Click);
+ //
+ // tabControl1
+ //
+ this.tabControl1.Controls.Add(this.tabPage1);
+ this.tabControl1.Controls.Add(this.tabPage2);
+ this.tabControl1.Controls.Add(this.tabPage3);
+ this.tabControl1.Controls.Add(this.tabPage4);
+ this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tabControl1.Location = new System.Drawing.Point(0, 24);
+ this.tabControl1.Name = "tabControl1";
+ this.tabControl1.SelectedIndex = 0;
+ this.tabControl1.Size = new System.Drawing.Size(489, 319);
+ this.tabControl1.TabIndex = 1;
+ //
+ // tabPage1
+ //
+ this.tabPage1.Controls.Add(this.groupBox1);
+ this.tabPage1.Location = new System.Drawing.Point(4, 22);
+ this.tabPage1.Name = "tabPage1";
+ this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage1.Size = new System.Drawing.Size(481, 293);
+ this.tabPage1.TabIndex = 0;
+ this.tabPage1.Text = "Background";
+ this.tabPage1.UseVisualStyleBackColor = true;
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.splitContainer1);
+ this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.groupBox1.Location = new System.Drawing.Point(3, 3);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(475, 287);
+ this.groupBox1.TabIndex = 1;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Part 1: Custom background";
+ //
+ // splitContainer1
+ //
+ this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitContainer1.Location = new System.Drawing.Point(3, 16);
+ this.splitContainer1.Name = "splitContainer1";
+ //
+ // splitContainer1.Panel1
+ //
+ this.splitContainer1.Panel1.Controls.Add(this.imgBackground);
+ //
+ // splitContainer1.Panel2
+ //
+ this.splitContainer1.Panel2.Controls.Add(this.btnRestoreAllBG);
+ this.splitContainer1.Panel2.Controls.Add(this.btnRestoreBG);
+ this.splitContainer1.Panel2.Controls.Add(this.btnSaveBG);
+ this.splitContainer1.Panel2.Controls.Add(this.btnLoadBG);
+ this.splitContainer1.Panel2.Controls.Add(this.cmbBackground);
+ this.splitContainer1.Panel2.Controls.Add(this.label1);
+ this.splitContainer1.Size = new System.Drawing.Size(469, 268);
+ this.splitContainer1.SplitterDistance = 227;
+ this.splitContainer1.TabIndex = 0;
+ //
+ // imgBackground
+ //
+ this.imgBackground.BackColor = System.Drawing.Color.Black;
+ this.imgBackground.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.imgBackground.Location = new System.Drawing.Point(0, 0);
+ this.imgBackground.Name = "imgBackground";
+ this.imgBackground.Size = new System.Drawing.Size(227, 268);
+ this.imgBackground.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
+ this.imgBackground.TabIndex = 0;
+ this.imgBackground.TabStop = false;
+ //
+ // btnRestoreAllBG
+ //
+ this.btnRestoreAllBG.Location = new System.Drawing.Point(6, 140);
+ this.btnRestoreAllBG.Name = "btnRestoreAllBG";
+ this.btnRestoreAllBG.Size = new System.Drawing.Size(75, 23);
+ this.btnRestoreAllBG.TabIndex = 5;
+ this.btnRestoreAllBG.Text = "Restore All";
+ this.btnRestoreAllBG.UseVisualStyleBackColor = true;
+ this.btnRestoreAllBG.Click += new System.EventHandler(this.btnRestoreAllBG_Click);
+ //
+ // btnRestoreBG
+ //
+ this.btnRestoreBG.Location = new System.Drawing.Point(6, 111);
+ this.btnRestoreBG.Name = "btnRestoreBG";
+ this.btnRestoreBG.Size = new System.Drawing.Size(75, 23);
+ this.btnRestoreBG.TabIndex = 4;
+ this.btnRestoreBG.Text = "Restore";
+ this.btnRestoreBG.UseVisualStyleBackColor = true;
+ this.btnRestoreBG.Click += new System.EventHandler(this.btnRestoreBG_Click);
+ //
+ // btnSaveBG
+ //
+ this.btnSaveBG.Location = new System.Drawing.Point(6, 82);
+ this.btnSaveBG.Name = "btnSaveBG";
+ this.btnSaveBG.Size = new System.Drawing.Size(75, 23);
+ this.btnSaveBG.TabIndex = 3;
+ this.btnSaveBG.Text = "Save";
+ this.btnSaveBG.UseVisualStyleBackColor = true;
+ this.btnSaveBG.Click += new System.EventHandler(this.btnSaveBG_Click);
+ //
+ // btnLoadBG
+ //
+ this.btnLoadBG.Location = new System.Drawing.Point(6, 53);
+ this.btnLoadBG.Name = "btnLoadBG";
+ this.btnLoadBG.Size = new System.Drawing.Size(75, 23);
+ this.btnLoadBG.TabIndex = 2;
+ this.btnLoadBG.Text = "Load";
+ this.btnLoadBG.UseVisualStyleBackColor = true;
+ this.btnLoadBG.Click += new System.EventHandler(this.btnLoadBG_Click);
+ //
+ // cmbBackground
+ //
+ this.cmbBackground.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cmbBackground.FormattingEnabled = true;
+ this.cmbBackground.Items.AddRange(new object[] {
+ "Hetero",
+ "Lean Hetero",
+ "Bisexual",
+ "Lean Homo",
+ "Homo",
+ "Rainbow"});
+ this.cmbBackground.Location = new System.Drawing.Point(6, 26);
+ this.cmbBackground.Name = "cmbBackground";
+ this.cmbBackground.Size = new System.Drawing.Size(98, 21);
+ this.cmbBackground.TabIndex = 1;
+ this.cmbBackground.SelectedIndexChanged += new System.EventHandler(this.cmbBackground_SelectedIndexChanged);
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(3, 10);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(154, 13);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "Currently selected background:";
+ //
+ // tabPage2
+ //
+ this.tabPage2.Controls.Add(this.groupBox2);
+ this.tabPage2.Location = new System.Drawing.Point(4, 22);
+ this.tabPage2.Name = "tabPage2";
+ this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage2.Size = new System.Drawing.Size(481, 293);
+ this.tabPage2.TabIndex = 1;
+ this.tabPage2.Text = "Clothes";
+ this.tabPage2.UseVisualStyleBackColor = true;
+ //
+ // groupBox2
+ //
+ this.groupBox2.Controls.Add(this.splitContainer2);
+ this.groupBox2.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.groupBox2.Location = new System.Drawing.Point(3, 3);
+ this.groupBox2.Name = "groupBox2";
+ this.groupBox2.Size = new System.Drawing.Size(475, 287);
+ this.groupBox2.TabIndex = 2;
+ this.groupBox2.TabStop = false;
+ this.groupBox2.Text = "Part 2: Assigning clothes";
+ //
+ // splitContainer2
+ //
+ this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.splitContainer2.Location = new System.Drawing.Point(3, 16);
+ this.splitContainer2.Name = "splitContainer2";
+ //
+ // splitContainer2.Panel1
+ //
+ this.splitContainer2.Panel1.Controls.Add(this.imgCharacter);
+ //
+ // splitContainer2.Panel2
+ //
+ this.splitContainer2.Panel2.Controls.Add(this.btnRestoreAllCHR);
+ this.splitContainer2.Panel2.Controls.Add(this.btnRestoreCHR);
+ this.splitContainer2.Panel2.Controls.Add(this.btnSaveCHR);
+ this.splitContainer2.Panel2.Controls.Add(this.btnLoadCHR);
+ this.splitContainer2.Panel2.Controls.Add(this.cmbCharacter);
+ this.splitContainer2.Panel2.Controls.Add(this.label2);
+ this.splitContainer2.Size = new System.Drawing.Size(469, 268);
+ this.splitContainer2.SplitterDistance = 227;
+ this.splitContainer2.TabIndex = 0;
+ //
+ // imgCharacter
+ //
+ this.imgCharacter.BackColor = System.Drawing.Color.Black;
+ this.imgCharacter.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.imgCharacter.Location = new System.Drawing.Point(0, 0);
+ this.imgCharacter.Name = "imgCharacter";
+ this.imgCharacter.Size = new System.Drawing.Size(227, 268);
+ this.imgCharacter.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
+ this.imgCharacter.TabIndex = 0;
+ this.imgCharacter.TabStop = false;
+ //
+ // btnRestoreAllCHR
+ //
+ this.btnRestoreAllCHR.Location = new System.Drawing.Point(6, 140);
+ this.btnRestoreAllCHR.Name = "btnRestoreAllCHR";
+ this.btnRestoreAllCHR.Size = new System.Drawing.Size(75, 23);
+ this.btnRestoreAllCHR.TabIndex = 5;
+ this.btnRestoreAllCHR.Text = "Restore All";
+ this.btnRestoreAllCHR.UseVisualStyleBackColor = true;
+ this.btnRestoreAllCHR.Click += new System.EventHandler(this.btnRestoreAllCHR_Click);
+ //
+ // btnRestoreCHR
+ //
+ this.btnRestoreCHR.Location = new System.Drawing.Point(6, 111);
+ this.btnRestoreCHR.Name = "btnRestoreCHR";
+ this.btnRestoreCHR.Size = new System.Drawing.Size(75, 23);
+ this.btnRestoreCHR.TabIndex = 4;
+ this.btnRestoreCHR.Text = "Restore";
+ this.btnRestoreCHR.UseVisualStyleBackColor = true;
+ this.btnRestoreCHR.Click += new System.EventHandler(this.btnRestoreCHR_Click);
+ //
+ // btnSaveCHR
+ //
+ this.btnSaveCHR.Location = new System.Drawing.Point(6, 82);
+ this.btnSaveCHR.Name = "btnSaveCHR";
+ this.btnSaveCHR.Size = new System.Drawing.Size(75, 23);
+ this.btnSaveCHR.TabIndex = 3;
+ this.btnSaveCHR.Text = "Save";
+ this.btnSaveCHR.UseVisualStyleBackColor = true;
+ this.btnSaveCHR.Click += new System.EventHandler(this.btnSaveCHR_Click);
+ //
+ // btnLoadCHR
+ //
+ this.btnLoadCHR.Location = new System.Drawing.Point(6, 53);
+ this.btnLoadCHR.Name = "btnLoadCHR";
+ this.btnLoadCHR.Size = new System.Drawing.Size(75, 23);
+ this.btnLoadCHR.TabIndex = 2;
+ this.btnLoadCHR.Text = "Load";
+ this.btnLoadCHR.UseVisualStyleBackColor = true;
+ this.btnLoadCHR.Click += new System.EventHandler(this.btnLoadCHR_Click);
+ //
+ // cmbCharacter
+ //
+ this.cmbCharacter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cmbCharacter.FormattingEnabled = true;
+ this.cmbCharacter.Items.AddRange(new object[] {
+ "Male",
+ "Female"});
+ this.cmbCharacter.Location = new System.Drawing.Point(6, 26);
+ this.cmbCharacter.Name = "cmbCharacter";
+ this.cmbCharacter.Size = new System.Drawing.Size(98, 21);
+ this.cmbCharacter.TabIndex = 1;
+ this.cmbCharacter.SelectedIndexChanged += new System.EventHandler(this.cmbCharacter_SelectedIndexChanged);
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(3, 10);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(130, 13);
+ this.label2.TabIndex = 0;
+ this.label2.Text = "Currently selected gender:";
+ //
+ // tabPage3
+ //
+ this.tabPage3.Controls.Add(this.groupBox5);
+ this.tabPage3.Controls.Add(this.groupBox4);
+ this.tabPage3.Controls.Add(this.groupBox3);
+ this.tabPage3.Location = new System.Drawing.Point(4, 22);
+ this.tabPage3.Name = "tabPage3";
+ this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage3.Size = new System.Drawing.Size(481, 293);
+ this.tabPage3.TabIndex = 2;
+ this.tabPage3.Text = "Poses";
+ this.tabPage3.UseVisualStyleBackColor = true;
+ //
+ // groupBox5
+ //
+ this.groupBox5.Controls.Add(this.btnRestoreAll33);
+ this.groupBox5.Controls.Add(this.btnSet33);
+ this.groupBox5.Controls.Add(this.cmbSecond33);
+ this.groupBox5.Controls.Add(this.label4);
+ this.groupBox5.Controls.Add(this.cmbFirst33);
+ this.groupBox5.Controls.Add(this.label3);
+ this.groupBox5.Dock = System.Windows.Forms.DockStyle.Top;
+ this.groupBox5.Location = new System.Drawing.Point(3, 107);
+ this.groupBox5.Name = "groupBox5";
+ this.groupBox5.Size = new System.Drawing.Size(475, 54);
+ this.groupBox5.TabIndex = 2;
+ this.groupBox5.TabStop = false;
+ this.groupBox5.Text = "Part 3.3: Change displayed personality";
+ //
+ // btnRestoreAll33
+ //
+ this.btnRestoreAll33.Location = new System.Drawing.Point(331, 18);
+ this.btnRestoreAll33.Name = "btnRestoreAll33";
+ this.btnRestoreAll33.Size = new System.Drawing.Size(75, 23);
+ this.btnRestoreAll33.TabIndex = 3;
+ this.btnRestoreAll33.Text = "Restore All";
+ this.btnRestoreAll33.UseVisualStyleBackColor = true;
+ this.btnRestoreAll33.Click += new System.EventHandler(this.btnRestoreAll33_Click);
+ //
+ // btnSet33
+ //
+ this.btnSet33.Location = new System.Drawing.Point(250, 18);
+ this.btnSet33.Name = "btnSet33";
+ this.btnSet33.Size = new System.Drawing.Size(75, 23);
+ this.btnSet33.TabIndex = 4;
+ this.btnSet33.Text = "Set";
+ this.btnSet33.UseVisualStyleBackColor = true;
+ this.btnSet33.Click += new System.EventHandler(this.btnSet33_Click);
+ //
+ // cmbSecond33
+ //
+ this.cmbSecond33.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cmbSecond33.FormattingEnabled = true;
+ this.cmbSecond33.Items.AddRange(new object[] {
+ "(00) Lively",
+ "(01) Delicate",
+ "(02) Cheerful",
+ "(03) Quiet",
+ "(04) Playful",
+ "(05) Frisky",
+ "(06) Kind",
+ "(07) Joyful",
+ "(08) Ordinary",
+ "(09) Irritated",
+ "(10) Harsh",
+ "(11) Sweet",
+ "(12) Creepy",
+ "(13) Reserved",
+ "(14) Dignified",
+ "(15) Aloof",
+ "(16) Smart",
+ "(17) Genuine",
+ "(18) Mature",
+ "(19) Lazy",
+ "(20) Manly",
+ "(21) Gentle",
+ "(22) Positive",
+ "(23) Otaku",
+ "(24) Savage"});
+ this.cmbSecond33.Location = new System.Drawing.Point(159, 19);
+ this.cmbSecond33.Name = "cmbSecond33";
+ this.cmbSecond33.Size = new System.Drawing.Size(85, 21);
+ this.cmbSecond33.TabIndex = 3;
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(135, 22);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(18, 13);
+ this.label4.TabIndex = 2;
+ this.label4.Text = "as";
+ //
+ // cmbFirst33
+ //
+ this.cmbFirst33.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cmbFirst33.FormattingEnabled = true;
+ this.cmbFirst33.Items.AddRange(new object[] {
+ "(00) Lively",
+ "(01) Delicate",
+ "(02) Cheerful",
+ "(03) Quiet",
+ "(04) Playful",
+ "(05) Frisky",
+ "(06) Kind",
+ "(07) Joyful",
+ "(08) Ordinary",
+ "(09) Irritated",
+ "(10) Harsh",
+ "(11) Sweet",
+ "(12) Creepy",
+ "(13) Reserved",
+ "(14) Dignified",
+ "(15) Aloof",
+ "(16) Smart",
+ "(17) Genuine",
+ "(18) Mature",
+ "(19) Lazy",
+ "(20) Manly",
+ "(21) Gentle",
+ "(22) Positive",
+ "(23) Otaku",
+ "(24) Savage"});
+ this.cmbFirst33.Location = new System.Drawing.Point(44, 19);
+ this.cmbFirst33.Name = "cmbFirst33";
+ this.cmbFirst33.Size = new System.Drawing.Size(85, 21);
+ this.cmbFirst33.TabIndex = 1;
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(6, 22);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(32, 13);
+ this.label3.TabIndex = 0;
+ this.label3.Text = "Save";
+ //
+ // groupBox4
+ //
+ this.groupBox4.Controls.Add(this.btnRestore32);
+ this.groupBox4.Controls.Add(this.btnSet32);
+ this.groupBox4.Controls.Add(this.numPose32);
+ this.groupBox4.Dock = System.Windows.Forms.DockStyle.Top;
+ this.groupBox4.Location = new System.Drawing.Point(3, 57);
+ this.groupBox4.Name = "groupBox4";
+ this.groupBox4.Size = new System.Drawing.Size(475, 50);
+ this.groupBox4.TabIndex = 1;
+ this.groupBox4.TabStop = false;
+ this.groupBox4.Text = "Part 3.2: Select pose";
+ //
+ // btnRestore32
+ //
+ this.btnRestore32.Location = new System.Drawing.Point(168, 16);
+ this.btnRestore32.Name = "btnRestore32";
+ this.btnRestore32.Size = new System.Drawing.Size(75, 23);
+ this.btnRestore32.TabIndex = 2;
+ this.btnRestore32.Text = "Restore";
+ this.btnRestore32.UseVisualStyleBackColor = true;
+ this.btnRestore32.Click += new System.EventHandler(this.btnRestore32_Click);
+ //
+ // btnSet32
+ //
+ this.btnSet32.Location = new System.Drawing.Point(87, 16);
+ this.btnSet32.Name = "btnSet32";
+ this.btnSet32.Size = new System.Drawing.Size(75, 23);
+ this.btnSet32.TabIndex = 1;
+ this.btnSet32.Text = "Set";
+ this.btnSet32.UseVisualStyleBackColor = true;
+ this.btnSet32.Click += new System.EventHandler(this.btnSet32_Click);
+ //
+ // numPose32
+ //
+ this.numPose32.Location = new System.Drawing.Point(6, 19);
+ this.numPose32.Maximum = new decimal(new int[] {
+ 187,
+ 0,
+ 0,
+ 0});
+ this.numPose32.Name = "numPose32";
+ this.numPose32.Size = new System.Drawing.Size(75, 20);
+ this.numPose32.TabIndex = 0;
+ //
+ // groupBox3
+ //
+ this.groupBox3.Controls.Add(this.btnDelete31);
+ this.groupBox3.Controls.Add(this.btnMove31);
+ this.groupBox3.Controls.Add(this.btnRestore31);
+ this.groupBox3.Controls.Add(this.btnBackup31);
+ this.groupBox3.Dock = System.Windows.Forms.DockStyle.Top;
+ this.groupBox3.Location = new System.Drawing.Point(3, 3);
+ this.groupBox3.Name = "groupBox3";
+ this.groupBox3.Size = new System.Drawing.Size(475, 54);
+ this.groupBox3.TabIndex = 0;
+ this.groupBox3.TabStop = false;
+ this.groupBox3.Text = "Part 3.1: Move poses to maker";
+ //
+ // btnDelete31
+ //
+ this.btnDelete31.Location = new System.Drawing.Point(250, 19);
+ this.btnDelete31.Name = "btnDelete31";
+ this.btnDelete31.Size = new System.Drawing.Size(98, 23);
+ this.btnDelete31.TabIndex = 3;
+ this.btnDelete31.Text = "Delete Backup";
+ this.btnDelete31.UseVisualStyleBackColor = true;
+ this.btnDelete31.Click += new System.EventHandler(this.btnDelete31_Click);
+ //
+ // btnMove31
+ //
+ this.btnMove31.Location = new System.Drawing.Point(168, 19);
+ this.btnMove31.Name = "btnMove31";
+ this.btnMove31.Size = new System.Drawing.Size(75, 23);
+ this.btnMove31.TabIndex = 2;
+ this.btnMove31.Text = "Move";
+ this.btnMove31.UseVisualStyleBackColor = true;
+ this.btnMove31.Click += new System.EventHandler(this.btnMove31_Click);
+ //
+ // btnRestore31
+ //
+ this.btnRestore31.Location = new System.Drawing.Point(87, 19);
+ this.btnRestore31.Name = "btnRestore31";
+ this.btnRestore31.Size = new System.Drawing.Size(75, 23);
+ this.btnRestore31.TabIndex = 1;
+ this.btnRestore31.Text = "Restore";
+ this.btnRestore31.UseVisualStyleBackColor = true;
+ this.btnRestore31.Click += new System.EventHandler(this.btnRestore31_Click);
+ //
+ // btnBackup31
+ //
+ this.btnBackup31.Location = new System.Drawing.Point(6, 19);
+ this.btnBackup31.Name = "btnBackup31";
+ this.btnBackup31.Size = new System.Drawing.Size(75, 23);
+ this.btnBackup31.TabIndex = 0;
+ this.btnBackup31.Text = "Backup";
+ this.btnBackup31.UseVisualStyleBackColor = true;
+ this.btnBackup31.Click += new System.EventHandler(this.btnBackup31_Click);
+ //
+ // tabPage4
+ //
+ this.tabPage4.Controls.Add(this.imgCard);
+ this.tabPage4.Controls.Add(this.toolStrip1);
+ this.tabPage4.Location = new System.Drawing.Point(4, 22);
+ this.tabPage4.Name = "tabPage4";
+ this.tabPage4.Size = new System.Drawing.Size(481, 293);
+ this.tabPage4.TabIndex = 3;
+ this.tabPage4.Text = "Card Replacer";
+ this.tabPage4.UseVisualStyleBackColor = true;
+ //
+ // imgCard
+ //
+ this.imgCard.BackColor = System.Drawing.Color.Black;
+ this.imgCard.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.imgCard.Location = new System.Drawing.Point(0, 25);
+ this.imgCard.Name = "imgCard";
+ this.imgCard.Size = new System.Drawing.Size(481, 268);
+ this.imgCard.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
+ this.imgCard.TabIndex = 1;
+ this.imgCard.TabStop = false;
+ //
+ // toolStrip1
+ //
+ this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
+ this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.openToolStripButton,
+ this.lblDimensions,
+ this.saveToolStripButton,
+ this.saveAsToolStripButton,
+ this.toolStripSeparator1,
+ this.replaceCardFaceToolStripButton});
+ this.toolStrip1.Location = new System.Drawing.Point(0, 0);
+ this.toolStrip1.Name = "toolStrip1";
+ this.toolStrip1.Size = new System.Drawing.Size(481, 25);
+ this.toolStrip1.TabIndex = 0;
+ this.toolStrip1.Text = "toolStrip1";
+ //
+ // openToolStripButton
+ //
+ this.openToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.openToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("openToolStripButton.Image")));
+ this.openToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.openToolStripButton.Name = "openToolStripButton";
+ this.openToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.openToolStripButton.Text = "&Open";
+ this.openToolStripButton.Click += new System.EventHandler(this.openToolStripButton_Click);
+ //
+ // lblDimensions
+ //
+ this.lblDimensions.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
+ this.lblDimensions.Name = "lblDimensions";
+ this.lblDimensions.Size = new System.Drawing.Size(33, 22);
+ this.lblDimensions.Text = "[0, 0]";
+ //
+ // saveToolStripButton
+ //
+ this.saveToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.saveToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveToolStripButton.Image")));
+ this.saveToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.saveToolStripButton.Name = "saveToolStripButton";
+ this.saveToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.saveToolStripButton.Text = "&Save";
+ this.saveToolStripButton.Click += new System.EventHandler(this.saveToolStripButton_Click);
+ //
+ // saveAsToolStripButton
+ //
+ this.saveAsToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.saveAsToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("saveAsToolStripButton.Image")));
+ this.saveAsToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.saveAsToolStripButton.Name = "saveAsToolStripButton";
+ this.saveAsToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.saveAsToolStripButton.Text = "Save &As";
+ this.saveAsToolStripButton.Click += new System.EventHandler(this.saveAsToolStripButton_Click);
+ //
+ // toolStripSeparator1
+ //
+ this.toolStripSeparator1.Name = "toolStripSeparator1";
+ this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25);
+ //
+ // replaceCardFaceToolStripButton
+ //
+ this.replaceCardFaceToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ this.replaceCardFaceToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("replaceCardFaceToolStripButton.Image")));
+ this.replaceCardFaceToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.replaceCardFaceToolStripButton.Name = "replaceCardFaceToolStripButton";
+ this.replaceCardFaceToolStripButton.Size = new System.Drawing.Size(23, 22);
+ this.replaceCardFaceToolStripButton.Text = "&Replace Card Face";
+ this.replaceCardFaceToolStripButton.Click += new System.EventHandler(this.replaceCardFaceToolStripButton_Click);
+ //
+ // formMain
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(489, 343);
+ this.Controls.Add(this.tabControl1);
+ this.Controls.Add(this.menuStrip1);
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
+ this.MainMenuStrip = this.menuStrip1;
+ this.MinimumSize = new System.Drawing.Size(505, 382);
+ this.Name = "formMain";
+ this.Text = "AA2Snowflake";
+ this.menuStrip1.ResumeLayout(false);
+ this.menuStrip1.PerformLayout();
+ this.tabControl1.ResumeLayout(false);
+ this.tabPage1.ResumeLayout(false);
+ this.groupBox1.ResumeLayout(false);
+ this.splitContainer1.Panel1.ResumeLayout(false);
+ this.splitContainer1.Panel2.ResumeLayout(false);
+ this.splitContainer1.Panel2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
+ this.splitContainer1.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.imgBackground)).EndInit();
+ this.tabPage2.ResumeLayout(false);
+ this.groupBox2.ResumeLayout(false);
+ this.splitContainer2.Panel1.ResumeLayout(false);
+ this.splitContainer2.Panel2.ResumeLayout(false);
+ this.splitContainer2.Panel2.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit();
+ this.splitContainer2.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.imgCharacter)).EndInit();
+ this.tabPage3.ResumeLayout(false);
+ this.groupBox5.ResumeLayout(false);
+ this.groupBox5.PerformLayout();
+ this.groupBox4.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)(this.numPose32)).EndInit();
+ this.groupBox3.ResumeLayout(false);
+ this.tabPage4.ResumeLayout(false);
+ this.tabPage4.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)(this.imgCard)).EndInit();
+ this.toolStrip1.ResumeLayout(false);
+ this.toolStrip1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.MenuStrip menuStrip1;
+ private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem;
+ private System.Windows.Forms.TabControl tabControl1;
+ private System.Windows.Forms.TabPage tabPage1;
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.SplitContainer splitContainer1;
+ private System.Windows.Forms.PictureBox imgBackground;
+ private System.Windows.Forms.Button btnSaveBG;
+ private System.Windows.Forms.Button btnLoadBG;
+ private System.Windows.Forms.ComboBox cmbBackground;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Button btnRestoreBG;
+ private System.Windows.Forms.Button btnRestoreAllBG;
+ private System.Windows.Forms.ToolStripMenuItem snowflakeGuideToolStripMenuItem;
+ private System.Windows.Forms.TabPage tabPage2;
+ private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.SplitContainer splitContainer2;
+ private System.Windows.Forms.PictureBox imgCharacter;
+ private System.Windows.Forms.Button btnRestoreAllCHR;
+ private System.Windows.Forms.Button btnRestoreCHR;
+ private System.Windows.Forms.Button btnSaveCHR;
+ private System.Windows.Forms.Button btnLoadCHR;
+ private System.Windows.Forms.ComboBox cmbCharacter;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.TabPage tabPage3;
+ private System.Windows.Forms.GroupBox groupBox3;
+ private System.Windows.Forms.Button btnMove31;
+ private System.Windows.Forms.Button btnRestore31;
+ private System.Windows.Forms.Button btnBackup31;
+ private System.Windows.Forms.GroupBox groupBox4;
+ private System.Windows.Forms.Button btnRestore32;
+ private System.Windows.Forms.Button btnSet32;
+ private System.Windows.Forms.NumericUpDown numPose32;
+ private System.Windows.Forms.GroupBox groupBox5;
+ private System.Windows.Forms.Button btnRestoreAll33;
+ private System.Windows.Forms.Button btnSet33;
+ private System.Windows.Forms.ComboBox cmbSecond33;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.ComboBox cmbFirst33;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TabPage tabPage4;
+ private System.Windows.Forms.PictureBox imgCard;
+ private System.Windows.Forms.ToolStrip toolStrip1;
+ private System.Windows.Forms.ToolStripButton openToolStripButton;
+ private System.Windows.Forms.ToolStripLabel lblDimensions;
+ private System.Windows.Forms.ToolStripButton saveToolStripButton;
+ private System.Windows.Forms.ToolStripButton saveAsToolStripButton;
+ private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
+ private System.Windows.Forms.ToolStripButton replaceCardFaceToolStripButton;
+ private System.Windows.Forms.Button btnDelete31;
+ }
+}
+
diff --git a/AA2Snowflake/formMain.cs b/AA2Snowflake/formMain.cs
new file mode 100644
index 0000000..877ae95
--- /dev/null
+++ b/AA2Snowflake/formMain.cs
@@ -0,0 +1,516 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.IO;
+using SB3Utility;
+using AA2Install;
+using AA2CardEditor;
+
+namespace AA2Snowflake
+{
+ public partial class formMain : Form
+ {
+#warning add a roster background editor
+#warning add a border editor
+#warning add eyebrows and eye settings
+#warning add support for append + custom personalities
+#warning add crash dialog and about dialog
+
+ #region Form
+ public formLoad load = new formLoad();
+ public formMain()
+ {
+ InitializeComponent();
+ }
+
+ public void ShowLoadingForm()
+ {
+ load.Show(this);
+
+ load.Location = new Point(this.Location.X + (this.Width / 2) - (load.Width / 2), this.Location.Y + (this.Height / 2) - (load.Height / 2));
+ this.Enabled = false;
+ }
+
+ public void HideLoadingForm()
+ {
+ load.Hide();
+ this.Activate();
+ this.Enabled = true;
+ }
+ #endregion
+ #region Menu Bar
+ private void exitToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ Application.Exit();
+ }
+
+ private void snowflakeGuideToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ System.Diagnostics.Process.Start("https://github.com/aa2g/AA2Snowflake/wiki/Snowflake-Guide-v3");
+ }
+ #endregion
+
+ #region Background
+ string backgroundpath;
+
+ private void cmbBackground_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ backgroundpath = null;
+
+ using (var mem = Tools.GetStreamFromSubfile(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_01_0" + cmbBackground.SelectedIndex.ToString() + ".bmp")))
+ {
+ if (imgBackground.Image != null)
+ imgBackground.Image.Dispose();
+
+ imgBackground.Image = Image.FromStream(mem);
+ }
+ }
+
+ private void btnLoadBG_Click(object sender, EventArgs e)
+ {
+ if (cmbBackground.SelectedIndex < 0)
+ return;
+
+ using (var file = new OpenFileDialog())
+ {
+ file.Filter = "Bitmap files (*.bmp)|*.bmp";
+ file.Multiselect = false;
+
+ if (file.ShowDialog() != DialogResult.Cancel)
+ {
+ backgroundpath = file.FileName;
+ if (imgBackground.Image != null)
+ imgBackground.Image.Dispose();
+
+ imgBackground.Image = Image.FromStream(Tools.GetStreamFromFile(file.FileName));
+ }
+ }
+ }
+
+ private void btnSaveBG_Click(object sender, EventArgs e)
+ {
+ if (!File.Exists(backgroundpath))
+ return;
+
+ var index = PP.jg2e06_00_00.Subfiles.IndexOf(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_01_0" + cmbBackground.SelectedIndex.ToString() + ".bmp"));
+ var sub = new Subfile(backgroundpath);
+ sub.Name = "sp_04_01_0" + cmbBackground.SelectedIndex.ToString() + ".bmp";
+ PP.jg2e06_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e06_00_00.WriteArchive(PP.jg2e06_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestoreBG_Click(object sender, EventArgs e)
+ {
+ if (cmbBackground.SelectedIndex < 0)
+ return;
+
+ var index = PP.jg2e06_00_00.Subfiles.IndexOf(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_01_0" + cmbBackground.SelectedIndex.ToString() + ".bmp"));
+ var sub = new Subfile(Paths.BACKUP + @"\sp_04_01_0" + cmbBackground.SelectedIndex.ToString() + ".bmp");
+ PP.jg2e06_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e06_00_00.WriteArchive(PP.jg2e06_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestoreAllBG_Click(object sender, EventArgs e)
+ {
+ for (int i = 0; i < 6; i++)
+ {
+ var index = PP.jg2e06_00_00.Subfiles.IndexOf(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_01_0" + i.ToString() + ".bmp"));
+ var sub = new Subfile(Paths.BACKUP + @"\sp_04_01_0" + i.ToString() + ".bmp");
+ PP.jg2e06_00_00.Subfiles[index] = sub;
+ }
+ var back = PP.jg2e06_00_00.WriteArchive(PP.jg2e06_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+ #endregion
+ #region Clothes
+ string chrpath;
+
+ private void cmbCharacter_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ chrpath = null;
+
+ using (var mem = Tools.GetStreamFromSubfile(PP.jg2e01_00_00.Subfiles.First(pp => pp.Name == "def0" + cmbCharacter.SelectedIndex.ToString() + ".png")))
+ {
+ if (imgCharacter.Image != null)
+ imgCharacter.Image.Dispose();
+
+ imgCharacter.Image = Image.FromStream(mem);
+ }
+ }
+
+ private void btnLoadCHR_Click(object sender, EventArgs e)
+ {
+ if (cmbCharacter.SelectedIndex < 0)
+ return;
+
+ using (var file = new OpenFileDialog())
+ {
+ file.Filter = "AA2 Card files (*.png)|*.png";
+ file.Multiselect = false;
+
+ if (file.ShowDialog() != DialogResult.Cancel)
+ {
+ chrpath = file.FileName;
+ if (imgCharacter.Image != null)
+ imgCharacter.Image.Dispose();
+
+ imgCharacter.Image = Image.FromStream(Tools.GetStreamFromFile(file.FileName));
+ }
+ }
+ }
+
+ private void btnSaveCHR_Click(object sender, EventArgs e)
+ {
+ if (!File.Exists(chrpath))
+ return;
+
+ var index = PP.jg2e01_00_00.Subfiles.IndexOf(PP.jg2e01_00_00.Subfiles.First(pp => pp.Name == "def0" + cmbCharacter.SelectedIndex.ToString() + ".png"));
+ var sub = new Subfile(chrpath);
+ sub.Name = "def0" + cmbCharacter.SelectedIndex.ToString() + ".png";
+ PP.jg2e01_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e01_00_00.WriteArchive(PP.jg2e01_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestoreCHR_Click(object sender, EventArgs e)
+ {
+ if (cmbCharacter.SelectedIndex < 0)
+ return;
+
+ var index = PP.jg2e01_00_00.Subfiles.IndexOf(PP.jg2e01_00_00.Subfiles.First(pp => pp.Name == "def0" + cmbCharacter.SelectedIndex.ToString() + ".png"));
+ var sub = new Subfile(Paths.BACKUP + @"\def0" + cmbCharacter.SelectedIndex.ToString() + ".png");
+ PP.jg2e01_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e01_00_00.WriteArchive(PP.jg2e01_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestoreAllCHR_Click(object sender, EventArgs e)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ var index = PP.jg2e01_00_00.Subfiles.IndexOf(PP.jg2e01_00_00.Subfiles.First(pp => pp.Name == "def0" + i.ToString() + ".png"));
+ var sub = new Subfile(Paths.BACKUP + @"\def0" + i.ToString() + ".png");
+ PP.jg2e01_00_00.Subfiles[index] = sub;
+ }
+ var back = PP.jg2e01_00_00.WriteArchive(PP.jg2e01_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+ #endregion
+
+ #region 3.1
+ private void btnBackup31_Click(object sender, EventArgs e)
+ {
+ Tools.BackupFile(Paths.AA2Edit + @"\jg2e01_00_00.pp");
+ }
+
+ private void btnRestore31_Click(object sender, EventArgs e)
+ {
+ Tools.RestoreFile(Paths.AA2Edit + @"\jg2e01_00_00.pp");
+ }
+
+ private void btnMove31_Click(object sender, EventArgs e)
+ {
+ if (MessageBox.Show("Are you absolutely certain you want to move poses? Have you made a backup yet?", "Anti-fuckup dialog", MessageBoxButtons.YesNo) != DialogResult.Yes)
+ return;
+
+ int index;
+ foreach (IWriteFile iw in PP.jg2p01_00_00.Subfiles)
+ {
+ if (iw.Name.StartsWith("HAK"))
+ iw.Name = "HAE" + iw.Name.Remove(0, 3);
+ if (iw.Name.StartsWith("HSK"))
+ iw.Name = "HSE" + iw.Name.Remove(0, 3);
+
+ index = PP.jg2e01_00_00.Subfiles.FindIndex(pp => pp.Name == iw.Name);
+ if (index < 0)
+ PP.jg2e01_00_00.Subfiles.Add(iw);
+ else
+ PP.jg2e01_00_00.Subfiles[index] = iw;
+ }
+
+ var back = PP.jg2e01_00_00.WriteArchive(PP.jg2e01_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ index = PP.jg2e00_00_00.Subfiles.IndexOf(PP.jg2e00_00_00.Subfiles.First(pp => pp.Name == "jg2e_00_01_00_00.lst"));
+ var sub = Tools.ManipulateLst(PP.jg2e00_00_00.Subfiles[index], 4, "51");
+ sub.Name = "jg2e_00_01_00_00.lst";
+ PP.jg2e00_00_00.Subfiles[index] = sub;
+ back = PP.jg2e00_00_00.WriteArchive(PP.jg2e00_00_00.FilePath, false, "bak", true);
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnDelete31_Click(object sender, EventArgs e)
+ {
+ if (MessageBox.Show("Are you sure you want to delete your backup?", "Anti-fuckup dialog", MessageBoxButtons.YesNo) != DialogResult.Yes)
+ return;
+
+ Tools.DeleteBackup(Paths.AA2Edit + @"\jg2e01_00_00.pp");
+ }
+ #endregion
+ #region 3.2
+ private void btnSet32_Click(object sender, EventArgs e)
+ {
+ var index = PP.jg2e00_00_00.Subfiles.IndexOf(PP.jg2e00_00_00.Subfiles.First(pp => pp.Name == "jg2e_00_01_00_00.lst"));
+ var sub = Tools.ManipulateLst(PP.jg2e00_00_00.Subfiles[index], 6, numPose32.Value.ToString());
+ sub = Tools.ManipulateLst(sub, 4, "51");
+ sub.Name = "jg2e_00_01_00_00.lst";
+ PP.jg2e00_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e00_00_00.WriteArchive(PP.jg2e00_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestore32_Click(object sender, EventArgs e)
+ {
+ var index = PP.jg2e00_00_00.Subfiles.IndexOf(PP.jg2e00_00_00.Subfiles.First(pp => pp.Name == "jg2e_00_01_00_00.lst"));
+ var sub = new Subfile(Paths.BACKUP + @"\jg2e_00_01_00_00.lst");
+ PP.jg2e00_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e00_00_00.WriteArchive(PP.jg2e00_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+ #endregion
+ #region 3.3
+ private void btnSet33_Click(object sender, EventArgs e)
+ {
+ if (cmbFirst33.SelectedIndex < 0 ||
+ cmbSecond33.SelectedIndex < 0)
+ return;
+
+ var index = PP.jg2e06_00_00.Subfiles.IndexOf(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_00_" + cmbSecond33.SelectedIndex.ToString().PadLeft(2, '0') + ".tga"));
+ var sub = new Subfile(Paths.Nature + @"\sp_04_00_" + cmbFirst33.SelectedIndex.ToString().PadLeft(2, '0') + ".tga");
+ sub.Name = "sp_04_00_" + cmbSecond33.SelectedIndex.ToString().PadLeft(2, '0') + ".tga";
+ PP.jg2e06_00_00.Subfiles[index] = sub;
+ var back = PP.jg2e06_00_00.WriteArchive(PP.jg2e06_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ private void btnRestoreAll33_Click(object sender, EventArgs e)
+ {
+ for (int i = 0; i < 25; i++)
+ {
+ var index = PP.jg2e06_00_00.Subfiles.IndexOf(PP.jg2e06_00_00.Subfiles.First(pp => pp.Name == "sp_04_00_" + i.ToString().PadLeft(2, '0') + ".tga"));
+ var sub = new Subfile(Paths.Nature + @"\sp_04_00_" + i.ToString().PadLeft(2, '0') + ".tga");
+ PP.jg2e06_00_00.Subfiles[index] = sub;
+ }
+ var back = PP.jg2e06_00_00.WriteArchive(PP.jg2e06_00_00.FilePath, false, "bak", true);
+ ShowLoadingForm();
+ back.RunWorkerAsync();
+ while (back.IsBusy)
+ {
+ Application.DoEvents();
+ }
+ HideLoadingForm();
+ MessageBox.Show("Finished!");
+ }
+
+ #endregion
+
+ #region Card Face
+ //I hope the AA2CardEditor anon doesn't mind that I borrowed his code
+ private AA2Card card = new AA2Card();
+ private void openToolStripButton_Click(object sender, EventArgs e)
+ {
+ OpenCardFile();
+ UpdateWindowState();
+ }
+
+ private void saveToolStripButton_Click(object sender, EventArgs e)
+ {
+ SaveCardFile();
+ UpdateWindowState();
+ }
+
+ private void saveAsToolStripButton_Click(object sender, EventArgs e)
+ {
+ SaveAsCardFile();
+ UpdateWindowState();
+ }
+
+ private void replaceCardFaceToolStripButton_Click(object sender, EventArgs e)
+ {
+ ReplaceCardFace();
+ UpdateWindowState();
+ }
+
+ private void OpenCardFile()
+ {
+ using (var file = new OpenFileDialog())
+ {
+ file.Filter = "AA2 Card files (*.png)|*.png";
+ file.Multiselect = false;
+ if (file.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.ReadCardFile(file.FileName);
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("The card file could not be opened.");
+ }
+ }
+ }
+ }
+
+ private void SaveCardFile()
+ {
+ try
+ {
+ card.Save();
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("The card file could not be saved.");
+ }
+ }
+
+ private void SaveAsCardFile()
+ {
+ using (var file = new SaveFileDialog())
+ {
+ file.Filter = "AA2 Card files (*.png)|*.png";
+ if (file.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.Save(file.FileName);
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("The card file could not be saved.");
+ }
+ }
+ }
+ }
+
+ private void ReplaceCardFace()
+ {
+ using (var file = new OpenFileDialog())
+ {
+ file.Filter = "PNG (*.png)|*.png";
+ file.Multiselect = false;
+ if (file.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ card.ReplaceFaceImage(file.FileName);
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("The card face image could not be replaced.");
+ }
+ }
+ }
+ }
+
+ private void UpdateWindowState()
+ {
+ UpdateToolstripState();
+ UpdateCardFaceView();
+ }
+
+ private void UpdateToolstripState()
+ {
+ if (card.FileName != "")
+ {
+ saveToolStripButton.Enabled = true;
+ saveAsToolStripButton.Enabled = true;
+ replaceCardFaceToolStripButton.Enabled = true;
+ }
+ else
+ {
+ saveToolStripButton.Enabled = false;
+ saveAsToolStripButton.Enabled = false;
+ replaceCardFaceToolStripButton.Enabled = false;
+ }
+ }
+
+ private void UpdateCardFaceView()
+ {
+ imgCard.Image = card.FaceImage;
+ var size = card.FaceImage.Size;
+ lblDimensions.Text = "[" + size.Width + ", " + size.Height + "]";
+ }
+ #endregion
+ }
+}
diff --git a/AA2Snowflake/formMain.resx b/AA2Snowflake/formMain.resx
new file mode 100644
index 0000000..e30d7de
--- /dev/null
+++ b/AA2Snowflake/formMain.resx
@@ -0,0 +1,951 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ 132, 17
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGPSURBVDhPjY3dK0NxGMefa5cu/BVy4165cePKlRsRSrgk
+ ZZrYsBerea3JyzLTsFArCTVWWmYzFEloEtZOmGNmNrM9znPOb52b47RvffpdPJ9PPygsbILmkAl0xUAu
+ y+QFjaDHLw9iYlMdwSGXZfICIzCMH+uYf11RhRxyWSbPPwSGXHwNf7llVcghl2XyDvVg/n1xYTbqUIUc
+ clkmzzcIlmzMiZnHBVXIIZdl8vYHwJp5tmPqfkaVn5gDyWWZvD0tTHw/zGHydvpfMk92jPi0SO5uP1Sy
+ VNpOH0ynIjZMXI8rQrc7r0agF3P5PJLPUmlbGrAlbyaRv7QoQrezjQ7x/UynkXyWSvP0wGziyopv50ZF
+ 6HbibhPfKM8j+SyVttENC/zFKL6E9YrQLeBqEd9bjkPyxbAwdxcsvgs/ccEBRejmdzaJb/VxHslnqbSl
+ TliNnw5j7EirCN28c/XiW7GdRPJZCiUCZVOtsMWfGfAtpFOEbp6xWvElt7EK6lgLpQLl7TUwb26Ag2Ig
+ lxoAKP0DjPNI27PECtEAAAAASUVORK5CYII=
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGsSURBVDhPrY3dK0NxGMef4/wBLpULV67dKNdciRtFI3nJ
+ vOWCOyWlpghjbGqaVl7DhpQpYSNjMikXXkNeI6LV0Gi12uN8T78VpWPKrz6dep7P5zn0L68ywVKnl809
+ OoOhNh7gohE5kV7u6z2bemO86C/gwUUjcqIK2WQ+HA1wJBrlcCSiCRy4aEROVCab+veHn/ldEV4+PjSB
+ AxeNyInKZaN1z/7AwXCYH19fNYEDF43IiUqkTtuu7Y6flD9cBwKawIGLRuRExVK73W+94avLF74PhTSB
+ AxeNyHGgbchnuWB352FcwEUjcqIiyTDq7T1jY+E4d+nGfgS7GDsDt4xG5EQ6qWVi1XjMpuJJ9vt3eHPT
+ 9w3MsJtp8rG51Mlb/ZeMRuREBVKzc6XjgC1l0+zxeLklZ0BlYWFJBTPscMBWPc8bfeeMRuREeVLTnKfr
+ iK36OXa53OoXOJ0uldgMB0bql3nddMpoRE6UQflZuVKDuzXbFpydXeHBGpeKw7GoEpvhgKPRy2vdJwxf
+ 5JSikJZKGVWZVL0ty3L3T2D3lXTKHRYtJSokKST/EaWhxE9C7iVGGC6e7AAAAABJRU5ErkJggg==
+
+
+
+
+ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
+ YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIzSURBVDhPnY1fSJNhFMbP1wfdSldBF9FFl9JNIBRUeuNw
+ FwU1CymjmUUXRQSJiIMJSTqbOmOyWLZm/5zKyG/D1K10btZMEilTW2yWWJYhqWFOEHz6zts3aSRFHfjx
+ wnOe33npf6YpM3NPQpIeRomyRVC0yXbOKNdfyzebz/4NQ0mJ6dIOwwAqKqBIUr84YJTramMPvoFn7Q98
+ /dwPv9+PZDIJw5a9w26iXHHglGytH3XPYXVtDSurqxsy+7EPTzv1SM6UwVRkwcHNF+8KmadQtja8dH3B
+ d7W4sLz8G9NTQYT9eUL2unLQe10BO5pOdFK22IedM5hfWcGnxcU04pNdCPl0Qm67uR/jE93gLjuaTnRc
+ qnIMOaYxq/72bm5unYn4EHo7coXsce7D8xde0eEuO5pOVCBVOqP295hMLODD0tI6AaUcWIqhtekARsYe
+ iYw73GVH0/nAlVsRWxyBqtE01BUqjTp4ypvTcu6y89NW55hkdodqY7AcvYPq/OYN4V2KwcYpsKPpRPmS
+ 6d5jyxisBfcRjQ4iHI6kwRnv2kojqD/hwUBDAuxoOtERqczTc/UVbIWtCAZDMOU1Cny+LgFnvOMDjuIO
+ 9Ne9BTuaTnRIKvUGq1/DbvRCUQLiZTweRZDK+MDt893os74BO5pOlEWHc/TShUCFzjHf3t6DG2cUQUtL
+ pyCV8YGWyyE8qRkH9zWdtqvs2klZp7Op+JksyzUbwbtf2U16l+ZShspWlW3/iOpQxg9N5hiCU69T1wAA
+ AABJRU5ErkJggg==
+
+
+
+
+ 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
+ /gAAAAAAAAAAYGBbX8/PwP7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+z8/A/mxsZl8AAAAAAAAAAJKSiP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+ysq8/kVFQmQTExMNAAAAAAAAAAAJCQkNS0tHHFdXU8/Fxbf+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+mpqQ/mBgW1AAAAAAWlpVX8/PwP7+/uv+/v7r/vr65/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv++Pjm/v7+6/7+/uv+z8/A/mRkX18AAAAAV1dTUJqakP7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7Fxbf+VlZSz1RUUB0SEhENAAAAAAAAAAAEBAQNLy8tBlpaVQRISEV2bW1n
+ /vX14//+/uv+/v7r/v//7P/+/uv+/f3q/rGxpP5RUU24UlJOj8/Pwf/+/uv+/v7r/r6+sf9sbGb339/O
+ /v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/t/fz/9sbGb6vb2w/v//7P/+/uv+z8/B/lZWUo9RUU25sbGk
+ /v7+6//+/uv+/v7r/v//7P/+/uv+9fXj/m1tZ/9LS0h2YmJdBDY2NAYJCQgNAAAAAAAAAAAUFBMNPz88
+ B0hIRVFYWFTYx8e5/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7y8uD+kJCH/tbWxv7+/uv+/v7r
+ /ry8r/5qamROTExIh4+PhvXy8uD+/v7r/v7+6/7y8uD+j4+G9U5OSodhYVxRvLyv/v7+6/7+/uv+1dXG
+ /pCQh/7y8uD+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/sfHuf5ZWVTYTk5KUUZGQgcbGxoNAAAA
+ AAAAAAAzMzENQUE+qqionf76+uf+/v7r/v7+6/7+/uv+/v7r/u3t3P77++j+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/ry8r/5kZF4/AAAAAGRkXlG7u67+/v7r/v7+6/67u67+bW1nUAAAAABYWFM/vLyv
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/77++j+7e3b/v7+6/7+/uv+/v7r/v7+6/76+uf+qKid
+ /kNDQKpBQT4NAAAAAAAAAAA5OTcNe3tz9f//7P/+/uv+/v7r/v//7P/09OL+lpaM91RUUJxhYVzWzc2/
+ /v7+6//+/uv+/v7r/v//7P/+/uv+/v7r/r+/sv9ZWVRmAAAAAFlZVVe7u67+/v7r/v//7P+7u67+YWFb
+ VQAAAABRUU1mv7+x/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/s3Nv/5hYVzXVFRPnJaWjPf09OL+/v7r
+ /v//7P/+/uv+/v7r/nt7dPVKSkYNAAAAAAAAAAAzMzENQUE+tuzs2/7+/uv+/f3q/rW1qP1eXlm0cHBp
+ KAAAAABkZF4HTExIcnR0bfrn59b+/v7r/v7+6/7+/uv+/v7r/vr65/6oqJ39Tk5KzZGRiPr09OL+/v7r
+ /v7+6/7y8uD+jIyD90xMSMyoqJ39+vrn/v7+6/7+/uv+/v7r/v7+6/7n59b+dnZv+VBQTHJoaGIHAAAA
+ AGpqZChdXVi0tLSo/f396v7+/uv+7Ozb/kREQbZCQj8NAAAAAAAAAAAcHBsNSkpGLXBwavTBwbP+bGxm
+ 2GdnYUp0dG0BAAAAAAAAAABVVVARRUVCkIiIf/3w8N7+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv++vrn
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/vr65/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7w8N7+iIh/
+ /EhIRZBbW1YRAAAAAAAAAABra2UBYmJcSmZmYdrBwbP+cHBq9FJSTi0mJiQNAAAAAAAAAAAGBgYNODg1
+ BldXUiJaWlVDdnZvBQAAAAAAAAAAX19aA09PS2Zra2Xs3t7O/v//7P/+/uv+/v7r/v//7P/8/On+/f3q
+ /v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v396v/8/Or+/v7r
+ /v//7P/+/uv+/v7r/t7ezv9tbWfpUlJOZmdnYQQAAAAAAAAAAHBwagVaWlVDXFxWIkNDPwYKCgoNAAAA
+ AAAAAAAAAAANCgoKBgAAAAAAAAAAAAAAAAAAAABdXVgEUVFOvcLCtP79/er+/v7r/v7+6/7+/uv+/v7r
+ /tTUxf5oaGLZZWVg4NXVxv7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+1dXF
+ /mVlYOBoaGLZ1NTE/v7+6/7+/uv+/v7r/v7+6/79/er+wcG0/lNTT71qamQEAAAAAAAAAAAAAAAAAAAA
+ AA0NDQYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAABRUU1CsrKl/v7+6/7+/uv+/v7r
+ /v7+6/7s7Nv+fHx1+VNTT3lwcGoJbGxmC0tLSLz4+Ob+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/74+Ob+TU1Ku3NzbAttbWcJT09LeX19dfnq6tn+/v7r/v7+6/7+/uv+/v7r/rKypf5dXVhBAAAA
+ AAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAABQUE1CsrKl
+ /v//7P/+/uv+/v7r/v//7P/s7Nv+fX11+VBQTXljY10JX19aC0lJRr/4+Ob+/v7r/v//7P/+/uv+/v7r
+ /v//7P/+/uv+/v7r/v//7P/4+Ob+TExJvmdnYQteXlkJTExJeX19dfrq6tn+/v7r/v//7P/+/uv+/v7r
+ /rKypv5dXVhBAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANBgYGBgAAAAAAAAAAAAAA
+ AAAAAABiYl0EU1NPvMHBtP79/er+/v7r/v7+6/7+/uv+/v7r/tTUxf5nZ2HZZ2dh4NXVxv7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+1dXG/mdnYeBnZ2HZ1NTF/v7+6/7+/uv+/v7r
+ /v7+6/79/er+wcG0/lRUUL1vb2kEAAAAAAAAAAAAAAAAAAAAAAcHBwYAAAANAAAAAAAAAAAEBAQNLy8s
+ BkxMSCJNTUpDZ2diBQAAAAAAAAAAbGxmA1VVUGZtbWfo3t7O/v7+6/7+/uv+/v7r/v7+6/78/On+/Pzq
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v396v78/On+/v7r
+ /v7+6/7+/uv+/v7r/t7ezv5ra2XrV1dSZnNzbAQAAAAAAAAAAGJiXQVOTkpDUFBMIjg4NQYHBwcNAAAA
+ AAAAAAAWFhUNRUVBLXFxavXBwbP+ampk2WBgW0pmZmEBAAAAAAAAAABgYFsRR0dEkIiIgP3w8N7+/v7r
+ /v//7P/+/uv+/v7r/v//7P/+/uv+9fXj/v7+6//+/uv+/v7r/v//7P/+/uv+/f3q/vX14//+/uv+/v7r
+ /v//7P/+/uv+/v7r/v//7P/x8d/+iIiA/EpKR5BlZWARAAAAAAAAAABcXFcCWlpWSmZmYNrBwbP+cHBq
+ 9E1NSS0fHx4NAAAAAAAAAAAxMS8NQEA9tuzs2/7+/uv+/f3q/rW1qP1cXFe0ZmZhKAAAAABYWFMHTk5L
+ cXZ2b/rn59b+/v7r/v7+6/7+/uv+/v7r/vr65/6oqJ39VlZSvHJya+nq6tn+/v7r/v7+6/7o6Nf+bGxm
+ 6FVVUb6oqJ39+vrn/v7+6/7+/uv+/v7r/v7+6/7o6Nf+eHhx+VNTT3NdXVgHAAAAAF9fWihYWFO2t7eq
+ /v396v7+/uv+7Ozb/kBAPbk/PzwNAAAAAAAAAAA5OTcNe3tz9f7+6/7+/uv+/v7r/v7+6/709OL+lpaM
+ 91FRTZxgYFvXzc2+/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/r+/sv5eXllmAAAAAGhoYkm7u67+/v7r
+ /v7+6/66uq3+cHBpSAAAAABWVlJmv7+x/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/s3Nvv5gYFvXUFBM
+ nJaWjPf09OL+/v7r/v7+6/7+/uv+/v7r/nx8dPVJSUYNAAAAAAAAAAA2NjMNQ0NAqqionf76+uf+/v7r
+ /v//7P/+/uv+/v7r/u3t3P/7++j+/v7r/v//7P/+/uv+/v7r/v7+6//+/uv+/v7r/r29r/9kZF8/AAAA
+ AF9fWlG7u67+/v7r/v//7P+7u67+aWljUQAAAABYWFQ/vLyv/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r
+ /v//7P/7++j+7e3c/v//7P/+/uv+/v7r/v//7P/6+uf+qKid/kVFQqpFRUENAAAAAAAAAAAbGxoNTExI
+ B1BQTFBZWVTYx8e5/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7y8uD+kJCH/tbWxv7+/uv+/v7r
+ /ry8r/5iYlxRR0dEh4+PhvXy8uD+/v7r/v7+6/7y8uD+j4+G9UlJRohgYFpPvLyv/v7+6/7+/uv+1dXG
+ /pCQh/7z8+H+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/sfHuf5ZWVXYVVVQUFNTTgcjIyINAAAA
+ AAAAAAAFBQUNKCgnBlRUUARHR0R2bW1n/vX14/7+/uv+/v7r/v7+6/7+/uv+/f3q/rCwpP5UVFC3VlZS
+ j8/Pwf7+/uv+/v7r/r29sP5sbGb639/O/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/t/fz/5sbGb3vb2w
+ /v7+6/7+/uv+z8/A/lhYU49SUk69s7Om/v396v7+/uv+/v7r/v7+6/7+/uv+8/Ph/m1tZv5LS0d2XV1Y
+ BDAwLgYICAgNAAAAAAAAAAAICAgNRkZCHFRUUM/Gxrj+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+mpqQ
+ /mRkX1AAAAAAW1tWX8/Pwf/+/uv+/v7r/vn55v/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r
+ /v//7P/+/uv++fnm/v//7P/+/uv+z8/B/mRkX18AAAAAXFxXUJqakP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7Fxbf+V1dSz1FRTB0QEBANAAAAAAAAAAAKCgoNOzs5ZMrKvP7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+kZGI/gAAAAAAAAAAX19aX8/PwP7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+z8/A/mtrZV8AAAAAAAAAAJKSiP7+/uv+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+y8u9/kZGQmQTExMNAAAAAAAAAAAJCQkNQUE+LoSEfPn+/uv+/v7r
+ /vj45f6fn5T+9/fk/v7+6/7+/uv+kZGI/gAAAAAAAAAAXl5ZX8/Pwf7+/uv+/v7r/v7+6/7+/uv++fnn
+ /pqakP7Gxrj+/v7r/v7+6/7Gxrj+mpqQ/vn55/7+/uv+/v7r/v7+6/7+/uv+z8/A/mpqZF8AAAAAAAAA
+ AJKSiP7+/uv+/v7r/vf35f6fn5T++Pjl/v7+6/7+/uv+hIR8+UxMSC4SEhENAAAAAAAAAAAFBQUNPDw5
+ BkJCP469vbD+t7eq/lVVUcM5OTe/9PTi/v//7P/+/uv+kpKI/gAAAAAAAAAAVVVRRrm5rP/+/uv+/v7r
+ /vz86v+6uq3+UlJOyFRUT3u7u67+/v7r/v//7P+7u63+V1dSe1FRTsi6uq3+/Pzp/v//7P/+/uv+ubms
+ /mFhXEcAAAAAAAAAAJKSiP7+/uv+/v7r/vT04v46Oji/WFhTw7q6rf69vbD+RkZDjklJRQYJCQkNAAAA
+ AAAAAAAAAAANGBgXBlhYUwdOTkthWFhUS19fWgFDQ0CP9PTi/v7+6/7+/uv+kZGI/gAAAAAAAAAAYGBa
+ CFBQTNLLy73+zc2+/mlpY+JcXFdfZGReA2NjXT+7u67+/v7r/v7+6/66uq3+bm5nPl5eWANYWFNfaGhi
+ 4s3Nvv7Ly7z+UFBM0m1tZggAAAAAAAAAAJKSiP7+/uv+/v7r/vT04v5HR0OPYWFbAVJSTk5OTktgY2Nd
+ ByIiIQYBAQENAAAAAAAAAAAAAAANAwMDBgAAAAAAAAAAAAAAAAAAAABBQT+P9PTi/v7+6/7+/uv+kZGI
+ /gAAAAAAAAAAAAAAAGpqZA5YWFRhXV1YX3JybAwAAAAAAAAAAGdnYT+7u67+/v7r/v7+6/66uq3+dHRu
+ PwAAAAAAAAAAbGxmDFxcV19ZWVVhb29pDgAAAAAAAAAAAAAAAJKSiP7+/uv+/v7r/vT04v5HR0OPAAAA
+ AAAAAAAAAAAAAAAAAAYGBgYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAABKSkd4v7+y
+ /uPj0//i4tH+aWlj6QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWlpWL1lZVb/b28v+/v7r
+ /v//7P/b28v+XFxXv19fWi8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGpqZOvi4tH+4+PT
+ /r+/sv5QUEx4AAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAA
+ AAAAAABjY14LVFRQalpaVX9XV1N9ZWVfLwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF9fWhdQUEyWm5uR
+ +fj45f7+/uv+/v7r/v7+6/7+/uv++Pjl/pubkflSUk6XZWVgFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AGJiXS9VVVF9W1tWf1VVUWpra2ULAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAA
+ BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZF8FUVFO
+ b319dezo6Nf+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7o6Nf+fX117FRUUG9sbGYFAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAA
+ AAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAABOTkuS0tLD/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r/v//7P/+/uv+/v7r
+ /tLSw/9SUk6RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAABQUEy29/fk/v7+6/7+/uv+/v7r/t7ezv7d3c3+/v7r/v7+6/7d3c3+3t7N
+ /v7+6/7+/uv+/v7r/vf35P5UVFC2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZWVRFnp6T/f7+6/7w8N/+i4uC+0JCP8S7u67+/v7r
+ /v7+6/66uq3+Q0NAxIuLgvvw8N/+/v7r/p6ek/1hYVxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAABgAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATU1Kl3p6c/NKSke6XV1Y
+ LmhoYj+7u67+/v7r/v//7P+7u63+cXFqP1lZVC5JSUa8enpz8k9PS5cAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAAAAAAAAAAAAANAAAA
+ BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AExMSQgAAAAAAAAAAFhYVD65uaz+/v7r/v7+6/65uaz+ZmZgPgAAAAAAAAAATExICAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAANAAAA
+ AAAAAAAAAAANAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGZmYA9QUEzTe3tz/nt7dP5QUE3Tc3NtDwAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAYAAAANAAAAAJ//+B//+QAAn//YG//5AACf/4AB//kAAJ//AAD/+QAAn/8AAP/5AACf/wAA//kA
+ AJ//AAD/+QAAng/AA/B5AACeD/AP8HkAAJ4OGBhweQAAgAwAADABAACADAAAMAEAAIAMAAAwAQAAgAwA
+ ADABAACABAAAIAEAAIAAAAAAAQAAgAAAAAABAACAAAgQAAEAAIAACBAAAQAAgEAAAAIBAACAwAAAAwEA
+ AIMAAAAAwQAAngAAAAB5AACeAAAAAHkAAJ4AAAAAeQAAngAAAAB5AACDAAAAAMEAAIDAAAADAQAAgEAA
+ AAIBAACAAAgQAAEAAIAACBAAAQAAgAAAAAABAACAAAAAAAEAAIAEAAAgAQAAgAwAADABAACADAAAMAEA
+ AIAMAAAwAQAAgAwAADABAACeDhgYcHkAAJ4P8A/weQAAng/AA/B5AACf/wAA//kAAJ//AAD/+QAAn/8A
+ AP/5AACf/wAA//kAAJ//gAH/+QAAn//YG//5AACf//gf//kAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAFZWUViiopf8oqKX/F5eWVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAV1dSQ2RkXqFMTEg1Z2dhfv7+6/7+/uv+bm5of0lJRTVkZF6hXFxXQwAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAEREQQiRkYjj/v7r/sXFt/1vb2jl/v7r/v7+6/5vb2nlxcW3/f7+6/6SkojjUFBM
+ CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAADQAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAQEA9HLKypvv+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r
+ /rKypvtMTEgbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAANAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYWFbM4KCecTn59b+/v7r/v7+6/7+/uv+/v7r
+ /ufn1v6Dg3rEZWVgMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAA
+ AAAAAA0AAAAAAAAAAD4+OwFzc2zZpKSZ/nZ2b9EAAAAAAAAAAAAAAAAAAAAAAAAAAF5eWVijo5ji/v7r
+ /v7+6/6kpJniYmJdWAAAAAAAAAAAAAAAAAAAAAAAAAAAdnZv0aSkmf50dG3ZS0tHAQAAAAAAAAAAAAAA
+ DQAAAAAAAAAAAwMDDQAAAAAAAAAAPz88CrS0p/7+/uv+trap/gAAAAAAAAAAVFRPW21tZ5hWVlI0AAAA
+ AGxsZn/+/uv+/v7r/nV1bn8AAAAAUVFNNG1tZ5hXV1JbAAAAAAAAAAC2tqn+/v7r/rS0p/5LS0cKAAAA
+ AAAAAAAFBQUNAAAAAAAAAAAfHx4NWlpVj5WVi+RSUk53tLSn/v7+6/62tqn+AAAAAEVFQiTAwLL+/v7r
+ /sfHuf1lZV+gYWFcmf7+6/7+/uv+ZWVfmWNjXqDHx7n9/v7r/r6+sf1UVFAjAAAAALa2qf7+/uv+tLSn
+ /lNTTneUlIrkXV1YjSgoJg0AAAAAAAAAAC4uLDS9vbD8/v7r/uzs2v7f38/+/v7r/ra2qf4AAAAAT09L
+ P9/fz/7+/uv+/v7r/vz86f65uaz+/v7r/v7+6/65uaz+/Pzp/v7+6/7+/uv+39/P/l9fWj8AAAAAtrap
+ /v7+6/7g4M/+7Ozb/v7+6/69vbD8ODg1NAAAAAAAAAAANDQxK5WVi+n4+Ob+/v7r/v7+6/7+/uv+urqt
+ /mVlXyNTU08/39/P/v7+6/78/On+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/Pzp/v7+6/7f38/+X19a
+ P1lZVCO6uq3+/v7r/v7+6/7+/uv++Pjm/pWVi+k9PTosAAAAAAAAAAAiIiENV1dTHmBgW+r6+uf+/v7r
+ /v7+6/7+/uv+uLir9ltbVrXf38/+/v7r/pqakO6bm5Dh9vbk/v7+6/7+/uv+9vbk/pubkeGXl43x/v7r
+ /t/fz/5dXVi2t7eq9v7+6/7+/uv+/v7r/vr65/5gYFvqXl5ZHikpJw0AAAAAAAAAAEBAPVaWlozp9vbk
+ /v7+6/7+/uv+9fXj/v7+6/7+/uv+9vbk/vb25P7+/uv+mJiO1GNjXQptbWet/v7r/v7+6/5ycmutYWFc
+ CpeXjdT+/uv+9vbk/vb25P7+/uv+/v7r/vX14/7+/uv+/v7r/vb25P6WlozpR0dDVgAAAAAAAAAAXl5Z
+ p/7+6/7+/uv+8PDf/pKSidNiYl1ujIyD2O/v3f7+/uv+/v7r/v7+6/6qqp7sV1dSRmpqZLL+/uv+/v7r
+ /mtrZbBTU09Fqamd7P7+6/7+/uv+/v7r/u/v3f6NjYTYYmJcb5GRiNPw8N/+/v7r/v7+6/5kZF6nAAAA
+ AAAAAAA8PDk9ra2h+qysoO5qamRtYmJcBAAAAABRUU1NjIyD/P7+6/7+/uv+/v7r/v7+6/7W1sb+9/fk
+ /v7+6/7+/uv+9vbk/tXVxf7+/uv+/v7r/v7+6/7+/uv+jY2D+1ZWUk0AAAAAW1tWBGZmYG2pqZ7ura2h
+ +kREQT0AAAAAAAAAAB8fHg1JSUUhXV1YDgAAAABWVlIgbW1nstbWx/7+/uv+/v7r/vj45v7T08T+/v7r
+ /v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7T08T++Pjm/v7+6/7+/uv+1dXG/nFxarFdXVggAAAA
+ AFpaVQ5MTEghJiYkDQAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAH5+d8b+/uv+/v7r/v7+6/69va/8aGhi
+ jV9fWj2ZmY/h/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+m5uQ4GFhXD1lZWCNvLyv/P7+6/7+/uv+/v7r
+ /oCAeMUAAAAAAAAAAAAAAAABAQENAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAf393xv7+6/7+/uv+/v7r
+ /r29r/xkZF+NVFRQPZmZj+L+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/6ampDiV1dTPWFhXI28vK/8/v7r
+ /v7+6/7+/uv+gYF5xQAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAABkZGA09PTohT09LDgAAAABfX1ogcnJs
+ sdXVxv7+/uv+/v7r/vj45v7U1MX+/v7r/v7+6/7+/uv+/v7r/v7+6/7+/uv+/v7r/v7+6/7U1MX++Pjl
+ /v7+6/7+/uv+1tbH/nFxa7JmZmAgAAAAAE1NSQ5AQD0hHx8dDQAAAAAAAAAANzc1Pa2tofqrq5/uZGRe
+ bVhYUwQAAAAAVVVRTI2Ng/z+/uv+/v7r/v7+6/7+/uv+0NDB/uzs2v7+/uv+/v7r/unp2P7Q0MH+/v7r
+ /v7+6/7+/uv+/v7r/o2NhPtaWlZNAAAAAFJSTgReXlluqamd7q2tofo/Pzw9AAAAAAAAAABeXlmn/v7r
+ /v7+6/7w8N/+kJCH01paVW6Li4LY7+/d/v7+6/7+/uv+/v7r/qqqnuxkZF8+aGhipP7+6/7+/uv+a2tl
+ o2BgWz+pqZ7s/v7r/v7+6/7+/uv+8PDe/ouLgthZWVRuj4+G0/Hx3/7+/uv+/v7r/mNjXagAAAAAAAAA
+ AEVFQlaXl43p9vbk/v7+6/7+/uv+9fXj/v7+6/7+/uv+9vbk/vb25P7+/uv+mJiO1F1dWApra2Wt/v7r
+ /v7+6/5wcGmtWlpVCpeXjdT+/uv+9vbk/vb25P7+/uv+/v7r/vX14/7+/uv+/v7r/vb25P6Xl43pTExI
+ VgAAAAAAAAAAIyMiDVdXUh5gYFvq+vrn/v7+6/7+/uv+/v7r/ri4q/ZdXVi139/P/v7+6/6Xl43xm5uR
+ 4fb25P7+/uv+/v7r/vb25P6ZmY/hmZmP7v7+6/7f38/+Xl5Ztrm5rPj+/uv+/v7r/v7+6/75+eb+YGBb
+ 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
+ AHgeAAB4HgAA
+
+
+
\ No newline at end of file
diff --git a/AA2Snowflake/nature/sp_04_00_00.tga b/AA2Snowflake/nature/sp_04_00_00.tga
new file mode 100644
index 0000000..368ad82
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_00.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_01.tga b/AA2Snowflake/nature/sp_04_00_01.tga
new file mode 100644
index 0000000..b2b297a
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_01.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_02.tga b/AA2Snowflake/nature/sp_04_00_02.tga
new file mode 100644
index 0000000..5e7c26d
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_02.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_03.tga b/AA2Snowflake/nature/sp_04_00_03.tga
new file mode 100644
index 0000000..a7ad58b
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_03.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_04.tga b/AA2Snowflake/nature/sp_04_00_04.tga
new file mode 100644
index 0000000..b59b210
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_04.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_05.tga b/AA2Snowflake/nature/sp_04_00_05.tga
new file mode 100644
index 0000000..2a88d7d
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_05.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_06.tga b/AA2Snowflake/nature/sp_04_00_06.tga
new file mode 100644
index 0000000..eeaa352
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_06.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_07.tga b/AA2Snowflake/nature/sp_04_00_07.tga
new file mode 100644
index 0000000..fcfc2f1
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_07.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_08.tga b/AA2Snowflake/nature/sp_04_00_08.tga
new file mode 100644
index 0000000..a7363ae
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_08.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_09.tga b/AA2Snowflake/nature/sp_04_00_09.tga
new file mode 100644
index 0000000..87acb3e
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_09.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_10.tga b/AA2Snowflake/nature/sp_04_00_10.tga
new file mode 100644
index 0000000..ead1de2
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_10.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_11.tga b/AA2Snowflake/nature/sp_04_00_11.tga
new file mode 100644
index 0000000..43412ec
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_11.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_12.tga b/AA2Snowflake/nature/sp_04_00_12.tga
new file mode 100644
index 0000000..c7529e7
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_12.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_13.tga b/AA2Snowflake/nature/sp_04_00_13.tga
new file mode 100644
index 0000000..65ccc40
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_13.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_14.tga b/AA2Snowflake/nature/sp_04_00_14.tga
new file mode 100644
index 0000000..803e248
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_14.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_15.tga b/AA2Snowflake/nature/sp_04_00_15.tga
new file mode 100644
index 0000000..0796c20
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_15.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_16.tga b/AA2Snowflake/nature/sp_04_00_16.tga
new file mode 100644
index 0000000..86c3d47
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_16.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_17.tga b/AA2Snowflake/nature/sp_04_00_17.tga
new file mode 100644
index 0000000..663b523
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_17.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_18.tga b/AA2Snowflake/nature/sp_04_00_18.tga
new file mode 100644
index 0000000..dea5ff4
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_18.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_19.tga b/AA2Snowflake/nature/sp_04_00_19.tga
new file mode 100644
index 0000000..778c4ee
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_19.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_20.tga b/AA2Snowflake/nature/sp_04_00_20.tga
new file mode 100644
index 0000000..8dd90de
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_20.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_21.tga b/AA2Snowflake/nature/sp_04_00_21.tga
new file mode 100644
index 0000000..261f1fa
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_21.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_22.tga b/AA2Snowflake/nature/sp_04_00_22.tga
new file mode 100644
index 0000000..cfeec15
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_22.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_23.tga b/AA2Snowflake/nature/sp_04_00_23.tga
new file mode 100644
index 0000000..4c4658b
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_23.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_24.tga b/AA2Snowflake/nature/sp_04_00_24.tga
new file mode 100644
index 0000000..cde6baa
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_24.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_25.tga b/AA2Snowflake/nature/sp_04_00_25.tga
new file mode 100644
index 0000000..cb1bd26
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_25.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_26.tga b/AA2Snowflake/nature/sp_04_00_26.tga
new file mode 100644
index 0000000..169c985
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_26.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_27.tga b/AA2Snowflake/nature/sp_04_00_27.tga
new file mode 100644
index 0000000..6d6ecc1
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_27.tga differ
diff --git a/AA2Snowflake/nature/sp_04_00_28.tga b/AA2Snowflake/nature/sp_04_00_28.tga
new file mode 100644
index 0000000..c7d23cf
Binary files /dev/null and b/AA2Snowflake/nature/sp_04_00_28.tga differ
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1930595
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AA2 Card Editor")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AA2 Card Editor")]
+[assembly: AssemblyCopyright("Copyright © 2014 Anonymous")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c3ce529d-f5d8-4199-92e2-58938f4fc979")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.1")]
+[assembly: AssemblyFileVersion("1.0.0.1")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..d808f21
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2CardEditor.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AA2CardEditor.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..163a6a3
--- /dev/null
+++ b/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace AA2CardEditor.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Properties/Settings.settings b/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/SB3UtilityPP/Ema.cs b/SB3UtilityPP/Ema.cs
new file mode 100644
index 0000000..97f981c
--- /dev/null
+++ b/SB3UtilityPP/Ema.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using SlimDX;
+using SlimDX.Direct3D9;
+
+namespace SB3Utility
+{
+ public class Ema : IWriteFile
+ {
+ public string Name { get; set; }
+ public byte[] Data { get; set; }
+
+ public Ema()
+ {
+ }
+
+ public Ema(Stream stream, string name)
+ : this(stream)
+ {
+ this.Name = name;
+ }
+
+ public Ema(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ Data = reader.ReadToEnd();
+ }
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Data);
+ }
+
+ public ImportedTexture ImportedTexture()
+ {
+ string ext = Encoding.ASCII.GetString(Data, 8, 4).ToLowerInvariant();
+ ImportedTexture importedTex = new ImportedTexture();
+ importedTex.Name = Path.GetFileNameWithoutExtension(Name) + ext;
+ importedTex.Data = CreateImageData();
+ return importedTex;
+ }
+
+ public void Export(string path)
+ {
+ using (BinaryWriter writer = new BinaryWriter(File.Create(path)))
+ {
+ writer.Write(CreateImageData());
+ }
+ }
+
+ byte[] CreateImageData()
+ {
+ string ext = Encoding.ASCII.GetString(Data, 8, 4).ToLowerInvariant();
+ byte[] buf = new byte[Data.Length - 13];
+ Array.Copy(Data, 13, buf, 0, buf.Length);
+ if (ext.ToLowerInvariant() == ".bmp")
+ {
+ buf[0] = (byte)'B';
+ buf[1] = (byte)'M';
+ }
+ return buf;
+ }
+
+ public static Ema Import(string path)
+ {
+ return Import(File.OpenRead(path), Path.GetFileNameWithoutExtension(path) + ".ema");
+ }
+
+ public static Ema Import(Stream stream, string name)
+ {
+ Ema ema = Import(stream);
+ ema.Name = name;
+ return ema;
+ }
+
+ public static Ema Import(Stream stream)
+ {
+ Ema ema = new Ema();
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ byte[] imgData = reader.ReadToEnd();
+ var imgInfo = ImageInformation.FromMemory(imgData);
+
+ ema.Data = new byte[imgData.Length + 13];
+ BinaryWriter dataWriter = new BinaryWriter(new MemoryStream(ema.Data));
+ dataWriter.Write(imgInfo.Width);
+ dataWriter.Write(imgInfo.Height);
+
+ string ext = Enum.GetName(typeof(ImageFileFormat), imgInfo.ImageFileFormat).ToLowerInvariant();
+ dataWriter.Write((byte)'.');
+ dataWriter.Write(Encoding.ASCII.GetBytes(ext));
+ dataWriter.Write((byte)0);
+
+ if (imgInfo.ImageFileFormat == ImageFileFormat.Bmp)
+ {
+ dataWriter.Write((short)0);
+ dataWriter.Write(imgData, 2, imgData.Length - 2);
+ }
+ else
+ {
+ dataWriter.Write(imgData);
+ }
+ }
+ return ema;
+ }
+ }
+}
diff --git a/SB3UtilityPP/Extensions.cs b/SB3UtilityPP/Extensions.cs
new file mode 100644
index 0000000..42d2e13
--- /dev/null
+++ b/SB3UtilityPP/Extensions.cs
@@ -0,0 +1,429 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.ComponentModel;
+using System.Windows.Forms;
+using SlimDX;
+
+namespace SB3Utility
+{
+ public static class Extensions
+ {
+ public static void AutoResizeColumns(this ListView listView)
+ {
+ for (int i = 0; i < listView.Columns.Count; i++)
+ {
+ listView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.HeaderSize);
+ int header = listView.Columns[i].Width;
+
+ listView.AutoResizeColumn(i, ColumnHeaderAutoResizeStyle.ColumnContent);
+ int content = listView.Columns[i].Width;
+
+ listView.Columns[i].Width = Math.Max(header, content);
+ }
+ }
+
+ public static void SelectTabWithoutLoosingFocus(this TabControl tab, TabPage newTab)
+ {
+ tab.Enabled = false;
+ tab.SelectedTab = newTab;
+ tab.Enabled = true;
+ }
+
+ public static void SaveDesignSizes(this Form f)
+ {
+ SaveDesignSizes(f.Controls);
+ }
+
+ private static void SaveDesignSizes(Control.ControlCollection controls)
+ {
+ foreach (Control c in controls)
+ {
+ c.Tag = new Tuple(c.Left, c.Top, c.Width, c.Height, c.Font.Size);
+
+ if (c.HasChildren)
+ {
+ SaveDesignSizes(c.Controls);
+ }
+ }
+ }
+
+ public static void ResizeControls(this Form f, System.Drawing.SizeF startSize)
+ {
+ if (startSize.Width != 0 && startSize.Height != 0)
+ {
+ f.Opacity = 0.75;
+ f.ResetControls();
+ System.Drawing.SizeF resize = new System.Drawing.SizeF(f.Width / startSize.Width, f.Height / startSize.Height);
+ foreach (Control child in f.Controls)
+ {
+ child.Scale(resize);
+ }
+ float factor = f.Width - startSize.Width < f.Height - startSize.Height ?
+ f.Width / startSize.Width : f.Height / startSize.Height;
+ ResizeFont(f.Controls, factor);
+ f.Opacity = 1;
+ }
+ }
+
+ public static void ResetControls(this Form f)
+ {
+ ResetControls(f.Controls);
+ }
+
+ private static void ResetControls(Control.ControlCollection controls)
+ {
+ foreach (Control c in controls)
+ {
+ c.Hide();
+ c.Left = ((Tuple)c.Tag).Item1;
+ c.Top = ((Tuple)c.Tag).Item2;
+ c.Width = ((Tuple)c.Tag).Item3;
+ c.Height = ((Tuple)c.Tag).Item4;
+ c.Font = new System.Drawing.Font(c.Font.FontFamily.Name, ((Tuple)c.Tag).Item5);
+ c.Show();
+
+ if (c.HasChildren)
+ {
+ ResetControls(c.Controls);
+ }
+ }
+ }
+
+ public static void ResizeFont(Control.ControlCollection controls, float scaleFactor)
+ {
+ foreach (Control c in controls)
+ {
+ c.Font = new System.Drawing.Font(c.Font.FontFamily.Name, c.Font.Size * scaleFactor);
+
+ if (c.HasChildren)
+ {
+ ResizeFont(c.Controls, scaleFactor);
+ }
+ }
+ }
+
+ public static string GenericName(this Type type)
+ {
+ string s = String.Empty;
+ if (type.IsGenericType)
+ {
+ s += type.Name.Substring(0, type.Name.Length - 2) + "<";
+ foreach (var arg in type.GetGenericArguments())
+ {
+ s += arg.Name + ", ";
+ }
+ s = s.Substring(0, s.Length - 2) + ">";
+ }
+ else
+ {
+ s += type.Name;
+ }
+ return s;
+ }
+
+ public static bool IsHex(this char c)
+ {
+ return (c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F');
+ }
+
+ public static string ToFloatString(this float value)
+ {
+ return value.ToString("0.############", Utility.CultureUS);
+ }
+
+ public static string ToFloat6String(this float value)
+ {
+ return value.ToString("f6", Utility.CultureUS);
+ }
+
+ public static Color4 ToColor4(this float[] rgba)
+ {
+ return new Color4(rgba[3], rgba[0], rgba[1], rgba[2]);
+ }
+
+ public static Vector3 Perpendicular(this Vector3 v)
+ {
+ Vector3 perp = Vector3.Cross(v, Vector3.UnitX);
+ if (perp.LengthSquared() == 0)
+ {
+ perp = Vector3.Cross(v, Vector3.UnitY);
+ }
+ perp.Normalize();
+
+ return perp;
+ }
+
+ public static string GetName(this Enum value)
+ {
+ return Enum.GetName(value.GetType(), value);
+ }
+
+ public static string GetDescription(this Enum value)
+ {
+ object[] attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
+ if ((attributes != null) && (attributes.Length > 0))
+ {
+ return ((DescriptionAttribute)attributes[0]).Description;
+ }
+ else
+ {
+ return value.ToString();
+ }
+ }
+
+ #region BinaryReader/BinaryWriter
+ public static byte[] ReadToEnd(this BinaryReader reader)
+ {
+ MemoryStream mem = new MemoryStream();
+ BinaryWriter memWriter = new BinaryWriter(mem);
+
+ byte[] buf;
+ while ((buf = reader.ReadBytes(Utility.BufSize)).Length > 0)
+ {
+ memWriter.Write(buf);
+ }
+
+ return mem.ToArray();
+ }
+
+ public static string ReadName(this BinaryReader reader)
+ {
+ int nameLen = reader.ReadInt32();
+ byte[] nameBuf = reader.ReadBytes(nameLen);
+ return Utility.DecryptName(nameBuf);
+ }
+
+ public static string ReadName(this BinaryReader reader, int length)
+ {
+ byte[] nameBuf = reader.ReadBytes(length);
+ return Utility.DecryptName(nameBuf);
+ }
+
+ public static void WriteName(this BinaryWriter writer, string name)
+ {
+ byte[] nameBuf = Utility.EncryptName(name);
+ writer.Write(nameBuf.Length);
+ writer.Write(nameBuf);
+ }
+
+ public static void WriteName(this BinaryWriter writer, string name, int length)
+ {
+ byte[] nameBuf = Utility.EncryptName(name, length);
+ writer.Write(nameBuf.Length);
+ writer.Write(nameBuf);
+ }
+
+ public static void WriteNameWithoutLength(this BinaryWriter writer, string name, int length)
+ {
+ byte[] nameBuf = Utility.EncryptName(name, length);
+ writer.Write(nameBuf);
+ }
+
+ public static Matrix ReadMatrix(this BinaryReader reader)
+ {
+ Matrix m = new Matrix();
+ for (int i = 0; i < 4; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ m[i, j] = reader.ReadSingle();
+ }
+ }
+ return m;
+ }
+
+ public static void Write(this BinaryWriter writer, Matrix m)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ writer.Write(m[i, j]);
+ }
+ }
+ }
+
+ public static Vector2 ReadVector2(this BinaryReader reader)
+ {
+ Vector2 v = new Vector2();
+ v.X = reader.ReadSingle();
+ v.Y = reader.ReadSingle();
+ return v;
+ }
+
+ public static void Write(this BinaryWriter writer, Vector2 v)
+ {
+ writer.Write(v.X);
+ writer.Write(v.Y);
+ }
+
+ public static Vector3 ReadVector3(this BinaryReader reader)
+ {
+ Vector3 v = new Vector3();
+ v.X = reader.ReadSingle();
+ v.Y = reader.ReadSingle();
+ v.Z = reader.ReadSingle();
+ return v;
+ }
+
+ public static void Write(this BinaryWriter writer, Vector3 v)
+ {
+ writer.Write(v.X);
+ writer.Write(v.Y);
+ writer.Write(v.Z);
+ }
+
+ public static Quaternion ReadQuaternion(this BinaryReader reader)
+ {
+ Quaternion q = new Quaternion();
+ q.X = reader.ReadSingle();
+ q.Y = reader.ReadSingle();
+ q.Z = reader.ReadSingle();
+ q.W = reader.ReadSingle();
+ return q;
+ }
+
+ public static void Write(this BinaryWriter writer, Quaternion q)
+ {
+ writer.Write(q.X);
+ writer.Write(q.Y);
+ writer.Write(q.Z);
+ writer.Write(q.W);
+ }
+
+ public static Color4 ReadColor4(this BinaryReader reader)
+ {
+ Color4 color = new Color4();
+ color.Red = Math.Abs(reader.ReadSingle());
+ color.Green = Math.Abs(reader.ReadSingle());
+ color.Blue = Math.Abs(reader.ReadSingle());
+ color.Alpha = Math.Abs(reader.ReadSingle());
+ return color;
+ }
+
+ public static void Write(this BinaryWriter writer, Color4 color)
+ {
+ writer.Write(-Math.Abs(color.Red));
+ writer.Write(-Math.Abs(color.Green));
+ writer.Write(-Math.Abs(color.Blue));
+ writer.Write(-Math.Abs(color.Alpha));
+ }
+
+ public static void WriteUnnegated(this BinaryWriter writer, Color4 color)
+ {
+ writer.Write(color.Red);
+ writer.Write(color.Green);
+ writer.Write(color.Blue);
+ writer.Write(color.Alpha);
+ }
+
+ static T[] ReadArray(BinaryReader reader, Func del, int length)
+ {
+ T[] array = new T[length];
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = del();
+ }
+ return array;
+ }
+
+ static void WriteArray(Action del, T[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ del(array[i]);
+ }
+ }
+
+ public static float[] ReadSingleArray(this BinaryReader reader, int length)
+ {
+ return ReadArray(reader, new Func(reader.ReadSingle), length);
+ }
+
+ public static void Write(this BinaryWriter writer, float[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+
+ public static ushort[] ReadUInt16Array(this BinaryReader reader, int length)
+ {
+ return ReadArray(reader, new Func(reader.ReadUInt16), length);
+ }
+
+ public static void Write(this BinaryWriter writer, ushort[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+
+ public static int[] ReadInt32Array(this BinaryReader reader, int length)
+ {
+ return ReadArray(reader, new Func(reader.ReadInt32), length);
+ }
+
+ public static void Write(this BinaryWriter writer, int[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+
+ public static Vector2[] ReadVector2Array(this BinaryReader reader, int length)
+ {
+ return ReadArray(reader, new Func(reader.ReadVector2), length);
+ }
+
+ public static void Write(this BinaryWriter writer, Vector2[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+
+ public static Vector3[] ReadVector3Array(this BinaryReader reader, int length)
+ {
+ return ReadArray(reader, new Func(reader.ReadVector3), length);
+ }
+
+ public static void Write(this BinaryWriter writer, Vector3[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+
+ public static byte[] ReadBytes(this BinaryReader reader, uint count)
+ {
+ return reader.ReadBytes((int)count);
+ }
+
+ public static sbyte[] ReadSBytes(this BinaryReader reader, int count)
+ {
+ return ReadArray(reader, new Func(reader.ReadSByte), count);
+ }
+
+ public static void Write(this BinaryWriter writer, SByte[] array)
+ {
+ WriteArray(new Action(writer.Write), array);
+ }
+ #endregion
+
+ #region IfNotNull
+ public static void WriteIfNotNull(this BinaryWriter writer, byte[] array)
+ {
+ if (array != null)
+ {
+ writer.Write(array);
+ }
+ }
+
+ public static byte[] CloneIfNotNull(this byte[] array)
+ {
+ if (array == null)
+ {
+ return null;
+ }
+ else
+ {
+ return (byte[])array.Clone();
+ }
+ }
+ #endregion
+ }
+}
diff --git a/SB3UtilityPP/IReadFile.cs b/SB3UtilityPP/IReadFile.cs
new file mode 100644
index 0000000..557cf1d
--- /dev/null
+++ b/SB3UtilityPP/IReadFile.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SB3Utility
+{
+ public interface IReadFile
+ {
+ string Name { get; set; }
+ Stream CreateReadStream();
+ }
+}
diff --git a/SB3UtilityPP/IWriteFile.cs b/SB3UtilityPP/IWriteFile.cs
new file mode 100644
index 0000000..41360b1
--- /dev/null
+++ b/SB3UtilityPP/IWriteFile.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+
+namespace SB3Utility
+{
+ public interface IWriteFile
+ {
+ string Name { get; set; }
+ void WriteTo(Stream stream);
+ }
+}
diff --git a/SB3UtilityPP/Imported.cs b/SB3UtilityPP/Imported.cs
new file mode 100644
index 0000000..e128974
--- /dev/null
+++ b/SB3UtilityPP/Imported.cs
@@ -0,0 +1,277 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using SlimDX;
+using SlimDX.Direct3D9;
+
+namespace SB3Utility
+{
+ public interface IImported
+ {
+ List FrameList { get; }
+ List MeshList { get; }
+ List MaterialList { get; }
+ List TextureList { get; }
+ List AnimationList { get; }
+ List MorphList { get; }
+ }
+
+ public class ImportedFrame : ObjChildren, IObjChild
+ {
+ public string Name { get; set; }
+ public Matrix Matrix { get; set; }
+
+ public dynamic Parent { get; set; }
+ }
+
+ public class ImportedMesh
+ {
+ public string Name { get; set; }
+ public List SubmeshList { get; set; }
+ public List BoneList { get; set; }
+ }
+
+ public class ImportedSubmesh
+ {
+ public List VertexList { get; set; }
+ public List FaceList { get; set; }
+ public string Material { get; set; }
+ public int Index { get; set; }
+ public bool WorldCoords { get; set; }
+ public bool Visible { get; set; }
+ }
+
+ public class ImportedVertex
+ {
+ public Vector3 Position { get; set; }
+ public float[] Weights { get; set; }
+ public byte[] BoneIndices { get; set; }
+ public Vector3 Normal { get; set; }
+ public float[] UV { get; set; }
+ }
+
+ public class ImportedVertexWithColour : ImportedVertex
+ {
+ public Color4 Colour { get; set; }
+ }
+
+ public class ImportedFace
+ {
+ public int[] VertexIndices { get; set; }
+ }
+
+ public class ImportedBone
+ {
+ public string Name { get; set; }
+ public Matrix Matrix { get; set; }
+ }
+
+ public class ImportedMaterial
+ {
+ public string Name { get; set; }
+ public Color4 Diffuse { get; set; }
+ public Color4 Ambient { get; set; }
+ public Color4 Specular { get; set; }
+ public Color4 Emissive { get; set; }
+ public float Power { get; set; }
+ public string[] Textures { get; set; }
+ }
+
+ public class ImportedTexture
+ {
+ public string Name { get; set; }
+ public byte[] Data { get; set; }
+ public bool isCompressed { get; protected set; }
+
+ public ImportedTexture()
+ {
+ }
+
+ public ImportedTexture(string path) : this(File.OpenRead(path), Path.GetFileName(path)) { }
+
+ public ImportedTexture(Stream stream, string name)
+ {
+ Name = name;
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ Data = reader.ReadToEnd();
+ }
+
+ if (Path.GetExtension(name).ToUpper() == ".TGA")
+ {
+ isCompressed = Data[2] == 0x0A;
+ }
+ }
+
+ public Texture ToTexture(Device device)
+ {
+ if (!isCompressed)
+ {
+ return Texture.FromMemory(device, Data);
+ }
+
+ int width = BitConverter.ToInt16(Data, 8 + 4);
+ int height = BitConverter.ToInt16(Data, 8 + 6);
+ int bpp = Data[8 + 8];
+
+ int bytesPerPixel = bpp / 8;
+ int total = width * height * bytesPerPixel;
+
+ byte[] uncompressedTGA = new byte[18 + total + 26];
+ Array.Copy(Data, uncompressedTGA, 18);
+ uncompressedTGA[2] = 0x02;
+
+ int srcIdx = 18, dstIdx = 18;
+ for (int end = 18 + total; dstIdx < end; )
+ {
+ byte packetHdr = Data[srcIdx++];
+ int packetType = packetHdr & (1 << 7);
+
+ if (packetType != 0)
+ {
+ int repeat = (packetHdr & ~(1 << 7)) + 1;
+ for (int j = 0; j < repeat; j++)
+ {
+ Array.Copy(Data, srcIdx, uncompressedTGA, dstIdx, bytesPerPixel);
+ dstIdx += bytesPerPixel;
+ }
+ srcIdx += bytesPerPixel;
+ }
+ else
+ {
+ int len = ((packetHdr & ~(1 << 7)) + 1) * bytesPerPixel;
+ Array.Copy(Data, srcIdx, uncompressedTGA, dstIdx, len);
+ srcIdx += len;
+ dstIdx += len;
+ }
+ }
+
+ Array.Copy(Data, srcIdx, uncompressedTGA, dstIdx, 26);
+ return Texture.FromMemory(device, uncompressedTGA);
+ }
+ }
+
+ public interface ImportedAnimation
+ {
+ // List TrackList { get; set; }
+ }
+
+ public abstract class ImportedAnimationTrackContainer : ImportedAnimation where TrackType : ImportedAnimationTrack
+ {
+ public List TrackList { get; set; }
+ }
+
+ public class ImportedKeyframedAnimation : ImportedAnimationTrackContainer { }
+
+ public class ImportedSampledAnimation : ImportedAnimationTrackContainer { }
+
+ public abstract class ImportedAnimationTrack
+ {
+ public string Name { get; set; }
+ }
+
+ public class ImportedAnimationKeyframedTrack : ImportedAnimationTrack
+ {
+ public ImportedAnimationKeyframe[] Keyframes { get; set; }
+ }
+
+ public class ImportedAnimationKeyframe
+ {
+ public Vector3 Scaling { get; set; }
+ public Quaternion Rotation { get; set; }
+ public Vector3 Translation { get; set; }
+ }
+
+ public class ImportedAnimationSampledTrack : ImportedAnimationTrack
+ {
+ public Vector3?[] Scalings;
+ public Quaternion?[] Rotations;
+ public Vector3?[] Translations;
+ }
+
+ public class ImportedMorph
+ {
+ ///
+ /// Target mesh name
+ ///
+ public string Name { get; set; }
+ public List KeyframeList { get; set; }
+ public List MorphedVertexIndices { get; set; }
+ }
+
+ public class ImportedMorphKeyframe
+ {
+ ///
+ /// Blend shape name
+ ///
+ public string Name { get; set; }
+ public List VertexList { get; set; }
+ }
+
+ public static class ImportedHelpers
+ {
+ public static ImportedFrame FindFrame(String name, ImportedFrame root)
+ {
+ ImportedFrame frame = root;
+ if ((frame != null) && (frame.Name == name))
+ {
+ return frame;
+ }
+
+ for (int i = 0; i < root.Count; i++)
+ {
+ if ((frame = FindFrame(name, root[i])) != null)
+ {
+ return frame;
+ }
+ }
+
+ return null;
+ }
+
+ public static ImportedMesh FindMesh(String frameName, List importedMeshList)
+ {
+ foreach (ImportedMesh mesh in importedMeshList)
+ {
+ if (mesh.Name == frameName)
+ {
+ return mesh;
+ }
+ }
+
+ return null;
+ }
+
+ public static ImportedMaterial FindMaterial(String name, List importedMats)
+ {
+ foreach (ImportedMaterial mat in importedMats)
+ {
+ if (mat.Name == name)
+ {
+ return mat;
+ }
+ }
+
+ return null;
+ }
+
+ public static ImportedTexture FindTexture(string name, List importedTextureList)
+ {
+ if (name == null || name == string.Empty)
+ {
+ return null;
+ }
+
+ foreach (ImportedTexture tex in importedTextureList)
+ {
+ if (tex.Name == name)
+ {
+ return tex;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/SB3UtilityPP/JchStream.cs b/SB3UtilityPP/JchStream.cs
new file mode 100644
index 0000000..7e8d84f
--- /dev/null
+++ b/SB3UtilityPP/JchStream.cs
@@ -0,0 +1,418 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.IO.Compression;
+
+namespace SB3Utility
+{
+ public class JchStream : Stream
+ {
+ private Stream stream;
+ private CompressionMode mode;
+ private bool leaveOpen;
+
+ private List readBuf;
+ private bool hasReadHeader;
+ private byte compressByte;
+ private int copyPos;
+ private int fileSize;
+ private int totalRead;
+
+ private MemoryStream writeBuf;
+ private int[] byteCount;
+
+ public JchStream(Stream stream, CompressionMode mode)
+ : this(stream, mode, false)
+ {
+ }
+
+ public JchStream(Stream stream, CompressionMode mode, bool leaveOpen)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException("stream");
+ }
+
+ if (mode == CompressionMode.Decompress)
+ {
+ if (!stream.CanRead)
+ {
+ throw new ArgumentException("The base stream is not writeable.", "stream");
+ }
+ }
+ else if (mode == CompressionMode.Compress)
+ {
+ if (!stream.CanWrite)
+ {
+ throw new ArgumentException("The base stream is not readable.", "stream");
+ }
+ }
+ else
+ {
+ throw new ArgumentException("Enum value was out of legal range.", "mode");
+ }
+
+ this.stream = stream;
+ this.mode = mode;
+ this.leaveOpen = leaveOpen;
+ if (mode == CompressionMode.Decompress)
+ {
+ readBuf = new List(0x20000);
+ hasReadHeader = false;
+ totalRead = 0;
+ }
+ else
+ {
+ writeBuf = new MemoryStream();
+ byteCount = new int[256];
+ }
+ }
+
+ public Stream BaseStream
+ {
+ get
+ {
+ return stream;
+ }
+ }
+
+ public override bool CanRead
+ {
+ get
+ {
+ return (stream != null) && (mode == CompressionMode.Decompress);
+ }
+ }
+
+ public override bool CanWrite
+ {
+ get
+ {
+ return (stream != null) && (mode == CompressionMode.Compress);
+ }
+ }
+
+ public override bool CanSeek
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+ set
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+ }
+
+ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ if (stream == null)
+ {
+ throw new ObjectDisposedException("stream");
+ }
+ if (mode != CompressionMode.Decompress)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+ return base.BeginRead(buffer, offset, count, callback, state);
+ }
+
+ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ if (stream == null)
+ {
+ throw new ObjectDisposedException("stream");
+ }
+ if (mode != CompressionMode.Compress)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+ return base.BeginWrite(buffer, offset, count, callback, state);
+ }
+
+ public override void Close()
+ {
+ if (stream != null)
+ {
+ if (mode == CompressionMode.Decompress)
+ {
+ readBuf = null;
+ }
+ else
+ {
+ int maxByteCount = 0;
+ for (int i = 0; i < byteCount.Length; i++)
+ {
+ if (byteCount[i] > maxByteCount)
+ {
+ compressByte = (byte)i;
+ maxByteCount = byteCount[i];
+ }
+ }
+
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(compressByte);
+ writer.Write((int)writeBuf.Length);
+
+ byte[] inputBuf = writeBuf.GetBuffer();
+ int bufLength = (int)writeBuf.Length;
+ int windowStart = 0;
+ int windowEnd = 0;
+ for (int i = 0; i < bufLength; )
+ {
+ Match maxMatch = new Match();
+ int maxMatchLength = 0;
+ for (int windowIdx = windowStart; windowIdx < windowEnd; windowIdx++)
+ {
+ int blockSize = 0;
+ int numBlocks = 1;
+ if (inputBuf[i] == inputBuf[windowIdx])
+ {
+ for (int k = 1; k < 0xFF; k++)
+ {
+ blockSize++;
+ int offset = i + k;
+ if ((offset >= bufLength) || ((windowIdx + k) >= windowEnd) || (inputBuf[offset] != inputBuf[windowIdx + k]))
+ {
+ break;
+ }
+ }
+
+ for (int k = 1; k < 0xFF; k++)
+ {
+ bool addBlock = true;
+ for (int m = 0; m < blockSize; m++)
+ {
+ int offset = i + (blockSize * k) + m;
+ if ((offset >= bufLength) || (inputBuf[offset] != inputBuf[windowIdx + m]))
+ {
+ addBlock = false;
+ break;
+ }
+ }
+ if (addBlock)
+ {
+ numBlocks++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ int matchLength = blockSize * numBlocks;
+ if (matchLength > maxMatchLength)
+ {
+ maxMatch.blockSize = blockSize;
+ maxMatch.numBlocks = numBlocks;
+ maxMatch.distance = i - windowIdx;
+ maxMatch.length = matchLength;
+ maxMatchLength = matchLength;
+ }
+ }
+ }
+
+ int windowOffset = 1;
+ if (maxMatchLength > 4)
+ {
+ writer.Write(compressByte);
+ if (maxMatch.distance == compressByte)
+ {
+ writer.Write((byte)0);
+ }
+ else
+ {
+ writer.Write((byte)maxMatch.distance);
+ }
+ writer.Write((byte)maxMatch.blockSize);
+ writer.Write((byte)maxMatch.numBlocks);
+ i += maxMatch.length;
+ windowOffset = maxMatch.length;
+ }
+ else
+ {
+ WriteSingleByte(writer, inputBuf[i], compressByte);
+ i++;
+ }
+
+ windowEnd += windowOffset;
+ int windowSize = windowEnd - windowStart;
+ if (windowSize >= 0xFF)
+ {
+ windowStart += windowSize - 0xFF;
+ }
+ }
+
+ writeBuf.Close();
+ writeBuf = null;
+ byteCount = null;
+ }
+
+ if (!leaveOpen)
+ {
+ stream.Close();
+ }
+
+ stream = null;
+ }
+ }
+
+ private class Match
+ {
+ public int distance;
+ public int length;
+ public int blockSize;
+ public int numBlocks;
+ }
+
+ private static void WriteSingleByte(BinaryWriter writer, byte b, byte compressedByte)
+ {
+ writer.Write(b);
+
+ if (b == compressedByte)
+ {
+ writer.Write(b);
+ }
+ }
+
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ return base.EndRead(asyncResult);
+ }
+
+ public override void EndWrite(IAsyncResult asyncResult)
+ {
+ base.EndWrite(asyncResult);
+ }
+
+ public override void Flush()
+ {
+ if (stream == null)
+ {
+ throw new ObjectDisposedException("stream");
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (stream == null)
+ {
+ throw new ObjectDisposedException("stream");
+ }
+ if (mode != CompressionMode.Decompress)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+
+ BinaryReader reader = new BinaryReader(stream);
+ if (!hasReadHeader)
+ {
+ compressByte = reader.ReadByte();
+ fileSize = reader.ReadInt32();
+
+ hasReadHeader = true;
+ }
+
+ if (totalRead >= fileSize)
+ {
+ return 0;
+ }
+
+ if ((totalRead + count) > fileSize)
+ {
+ count = fileSize - totalRead;
+ }
+
+ while (readBuf.Count - copyPos < count)
+ {
+ byte b = reader.ReadByte();
+ if (b == compressByte)
+ {
+ byte distance = reader.ReadByte();
+ if (distance == compressByte)
+ {
+ readBuf.Add(distance);
+ }
+ else
+ {
+ if (distance == 0)
+ {
+ distance = compressByte;
+ }
+ byte blockSize = reader.ReadByte();
+ byte numBlocks = reader.ReadByte();
+ byte[] block = new byte[blockSize];
+ readBuf.CopyTo(readBuf.Count - distance, block, 0, blockSize);
+
+ for (int i = 0; i < numBlocks; i++)
+ {
+ readBuf.AddRange(block);
+ }
+ }
+ }
+ else
+ {
+ readBuf.Add(b);
+ }
+ }
+
+ readBuf.CopyTo(copyPos, buffer, offset, count);
+ copyPos += count;
+ if (copyPos > 255)
+ {
+ int remove = copyPos - 255;
+ readBuf.RemoveRange(0, remove);
+ copyPos -= remove;
+ }
+
+ totalRead += count;
+ return count;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (stream == null)
+ {
+ throw new ObjectDisposedException("stream");
+ }
+ if (mode != CompressionMode.Compress)
+ {
+ throw new NotSupportedException("This operation is not supported.");
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ byteCount[buffer[offset + i]]++;
+ }
+
+ writeBuf.Write(buffer, offset, count);
+ }
+ }
+}
diff --git a/SB3UtilityPP/MemSubfile .cs b/SB3UtilityPP/MemSubfile .cs
new file mode 100644
index 0000000..53bc339
--- /dev/null
+++ b/SB3UtilityPP/MemSubfile .cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace SB3Utility
+{
+ public class MemSubfile: IReadFile, IWriteFile
+ {
+ public string Name { get; set; }
+ public MemoryStream data;
+
+ public MemSubfile(MemoryStream mem, string name)
+ {
+ data = mem;
+ Name = name;
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(CreateReadStream()))
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ byte[] buf;
+ while ((buf = reader.ReadBytes(Utility.BufSize)).Length == Utility.BufSize)
+ {
+ writer.Write(buf);
+ }
+ writer.Write(buf);
+ }
+ }
+
+ public Stream CreateReadStream()
+ {
+ MemoryStream mem = new MemoryStream();
+ data.Position = 0;
+ data.CopyTo(mem);
+ mem.Position = 0;
+ return mem;
+ }
+ }
+}
diff --git a/SB3UtilityPP/ObjChildren.cs b/SB3UtilityPP/ObjChildren.cs
new file mode 100644
index 0000000..de9891c
--- /dev/null
+++ b/SB3UtilityPP/ObjChildren.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SB3Utility
+{
+ public interface IObjChild
+ {
+ dynamic Parent { get; set; }
+ }
+
+ public abstract class ObjChildren : IEnumerable where T : IObjChild
+ {
+ protected List children;
+
+ public T this[int i]
+ {
+ get { return (T)children[i]; }
+ }
+
+ public int Count
+ {
+ get { return children.Count; }
+ }
+
+ public void InitChildren(int count)
+ {
+ children = new List(count);
+ }
+
+ public void AddChild(T obj)
+ {
+ children.Add(obj);
+ obj.Parent = this;
+ }
+
+ public void InsertChild(int i, T obj)
+ {
+ children.Insert(i, obj);
+ obj.Parent = this;
+ }
+
+ public void RemoveChild(T obj)
+ {
+ obj.Parent = null;
+ children.Remove(obj);
+ }
+
+ public void RemoveChild(int i)
+ {
+ children[i].Parent = null;
+ children.RemoveAt(i);
+ }
+
+ public int IndexOf(T obj)
+ {
+ return children.IndexOf(obj);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return children.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/SB3UtilityPP/Properties/AssemblyInfo.cs b/SB3UtilityPP/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3e28e51
--- /dev/null
+++ b/SB3UtilityPP/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SB3UtilityPP")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SB3UtilityPP")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("51277abf-8d1b-4a71-8f03-fec314a4c16d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SB3UtilityPP/Report.cs b/SB3UtilityPP/Report.cs
new file mode 100644
index 0000000..5a7a475
--- /dev/null
+++ b/SB3UtilityPP/Report.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace SB3Utility
+{
+ public static class Report
+ {
+ public static event Action Log;
+ public static event Action Status;
+
+ public static void ReportLog(string msg)
+ {
+ if (Log != null)
+ {
+ Log(msg);
+ }
+ }
+
+ public static void ReportStatus(string msg)
+ {
+ if (Status != null)
+ {
+ Status(msg);
+ }
+ }
+ }
+}
diff --git a/SB3UtilityPP/SB3UtilityPP.csproj b/SB3UtilityPP/SB3UtilityPP.csproj
new file mode 100644
index 0000000..e7d8b73
--- /dev/null
+++ b/SB3UtilityPP/SB3UtilityPP.csproj
@@ -0,0 +1,94 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {5647B104-8F58-4867-B849-DAC65D5243BD}
+ Library
+ Properties
+ SB3UtilityPP
+ SB3UtilityPP
+ v4.0
+ 512
+
+
+ true
+ bin\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ true
+ true
+ SB3UtilityPP.ruleset
+
+
+ ..\..\bin\plugins\
+ TRACE
+ true
+ none
+ AnyCPU
+ prompt
+ true
+ true
+ false
+ true
+ SB3UtilityPP.ruleset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+ Code
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SB3UtilityPP/SB3UtilityPP.ruleset b/SB3UtilityPP/SB3UtilityPP.ruleset
new file mode 100644
index 0000000..d7eb362
--- /dev/null
+++ b/SB3UtilityPP/SB3UtilityPP.ruleset
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SB3UtilityPP/Structures.cs b/SB3UtilityPP/Structures.cs
new file mode 100644
index 0000000..8f5e682
--- /dev/null
+++ b/SB3UtilityPP/Structures.cs
@@ -0,0 +1,900 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using SlimDX;
+
+namespace SB3Utility
+{
+ public interface IObjInfo
+ {
+ void WriteTo(Stream stream);
+ }
+
+ #region .xx
+ public class xxFrame : ObjChildren, IObjChild, IObjInfo, IComparable
+ {
+ public string Name { get; set; }
+ public Matrix Matrix { get; set; }
+ public byte[] Unknown1 { get; set; }
+ public BoundingBox Bounds { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public string Name2 { get; set; }
+ public xxMesh Mesh { get; set; }
+
+ public dynamic Parent { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(children.Count);
+ writer.Write(Matrix);
+ writer.Write(Unknown1);
+
+ if (Mesh == null)
+ {
+ writer.Write((int)0);
+ }
+ else
+ {
+ writer.Write(Mesh.SubmeshList.Count);
+ }
+
+ writer.Write(Bounds.Minimum);
+ writer.Write(Bounds.Maximum);
+ writer.Write(Unknown2);
+
+ if (Name2 != null)
+ {
+ writer.WriteName(Name2);
+ }
+
+ if (Mesh != null)
+ {
+ Mesh.WriteTo(stream);
+ }
+
+ for (int i = 0; i < children.Count; i++)
+ {
+ children[i].WriteTo(stream);
+ }
+ }
+
+ public xxFrame Clone(bool mesh, bool childFrames, int[] matTranslations)
+ {
+ xxFrame frame = new xxFrame();
+ frame.InitChildren(children.Count);
+ frame.Name = Name;
+ frame.Matrix = Matrix;
+ frame.Unknown1 = (byte[])Unknown1.Clone();
+ frame.Bounds = Bounds;
+ frame.Unknown2 = (byte[])Unknown2.Clone();
+ if (Name2 != null)
+ {
+ frame.Name2 = Name2;
+ }
+
+ if (mesh && (Mesh != null))
+ {
+ frame.Mesh = Mesh.Clone(true, true, true);
+ if (matTranslations != null)
+ {
+ foreach (xxSubmesh submesh in frame.Mesh.SubmeshList)
+ {
+ if (submesh.MaterialIndex >= 0)
+ {
+ submesh.MaterialIndex = matTranslations[submesh.MaterialIndex];
+ }
+ }
+ }
+ }
+ if (childFrames)
+ {
+ for (int i = 0; i < children.Count; i++)
+ {
+ frame.AddChild(children[i].Clone(mesh, true, matTranslations));
+ }
+ }
+ return frame;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public int CompareTo(object obj)
+ {
+ xxFrame otherFrame = obj as xxFrame;
+ if (otherFrame == null)
+ {
+ return -100;
+ }
+ return Name.CompareTo(otherFrame.Name);
+ }
+ }
+
+ public class xxMesh : IObjInfo
+ {
+ public byte NumVector2PerVertex { get; set; }
+ public List SubmeshList { get; set; }
+ public byte[] VertexListDuplicateUnknown { get; set; }
+ public List VertexListDuplicate { get; set; }
+ public List BoneList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(NumVector2PerVertex);
+
+ for (int i = 0; i < SubmeshList.Count; i++)
+ {
+ SubmeshList[i].WriteTo(stream);
+ }
+
+ writer.Write((ushort)VertexListDuplicate.Count);
+ writer.Write(VertexListDuplicateUnknown);
+ for (int i = 0; i < VertexListDuplicate.Count; i++)
+ {
+ VertexListDuplicate[i].WriteTo(stream);
+ }
+
+ writer.Write(BoneList.Count);
+ for (int i = 0; i < BoneList.Count; i++)
+ {
+ BoneList[i].WriteTo(stream);
+ }
+ }
+
+ public xxMesh Clone(bool submeshes, bool vertexListDup, bool boneList)
+ {
+ xxMesh mesh = new xxMesh();
+ mesh.SubmeshList = new List(SubmeshList.Count);
+ mesh.NumVector2PerVertex = NumVector2PerVertex;
+ mesh.VertexListDuplicateUnknown = (byte[])VertexListDuplicateUnknown.Clone();
+ mesh.VertexListDuplicate = new List(VertexListDuplicate.Count);
+ mesh.BoneList = new List(BoneList.Count);
+
+ if (submeshes)
+ {
+ for (int i = 0; i < SubmeshList.Count; i++)
+ {
+ mesh.SubmeshList.Add(SubmeshList[i].Clone());
+ }
+ }
+ if (vertexListDup)
+ {
+ for (int i = 0; i < VertexListDuplicate.Count; i++)
+ {
+ mesh.VertexListDuplicate.Add(VertexListDuplicate[i].Clone());
+ }
+ }
+ if (boneList)
+ {
+ for (int i = 0; i < BoneList.Count; i++)
+ {
+ mesh.BoneList.Add(BoneList[i].Clone());
+ }
+ }
+ return mesh;
+ }
+ }
+
+ public class xxSubmesh : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public int MaterialIndex { get; set; }
+ public List FaceList { get; set; }
+ public List VertexList { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public List> Vector2Lists { get; set; }
+ public byte[] Unknown3 { get; set; }
+ public byte[] Unknown4 { get; set; }
+ public byte[] Unknown5 { get; set; }
+ public byte[] Unknown6 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Unknown1);
+ writer.Write(MaterialIndex);
+
+ writer.Write(FaceList.Count * 3);
+ for (int i = 0; i < FaceList.Count; i++)
+ {
+ FaceList[i].WriteTo(stream);
+ }
+
+ writer.Write(VertexList.Count);
+ for (int i = 0; i < VertexList.Count; i++)
+ {
+ VertexList[i].WriteTo(stream);
+ }
+
+ writer.WriteIfNotNull(Unknown2);
+
+ if (Vector2Lists != null)
+ {
+ for (int i = 0; i < Vector2Lists.Count; i++)
+ {
+ for (int j = 0; j < Vector2Lists[i].Count; j++)
+ {
+ writer.Write(Vector2Lists[i][j]);
+ }
+ }
+ }
+
+ writer.WriteIfNotNull(Unknown3);
+ writer.WriteIfNotNull(Unknown4);
+ writer.WriteIfNotNull(Unknown5);
+ writer.WriteIfNotNull(Unknown6);
+ }
+
+ public xxSubmesh Clone()
+ {
+ xxSubmesh submesh = new xxSubmesh();
+ submesh.Unknown1 = (byte[])Unknown1.Clone();
+ submesh.MaterialIndex = MaterialIndex;
+ submesh.FaceList = new List(FaceList.Count);
+ for (int i = 0; i < FaceList.Count; i++)
+ {
+ submesh.FaceList.Add(FaceList[i].Clone());
+ }
+ submesh.VertexList = new List(VertexList.Count);
+ for (int i = 0; i < VertexList.Count; i++)
+ {
+ submesh.VertexList.Add(VertexList[i].Clone());
+ }
+
+ submesh.Unknown2 = Unknown2.CloneIfNotNull();
+
+ if (Vector2Lists != null)
+ {
+ submesh.Vector2Lists = new List>(Vector2Lists.Count);
+ for (int i = 0; i < Vector2Lists.Count; i++)
+ {
+ List vectorList = new List(Vector2Lists[i].Count);
+ submesh.Vector2Lists.Add(vectorList);
+ for (int j = 0; j < Vector2Lists[i].Count; j++)
+ {
+ vectorList.Add(Vector2Lists[i][j]);
+ }
+ }
+ }
+
+ submesh.Unknown3 = Unknown3.CloneIfNotNull();
+ submesh.Unknown4 = Unknown4.CloneIfNotNull();
+ submesh.Unknown5 = Unknown5.CloneIfNotNull();
+ submesh.Unknown6 = Unknown6.CloneIfNotNull();
+
+ return submesh;
+ }
+ }
+
+ public class xxFace : IObjInfo
+ {
+ public ushort[] VertexIndices { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(VertexIndices);
+ }
+
+ public xxFace Clone()
+ {
+ xxFace face = new xxFace();
+ face.VertexIndices = (ushort[])VertexIndices.Clone();
+ return face;
+ }
+ }
+
+ public class xxVertexUShort : xxVertex
+ {
+ protected override void WriteIndex(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write((ushort)Index);
+ }
+
+ protected override xxVertex InitClone()
+ {
+ return new xxVertexUShort();
+ }
+ }
+
+ public class xxVertexInt : xxVertex
+ {
+ protected override void WriteIndex(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Index);
+ }
+
+ protected override xxVertex InitClone()
+ {
+ return new xxVertexInt();
+ }
+ }
+
+ public abstract class xxVertex : IObjInfo
+ {
+ public int Index { get; set; }
+ public Vector3 Position { get; set; }
+ public float[] Weights3 { get; set; }
+ public byte[] BoneIndices { get; set; }
+ public Vector3 Normal { get; set; }
+ public float[] UV { get; set; }
+ public byte[] Unknown1 { get; set; }
+
+ public float[] Weights4(bool skinned)
+ {
+ float[] w3 = Weights3;
+ float[] w4;
+ if (skinned)
+ {
+ w4 = new float[] { w3[0], w3[1], w3[2], 1f - (w3[0] + w3[1] + w3[2]) };
+ if (w4[3] < 0)
+ {
+ w4[3] = 0;
+ }
+ }
+ else
+ {
+ w4 = new float[4];
+ }
+
+ return w4;
+ }
+
+ protected abstract void WriteIndex(Stream stream);
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ WriteIndex(stream);
+ writer.Write(Position);
+ writer.Write(Weights3);
+ writer.Write(BoneIndices);
+ writer.Write(Normal);
+ writer.Write(UV);
+
+ writer.WriteIfNotNull(Unknown1);
+ }
+
+ protected abstract xxVertex InitClone();
+
+ public xxVertex Clone()
+ {
+ xxVertex vertex = InitClone();
+ vertex.Index = Index;
+ vertex.Position = Position;
+ vertex.Weights3 = (float[])Weights3.Clone();
+ vertex.BoneIndices = (byte[])BoneIndices.Clone();
+ vertex.Normal = Normal;
+ vertex.UV = (float[])UV.Clone();
+ vertex.Unknown1 = Unknown1.CloneIfNotNull();
+ return vertex;
+ }
+ }
+
+ public class xxBone : IObjInfo
+ {
+ public string Name { get; set; }
+ public int Index { get; set; }
+ public Matrix Matrix { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(Index);
+ writer.Write(Matrix);
+ }
+
+ public xxBone Clone()
+ {
+ xxBone bone = new xxBone();
+ bone.Name = Name;
+ bone.Index = Index;
+ bone.Matrix = Matrix;
+ return bone;
+ }
+ }
+
+ public class xxMaterial : IObjInfo
+ {
+ public string Name { get; set; }
+ public Color4 Diffuse { get; set; }
+ public Color4 Ambient { get; set; }
+ public Color4 Specular { get; set; }
+ public Color4 Emissive { get; set; }
+ public float Power { get; set; }
+ public xxMaterialTexture[] Textures { get; set; }
+ public byte[] Unknown1 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(Diffuse);
+ writer.Write(Ambient);
+ writer.Write(Specular);
+ writer.Write(Emissive);
+ writer.Write(Power);
+
+ for (int i = 0; i < Textures.Length; i++)
+ {
+ Textures[i].WriteTo(stream);
+ }
+
+ writer.Write(Unknown1);
+ }
+
+ public xxMaterial Clone()
+ {
+ xxMaterial mat = new xxMaterial();
+ mat.Name = Name;
+ mat.Diffuse = Diffuse;
+ mat.Ambient = Ambient;
+ mat.Specular = Specular;
+ mat.Emissive = Emissive;
+ mat.Power = Power;
+ mat.Textures = new xxMaterialTexture[Textures.Length];
+ for (int i = 0; i < Textures.Length; i++)
+ {
+ mat.Textures[i] = Textures[i].Clone();
+ }
+ mat.Unknown1 = (byte[])Unknown1.Clone();
+ return mat;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ public class xxMaterialTexture : IObjInfo
+ {
+ public string Name { get; set; }
+ public byte[] Unknown1 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(Unknown1);
+ }
+
+ public xxMaterialTexture Clone()
+ {
+ xxMaterialTexture matTex = new xxMaterialTexture();
+ matTex.Name = Name;
+ matTex.Unknown1 = (byte[])Unknown1.Clone();
+ return matTex;
+ }
+ }
+
+ public class xxTexture : IObjInfo
+ {
+ public string Name { get; set; }
+ public byte[] Unknown1 { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public int Depth { get; set; }
+ public int MipLevels { get; set; }
+ public int Format { get; set; }
+ public int ResourceType { get; set; }
+ public int ImageFileFormat { get; set; }
+ public byte Checksum { get; set; }
+ public byte[] ImageData { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(Unknown1);
+
+ writer.Write(Width);
+ writer.Write(Height);
+ writer.Write(Depth);
+ writer.Write(MipLevels);
+ writer.Write(Format);
+ writer.Write(ResourceType);
+ writer.Write(ImageFileFormat);
+ writer.Write(Checksum);
+
+ writer.Write(ImageData.Length);
+ writer.Write(ImageData);
+ }
+
+ public xxTexture Clone()
+ {
+ xxTexture tex = new xxTexture();
+ tex.Name = Name;
+ tex.Unknown1 = (byte[])Unknown1.Clone();
+ tex.Width = Width;
+ tex.Height = Height;
+ tex.Depth = Depth;
+ tex.MipLevels = MipLevels;
+ tex.Format = Format;
+ tex.ResourceType = ResourceType;
+ tex.ImageFileFormat = ImageFileFormat;
+ tex.Checksum = Checksum;
+ tex.ImageData = (byte[])ImageData.Clone();
+ return tex;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+ #endregion
+
+ #region .xa
+ public class xaMaterialSection : IObjInfo
+ {
+ public List MaterialList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(MaterialList.Count);
+ for (int i = 0; i < MaterialList.Count; i++)
+ {
+ MaterialList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaMaterial : IObjInfo
+ {
+ public string Name { get; set; }
+ public List ColorList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+
+ writer.Write(ColorList.Count);
+ for (int i = 0; i < ColorList.Count; i++)
+ {
+ ColorList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaMaterialColor : IObjInfo
+ {
+ public Color4 Diffuse { get; set; }
+ public Color4 Ambient { get; set; }
+ public Color4 Specular { get; set; }
+ public Color4 Emissive { get; set; }
+ public float Power { get; set; }
+ public byte[] Unknown1 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteUnnegated(Diffuse);
+ writer.WriteUnnegated(Ambient);
+ writer.WriteUnnegated(Specular);
+ writer.WriteUnnegated(Emissive);
+ writer.Write(Power);
+ writer.Write(Unknown1);
+ }
+ }
+
+ public class xaSection2 : IObjInfo
+ {
+ public List ItemList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(ItemList.Count);
+ for (int i = 0; i < ItemList.Count; i++)
+ {
+ ItemList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaSection2Item : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public string Name { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public byte[] Unknown3 { get; set; }
+ public byte[] Unknown4 { get; set; }
+ public List ItemBlockList { get; set; }
+ public byte[] Unknown5 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Unknown1);
+ writer.WriteName(Name);
+ writer.Write(Unknown2);
+ writer.Write(Unknown3);
+ writer.Write(ItemBlockList.Count);
+ writer.Write(Unknown4);
+
+ for (int i = 0; i < ItemBlockList.Count; i++)
+ {
+ ItemBlockList[i].WriteTo(stream);
+ }
+
+ writer.Write(Unknown5);
+ }
+ }
+
+ public class xaSection2ItemBlock : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public byte[] Unknown2 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Unknown1);
+ writer.Write(Unknown2);
+ }
+ }
+
+ public class xaMorphSection : IObjInfo
+ {
+ public List IndexSetList { get; set; }
+ public List KeyframeList { get; set; }
+ public List ClipList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(IndexSetList.Count);
+ for (int i = 0; i < IndexSetList.Count; i++)
+ {
+ IndexSetList[i].WriteTo(stream);
+ }
+
+ writer.Write(KeyframeList.Count);
+ for (int i = 0; i < KeyframeList.Count; i++)
+ {
+ KeyframeList[i].WriteTo(stream);
+ }
+
+ writer.Write(ClipList.Count);
+ for (int i = 0; i < ClipList.Count; i++)
+ {
+ ClipList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaMorphIndexSet : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public ushort[] MeshIndices { get; set; }
+ public ushort[] MorphIndices { get; set; }
+ public string Name { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(Unknown1);
+
+ writer.Write(MeshIndices.Length);
+ for (int i = 0; i < MeshIndices.Length; i++)
+ {
+ writer.Write(MeshIndices[i]);
+ }
+ for (int i = 0; i < MorphIndices.Length; i++)
+ {
+ writer.Write(MorphIndices[i]);
+ }
+
+ writer.WriteName(Name);
+ }
+ }
+
+ public class xaMorphKeyframe : IObjInfo
+ {
+ public List PositionList { get; set; }
+ public List NormalList { get; set; }
+ public string Name { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(PositionList.Count);
+ for (int i = 0; i < PositionList.Count; i++)
+ {
+ writer.Write(PositionList[i]);
+ }
+ for (int i = 0; i < NormalList.Count; i++)
+ {
+ writer.Write(NormalList[i]);
+ }
+
+ writer.WriteName(Name);
+ }
+ }
+
+ public class xaMorphClip : IObjInfo
+ {
+ public string MeshName { get; set; }
+ public string Name { get; set; }
+ public List KeyframeRefList { get; set; }
+ public byte[] Unknown1 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(MeshName);
+ writer.WriteName(Name);
+
+ writer.Write(KeyframeRefList.Count);
+ for (int i = 0; i < KeyframeRefList.Count; i++)
+ {
+ KeyframeRefList[i].WriteTo(stream);
+ }
+
+ writer.Write(Unknown1);
+ }
+ }
+
+ public class xaMorphKeyframeRef : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public int Index { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public string Name { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Unknown1);
+ writer.Write(Index);
+ writer.Write(Unknown2);
+ writer.WriteName(Name);
+ }
+ }
+
+ public class xaSection4 : IObjInfo
+ {
+ public List> ItemListList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ writer.Write(ItemListList.Count);
+ for (int i = 0; i < ItemListList.Count; i++)
+ {
+ writer.Write(ItemListList[i].Count);
+ for (int j = 0; j < ItemListList[i].Count; j++)
+ {
+ ItemListList[i][j].WriteTo(stream);
+ }
+ }
+ }
+ }
+
+ public class xaSection4Item : IObjInfo
+ {
+ public byte[] Unknown1 { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public byte[] Unknown3 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Unknown1);
+ writer.Write(Unknown2);
+ writer.Write(Unknown3);
+ }
+ }
+
+ public class xaAnimationSection : IObjInfo
+ {
+ public List ClipList { get; set; }
+ public List TrackList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ for (int i = 0; i < ClipList.Count; i++)
+ {
+ ClipList[i].WriteTo(stream);
+ }
+
+ writer.Write(TrackList.Count);
+ for (int i = 0; i < TrackList.Count; i++)
+ {
+ TrackList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaAnimationClip : IObjInfo
+ {
+ public string Name { get; set; }
+ public float Speed { get; set; }
+ public byte[] Unknown1 { get; set; }
+ public float Start { get; set; }
+ public float End { get; set; }
+ public byte[] Unknown2 { get; set; }
+ public byte[] Unknown3 { get; set; }
+ public byte[] Unknown4 { get; set; }
+ public int Next { get; set; }
+ public byte[] Unknown5 { get; set; }
+ public byte[] Unknown6 { get; set; }
+ public byte[] Unknown7 { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteNameWithoutLength(Name, 64);
+ writer.Write(Speed);
+ writer.Write(Unknown1);
+ writer.Write(Start);
+ writer.Write(End);
+ writer.Write(Unknown2);
+ writer.Write(Unknown3);
+ writer.Write(Unknown4);
+ writer.Write(Next);
+ writer.Write(Unknown5);
+ writer.Write(Unknown6);
+ writer.Write(Unknown7);
+ }
+ }
+
+ public class xaAnimationTrack : IObjInfo
+ {
+ public string Name { get; set; }
+ public byte[] Unknown1 { get; set; }
+ public List KeyframeList { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.WriteName(Name);
+ writer.Write(KeyframeList.Count);
+ writer.Write(Unknown1);
+
+ for (int i = 0; i < KeyframeList.Count; i++)
+ {
+ KeyframeList[i].WriteTo(stream);
+ }
+ }
+ }
+
+ public class xaAnimationKeyframe : IObjInfo
+ {
+ public int Index { get; set; }
+ public Quaternion Rotation { get; set; }
+ public byte[] Unknown1 { get; set; }
+ public Vector3 Translation { get; set; }
+ public Vector3 Scaling { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Index);
+ writer.Write(Rotation);
+ writer.Write(Unknown1);
+ writer.Write(Translation);
+ writer.Write(Scaling);
+ }
+ }
+ #endregion
+}
diff --git a/SB3UtilityPP/Subfile.cs b/SB3UtilityPP/Subfile.cs
new file mode 100644
index 0000000..ac09929
--- /dev/null
+++ b/SB3UtilityPP/Subfile.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace SB3Utility
+{
+ public class Subfile: IReadFile, IWriteFile
+ {
+ public string Name { get; set; }
+ public string path;
+
+ public Subfile(string filepath)
+ {
+ path = filepath;
+ Name = path.Remove(0, path.LastIndexOf('\\')+1);
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(CreateReadStream()))
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ byte[] buf;
+ while ((buf = reader.ReadBytes(Utility.BufSize)).Length == Utility.BufSize)
+ {
+ writer.Write(buf);
+ }
+ writer.Write(buf);
+ }
+ }
+
+ public Stream CreateReadStream()
+ {
+ return new FileStream(path, FileMode.Open);
+ }
+ }
+}
diff --git a/SB3UtilityPP/Utility.cs b/SB3UtilityPP/Utility.cs
new file mode 100644
index 0000000..914161e
--- /dev/null
+++ b/SB3UtilityPP/Utility.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Globalization;
+using System.IO;
+using System.Windows.Forms;
+using SlimDX;
+using SlimDX.Direct3D9;
+using System.Configuration;
+using System.Reflection;
+
+namespace SB3Utility
+{
+ public static partial class Utility
+ {
+ public static Encoding EncodingShiftJIS = Encoding.GetEncoding("Shift-JIS");
+ public static CultureInfo CultureUS = new CultureInfo("en-US");
+ public const uint BufSize = 0x400000;
+
+ public static string GetDestFile(DirectoryInfo dir, string prefix, string ext)
+ {
+ string dest = dir.FullName + @"\" + prefix;
+ int destIdx = 0;
+ while (File.Exists(dest + destIdx + ext))
+ {
+ destIdx++;
+ }
+ dest += destIdx + ext;
+ return dest;
+ }
+
+ public static T[] Convert(object[] array)
+ {
+ T[] newArray = new T[array.Length];
+ for (int i = 0; i < array.Length; i++)
+ {
+ newArray[i] = (T)array[i];
+ }
+ return newArray;
+ }
+
+ public static float[] ConvertToFloatArray(object[] array)
+ {
+ float[] newArray = new float[array.Length];
+ for (int i = 0; i < array.Length; i++)
+ {
+ newArray[i] = (float)(double)array[i];
+ }
+ return newArray;
+ }
+
+ public static string DecryptName(byte[] buf)
+ {
+ if (buf.Length < 1)
+ {
+ return String.Empty;
+ }
+
+ byte[] decrypt = new byte[buf.Length];
+ for (int i = 0; i < decrypt.Length; i++)
+ {
+ decrypt[i] = (byte)~buf[i];
+ }
+ return EncodingShiftJIS.GetString(decrypt).TrimEnd(new char[] { '\0' });
+ }
+
+ public static byte[] EncryptName(string name)
+ {
+ if (name.Length < 1)
+ {
+ return new byte[0];
+ }
+
+ byte[] buf = EncodingShiftJIS.GetBytes(name);
+ byte[] encrypt = new byte[buf.Length + 1];
+ buf.CopyTo(encrypt, 0);
+ for (int i = 0; i < encrypt.Length; i++)
+ {
+ encrypt[i] = (byte)~encrypt[i];
+ }
+ return encrypt;
+ }
+
+ public static byte[] EncryptName(string name, int length)
+ {
+ byte[] encrypt = new byte[length];
+ EncodingShiftJIS.GetBytes(name).CopyTo(encrypt, 0);
+ for (int i = 0; i < encrypt.Length; i++)
+ {
+ encrypt[i] = (byte)~encrypt[i];
+ }
+ return encrypt;
+ }
+
+ public static float ParseFloat(string s)
+ {
+ return Single.Parse(s, CultureUS);
+ }
+
+ public static void ReportException(Exception ex)
+ {
+ Exception inner = ex;
+ while (inner != null)
+ {
+ Report.ReportLog(inner.Message);
+ inner = inner.InnerException;
+ }
+ }
+
+ public static string BytesToString(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ return String.Empty;
+ }
+
+ StringBuilder s = new StringBuilder(bytes.Length * 3 + 1);
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ for (int j = 0; (j < 3) && (i < bytes.Length); i++, j++)
+ {
+ s.Append(bytes[i].ToString("X2") + "-");
+ }
+ if (i < bytes.Length)
+ {
+ s.Append(bytes[i].ToString("X2") + " ");
+ }
+ }
+ if (s.Length > 0)
+ {
+ s.Remove(s.Length - 1, 1);
+ }
+ return s.ToString();
+ }
+
+ public static byte[] StringToBytes(string s)
+ {
+ StringBuilder sb = new StringBuilder(s.Length);
+ for (int i = 0; i < s.Length; i++)
+ {
+ if (s[i].IsHex())
+ {
+ sb.Append(s[i]);
+ }
+ }
+ if ((sb.Length % 2) != 0)
+ {
+ throw new Exception("Hex string doesn't have an even number of digits");
+ }
+
+ string byteString = sb.ToString();
+ byte[] b = new byte[byteString.Length / 2];
+ for (int i = 0; i < b.Length; i++)
+ {
+ b[i] = Byte.Parse(byteString.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier);
+ }
+ return b;
+ }
+
+ public static bool ImageSupported(string ext)
+ {
+ string lower = ext.ToLowerInvariant();
+
+ string[] names = Enum.GetNames(typeof(ImageFileFormat));
+ for (int i = 0; i < names.Length; i++)
+ {
+ if (lower == ("." + names[i].ToLowerInvariant()))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool ValidFilePath(string path)
+ {
+ try
+ {
+ FileInfo file = new FileInfo(path);
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+}
diff --git a/SB3UtilityPP/WakeariStream.cs b/SB3UtilityPP/WakeariStream.cs
new file mode 100644
index 0000000..8de60a9
--- /dev/null
+++ b/SB3UtilityPP/WakeariStream.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace SB3Utility
+{
+ public class WakeariStream : CryptoStream
+ {
+ private int lastOffset = 0;
+
+ public byte[] LastBytes { get; protected set; }
+
+ public WakeariStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
+ : base(stream, transform, mode)
+ {
+ LastBytes = new byte[20];
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (count >= 20)
+ {
+ Array.Copy(buffer, offset + count - 20, LastBytes, 0, 20);
+ lastOffset = 20;
+ }
+ else
+ {
+ int total = lastOffset + count;
+ if (total > 20)
+ {
+ int shift = total - 20;
+ int copy = 20 - count;
+ for (int i = 0; i < copy; i++)
+ {
+ LastBytes[i] = LastBytes[shift + i];
+ }
+
+ for (int i = count; i < count; i++)
+ {
+ LastBytes[i + copy] = buffer[offset + i];
+ }
+ lastOffset = 20;
+ }
+ else
+ {
+ for (int i = 0; i < count; i++)
+ {
+ LastBytes[i + lastOffset] = buffer[offset + i];
+ }
+ lastOffset += count;
+ }
+ }
+
+ base.Write(buffer, offset, count);
+ }
+
+ public override void Close()
+ {
+ LastBytes = null;
+ base.Close();
+ }
+ }
+}
diff --git a/SB3UtilityPP/Workspace.cs b/SB3UtilityPP/Workspace.cs
new file mode 100644
index 0000000..5e95ebc
--- /dev/null
+++ b/SB3UtilityPP/Workspace.cs
@@ -0,0 +1,254 @@
+using System;
+using System.Collections.Generic;
+
+namespace SB3Utility
+{
+ public class WorkspaceMesh : ImportedMesh
+ {
+ protected class AdditionalSubmeshOptions
+ {
+ public bool Enabled = true;
+ public bool ReplaceOriginal = true;
+ }
+ protected Dictionary SubmeshOptions { get; set; }
+
+ public WorkspaceMesh(ImportedMesh importedMesh) :
+ base()
+ {
+ this.Name = importedMesh.Name;
+ this.SubmeshList = importedMesh.SubmeshList;
+ this.BoneList = importedMesh.BoneList;
+
+ this.SubmeshOptions = new Dictionary(importedMesh.SubmeshList.Count);
+ foreach (ImportedSubmesh submesh in importedMesh.SubmeshList)
+ {
+ AdditionalSubmeshOptions options = new AdditionalSubmeshOptions();
+ this.SubmeshOptions.Add(submesh, options);
+ }
+ }
+
+ public bool isSubmeshEnabled(ImportedSubmesh submesh)
+ {
+ AdditionalSubmeshOptions options;
+ if (this.SubmeshOptions.TryGetValue(submesh, out options))
+ {
+ return options.Enabled;
+ }
+ throw new Exception("Submesh not found");
+ }
+
+ public void setSubmeshEnabled(ImportedSubmesh submesh, bool enabled)
+ {
+ AdditionalSubmeshOptions options;
+ if (this.SubmeshOptions.TryGetValue(submesh, out options))
+ {
+ options.Enabled = enabled;
+ return;
+ }
+ throw new Exception("Submesh not found");
+ }
+
+ public bool isSubmeshReplacingOriginal(ImportedSubmesh submesh)
+ {
+ AdditionalSubmeshOptions options;
+ if (this.SubmeshOptions.TryGetValue(submesh, out options))
+ {
+ return options.ReplaceOriginal;
+ }
+ throw new Exception("Submesh not found");
+ }
+
+ public void setSubmeshReplacingOriginal(ImportedSubmesh submesh, bool replaceOriginal)
+ {
+ AdditionalSubmeshOptions options;
+ if (this.SubmeshOptions.TryGetValue(submesh, out options))
+ {
+ options.ReplaceOriginal = replaceOriginal;
+ return;
+ }
+ throw new Exception("Submesh not found");
+ }
+ }
+
+ public class WorkspaceMorph : ImportedMorph
+ {
+ protected class AdditionalMorphKeyframeOptions
+ {
+ public bool Enabled = true;
+ public string NewName;
+ }
+ protected Dictionary MorphKeyframeOptions { get; set; }
+
+ public WorkspaceMorph(ImportedMorph importedMorph) :
+ base()
+ {
+ this.Name = importedMorph.Name;
+ this.KeyframeList = importedMorph.KeyframeList;
+ this.MorphedVertexIndices = importedMorph.MorphedVertexIndices;
+
+ this.MorphKeyframeOptions = new Dictionary(importedMorph.KeyframeList.Count);
+ foreach (ImportedMorphKeyframe keyframe in importedMorph.KeyframeList)
+ {
+ AdditionalMorphKeyframeOptions options = new AdditionalMorphKeyframeOptions();
+ this.MorphKeyframeOptions.Add(keyframe, options);
+ }
+ }
+
+ public bool isMorphKeyframeEnabled(ImportedMorphKeyframe keyframe)
+ {
+ AdditionalMorphKeyframeOptions options;
+ if (this.MorphKeyframeOptions.TryGetValue(keyframe, out options))
+ {
+ return options.Enabled;
+ }
+ throw new Exception("Morph keyframe not found");
+ }
+
+ public void setMorphKeyframeEnabled(ImportedMorphKeyframe keyframe, bool enabled)
+ {
+ AdditionalMorphKeyframeOptions options;
+ if (this.MorphKeyframeOptions.TryGetValue(keyframe, out options))
+ {
+ options.Enabled = enabled;
+ return;
+ }
+ throw new Exception("Morph keyframe not found");
+ }
+
+ public string getMorphKeyframeNewName(ImportedMorphKeyframe keyframe)
+ {
+ AdditionalMorphKeyframeOptions options;
+ if (this.MorphKeyframeOptions.TryGetValue(keyframe, out options))
+ {
+ return options.NewName != null ? options.NewName : String.Empty;
+ }
+ throw new Exception("Morph keyframe not found");
+ }
+
+ public void setMorphKeyframeNewName(ImportedMorphKeyframe keyframe, string newName)
+ {
+ AdditionalMorphKeyframeOptions options;
+ if (this.MorphKeyframeOptions.TryGetValue(keyframe, out options))
+ {
+ options.NewName = newName;
+ return;
+ }
+ throw new Exception("Morph keyframe not found");
+ }
+ }
+
+ public class WorkspaceAnimation
+ {
+ public ImportedAnimation importedAnimation { get; protected set; }
+
+ protected class AdditionalTrackOptions
+ {
+ public bool Enabled = true;
+ }
+ protected Dictionary TrackOptions { get; set; }
+
+ public WorkspaceAnimation(ImportedAnimation importedAnimation) :
+ base()
+ {
+ this.importedAnimation = importedAnimation;
+
+ if (importedAnimation is ImportedKeyframedAnimation)
+ {
+ List importedTrackList = ((ImportedKeyframedAnimation)importedAnimation).TrackList;
+ this.TrackOptions = new Dictionary(importedTrackList.Count);
+ for (int i = 0; i < importedTrackList.Count; i++)
+ {
+ ImportedAnimationTrack track = importedTrackList[i];
+ AdditionalTrackOptions options = new AdditionalTrackOptions();
+ this.TrackOptions.Add(track, options);
+ }
+ }
+ else if (importedAnimation is ImportedSampledAnimation)
+ {
+ List importedTrackList = ((ImportedSampledAnimation)importedAnimation).TrackList;
+ this.TrackOptions = new Dictionary(importedTrackList.Count);
+ for (int i = 0; i < importedTrackList.Count; i++)
+ {
+ ImportedAnimationTrack track = importedTrackList[i];
+ AdditionalTrackOptions options = new AdditionalTrackOptions();
+ this.TrackOptions.Add(track, options);
+ }
+ }
+ }
+
+ public void SetAnimation(ImportedAnimation importedAnimation)
+ {
+ if (importedAnimation is ImportedKeyframedAnimation)
+ {
+ List importedTrackList = ((ImportedKeyframedAnimation)importedAnimation).TrackList;
+ for (int i = 0; i < importedTrackList.Count; i++)
+ {
+ ImportedAnimationTrack track = importedTrackList[i];
+
+ foreach (KeyValuePair pair in this.TrackOptions)
+ {
+ if (pair.Key.Name == track.Name)
+ {
+ this.TrackOptions.Remove(pair.Key);
+ this.TrackOptions.Add(track, pair.Value);
+ track = null;
+ break;
+ }
+ }
+ if (track != null)
+ {
+ AdditionalTrackOptions options = new AdditionalTrackOptions();
+ this.TrackOptions.Add(track, options);
+ }
+ }
+ }
+ else if (importedAnimation is ImportedSampledAnimation)
+ {
+ List importedTrackList = ((ImportedSampledAnimation)importedAnimation).TrackList;
+ for (int i = 0; i < importedTrackList.Count; i++)
+ {
+ ImportedAnimationTrack track = importedTrackList[i];
+
+ foreach (KeyValuePair pair in this.TrackOptions)
+ {
+ if (pair.Key.Name == track.Name)
+ {
+ this.TrackOptions.Remove(pair.Key);
+ this.TrackOptions.Add(track, pair.Value);
+ track = null;
+ break;
+ }
+ }
+ if (track != null)
+ {
+ AdditionalTrackOptions options = new AdditionalTrackOptions();
+ this.TrackOptions.Add(track, options);
+ }
+ }
+ }
+
+ this.importedAnimation = importedAnimation;
+ }
+
+ public bool isTrackEnabled(ImportedAnimationTrack track)
+ {
+ AdditionalTrackOptions options;
+ if (this.TrackOptions.TryGetValue(track, out options))
+ {
+ return options.Enabled;
+ }
+ throw new Exception("Track " + track.Name + " not found");
+ }
+
+ public void setTrackEnabled(ImportedAnimationTrack track, bool enabled)
+ {
+ AdditionalTrackOptions options;
+ if (this.TrackOptions.TryGetValue(track, out options))
+ {
+ options.Enabled = enabled;
+ return;
+ }
+ throw new Exception("Track " + track.Name + " not found");
+ }
+ }
+}
diff --git a/SB3UtilityPP/lstParser.cs b/SB3UtilityPP/lstParser.cs
new file mode 100644
index 0000000..40af01e
--- /dev/null
+++ b/SB3UtilityPP/lstParser.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SB3Utility
+{
+ public class lstParser : IWriteFile
+ {
+ public string Name { get; set; }
+ public string Text { get; set; }
+
+ public lstParser(Stream stream, string name)
+ {
+ this.Name = name;
+
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ List byteList = new List();
+ try
+ {
+ for (; true; )
+ {
+ byteList.Add(reader.ReadByte());
+ }
+ }
+ catch (EndOfStreamException) { }
+ this.Text += Utility.EncodingShiftJIS.GetString(byteList.ToArray());
+ }
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ writer.Write(Utility.EncodingShiftJIS.GetBytes(this.Text.ToCharArray()));
+ }
+ }
+}
diff --git a/SB3UtilityPP/ppFormat.cs b/SB3UtilityPP/ppFormat.cs
new file mode 100644
index 0000000..49832d0
--- /dev/null
+++ b/SB3UtilityPP/ppFormat.cs
@@ -0,0 +1,1256 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.IO.Compression;
+using System.Security.Cryptography;
+using SlimDX;
+using SlimDX.Direct3D9;
+
+namespace SB3Utility
+{
+ public enum ppFormatIdx
+ {
+ SB3,
+ SMFigure,
+ SMTrial,
+ SMRetail,
+ AG3Welcome,
+ AG3Retail,
+ DG,
+ HakoTrial,
+ HakoRetail,
+ SMSweets,
+ EskMate,
+ AHMFigure,
+ AHMTrial,
+ AHMRetail,
+ YuushaTrial,
+ YuushaRetail,
+ RGF,
+ SM2Trial,
+ SM2Retail,
+ SBZ,
+ CharacolleAlicesoft,
+ CharacolleBaseson,
+ CharacolleKey,
+ CharacolleLumpOfSugar,
+ CharacolleWhirlpool,
+ BestCollection,
+ AATrial,
+ AARetail,
+ AAJCH,
+ Wakeari,
+ LoveGirl,
+ Hero,
+ HET,
+ HETDTL,
+ PPD,
+ Musumakeup,
+ ImmoralWard,
+ RealPlayTrial,
+ RealPlay,
+ AA2
+ }
+
+ public abstract class ppFormat
+ {
+ public static ppFormat[] Array = new ppFormat[] {
+ new ppFormat_SB3(),
+ new ppFormat_SMFigure(),
+ new ppFormat_SMTrial(),
+ new ppFormat_SMRetail(),
+ new ppFormat_AG3Welcome(),
+ new ppFormat_AG3Retail(),
+ new ppFormat_DG(),
+ new ppFormat_HakoTrial(),
+ new ppFormat_HakoRetail(),
+ new ppFormat_SMSweets(),
+ new ppFormat_EskMate(),
+ new ppFormat_AHMFigure(),
+ new ppFormat_AHMTrial(),
+ new ppFormat_AHMRetail(),
+ new ppFormat_YuushaTrial(),
+ new ppFormat_YuushaRetail(),
+ new ppFormat_RGF(),
+ new ppFormat_SM2Trial(),
+ new ppFormat_SM2Retail(),
+ new ppFormat_SBZ(),
+ new ppFormat_CharacolleAlicesoft(),
+ new ppFormat_CharacolleBaseson(),
+ new ppFormat_CharacolleKey(),
+ new ppFormat_CharacolleLumpOfSugar(),
+ new ppFormat_CharacolleWhirlpool(),
+ new ppFormat_BestCollection(),
+ new ppFormat_AATrial(),
+ new ppFormat_AARetail(),
+ new ppFormat_AAJCH(),
+ new ppFormat_Wakeari(),
+ new ppFormat_LoveGirl(),
+ new ppFormat_Hero(),
+ new ppFormat_HET(),
+ new ppFormat_HETDTL(),
+ new ppFormat_PPD(),
+ new ppFormat_Musumakeup(),
+ new ppFormat_ImmoralWard(),
+ new ppFormat_RealPlayTrial(),
+ new ppFormat_RealPlay(),
+ new ppFormat_AA2()
+ };
+
+ public abstract Stream ReadStream(Stream stream);
+ public abstract Stream WriteStream(Stream stream);
+ public abstract object FinishWriteTo(Stream stream);
+
+ private string Name { get; set; }
+ public ppHeader ppHeader { get; protected set; }
+ public ppFormatIdx ppFormatIdx { get; protected set; }
+
+ protected ppFormat(string name, ppHeader header, ppFormatIdx idx)
+ {
+ this.Name = name;
+ this.ppHeader = header;
+ this.ppFormatIdx = idx;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ #region GetFormat
+ public static ppFormat GetFormat(string path, out ppHeader header)
+ {
+ header = null;
+ for (int i = 0; i < ppHeader.Array.Length; i++)
+ {
+ try
+ {
+ if ((header = ppHeader.Array[i].TryHeader(path)) != null)
+ {
+ break;
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ ppFormat resultFormat = null;
+ if (header != null)
+ {
+ if (header.ppFormats.Length == 1)
+ {
+ resultFormat = header.ppFormats[0];
+ }
+ else
+ {
+ List subfiles = header.ReadHeader(path, null);
+ for (int i = 0; i < subfiles.Count; i++)
+ {
+ if ((resultFormat = TryFile((ppSubfile)subfiles[i], header.ppFormats)) != null)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return resultFormat;
+ }
+
+ private static ppFormat TryFile(ppSubfile subfile, ppFormat[] formats)
+ {
+ Func tryFunc = null;
+
+ string ext = Path.GetExtension(subfile.Name).ToLower();
+ if (ext == ".xx")
+ {
+ tryFunc = new Func(TryFileXX);
+ }
+ else if (ext == ".xa")
+ {
+ tryFunc = new Func(TryFileXA);
+ }
+ else if (ext == ".svi")
+ {
+ tryFunc = new Func(TryFileSVI);
+ }
+ else if (ext == ".sviex")
+ {
+ tryFunc = new Func(TryFileSVIEX);
+ }
+ else if (ext == ".bmp")
+ {
+ tryFunc = new Func(TryFileBMP);
+ }
+ else if (ext == ".tga")
+ {
+ tryFunc = new Func(TryFileTGA);
+ }
+ else if (ext == ".ema")
+ {
+ tryFunc = new Func(TryFileEMA);
+ }
+ else if (Utility.ImageSupported(ext))
+ {
+ tryFunc = new Func(TryFileImage);
+ }
+ else if (ext == ".lst")
+ {
+ tryFunc = new Func(TryFileLst);
+ }
+ else if (ext == ".wav" || ext == ".ogg")
+ {
+ tryFunc = new Func(TryFileSound);
+ }
+
+ if (tryFunc != null)
+ {
+ for (int i = 0; i < formats.Length; i++)
+ {
+ subfile.ppFormat = formats[i];
+ if (tryFunc(subfile))
+ {
+ return subfile.ppFormat;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static bool TryFileXX(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] buf = reader.ReadBytes(5);
+ if ((buf[0] >= 0x01) && (BitConverter.ToInt32(buf, 1) == 0))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool TryFileXA(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte type = reader.ReadByte();
+ if ((type == 0x00) || (type == 0x01) || (type == 0x02) || (type == 0x03))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool TryFileBMP(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] buf = reader.ReadBytes(2);
+ if ((buf[0] == 'B') && (buf[1] == 'M'))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool TryFileTGA(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] buf = reader.ReadBytes(8);
+ int bufSum = 0;
+ for (int i = 0; i < buf.Length; i++)
+ {
+ bufSum += buf[i];
+ }
+
+ if ((buf[2] == 0x02) && (bufSum == 0x02))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool TryFileEMA(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ reader.ReadBytes(8);
+ string imgExt = Encoding.ASCII.GetString(reader.ReadBytes(4)).ToLower();
+ if ((imgExt == ".bmp") || (imgExt == ".tga"))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static bool TryFileImage(ppSubfile subfile)
+ {
+ try
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] data = reader.ReadToEnd();
+ var imgInfo = ImageInformation.FromMemory(data);
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool TryFileLst(ppSubfile subfile)
+ {
+ try
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] buf = reader.ReadBytes(128);
+ string ascii = Utility.EncodingShiftJIS.GetString(buf);
+
+ int i = 0, numbersInLine = 0, stringsInLine = 0;
+ while (i < buf.Length)
+ {
+ if (i < ascii.Length && ascii[i] == '-')
+ i++;
+ int startPos = i;
+ while (i < ascii.Length && char.IsDigit(ascii[i]))
+ i++;
+ if (i > startPos)
+ numbersInLine++;
+ if (ascii[i] == '\t')
+ {
+ i++;
+ continue;
+ }
+ else if (ascii[i] == '\r')
+ {
+ return (numbersInLine > 0 || stringsInLine > 0) && ascii[++i] == '\n';
+ }
+ else
+ {
+ startPos = i;
+ while (i < ascii.Length)
+ {
+ if (ascii[i] == '\t')
+ {
+ i++;
+ break;
+ }
+ if (ascii[i] == '\r')
+ {
+ if (ascii[++i] == '\n')
+ break;
+ return false;
+ }
+ if (char.IsControl(ascii[i]) || (byte)ascii[i] >= (byte)'\xe0')
+ return false;
+ i++;
+ }
+ if (i > startPos)
+ stringsInLine++;
+ }
+ }
+ if (numbersInLine == 0 && stringsInLine == 0)
+ return false;
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool TryFileSound(ppSubfile subfile)
+ {
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ byte[] buf = reader.ReadBytes(4);
+ if (buf[0] == 'O' && buf[1] == 'g' && buf[2] == 'g' && buf[3] == 'S' ||
+ buf[0] == 'R' && buf[1] == 'I' && buf[2] == 'F' && buf[3] == 'F')
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool TryFileSVI(ppSubfile subfile)
+ {
+ if (subfile.size < 32)
+ {
+ return false;
+ }
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ int version = reader.ReadInt32();
+ if (version != 100)
+ {
+ return false;
+ }
+ int lenFirstMesh = reader.ReadInt32();
+ if (lenFirstMesh < 1 || lenFirstMesh > 50)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool TryFileSVIEX(ppSubfile subfile)
+ {
+ if (subfile.size < 32)
+ {
+ return false;
+ }
+ using (BinaryReader reader = new BinaryReader(subfile.CreateReadStream()))
+ {
+ int version = reader.ReadInt32();
+ if (version != 100)
+ {
+ return false;
+ }
+ int numSections = reader.ReadInt32();
+ if (numSections < 1 || numSections > 1000)
+ {
+ return false;
+ }
+ version = reader.ReadInt32();
+ if (version != 100)
+ {
+ return false;
+ }
+ int lenFirstMesh = reader.ReadInt32();
+ if (lenFirstMesh < 1 || lenFirstMesh > 50)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ #endregion
+ }
+
+ public abstract class ppFormatCrypto : ppFormat
+ {
+ protected abstract ICryptoTransform CryptoTransform();
+
+ protected ppFormatCrypto(string name, ppHeader header, ppFormatIdx idx) : base(name, header, idx) { }
+
+ public override Stream ReadStream(Stream stream)
+ {
+ return new CryptoStream(stream, CryptoTransform(), CryptoStreamMode.Read);
+ }
+
+ public override Stream WriteStream(Stream stream)
+ {
+ return new CryptoStream(stream, CryptoTransform(), CryptoStreamMode.Write);
+ }
+
+ public override object FinishWriteTo(Stream stream)
+ {
+ ((CryptoStream)stream).FlushFinalBlock();
+ return null;
+ }
+ }
+
+ public class ppFormat_SB3 : ppFormatCrypto
+ {
+ public ppFormat_SB3() : base("OS2 / RL / SB3", ppHeader.Array[(int)ppHeaderIdx.SB3], ppFormatIdx.SB3) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformSB3();
+ }
+ }
+
+ public class ppFormat_SMFigure : ppFormatCrypto
+ {
+ public ppFormat_SMFigure() : base("SM Figure", ppHeader.Array[(int)ppHeaderIdx.SMFigure], ppFormatIdx.SMFigure) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x58,0x62,0x86,0xD2,0x3B,0x2F,0xC4,0x5F,
+ 0xEE,0x58,0x76,0x2D,0xB4,0x02,0x02,0xCD,
+ 0x0A,0x08,0x40,0x30,0x08,0x66,0x1D,0xE8,
+ 0x9B,0xA6,0x61,0xCB,0x63,0xF3,0xF3,0xB4 });
+ }
+ }
+
+ public abstract class ppFormat_SMHeader : ppFormatCrypto
+ {
+ public ppFormat_SMHeader(string name, ppFormatIdx idx)
+ : base(name, ppHeader.Array[(int)ppHeaderIdx.SMRetail], idx)
+ {
+ }
+ }
+
+ public abstract class ppFormat_AG3Header : ppFormatCrypto
+ {
+ public ppFormat_AG3Header(string name, ppFormatIdx idx)
+ : base(name, ppHeader.Array[(int)ppHeaderIdx.AG3], idx)
+ {
+ }
+ }
+
+ public class ppFormat_SMTrial : ppFormat_SMHeader
+ {
+ public ppFormat_SMTrial() : base("SM Trial", ppFormatIdx.SMTrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x7C,0xF2,0x35,0x77,0x54,0x18,0x20,0x6E,
+ 0x9C,0x7B,0x9E,0x85,0x1F,0xB5,0x71,0x40,
+ 0x25,0xAD,0x71,0x43,0x64,0x20,0x20,0x7E,
+ 0xCF,0xE3,0x85,0xC0,0x41,0xDE,0x23,0x12 });
+ }
+ }
+
+ public class ppFormat_SMRetail : ppFormat_SMHeader
+ {
+ public ppFormat_SMRetail() : base("SM Retail", ppFormatIdx.SMRetail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x1E,0x5D,0x13,0xDD,0x7D,0x4C,0x4F,0xA7,
+ 0xDB,0xA7,0x29,0x14,0x10,0xF8,0xC0,0xBE,
+ 0x44,0x7F,0xD0,0x63,0x1C,0x22,0x7C,0x9F,
+ 0xE8,0xB9,0xF8,0xBE,0x58,0xB3,0xEF,0xF4 });
+ }
+ }
+
+ public class ppFormat_AG3Welcome : ppFormat_SMHeader
+ {
+ public ppFormat_AG3Welcome() : base("AG3 Welcome", ppFormatIdx.AG3Welcome) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xE5,0x77,0x64,0x05,0xD2,0x37,0x4D,0x2E,
+ 0xB7,0x4A,0xB7,0x2B,0x22,0x70,0xF1,0xD6,
+ 0xC7,0xE7,0x61,0x6D,0x10,0xED,0xF5,0xC1,
+ 0xD9,0x08,0x28,0xEC,0xE2,0x09,0xEA,0xD7 });
+ }
+ }
+
+ public class ppFormat_AG3Retail : ppFormat_AG3Header
+ {
+ public ppFormat_AG3Retail() : base("AG3 Retail", ppFormatIdx.AG3Retail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0x00CA, 0x006E, 0x000D, 0x00B3 },
+ new int[] { 0x009C, 0x0036, 0x001E, 0x00E8 });
+ }
+ }
+
+ public class ppFormat_DG : ppFormat_AG3Header
+ {
+ public ppFormat_DG() : base("Digital Girl", ppFormatIdx.DG) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0x2110, 0x8BD0, 0x5063, 0xD8F6 },
+ new int[] { 0x7311, 0xA15A, 0x9132, 0xA8E9 });
+ }
+ }
+
+ public class ppFormat_HakoTrial : ppFormat_SMHeader
+ {
+ public ppFormat_HakoTrial() : base("Hako Trial", ppFormatIdx.HakoTrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x11,0x73,0x10,0x21,0x5A,0xA1,0xD0,0x8B,
+ 0x32,0x91,0x63,0x50,0xE9,0xA8,0xF6,0xD8,
+ 0x40,0x72,0x80,0xF9,0xEC,0x79,0x6E,0x8D,
+ 0x36,0x72,0x2B,0xA1,0x76,0xB6,0x67,0x92 });
+ }
+ }
+
+ public class ppFormat_HakoRetail : ppFormat_AG3Header
+ {
+ public ppFormat_HakoRetail() : base("Hako Retail", ppFormatIdx.HakoRetail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0xCBEE, 0x1675, 0x3533, 0x4CE6 },
+ new int[] { 0x2F68, 0x936D, 0xF40D, 0x0539 });
+ }
+ }
+
+ public class ppFormat_SMSweets : ppFormat_AG3Header
+ {
+ public ppFormat_SMSweets() : base("SM Sweets", ppFormatIdx.SMSweets) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0x3F86, 0xB8D5, 0x4AB4, 0x06F4 },
+ new int[] { 0x70F6, 0x078A, 0x2F26, 0x3572 });
+ }
+ }
+
+ public class ppFormat_EskMate : ppFormat_SMHeader
+ {
+ public ppFormat_EskMate() : base("Esk Mate", ppFormatIdx.EskMate) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xE9,0xEC,0xFC,0x9F,0x67,0x4A,0x91,0x8D,
+ 0x72,0x4F,0x5F,0xAE,0xBB,0xA5,0xF7,0x0A,
+ 0x12,0xB9,0x03,0xC5,0x4E,0x1C,0xE3,0x7A,
+ 0x7E,0xF4,0x05,0x48,0x51,0x18,0x16,0x99 });
+ }
+ }
+
+ public class ppFormat_AHMFigure : ppFormat_SMHeader
+ {
+ public ppFormat_AHMFigure() : base("AHM Figure", ppFormatIdx.AHMFigure) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xAB,0x2C,0xC4,0x4E,0x7B,0xDF,0xBD,0x17,
+ 0xDC,0x2E,0x23,0x1E,0x4B,0xE5,0x80,0x3C,
+ 0x93,0xB1,0x1D,0x8C,0x81,0x36,0xB3,0x88,
+ 0x35,0x2D,0x30,0x4B,0x10,0x66,0xC8,0xE6 });
+ }
+ }
+
+ public class ppFormat_AHMTrial : ppFormat_SMHeader
+ {
+ public ppFormat_AHMTrial() : base("AHM Trial", ppFormatIdx.AHMTrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x67,0xF9,0x30,0x5A,0x09,0xAB,0xF5,0x60,
+ 0xD6,0x9F,0xFD,0x93,0xBA,0x9C,0xF5,0x60,
+ 0x11,0x6A,0xBA,0x79,0x4C,0x41,0x4A,0x8D,
+ 0xC7,0xBA,0xBB,0x9C,0x26,0x34,0x0F,0xEF });
+ }
+ }
+
+ public class ppFormat_AHMRetail : ppFormat_AG3Header
+ {
+ public ppFormat_AHMRetail() : base("AHM Retail", ppFormatIdx.AHMRetail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0x717E, 0x0E78, 0xAFE7, 0x8FA7 },
+ new int[] { 0x9E1F, 0xC5E3, 0x0008, 0x713A });
+ }
+ }
+
+ public class ppFormat_YuushaTrial : ppFormat_SMHeader
+ {
+ public ppFormat_YuushaTrial() : base("Yuusha MIK Trial", ppFormatIdx.YuushaTrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xD1,0xEC,0x08,0xA1,0x48,0x7F,0xD6,0x8F,
+ 0xAD,0x34,0xB2,0xA2,0x35,0x4D,0x55,0xD1,
+ 0x1F,0xC1,0xB4,0x47,0x2F,0x54,0x89,0x24,
+ 0x61,0xCE,0xB7,0xA5,0x22,0x80,0x05,0x29 });
+ }
+ }
+
+ public class ppFormat_YuushaRetail : ppFormat_AG3Header
+ {
+ public ppFormat_YuushaRetail() : base("Yuusha Retail", ppFormatIdx.YuushaRetail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0xA82B, 0x1EF2, 0x1DDD, 0xC895 },
+ new int[] { 0xD47E, 0x764F, 0x416F, 0xC7BF });
+ }
+ }
+
+ public class ppFormat_RGF : ppFormat_SMHeader
+ {
+ public ppFormat_RGF() : base("RGF", ppFormatIdx.RGF) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x58,0x62,0x86,0xD2,0x3B,0x2F,0xC4,0x5F,
+ 0xEE,0x58,0x76,0x2D,0xB4,0x02,0x02,0xCD,
+ 0x0A,0x08,0x40,0x30,0x08,0x66,0x1D,0xE8,
+ 0x9B,0xA6,0x61,0xCB,0x63,0xF3,0xF3,0xB4 });
+ }
+ }
+
+ public class ppFormat_SM2Trial : ppFormat_SMHeader
+ {
+ public ppFormat_SM2Trial() : base("SM2 Trial", ppFormatIdx.SM2Trial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x85,0x45,0x1B,0xBC,0x6E,0xDA,0x0E,0xA6,
+ 0x3F,0xCE,0x98,0x7D,0xD7,0x68,0xD9,0xEF,
+ 0xB4,0x3C,0x86,0xEF,0x4B,0x0D,0x08,0x28,
+ 0xF7,0xDE,0x12,0xA6,0xB7,0x0A,0x61,0x7A });
+ }
+ }
+
+ public class ppFormat_SM2Retail : ppFormat_AG3Header
+ {
+ public ppFormat_SM2Retail() : base("SM2 Retail", ppFormatIdx.SM2Retail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0xCE43, 0x6F31, 0xFC65, 0x9D2F },
+ new int[] { 0x4182, 0xC473, 0x9D75, 0xD5B7 });
+ }
+ }
+
+ public class ppFormat_SBZ : ppFormat_SMHeader
+ {
+ public ppFormat_SBZ() : base("SBZ", ppFormatIdx.SBZ) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x14,0x6F,0x07,0xB8,0x9A,0x0E,0x84,0x44,
+ 0x59,0x25,0x8E,0x18,0xBC,0x39,0x9E,0x5C,
+ 0x99,0x7A,0xA0,0x92,0xD4,0xB7,0xBC,0x55,
+ 0x1E,0x2E,0x88,0x27,0x14,0xA1,0xE6,0x27 });
+ }
+ }
+
+ public class ppFormat_CharacolleAlicesoft : ppFormat_SMHeader
+ {
+ public ppFormat_CharacolleAlicesoft() : base("Characolle AliceSoft", ppFormatIdx.CharacolleAlicesoft) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xEB,0xD6,0x6B,0x29,0x21,0x03,0xA9,0x2C,
+ 0x5F,0x5F,0xEF,0xBB,0xEC,0x10,0xFC,0x4C,
+ 0x51,0xED,0xD4,0xBE,0x99,0x4D,0x45,0x06,
+ 0x65,0x51,0x8E,0x25,0x33,0x5C,0x05,0x53 });
+ }
+ }
+
+ public class ppFormat_CharacolleBaseson : ppFormat_SMHeader
+ {
+ public ppFormat_CharacolleBaseson() : base("Characolle BaseSon", ppFormatIdx.CharacolleBaseson) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xD4,0xED,0x6B,0x29,0x0A,0x1A,0xA9,0x2C,
+ 0x48,0x76,0xEF,0xBB,0xD5,0x27,0xFC,0x4C,
+ 0x3A,0x04,0xD5,0xBE,0x82,0x64,0x45,0x06,
+ 0x4E,0x68,0x8E,0x25,0x1C,0x73,0x05,0x53 });
+ }
+ }
+
+ public class ppFormat_CharacolleKey : ppFormat_SMHeader
+ {
+ public ppFormat_CharacolleKey() : base("Characolle Key", ppFormatIdx.CharacolleKey) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xCE,0xFA,0x6A,0x29,0x04,0x27,0xA8,0x2C,
+ 0x42,0x83,0xEE,0xBB,0xCF,0x34,0xFB,0x4C,
+ 0x34,0x11,0xD4,0xBE,0x7C,0x71,0x44,0x06,
+ 0x48,0x75,0x8D,0x25,0x16,0x80,0x04,0x53 });
+ }
+ }
+
+ public class ppFormat_CharacolleLumpOfSugar : ppFormat_SMHeader
+ {
+ public ppFormat_CharacolleLumpOfSugar() : base("Characolle Lump of Sugar", ppFormatIdx.CharacolleLumpOfSugar) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xCD,0xF2,0x6B,0x29,0x03,0x1F,0xA9,0x2C,
+ 0x41,0x7B,0xEF,0xBB,0xCE,0x2C,0xFC,0x4C,
+ 0x33,0x09,0xD5,0xBE,0x7B,0x69,0x45,0x06,
+ 0x47,0x6D,0x8E,0x25,0x15,0x78,0x05,0x53 });
+ }
+ }
+
+ public class ppFormat_CharacolleWhirlpool : ppFormat_SMHeader
+ {
+ public ppFormat_CharacolleWhirlpool() : base("Characolle Whirlpool", ppFormatIdx.CharacolleWhirlpool) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xDF,0x9F,0x6B,0x29,0x15,0xCC,0xA8,0x2C,
+ 0x53,0x28,0xEF,0xBB,0xE0,0xD9,0xFB,0x4C,
+ 0x45,0xB6,0xD4,0xBE,0x8D,0x16,0x45,0x06,
+ 0x59,0x1A,0x8E,0x25,0x27,0x25,0x05,0x53 });
+ }
+ }
+
+ public class ppFormat_BestCollection : ppFormat_AG3Header
+ {
+ public ppFormat_BestCollection() : base("Best Collection", ppFormatIdx.BestCollection) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformTwoCodes(
+ new int[] { 0xCD8D, 0x28AA, 0x3A3F, 0xE801 },
+ new int[] { 0x55A7, 0x8D89, 0x1809, 0xF0AC });
+ }
+ }
+
+ public class ppFormat_AATrial : ppFormat_SMHeader
+ {
+ public ppFormat_AATrial() : base("AA Trial", ppFormatIdx.AATrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xC7,0xE7,0x61,0x6D,0x10,0xED,0xF5,0xC1,
+ 0xD9,0x08,0x28,0xEC,0xE2,0x09,0xEA,0xD7,
+ 0x17,0xB0,0x10,0xCE,0xA3,0x4B,0x82,0xE9,
+ 0x23,0x2F,0x62,0x93,0xB2,0x10,0xD8,0xE9 });
+ }
+ }
+
+ public class ppFormat_AARetail : ppFormat_SMHeader
+ {
+ public ppFormat_AARetail() : base("AA Retail", ppFormatIdx.AARetail) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x8B,0x11,0x73,0x10,0x50,0x5A,0xA1,0xD0,
+ 0xD8,0x32,0x91,0x63,0xF9,0xE9,0xA8,0xF6,
+ 0x8D,0x40,0x72,0x80,0xA1,0xEC,0x79,0x6E,
+ 0x92,0x36,0x72,0x2B,0x35,0x76,0xB6,0x67 });
+ }
+ }
+
+ public abstract class ppFormat_WakeariHeader : ppFormatCrypto
+ {
+ public ppFormat_WakeariHeader(string name, ppFormatIdx idx)
+ : base(name, ppHeader.Array[(int)ppHeaderIdx.Wakeari], idx)
+ {
+ }
+
+ public override Stream WriteStream(Stream stream)
+ {
+ return new WakeariStream(stream, CryptoTransform(), CryptoStreamMode.Write);
+ }
+
+ public override object FinishWriteTo(Stream stream)
+ {
+ base.FinishWriteTo(stream);
+
+ ppHeader_Wakeari.Metadata metadata = new ppHeader_Wakeari.Metadata();
+ metadata.LastBytes = ((WakeariStream)stream).LastBytes;
+ return metadata;
+ }
+ }
+
+ public class ppFormat_Wakeari : ppFormat_WakeariHeader
+ {
+ public ppFormat_Wakeari() : base("Wakeari", ppFormatIdx.Wakeari) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x9D,0xF0,0x69,0x74,0xD6,0xB7,0x1D,0x7B,
+ 0x78,0xF4,0xEA,0x65,0x29,0x5F,0x96,0xB4,
+ 0xEE,0xE6,0x83,0x0E,0x37,0xFF,0x8D,0xEF,
+ 0x1C,0x3C,0x36,0x9C,0xE6,0x1F,0x01,0x58 });
+ }
+ }
+
+ public class ppFormat_LoveGirl : ppFormat_WakeariHeader
+ {
+ public ppFormat_LoveGirl() : base("LoveGirl", ppFormatIdx.LoveGirl) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x71,0x01,0x9B,0x18,0xD7,0x25,0x0D,0xEB,
+ 0x02,0x0B,0x3D,0x80,0x0B,0x44,0x00,0xA9,
+ 0xFF,0x68,0xD5,0xAD,0xDF,0x65,0xC5,0xF8,
+ 0xEB,0x16,0x8D,0x10,0x20,0x18,0xFF,0xCB });
+ }
+ }
+
+ public class ppFormat_Hero : ppFormat_WakeariHeader
+ {
+ public ppFormat_Hero() : base("Hero", ppFormatIdx.Hero) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x10,0x0C,0x20,0x7F,0xFB,0x71,0x58,0x1D,
+ 0x32,0x11,0x27,0x4F,0x8F,0xA8,0x6A,0xA8,
+ 0x70,0x1D,0xED,0x66,0x0E,0x62,0x27,0x40,
+ 0x0A,0x9D,0x24,0x5F,0x49,0x85,0xC2,0xAA });
+ }
+ }
+
+ public class ppFormat_HET : ppFormat_WakeariHeader
+ {
+ public ppFormat_HET() : base("HET", ppFormatIdx.HET) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x54, 0x31, 0x47, 0x70, 0x3E, 0x12, 0xF3, 0xB2,
+ 0x25, 0xD2, 0xB6, 0x94, 0x44, 0x0F, 0x74, 0xA3,
+ 0xE0, 0xB7, 0x50, 0x05, 0x1E, 0x6D, 0xD7, 0xBB,
+ 0x17, 0x2E, 0x7A, 0x23, 0x2E, 0x34, 0x42, 0xC1 });
+ }
+ }
+
+ public class ppFormat_HETDTL : ppFormat_WakeariHeader
+ {
+ public ppFormat_HETDTL() : base("HET DTL", ppFormatIdx.HETDTL) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x97, 0x03, 0x8E, 0x6E, 0x57, 0xE0, 0xEC, 0xAC,
+ 0x9B, 0xE4, 0x78, 0xAB, 0x19, 0x18, 0x32, 0x31,
+ 0x23, 0xA5, 0x13, 0xAE, 0x72, 0xE8, 0xAB, 0xBA,
+ 0x23, 0xC9, 0x09, 0xBF, 0xFF, 0x49, 0xDB, 0xF3 });
+ }
+ }
+
+ public class ppFormat_PPD : ppFormat_WakeariHeader
+ {
+ public ppFormat_PPD() : base("PPD", ppFormatIdx.PPD) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x69, 0x55, 0x05, 0x29, 0x58, 0xD0, 0xA0, 0x11,
+ 0x7E, 0x59, 0x64, 0x6A, 0xDF, 0x21, 0x30, 0xEF,
+ 0x05, 0x29, 0x4A, 0xE4, 0xF6, 0x49, 0xAF, 0xF9,
+ 0x63, 0x77, 0xA8, 0x72, 0x22, 0x46, 0x9E, 0x85 });
+ }
+ }
+
+ public class ppFormat_Musumakeup : ppFormat_WakeariHeader
+ {
+ public ppFormat_Musumakeup() : base("Musumakeup", ppFormatIdx.Musumakeup) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xAD, 0x6B, 0x04, 0x3D, 0x86, 0x1B, 0x9B, 0x77,
+ 0xA6, 0xFB, 0x5F, 0x71, 0xB6, 0x01, 0x99, 0x17,
+ 0xB2, 0xB5, 0x61, 0xBE, 0x9E, 0xF0, 0xB5, 0xBF,
+ 0x75, 0xE3, 0xC9, 0x65, 0x63, 0x5C, 0x9E, 0x0A });
+ }
+ }
+
+ public class ppFormat_ImmoralWard : ppFormat_WakeariHeader
+ {
+ public ppFormat_ImmoralWard() : base("Immoral Ward", ppFormatIdx.ImmoralWard) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xE8, 0x7F, 0xB1, 0xF9, 0xD7, 0x1B, 0x35, 0x25,
+ 0x32, 0x55, 0x28, 0x4B, 0xEC, 0x61, 0xBD, 0x5F,
+ 0x57, 0xFE, 0xA5, 0xD7, 0x97, 0xFC, 0xDF, 0x0C,
+ 0x57, 0xFB, 0xD2, 0xE4, 0xBA, 0x75, 0x99, 0xAE });
+ }
+ }
+
+ public class ppFormat_RealPlayTrial : ppFormat_WakeariHeader
+ {
+ public ppFormat_RealPlayTrial() : base("Real Play Trial", ppFormatIdx.RealPlayTrial) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0xDE, 0xBE, 0xEB, 0xF6, 0xB8, 0xA3, 0x25, 0xA1,
+ 0xDA, 0xBB, 0xFC, 0xC1, 0x79, 0xD1, 0x71, 0x5B,
+ 0xF2, 0x9F, 0x8C, 0x1D, 0xBC, 0xDA, 0xAB, 0x42,
+ 0x9B, 0x6F, 0x19, 0xB6, 0x17, 0x0D, 0xE2, 0x7A });
+ }
+ }
+
+ public class ppFormat_RealPlay : ppFormat_WakeariHeader
+ {
+ public ppFormat_RealPlay() : base("Real Play", ppFormatIdx.RealPlay) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x38, 0x0C, 0x6B, 0xC1, 0x47, 0x75, 0x9D, 0xB8,
+ 0xAC, 0x56, 0x02, 0xF0, 0x8F, 0x14, 0x00, 0xE4,
+ 0xB0, 0x32, 0x9A, 0x2C, 0x4D, 0x0D, 0x37, 0x95,
+ 0x46, 0xC9, 0xA5, 0x77, 0x7F, 0x8B, 0x5D, 0x43 });
+ }
+ }
+
+ public class ppFormat_AA2 : ppFormat_WakeariHeader
+ {
+ public ppFormat_AA2() : base("AA2", ppFormatIdx.AA2) { }
+
+ protected override ICryptoTransform CryptoTransform()
+ {
+ return new CryptoTransformOneCode(new byte[] {
+ 0x4D, 0x2D, 0xBF, 0x6A, 0x5B, 0x4A, 0xCE, 0x9D,
+ 0xF4, 0xA5, 0x16, 0x87, 0x92, 0x9B, 0x13, 0x03,
+ 0x8F, 0x92, 0x3C, 0xF0, 0x98, 0x81, 0xDB, 0x8E,
+ 0x5F, 0xB4, 0x1D, 0x2B, 0x90, 0xC9, 0x65, 0x00 });
+ }
+ }
+
+ public class ppFormat_AAJCH : ppFormat
+ {
+ public ppFormat_AAJCH() : base("AA JCH", ppHeader.Array[(int)ppHeaderIdx.AAJCH], ppFormatIdx.AAJCH) { }
+
+ public override Stream ReadStream(Stream stream)
+ {
+ return new JchStream(stream, CompressionMode.Decompress, false);
+ }
+
+ public override Stream WriteStream(Stream stream)
+ {
+ return new JchStream(stream, CompressionMode.Compress, true);
+ }
+
+ public override object FinishWriteTo(Stream stream)
+ {
+ ((JchStream)stream).Close();
+ return null;
+ }
+ }
+
+ #region CryptoTransform
+ public class CryptoTransformSB3 : ICryptoTransform
+ {
+ #region ICryptoTransform Members
+ public bool CanReuseTransform
+ {
+ get { return true; }
+ }
+
+ public bool CanTransformMultipleBlocks
+ {
+ get { return true; }
+ }
+
+ public int InputBlockSize
+ {
+ get { return 1; }
+ }
+
+ public int OutputBlockSize
+ {
+ get { return 1; }
+ }
+
+ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ for (int i = 0; i < inputCount; i++)
+ {
+ outputBuffer[outputOffset + i] = (byte)(~inputBuffer[inputOffset + i] + 1);
+ }
+ return inputCount;
+ }
+
+ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ byte[] outputBuffer = new byte[inputCount];
+ for (int i = 0; i < inputCount; i++)
+ {
+ outputBuffer[i] = (byte)(~inputBuffer[inputOffset + i] + 1);
+ }
+ return outputBuffer;
+ }
+ #endregion
+
+ #region IDisposable Members
+ public void Dispose()
+ {
+ }
+ #endregion
+
+ public CryptoTransformSB3()
+ {
+ }
+ }
+
+ public class CryptoTransformOneCode : ICryptoTransform
+ {
+ #region ICryptoTransform Members
+ public bool CanReuseTransform
+ {
+ get { return true; }
+ }
+
+ public bool CanTransformMultipleBlocks
+ {
+ get { return true; }
+ }
+
+ public int InputBlockSize
+ {
+ get { return code.Length; }
+ }
+
+ public int OutputBlockSize
+ {
+ get { return code.Length; }
+ }
+
+ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ int transformCount = 0;
+ while (transformCount < inputCount)
+ {
+ for (int i = 0; i < code.Length; i++, transformCount++)
+ {
+ outputBuffer[outputOffset + transformCount] = (byte)(inputBuffer[inputOffset + transformCount] ^ code[i]);
+ }
+ }
+ return transformCount;
+ }
+
+ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ byte[] outputBuffer = new byte[inputCount];
+ int remainder = inputCount % 4;
+ int transformLength = inputCount - remainder;
+ for (int i = 0; i < transformLength; i++)
+ {
+ outputBuffer[i] = (byte)(inputBuffer[inputOffset + i] ^ code[i]);
+ }
+ Array.Copy(inputBuffer, inputOffset + transformLength, outputBuffer, transformLength, remainder);
+ return outputBuffer;
+ }
+ #endregion
+
+ #region IDisposable Members
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+ #endregion
+
+ private byte[] code = null;
+
+ public CryptoTransformOneCode(byte[] code)
+ {
+ this.code = code;
+ }
+ }
+
+ public class CryptoTransformTwoCodes : ICryptoTransform
+ {
+ #region ICryptoTransform Members
+ public bool CanReuseTransform
+ {
+ get { return false; }
+ }
+
+ public bool CanTransformMultipleBlocks
+ {
+ get { return true; }
+ }
+
+ public int InputBlockSize
+ {
+ get { return 2; }
+ }
+
+ public int OutputBlockSize
+ {
+ get { return 2; }
+ }
+
+ public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ for (int i = 0; i < inputCount; )
+ {
+ codeA[codeIdx] = codeA[codeIdx] + codeB[codeIdx];
+ outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ codeA[codeIdx]);
+ i++;
+ outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ (codeA[codeIdx] >> 8));
+ i++;
+ codeIdx = (codeIdx + 1) & 0x3;
+ }
+ return inputCount;
+ }
+
+ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ byte[] outputBuffer = new byte[inputCount];
+ int remainder = inputCount % 2;
+ int transformLength = inputCount - remainder;
+ for (int i = 0; i < transformLength; )
+ {
+ codeA[codeIdx] = codeA[codeIdx] + codeB[codeIdx];
+ outputBuffer[i] = (byte)(inputBuffer[inputOffset + i] ^ codeA[codeIdx]);
+ i++;
+ outputBuffer[i] = (byte)(inputBuffer[inputOffset + i] ^ (codeA[codeIdx] >> 8));
+ i++;
+ codeIdx = (codeIdx + 1) & 0x3;
+ }
+ Array.Copy(inputBuffer, inputOffset + transformLength, outputBuffer, transformLength, remainder);
+ return outputBuffer;
+ }
+ #endregion
+
+ #region IDisposable Members
+ public void Dispose()
+ {
+ }
+ #endregion
+
+ private int[] codeA = null;
+ private int[] codeB = null;
+ private int codeIdx = 0;
+
+ public CryptoTransformTwoCodes(int[] codeA, int[] codeB)
+ {
+ this.codeA = codeA;
+ this.codeB = codeB;
+ }
+ }
+ #endregion
+}
diff --git a/SB3UtilityPP/ppHeader.cs b/SB3UtilityPP/ppHeader.cs
new file mode 100644
index 0000000..530bbdf
--- /dev/null
+++ b/SB3UtilityPP/ppHeader.cs
@@ -0,0 +1,607 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SB3Utility
+{
+ public enum ppHeaderIdx
+ {
+ SB3,
+ SMFigure,
+ SMRetail,
+ AG3,
+ AAJCH,
+ Wakeari
+ }
+
+ public abstract class ppHeader
+ {
+ public static ppHeader[] Array = new ppHeader[] {
+ new ppHeader_SB3(),
+ new ppHeader_SMFigure(),
+ new ppHeader_SMRetail(),
+ new ppHeader_AG3(),
+ new ppHeader_AAJCH(),
+ new ppHeader_Wakeari()
+ };
+
+ public abstract uint HeaderSize(int numFiles);
+ public abstract List ReadHeader(string path, ppFormat format);
+ public abstract void WriteHeader(Stream stream, List files, uint[] sizes, object[] metadata);
+ public abstract ppHeader TryHeader(string path);
+ public abstract ppFormat[] ppFormats { get; }
+ }
+
+ public class ppHeader_SB3 : ppHeader
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.SB3]
+ };
+ }
+ }
+
+ public override uint HeaderSize(int numFiles)
+ {
+ return (uint)((36 * numFiles) + 8);
+ }
+
+ public override List ReadHeader(string path, ppFormat format)
+ {
+ List subfiles = null;
+ using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
+ {
+ int numFiles = binaryReader.ReadInt32();
+
+ subfiles = new List(numFiles);
+ binaryReader.ReadInt32(); // total size
+
+ // get filenames
+ for (int i = 0; i < numFiles; i++)
+ {
+ byte[] nameBuf = binaryReader.ReadBytes(0x20);
+ for (int j = 0; j < nameBuf.Length; j++)
+ {
+ nameBuf[j] = (byte)(~nameBuf[j] + 1);
+ }
+
+ ppSubfile subfile = new ppSubfile(path);
+ subfile.ppFormat = format;
+ subfile.Name = Utility.EncodingShiftJIS.GetString(nameBuf).TrimEnd(new char[] { '\0' });
+ subfiles.Add(subfile);
+ }
+
+ // get filesizes
+ uint offset = HeaderSize(numFiles); // start of first file data
+ for (int i = 0; i < numFiles; i++)
+ {
+ ppSubfile subfile = (ppSubfile)subfiles[i];
+ subfile.offset = offset;
+ subfile.size = binaryReader.ReadUInt32();
+ offset += subfile.size;
+ }
+ }
+
+ return subfiles;
+ }
+
+ public override void WriteHeader(Stream stream, List files, uint[] sizes, object[] metadata)
+ {
+ byte[] headerBuf = new byte[HeaderSize(files.Count)];
+ BinaryWriter headerWriter = new BinaryWriter(new MemoryStream(headerBuf));
+
+ headerWriter.Write(files.Count);
+ headerWriter.BaseStream.Seek(4, SeekOrigin.Current); // placeholder for total size
+
+ // names
+ for (int i = 0; i < files.Count; i++)
+ {
+ byte[] nameBuf = new byte[0x20];
+ Utility.EncodingShiftJIS.GetBytes(files[i].Name).CopyTo(nameBuf, 0);
+ for (int j = 0; j < nameBuf.Length; j++)
+ {
+ nameBuf[j] = (byte)(~nameBuf[j] + 1);
+ }
+
+ headerWriter.Write(nameBuf);
+ }
+
+ // file sizes
+ uint totalSize = 0;
+ for (int i = 0; i < files.Count; i++)
+ {
+ headerWriter.Write(sizes[i]);
+ totalSize += sizes[i];
+ }
+
+ // total size
+ headerWriter.BaseStream.Seek(4, SeekOrigin.Begin);
+ headerWriter.Write(totalSize);
+
+ headerWriter.Flush();
+ stream.Write(headerBuf, 0, headerBuf.Length);
+ }
+
+ public override ppHeader TryHeader(string path)
+ {
+ using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
+ {
+ int numFiles = binaryReader.ReadInt32();
+ uint headerSizeTemp = HeaderSize(numFiles);
+ if ((numFiles > 0) && (headerSizeTemp > 0) && (headerSizeTemp <= binaryReader.BaseStream.Length))
+ {
+ int totalSizeRead = binaryReader.ReadInt32();
+ binaryReader.BaseStream.Seek(numFiles * 0x20, SeekOrigin.Current);
+ int totalSize = 0;
+ for (int i = 0; i < numFiles; i++)
+ {
+ int filesize = binaryReader.ReadInt32();
+ if (filesize < 0)
+ {
+ break;
+ }
+ totalSize += filesize;
+ if (totalSize >= binaryReader.BaseStream.Length)
+ {
+ break;
+ }
+ }
+
+ if ((totalSizeRead == totalSize) && ((totalSize + headerSizeTemp) == binaryReader.BaseStream.Length))
+ {
+ return this;
+ }
+ }
+
+ return null;
+ }
+ }
+ }
+
+ public class ppHeader_AAJCH : ppHeader_SMRetail
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.AAJCH]
+ };
+ }
+ }
+
+ public override byte FirstByte
+ {
+ get { return 0x04; }
+ }
+ }
+
+ public class ppHeader_AG3 : ppHeader_SMRetail
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.YuushaRetail],
+ ppFormat.Array[(int)ppFormatIdx.AHMRetail],
+ ppFormat.Array[(int)ppFormatIdx.HakoRetail],
+ ppFormat.Array[(int)ppFormatIdx.AG3Retail],
+ ppFormat.Array[(int)ppFormatIdx.DG],
+ ppFormat.Array[(int)ppFormatIdx.SMSweets],
+ ppFormat.Array[(int)ppFormatIdx.SM2Retail],
+ ppFormat.Array[(int)ppFormatIdx.BestCollection]
+ };
+ }
+ }
+
+ public override byte FirstByte
+ {
+ get { return 0x03; }
+ }
+ }
+
+ public abstract class ppHeader_SMBase : ppHeader
+ {
+ public abstract byte[] DecryptHeaderBytes(byte[] buf, byte[] SMFigTable);
+
+ public virtual byte FirstByte
+ {
+ get { return 0x01; }
+ }
+
+ public override uint HeaderSize(int numFiles)
+ {
+ return (uint)((268 * numFiles) + 9);
+ }
+
+ public List ReadHeader(string path, ppFormat format, byte[] SMFigTable)
+ {
+ List subfiles = null;
+ using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
+ {
+ DecryptHeaderBytes(binaryReader.ReadBytes(1), SMFigTable); // first byte
+ int numFiles = BitConverter.ToInt32(DecryptHeaderBytes(binaryReader.ReadBytes(4), SMFigTable), 0);
+ byte[] buf = DecryptHeaderBytes(binaryReader.ReadBytes(numFiles * 268), SMFigTable);
+
+ subfiles = new List(numFiles);
+ for (int i = 0; i < numFiles; i++)
+ {
+ int offset = i * 268;
+ ppSubfile subfile = new ppSubfile(path);
+ subfile.ppFormat = format;
+ subfile.Name = Utility.EncodingShiftJIS.GetString(buf, offset, 260).TrimEnd(new char[] { '\0' });
+ subfile.size = BitConverter.ToUInt32(buf, offset + 260);
+ subfile.offset = BitConverter.ToUInt32(buf, offset + 264);
+ subfiles.Add(subfile);
+ }
+ }
+
+ return subfiles;
+ }
+
+ public void WriteHeader(Stream stream, List files, uint[] sizes, byte[] SMFigTable)
+ {
+ byte[] headerBuf = new byte[HeaderSize(files.Count)];
+ BinaryWriter headerWriter = new BinaryWriter(new MemoryStream(headerBuf));
+
+ headerWriter.Write(DecryptHeaderBytes(new byte[] { FirstByte }, SMFigTable));
+ headerWriter.Write(DecryptHeaderBytes(BitConverter.GetBytes(files.Count), SMFigTable));
+
+ byte[] fileHeaderBuf = new byte[268 * files.Count];
+ uint fileOffset = (uint)headerBuf.Length;
+ for (int i = 0; i < files.Count; i++)
+ {
+ int idx = i * 268;
+ Utility.EncodingShiftJIS.GetBytes(files[i].Name).CopyTo(fileHeaderBuf, idx);
+ BitConverter.GetBytes(sizes[i]).CopyTo(fileHeaderBuf, idx + 260);
+ BitConverter.GetBytes(fileOffset).CopyTo(fileHeaderBuf, idx + 264);
+ fileOffset += sizes[i];
+ }
+
+ headerWriter.Write(DecryptHeaderBytes(fileHeaderBuf, SMFigTable));
+ headerWriter.Write(DecryptHeaderBytes(BitConverter.GetBytes(headerBuf.Length), SMFigTable));
+ headerWriter.Flush();
+ stream.Write(headerBuf, 0, headerBuf.Length);
+ }
+
+ public ppHeader TryHeader(string path, byte[] SMFigTable)
+ {
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(path)))
+ {
+ byte[] readFirstByte = reader.ReadBytes(1);
+ byte firstByteDecrypted = DecryptHeaderBytes(readFirstByte, SMFigTable)[0];
+
+ if (firstByteDecrypted == FirstByte)
+ {
+ int numFiles = BitConverter.ToInt32(DecryptHeaderBytes(reader.ReadBytes(4), SMFigTable), 0);
+
+ uint headerSizeTemp = HeaderSize(numFiles);
+ if ((numFiles > 0) && (headerSizeTemp > 0) && (headerSizeTemp <= reader.BaseStream.Length))
+ {
+ DecryptHeaderBytes(reader.ReadBytes(numFiles * 268), SMFigTable);
+ int headerSize = BitConverter.ToInt32(DecryptHeaderBytes(reader.ReadBytes(4), SMFigTable), 0);
+
+ if (headerSize == headerSizeTemp)
+ {
+ return this;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+ }
+
+ public class ppHeader_SMFigure : ppHeader_SMBase
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.SMFigure]
+ };
+ }
+ }
+
+ public override List ReadHeader(string path, ppFormat format)
+ {
+ return ReadHeader(path, format, InitTableFigure());
+ }
+
+ public override void WriteHeader(Stream stream, List files, uint[] sizes, object[] metadata)
+ {
+ base.WriteHeader(stream, files, sizes, InitTableFigure());
+ }
+
+ public override ppHeader TryHeader(string path)
+ {
+ return TryHeader(path, InitTableFigure());
+ }
+
+ private static byte[] InitTableFigure()
+ {
+ return new byte[]
+ {
+ 0x04, 0x0A, 0x06,
+ 0x02, 0x0D, 0x09, 0x00, 0x0E, 0x06, 0x01, 0x0C, 0x08, 0x05, 0x0B, 0x0F, 0x07, 0x0A, 0x04, 0x03,
+ 0x04, 0x0B, 0x03, 0x01, 0x0F, 0x00, 0x0D, 0x0A, 0x0E, 0x08, 0x05, 0x09, 0x0C, 0x02, 0x06, 0x07,
+ 0x03, 0x06, 0x00, 0x0F, 0x0E, 0x09, 0x05, 0x0C, 0x08, 0x02, 0x0D, 0x0A, 0x07, 0x01, 0x04, 0x0B,
+ 0x08, 0x04, 0x01, 0x06, 0x0D, 0x09, 0x00, 0x0E, 0x0A, 0x05, 0x02, 0x0F, 0x07, 0x0B, 0x03, 0x0C,
+ 0x05, 0x03, 0x0D, 0x02, 0x00, 0x0A, 0x0E, 0x0F, 0x09, 0x0B, 0x07, 0x01, 0x0C, 0x06, 0x08, 0x04,
+ 0x0B, 0x08, 0x0D, 0x07, 0x0A, 0x0F, 0x0C, 0x03, 0x01, 0x0E, 0x04, 0x00, 0x06, 0x02, 0x09, 0x05,
+ 0x06, 0x02, 0x0A, 0x0E, 0x01, 0x09, 0x03, 0x0C, 0x00, 0x05, 0x08, 0x0D, 0x0F, 0x04, 0x07, 0x0B
+ };
+ }
+
+ public override byte[] DecryptHeaderBytes(byte[] buf, byte[] table)
+ {
+ byte byte_7 = table[0];
+ byte byte_8 = table[1];
+ byte byte_9 = table[2];
+ for (int i = 0; i < buf.Length; i++)
+ {
+ byte var_2F = table[(buf[i] & 0x0f) + 3];
+ byte var_26 = table[var_2F + 0x13];
+ byte var_5 = table[var_26 + 0x33];
+ byte var_2D = table[var_5 + 0x53];
+ byte var_25 = table[var_2D + 0x63];
+ byte var_2E = table[var_25 + 0x43];
+ buf[i] = (byte)((table[var_2E + 0x23] & 0x0f) | (buf[i] & 0xf0));
+ ShuffleTableFigure(table, ref byte_7, ref byte_8, ref byte_9);
+ }
+
+ return buf;
+ }
+
+ private static void ShuffleTableFigure(byte[] table, ref byte byte_7, ref byte byte_8, ref byte byte_9)
+ {
+ byte var_A;
+ byte var_B = 0x0f;
+
+ var_A = table[3];
+ for (int i = 0; i < var_B; i++)
+ {
+ table[3 + i] = table[3 + i + 1];
+ }
+ table[3 + var_B] = var_A;
+ byte_7 = (byte)((byte_7 + 1) & 0x0f);
+
+ for (int i = 0; i < 0x10; i++)
+ {
+ table[0x13 + i + 0x10] = (byte)((table[0x13 + i + 0x10] + var_B) & 0x0f);
+ }
+ if (byte_7 != 0)
+ {
+ return;
+ }
+
+ var_A = table[0x13];
+ for (int i = 0; i < var_B; i++)
+ {
+ table[0x13 + i] = table[0x13 + i + 1];
+ }
+ table[0x13 + var_B] = var_A;
+ byte_8 = (byte)((byte_8 + 1) & 0x0f);
+
+ for (int i = 0; i < 0x10; i++)
+ {
+ table[0x33 + i + 0x10] = (byte)((table[0x33 + i + 0x10] + var_B) & 0x0f);
+ }
+ if (byte_8 != 0)
+ {
+ return;
+ }
+
+ var_A = table[0x33];
+ for (int i = 0; i < var_B; i++)
+ {
+ table[0x33 + i] = table[0x33 + i + 1];
+ }
+ table[0x33 + var_B] = var_A;
+ byte_9 = (byte)((byte_9 + 1) & 0x0f);
+
+ for (int i = 0; i < 0x10; i++)
+ {
+ table[0x53 + i + 0x10] = (byte)((table[0x53 + i + 0x10] + var_B) & 0x0f);
+ }
+ }
+ }
+
+ public class ppHeader_SMRetail : ppHeader_SMBase
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.SMRetail],
+ ppFormat.Array[(int)ppFormatIdx.RGF],
+ ppFormat.Array[(int)ppFormatIdx.HakoTrial],
+ ppFormat.Array[(int)ppFormatIdx.SMTrial],
+ ppFormat.Array[(int)ppFormatIdx.AG3Welcome],
+ ppFormat.Array[(int)ppFormatIdx.YuushaTrial],
+ ppFormat.Array[(int)ppFormatIdx.EskMate],
+ ppFormat.Array[(int)ppFormatIdx.AHMFigure],
+ ppFormat.Array[(int)ppFormatIdx.AHMTrial],
+ ppFormat.Array[(int)ppFormatIdx.SM2Trial],
+ ppFormat.Array[(int)ppFormatIdx.SBZ],
+ ppFormat.Array[(int)ppFormatIdx.CharacolleAlicesoft],
+ ppFormat.Array[(int)ppFormatIdx.CharacolleBaseson],
+ ppFormat.Array[(int)ppFormatIdx.CharacolleKey],
+ ppFormat.Array[(int)ppFormatIdx.CharacolleLumpOfSugar],
+ ppFormat.Array[(int)ppFormatIdx.CharacolleWhirlpool],
+ ppFormat.Array[(int)ppFormatIdx.AATrial],
+ ppFormat.Array[(int)ppFormatIdx.AARetail]
+ };
+ }
+ }
+
+ public override List ReadHeader(string path, ppFormat format)
+ {
+ return ReadHeader(path, format, null);
+ }
+
+ public override void WriteHeader(Stream stream, List files, uint[] sizes, object[] metadata)
+ {
+ base.WriteHeader(stream, files, sizes, null);
+ }
+
+ public override ppHeader TryHeader(string path)
+ {
+ return TryHeader(path, null);
+ }
+
+ public static byte[] DecryptHeaderBytes(byte[] buf)
+ {
+ byte[] table = new byte[]
+ {
+ 0xFA, 0x49, 0x7B, 0x1C, // var48
+ 0xF9, 0x4D, 0x83, 0x0A,
+ 0x3A, 0xE3, 0x87, 0xC2, // var24
+ 0xBD, 0x1E, 0xA6, 0xFE
+ };
+
+ byte var28;
+ for (int var4 = 0; var4 < buf.Length; var4++)
+ {
+ var28 = (byte)(var4 & 0x7);
+ table[var28] += table[8 + var28];
+ buf[var4] ^= table[var28];
+ }
+
+ return buf;
+ }
+
+ public override byte[] DecryptHeaderBytes(byte[] buf, byte[] SMFigTable)
+ {
+ return DecryptHeaderBytes(buf);
+ }
+ }
+
+ public class ppHeader_Wakeari : ppHeader
+ {
+ public override ppFormat[] ppFormats
+ {
+ get
+ {
+ return new ppFormat[] {
+ ppFormat.Array[(int)ppFormatIdx.Wakeari],
+ ppFormat.Array[(int)ppFormatIdx.LoveGirl],
+ ppFormat.Array[(int)ppFormatIdx.Hero],
+ ppFormat.Array[(int)ppFormatIdx.HET],
+ ppFormat.Array[(int)ppFormatIdx.HETDTL],
+ ppFormat.Array[(int)ppFormatIdx.PPD],
+ ppFormat.Array[(int)ppFormatIdx.Musumakeup],
+ ppFormat.Array[(int)ppFormatIdx.ImmoralWard],
+ ppFormat.Array[(int)ppFormatIdx.RealPlayTrial],
+ ppFormat.Array[(int)ppFormatIdx.RealPlay],
+ ppFormat.Array[(int)ppFormatIdx.AA2]
+ };
+ }
+ }
+
+ const byte FirstByte = 0x01;
+ const int Version = 0x6C;
+ byte[] ppVersionBytes = Encoding.ASCII.GetBytes("[PPVER]\0");
+
+ public override uint HeaderSize(int numFiles)
+ {
+ return (uint)((288 * numFiles) + 9 + 12);
+ }
+
+ public override List ReadHeader(string path, ppFormat format)
+ {
+ List subfiles = null;
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(path)))
+ {
+ byte[] versionHeader = reader.ReadBytes(8);
+ int version = BitConverter.ToInt32(ppHeader_SMRetail.DecryptHeaderBytes(reader.ReadBytes(4)), 0);
+
+ ppHeader_SMRetail.DecryptHeaderBytes(reader.ReadBytes(1)); // first byte
+ int numFiles = BitConverter.ToInt32(ppHeader_SMRetail.DecryptHeaderBytes(reader.ReadBytes(4)), 0);
+ byte[] buf = ppHeader_SMRetail.DecryptHeaderBytes(reader.ReadBytes(numFiles * 288));
+
+ subfiles = new List(numFiles);
+ for (int i = 0; i < numFiles; i++)
+ {
+ int offset = i * 288;
+ ppSubfile subfile = new ppSubfile(path);
+ subfile.ppFormat = format;
+ subfile.Name = Utility.EncodingShiftJIS.GetString(buf, offset, 260).TrimEnd(new char[] { '\0' });
+ subfile.size = BitConverter.ToUInt32(buf, offset + 260);
+ subfile.offset = BitConverter.ToUInt32(buf, offset + 264);
+
+ Metadata metadata = new Metadata();
+ metadata.LastBytes = new byte[20];
+ System.Array.Copy(buf, offset + 268, metadata.LastBytes, 0, 20);
+ subfile.Metadata = metadata;
+
+ subfiles.Add(subfile);
+ }
+ }
+ return subfiles;
+ }
+
+ public override void WriteHeader(Stream stream, List files, uint[] sizes, object[] metadata)
+ {
+ byte[] headerBuf = new byte[HeaderSize(files.Count)];
+ BinaryWriter writer = new BinaryWriter(new MemoryStream(headerBuf));
+
+ writer.Write(ppVersionBytes);
+ writer.Write(ppHeader_SMRetail.DecryptHeaderBytes(BitConverter.GetBytes(Version)));
+
+ writer.Write(ppHeader_SMRetail.DecryptHeaderBytes(new byte[] { FirstByte }));
+ writer.Write(ppHeader_SMRetail.DecryptHeaderBytes(BitConverter.GetBytes(files.Count)));
+
+ byte[] fileHeaderBuf = new byte[288 * files.Count];
+ uint fileOffset = (uint)headerBuf.Length;
+ for (int i = 0; i < files.Count; i++)
+ {
+ int idx = i * 288;
+ Utility.EncodingShiftJIS.GetBytes(files[i].Name).CopyTo(fileHeaderBuf, idx);
+ BitConverter.GetBytes(sizes[i]).CopyTo(fileHeaderBuf, idx + 260);
+ BitConverter.GetBytes(fileOffset).CopyTo(fileHeaderBuf, idx + 264);
+
+ Metadata wakeariMetadata = (Metadata)metadata[i];
+ System.Array.Copy(wakeariMetadata.LastBytes, 0, fileHeaderBuf, idx + 268, 20);
+ BitConverter.GetBytes(sizes[i]).CopyTo(fileHeaderBuf, idx + 284);
+
+ fileOffset += sizes[i];
+ }
+
+ writer.Write(ppHeader_SMRetail.DecryptHeaderBytes(fileHeaderBuf));
+ writer.Write(ppHeader_SMRetail.DecryptHeaderBytes(BitConverter.GetBytes(headerBuf.Length)));
+ writer.Flush();
+ stream.Write(headerBuf, 0, headerBuf.Length);
+ }
+
+ public override ppHeader TryHeader(string path)
+ {
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(path)))
+ {
+ byte[] version = reader.ReadBytes(8);
+ for (int i = 0; i < version.Length; i++)
+ {
+ if (ppVersionBytes[i] != version[i])
+ {
+ return null;
+ }
+ }
+ return this;
+ }
+ }
+
+ public struct Metadata
+ {
+ public byte[] LastBytes { get; set; }
+ }
+ }
+}
diff --git a/SB3UtilityPP/ppParser.cs b/SB3UtilityPP/ppParser.cs
new file mode 100644
index 0000000..c14727d
--- /dev/null
+++ b/SB3UtilityPP/ppParser.cs
@@ -0,0 +1,213 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.ComponentModel;
+using SB3Utility;
+
+namespace SB3Utility
+{
+ public class ppParser
+ {
+ public string FilePath { get; protected set; }
+ public ppFormat Format { get; set; }
+ public List Subfiles { get; protected set; }
+
+ private string destPath;
+ private bool keepBackup;
+ private string backupExt;
+
+ public ppParser(string path, ppFormat format)
+ {
+ this.Format = format;
+ this.FilePath = path;
+ if (File.Exists(path))
+ {
+ this.Subfiles = format.ppHeader.ReadHeader(path, format);
+ }
+ else
+ {
+ this.Subfiles = new List();
+ }
+ }
+
+ public BackgroundWorker WriteArchive(string destPath, bool keepBackup, string backupExtension, bool background)
+ {
+ this.destPath = destPath;
+ this.keepBackup = keepBackup;
+ this.backupExt = backupExtension;
+
+ BackgroundWorker worker = new BackgroundWorker();
+ worker.WorkerSupportsCancellation = true;
+ worker.WorkerReportsProgress = true;
+
+ worker.DoWork += new DoWorkEventHandler(writeArchiveWorker_DoWork);
+
+ if (!background)
+ {
+ writeArchiveWorker_DoWork(worker, new DoWorkEventArgs(null));
+ }
+
+ return worker;
+ }
+
+ void writeArchiveWorker_DoWork(object sender, DoWorkEventArgs e)
+ {
+ BackgroundWorker worker = (BackgroundWorker)sender;
+ string backup = null;
+
+ string dirName = Path.GetDirectoryName(destPath);
+ if (dirName == String.Empty)
+ {
+ dirName = @".\";
+ }
+ DirectoryInfo dir = new DirectoryInfo(dirName);
+ if (!dir.Exists)
+ {
+ dir.Create();
+ }
+
+ if (File.Exists(destPath))
+ {
+ backup = Utility.GetDestFile(dir, Path.GetFileNameWithoutExtension(destPath) + ".bak", backupExt);
+ File.Move(destPath, backup);
+
+ if (destPath.Equals(this.FilePath, StringComparison.InvariantCultureIgnoreCase))
+ {
+ for (int i = 0; i < Subfiles.Count; i++)
+ {
+ ppSubfile subfile = Subfiles[i] as ppSubfile;
+ if ((subfile != null) && subfile.ppPath.Equals(this.FilePath, StringComparison.InvariantCultureIgnoreCase))
+ {
+ subfile.ppPath = backup;
+ }
+ }
+ }
+ }
+
+ try
+ {
+ using (BinaryWriter writer = new BinaryWriter(File.Create(destPath)))
+ {
+ writer.BaseStream.Seek(Format.ppHeader.HeaderSize(Subfiles.Count), SeekOrigin.Begin);
+ long offset = writer.BaseStream.Position;
+ uint[] sizes = new uint[Subfiles.Count];
+ object[] metadata = new object[Subfiles.Count];
+
+ for (int i = 0; i < Subfiles.Count; i++)
+ {
+ if (worker.CancellationPending)
+ {
+ e.Cancel = true;
+ break;
+ }
+
+ worker.ReportProgress(i * 100 / Subfiles.Count);
+
+ ppSubfile subfile = Subfiles[i] as ppSubfile;
+ if ((subfile != null) && (subfile.ppFormat == this.Format))
+ {
+ using (BinaryReader reader = new BinaryReader(File.OpenRead(subfile.ppPath)))
+ {
+ reader.BaseStream.Seek(subfile.offset, SeekOrigin.Begin);
+
+ uint readSteps = subfile.size / Utility.BufSize;
+ for (int j = 0; j < readSteps; j++)
+ {
+ writer.Write(reader.ReadBytes(Utility.BufSize));
+ }
+ writer.Write(reader.ReadBytes(subfile.size % Utility.BufSize));
+ }
+ metadata[i] = subfile.Metadata;
+ }
+ else
+ {
+ Stream stream = Format.WriteStream(writer.BaseStream);
+ Subfiles[i].WriteTo(stream);
+ metadata[i] = Format.FinishWriteTo(stream);
+ }
+
+ long pos = writer.BaseStream.Position;
+ sizes[i] = (uint)(pos - offset);
+ offset = pos;
+ }
+
+ if (!e.Cancel)
+ {
+ writer.BaseStream.Seek(0, SeekOrigin.Begin);
+ Format.ppHeader.WriteHeader(writer.BaseStream, Subfiles, sizes, metadata);
+ offset = writer.BaseStream.Position;
+ for (int i = 0; i < Subfiles.Count; i++)
+ {
+ ppSubfile subfile = Subfiles[i] as ppSubfile;
+ if (subfile != null)
+ {
+ subfile.offset = offset;
+ subfile.size = sizes[i];
+ }
+ offset += sizes[i];
+ }
+ }
+ }
+
+ if (e.Cancel)
+ {
+ RestoreBackup(destPath, backup);
+ }
+ else
+ {
+ if (destPath.Equals(this.FilePath, StringComparison.InvariantCultureIgnoreCase))
+ {
+ for (int i = 0; i < Subfiles.Count; i++)
+ {
+ ppSubfile subfile = Subfiles[i] as ppSubfile;
+ if ((subfile != null) && subfile.ppPath.Equals(backup, StringComparison.InvariantCultureIgnoreCase))
+ {
+ subfile.ppPath = this.FilePath;
+ }
+ }
+ }
+ else
+ {
+ this.FilePath = destPath;
+ }
+
+ if ((backup != null) && !keepBackup)
+ {
+ File.Delete(backup);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ RestoreBackup(destPath, backup);
+ Utility.ReportException(ex);
+ }
+ }
+
+ void RestoreBackup(string destPath, string backup)
+ {
+ if (File.Exists(destPath) && File.Exists(backup))
+ {
+ File.Delete(destPath);
+
+ if (backup != null)
+ {
+ File.Move(backup, destPath);
+
+ if (destPath.Equals(this.FilePath, StringComparison.InvariantCultureIgnoreCase))
+ {
+ for (int i = 0; i < Subfiles.Count; i++)
+ {
+ ppSubfile subfile = Subfiles[i] as ppSubfile;
+ if ((subfile != null) && subfile.ppPath.Equals(backup, StringComparison.InvariantCultureIgnoreCase))
+ {
+ subfile.ppPath = this.FilePath;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/SB3UtilityPP/ppSubfile.cs b/SB3UtilityPP/ppSubfile.cs
new file mode 100644
index 0000000..5e654bd
--- /dev/null
+++ b/SB3UtilityPP/ppSubfile.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace SB3Utility
+{
+ ///
+ /// If removed from a ppParser, CreateReadStream() is no longer guaranteed to work. The .pp file may have changed,
+ /// so you have to transfer the ppSubfile's data when removing.
+ ///
+ public class ppSubfile : IReadFile, IWriteFile
+ {
+ public string ppPath;
+ public long offset;
+ public uint size;
+ public ppFormat ppFormat;
+
+ public object Metadata { get; set; }
+
+ public ppSubfile(string ppPath)
+ {
+ this.ppPath = ppPath;
+ }
+
+ public string Name { get; set; }
+
+ public void WriteTo(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(CreateReadStream()))
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ byte[] buf;
+ while ((buf = reader.ReadBytes(Utility.BufSize)).Length == Utility.BufSize)
+ {
+ writer.Write(buf);
+ }
+ writer.Write(buf);
+ }
+ }
+
+ public Stream CreateReadStream()
+ {
+ FileStream fs = null;
+ try
+ {
+ fs = File.OpenRead(ppPath);
+ fs.Seek(offset, SeekOrigin.Begin);
+ return ppFormat.ReadStream(new PartialStream(fs, size));
+ }
+ catch (Exception e)
+ {
+ if (fs != null)
+ {
+ fs.Close();
+ }
+ throw e;
+ }
+ }
+
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+
+ // Represents a subsection of the stream. This forces a CryptoStream to use TransformFinalBlock() at the end of the subsection.
+ public class PartialStream : Stream
+ {
+ public override bool CanRead
+ {
+ get { return stream.CanRead; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return stream.CanSeek; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return stream.CanWrite; }
+ }
+
+ public override void Flush()
+ {
+ stream.Flush();
+ }
+
+ public override long Length
+ {
+ get { return this.length; }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ return stream.Position - offset;
+ }
+ set
+ {
+ Seek(value, SeekOrigin.Begin);
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if ((stream.Position + count) > end)
+ {
+ count = (int)(end - stream.Position);
+ }
+
+ if (count < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return stream.Read(buffer, offset, count);
+ }
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ private Stream stream = null;
+ private long offset = 0;
+ private long length = 0;
+ private long end = 0;
+
+ public PartialStream(Stream stream, long length)
+ {
+ if ((length + stream.Position) > stream.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ this.stream = stream;
+ this.offset = stream.Position;
+ this.length = length;
+ this.end = this.offset + this.length;
+ }
+
+ public override void Close()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (disposing)
+ {
+ this.stream.Close();
+ }
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
diff --git a/SB3UtilityPP/ppSwapfile.cs b/SB3UtilityPP/ppSwapfile.cs
new file mode 100644
index 0000000..ecb84dd
--- /dev/null
+++ b/SB3UtilityPP/ppSwapfile.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SB3Utility
+{
+ public class ppSwapfile : IReadFile, IWriteFile, IDisposable
+ {
+ static string tmpFolder = (Environment.GetEnvironmentVariable("TMP") != null ? Environment.GetEnvironmentVariable("TMP") + @"\" : "") + @"SB3Utility(G+S)_swap";
+
+ string swapFilePath;
+
+ public ppSwapfile(string ppPath, IWriteFile source)
+ {
+ this.Name = source.Name;
+ this.swapFilePath = tmpFolder + @"\" + ppPath.Replace('\\', '#').Replace(':', '~') + "#" + source.Name;
+
+ if (!Directory.Exists(tmpFolder))
+ {
+ Directory.CreateDirectory(tmpFolder);
+ }
+ else
+ {
+ string rnd = string.Empty;
+ Random rand = new Random();
+ while (File.Exists(swapFilePath + rnd))
+ {
+ rnd = "-" + rand.Next();
+ }
+ swapFilePath += rnd;
+ }
+ using (FileStream stream = File.OpenWrite(swapFilePath))
+ {
+ source.WriteTo(stream);
+ }
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ File.Delete(swapFilePath);
+ }
+ }
+
+ ~ppSwapfile()
+ {
+ try
+ {
+ File.Delete(swapFilePath);
+ }
+ catch (Exception ex)
+ {
+ using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(swapFilePath + "-exc.txt")))
+ {
+ writer.Seek(0, SeekOrigin.End);
+ writer.Write(System.DateTime.Now + " " + ex);
+ }
+ }
+ }
+
+ public string Name { get; set; }
+
+ public Stream CreateReadStream()
+ {
+ return File.OpenRead(swapFilePath);
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(CreateReadStream()))
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ for (byte[] buffer; (buffer = reader.ReadBytes(Utility.BufSize)).Length > 0; )
+ {
+ writer.Write(buffer);
+ }
+ }
+ }
+ }
+}
diff --git a/SB3UtilityPP/xaOps.cs b/SB3UtilityPP/xaOps.cs
new file mode 100644
index 0000000..3fd25ce
--- /dev/null
+++ b/SB3UtilityPP/xaOps.cs
@@ -0,0 +1,470 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using SlimDX;
+
+namespace SB3Utility
+{
+ public enum ReplaceAnimationMethod
+ {
+ Replace,
+ ReplacePresent,
+ Merge,
+ Insert,
+ Append
+ }
+
+ public static class xa
+ {
+ public static xaMorphIndexSet FindMorphIndexSet(string name, xaMorphSection section)
+ {
+ for (int i = 0; i < section.IndexSetList.Count; i++)
+ {
+ if (section.IndexSetList[i].Name == name)
+ {
+ return section.IndexSetList[i];
+ }
+ }
+ return null;
+ }
+
+ public static xaMorphKeyframe FindMorphKeyFrame(string name, xaMorphSection section)
+ {
+ for (int i = 0; i < section.KeyframeList.Count; i++)
+ {
+ if (section.KeyframeList[i].Name == name)
+ {
+ return section.KeyframeList[i];
+ }
+ }
+ return null;
+ }
+
+ public static int MorphMeshObjIdx(ushort[] meshIndices, xxMesh mesh)
+ {
+ int meshObjIdx = -1;
+ if (mesh.SubmeshList.Count > 0)
+ {
+ if (mesh.SubmeshList.Count == 1)
+ {
+ if (ValidIndices(meshIndices, mesh.SubmeshList[0].VertexList))
+ {
+ meshObjIdx = 0;
+ }
+ }
+ else
+ {
+ float maxModified = 0;
+ for (int i = 0; i < mesh.SubmeshList.Count; i++)
+ {
+ if (ValidIndices(meshIndices, mesh.SubmeshList[i].VertexList))
+ {
+ float modified = (float)meshIndices.Length / mesh.SubmeshList[i].VertexList.Count;
+ if (modified > maxModified)
+ {
+ maxModified = modified;
+ meshObjIdx = i;
+ }
+ }
+ }
+ }
+ }
+ return meshObjIdx;
+ }
+
+ static bool ValidIndices(ushort[] meshIndices, List vertList)
+ {
+ bool valid = true;
+ foreach (ushort index in meshIndices)
+ {
+ if (index >= vertList.Count)
+ {
+ valid = false;
+ break;
+ }
+ }
+ return valid;
+ }
+
+ public static void SaveXA(xaParser parser, string destPath, bool keepBackup)
+ {
+ DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(destPath));
+
+ string backup = null;
+ if (keepBackup && File.Exists(destPath))
+ {
+ backup = Utility.GetDestFile(dir, Path.GetFileNameWithoutExtension(destPath) + ".bak", Path.GetExtension(destPath));
+ File.Move(destPath, backup);
+ }
+
+ try
+ {
+ using (BufferedStream bufStr = new BufferedStream(File.OpenWrite(destPath)))
+ {
+ parser.WriteTo(bufStr);
+ }
+ }
+ catch
+ {
+ if (File.Exists(backup))
+ {
+ if (File.Exists(destPath))
+ File.Delete(destPath);
+ File.Move(backup, destPath);
+ }
+ }
+ }
+
+ public static void ReplaceMorph(string destMorphName, xaParser parser, WorkspaceMorph wsMorphList, string newMorphName, bool replaceMorphMask, bool replaceNormals, float minSquaredDistance, bool minKeyframes)
+ {
+ if (parser.MorphSection == null)
+ {
+ Report.ReportLog("The .xa file doesn't have a morph section. Skipping these morphs");
+ return;
+ }
+
+ xaMorphSection morphSection = parser.MorphSection;
+ xaMorphIndexSet indices = FindMorphIndexSet(destMorphName, morphSection);
+ if (indices == null)
+ {
+ Report.ReportLog("Couldn't find morph clip " + destMorphName + ". Skipping these morphs");
+ return;
+ }
+ if (replaceMorphMask && wsMorphList.MorphedVertexIndices != null)
+ {
+ int index = morphSection.IndexSetList.IndexOf(indices);
+ xaMorphIndexSet newIndices = new xaMorphIndexSet();
+ newIndices.Name = indices.Name;
+ int numMorphedVertices = wsMorphList.MorphedVertexIndices.Count;
+ newIndices.MeshIndices = new ushort[numMorphedVertices];
+ wsMorphList.MorphedVertexIndices.CopyTo(newIndices.MeshIndices);
+ newIndices.MorphIndices = new ushort[numMorphedVertices];
+ if (minKeyframes)
+ {
+ for (ushort i = 0; i < numMorphedVertices; i++)
+ {
+ newIndices.MorphIndices[i] = i;
+ }
+ }
+ else
+ {
+ wsMorphList.MorphedVertexIndices.CopyTo(newIndices.MorphIndices);
+ }
+ newIndices.Unknown1 = indices.Unknown1;
+ morphSection.IndexSetList.RemoveAt(index);
+ morphSection.IndexSetList.Insert(index, newIndices);
+ indices = newIndices;
+ }
+
+ Report.ReportLog("Replacing morphs ...");
+ try
+ {
+ ushort[] meshIndices = indices.MeshIndices;
+ ushort[] morphIndices = indices.MorphIndices;
+ foreach (ImportedMorphKeyframe wsMorph in wsMorphList.KeyframeList)
+ {
+ if (!wsMorphList.isMorphKeyframeEnabled(wsMorph))
+ continue;
+
+ List vertList = wsMorph.VertexList;
+ xaMorphKeyframe keyframe = FindMorphKeyFrame(wsMorph.Name, morphSection);
+ if (keyframe == null)
+ {
+ Report.ReportLog("Adding new Keyframe " + wsMorph.Name);
+ keyframe = new xaMorphKeyframe();
+ keyframe.Name = wsMorph.Name;
+ int numVertices = minKeyframes ? meshIndices.Length : wsMorph.VertexList.Count;
+ keyframe.PositionList = new List(new Vector3[numVertices]);
+ keyframe.NormalList = new List(new Vector3[numVertices]);
+ for (int i = 0; i < meshIndices.Length; i++)
+ {
+ keyframe.PositionList[morphIndices[i]] = vertList[meshIndices[i]].Position;
+ keyframe.NormalList[morphIndices[i]] = vertList[meshIndices[i]].Normal;
+ }
+ morphSection.KeyframeList.Add(keyframe);
+ }
+ else
+ {
+ if (!minKeyframes && keyframe.PositionList.Count != vertList.Count ||
+ minKeyframes && keyframe.PositionList.Count != meshIndices.Length)
+ {
+ Report.ReportLog("Adapting Keyframe " + wsMorph.Name + " to new length.");
+ int length = minKeyframes ? meshIndices.Length : vertList.Count;
+ Vector3[] newPositions = new Vector3[length];
+ Vector3[] newNormals = new Vector3[length];
+ if (!minKeyframes)
+ {
+ for (int i = 0; i < vertList.Count; i++)
+ {
+ newPositions[i] = vertList[i].Position;
+ newNormals[i] = vertList[i].Normal;
+ }
+ }
+ for (int i = 0; i < meshIndices.Length; i++)
+ {
+ newPositions[morphIndices[i]] = vertList[meshIndices[i]].Position;
+ newNormals[morphIndices[i]] = vertList[meshIndices[i]].Normal;
+ }
+ keyframe.PositionList.Clear();
+ keyframe.NormalList.Clear();
+ keyframe.PositionList.AddRange(newPositions);
+ keyframe.NormalList.AddRange(newNormals);
+ }
+ else
+ {
+ Report.ReportLog("Replacing Keyframe " + wsMorph.Name);
+ for (int i = 0; i < meshIndices.Length; i++)
+ {
+ Vector3 orgPos = new Vector3(keyframe.PositionList[morphIndices[i]].X, keyframe.PositionList[morphIndices[i]].Y, keyframe.PositionList[morphIndices[i]].Z),
+ newPos = new Vector3(vertList[meshIndices[i]].Position.X, vertList[meshIndices[i]].Position.Y, vertList[meshIndices[i]].Position.Z);
+ if ((orgPos - newPos).LengthSquared() >= minSquaredDistance)
+ {
+ keyframe.PositionList[morphIndices[i]] = vertList[meshIndices[i]].Position;
+ if (replaceNormals)
+ {
+ keyframe.NormalList[morphIndices[i]] = vertList[meshIndices[i]].Normal;
+ }
+ }
+ }
+ }
+ }
+
+ string morphNewName = wsMorphList.getMorphKeyframeNewName(wsMorph);
+ if (morphNewName != String.Empty)
+ {
+ for (int i = 0; i < morphSection.ClipList.Count; i++)
+ {
+ xaMorphClip clip = morphSection.ClipList[i];
+ for (int j = 0; j < clip.KeyframeRefList.Count; j++)
+ {
+ xaMorphKeyframeRef keyframeRef = clip.KeyframeRefList[j];
+ if (keyframeRef.Name == wsMorph.Name)
+ keyframeRef.Name = morphNewName;
+ }
+ }
+ keyframe.Name = morphNewName;
+ }
+ }
+ if (newMorphName != String.Empty)
+ {
+ for (int i = 0; i < morphSection.ClipList.Count; i++)
+ {
+ xaMorphClip clip = morphSection.ClipList[i];
+ if (clip.Name == destMorphName)
+ {
+ clip.Name = newMorphName;
+ break;
+ }
+ }
+ indices.Name = newMorphName;
+ }
+ }
+ catch (Exception ex)
+ {
+ Report.ReportLog("Error replacing morphs: " + ex.Message);
+ }
+ }
+
+ public static void CalculateNormals(xaParser parser, xxFrame meshFrame, string morphClip, string keyframe, float threshold)
+ {
+ HashSet> keyframes = new HashSet>();
+ foreach (xaMorphClip clip in parser.MorphSection.ClipList)
+ {
+ if (morphClip != null && clip.Name != morphClip)
+ continue;
+
+ if (keyframe != null)
+ {
+ xaMorphKeyframe xaKeyframe = FindMorphKeyFrame(keyframe, parser.MorphSection);
+ if (xaKeyframe == null)
+ {
+ throw new Exception("keyframe " + keyframe + " not found in morph clip " + morphClip);
+ }
+ keyframes.Add(new Tuple(clip, xaKeyframe));
+ break;
+ }
+ else
+ {
+ foreach (xaMorphKeyframeRef morphRef in clip.KeyframeRefList)
+ {
+ xaMorphKeyframe xaKeyframe = FindMorphKeyFrame(morphRef.Name, parser.MorphSection);
+ keyframes.Add(new Tuple(clip, xaKeyframe));
+ }
+ }
+ }
+ if (keyframes.Count == 0)
+ {
+ Report.ReportLog("No keyframe for mesh " + meshFrame.Name + " to calculate normals for found.");
+ return;
+ }
+
+ foreach (var tup in keyframes)
+ {
+ xaMorphIndexSet set = FindMorphIndexSet(tup.Item1.Name, parser.MorphSection);
+ CalculateNormals(parser, meshFrame, tup.Item2, set, threshold);
+ }
+ }
+
+ private static void CalculateNormals(xaParser parser, xxFrame meshFrame, xaMorphKeyframe keyframe, xaMorphIndexSet set, float threshold)
+ {
+ xxMesh mesh = meshFrame.Mesh;
+ ushort[] meshIndices = set.MeshIndices;
+ ushort[] morphIndices = set.MorphIndices;
+ int morphSubmeshIdx = MorphMeshObjIdx(meshIndices, mesh);
+ if (morphSubmeshIdx < 0)
+ {
+ throw new Exception("no valid mesh object was found for the morph " + set.Name);
+ }
+ xxSubmesh submesh = mesh.SubmeshList[morphSubmeshIdx];
+ List morphedVertices = new List(submesh.VertexList.Count);
+ for (ushort i = 0; i < submesh.VertexList.Count; i++)
+ {
+ xxVertex vert = new xxVertexUShort();
+ vert.Index = i;
+ vert.Position = submesh.VertexList[i].Position;
+ vert.Normal = submesh.VertexList[i].Normal;
+ morphedVertices.Add(vert);
+ }
+ for (int i = 0; i < meshIndices.Length; i++)
+ {
+ morphedVertices[meshIndices[i]].Position = keyframe.PositionList[morphIndices[i]];
+ }
+
+ var pairList = new List, List>>(1);
+ pairList.Add(new Tuple, List>(submesh.FaceList, morphedVertices));
+ xx.CalculateNormals(pairList, threshold);
+
+ for (int i = 0; i < meshIndices.Length; i++)
+ {
+ keyframe.NormalList[morphIndices[i]] = morphedVertices[meshIndices[i]].Normal;
+ }
+ }
+
+ public static xaAnimationTrack FindTrack(string name, xaParser parser)
+ {
+ foreach (xaAnimationTrack track in parser.AnimationSection.TrackList)
+ {
+ if (track.Name == name)
+ {
+ return track;
+ }
+ }
+
+ return null;
+ }
+
+ public static void animationNormalizeTrack(xaAnimationKeyframe[] origKeyframes, xaAnimationKeyframe[] destKeyframes, int count)
+ {
+ xaAnimationKeyframe keyframeToCopy;
+ if (origKeyframes.Length > 0)
+ {
+ keyframeToCopy = origKeyframes[origKeyframes.Length - 1];
+ }
+ else
+ {
+ keyframeToCopy = new xaAnimationKeyframe();
+ keyframeToCopy.Rotation = Quaternion.Identity;
+ keyframeToCopy.Scaling = new Vector3(1, 1, 1);
+ keyframeToCopy.Translation = new Vector3(0, 0, 0);
+ CreateUnknowns(keyframeToCopy);
+ }
+ for (int j = origKeyframes.Length; j < count; j++)
+ {
+ xaAnimationKeyframe copy = new xaAnimationKeyframe();
+ copy.Index = j;
+ copy.Rotation = keyframeToCopy.Rotation;
+ copy.Scaling = keyframeToCopy.Scaling;
+ copy.Translation = keyframeToCopy.Translation;
+ CreateUnknowns(copy);
+ destKeyframes[j] = copy;
+ }
+ }
+
+ public static void CreateUnknowns(xaAnimationKeyframe keyframe)
+ {
+ keyframe.Unknown1 = new byte[8];
+ }
+
+ public static void CreateUnknowns(xaAnimationTrack track)
+ {
+ track.Unknown1 = new byte[4];
+ }
+
+ public static void CreateUnknowns(xaMorphKeyframeRef morphRef)
+ {
+ morphRef.Unknown1 = new byte[1];
+ morphRef.Unknown2 = new byte[1];
+ }
+
+ public static void CreateUnknowns(xaAnimationClip clip)
+ {
+ clip.Unknown1 = new byte[4];
+ clip.Unknown2 = new byte[1];
+ clip.Unknown3 = new byte[1];
+ clip.Unknown4 = new byte[1];
+ clip.Unknown5 = new byte[1];
+ clip.Unknown6 = new byte[4];
+ clip.Unknown7 = new byte[16];
+ }
+
+ public static void CopyUnknowns(xaAnimationClip src, xaAnimationClip dest)
+ {
+ dest.Unknown1 = (byte[])src.Unknown1.Clone();
+ dest.Unknown2 = (byte[])src.Unknown2.Clone();
+ dest.Unknown3 = (byte[])src.Unknown3.Clone();
+ dest.Unknown4 = (byte[])src.Unknown4.Clone();
+ dest.Unknown5 = (byte[])src.Unknown5.Clone();
+ dest.Unknown6 = (byte[])src.Unknown6.Clone();
+ dest.Unknown7 = (byte[])src.Unknown7.Clone();
+ }
+
+ public static void animationCopyKeyframeTransformArray(xaAnimationKeyframe[] src, int srcIdx, xaAnimationKeyframe[] dest, int destIdx, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ xaAnimationKeyframe keyframe = src[srcIdx + i];
+ keyframe.Index = destIdx + i;
+ dest[destIdx + i] = keyframe;
+ }
+ }
+
+ public static void animationCopyKeyframeTransformArray(xaAnimationKeyframe[] src, xaAnimationKeyframe[] dest, int destOffset)
+ {
+ for (int i = 0; i < src.Length; i++)
+ {
+ xaAnimationKeyframe keyframe = src[i];
+ keyframe.Index += destOffset;
+ dest[keyframe.Index] = keyframe;
+ }
+ }
+
+ public static xaAnimationTrack animationGetOriginalKeyframes(Dictionary animationNodeDic, string trackName, List animationNodeList)
+ {
+ xaAnimationTrack animationNode;
+ if (!animationNodeDic.TryGetValue(trackName, out animationNode))
+ {
+ animationNode = new xaAnimationTrack();
+ animationNodeList.Add(animationNode);
+ animationNode.Name = trackName;
+ CreateUnknowns(animationNode);
+ }
+ return animationNode;
+ }
+
+ public static xaAnimationKeyframe[] animationGetOriginalKeyframes(Dictionary animationNodeDic, string trackName, List animationNodeList, out xaAnimationTrack animationNode)
+ {
+ animationNode = animationGetOriginalKeyframes(animationNodeDic, trackName, animationNodeList);
+ xaAnimationKeyframe[] origKeyframes;
+ if (animationNode.KeyframeList != null)
+ {
+ origKeyframes = animationNode.KeyframeList.ToArray();
+ }
+ else
+ {
+ origKeyframes = new xaAnimationKeyframe[0];
+ }
+ return origKeyframes;
+ }
+ }
+}
diff --git a/SB3UtilityPP/xaParser.cs b/SB3UtilityPP/xaParser.cs
new file mode 100644
index 0000000..6fd294d
--- /dev/null
+++ b/SB3UtilityPP/xaParser.cs
@@ -0,0 +1,377 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using SlimDX;
+
+namespace SB3Utility
+{
+ public class xaParser : IWriteFile
+ {
+ public byte[] Header { get; set; }
+ public xaMaterialSection MaterialSection { get; set; }
+ public xaSection2 Section2 { get; set; }
+ public xaMorphSection MorphSection { get; set; }
+ public xaSection4 Section4 { get; set; }
+ public xaAnimationSection AnimationSection { get; set; }
+ public byte[] Footer { get; set; }
+
+ public int Format { get; protected set; }
+ public string Name { get; set; }
+
+ protected BinaryReader reader;
+
+ public xaParser(Stream stream, string name)
+ : this(stream)
+ {
+ this.Name = name;
+ }
+
+ public xaParser(Stream stream)
+ {
+ using (BinaryReader reader = new BinaryReader(stream))
+ {
+ this.reader = reader;
+
+ byte type = reader.ReadByte();
+ if (type == 0x00)
+ {
+ Format = -1;
+ Section2 = ParseSection2();
+ MorphSection = ParseMorphSection();
+ AnimationSection = ParseAnimationSection();
+ }
+ else if ((type == 0x02) || (type == 0x03))
+ {
+ Header = new byte[5];
+ Header[0] = type;
+ reader.ReadBytes(4).CopyTo(Header, 1);
+ ParseSB3Format();
+ }
+ else if (type == 0x01)
+ {
+ byte[] testBuf = reader.ReadBytes(4);
+ if ((testBuf[0] | testBuf[1] | testBuf[2]) == 0)
+ {
+ Header = new byte[5];
+ Header[0] = type;
+ testBuf.CopyTo(Header, 1);
+ ParseSB3Format();
+ }
+ else
+ {
+ Format = -1;
+ MaterialSection = ParseMaterialSection(BitConverter.ToInt32(testBuf, 0));
+ Section2 = ParseSection2();
+ MorphSection = ParseMorphSection();
+ AnimationSection = ParseAnimationSection();
+ }
+ }
+ else
+ {
+ throw new Exception("Unable to determine .xa format");
+ }
+
+ Footer = reader.ReadToEnd();
+
+ this.reader = null;
+ }
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ if (Header != null)
+ {
+ writer.Write(Header);
+ }
+
+ WriteSection(stream, MaterialSection);
+ WriteSection(stream, Section2);
+ WriteSection(stream, MorphSection);
+ if (Format >= 0x02)
+ {
+ WriteSection(stream, Section4);
+ }
+ WriteSection(stream, AnimationSection);
+
+ writer.Write(Footer);
+ }
+
+ protected void WriteSection(Stream stream, IObjInfo section)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ if (section == null)
+ {
+ writer.Write((byte)0);
+ }
+ else
+ {
+ writer.Write((byte)1);
+ section.WriteTo(stream);
+ }
+ }
+
+ protected void ParseSB3Format()
+ {
+ Format = BitConverter.ToInt32(Header, 0);
+ MaterialSection = ParseMaterialSection();
+ Section2 = ParseSection2();
+ MorphSection = ParseMorphSection();
+ if (Format >= 0x02)
+ {
+ Section4 = ParseSection4();
+ }
+ AnimationSection = ParseAnimationSection();
+ }
+
+ protected xaMaterialSection ParseMaterialSection()
+ {
+ xaMaterialSection section = null;
+ if (reader.ReadByte() == 1)
+ {
+ section = ParseMaterialSection(reader.ReadInt32());
+ }
+ return section;
+ }
+
+ protected xaMaterialSection ParseMaterialSection(int numMaterials)
+ {
+ xaMaterialSection section = new xaMaterialSection();
+ section.MaterialList = new List(numMaterials);
+ for (int i = 0; i < numMaterials; i++)
+ {
+ xaMaterial mat = new xaMaterial();
+ section.MaterialList.Add(mat);
+ mat.Name = reader.ReadName();
+
+ int numColors = reader.ReadInt32();
+ mat.ColorList = new List(numColors);
+ for (int j = 0; j < numColors; j++)
+ {
+ xaMaterialColor color = new xaMaterialColor();
+ mat.ColorList.Add(color);
+
+ color.Diffuse = reader.ReadColor4();
+ color.Ambient = reader.ReadColor4();
+ color.Specular = reader.ReadColor4();
+ color.Emissive = reader.ReadColor4();
+ color.Power = reader.ReadSingle();
+ color.Unknown1 = reader.ReadBytes(4);
+ }
+ }
+ return section;
+ }
+
+ protected xaSection2 ParseSection2()
+ {
+ if (reader.ReadByte() == 0)
+ {
+ return null;
+ }
+
+ xaSection2 section = new xaSection2();
+
+ int numItems = reader.ReadInt32();
+ section.ItemList = new List(numItems);
+ for (int i = 0; i < numItems; i++)
+ {
+ xaSection2Item item = new xaSection2Item();
+ section.ItemList.Add(item);
+
+ item.Unknown1 = reader.ReadBytes(4);
+ item.Name = reader.ReadName();
+ item.Unknown2 = reader.ReadBytes(4);
+ item.Unknown3 = reader.ReadBytes(4);
+
+ int numItemBlocks = reader.ReadInt32();
+ item.Unknown4 = reader.ReadBytes(4);
+ item.ItemBlockList = new List(numItemBlocks);
+ for (int j = 0; j < numItemBlocks; j++)
+ {
+ xaSection2ItemBlock itemBlock = new xaSection2ItemBlock();
+ item.ItemBlockList.Add(itemBlock);
+
+ itemBlock.Unknown1 = reader.ReadBytes(1);
+ itemBlock.Unknown2 = reader.ReadBytes(4);
+ }
+
+ item.Unknown5 = reader.ReadBytes(1);
+ }
+
+ return section;
+ }
+
+ protected xaMorphSection ParseMorphSection()
+ {
+ if (reader.ReadByte() == 0)
+ {
+ return null;
+ }
+
+ xaMorphSection section = new xaMorphSection();
+
+ int numIndexSets = reader.ReadInt32();
+ section.IndexSetList = new List(numIndexSets);
+ for (int i = 0; i < numIndexSets; i++)
+ {
+ xaMorphIndexSet indexSet = new xaMorphIndexSet();
+ section.IndexSetList.Add(indexSet);
+
+ indexSet.Unknown1 = reader.ReadBytes(1);
+
+ int numVertices = reader.ReadInt32();
+ indexSet.MeshIndices = reader.ReadUInt16Array(numVertices);
+ indexSet.MorphIndices = reader.ReadUInt16Array(numVertices);
+
+ indexSet.Name = reader.ReadName();
+ }
+
+ int numKeyframes = reader.ReadInt32();
+ section.KeyframeList = new List(numKeyframes);
+ for (int i = 0; i < numKeyframes; i++)
+ {
+ xaMorphKeyframe keyframe = new xaMorphKeyframe();
+ section.KeyframeList.Add(keyframe);
+
+ int numVertices = reader.ReadInt32();
+ keyframe.PositionList = new List(numVertices);
+ keyframe.NormalList = new List(numVertices);
+ for (int j = 0; j < numVertices; j++)
+ {
+ keyframe.PositionList.Add(reader.ReadVector3());
+ }
+ for (int j = 0; j < numVertices; j++)
+ {
+ keyframe.NormalList.Add(reader.ReadVector3());
+ }
+
+ keyframe.Name = reader.ReadName();
+ }
+
+ int numClips = reader.ReadInt32();
+ section.ClipList = new List(numClips);
+ for (int i = 0; i < numClips; i++)
+ {
+ xaMorphClip clip = new xaMorphClip();
+ section.ClipList.Add(clip);
+
+ clip.MeshName = reader.ReadName();
+ clip.Name = reader.ReadName();
+
+ int numKeyframeRefs = reader.ReadInt32();
+ clip.KeyframeRefList = new List(numKeyframeRefs);
+ for (int j = 0; j < numKeyframeRefs; j++)
+ {
+ xaMorphKeyframeRef keyframeRef = new xaMorphKeyframeRef();
+ clip.KeyframeRefList.Add(keyframeRef);
+
+ keyframeRef.Unknown1 = reader.ReadBytes(1);
+ keyframeRef.Index = reader.ReadInt32();
+ keyframeRef.Unknown2 = reader.ReadBytes(1);
+ keyframeRef.Name = reader.ReadName();
+ }
+
+ clip.Unknown1 = reader.ReadBytes(4);
+ }
+
+ return section;
+ }
+
+ protected xaSection4 ParseSection4()
+ {
+ if (reader.ReadByte() == 0)
+ {
+ return null;
+ }
+
+ xaSection4 section = new xaSection4();
+
+ int numItemLists = reader.ReadInt32();
+ section.ItemListList = new List>(numItemLists);
+ for (int i = 0; i < numItemLists; i++)
+ {
+ int numItems = reader.ReadInt32();
+ List itemList = new List(numItems);
+ section.ItemListList.Add(itemList);
+
+ for (int j = 0; j < numItems; j++)
+ {
+ xaSection4Item item = new xaSection4Item();
+ item.Unknown1 = reader.ReadBytes(104);
+ item.Unknown2 = reader.ReadBytes(4);
+ item.Unknown3 = reader.ReadBytes(64);
+ }
+ }
+
+ return section;
+ }
+
+ protected xaAnimationSection ParseAnimationSection()
+ {
+ if (reader.ReadByte() == 0)
+ {
+ return null;
+ }
+
+ xaAnimationSection section = new xaAnimationSection();
+
+ int numClips;
+ if (Format == 0x03)
+ {
+ numClips = 1024;
+ }
+ else
+ {
+ numClips = 512;
+ }
+
+ section.ClipList = new List(numClips);
+ for (int i = 0; i < numClips; i++)
+ {
+ xaAnimationClip clip = new xaAnimationClip();
+ section.ClipList.Add(clip);
+
+ clip.Name = reader.ReadName(64);
+ clip.Speed = reader.ReadSingle();
+ clip.Unknown1 = reader.ReadBytes(4);
+ clip.Start = reader.ReadSingle();
+ clip.End = reader.ReadSingle();
+ clip.Unknown2 = reader.ReadBytes(1);
+ clip.Unknown3 = reader.ReadBytes(1);
+ clip.Unknown4 = reader.ReadBytes(1);
+ clip.Next = reader.ReadInt32();
+ clip.Unknown5 = reader.ReadBytes(1);
+ clip.Unknown6 = reader.ReadBytes(4);
+ clip.Unknown7 = reader.ReadBytes(16);
+ }
+
+ int numTracks = reader.ReadInt32();
+ section.TrackList = new List(numTracks);
+ for (int i = 0; i < numTracks; i++)
+ {
+ xaAnimationTrack track = new xaAnimationTrack();
+ section.TrackList.Add(track);
+
+ track.Name = reader.ReadName();
+ int numKeyframes = reader.ReadInt32();
+ track.Unknown1 = reader.ReadBytes(4);
+
+ track.KeyframeList = new List(numKeyframes);
+ for (int j = 0; j < numKeyframes; j++)
+ {
+ xaAnimationKeyframe keyframe = new xaAnimationKeyframe();
+ track.KeyframeList.Add(keyframe);
+
+ keyframe.Index = reader.ReadInt32();
+ keyframe.Rotation = reader.ReadQuaternion();
+ keyframe.Unknown1 = reader.ReadBytes(8);
+ keyframe.Translation = reader.ReadVector3();
+ keyframe.Scaling = reader.ReadVector3();
+ }
+ }
+
+ return section;
+ }
+ }
+}
diff --git a/SB3UtilityPP/xxFormatDiff.txt b/SB3UtilityPP/xxFormatDiff.txt
new file mode 100644
index 0000000..839a77a
--- /dev/null
+++ b/SB3UtilityPP/xxFormatDiff.txt
@@ -0,0 +1,38 @@
+xx
+==
+Header: (>= 1) 26, (< 1) 21
+Footer: (>= 2) 10
+
+Frame
+=====
+Unknown1: (>= 7) 32, (< 7) 16
+Unknown2: (>= 7) 64, (< 7) 16
+Name2: (>= 6) name
+
+Submesh
+=======
+Unknown1: (>= 7) 64, (< 7) 16
+Unknown2: (>= 7) 20
+Unknown3: (>= 2) 100
+Unknown4: (>= 7) 284, (>= 3) 64
+Unknown5: (>= 8) 21, (>= 5) 20
+Unknown6: (>= 6 && < 7) 28
+
+Vertex
+======
+Index: (>= 4) ushort, (< 4) int
+Unknown1: (>= 4) 20
+
+Material
+========
+Unknown1: (>= 0) 88, (< 0) 4
+
+
+Files affected:
+xxParser.cs
+xxReplace.cs: Create()
+Structures.cs: WriteTo(), Clone()
+xxOps.cs: CopyUnknowns(), ConvertFormat()
+Plugins\pp\FormXX.cs
+Plugins\pp\FormXXEditHex.cs
+Plugins\pp\xxEditor.cs
\ No newline at end of file
diff --git a/SB3UtilityPP/xxOps.cs b/SB3UtilityPP/xxOps.cs
new file mode 100644
index 0000000..febe397
--- /dev/null
+++ b/SB3UtilityPP/xxOps.cs
@@ -0,0 +1,1728 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using SlimDX;
+
+namespace SB3Utility
+{
+ public static partial class xx
+ {
+ private class VertexRef
+ {
+ public xxVertex vert;
+ public Vector3 norm;
+ }
+
+ private class VertexRefComparerX : IComparer
+ {
+ public int Compare(VertexRef x, VertexRef y)
+ {
+ return System.Math.Sign(x.vert.Position[0] - y.vert.Position[0]);
+ }
+ }
+
+ private class VertexRefComparerY : IComparer
+ {
+ public int Compare(VertexRef x, VertexRef y)
+ {
+ return System.Math.Sign(x.vert.Position[1] - y.vert.Position[1]);
+ }
+ }
+
+ private class VertexRefComparerZ : IComparer
+ {
+ public int Compare(VertexRef x, VertexRef y)
+ {
+ return System.Math.Sign(x.vert.Position[2] - y.vert.Position[2]);
+ }
+ }
+
+ public static bool IsSkinned(xxMesh mesh)
+ {
+ return (mesh.BoneList.Count > 0);
+ }
+
+ public static void ExportTexture(xxTexture tex, string path)
+ {
+ FileInfo file = new FileInfo(path);
+ DirectoryInfo dir = file.Directory;
+ if (!dir.Exists)
+ {
+ dir.Create();
+ }
+ using (FileStream stream = file.Create())
+ {
+ ExportTexture(tex, stream);
+ }
+ }
+
+ public static void ExportTexture(xxTexture tex, Stream stream)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+ if (Path.GetExtension(tex.Name).ToLowerInvariant() == ".bmp")
+ {
+ writer.Write((byte)'B');
+ writer.Write((byte)'M');
+ writer.Write(tex.ImageData, 2, tex.ImageData.Length - 2);
+ }
+ else
+ {
+ writer.Write(tex.ImageData);
+ }
+ }
+
+ public static ImportedTexture ImportedTexture(xxTexture texture)
+ {
+ ImportedTexture importedTex = new ImportedTexture();
+ importedTex.Name = texture.Name;
+ importedTex.Data = (byte[])texture.ImageData.Clone();
+ if (Path.GetExtension(texture.Name).ToLowerInvariant() == ".bmp")
+ {
+ importedTex.Data[0] = (byte)'B';
+ importedTex.Data[1] = (byte)'M';
+ }
+ return importedTex;
+ }
+
+ public static List ImportedVertexList(List vertList, bool skinned)
+ {
+ List importedList = new List(vertList.Count);
+ for (int i = 0; i < vertList.Count; i++)
+ {
+ ImportedVertex importedVert = new ImportedVertex();
+ importedList.Add(importedVert);
+ importedVert.Position = vertList[i].Position;
+ importedVert.Normal = vertList[i].Normal;
+ importedVert.UV = vertList[i].UV;
+ importedVert.BoneIndices = vertList[i].BoneIndices;
+ importedVert.Weights = vertList[i].Weights4(skinned);
+ }
+ return importedList;
+ }
+
+ public static List ImportedFaceList(List faceList)
+ {
+ List importedList = new List(faceList.Count);
+ for (int i = 0; i < faceList.Count; i++)
+ {
+ ImportedFace importedFace = new ImportedFace();
+ importedList.Add(importedFace);
+ importedFace.VertexIndices = new int[3];
+ for (int j = 0; j < 3; j++)
+ {
+ importedFace.VertexIndices[j] = faceList[i].VertexIndices[j];
+ }
+ }
+ return importedList;
+ }
+
+ private class VertexCompare : IComparable
+ {
+ public int index;
+ public float[] position;
+ public float[] normal;
+ public byte[] boneIndices;
+ public float[] weights;
+
+ public VertexCompare(int index, Vector3 position, Vector3 normal, byte[] boneIndices, float[] weights)
+ {
+ this.index = index;
+ this.position = new float[] { position.X, position.Y, position.Z };
+ this.normal = new float[] { normal.X, normal.Y, normal.Z };
+ this.boneIndices = boneIndices;
+ this.weights = weights;
+ }
+
+ public int CompareTo(VertexCompare other)
+ {
+ int diff = Diff(this.position, other.position);
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ diff = Diff(this.normal, other.normal);
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ diff = Diff(this.boneIndices, other.boneIndices);
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ diff = Diff(this.weights, other.weights);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ return 0;
+ }
+
+ private static int Diff(float[] a, float[] b)
+ {
+ for (int i = 0; i < a.Length; i++)
+ {
+ int diff = System.Math.Sign(a[i] - b[i]);
+ if (diff != 0)
+ {
+ return diff;
+ }
+ }
+ return 0;
+ }
+
+ private static int Diff(byte[] a, byte[] b)
+ {
+ for (int i = 0; i < a.Length; i++)
+ {
+ int diff = a[i] - b[i];
+ if (diff != 0)
+ {
+ return diff;
+ }
+ }
+ return 0;
+ }
+ }
+
+ public static HashSet SearchHierarchy(xxFrame frame, HashSet meshNames)
+ {
+ HashSet exportFrames = new HashSet();
+ SearchHierarchy(frame, frame, meshNames, exportFrames);
+ return exportFrames;
+ }
+
+ static void SearchHierarchy(xxFrame root, xxFrame frame, HashSet meshNames, HashSet exportFrames)
+ {
+ if (frame.Mesh != null)
+ {
+ if (meshNames.Contains(frame.Name))
+ {
+ xxFrame parent = frame;
+ while (parent != null)
+ {
+ exportFrames.Add(parent.Name);
+ parent = (xxFrame)parent.Parent;
+ }
+
+ xxMesh meshListSome = frame.Mesh;
+ List boneList = meshListSome.BoneList;
+ for (int i = 0; i < boneList.Count; i++)
+ {
+ if (!exportFrames.Contains(boneList[i].Name))
+ {
+ xxFrame boneParent = FindFrame(boneList[i].Name, root);
+ while (boneParent != null)
+ {
+ exportFrames.Add(boneParent.Name);
+ boneParent = (xxFrame)boneParent.Parent;
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < frame.Count; i++)
+ {
+ SearchHierarchy(root, frame[i], meshNames, exportFrames);
+ }
+ }
+
+ public static xxFrame FindFrame(string name, xxFrame root)
+ {
+ xxFrame frame = root;
+ if ((frame != null) && (frame.Name == name))
+ {
+ return frame;
+ }
+
+ for (int i = 0; i < root.Count; i++)
+ {
+ if ((frame = FindFrame(name, root[i])) != null)
+ {
+ return frame;
+ }
+ }
+
+ return null;
+ }
+
+ public static List FindMeshFrames(xxFrame frame, List nameList)
+ {
+ List frameList = new List(nameList.Count);
+ FindMeshFrames(frame, frameList, nameList);
+ return frameList;
+ }
+
+ static void FindMeshFrames(xxFrame frame, List frameList, List nameList)
+ {
+ if ((frame.Mesh != null) && nameList.Contains(frame.Name))
+ {
+ frameList.Add(frame);
+ }
+
+ for (int i = 0; i < frame.Count; i++)
+ {
+ FindMeshFrames(frame[i], frameList, nameList);
+ }
+ }
+
+ public static List FindMeshFrames(xxFrame frame)
+ {
+ List frameList = new List();
+ FindMeshFrames(frame, frameList);
+ return frameList;
+ }
+
+ static void FindMeshFrames(xxFrame frame, List frameList)
+ {
+ if (frame.Mesh != null)
+ {
+ frameList.Add(frame);
+ }
+
+ for (int i = 0; i < frame.Count; i++)
+ {
+ FindMeshFrames(frame[i], frameList);
+ }
+ }
+
+ public static xxBone FindBone(List boneList, string name)
+ {
+ foreach (xxBone bone in boneList)
+ {
+ if (bone.Name == name)
+ return bone;
+ }
+
+ return null;
+ }
+
+ public static void SetBoundingBox(xxFrame frame)
+ {
+ if (frame.Mesh == null)
+ {
+ frame.Bounds = new BoundingBox();
+ }
+ else
+ {
+ xxMesh meshList = frame.Mesh;
+ Vector3 min = new Vector3(Single.MaxValue, Single.MaxValue, Single.MaxValue);
+ Vector3 max = new Vector3(Single.MinValue, Single.MinValue, Single.MinValue);
+ for (int i = 0; i < meshList.SubmeshList.Count; i++)
+ {
+ List vertList = meshList.SubmeshList[i].VertexList;
+ for (int j = 0; j < vertList.Count; j++)
+ {
+ xxVertex vert = vertList[j];
+ Vector3 pos = vert.Position;
+ min = Vector3.Minimize(min, pos);
+ max = Vector3.Maximize(max, pos);
+ }
+ }
+ frame.Bounds = new BoundingBox(min, max);
+ }
+ }
+
+ public static void CopyUnknowns(xxFrame src, xxFrame dest)
+ {
+ dest.Name2 = src.Name2;
+ dest.Unknown1 = (byte[])src.Unknown1.Clone();
+ dest.Unknown2 = (byte[])src.Unknown2.Clone();
+ }
+
+ public static void CreateUnknowns(xxFrame frame, int xxFormat)
+ {
+ if (xxFormat >= 7)
+ {
+ frame.Unknown1 = new byte[32];
+ frame.Unknown2 = new byte[64];
+ }
+ else
+ {
+ frame.Unknown1 = new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ frame.Unknown2 = new byte[16] { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ }
+
+ if (xxFormat >= 6)
+ {
+ frame.Name2 = String.Empty;
+ }
+ }
+
+ public static void CopyUnknowns(xxMesh src, xxMesh dest)
+ {
+ dest.NumVector2PerVertex = src.NumVector2PerVertex;
+ dest.VertexListDuplicateUnknown = (byte[])src.VertexListDuplicateUnknown.Clone();
+ }
+
+ public static void CreateUnknowns(xxMesh mesh)
+ {
+ mesh.NumVector2PerVertex = 0;
+ mesh.VertexListDuplicateUnknown = IsSkinned(mesh)
+ ? new byte[] { 0x1C, 0x11, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00 }
+ : new byte[] { 0x12, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 };
+ }
+
+ public static void CopyUnknowns(xxSubmesh src, xxSubmesh dest, int xxFormat, byte xxMeshNumUnknownA)
+ {
+ List srcVertexList = src.VertexList;
+ List destVertexList = dest.VertexList;
+
+ dest.Unknown1 = (byte[])src.Unknown1.Clone();
+
+ if (xxFormat >= 7)
+ {
+ dest.Unknown2 = (byte[])src.Unknown2.Clone();
+ }
+
+ if (xxMeshNumUnknownA > 0)
+ {
+ dest.Vector2Lists = new List>(destVertexList.Count);
+ for (int j = 0; (j < destVertexList.Count) && (j < srcVertexList.Count); j++)
+ {
+ List vectorList = new List(xxMeshNumUnknownA);
+ dest.Vector2Lists.Add(vectorList);
+ for (byte k = 0; k < xxMeshNumUnknownA; k++)
+ {
+ vectorList.Add(src.Vector2Lists[j][k]);
+ }
+ }
+ for (int j = srcVertexList.Count; j < destVertexList.Count; j++)
+ {
+ List vectorList = new List(xxMeshNumUnknownA);
+ dest.Vector2Lists.Add(vectorList);
+ for (byte k = 0; k < xxMeshNumUnknownA; k++)
+ {
+ vectorList.Add(new Vector2());
+ }
+ }
+ }
+
+ if (xxFormat >= 2)
+ {
+ dest.Unknown3 = (byte[])src.Unknown3.Clone();
+ }
+
+ if (xxFormat >= 7)
+ {
+ dest.Unknown4 = (byte[])src.Unknown4.Clone();
+
+ if (xxFormat >= 7)
+ {
+ dest.Unknown5 = (byte[])src.Unknown5.Clone();
+ }
+ }
+ else
+ {
+ if (xxFormat >= 3)
+ {
+ dest.Unknown4 = (byte[])src.Unknown4.Clone();
+ }
+ if (xxFormat >= 5)
+ {
+ dest.Unknown5 = (byte[])src.Unknown5.Clone();
+ }
+ if (xxFormat >= 6)
+ {
+ dest.Unknown6 = (byte[])src.Unknown6.Clone();
+ }
+ }
+
+ if (xxFormat >= 4)
+ {
+ for (int j = 0; j < destVertexList.Count; j++)
+ {
+ if (j < srcVertexList.Count)
+ {
+ destVertexList[j].Unknown1 = (byte[])srcVertexList[j].Unknown1.Clone();
+ }
+ else
+ {
+ destVertexList[j].Unknown1 = new byte[20];
+ }
+ }
+ }
+ }
+
+ public static void CreateUnknowns(xxSubmesh submesh, int xxFormat, byte numVector2PerVertex)
+ {
+ List vertList = submesh.VertexList;
+
+ submesh.Unknown1 = (xxFormat >= 7) ? new byte[64] : new byte[] { 0x0C, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ if (numVector2PerVertex > 0)
+ {
+ submesh.Vector2Lists = new List>(vertList.Count);
+ for (int j = 0; j < vertList.Count; j++)
+ {
+ List vectorList = new List(numVector2PerVertex);
+ submesh.Vector2Lists.Add(vectorList);
+ for (byte k = 0; k < numVector2PerVertex; k++)
+ {
+ vectorList.Add(new Vector2());
+ }
+ }
+ }
+
+ if (xxFormat >= 2)
+ {
+ submesh.Unknown3 = new byte[100];
+ }
+
+ if (xxFormat >= 7)
+ {
+ submesh.Unknown2 = new byte[20];
+ submesh.Unknown4 = new byte[284];
+
+ if (xxFormat >= 8)
+ {
+ submesh.Unknown5 = new byte[21];
+ }
+ }
+ else
+ {
+ if (xxFormat >= 3)
+ {
+ submesh.Unknown4 = new byte[64];
+ }
+ if (xxFormat >= 5)
+ {
+ submesh.Unknown5 = new byte[20];
+ }
+ if (xxFormat >= 6)
+ {
+ submesh.Unknown6 = new byte[28];
+ }
+ }
+
+ if (xxFormat >= 4)
+ {
+ for (int j = 0; j < vertList.Count; j++)
+ {
+ vertList[j].Unknown1 = new byte[20];
+ }
+ }
+ }
+
+ public static void CreateUnknown(xxVertex vertex)
+ {
+ vertex.Unknown1 = new byte[20];
+ }
+
+ public static void CopyUnknowns(xxMaterial src, xxMaterial dest)
+ {
+ dest.Unknown1 = (byte[])src.Unknown1.Clone();
+ for (int j = 0; j < src.Textures.Length; j++)
+ {
+ dest.Textures[j].Unknown1 = (byte[])src.Textures[j].Unknown1.Clone();
+ }
+ }
+
+ public static void CreateUnknowns(xxMaterial mat, int xxFormat)
+ {
+ byte[] buf = (xxFormat < 0) ? new byte[4] : new byte[88];
+ if (xxFormat < 0)
+ {
+ buf = new byte[4];
+ }
+ else
+ {
+ buf = new byte[88];
+ }
+ buf[1] = 0x01;
+ buf[2] = 0x02;
+ buf[3] = 0x03;
+ mat.Unknown1 = buf;
+
+ for (int i = 0; i < mat.Textures.Length; i++)
+ {
+ var matTex = mat.Textures[i];
+ byte[] matTexBytes = new byte[16];
+ if (matTex.Name != String.Empty)
+ {
+ string ext = Path.GetExtension(matTex.Name).ToLowerInvariant();
+ if ((i == 0) && (ext == ".tga"))
+ {
+ matTexBytes[0] = 0x01;
+ }
+ if ((i == 1) && (ext == ".bmp"))
+ {
+ matTexBytes[0] = 0x07;
+ matTexBytes[3] = 0x03;
+ }
+ }
+ matTex.Unknown1 = matTexBytes;
+ }
+ }
+
+ public static void CopyUnknowns(xxTexture src, xxTexture dest)
+ {
+ dest.Unknown1 = (byte[])src.Unknown1.Clone();
+ }
+
+ public static void CreateUnknowns(xxTexture tex)
+ {
+ tex.Unknown1 = new byte[4];
+ }
+
+ public static List CreateVertexListDup(List submeshList)
+ {
+ List vertListDup = new List(UInt16.MaxValue);
+ List vertCompareList = new List(UInt16.MaxValue);
+ for (int i = 0; i < submeshList.Count; i++)
+ {
+ xxSubmesh meshObj = submeshList[i];
+ List vertList = meshObj.VertexList;
+ for (int j = 0; j < vertList.Count; j++)
+ {
+ xxVertex vert = vertList[j];
+ VertexCompare vertCompare = new VertexCompare(vertCompareList.Count, vert.Position, vert.Normal, vert.BoneIndices, vert.Weights3);
+ int idx = vertCompareList.BinarySearch(vertCompare);
+ if (idx < 0)
+ {
+ vertCompareList.Insert(~idx, vertCompare);
+ vert.Index = vertCompareList.Count;
+ vertListDup.Add(vert.Clone());
+ }
+ else
+ {
+ vert.Index = vertCompareList[idx].index;
+ }
+ }
+ }
+ vertListDup.TrimExcess();
+ if (vertListDup.Count >= 64*1024)
+ {
+ throw new Exception("VertexListDuplicate would become unstorable if it exceeded 65535 vertices!");
+ }
+ return vertListDup;
+ }
+
+ public static List MergeBoneList(List boneList1, List boneList2, out byte[] boneList2IdxMap)
+ {
+ boneList2IdxMap = new byte[boneList2.Count];
+ Dictionary boneDic = new Dictionary();
+ List mergedList = new List(boneList1.Count + boneList2.Count);
+ for (int i = 0; i < boneList1.Count; i++)
+ {
+ xxBone xxBone = boneList1[i].Clone();
+ boneDic.Add(xxBone.Name, i);
+ mergedList.Add(xxBone);
+ }
+ for (int i = 0; i < boneList2.Count; i++)
+ {
+ xxBone xxBone = boneList2[i].Clone();
+ int boneIdx;
+ if (boneDic.TryGetValue(xxBone.Name, out boneIdx))
+ {
+ mergedList[boneIdx] = xxBone;
+ }
+ else
+ {
+ boneIdx = mergedList.Count;
+ mergedList.Add(xxBone);
+ boneDic.Add(xxBone.Name, boneIdx);
+ }
+ xxBone.Index = boneIdx;
+ boneList2IdxMap[i] = (byte)boneIdx;
+ }
+ return mergedList;
+ }
+
+ public static void MergeFrame(xxFrame oldFrame, xxFrame newFrame)
+ {
+ for (int i = oldFrame.Count - 1; i >= 0; i--)
+ {
+ xxFrame oldChild = oldFrame[i];
+ xxFrame newChild = null;
+ for (int j = 0; j < newFrame.Count; j++)
+ {
+ if (oldChild.Name == newFrame[j].Name)
+ {
+ newChild = newFrame[j];
+ break;
+ }
+ }
+
+ if (newChild == null)
+ {
+ newFrame.InsertChild(0, oldChild.Clone(true, true, null));
+ }
+ else
+ {
+ if ((newChild.Mesh == null) && (oldChild.Mesh != null))
+ {
+ newChild.Mesh = oldChild.Mesh.Clone(true, true, true);
+ newChild.Bounds = oldChild.Bounds;
+ }
+ CopyUnknowns(oldChild, newChild);
+
+ MergeFrame(oldChild, newChild);
+ }
+ }
+ }
+
+ public static void ConvertFormat(xxParser parser, int destFormat)
+ {
+ int srcFormat = parser.Format;
+ if ((srcFormat < 1) && (destFormat >= 1))
+ {
+ byte[] headerBuf = new byte[26];
+ headerBuf[0] = (byte)destFormat;
+ parser.Header.CopyTo(headerBuf, 5);
+ parser.Header = headerBuf;
+ }
+ else if ((srcFormat >= 1) && (destFormat < 1))
+ {
+ byte[] headerBuf = new byte[21];
+ Array.Copy(parser.Header, 5, headerBuf, 0, headerBuf.Length);
+ parser.Header = headerBuf;
+ }
+ if (destFormat >= 1)
+ {
+ MemoryStream stream = new MemoryStream(parser.Header);
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ writer.Write(destFormat);
+ }
+ }
+
+ ConvertFormat(parser.Frame, srcFormat, destFormat);
+
+ for (int i = 0; i < parser.MaterialList.Count; i++)
+ {
+ ConvertFormat(parser.MaterialList[i], srcFormat, destFormat);
+ }
+
+ if ((srcFormat < 2) && (destFormat >= 2))
+ {
+ parser.Footer = new byte[10];
+ }
+ else if ((srcFormat >= 2) && (destFormat < 2))
+ {
+ parser.Footer = null;
+ }
+
+ parser.Format = destFormat;
+ }
+
+ public static void ConvertFormat(xxFrame frame, int srcFormat, int destFormat)
+ {
+ if ((srcFormat < 7) && (destFormat >= 7))
+ {
+ byte[] unknown1 = frame.Unknown1;
+ frame.Unknown1 = new byte[32];
+ Array.Copy(unknown1, frame.Unknown1, unknown1.Length);
+
+ byte[] unknown2 = frame.Unknown2;
+ frame.Unknown2 = new byte[64];
+ Array.Copy(unknown2, frame.Unknown2, unknown2.Length);
+ }
+ else if ((srcFormat >= 7) && (destFormat < 7))
+ {
+ byte[] unknown1 = frame.Unknown1;
+ frame.Unknown1 = new byte[16];
+ Array.Copy(unknown1, frame.Unknown1, frame.Unknown1.Length);
+
+ byte[] unknown2 = frame.Unknown2;
+ frame.Unknown2 = new byte[16];
+ Array.Copy(unknown2, frame.Unknown2, frame.Unknown2.Length);
+ }
+
+ if ((srcFormat < 6) && (destFormat >= 6))
+ {
+ frame.Name2 = String.Empty;
+ }
+ else if ((srcFormat >= 6) && (destFormat < 6))
+ {
+ frame.Name2 = null;
+ }
+
+ if (frame.Mesh != null)
+ {
+ for (int i = 0; i < frame.Mesh.SubmeshList.Count; i++)
+ {
+ xxSubmesh submesh = frame.Mesh.SubmeshList[i];
+
+ if ((srcFormat < 7) && (destFormat >= 7))
+ {
+ byte[] unknown1 = submesh.Unknown1;
+ submesh.Unknown1 = new byte[64];
+ Array.Copy(unknown1, submesh.Unknown1, unknown1.Length);
+
+ submesh.Unknown2 = new byte[20];
+ }
+ else if ((srcFormat >= 7) && (destFormat < 7))
+ {
+ byte[] unknown1 = submesh.Unknown1;
+ submesh.Unknown1 = new byte[16];
+ Array.Copy(unknown1, submesh.Unknown1, submesh.Unknown1.Length);
+
+ submesh.Unknown2 = null;
+ }
+
+ if ((srcFormat < 2) && (destFormat >= 2))
+ {
+ submesh.Unknown3 = new byte[100];
+ }
+ else if ((srcFormat >= 2) && (destFormat < 2))
+ {
+ submesh.Unknown3 = null;
+ }
+
+ if ((srcFormat < 3) && ((destFormat >= 3) && (destFormat < 7)))
+ {
+ submesh.Unknown4 = new byte[64];
+ }
+ else if ((srcFormat < 3) && (destFormat >= 7))
+ {
+ submesh.Unknown4 = new byte[284];
+ }
+ else if (((srcFormat >= 3) && (srcFormat < 7)) && (destFormat < 3))
+ {
+ submesh.Unknown4 = null;
+ }
+ else if (((srcFormat >= 3) && (srcFormat < 7)) && (destFormat >= 7))
+ {
+ byte[] unknown4 = submesh.Unknown4;
+ submesh.Unknown4 = new byte[284];
+ Array.Copy(unknown4, submesh.Unknown4, unknown4.Length);
+ }
+ else if ((srcFormat >= 7) && (destFormat < 3))
+ {
+ submesh.Unknown4 = null;
+ }
+ else if ((srcFormat >= 7) && ((destFormat >= 3) && (destFormat < 7)))
+ {
+ byte[] unknown4 = submesh.Unknown4;
+ submesh.Unknown4 = new byte[64];
+ Array.Copy(unknown4, submesh.Unknown4, submesh.Unknown4.Length);
+ }
+
+ if ((srcFormat < 5) && ((destFormat >= 5) && (destFormat < 8)))
+ {
+ submesh.Unknown5 = new byte[20];
+ }
+ else if ((srcFormat < 8) && (destFormat >= 8))
+ {
+ submesh.Unknown5 = new byte[21];
+ }
+ else if (((srcFormat >= 5) && (srcFormat < 8)) && (destFormat < 5))
+ {
+ submesh.Unknown5 = null;
+ }
+ else if ((srcFormat >= 8) && (destFormat < 5))
+ {
+ submesh.Unknown5 = null;
+ }
+ else if ((srcFormat >= 8) && ((destFormat >= 5) && (destFormat < 8)))
+ {
+ byte[] unknown5 = submesh.Unknown5;
+ submesh.Unknown5 = new byte[20];
+ Array.Copy(unknown5, submesh.Unknown5, submesh.Unknown5.Length);
+ }
+
+ if ((srcFormat == 6) && (destFormat != 6))
+ {
+ submesh.Unknown6 = null;
+ }
+ else if ((srcFormat != 6) && (destFormat == 6))
+ {
+ submesh.Unknown6 = new byte[28];
+ }
+
+ ConvertFormat(submesh.VertexList, srcFormat, destFormat);
+ }
+
+ ConvertFormat(frame.Mesh.VertexListDuplicate, srcFormat, destFormat);
+ }
+
+ for (int i = 0; i < frame.Count; i++)
+ {
+ ConvertFormat(frame[i], srcFormat, destFormat);
+ }
+ }
+
+ public static void ConvertFormat(List vertexList, int srcFormat, int destFormat)
+ {
+ if ((srcFormat < 4) && (destFormat >= 4))
+ {
+ for (int i = 0; i < vertexList.Count; i++)
+ {
+ vertexList[i] = ConvertVertex((xxVertexInt)vertexList[i]);
+ }
+ }
+ else if ((srcFormat >= 4) && (destFormat < 4))
+ {
+ for (int i = 0; i < vertexList.Count; i++)
+ {
+ vertexList[i] = ConvertVertex((xxVertexUShort)vertexList[i]);
+ }
+ }
+ }
+
+ public static xxVertexUShort ConvertVertex(xxVertexInt vertex)
+ {
+ xxVertexUShort vertShort = new xxVertexUShort();
+ vertShort.Index = vertex.Index;
+ vertShort.Position = vertex.Position;
+ vertShort.Weights3 = (float[])vertex.Weights3.Clone();
+ vertShort.BoneIndices = (byte[])vertex.BoneIndices.Clone();
+ vertShort.Normal = vertex.Normal;
+ vertShort.UV = (float[])vertex.UV.Clone();
+ vertShort.Unknown1 = new byte[20];
+ return vertShort;
+ }
+
+ public static xxVertexInt ConvertVertex(xxVertexUShort vertex)
+ {
+ xxVertexInt vertInt = new xxVertexInt();
+ vertInt.Index = vertex.Index;
+ vertInt.Position = vertex.Position;
+ vertInt.Weights3 = (float[])vertex.Weights3.Clone();
+ vertInt.BoneIndices = (byte[])vertex.BoneIndices.Clone();
+ vertInt.Normal = vertex.Normal;
+ vertInt.UV = (float[])vertex.UV.Clone();
+ return vertInt;
+ }
+
+ public static void ConvertFormat(xxMaterial mat, int srcFormat, int destFormat)
+ {
+ if ((srcFormat < 0) && (destFormat >= 0))
+ {
+ byte[] unknown1 = mat.Unknown1;
+ mat.Unknown1 = new byte[88];
+ Array.Copy(unknown1, mat.Unknown1, unknown1.Length);
+ }
+ else if ((srcFormat >= 0) && (destFormat < 0))
+ {
+ byte[] unknown1 = mat.Unknown1;
+ mat.Unknown1 = new byte[4];
+ Array.Copy(unknown1, mat.Unknown1, mat.Unknown1.Length);
+ }
+ }
+
+ public static void SnapBorders(List sourceMeshList, xxMesh targetMesh, List targetSubmeshes, float tolerance, bool position, bool normal, bool bonesAndWeights, bool uv)
+ {
+ List targetVertices = new List();
+ foreach (xxSubmesh submesh in targetSubmeshes)
+ {
+ SortedDictionary edges = new SortedDictionary();
+ foreach (xxFace face in submesh.FaceList)
+ {
+ List sortedIndices = new List(3);
+ sortedIndices.AddRange(face.VertexIndices);
+ sortedIndices.Sort();
+ for (ushort i = 0; i < 3; i++)
+ {
+ ushort v1;
+ ushort v2;
+ if (i < 2)
+ {
+ v1 = sortedIndices[i];
+ v2 = sortedIndices[i + 1];
+ }
+ else
+ {
+ v1 = sortedIndices[0];
+ v2 = sortedIndices[2];
+ }
+ UInt32 edge = ((UInt32)v2 << 16) | v1;
+ if (edges.ContainsKey(edge))
+ {
+ edges[edge]++;
+ }
+ else
+ {
+ edges.Add(edge, 1);
+ }
+ }
+ }
+ HashSet vertIndices = new HashSet();
+ foreach (KeyValuePair edge in edges)
+ {
+ if (edge.Value == 1)
+ {
+ vertIndices.Add((ushort)(edge.Key >> 16));
+ vertIndices.Add((ushort)edge.Key);
+ }
+ }
+ foreach (ushort idx in vertIndices)
+ {
+ targetVertices.Add(submesh.VertexList[idx]);
+ }
+ }
+
+ xxVertex[] nearestVertMap = new xxVertex[targetVertices.Count];
+ float[] nearestSqrDist = new float[targetVertices.Count];
+ List[] nearestBoneList = new List[targetVertices.Count];
+ foreach (xxMesh srcMesh in sourceMeshList)
+ {
+ if (srcMesh.VertexListDuplicate != null)
+ {
+ FindNearestVerticesForSnapping(srcMesh.VertexListDuplicate, srcMesh.BoneList, targetVertices, tolerance, nearestVertMap, nearestSqrDist, nearestBoneList);
+ }
+ else
+ {
+ foreach (xxSubmesh srcSubmesh in srcMesh.SubmeshList)
+ {
+ FindNearestVerticesForSnapping(srcSubmesh.VertexList, srcMesh.BoneList, targetVertices, tolerance, nearestVertMap, nearestSqrDist, nearestBoneList);
+ }
+ }
+ }
+ ushort count = 0;
+ foreach (xxVertex vert in nearestVertMap)
+ {
+ if (vert == null)
+ {
+ count++;
+ }
+ }
+ if (count > 0)
+ {
+ Report.ReportLog("Warning! No vertex found in source mesh(es) for " + count + " of " + targetVertices.Count + " target vertices leaving those unsnapped.");
+ }
+
+ if (position)
+ {
+ for (int i = 0; i < targetVertices.Count; i++)
+ {
+ if (nearestVertMap[i] != null)
+ {
+ targetVertices[i].Position = nearestVertMap[i].Position;
+ }
+ }
+ }
+ if (bonesAndWeights)
+ {
+ foreach (xxMesh srcMesh in sourceMeshList)
+ {
+ byte[] boneMap = null;
+ foreach (List boneList in nearestBoneList)
+ {
+ if (srcMesh.BoneList == boneList)
+ {
+ boneMap = new byte[srcMesh.BoneList.Count];
+ for (byte i = 0; i < boneList.Count; i++)
+ {
+ xxBone srcBone = boneList[i];
+ boneMap[i] = 0xFF;
+ for (byte j = 0; j < targetMesh.BoneList.Count; j++)
+ {
+ xxBone bone = targetMesh.BoneList[j];
+ if (srcBone.Name == bone.Name)
+ {
+ boneMap[i] = j;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (boneMap == null)
+ {
+ continue;
+ }
+
+ for (int i = 0; i < targetVertices.Count; i++)
+ {
+ if (nearestBoneList[i] == srcMesh.BoneList)
+ {
+ xxVertex srcVert = nearestVertMap[i];
+ xxVertex vert = targetVertices[i];
+ for (int j = 0; j < 4; j++)
+ {
+ byte boneIdx = srcVert.BoneIndices[j];
+ if (boneIdx != 0xFF)
+ {
+ if (boneMap[boneIdx] == 0xFF)
+ {
+ xxBone srcBone = srcMesh.BoneList[boneIdx];
+ xxBone newBone = new xxBone();
+ newBone.Index = targetMesh.BoneList.Count;
+ newBone.Name = (string)srcBone.Name.Clone();
+ newBone.Matrix = srcBone.Matrix;
+ targetMesh.BoneList.Add(newBone);
+ boneMap[boneIdx] = (byte)newBone.Index;
+ }
+ boneIdx = boneMap[boneIdx];
+ }
+ vert.BoneIndices[j] = boneIdx;
+ }
+ vert.Weights3 = (float[])srcVert.Weights3.Clone();
+ }
+ }
+ }
+ }
+ if (normal)
+ {
+ for (int i = 0; i < targetVertices.Count; i++)
+ {
+ if (nearestVertMap[i] != null)
+ {
+ targetVertices[i].Normal = nearestVertMap[i].Normal;
+ }
+ }
+ }
+ if (uv)
+ {
+ for (int i = 0; i < targetVertices.Count; i++)
+ {
+ if (nearestVertMap[i] != null)
+ {
+ targetVertices[i].UV = (float[])nearestVertMap[i].UV.Clone();
+ }
+ }
+ }
+ targetMesh.VertexListDuplicate = CreateVertexListDup(targetMesh.SubmeshList);
+ }
+
+ private static void FindNearestVerticesForSnapping(List srcVertices, List srcBoneList, List targetVertices, float tolerance, xxVertex[] nearestVertMap, float[] nearestSqrDist, List[] nearestBoneList)
+ {
+ for (int i = 0; i < targetVertices.Count; i++)
+ {
+ if (nearestVertMap[i] != null && nearestSqrDist[i] == 0)
+ {
+ continue;
+ }
+ xxVertex vert = targetVertices[i];
+ foreach (xxVertex srcVert in srcVertices)
+ {
+ float srcDist = (vert.Position - srcVert.Position).LengthSquared();
+ if (srcDist < tolerance)
+ {
+ if (nearestVertMap[i] == null)
+ {
+ nearestVertMap[i] = srcVert;
+ nearestSqrDist[i] = srcDist;
+ nearestBoneList[i] = srcBoneList;
+ }
+ else
+ {
+ if (srcDist < nearestSqrDist[i])
+ {
+ nearestVertMap[i] = srcVert;
+ nearestSqrDist[i] = srcDist;
+ nearestBoneList[i] = srcBoneList;
+ }
+ }
+ if (srcDist == 0)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public static void SetNumVector2PerVertex(xxMesh mesh, byte value)
+ {
+ int diff = value - mesh.NumVector2PerVertex;
+
+ if (diff < 0)
+ {
+ if (value == 0)
+ {
+ for (int i = 0; i < mesh.SubmeshList.Count; i++)
+ {
+ mesh.SubmeshList[i].Vector2Lists = null;
+ }
+ }
+ else
+ {
+ diff = Math.Abs(diff);
+ for (int i = 0; i < mesh.SubmeshList.Count; i++)
+ {
+ var submesh = mesh.SubmeshList[i];
+ for (int j = 0; j < submesh.VertexList.Count; j++)
+ {
+ var vectorList = submesh.Vector2Lists[j];
+ vectorList.RemoveRange(vectorList.Count - diff, diff);
+ }
+ }
+ }
+ }
+ else if (diff > 0)
+ {
+ for (int i = 0; i < mesh.SubmeshList.Count; i++)
+ {
+ var submesh = mesh.SubmeshList[i];
+ if (submesh.Vector2Lists == null)
+ {
+ submesh.Vector2Lists = new List>(submesh.VertexList.Count);
+ for (int j = 0; j < submesh.VertexList.Count; j++)
+ {
+ submesh.Vector2Lists.Add(new List(value));
+ for (int k = 0; k < diff; k++)
+ {
+ submesh.Vector2Lists[j].Add(new Vector2());
+ }
+ }
+ }
+ else
+ {
+ for (int j = 0; j < submesh.VertexList.Count; j++)
+ {
+ for (int k = 0; k < diff; k++)
+ {
+ submesh.Vector2Lists[j].Add(new Vector2());
+ }
+ }
+ }
+ }
+ }
+
+ mesh.NumVector2PerVertex = value;
+ }
+
+ public static void CopyNormalsOrder(List src, List