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 + 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 + /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 dest) + { + int len = (src.Count < dest.Count) ? src.Count : dest.Count; + for (int i = 0; i < len; i++) + { + dest[i].Normal = src[i].Normal; + } + } + + public static void CopyNormalsNear(List src, List dest) + { + for (int i = 0; i < dest.Count; i++) + { + var destVert = dest[i]; + var destPos = destVert.Position; + float minDistSq = Single.MaxValue; + xxVertex nearestVert = null; + foreach (xxVertex srcVert in src) + { + var srcPos = srcVert.Position; + float[] diff = new float[] { destPos[0] - srcPos[0], destPos[1] - srcPos[1], destPos[2] - srcPos[2] }; + float distSq = (diff[0] * diff[0]) + (diff[1] * diff[1]) + (diff[2] * diff[2]); + if (distSq < minDistSq) + { + minDistSq = distSq; + nearestVert = srcVert; + } + } + + destVert.Normal = nearestVert.Normal; + } + } + + public static void CopyBonesOrder(List src, List dest) + { + int len = (src.Count < dest.Count) ? src.Count : dest.Count; + for (int i = 0; i < len; i++) + { + dest[i].BoneIndices = (byte[])src[i].BoneIndices.Clone(); + dest[i].Weights3 = (float[])src[i].Weights3.Clone(); + } + } + + public static void CopyBonesNear(List src, List dest) + { + for (int i = 0; i < dest.Count; i++) + { + var destVert = dest[i]; + var destPos = destVert.Position; + float minDistSq = Single.MaxValue; + xxVertex nearestVert = null; + foreach (xxVertex srcVert in src) + { + var srcPos = srcVert.Position; + float[] diff = new float[] { destPos[0] - srcPos[0], destPos[1] - srcPos[1], destPos[2] - srcPos[2] }; + float distSq = (diff[0] * diff[0]) + (diff[1] * diff[1]) + (diff[2] * diff[2]); + if (distSq < minDistSq) + { + minDistSq = distSq; + nearestVert = srcVert; + } + } + + destVert.BoneIndices = (byte[])nearestVert.BoneIndices.Clone(); + destVert.Weights3 = (float[])nearestVert.Weights3.Clone(); + } + } + + public static void CalculateNormals(List submeshes, float threshold) + { + var pairList = new List, List>>(submeshes.Count); + for (int i = 0; i < submeshes.Count; i++) + { + pairList.Add(new Tuple, List>(submeshes[i].FaceList, submeshes[i].VertexList)); + } + CalculateNormals(pairList, threshold); + } + + public static void CalculateNormals(List, List>> pairList, float threshold) + { + if (threshold < 0) + { + VertexRef[][] vertRefArray = new VertexRef[pairList.Count][]; + for (int i = 0; i < pairList.Count; i++) + { + List vertList = pairList[i].Item2; + vertRefArray[i] = new VertexRef[vertList.Count]; + for (int j = 0; j < vertList.Count; j++) + { + xxVertex vert = vertList[j]; + VertexRef vertRef = new VertexRef(); + vertRef.vert = vert; + vertRef.norm = new Vector3(); + vertRefArray[i][j] = vertRef; + } + } + + for (int i = 0; i < pairList.Count; i++) + { + List faceList = pairList[i].Item1; + for (int j = 0; j < faceList.Count; j++) + { + xxFace face = faceList[j]; + Vector3 v1 = vertRefArray[i][face.VertexIndices[1]].vert.Position - vertRefArray[i][face.VertexIndices[2]].vert.Position; + Vector3 v2 = vertRefArray[i][face.VertexIndices[0]].vert.Position - vertRefArray[i][face.VertexIndices[2]].vert.Position; + Vector3 norm = Vector3.Cross(v2, v1); + norm.Normalize(); + for (int k = 0; k < face.VertexIndices.Length; k++) + { + vertRefArray[i][face.VertexIndices[k]].norm += norm; + } + } + } + + for (int i = 0; i < vertRefArray.Length; i++) + { + for (int j = 0; j < vertRefArray[i].Length; j++) + { + Vector3 norm = vertRefArray[i][j].norm; + norm.Normalize(); + vertRefArray[i][j].vert.Normal = norm; + } + } + } + else + { + int vertCount = 0; + for (int i = 0; i < pairList.Count; i++) + { + vertCount += pairList[i].Item2.Count; + } + + VertexRefComparerX vertRefComparerX = new VertexRefComparerX(); + List vertRefListX = new List(vertCount); + VertexRef[][] vertRefArray = new VertexRef[pairList.Count][]; + for (int i = 0; i < pairList.Count; i++) + { + var vertList = pairList[i].Item2; + vertRefArray[i] = new VertexRef[vertList.Count]; + for (int j = 0; j < vertList.Count; j++) + { + xxVertex vert = vertList[j]; + VertexRef vertRef = new VertexRef(); + vertRef.vert = vert; + vertRef.norm = new Vector3(); + vertRefArray[i][j] = vertRef; + vertRefListX.Add(vertRef); + } + } + vertRefListX.Sort(vertRefComparerX); + + for (int i = 0; i < pairList.Count; i++) + { + var faceList = pairList[i].Item1; + for (int j = 0; j < faceList.Count; j++) + { + xxFace face = faceList[j]; + Vector3 v1 = vertRefArray[i][face.VertexIndices[1]].vert.Position - vertRefArray[i][face.VertexIndices[2]].vert.Position; + Vector3 v2 = vertRefArray[i][face.VertexIndices[0]].vert.Position - vertRefArray[i][face.VertexIndices[2]].vert.Position; + Vector3 norm = Vector3.Cross(v2, v1); + norm.Normalize(); + for (int k = 0; k < face.VertexIndices.Length; k++) + { + vertRefArray[i][face.VertexIndices[k]].norm += norm; + } + } + } + + float squaredThreshold = threshold * threshold; + while (vertRefListX.Count > 0) + { + VertexRef vertRef = vertRefListX[vertRefListX.Count - 1]; + List dupList = new List(); + List dupListX = GetAxisDups(vertRef, vertRefListX, 0, threshold, null); + foreach (VertexRef dupRef in dupListX) + { + if (((vertRef.vert.Position.Y - dupRef.vert.Position.Y) <= threshold) && + ((vertRef.vert.Position.Z - dupRef.vert.Position.Z) <= threshold) && + ((vertRef.vert.Position - dupRef.vert.Position).LengthSquared() <= squaredThreshold)) + { + dupList.Add(dupRef); + } + } + vertRefListX.RemoveAt(vertRefListX.Count - 1); + + Vector3 norm = vertRef.norm; + foreach (VertexRef dupRef in dupList) + { + norm += dupRef.norm; + vertRefListX.Remove(dupRef); + } + norm.Normalize(); + + vertRef.vert.Normal = norm; + foreach (VertexRef dupRef in dupList) + { + dupRef.vert.Normal = norm; + vertRefListX.Remove(dupRef); + } + } + } + } + + private static List GetAxisDups(VertexRef vertRef, List compareList, int axis, float threshold, IComparer binaryComparer) + { + List dupList = new List(); + int startIdx; + if (binaryComparer == null) + { + startIdx = compareList.IndexOf(vertRef); + if (startIdx < 0) + { + throw new Exception("Vertex wasn't found in the compare list"); + } + } + else + { + startIdx = compareList.BinarySearch(vertRef, binaryComparer); + if (startIdx < 0) + { + startIdx = ~startIdx; + } + if (startIdx < compareList.Count) + { + VertexRef compRef = compareList[startIdx]; + if (System.Math.Abs(vertRef.vert.Position[axis] - compRef.vert.Position[axis]) <= threshold) + { + dupList.Add(compRef); + } + } + } + + for (int i = startIdx + 1; i < compareList.Count; i++) + { + VertexRef compRef = compareList[i]; + if ((System.Math.Abs(vertRef.vert.Position[axis] - compRef.vert.Position[axis]) <= threshold)) + { + dupList.Add(compRef); + } + else + { + break; + } + } + for (int i = startIdx - 1; i >= 0; i--) + { + VertexRef compRef = compareList[i]; + if ((System.Math.Abs(vertRef.vert.Position[axis] - compRef.vert.Position[axis]) <= threshold)) + { + dupList.Add(compRef); + } + else + { + break; + } + } + return dupList; + } + + public static bool RemoveUnusedBones(xxMesh mesh) + { + List> vertexLists = new List>(mesh.SubmeshList.Count); + foreach (xxSubmesh submesh in mesh.SubmeshList) + { + vertexLists.Add(submesh.VertexList); + } + RemoveUnusedBones(vertexLists, mesh.BoneList); + + return (mesh.BoneList.Count > 0); + } + + public static void RemoveUnusedBones(List> vertexLists, List boneList) + { + byte[] boneMap = null; + bool skinnedStart = (boneList.Count > 0); + if (skinnedStart) + { + bool[] bonesUsed = IsBoneUsedList(vertexLists, boneList); + + boneMap = new byte[boneList.Count]; + int numRemoved = 0; + for (int i = 0; i < bonesUsed.Length; i++) + { + int removeIdx = i - numRemoved; + if (bonesUsed[i]) + { + boneMap[i] = (byte)removeIdx; + xxBone bone = boneList[removeIdx]; + bone.Index = removeIdx; + } + else + { + boneList.RemoveAt(removeIdx); + numRemoved++; + } + } + } + + bool skinnedEnd = (boneList.Count > 0); + if (skinnedStart && skinnedEnd) + { + foreach (var vertList in vertexLists) + { + UpdateBoneIndices(vertList, boneMap); + } + } + else if (skinnedStart && !skinnedEnd) + { + // skinned -> unskinned + foreach (var vertList in vertexLists) + { + foreach (var vert in vertList) + { + vert.BoneIndices = new byte[4]; + vert.Weights3 = new float[3]; + } + } + } + } + + private static bool[] IsBoneUsedList(List> vertexLists, List boneList) + { + bool[] bonesUsed = new bool[boneList.Count]; + foreach (var vertList in vertexLists) + { + foreach (var vert in vertList) + { + foreach (byte boneIdx in vert.BoneIndices) + { + if (boneIdx != 0xFF) + { + bonesUsed[boneIdx] = true; + } + } + } + } + return bonesUsed; + } + + private static void UpdateBoneIndices(List vertList, byte[] boneMap) + { + for (int i = 0; i < vertList.Count; i++) + { + xxVertex vert = vertList[i]; + byte[] boneIndices = (byte[])vert.BoneIndices.Clone(); + for (int j = 0; j < boneIndices.Length; j++) + { + if (boneIndices[j] != 0xFF) + { + boneIndices[j] = boneMap[boneIndices[j]]; + } + } + vert.BoneIndices = boneIndices; + } + } + + public static void CreateSkin(xxFrame meshFrame, List skeletons) + { + xxMesh mesh = meshFrame.Mesh; + Matrix meshWorldTransform = meshFrame.WorldTransform(); + Dictionary boneFrames = new Dictionary(skeletons.Count); + Dictionary origins = new Dictionary(skeletons.Count); + foreach (xxFrame frame in skeletons) + { + AddBones(frame, frame.WorldTransform(), boneFrames, origins); + } + + int numVertices = 0; + foreach (xxSubmesh submesh in mesh.SubmeshList) + { + numVertices += submesh.VertexList.Count; + } + SortedList[] vertexDistances = new SortedList[numVertices]; + for (int i = 0; i < numVertices; i++) + { + vertexDistances[i] = new SortedList(4); + } + + int vertexIndex = 0; + foreach (xxSubmesh submesh in mesh.SubmeshList) + { + for (int i = 0; i < submesh.VertexList.Count; i++) + { + xxVertex v = submesh.VertexList[i]; + SortedList distances = vertexDistances[vertexIndex + i]; + foreach (KeyValuePair keyVal in origins) + { + Vector3 origin = keyVal.Value; + while (true) + { + try + { + distances.Add((origin - Vector3.TransformCoordinate(v.Position, meshWorldTransform)).LengthSquared(), keyVal.Key); + break; + } + catch (ArgumentException) + { + origin.X -= (float)0.0000001; + origin.Y -= (float)0.0000001; + origin.Z -= (float)0.0000001; + } + } + if (distances.Count > 4) + { + distances.RemoveAt(4); + } + } + } + vertexIndex += submesh.VertexList.Count; + } + + mesh.BoneList.Clear(); + SortedDictionary boneDic = new SortedDictionary(); + byte boneIdx = 0; + foreach (KeyValuePair boneFrame in boneFrames) + { + xxBone bone = new xxBone(); + bone.Name = boneFrame.Key.Name; + bone.Index = boneIdx++; + bone.Matrix = Matrix.Invert(boneFrame.Value) * meshWorldTransform; + mesh.BoneList.Add(bone); + boneDic.Add(boneFrame.Key, (byte)bone.Index); + } + vertexIndex = 0; + foreach (xxSubmesh submesh in mesh.SubmeshList) + { + for (int i = 0; i < submesh.VertexList.Count; i++) + { + xxVertex v = submesh.VertexList[i]; + SortedList distances = vertexDistances[vertexIndex + i]; + int index = 0; + float weightFactor = 0f; + float[] weights = new float[4]; + foreach (KeyValuePair dist in distances) + { + if (dist.Key == 0) + { + weights[0] = weightFactor = 1; + break; + } + + weights[index] = (float)(1 / Math.Sqrt(dist.Key)); + weightFactor += weights[index]; + index++; + } + index = 0; + foreach (KeyValuePair dist in distances) + { + boneIdx = 0xFF; + boneDic.TryGetValue(dist.Value, out boneIdx); + v.BoneIndices[index] = boneIdx; + if (index < 3) + { + v.Weights3[index] = weights[index] / weightFactor; + if (v.Weights3[0] == 1) + { + v.Weights3[1] = v.Weights3[2] = 0; + v.BoneIndices[1] = v.BoneIndices[2] = v.BoneIndices[3] = 0xFF; + break; + } + } + index++; + } + } + vertexIndex += submesh.VertexList.Count; + } + } + + public static Matrix WorldTransform(this xxFrame frame) + { + Matrix m = frame.Matrix; + xxFrame parent = (xxFrame)frame.Parent; + while (parent != null) + { + m = m * parent.Matrix; + parent = parent.Parent; + } + return m; + } + + private static void AddBones(xxFrame frame, Matrix worldTransform, Dictionary boneFrames, Dictionary origins) + { + boneFrames.Add(frame, worldTransform); + origins.Add(frame, Vector3.TransformCoordinate(Vector3.Zero, worldTransform)); + foreach (xxFrame child in frame) + { + AddBones(child, child.Matrix * worldTransform, boneFrames, origins); + } + } + + public static void ComputeBoneMatrices(List boneList, xxFrame root) + { + foreach (xxBone bone in boneList) + { + xxFrame boneFrame = xx.FindFrame(bone.Name, root); + if (boneFrame != null) + { + Matrix m = boneFrame.WorldTransform(); + m.Invert(); + bone.Matrix = m; + } + } + } + + public static void CreateBone(xxFrame boneFrame, xxMesh mesh) + { + xxBone bone = new xxBone(); + bone.Name = boneFrame.Name; + Matrix m = boneFrame.WorldTransform(); + bone.Matrix = Matrix.Invert(m); + bone.Index = mesh.BoneList.Count; + mesh.BoneList.Add(bone); + } + + public static void SaveXX(xxParser 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); + bufStr.SetLength(bufStr.Position); + } + } + catch + { + if (File.Exists(backup)) + { + if (File.Exists(destPath)) + File.Delete(destPath); + File.Move(backup, destPath); + } + } + } + } +} diff --git a/SB3UtilityPP/xxParser.cs b/SB3UtilityPP/xxParser.cs new file mode 100644 index 0000000..b987998 --- /dev/null +++ b/SB3UtilityPP/xxParser.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using SlimDX; + +namespace SB3Utility +{ + public class xxParser : IWriteFile + { + public byte[] Header { get; set; } + public xxFrame Frame { get; set; } + public byte[] MaterialSectionUnknown { get; set; } + public List MaterialList { get; set; } + public List TextureList { get; set; } + public byte[] Footer { get; set; } + + public string Name { get; set; } + public int Format { get; set; } + + protected BinaryReader reader; + + public xxParser(Stream stream, string name) + : this(stream) + { + this.Name = name; + } + + public xxParser(Stream stream) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + this.reader = reader; + + Format = 0; + byte[] formatBuf = reader.ReadBytes(5); + if ((formatBuf[0] >= 0x01) && (BitConverter.ToInt32(formatBuf, 1) == 0)) + { + Format = BitConverter.ToInt32(formatBuf, 0); + } + else + { + uint id = BitConverter.ToUInt32(formatBuf, 0); + if ((id == 0x3F8F5C29) || (id == 0x3F90A3D7) || (id == 0x3F91EB85) || (id == 0x3F933333) || + (id == 0x3F947AE1) || (id == 0x3F95C28F) || (id == 0x3F970A3D) || (id == 0x3F99999A) || + (id == 0x3FA66666) || (id == 0x3FB33333)) + { + Format = -1; + } + } + + byte[] headerBuf; + int headerBufLen; + if (Format >= 1) + { + headerBufLen = 26; + } + else + { + headerBufLen = 21; + } + headerBuf = new byte[headerBufLen]; + formatBuf.CopyTo(headerBuf, 0); + reader.ReadBytes(headerBufLen - formatBuf.Length).CopyTo(headerBuf, formatBuf.Length); + Header = headerBuf; + + this.Frame = ParseFrame(); + + this.MaterialSectionUnknown = reader.ReadBytes(4); + int numMaterials = reader.ReadInt32(); + this.MaterialList = new List(numMaterials); + for (int i = 0; i < numMaterials; i++) + { + MaterialList.Add(ParseMaterial()); + } + + int numTextures = reader.ReadInt32(); + this.TextureList = new List(numTextures); + for (int i = 0; i < numTextures; i++) + { + TextureList.Add(ParseTexture()); + } + + if (Format >= 2) + { + Footer = reader.ReadBytes(10); + } + + if (reader.ReadBytes(1).Length > 0) + { + Report.ReportLog("Parsing " + Name + " finished before the end of the file - ignoring the rest"); + } + + this.reader = null; + } + } + + public void WriteTo(Stream stream) + { + BinaryWriter writer = new BinaryWriter(stream); + writer.Write(Header); + + Frame.WriteTo(stream); + + writer.Write(MaterialSectionUnknown); + writer.Write(MaterialList.Count); + for (int i = 0; i < MaterialList.Count; i++) + { + MaterialList[i].WriteTo(stream); + } + + writer.Write(TextureList.Count); + for (int i = 0; i < TextureList.Count; i++) + { + TextureList[i].WriteTo(stream); + } + + if (Footer != null) + { + writer.Write(Footer); + } + } + + protected xxFrame ParseFrame() + { + xxFrame frame = new xxFrame(); + frame.Name = reader.ReadName(); + + int numChildFrames = reader.ReadInt32(); + frame.InitChildren(numChildFrames); + + frame.Matrix = reader.ReadMatrix(); + frame.Unknown1 = (Format >= 7) ? reader.ReadBytes(32) : reader.ReadBytes(16); + + int numSubmeshes = reader.ReadInt32(); + + frame.Bounds = new BoundingBox(reader.ReadVector3(), reader.ReadVector3()); + frame.Unknown2 = (Format >= 7) ? reader.ReadBytes(64) : reader.ReadBytes(16); + + if (Format >= 6) + { + frame.Name2 = reader.ReadName(); + } + + if (numSubmeshes > 0) + { + xxMesh mesh = new xxMesh(); + frame.Mesh = mesh; + mesh.NumVector2PerVertex = reader.ReadByte(); + + mesh.SubmeshList = new List(numSubmeshes); + for (int i = 0; i < numSubmeshes; i++) + { + xxSubmesh submesh = new xxSubmesh(); + mesh.SubmeshList.Add(submesh); + + submesh.Unknown1 = (Format >= 7) ? reader.ReadBytes(64) : reader.ReadBytes(16); + submesh.MaterialIndex = reader.ReadInt32(); + + submesh.FaceList = ParseFaceList(); + submesh.VertexList = ParseVertexList(); + + if (Format >= 7) + { + submesh.Unknown2 = reader.ReadBytes(20); + } + + if (mesh.NumVector2PerVertex > 0) + { + submesh.Vector2Lists = new List>(submesh.VertexList.Count); + for (int j = 0; j < submesh.VertexList.Count; j++) + { + List vectorList = new List(mesh.NumVector2PerVertex); + submesh.Vector2Lists.Add(vectorList); + for (byte k = 0; k < mesh.NumVector2PerVertex; k++) + { + vectorList.Add(reader.ReadVector2()); + } + } + } + + if (Format >= 2) + { + submesh.Unknown3 = reader.ReadBytes(100); // 96 + 4 + } + + if (Format >= 7) + { + submesh.Unknown4 = reader.ReadBytes(284); // 256 + 28 + + if (Format >= 8) + { + byte format = reader.ReadByte(); + string nullFrame = reader.ReadName(); + byte[] u5end = reader.ReadBytes(12 + 4); + + byte[] encryptedName = Utility.EncryptName(nullFrame); + submesh.Unknown5 = new byte[1 + 4 + encryptedName.Length + 12 + 4]; + submesh.Unknown5[0] = format; + BitConverter.GetBytes(encryptedName.Length).CopyTo(submesh.Unknown5, 1); + encryptedName.CopyTo(submesh.Unknown5, 1 + 4); + u5end.CopyTo(submesh.Unknown5, 1 + 4 + encryptedName.Length); + } + } + else + { + if (Format >= 3) + { + submesh.Unknown4 = reader.ReadBytes(64); + } + if (Format >= 5) + { + submesh.Unknown5 = reader.ReadBytes(20); + } + if (Format >= 6) + { + submesh.Unknown6 = reader.ReadBytes(28); + } + } + } + + ushort numVerticesDup = reader.ReadUInt16(); + mesh.VertexListDuplicate = new List(numVerticesDup); + mesh.VertexListDuplicateUnknown = reader.ReadBytes(8); // 4 + 4 + for (int i = 0; i < numVerticesDup; i++) + { + mesh.VertexListDuplicate.Add(ParseVertex()); + } + + mesh.BoneList = ParseBoneList(); + } + + for (int i = 0; i < numChildFrames; i++) + { + frame.AddChild(ParseFrame()); + } + + return frame; + } + + protected List ParseFaceList() + { + int numFaces = reader.ReadInt32() / 3; + List faceList = new List(numFaces); + for (int i = 0; i < numFaces; i++) + { + xxFace face = new xxFace(); + faceList.Add(face); + face.VertexIndices = reader.ReadUInt16Array(3); + } + return faceList; + } + + protected List ParseVertexList() + { + int numVertices = reader.ReadInt32(); + List vertexList = new List(numVertices); + for (int i = 0; i < numVertices; i++) + { + vertexList.Add(ParseVertex()); + } + return vertexList; + } + + protected xxVertex ParseVertex() + { + xxVertex vertex; + if (Format >= 4) + { + vertex = new xxVertexUShort(); + vertex.Index = reader.ReadUInt16(); + } + else + { + vertex = new xxVertexInt(); + vertex.Index = reader.ReadInt32(); + } + + vertex.Position = reader.ReadVector3(); + vertex.Weights3 = reader.ReadSingleArray(3); + vertex.BoneIndices = reader.ReadBytes(4); + vertex.Normal = reader.ReadVector3(); + vertex.UV = reader.ReadSingleArray(2); + + if (Format >= 4) + { + vertex.Unknown1 = reader.ReadBytes(20); + } + return vertex; + } + + protected List ParseBoneList() + { + int numBones = reader.ReadInt32(); + List boneList = new List(numBones); + for (int i = 0; i < numBones; i++) + { + xxBone bone = new xxBone(); + boneList.Add(bone); + + bone.Name = reader.ReadName(); + bone.Index = reader.ReadInt32(); + bone.Matrix = reader.ReadMatrix(); + } + return boneList; + } + + protected xxMaterial ParseMaterial() + { + xxMaterial mat = new xxMaterial(); + mat.Name = reader.ReadName(); + mat.Diffuse = reader.ReadColor4(); + mat.Ambient = reader.ReadColor4(); + mat.Specular = reader.ReadColor4(); + mat.Emissive = reader.ReadColor4(); + mat.Power = reader.ReadSingle(); + + mat.Textures = new xxMaterialTexture[4]; + for (int i = 0; i < mat.Textures.Length; i++) + { + mat.Textures[i] = new xxMaterialTexture(); + mat.Textures[i].Name = reader.ReadName(); + mat.Textures[i].Unknown1 = reader.ReadBytes(16); + } + + if (Format < 0) + { + mat.Unknown1 = reader.ReadBytes(4); + } + else + { + mat.Unknown1 = reader.ReadBytes(88); + } + return mat; + } + + protected xxTexture ParseTexture() + { + xxTexture tex = new xxTexture(); + tex.Name = reader.ReadName(); + tex.Unknown1 = reader.ReadBytes(4); + tex.Width = reader.ReadInt32(); + tex.Height = reader.ReadInt32(); + tex.Depth = reader.ReadInt32(); + tex.MipLevels = reader.ReadInt32(); + tex.Format = reader.ReadInt32(); + tex.ResourceType = reader.ReadInt32(); + tex.ImageFileFormat = reader.ReadInt32(); + tex.Checksum = reader.ReadByte(); + + int imgDataLen = reader.ReadInt32(); + tex.ImageData = reader.ReadBytes(imgDataLen); + return tex; + } + } +} diff --git a/SB3UtilityPP/xxReplace.cs b/SB3UtilityPP/xxReplace.cs new file mode 100644 index 0000000..d5e7c06 --- /dev/null +++ b/SB3UtilityPP/xxReplace.cs @@ -0,0 +1,501 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using SlimDX; +using SlimDX.Direct3D9; + +namespace SB3Utility +{ + public enum CopyFrameMethod + { + MergeFrame, + AddFrame, + ReplaceFrame + } + + public enum CopyMeshMethod + { + Replace, + CopyNear, + CopyOrder + } + + public static partial class xx + { + public static xxFrame CreateFrame(ImportedFrame frame) + { + xxFrame xxFrame = new xxFrame(); + xxFrame.Matrix = frame.Matrix; + xxFrame.Bounds = new BoundingBox(); + xxFrame.Name = frame.Name; + + xxFrame.InitChildren(frame.Count); + for (int i = 0; i < frame.Count; i++) + { + xxFrame.AddChild(CreateFrame(frame[i])); + } + + return xxFrame; + } + + public static void CopyOrCreateUnknowns(xxFrame dest, xxFrame root, int xxFormat) + { + xxFrame src = FindFrame(dest.Name, root); + if (src == null) + { + CreateUnknowns(dest, xxFormat); + } + else + { + CopyUnknowns(src, dest); + } + + for (int i = 0; i < dest.Count; i++) + { + CopyOrCreateUnknowns(dest[i], root, xxFormat); + } + } + + public static List CreateBoneList(List boneList) + { + List xxBoneList = new List(boneList.Count); + for (int i = 0; i < boneList.Count; i++) + { + xxBone xxBone = new xxBone(); + xxBone.Name = boneList[i].Name; + xxBone.Matrix = boneList[i].Matrix; + xxBone.Index = i; + xxBoneList.Add(xxBone); + } + return xxBoneList; + } + + public static xxMesh CreateMesh(WorkspaceMesh mesh, int xxFormat, out string[] materialNames, out int[] indices, out bool[] worldCoords, out bool[] replaceSubmeshesOption) + { + int numUncheckedSubmeshes = 0; + foreach (ImportedSubmesh submesh in mesh.SubmeshList) + { + if (!mesh.isSubmeshEnabled(submesh)) + numUncheckedSubmeshes++; + } + int numSubmeshes = mesh.SubmeshList.Count - numUncheckedSubmeshes; + materialNames = new string[numSubmeshes]; + indices = new int[numSubmeshes]; + worldCoords = new bool[numSubmeshes]; + replaceSubmeshesOption = new bool[numSubmeshes]; + + xxMesh xxMesh = new xxMesh(); + xxMesh.BoneList = CreateBoneList(mesh.BoneList); + + xxMesh.SubmeshList = new List(mesh.SubmeshList.Count); + for (int i = 0, submeshIdx = 0; i < numSubmeshes; i++, submeshIdx++) + { + while (!mesh.isSubmeshEnabled(mesh.SubmeshList[submeshIdx])) + submeshIdx++; + + xxSubmesh xxSubmesh = new xxSubmesh(); + xxMesh.SubmeshList.Add(xxSubmesh); + + xxSubmesh.MaterialIndex = -1; + materialNames[i] = mesh.SubmeshList[submeshIdx].Material; + indices[i] = mesh.SubmeshList[submeshIdx].Index; + worldCoords[i] = mesh.SubmeshList[submeshIdx].WorldCoords; + replaceSubmeshesOption[i] = mesh.isSubmeshReplacingOriginal(mesh.SubmeshList[submeshIdx]); + + List vertexList = mesh.SubmeshList[submeshIdx].VertexList; + List xxVertexList = new List(vertexList.Count); + for (int j = 0; j < vertexList.Count; j++) + { + ImportedVertex vert = vertexList[j]; + xxVertex xxVertex; + if (xxFormat >= 4) + { + xxVertex = new xxVertexUShort(); + CreateUnknown(xxVertex); + } + else + { + xxVertex = new xxVertexInt(); + } + + xxVertex.Index = j; + xxVertex.Normal = vert.Normal; + xxVertex.UV = (float[])vert.UV.Clone(); + xxVertex.Weights3 = new float[3] { vert.Weights[0], vert.Weights[1], vert.Weights[2] }; + xxVertex.BoneIndices = (byte[])vert.BoneIndices.Clone(); + xxVertex.Position = vert.Position; + xxVertexList.Add(xxVertex); + } + xxSubmesh.VertexList = xxVertexList; + + List faceList = mesh.SubmeshList[submeshIdx].FaceList; + List xxFaceList = new List(faceList.Count); + for (int j = 0; j < faceList.Count; j++) + { + int[] vertexIndices = faceList[j].VertexIndices; + xxFace xxFace = new xxFace(); + xxFace.VertexIndices = new ushort[3] { (ushort)vertexIndices[0], (ushort)vertexIndices[1], (ushort)vertexIndices[2] }; + xxFaceList.Add(xxFace); + } + xxSubmesh.FaceList = xxFaceList; + } + + xxMesh.VertexListDuplicate = CreateVertexListDup(xxMesh.SubmeshList); + return xxMesh; + } + + private class VertexPositionComparer : IEqualityComparer + { + public bool Equals(Vector3 p1, Vector3 p2) + { + return p1 == p2; + } + + public int GetHashCode(Vector3 v) + { + return v.GetHashCode(); + } + } + + public static void ReplaceMesh(xxFrame frame, xxParser parser, WorkspaceMesh mesh, bool merge, CopyMeshMethod normalsMethod, CopyMeshMethod bonesMethod, bool targetFullMesh) + { + Matrix transform = Matrix.Identity; + xxFrame transformFrame = frame; + while (transformFrame != null) + { + transform = transformFrame.Matrix * transform; + transformFrame = (xxFrame)transformFrame.Parent; + } + transform.Invert(); + + string[] materialNames; + int[] indices; + bool[] worldCoords; + bool[] replaceSubmeshesOption; + xxMesh xxMesh = CreateMesh(mesh, parser.Format, out materialNames, out indices, out worldCoords, out replaceSubmeshesOption); + + List allVertices = null; + if (frame.Mesh == null) + { + CreateUnknowns(xxMesh); + } + else + { + CopyUnknowns(frame.Mesh, xxMesh); + + if ((bonesMethod == CopyMeshMethod.CopyOrder) || (bonesMethod == CopyMeshMethod.CopyNear)) + { + xxMesh.BoneList = new List(frame.Mesh.BoneList.Count); + for (int i = 0; i < frame.Mesh.BoneList.Count; i++) + { + xxMesh.BoneList.Add(frame.Mesh.BoneList[i].Clone()); + } + } + + if (targetFullMesh && (normalsMethod == CopyMeshMethod.CopyNear || bonesMethod == CopyMeshMethod.CopyNear)) + { + allVertices = new List(); + HashSet posSet = new HashSet(new VertexPositionComparer()); + foreach (xxSubmesh submesh in frame.Mesh.SubmeshList) + { + allVertices.Capacity = allVertices.Count + submesh.VertexList.Count; + foreach (xxVertex vertex in submesh.VertexList) + { + if (!posSet.Contains(vertex.Position)) + { + posSet.Add(vertex.Position); + allVertices.Add(vertex); + } + } + } + } + } + + xxSubmesh[] replaceSubmeshes = (frame.Mesh == null) ? null : new xxSubmesh[frame.Mesh.SubmeshList.Count]; + List addSubmeshes = new List(xxMesh.SubmeshList.Count); + for (int i = 0; i < xxMesh.SubmeshList.Count; i++) + { + for (int j = 0; j < parser.MaterialList.Count; j++) + { + if (parser.MaterialList[j].Name == materialNames[i]) + { + xxMesh.SubmeshList[i].MaterialIndex = j; + break; + } + } + + xxSubmesh xxSubmesh = xxMesh.SubmeshList[i]; + List xxVertexList = xxSubmesh.VertexList; + if (worldCoords[i]) + { + for (int j = 0; j < xxVertexList.Count; j++) + { + xxVertexList[j].Position = Vector3.TransformCoordinate(xxVertexList[j].Position, transform); + } + } + + xxSubmesh baseSubmesh = null; + int idx = indices[i]; + if ((frame.Mesh != null) && (idx >= 0) && (idx < frame.Mesh.SubmeshList.Count)) + { + baseSubmesh = frame.Mesh.SubmeshList[idx]; + CopyUnknowns(baseSubmesh, xxSubmesh, parser.Format, xxMesh.NumVector2PerVertex); + } + else + { + CreateUnknowns(xxSubmesh, parser.Format, xxMesh.NumVector2PerVertex); + } + + if (baseSubmesh != null) + { + if (normalsMethod == CopyMeshMethod.CopyOrder) + { + xx.CopyNormalsOrder(baseSubmesh.VertexList, xxSubmesh.VertexList); + } + else if (normalsMethod == CopyMeshMethod.CopyNear) + { + xx.CopyNormalsNear(targetFullMesh ? allVertices : baseSubmesh.VertexList, xxSubmesh.VertexList); + } + + if (bonesMethod == CopyMeshMethod.CopyOrder) + { + xx.CopyBonesOrder(baseSubmesh.VertexList, xxSubmesh.VertexList); + } + else if (bonesMethod == CopyMeshMethod.CopyNear) + { + xx.CopyBonesNear(targetFullMesh ? allVertices : baseSubmesh.VertexList, xxSubmesh.VertexList); + } + } + + if ((baseSubmesh != null) && merge && replaceSubmeshesOption[i]) + { + replaceSubmeshes[idx] = xxSubmesh; + } + else + { + addSubmeshes.Add(xxSubmesh); + } + } + + if ((frame.Mesh != null) && merge) + { + xxMesh.SubmeshList = new List(replaceSubmeshes.Length + addSubmeshes.Count); + List copiedSubmeshes = new List(replaceSubmeshes.Length); + for (int i = 0; i < replaceSubmeshes.Length; i++) + { + if (replaceSubmeshes[i] == null) + { + xxSubmesh xxSubmesh = frame.Mesh.SubmeshList[i].Clone(); + copiedSubmeshes.Add(xxSubmesh); + xxMesh.SubmeshList.Add(xxSubmesh); + } + else + { + xxMesh.SubmeshList.Add(replaceSubmeshes[i]); + } + } + xxMesh.SubmeshList.AddRange(addSubmeshes); + + if ((frame.Mesh.BoneList.Count == 0) && (xxMesh.BoneList.Count > 0)) + { + for (int i = 0; i < copiedSubmeshes.Count; i++) + { + List vertexList = copiedSubmeshes[i].VertexList; + for (int j = 0; j < vertexList.Count; j++) + { + vertexList[j].BoneIndices = new byte[4] { 0xFF, 0xFF, 0xFF, 0xFF }; + } + } + } + else if ((frame.Mesh.BoneList.Count > 0) && (xxMesh.BoneList.Count == 0)) + { + for (int i = 0; i < replaceSubmeshes.Length; i++) + { + if (replaceSubmeshes[i] != null) + { + List vertexList = replaceSubmeshes[i].VertexList; + for (int j = 0; j < vertexList.Count; j++) + { + vertexList[j].BoneIndices = new byte[4] { 0xFF, 0xFF, 0xFF, 0xFF }; + } + } + } + for (int i = 0; i < addSubmeshes.Count; i++) + { + List vertexList = addSubmeshes[i].VertexList; + for (int j = 0; j < vertexList.Count; j++) + { + vertexList[j].BoneIndices = new byte[4] { 0xFF, 0xFF, 0xFF, 0xFF }; + } + } + } + else if ((frame.Mesh.BoneList.Count > 0) && (xxMesh.BoneList.Count > 0)) + { + byte[] boneIdxMap; + xxMesh.BoneList = MergeBoneList(frame.Mesh.BoneList, xxMesh.BoneList, out boneIdxMap); + for (int i = 0; i < replaceSubmeshes.Length; i++) + { + if (replaceSubmeshes[i] != null) + { + List vertexList = replaceSubmeshes[i].VertexList; + for (int j = 0; j < vertexList.Count; j++) + { + byte[] boneIndices = vertexList[j].BoneIndices; + vertexList[j].BoneIndices = new byte[4]; + for (int k = 0; k < 4; k++) + { + vertexList[j].BoneIndices[k] = boneIndices[k] < 0xFF ? boneIdxMap[boneIndices[k]] : (byte)0xFF; + } + } + } + } + for (int i = 0; i < addSubmeshes.Count; i++) + { + List vertexList = addSubmeshes[i].VertexList; + for (int j = 0; j < vertexList.Count; j++) + { + byte[] boneIndices = vertexList[j].BoneIndices; + vertexList[j].BoneIndices = new byte[4]; + for (int k = 0; k < 4; k++) + { + vertexList[j].BoneIndices[k] = boneIndices[k] < 0xFF ? boneIdxMap[boneIndices[k]] : (byte)0xFF; + } + } + } + } + } + + if ((xxMesh.NumVector2PerVertex > 0) || ((frame.Mesh != null) && merge)) + { + xxMesh.VertexListDuplicate = CreateVertexListDup(xxMesh.SubmeshList); + } + + frame.Mesh = xxMesh; + SetBoundingBox(frame); + } + + public static xxMaterial CreateMaterial(ImportedMaterial material) + { + xxMaterial xxMat = new xxMaterial(); + xxMat.Name = material.Name; + xxMat.Diffuse = material.Diffuse; + xxMat.Ambient = material.Ambient; + xxMat.Specular = material.Specular; + xxMat.Emissive = material.Emissive; + xxMat.Power = material.Power; + + xxMat.Textures = new xxMaterialTexture[4]; + for (int i = 0; i < xxMat.Textures.Length; i++) + { + xxMat.Textures[i] = new xxMaterialTexture(); + if ((material.Textures != null) && (i < material.Textures.Length) && (material.Textures[i] != null)) + { + xxMat.Textures[i].Name = material.Textures[i]; + } + else + { + xxMat.Textures[i].Name = String.Empty; + } + } + return xxMat; + } + + public static void ReplaceMaterial(xxParser parser, ImportedMaterial material) + { + xxMaterial mat = xx.CreateMaterial(material); + + bool found = false; + for (int i = 0; i < parser.MaterialList.Count; i++) + { + if (parser.MaterialList[i].Name == material.Name) + { + CopyUnknowns(parser.MaterialList[i], mat); + + parser.MaterialList.RemoveAt(i); + parser.MaterialList.Insert(i, mat); + found = true; + break; + } + } + + if (!found) + { + CreateUnknowns(mat, parser.Format); + parser.MaterialList.Add(mat); + } + } + + public static xxTexture CreateTexture(ImportedTexture texture) + { + var imgInfo = ImageInformation.FromMemory(texture.Data); + + xxTexture xxTex = new xxTexture(); + xxTex.Width = imgInfo.Width; + xxTex.Height = imgInfo.Height; + xxTex.Depth = imgInfo.Depth; + xxTex.Format = (int)imgInfo.Format; + xxTex.ImageFileFormat = (int)imgInfo.ImageFileFormat; + xxTex.MipLevels = imgInfo.MipLevels; + xxTex.Name = texture.Name; + xxTex.ResourceType = (int)imgInfo.ResourceType; + + byte checksum = 0; + for (int i = 0; i < texture.Data.Length; i += 32) + { + checksum += texture.Data[i]; + } + xxTex.Checksum = checksum; + + if (imgInfo.ImageFileFormat == ImageFileFormat.Bmp) + { + xxTex.ImageData = (byte[])texture.Data.Clone(); + xxTex.ImageData[0] = 0; + xxTex.ImageData[1] = 0; + } + else if(imgInfo.ImageFileFormat == ImageFileFormat.Tga) + { + byte[] tgaHeader = new byte[18]; + Array.Copy(texture.Data, tgaHeader, tgaHeader.Length); + int imgdataLen = tgaHeader[16] / 8 * BitConverter.ToInt16(tgaHeader, 12) * BitConverter.ToInt16(tgaHeader, 14); + if (imgdataLen > texture.Data.Length - (tgaHeader.Length + tgaHeader[0])) + imgdataLen = texture.Data.Length - (tgaHeader.Length + tgaHeader[0]); + xxTex.ImageData = new byte[tgaHeader.Length + imgdataLen]; + Array.Copy(texture.Data, xxTex.ImageData, tgaHeader.Length); + Array.Copy(texture.Data, tgaHeader.Length + tgaHeader[0], xxTex.ImageData, tgaHeader.Length, imgdataLen); + xxTex.ImageData[0] = 0; + } + else + { + xxTex.ImageData = (byte[])texture.Data.Clone(); + } + + return xxTex; + } + + public static void ReplaceTexture(xxParser parser, ImportedTexture texture) + { + xxTexture tex = xx.CreateTexture(texture); + + bool found = false; + for (int i = 0; i < parser.TextureList.Count; i++) + { + if (parser.TextureList[i].Name == texture.Name) + { + CopyUnknowns(parser.TextureList[i], tex); + + parser.TextureList.RemoveAt(i); + parser.TextureList.Insert(i, tex); + found = true; + break; + } + } + + if (!found) + { + CreateUnknowns(tex); + parser.TextureList.Add(tex); + } + } + } +}