From 9eec79fb169ba15c090742dd8854907c83aabc09 Mon Sep 17 00:00:00 2001 From: Zsolt Zitting Date: Sat, 1 Feb 2020 03:24:20 -0700 Subject: [PATCH] Finally, a new version! - Fixed timestamp issue when extracting RFx files (thanks Cyrem!) - Added AI Data and Cornering data loading and saving - Added TRK track loading, track info display and track thumbnail - Optimized some code, bugfixes --- App.config | 2 +- EmbeddedAssembly.cs | 126 ++++++++ Formats/AIData/AIData.cs | 53 ++++ Formats/Cornering/Cornering.cs | 51 ++++ Formats/LSRUtil.cs | 199 +++++++++++++ Formats/RFx/RFH.cs | 45 +-- Formats/RTB/RTB.cs | 3 - Formats/RTB/RTBViewForm.Designer.cs | 10 +- Formats/TRK/TRK.cs | 91 ++++++ Formats/XBF/XBFtool.cs | 11 +- MainForm.Designer.cs | 433 ++++++++++++++++++---------- MainForm.cs | 312 ++++++++++++++++---- MainForm.resx | 17 +- Program.cs | 37 ++- Properties/AssemblyInfo.cs | 8 +- Properties/Resources.Designer.cs | 10 - Properties/Resources.resx | 3 - Properties/Settings.Designer.cs | 2 +- SRtoolbox.csproj | 20 +- Structures.cs | 89 ++++++ lib/Pfim.dll | Bin 0 -> 93184 bytes 21 files changed, 1231 insertions(+), 291 deletions(-) create mode 100644 EmbeddedAssembly.cs create mode 100644 Formats/AIData/AIData.cs create mode 100644 Formats/Cornering/Cornering.cs create mode 100644 Formats/LSRUtil.cs create mode 100644 Formats/TRK/TRK.cs create mode 100644 Structures.cs create mode 100644 lib/Pfim.dll diff --git a/App.config b/App.config index 2d2a12d..bae5d6d 100644 --- a/App.config +++ b/App.config @@ -1,6 +1,6 @@ - + diff --git a/EmbeddedAssembly.cs b/EmbeddedAssembly.cs new file mode 100644 index 0000000..4e5ea5c --- /dev/null +++ b/EmbeddedAssembly.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; + +/// +/// A class for loading Embedded Assembly +/// +public class EmbeddedAssembly +{ + // Version 1.3 + + static Dictionary dic = null; + + /// + /// Load Assembly, DLL from Embedded Resources into memory. + /// + /// Embedded Resource string. Example: WindowsFormsApplication1.SomeTools.dll + /// File Name. Example: SomeTools.dll + public static void Load(string embeddedResource, string fileName) + { + if (dic == null) + dic = new Dictionary(); + + byte[] ba = null; + Assembly asm = null; + Assembly curAsm = Assembly.GetExecutingAssembly(); + + using (Stream stm = curAsm.GetManifestResourceStream(embeddedResource)) + { + // Either the file is not existed or it is not mark as embedded resource + if (stm == null) + throw new Exception(embeddedResource + " is not found in Embedded Resources."); + + // Get byte[] from the file from embedded resource + ba = new byte[(int)stm.Length]; + stm.Read(ba, 0, (int)stm.Length); + try + { + asm = Assembly.Load(ba); + + // Add the assembly/dll into dictionary + dic.Add(asm.FullName, asm); + return; + } + catch + { + // Purposely do nothing + // Unmanaged dll or assembly cannot be loaded directly from byte[] + // Let the process fall through for next part + } + } + + bool fileOk = false; + string tempFile = ""; + + using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider()) + { + // Get the hash value from embedded DLL/assembly + string fileHash = BitConverter.ToString(sha1.ComputeHash(ba)).Replace("-", string.Empty); + + // Define the temporary storage location of the DLL/assembly + tempFile = Path.GetTempPath() + fileName; + + // Determines whether the DLL/assembly is existed or not + if (File.Exists(tempFile)) + { + // Get the hash value of the existed file + byte[] bb = File.ReadAllBytes(tempFile); + string fileHash2 = BitConverter.ToString(sha1.ComputeHash(bb)).Replace("-", string.Empty); + + // Compare the existed DLL/assembly with the Embedded DLL/assembly + if (fileHash == fileHash2) + { + // Same file + fileOk = true; + } + else + { + // Not same + fileOk = false; + } + } + else + { + // The DLL/assembly is not existed yet + fileOk = false; + } + } + + // Create the file on disk + if (!fileOk) + { + System.IO.File.WriteAllBytes(tempFile, ba); + } + + // Load it into memory + asm = Assembly.LoadFile(tempFile); + + // Add the loaded DLL/assembly into dictionary + dic.Add(asm.FullName, asm); + } + + /// + /// Retrieve specific loaded DLL/assembly from memory + /// + /// + /// + public static Assembly Get(string assemblyFullName) + { + if (dic == null || dic.Count == 0) + return null; + + if (dic.ContainsKey(assemblyFullName)) + return dic[assemblyFullName]; + + return null; + + // Don't throw Exception if the dictionary does not contain the requested assembly. + // This is because the event of AssemblyResolve will be raised for every + // Embedded Resources (such as pictures) of the projects. + // Those resources wil not be loaded by this class and will not exist in dictionary. + } +} \ No newline at end of file diff --git a/Formats/AIData/AIData.cs b/Formats/AIData/AIData.cs new file mode 100644 index 0000000..bec0385 --- /dev/null +++ b/Formats/AIData/AIData.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SRtoolbox +{ + class AIData + { + public AIFile load(string filename) + { + AIFile ai = new AIFile(); + + FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read); + using (BinaryReader reader = new BinaryReader(file)) + { + reader.ReadBytes(4); + ai.RacingLine = reader.ReadUInt16(); + ai.Braking = reader.ReadUInt16(); + ai.Overtaking = reader.ReadUInt16(); + ai.Speed = reader.ReadUInt16(); + ai.Reflex = reader.ReadUInt16(); + reader.ReadBytes(2); + ai.Blocking = reader.ReadByte(); + ai.CutsCorners = reader.ReadByte(); + ai.Intelligence = reader.ReadByte(); + ai.Craziness = reader.ReadByte(); + } + + return ai; + } + + public void save(string filename, AIFile ai) + { + using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create))) + { + writer.Write(new byte[] { 0, 0, 0, 0 }); + writer.Write(ai.RacingLine); + writer.Write(ai.Braking); + writer.Write(ai.Overtaking); + writer.Write(ai.Speed); + writer.Write(ai.Reflex); + writer.Write(new byte[] { 0, 0 }); + writer.Write(ai.Blocking); + writer.Write(ai.CutsCorners); + writer.Write(ai.Intelligence); + writer.Write(ai.Craziness); + } + } + } +} diff --git a/Formats/Cornering/Cornering.cs b/Formats/Cornering/Cornering.cs new file mode 100644 index 0000000..72ce41c --- /dev/null +++ b/Formats/Cornering/Cornering.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SRtoolbox +{ + class Cornering + { + public CorneringFile load(string filename) + { + CorneringFile cor = new CorneringFile(); + + FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read); + using (BinaryReader reader = new BinaryReader(file)) + { + cor.Small = reader.ReadSingle(); + cor.Big = reader.ReadSingle(); + cor.SmallSmall_Same = reader.ReadSingle(); + cor.SmallSmall_Opp = reader.ReadSingle(); + cor.BigBig_Same = reader.ReadSingle(); + cor.BigBig_Opp = reader.ReadSingle(); + cor.BigSmall_Same = reader.ReadSingle(); + cor.SmallBig_Same = reader.ReadSingle(); + cor.BigSmall_Opp = reader.ReadSingle(); + cor.SmallBig_Opp = reader.ReadSingle(); + } + + return cor; + } + + public void save(string filename, CorneringFile cor) + { + using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create))) + { + writer.Write(cor.Small); + writer.Write(cor.Big); + writer.Write(cor.SmallSmall_Same); + writer.Write(cor.SmallSmall_Opp); + writer.Write(cor.BigBig_Same); + writer.Write(cor.BigBig_Opp); + writer.Write(cor.BigSmall_Same); + writer.Write(cor.SmallBig_Same); + writer.Write(cor.BigSmall_Opp); + writer.Write(cor.SmallBig_Opp); + } + } + } +} diff --git a/Formats/LSRUtil.cs b/Formats/LSRUtil.cs new file mode 100644 index 0000000..ff8153a --- /dev/null +++ b/Formats/LSRUtil.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SRtoolbox +{ + public class LSRUtil + { + public enum Theme + { + None = -1, + Jungle = 0, + Ice = 1, + Desert = 2, + City = 3 + } + + public enum TrackType + { + Multiplayer = 0, + Singleplayer = 1, + } + + public enum TrackTime + { + Day = 0, + Night = 1 + } + + [Flags] + public enum Compatibility + { + Full = 0, // Compatible + Overlap = 1, // Track pieces overlap [TODO] + EightSixteen = 2, // Not 8x8 or 16x16 + NonSquare = 4, // Not square track size + TooHigh = 8, // Pieces are over 3 height + ThemeMismatch = 16, // Some piece have different theme + CustomHeader = 32, // LEGO MOTO header mismatch + } + + public class TrackCoord : IEquatable + { + public int X { get; set; } + public int Y { get; set; } + + public TrackCoord(int x, int y) + { + this.X = x; + this.Y = y; + } + + public override string ToString() + { + return ToString(false); + } + + public string ToString(bool size) + { + return string.Format("{0}x{1}", size ? this.X + 1 : this.X, size ? this.Y + 1 : this.Y); + } + + public override bool Equals(object value) + { + if (value == null) return false; + + TrackCoord coord = value as TrackCoord; + + return !ReferenceEquals(null, coord) && + Equals(X, coord.X) && + Equals(Y, coord.Y); + } + + public bool Equals(TrackCoord other) + { + return !ReferenceEquals(null, other) && + Equals(X, other.X) && + Equals(Y, other.Y); + } + + public override int GetHashCode() + { + var hashCode = 1861411795; + hashCode = hashCode * -1521134295 + X.GetHashCode(); + hashCode = hashCode * -1521134295 + Y.GetHashCode(); + return hashCode; + } + + // This is gross -Y + + public static bool operator ==(TrackCoord x, TrackCoord y) + { + return x.X == y.X && x.Y == y.Y; + } + + public static bool operator !=(TrackCoord x, TrackCoord y) + { + return !(x == y); + } + } + + public static int[][] Pieces = { + /* Jungle */ new int[] { 0x65, 0x61, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x83, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x66, 0x97, 0x98, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0 }, + /* Ice */ new int[] { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F, 0x50, 0x51, 0x53, 0x54, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6B, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7E, 0x7F, 0x80, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8 }, + /* Desert */ new int[] { 0x1D, 0x19, 0x1A, 0x1B, 0x1C, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3B, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F, 0x50, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78 }, + /* City */ new int[] { 0x30, 0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x38, 0x39, 0x3B, 0x3C, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x53, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0x67, 0x68, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90 } + }; + + public int GetPiece(Theme theme, int nid) + { + return Pieces[(int)theme][nid]; + } + + public string GetThemeName(Theme theme) + { + return theme.ToString(); + } + + public static Theme TRK_GetTheme(byte theme) + { + Theme t = Theme.None; + + if (theme == 0x3B) t = Theme.Jungle; + else if (theme == 0x3F) t = Theme.Ice; + else if (theme == 0x47) t = Theme.Desert; + else if (theme == 0x43) t = Theme.City; + return t; + } + + public static int GetNid(int id, Theme theme) + { + int ret = 0; + + if (theme != Theme.None) + { + int[] tb = Pieces[(int)theme]; + + foreach (int i in tb) + { + if (i == id) break; + ret += 1; + } + } + + return ret; + } + + public static TrackPiece ParsePiece(byte[][] data) + { + int height = (int)BitConverter.ToSingle(data[0],0); + if (height == -1) height = 0; + else height /= 8; + + byte[] piece = data[1]; + + TrackPiece ret = new TrackPiece() + { + id = piece[0], + rotation = piece[2], + theme = TRK_GetTheme(piece[1]), + height = height + }; + + ret.nid = GetNid(ret.id, ret.theme); + + return ret; + } + + public static Compatibility CheckCompat(TrackFile track) + { + Compatibility compat = Compatibility.Full; + + string lego = (track as TRKFile).legoHeader; + if (lego != "LEGO MOTO\0\0\0" && lego != string.Empty) compat |= Compatibility.CustomHeader; + + if (track.size.X != track.size.Y) compat |= Compatibility.NonSquare; + if (track.size != new TrackCoord(7, 7) && track.size != new TrackCoord(15, 15)) { MessageBox.Show("Track: "+track.size.ToString()); compat |= Compatibility.EightSixteen; } + + foreach (var piece in track.pieces) + { + if (piece.height > 3) + { + //MessageBox.Show("Height: " + piece.height); + compat |= Compatibility.TooHigh; + } + if (piece.theme != track.theme && piece.theme != Theme.None) + { + //MessageBox.Show("Piece: " + piece.theme.ToString(), "Track: " + track.theme.ToString()); + compat |= Compatibility.ThemeMismatch; + } + } + + return compat; + } + } +} diff --git a/Formats/RFx/RFH.cs b/Formats/RFx/RFH.cs index c1c434a..060214a 100644 --- a/Formats/RFx/RFH.cs +++ b/Formats/RFx/RFH.cs @@ -7,21 +7,9 @@ namespace SRtoolbox { - internal class RFile - { - - public int uncompressedSize; - public int pathLength; - public int compressionType; - public int compressedSize; - public int offset; - public string filepath; - public byte[] data; - } - internal class RFH { - private List RFH_fileList = new List(); + private List RFH_fileList = new List(); public void load(string fileRFH, string fileRFD) { @@ -31,19 +19,8 @@ public void load(string fileRFH, string fileRFD) BinaryReader brData = new BinaryReader(fsData); do { - int pathLen = brHeader.ReadInt16() - 1; - int ucmpSize = brHeader.ReadInt32(); - - /* - byte[] temp = new byte[4]; - byte[] tmp2 = brHeader.ReadBytes(2); - temp[2] = tmp2[0]; temp[3] = tmp2[1]; - int timestamp = BitConverter.ToInt32(temp, 0); - */ - - // The timestamp is here, but it's stupid, so I'm ignoring it. - brHeader.BaseStream.Seek(2L, SeekOrigin.Current); - + int pathLen = brHeader.ReadInt32() - 1; + DateTime timestamp = DateTimeOffset.FromUnixTimeSeconds(brHeader.ReadUInt32()).DateTime; int cmpType = brHeader.ReadInt32(); int cmpSize = brHeader.ReadInt32(); int offset = brHeader.ReadInt32(); @@ -51,16 +28,16 @@ public void load(string fileRFH, string fileRFD) brHeader.BaseStream.Seek(1L, SeekOrigin.Current); brData.BaseStream.Seek((long)offset, SeekOrigin.Begin); byte[] data = brData.ReadBytes(cmpSize); - this.RFH_fileList.Add(new RFile + this.RFH_fileList.Add(new RFxFile { filepath = Encoding.UTF8.GetString(relPath), pathLength = pathLen, + timestamp = timestamp, compressionType = cmpType, compressedSize = cmpSize, - uncompressedSize = ucmpSize, offset = offset, data = data - }); + }); ; } while (brHeader.BaseStream.Position < brHeader.BaseStream.Length); brData.Close(); @@ -74,7 +51,7 @@ public bool extractAllFiles(string directory) bool success = false; if (RFH_fileList.Count() > 0) { - foreach (RFile file in this.RFH_fileList) + foreach (RFxFile file in this.RFH_fileList) { extractFile(file, directory, true); } @@ -83,13 +60,13 @@ public bool extractAllFiles(string directory) return success; } - public bool extractFile(RFile file, string directory, bool preserveStructure) + public bool extractFile(RFxFile file, string directory, bool preserveStructure) { bool success = false; if (file != null) { - Directory.CreateDirectory(directory + (preserveStructure ? ("\\" + Path.GetDirectoryName(file.filepath) + "\\") : "")); - FileStream fileStream = new FileStream(directory + "\\" + (preserveStructure ? ("\\" + Path.GetDirectoryName(file.filepath) + "\\") : "") + Path.GetFileName(file.filepath), FileMode.Create); + Directory.CreateDirectory(directory + (preserveStructure ? ("\\" + Path.GetDirectoryName(file.filepath) + "\\") : string.Empty)); + FileStream fileStream = new FileStream(directory + "\\" + (preserveStructure ? ("\\" + Path.GetDirectoryName(file.filepath) + "\\") : string.Empty) + Path.GetFileName(file.filepath), FileMode.Create); BinaryWriter binaryWriter = new BinaryWriter(fileStream); byte[] buffer = file.data; if (file.data.Count() > 4) @@ -104,7 +81,7 @@ public bool extractFile(RFile file, string directory, bool preserveStructure) binaryWriter.Write(buffer); binaryWriter.Close(); fileStream.Close(); - //File.SetLastWriteTimeUtc(directory + "\\" + file.filepath, DateTimeOffset.FromUnixTimeSeconds(file.timestamp).UtcDateTime); + File.SetLastWriteTime(directory + "\\" + file.filepath, file.timestamp); success = true; } return success; diff --git a/Formats/RTB/RTB.cs b/Formats/RTB/RTB.cs index 74fb26c..c683a99 100644 --- a/Formats/RTB/RTB.cs +++ b/Formats/RTB/RTB.cs @@ -11,17 +11,14 @@ namespace SRtoolbox { class RTB { - // Get the DataTable. DataTable table = new DataTable(); public BindingSource bs = new BindingSource(); - // ... Use the DataTable here with SQL. /// /// This example method generates a DataTable. /// public void GenTable(string filename) { // Here we create a DataTable with four columns. - DataTable table = new DataTable(); table.Columns.Add("ID", typeof(string)); table.Columns.Add("Path", typeof(string)); diff --git a/Formats/RTB/RTBViewForm.Designer.cs b/Formats/RTB/RTBViewForm.Designer.cs index 8d7b3ed..73695e1 100644 --- a/Formats/RTB/RTBViewForm.Designer.cs +++ b/Formats/RTB/RTBViewForm.Designer.cs @@ -43,15 +43,20 @@ private void InitializeComponent() this.rtbDataGrid.AllowUserToDeleteRows = false; this.rtbDataGrid.AllowUserToResizeRows = false; this.rtbDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; - this.rtbDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.rtbDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing; this.rtbDataGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.assetId, this.assetPath}); + this.rtbDataGrid.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically; this.rtbDataGrid.Location = new System.Drawing.Point(12, 41); this.rtbDataGrid.MultiSelect = false; this.rtbDataGrid.Name = "rtbDataGrid"; this.rtbDataGrid.ReadOnly = true; + this.rtbDataGrid.RowHeadersVisible = false; + this.rtbDataGrid.RowHeadersWidth = 21; + this.rtbDataGrid.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; this.rtbDataGrid.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.rtbDataGrid.ShowEditingIcon = false; this.rtbDataGrid.Size = new System.Drawing.Size(572, 371); this.rtbDataGrid.TabIndex = 0; // @@ -75,12 +80,15 @@ private void InitializeComponent() // // assetId // + this.assetId.FillWeight = 20.19841F; this.assetId.HeaderText = "ID"; + this.assetId.MinimumWidth = 40; this.assetId.Name = "assetId"; this.assetId.ReadOnly = true; // // assetPath // + this.assetPath.FillWeight = 79.27875F; this.assetPath.HeaderText = "Path"; this.assetPath.Name = "assetPath"; this.assetPath.ReadOnly = true; diff --git a/Formats/TRK/TRK.cs b/Formats/TRK/TRK.cs new file mode 100644 index 0000000..871fb49 --- /dev/null +++ b/Formats/TRK/TRK.cs @@ -0,0 +1,91 @@ +using Pfim; +using SRtoolbox.VariableTypes; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SRtoolbox +{ + class TRK + { + private static GCHandle handle; + + private List pieces; + + public TRKFile load(string filename) + { + TRKFile trk = new TRKFile(); + pieces = new List(); + + int realsize, iters; + bool sizeMismatch = false; + + FileStream file = File.Open(filename, FileMode.Open, FileAccess.Read); + using (BinaryReader reader = new BinaryReader(file)) + { + realsize = (int)file.Length; + trk.legoHeader = Encoding.UTF8.GetString(reader.ReadBytes(12)); + trk.crashInt = reader.ReadInt32(); + trk.filesize = reader.ReadInt32(); + if (trk.filesize != 65576) sizeMismatch = true; + else if (trk.filesize != realsize) sizeMismatch = true; + if (sizeMismatch) MessageBox.Show("Filesize mismatch detected, may cause problems!","TRK/XTK Manager"); + trk.type = (LSRUtil.TrackType)reader.ReadInt32(); + trk.theme = (LSRUtil.Theme)reader.ReadInt32(); + trk.time = (LSRUtil.TrackTime)reader.ReadInt32(); + int sz = 8 * ((int)trk.type + 1); + trk.size = new LSRUtil.TrackCoord(sz-1, sz-1); + trk.compat = LSRUtil.Compatibility.Full; + + try + { + var image = Pfim.Pfim.FromFile(Path.Combine(Path.GetDirectoryName(filename), "Images", Path.GetFileNameWithoutExtension(filename) + ".tga")); + handle = GCHandle.Alloc(image.Data, GCHandleType.Pinned); + var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(image.Data, 0); + trk.image = new Bitmap(image.Width, image.Height, image.Stride, PixelFormat.Format24bppRgb, ptr); + } catch (FileNotFoundException) { + Console.WriteLine("DEBUG: No image found for "+Path.GetFileName(filename)); + } + + iters = (int)trk.size.X+1; + int skip = (56 - ((int)trk.type * 8)) * 16; + byte[][][][] pieces_bytes = new byte[iters][][][]; + + for (int y = 0; y < iters; y++) + { + byte[][][] tmp = new byte[iters][][]; + for (int x = 0; x < iters; x++) + { + byte[][] piece = new byte[3][]; + reader.ReadBytes(4); + for (int z = 0; z < 3; z++) + { + byte[] bytes = reader.ReadBytes(4); + piece[z] = bytes; + } + // right here + TrackPiece newpc = LSRUtil.ParsePiece(piece); + newpc.position = new LSRUtil.TrackCoord(x, y); + pieces.Add(newpc); + tmp[x] = piece; + } + reader.ReadBytes(skip); + pieces_bytes[y] = tmp; + } + + trk.pieces = pieces; + + trk.compat = LSRUtil.CheckCompat(trk); + } + + return trk; + } + } +} diff --git a/Formats/XBF/XBFtool.cs b/Formats/XBF/XBFtool.cs index c3a31a1..56a976a 100644 --- a/Formats/XBF/XBFtool.cs +++ b/Formats/XBF/XBFtool.cs @@ -5,6 +5,13 @@ using System.Text; using SRtoolbox; +/* + * + * Disclaimer: + * This code was decompiled using dnSpy. Understand at your own risk. + * + */ + namespace SRtoolbox { class XBFtool @@ -12,7 +19,7 @@ class XBFtool // Token: 0x06000004 RID: 4 RVA: 0x00002118 File Offset: 0x00000318 public void Setup(string args) { - if (args == "") + if (args == string.Empty) { throw new ArgumentException("No file handed over!"); } @@ -328,7 +335,7 @@ public void WriteObj(Mesh mesh) { streamWriter.WriteLine(string.Format("vt {0}", arg3).Replace(',', '.')); } - string text = ""; + string text = string.Empty; streamWriter.WriteLine(); streamWriter.WriteLine("g " + mesh.Name); int count = mesh.Vectors.Count; diff --git a/MainForm.Designer.cs b/MainForm.Designer.cs index f02bf25..b6c352b 100644 --- a/MainForm.Designer.cs +++ b/MainForm.Designer.cs @@ -28,16 +28,17 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.rfxunpackBtn = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.rfxunpackHelp = new System.Windows.Forms.Button(); this.tabControl = new System.Windows.Forms.TabControl(); this.gameTab = new System.Windows.Forms.TabPage(); - this.groupBox11 = new System.Windows.Forms.GroupBox(); + this.corDataGroup = new System.Windows.Forms.GroupBox(); this.corneringHelp = new System.Windows.Forms.Button(); - this.button16 = new System.Windows.Forms.Button(); - this.button15 = new System.Windows.Forms.Button(); + this.cornSaveBtn = new System.Windows.Forms.Button(); + this.cornLoadBtn = new System.Windows.Forms.Button(); this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); this.label19 = new System.Windows.Forms.Label(); this.label10 = new System.Windows.Forms.Label(); @@ -59,18 +60,17 @@ private void InitializeComponent() this.corSmallBig_Same = new System.Windows.Forms.NumericUpDown(); this.corBigSmall_Opp = new System.Windows.Forms.NumericUpDown(); this.corSmallBig_Opp = new System.Windows.Forms.NumericUpDown(); - this.groupBox10 = new System.Windows.Forms.GroupBox(); + this.aiDataGroup = new System.Windows.Forms.GroupBox(); this.button19 = new System.Windows.Forms.Button(); - this.button18 = new System.Windows.Forms.Button(); - this.button17 = new System.Windows.Forms.Button(); - this.button14 = new System.Windows.Forms.Button(); + this.aiSaveBtn = new System.Windows.Forms.Button(); + this.aiLoadBtn = new System.Windows.Forms.Button(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.aiBlocking = new System.Windows.Forms.NumericUpDown(); this.label20 = new System.Windows.Forms.Label(); this.aiRacingLine = new System.Windows.Forms.NumericUpDown(); this.aiReflex = new System.Windows.Forms.NumericUpDown(); this.label2 = new System.Windows.Forms.Label(); - this.Overtaking = new System.Windows.Forms.NumericUpDown(); + this.aiOvertaking = new System.Windows.Forms.NumericUpDown(); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label(); @@ -108,7 +108,14 @@ private void InitializeComponent() this.button2 = new System.Windows.Forms.Button(); this.trackTab = new System.Windows.Forms.TabPage(); this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.trkxtkInfoBtn = new System.Windows.Forms.Button(); + this.panel1 = new System.Windows.Forms.Panel(); + this.groupBox12 = new System.Windows.Forms.GroupBox(); + this.trackImage = new System.Windows.Forms.PictureBox(); + this.trackSize = new System.Windows.Forms.Label(); + this.trackCompat = new System.Windows.Forms.Label(); + this.trackTheme = new System.Windows.Forms.Label(); + this.trackMode = new System.Windows.Forms.Label(); + this.trackLabel = new System.Windows.Forms.Label(); this.trkxtkHelp = new System.Windows.Forms.Button(); this.trkxtkLoad = new System.Windows.Forms.Button(); this.aboutTab = new System.Windows.Forms.TabPage(); @@ -116,15 +123,16 @@ private void InitializeComponent() this.githubLink = new System.Windows.Forms.LinkLabel(); this.label1 = new System.Windows.Forms.Label(); this.textBox1 = new System.Windows.Forms.TextBox(); + this.changelogTab = new System.Windows.Forms.TabPage(); + this.textBox2 = new System.Windows.Forms.TextBox(); this.statusBar = new System.Windows.Forms.ProgressBar(); this.statusStrip = new System.Windows.Forms.StatusStrip(); this.statusState = new System.Windows.Forms.ToolStripStatusLabel(); - this.changelogTab = new System.Windows.Forms.TabPage(); - this.textBox2 = new System.Windows.Forms.TextBox(); + this.intToolTip = new System.Windows.Forms.ToolTip(this.components); this.groupBox1.SuspendLayout(); this.tabControl.SuspendLayout(); this.gameTab.SuspendLayout(); - this.groupBox11.SuspendLayout(); + this.corDataGroup.SuspendLayout(); this.tableLayoutPanel2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.corSmall)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.corBig)).BeginInit(); @@ -136,12 +144,12 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.corSmallBig_Same)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.corBigSmall_Opp)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.corSmallBig_Opp)).BeginInit(); - this.groupBox10.SuspendLayout(); + this.aiDataGroup.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.aiBlocking)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.aiRacingLine)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.aiReflex)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.Overtaking)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.aiOvertaking)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.aiCraziness)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.aiIntelligence)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.aiSpeed)).BeginInit(); @@ -157,9 +165,12 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.compressionBar)).BeginInit(); this.trackTab.SuspendLayout(); this.groupBox2.SuspendLayout(); + this.panel1.SuspendLayout(); + this.groupBox12.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackImage)).BeginInit(); this.aboutTab.SuspendLayout(); - this.statusStrip.SuspendLayout(); this.changelogTab.SuspendLayout(); + this.statusStrip.SuspendLayout(); this.SuspendLayout(); // // rfxunpackBtn @@ -210,8 +221,8 @@ private void InitializeComponent() // gameTab // this.gameTab.BackColor = System.Drawing.SystemColors.ControlLightLight; - this.gameTab.Controls.Add(this.groupBox11); - this.gameTab.Controls.Add(this.groupBox10); + this.gameTab.Controls.Add(this.corDataGroup); + this.gameTab.Controls.Add(this.aiDataGroup); this.gameTab.Controls.Add(this.groupBox9); this.gameTab.Controls.Add(this.groupBox8); this.gameTab.Controls.Add(this.groupBox7); @@ -227,18 +238,18 @@ private void InitializeComponent() this.gameTab.TabIndex = 0; this.gameTab.Text = "Game Files"; // - // groupBox11 + // corDataGroup // - this.groupBox11.Controls.Add(this.corneringHelp); - this.groupBox11.Controls.Add(this.button16); - this.groupBox11.Controls.Add(this.button15); - this.groupBox11.Controls.Add(this.tableLayoutPanel2); - this.groupBox11.Location = new System.Drawing.Point(315, 168); - this.groupBox11.Name = "groupBox11"; - this.groupBox11.Size = new System.Drawing.Size(303, 348); - this.groupBox11.TabIndex = 7; - this.groupBox11.TabStop = false; - this.groupBox11.Text = "Cornering data editor"; + this.corDataGroup.Controls.Add(this.corneringHelp); + this.corDataGroup.Controls.Add(this.cornSaveBtn); + this.corDataGroup.Controls.Add(this.cornLoadBtn); + this.corDataGroup.Controls.Add(this.tableLayoutPanel2); + this.corDataGroup.Location = new System.Drawing.Point(315, 168); + this.corDataGroup.Name = "corDataGroup"; + this.corDataGroup.Size = new System.Drawing.Size(303, 348); + this.corDataGroup.TabIndex = 7; + this.corDataGroup.TabStop = false; + this.corDataGroup.Text = "Cornering data editor"; // // corneringHelp // @@ -251,25 +262,25 @@ private void InitializeComponent() this.corneringHelp.UseVisualStyleBackColor = true; this.corneringHelp.Click += new System.EventHandler(this.corneringHelp_Click); // - // button16 + // cornSaveBtn // - this.button16.Enabled = false; - this.button16.Location = new System.Drawing.Point(6, 319); - this.button16.Name = "button16"; - this.button16.Size = new System.Drawing.Size(262, 23); - this.button16.TabIndex = 4; - this.button16.Text = "Save DAT"; - this.button16.UseVisualStyleBackColor = true; + this.cornSaveBtn.Location = new System.Drawing.Point(6, 319); + this.cornSaveBtn.Name = "cornSaveBtn"; + this.cornSaveBtn.Size = new System.Drawing.Size(262, 23); + this.cornSaveBtn.TabIndex = 4; + this.cornSaveBtn.Text = "Save DAT"; + this.cornSaveBtn.UseVisualStyleBackColor = true; + this.cornSaveBtn.Click += new System.EventHandler(this.cornSaveBtn_Click); // - // button15 + // cornLoadBtn // - this.button15.Enabled = false; - this.button15.Location = new System.Drawing.Point(6, 19); - this.button15.Name = "button15"; - this.button15.Size = new System.Drawing.Size(291, 23); - this.button15.TabIndex = 3; - this.button15.Text = "Load DAT"; - this.button15.UseVisualStyleBackColor = true; + this.cornLoadBtn.Location = new System.Drawing.Point(6, 19); + this.cornLoadBtn.Name = "cornLoadBtn"; + this.cornLoadBtn.Size = new System.Drawing.Size(291, 23); + this.cornLoadBtn.TabIndex = 3; + this.cornLoadBtn.Text = "Load DAT"; + this.cornLoadBtn.UseVisualStyleBackColor = true; + this.cornLoadBtn.Click += new System.EventHandler(this.cornLoadBtn_Click); // // tableLayoutPanel2 // @@ -334,6 +345,11 @@ private void InitializeComponent() // this.corSmall.Cursor = System.Windows.Forms.Cursors.Default; this.corSmall.DecimalPlaces = 2; + this.corSmall.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corSmall.Location = new System.Drawing.Point(119, 3); this.corSmall.Maximum = new decimal(new int[] { 9999, @@ -348,6 +364,11 @@ private void InitializeComponent() // this.corBig.Cursor = System.Windows.Forms.Cursors.Default; this.corBig.DecimalPlaces = 2; + this.corBig.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corBig.Location = new System.Drawing.Point(119, 29); this.corBig.Maximum = new decimal(new int[] { 9999, @@ -434,6 +455,11 @@ private void InitializeComponent() // this.corSmallSmall_Same.Cursor = System.Windows.Forms.Cursors.Default; this.corSmallSmall_Same.DecimalPlaces = 2; + this.corSmallSmall_Same.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corSmallSmall_Same.Location = new System.Drawing.Point(119, 55); this.corSmallSmall_Same.Maximum = new decimal(new int[] { 9999, @@ -448,6 +474,11 @@ private void InitializeComponent() // this.corSmallSmall_Opp.Cursor = System.Windows.Forms.Cursors.Default; this.corSmallSmall_Opp.DecimalPlaces = 2; + this.corSmallSmall_Opp.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corSmallSmall_Opp.Location = new System.Drawing.Point(119, 81); this.corSmallSmall_Opp.Maximum = new decimal(new int[] { 9999, @@ -462,6 +493,11 @@ private void InitializeComponent() // this.corBigBig_Same.Cursor = System.Windows.Forms.Cursors.Default; this.corBigBig_Same.DecimalPlaces = 2; + this.corBigBig_Same.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corBigBig_Same.Location = new System.Drawing.Point(119, 107); this.corBigBig_Same.Maximum = new decimal(new int[] { 9999, @@ -476,6 +512,11 @@ private void InitializeComponent() // this.corBigBig_Opp.Cursor = System.Windows.Forms.Cursors.Default; this.corBigBig_Opp.DecimalPlaces = 2; + this.corBigBig_Opp.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corBigBig_Opp.Location = new System.Drawing.Point(119, 133); this.corBigBig_Opp.Maximum = new decimal(new int[] { 9999, @@ -490,6 +531,11 @@ private void InitializeComponent() // this.corBigSmall_Same.Cursor = System.Windows.Forms.Cursors.Default; this.corBigSmall_Same.DecimalPlaces = 2; + this.corBigSmall_Same.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corBigSmall_Same.Location = new System.Drawing.Point(119, 159); this.corBigSmall_Same.Maximum = new decimal(new int[] { 9999, @@ -504,6 +550,11 @@ private void InitializeComponent() // this.corSmallBig_Same.Cursor = System.Windows.Forms.Cursors.Default; this.corSmallBig_Same.DecimalPlaces = 2; + this.corSmallBig_Same.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corSmallBig_Same.Location = new System.Drawing.Point(119, 185); this.corSmallBig_Same.Maximum = new decimal(new int[] { 9999, @@ -518,6 +569,11 @@ private void InitializeComponent() // this.corBigSmall_Opp.Cursor = System.Windows.Forms.Cursors.Default; this.corBigSmall_Opp.DecimalPlaces = 2; + this.corBigSmall_Opp.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corBigSmall_Opp.Location = new System.Drawing.Point(119, 211); this.corBigSmall_Opp.Maximum = new decimal(new int[] { 9999, @@ -532,6 +588,11 @@ private void InitializeComponent() // this.corSmallBig_Opp.Cursor = System.Windows.Forms.Cursors.Default; this.corSmallBig_Opp.DecimalPlaces = 2; + this.corSmallBig_Opp.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); this.corSmallBig_Opp.Location = new System.Drawing.Point(119, 237); this.corSmallBig_Opp.Maximum = new decimal(new int[] { 9999, @@ -542,19 +603,18 @@ private void InitializeComponent() this.corSmallBig_Opp.Size = new System.Drawing.Size(169, 20); this.corSmallBig_Opp.TabIndex = 22; // - // groupBox10 + // aiDataGroup // - this.groupBox10.Controls.Add(this.button19); - this.groupBox10.Controls.Add(this.button18); - this.groupBox10.Controls.Add(this.button17); - this.groupBox10.Controls.Add(this.button14); - this.groupBox10.Controls.Add(this.tableLayoutPanel1); - this.groupBox10.Location = new System.Drawing.Point(6, 168); - this.groupBox10.Name = "groupBox10"; - this.groupBox10.Size = new System.Drawing.Size(303, 348); - this.groupBox10.TabIndex = 6; - this.groupBox10.TabStop = false; - this.groupBox10.Text = "Car AI data editor"; + this.aiDataGroup.Controls.Add(this.button19); + this.aiDataGroup.Controls.Add(this.aiSaveBtn); + this.aiDataGroup.Controls.Add(this.aiLoadBtn); + this.aiDataGroup.Controls.Add(this.tableLayoutPanel1); + this.aiDataGroup.Location = new System.Drawing.Point(6, 168); + this.aiDataGroup.Name = "aiDataGroup"; + this.aiDataGroup.Size = new System.Drawing.Size(303, 348); + this.aiDataGroup.TabIndex = 6; + this.aiDataGroup.TabStop = false; + this.aiDataGroup.Text = "Car AI data editor"; // // button19 // @@ -567,35 +627,25 @@ private void InitializeComponent() this.button19.UseVisualStyleBackColor = true; this.button19.Click += new System.EventHandler(this.button19_Click); // - // button18 - // - this.button18.Enabled = false; - this.button18.Location = new System.Drawing.Point(6, 48); - this.button18.Name = "button18"; - this.button18.Size = new System.Drawing.Size(291, 23); - this.button18.TabIndex = 4; - this.button18.Text = "Load TXT"; - this.button18.UseVisualStyleBackColor = true; - // - // button17 + // aiSaveBtn // - this.button17.Enabled = false; - this.button17.Location = new System.Drawing.Point(6, 319); - this.button17.Name = "button17"; - this.button17.Size = new System.Drawing.Size(262, 23); - this.button17.TabIndex = 3; - this.button17.Text = "Save BIN"; - this.button17.UseVisualStyleBackColor = true; + this.aiSaveBtn.Location = new System.Drawing.Point(6, 319); + this.aiSaveBtn.Name = "aiSaveBtn"; + this.aiSaveBtn.Size = new System.Drawing.Size(262, 23); + this.aiSaveBtn.TabIndex = 3; + this.aiSaveBtn.Text = "Save BIN"; + this.aiSaveBtn.UseVisualStyleBackColor = true; + this.aiSaveBtn.Click += new System.EventHandler(this.aiSaveBtn_Click); // - // button14 + // aiLoadBtn // - this.button14.Enabled = false; - this.button14.Location = new System.Drawing.Point(6, 19); - this.button14.Name = "button14"; - this.button14.Size = new System.Drawing.Size(291, 23); - this.button14.TabIndex = 2; - this.button14.Text = "Load BIN"; - this.button14.UseVisualStyleBackColor = true; + this.aiLoadBtn.Location = new System.Drawing.Point(6, 19); + this.aiLoadBtn.Name = "aiLoadBtn"; + this.aiLoadBtn.Size = new System.Drawing.Size(291, 23); + this.aiLoadBtn.TabIndex = 2; + this.aiLoadBtn.Text = "Load BIN"; + this.aiLoadBtn.UseVisualStyleBackColor = true; + this.aiLoadBtn.Click += new System.EventHandler(this.aiLoadBtn_Click); // // tableLayoutPanel1 // @@ -607,7 +657,7 @@ private void InitializeComponent() this.tableLayoutPanel1.Controls.Add(this.aiRacingLine, 1, 1); this.tableLayoutPanel1.Controls.Add(this.aiReflex, 1, 0); this.tableLayoutPanel1.Controls.Add(this.label2, 0, 0); - this.tableLayoutPanel1.Controls.Add(this.Overtaking, 1, 2); + this.tableLayoutPanel1.Controls.Add(this.aiOvertaking, 1, 2); this.tableLayoutPanel1.Controls.Add(this.label3, 0, 1); this.tableLayoutPanel1.Controls.Add(this.label4, 0, 2); this.tableLayoutPanel1.Controls.Add(this.label9, 0, 8); @@ -620,7 +670,7 @@ private void InitializeComponent() this.tableLayoutPanel1.Controls.Add(this.aiBraking, 1, 5); this.tableLayoutPanel1.Controls.Add(this.label5, 0, 4); this.tableLayoutPanel1.Controls.Add(this.aiCutsCorners, 1, 4); - this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 77); + this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 51); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 9; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); @@ -640,7 +690,7 @@ private void InitializeComponent() this.aiBlocking.Cursor = System.Windows.Forms.Cursors.Default; this.aiBlocking.Location = new System.Drawing.Point(119, 81); this.aiBlocking.Maximum = new decimal(new int[] { - 32767, + 255, 0, 0, 0}); @@ -692,18 +742,18 @@ private void InitializeComponent() this.label2.Text = "Reflex"; this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // - // Overtaking + // aiOvertaking // - this.Overtaking.Cursor = System.Windows.Forms.Cursors.Default; - this.Overtaking.Location = new System.Drawing.Point(119, 55); - this.Overtaking.Maximum = new decimal(new int[] { + this.aiOvertaking.Cursor = System.Windows.Forms.Cursors.Default; + this.aiOvertaking.Location = new System.Drawing.Point(119, 55); + this.aiOvertaking.Maximum = new decimal(new int[] { 32767, 0, 0, 0}); - this.Overtaking.Name = "Overtaking"; - this.Overtaking.Size = new System.Drawing.Size(169, 20); - this.Overtaking.TabIndex = 3; + this.aiOvertaking.Name = "aiOvertaking"; + this.aiOvertaking.Size = new System.Drawing.Size(169, 20); + this.aiOvertaking.TabIndex = 3; // // label3 // @@ -737,7 +787,7 @@ private void InitializeComponent() this.aiCraziness.Cursor = System.Windows.Forms.Cursors.Default; this.aiCraziness.Location = new System.Drawing.Point(119, 211); this.aiCraziness.Maximum = new decimal(new int[] { - 32767, + 255, 0, 0, 0}); @@ -750,7 +800,7 @@ private void InitializeComponent() this.aiIntelligence.Cursor = System.Windows.Forms.Cursors.Default; this.aiIntelligence.Location = new System.Drawing.Point(119, 185); this.aiIntelligence.Maximum = new decimal(new int[] { - 32767, + 255, 0, 0, 0}); @@ -825,7 +875,7 @@ private void InitializeComponent() this.aiCutsCorners.Cursor = System.Windows.Forms.Cursors.Default; this.aiCutsCorners.Location = new System.Drawing.Point(119, 107); this.aiCutsCorners.Maximum = new decimal(new int[] { - 32767, + 255, 0, 0, 0}); @@ -968,12 +1018,14 @@ private void InitializeComponent() // this.groupBox5.Controls.Add(this.button5); this.groupBox5.Controls.Add(this.mdfeditBtn); + this.groupBox5.ForeColor = System.Drawing.Color.Red; this.groupBox5.Location = new System.Drawing.Point(418, 6); this.groupBox5.Name = "groupBox5"; this.groupBox5.Size = new System.Drawing.Size(200, 48); this.groupBox5.TabIndex = 3; this.groupBox5.TabStop = false; this.groupBox5.Text = "MDF editor"; + this.intToolTip.SetToolTip(this.groupBox5, "Not yet implemented!"); // // button5 // @@ -1035,12 +1087,14 @@ private void InitializeComponent() this.groupBox3.Controls.Add(this.button1); this.groupBox3.Controls.Add(this.compressionBar); this.groupBox3.Controls.Add(this.button2); + this.groupBox3.ForeColor = System.Drawing.Color.Red; this.groupBox3.Location = new System.Drawing.Point(6, 60); this.groupBox3.Name = "groupBox3"; this.groupBox3.Size = new System.Drawing.Size(200, 102); this.groupBox3.TabIndex = 2; this.groupBox3.TabStop = false; this.groupBox3.Text = "RFH/RFD packer"; + this.intToolTip.SetToolTip(this.groupBox3, "Not yet implemented!"); // // compressionLevel // @@ -1089,6 +1143,8 @@ private void InitializeComponent() // this.trackTab.BackColor = System.Drawing.SystemColors.ControlLightLight; this.trackTab.Controls.Add(this.groupBox2); + this.trackTab.Controls.Add(this.trkxtkHelp); + this.trackTab.Controls.Add(this.trkxtkLoad); this.trackTab.Location = new System.Drawing.Point(4, 22); this.trackTab.Name = "trackTab"; this.trackTab.Padding = new System.Windows.Forms.Padding(3); @@ -1098,31 +1154,95 @@ private void InitializeComponent() // // groupBox2 // - this.groupBox2.Controls.Add(this.trkxtkInfoBtn); - this.groupBox2.Controls.Add(this.trkxtkHelp); - this.groupBox2.Controls.Add(this.trkxtkLoad); - this.groupBox2.Location = new System.Drawing.Point(6, 6); + this.groupBox2.Controls.Add(this.panel1); + this.groupBox2.Location = new System.Drawing.Point(200, 6); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(200, 74); + this.groupBox2.Size = new System.Drawing.Size(418, 510); this.groupBox2.TabIndex = 2; this.groupBox2.TabStop = false; - this.groupBox2.Text = "TRK/XTK manager"; - // - // trkxtkInfoBtn - // - this.trkxtkInfoBtn.Enabled = false; - this.trkxtkInfoBtn.Location = new System.Drawing.Point(6, 45); - this.trkxtkInfoBtn.Name = "trkxtkInfoBtn"; - this.trkxtkInfoBtn.Size = new System.Drawing.Size(159, 23); - this.trkxtkInfoBtn.TabIndex = 2; - this.trkxtkInfoBtn.Text = "Track Info"; - this.trkxtkInfoBtn.UseVisualStyleBackColor = true; + this.groupBox2.Text = "Track Info"; + // + // panel1 + // + this.panel1.Controls.Add(this.groupBox12); + this.panel1.Controls.Add(this.trackSize); + this.panel1.Controls.Add(this.trackCompat); + this.panel1.Controls.Add(this.trackTheme); + this.panel1.Controls.Add(this.trackMode); + this.panel1.Controls.Add(this.trackLabel); + this.panel1.Location = new System.Drawing.Point(6, 19); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(406, 485); + this.panel1.TabIndex = 4; + // + // groupBox12 + // + this.groupBox12.Controls.Add(this.trackImage); + this.groupBox12.Location = new System.Drawing.Point(3, 137); + this.groupBox12.Name = "groupBox12"; + this.groupBox12.Size = new System.Drawing.Size(332, 345); + this.groupBox12.TabIndex = 9; + this.groupBox12.TabStop = false; + this.groupBox12.Text = "Track Thumbnail"; + // + // trackImage + // + this.trackImage.Location = new System.Drawing.Point(6, 19); + this.trackImage.Name = "trackImage"; + this.trackImage.Size = new System.Drawing.Size(320, 320); + this.trackImage.TabIndex = 4; + this.trackImage.TabStop = false; + // + // trackSize + // + this.trackSize.AutoSize = true; + this.trackSize.Location = new System.Drawing.Point(3, 35); + this.trackSize.Name = "trackSize"; + this.trackSize.Size = new System.Drawing.Size(33, 13); + this.trackSize.TabIndex = 8; + this.trackSize.Text = "Size: "; + // + // trackCompat + // + this.trackCompat.AutoSize = true; + this.trackCompat.Location = new System.Drawing.Point(3, 71); + this.trackCompat.Name = "trackCompat"; + this.trackCompat.Size = new System.Drawing.Size(71, 13); + this.trackCompat.TabIndex = 7; + this.trackCompat.Text = "Compatibility: "; + // + // trackTheme + // + this.trackTheme.AutoSize = true; + this.trackTheme.Location = new System.Drawing.Point(3, 53); + this.trackTheme.Name = "trackTheme"; + this.trackTheme.Size = new System.Drawing.Size(46, 13); + this.trackTheme.TabIndex = 6; + this.trackTheme.Text = "Theme: "; + // + // trackMode + // + this.trackMode.AutoSize = true; + this.trackMode.Location = new System.Drawing.Point(3, 17); + this.trackMode.Name = "trackMode"; + this.trackMode.Size = new System.Drawing.Size(40, 13); + this.trackMode.TabIndex = 5; + this.trackMode.Text = "Mode: "; + // + // trackLabel + // + this.trackLabel.AutoSize = true; + this.trackLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.trackLabel.Location = new System.Drawing.Point(3, 0); + this.trackLabel.Name = "trackLabel"; + this.trackLabel.Size = new System.Drawing.Size(44, 13); + this.trackLabel.TabIndex = 3; + this.trackLabel.Text = "Track:"; // // trkxtkHelp // - this.trkxtkHelp.Enabled = false; this.trkxtkHelp.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.trkxtkHelp.Location = new System.Drawing.Point(171, 45); + this.trkxtkHelp.Location = new System.Drawing.Point(171, 6); this.trkxtkHelp.Name = "trkxtkHelp"; this.trkxtkHelp.Size = new System.Drawing.Size(23, 23); this.trkxtkHelp.TabIndex = 1; @@ -1132,14 +1252,13 @@ private void InitializeComponent() // // trkxtkLoad // - this.trkxtkLoad.Enabled = false; - this.trkxtkLoad.Location = new System.Drawing.Point(6, 19); + this.trkxtkLoad.Location = new System.Drawing.Point(6, 6); this.trkxtkLoad.Name = "trkxtkLoad"; this.trkxtkLoad.Size = new System.Drawing.Size(159, 23); this.trkxtkLoad.TabIndex = 0; this.trkxtkLoad.Text = "Load Track"; this.trkxtkLoad.UseVisualStyleBackColor = true; - this.trkxtkLoad.Click += new System.EventHandler(this.TRK_to_XTK_Click); + this.trkxtkLoad.Click += new System.EventHandler(this.trkxtkLoad_Click); // // aboutTab // @@ -1186,6 +1305,7 @@ private void InitializeComponent() this.label1.TabIndex = 2; this.label1.Text = "Stunt Rally Toolbox"; this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.intToolTip.SetToolTip(this.label1, "You found me!"); this.label1.DoubleClick += new System.EventHandler(this.label1_DoubleClick); // // textBox1 @@ -1201,6 +1321,27 @@ private void InitializeComponent() this.textBox1.TabIndex = 1; this.textBox1.Text = resources.GetString("textBox1.Text"); // + // changelogTab + // + this.changelogTab.Controls.Add(this.textBox2); + this.changelogTab.Location = new System.Drawing.Point(4, 22); + this.changelogTab.Name = "changelogTab"; + this.changelogTab.Size = new System.Drawing.Size(624, 522); + this.changelogTab.TabIndex = 3; + this.changelogTab.Text = "Changelog"; + this.changelogTab.UseVisualStyleBackColor = true; + // + // textBox2 + // + this.textBox2.Location = new System.Drawing.Point(3, 3); + this.textBox2.Multiline = true; + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + this.textBox2.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.textBox2.Size = new System.Drawing.Size(618, 516); + this.textBox2.TabIndex = 0; + this.textBox2.Text = resources.GetString("textBox2.Text"); + // // statusBar // this.statusBar.Location = new System.Drawing.Point(12, 566); @@ -1226,27 +1367,6 @@ private void InitializeComponent() this.statusState.Size = new System.Drawing.Size(70, 17); this.statusState.Text = "Initializing..."; // - // changelogTab - // - this.changelogTab.Controls.Add(this.textBox2); - this.changelogTab.Location = new System.Drawing.Point(4, 22); - this.changelogTab.Name = "changelogTab"; - this.changelogTab.Size = new System.Drawing.Size(624, 522); - this.changelogTab.TabIndex = 3; - this.changelogTab.Text = "Changelog"; - this.changelogTab.UseVisualStyleBackColor = true; - // - // textBox2 - // - this.textBox2.Location = new System.Drawing.Point(3, 3); - this.textBox2.Multiline = true; - this.textBox2.Name = "textBox2"; - this.textBox2.ReadOnly = true; - this.textBox2.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.textBox2.Size = new System.Drawing.Size(618, 516); - this.textBox2.TabIndex = 0; - this.textBox2.Text = "== 0.1.0\r\n- Initial release."; - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1265,7 +1385,7 @@ private void InitializeComponent() this.groupBox1.ResumeLayout(false); this.tabControl.ResumeLayout(false); this.gameTab.ResumeLayout(false); - this.groupBox11.ResumeLayout(false); + this.corDataGroup.ResumeLayout(false); this.tableLayoutPanel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.corSmall)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.corBig)).EndInit(); @@ -1277,12 +1397,12 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.corSmallBig_Same)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.corBigSmall_Opp)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.corSmallBig_Opp)).EndInit(); - this.groupBox10.ResumeLayout(false); + this.aiDataGroup.ResumeLayout(false); this.tableLayoutPanel1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.aiBlocking)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.aiRacingLine)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.aiReflex)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.Overtaking)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.aiOvertaking)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.aiCraziness)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.aiIntelligence)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.aiSpeed)).EndInit(); @@ -1299,12 +1419,16 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.compressionBar)).EndInit(); this.trackTab.ResumeLayout(false); this.groupBox2.ResumeLayout(false); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.groupBox12.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.trackImage)).EndInit(); this.aboutTab.ResumeLayout(false); this.aboutTab.PerformLayout(); - this.statusStrip.ResumeLayout(false); - this.statusStrip.PerformLayout(); this.changelogTab.ResumeLayout(false); this.changelogTab.PerformLayout(); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -1321,7 +1445,6 @@ private void InitializeComponent() private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.Button trkxtkHelp; private System.Windows.Forms.Button trkxtkLoad; - private System.Windows.Forms.Button trkxtkInfoBtn; private System.Windows.Forms.TabPage aboutTab; private System.Windows.Forms.GroupBox groupBox3; private System.Windows.Forms.Label compressionLevel; @@ -1351,8 +1474,8 @@ private void InitializeComponent() private System.Windows.Forms.GroupBox groupBox8; private System.Windows.Forms.Button button10; private System.Windows.Forms.Button button11; - private System.Windows.Forms.GroupBox groupBox11; - private System.Windows.Forms.GroupBox groupBox10; + private System.Windows.Forms.GroupBox corDataGroup; + private System.Windows.Forms.GroupBox aiDataGroup; private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.NumericUpDown aiReflex; private System.Windows.Forms.Label label2; @@ -1368,7 +1491,7 @@ private void InitializeComponent() private System.Windows.Forms.NumericUpDown aiCutsCorners; private System.Windows.Forms.Label label5; private System.Windows.Forms.NumericUpDown aiRacingLine; - private System.Windows.Forms.NumericUpDown Overtaking; + private System.Windows.Forms.NumericUpDown aiOvertaking; private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label4; private System.Windows.Forms.Label label7; @@ -1391,11 +1514,9 @@ private void InitializeComponent() private System.Windows.Forms.NumericUpDown corSmallBig_Same; private System.Windows.Forms.NumericUpDown corBigSmall_Opp; private System.Windows.Forms.NumericUpDown corSmallBig_Opp; - private System.Windows.Forms.Button button16; - private System.Windows.Forms.Button button15; - private System.Windows.Forms.Button button18; - private System.Windows.Forms.Button button17; - private System.Windows.Forms.Button button14; + private System.Windows.Forms.Button cornSaveBtn; + private System.Windows.Forms.Button cornLoadBtn; + private System.Windows.Forms.Button aiLoadBtn; private System.Windows.Forms.NumericUpDown aiBlocking; private System.Windows.Forms.Label label20; private System.Windows.Forms.Button corneringHelp; @@ -1404,6 +1525,16 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripStatusLabel statusState; private System.Windows.Forms.TextBox textBox2; private System.Windows.Forms.TabPage changelogTab; + private System.Windows.Forms.Label trackLabel; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.PictureBox trackImage; + private System.Windows.Forms.Label trackMode; + private System.Windows.Forms.Label trackSize; + private System.Windows.Forms.Label trackCompat; + private System.Windows.Forms.Label trackTheme; + private System.Windows.Forms.GroupBox groupBox12; + private System.Windows.Forms.Button aiSaveBtn; + private System.Windows.Forms.ToolTip intToolTip; } } diff --git a/MainForm.cs b/MainForm.cs index c65941a..c8b0215 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -1,4 +1,5 @@ -using System; +using Pfim; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -15,59 +16,80 @@ namespace SRtoolbox { public partial class MainForm : Form { - public string tagline = "The first release!"; - public string version = "0.1.0"; + public string tagline = "It's all in the timing..."; + public string version = "0.1.1"; + + public TRKFile trackFile; + + public void checkFile(string file) + { + string ext = Path.GetExtension(file); + + if (ext == ".trk") + { + tabControl.SelectedIndex = 1; // Track tab + trkLoad(file); + } + else if (ext == ".bin") aiLoad(file); + else if (ext == ".dat") cornLoad(file); + else if (ext == ".rfh") rfxUnpack(file); + } public MainForm() { InitializeComponent(); - - + statusBar.Style = ProgressBarStyle.Continuous; statusState.Text = "Idle"; + + if(Program.arguments.Length > 0) + { + foreach (var arg in Program.arguments) + { + checkFile(arg); + } + } } - private void RFx_Unpack_Click(object sender, EventArgs e) + public void rfxUnpack(string filename) { statusBar.Style = ProgressBarStyle.Marquee; statusState.Text = "Working..."; - OpenFileDialog openFileDialog = new OpenFileDialog(); - openFileDialog.Title = "Open RFH File"; - openFileDialog.Filter = "Resource File Header (*.rfh)|*.rfh"; - if (openFileDialog.ShowDialog() == DialogResult.OK) + + string text = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".rfd"; + if (File.Exists(text)) { - string text = Path.GetDirectoryName(openFileDialog.FileName) + "\\" + Path.GetFileNameWithoutExtension(openFileDialog.FileName) + ".rfd"; - if (File.Exists(text)) - { - RFH rfh = new RFH(); - rfh.load(openFileDialog.FileName, text); - rfh.extractAllFiles(Path.GetFileNameWithoutExtension(openFileDialog.FileName)); - statusBar.Style = ProgressBarStyle.Continuous; - statusState.Text = "Idle"; - MessageBox.Show("Successfully unpacked "+Path.GetFileName(openFileDialog.FileName)+".", "RFH/RFD Unpacker"); - } - else - { - statusBar.Style = ProgressBarStyle.Continuous; - MessageBox.Show("Error: Could not locate the data file for this RFH file.", "RFH/RFD Unpacker", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + RFH rfh = new RFH(); + rfh.load(filename, text); + rfh.extractAllFiles(Path.GetFileNameWithoutExtension(filename)); + MessageBox.Show("Successfully unpacked " + Path.GetFileName(filename) + ".", "RFH/RFD Unpacker"); + } + else + { + MessageBox.Show("Error: Could not locate the data file for this RFH file.", "RFH/RFD Unpacker", MessageBoxButtons.OK, MessageBoxIcon.Error); } statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; } - private void RFx_Unpack_Help_Click(object sender, EventArgs e) + private void RFx_Unpack_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nUnpacks a Resource File Data file using the associated Resource File Header file into a directory.\n\nWritten by:\nCyrem, Yellowberry", "RFH/RFD Unpacker",MessageBoxButtons.OK, MessageBoxIcon.Information); + OpenFileDialog openFileDialog = new OpenFileDialog(); + openFileDialog.Title = "Open RFH File"; + openFileDialog.Filter = "Resource File Header (*.rfh)|*.rfh"; + if (openFileDialog.ShowDialog(this) != DialogResult.OK) { return; } + + rfxUnpack(openFileDialog.FileName); } - private void TRK_to_XTK_Click(object sender, EventArgs e) + private void RFx_Unpack_Help_Click(object sender, EventArgs e) { - + MessageBox.Show("Unpacks a Resource File Data file using the associated Resource File Header file into a directory.\n\nWritten by:\nCyrem, Yellowberry", "RFH/RFD Unpacker",MessageBoxButtons.OK, MessageBoxIcon.Information); } private void trkxtkHelp_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nManages the loading and conversion of TRK and XTK files.\n\nWritten by:\nYellowberry", "TRK/XTK Manager", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Manages the loading and conversion of TRK and XTK files.\n\nWritten by:\nYellowberry", "TRK/XTK Manager", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void compressionLevel_Click(object sender, EventArgs e) @@ -82,7 +104,7 @@ private void compressionBar_ValueChanged(object sender, EventArgs e) private void xbfobjHelp_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nConverts LSR binary models into OBJ format (very experimental and buggy).\n\nWritten by:\nSluicer, Yellowberry", "XBF to OBJ Converter", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Converts LSR binary models into OBJ format (very experimental and buggy).\n\nWritten by:\nSluicer, Yellowberry", "XBF to OBJ Converter", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void RFHPacker_Click(object sender, EventArgs e) @@ -92,7 +114,7 @@ private void RFHPacker_Click(object sender, EventArgs e) openFileDialog.Title = "Open TRK File"; openFileDialog.Filter = "Resource File Header (*.trk)|*.trk"; openFileDialog.ShowDialog(); - byte[] packed = rfh.packFile(openFileDialog.FileName,"", 0,false); + byte[] packed = rfh.packFile(openFileDialog.FileName,string.Empty, 0,false); FileStream fileStream = new FileStream(openFileDialog.FileName+".zl", FileMode.Create); BinaryWriter binaryWriter = new BinaryWriter(fileStream); binaryWriter.Write(packed); @@ -100,30 +122,17 @@ private void RFHPacker_Click(object sender, EventArgs e) fileStream.Close(); } - private void button4_Click(object sender, EventArgs e) + public void xbfobjConvert(string filename, string location) { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; try { - statusBar.Style = ProgressBarStyle.Marquee; - statusState.Text = "Working..."; - XBFtool xBFtool = new XBFtool(); - OpenFileDialog ofd = new OpenFileDialog(); - ofd.Title = "Open XBF File"; - ofd.Filter = "XBF file (*.xbf)|*.xbf"; - if (ofd.ShowDialog() == DialogResult.OK) - { - FolderBrowserDialog fbd = new FolderBrowserDialog(); - fbd.Description = "Please select a folder for the converted files."; - fbd.SelectedPath = Path.GetDirectoryName(ofd.FileName); - if (fbd.ShowDialog() == DialogResult.OK) - { - xBFtool.Setup(ofd.FileName); - xBFtool.Extract(fbd.SelectedPath); - statusBar.Style = ProgressBarStyle.Continuous; - statusState.Text = "Idle"; - MessageBox.Show("Successfully converted " + Path.GetFileName(ofd.FileName) + ".", "XBF to OBJ Converter"); - } - } + XBFtool xbf = new XBFtool(); + + xbf.Setup(filename); + xbf.Extract(location); + MessageBox.Show("Successfully converted " + Path.GetFileName(filename) + ".", "XBF to OBJ Converter"); } catch (NotSupportedException ex) { @@ -133,6 +142,23 @@ private void button4_Click(object sender, EventArgs e) { MessageBox.Show("Exception: " + ex.ToString(), "XBF to OBJ Converter", MessageBoxButtons.OK, MessageBoxIcon.Error); } + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void button4_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Title = "Open XBF File"; + ofd.Filter = "XBF file (*.xbf)|*.xbf"; + if (ofd.ShowDialog(this) != DialogResult.OK) { return; } + + FolderBrowserDialog fbd = new FolderBrowserDialog(); + fbd.Description = "Please select a folder for the converted files."; + fbd.SelectedPath = Path.GetDirectoryName(ofd.FileName); + if (fbd.ShowDialog(this) != DialogResult.OK) { return; } + + xbfobjConvert(ofd.FileName, fbd.SelectedPath); } private void button7_Click(object sender, EventArgs e) @@ -143,17 +169,17 @@ private void button7_Click(object sender, EventArgs e) private void button3_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nAllows you to view the contents of the MOTO.rtb file, which contains the asset mapping for the game.\n\nWritten by:\nYellowberry", "RTB Viewer", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Allows you to view the contents of the MOTO.rtb file, which contains the asset mapping for the game.\n\nWritten by:\nYellowberry", "RTB Viewer", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void button19_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nAllows you to edit the parameters of the AI characters.\n\nWritten by:\nYellowberry", "AI Data Editor", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Allows you to edit the parameters of the AI characters.\n\nWritten by:\nYellowberry", "AI Data Editor", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void corneringHelp_Click(object sender, EventArgs e) { - MessageBox.Show("About: \nAllows you to edit the AI cornering data of each theme.\n\nWritten by:\nYellowberry", "Cornering Data Editor", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show("Allows you to edit the AI cornering data of each theme.\n\nWritten by:\nYellowberry", "Cornering Data Editor", MessageBoxButtons.OK, MessageBoxIcon.Information); } private void mdfeditBtn_Click(object sender, EventArgs e) @@ -176,5 +202,179 @@ private void label1_DoubleClick(object sender, EventArgs e) { MessageBox.Show("ok boomer", "a message from yellowberry"); } + + public void trkLoad(string filename) + { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; + + TRK trk = new TRK(); + trackFile = trk.load(filename); + trackLabel.Text = "Track: " + Path.GetFileName(filename); + trackImage.Image = trackFile.image; + trackMode.Text = "Mode: " + trackFile.type.ToString(); + trackSize.Text = "Size: " + trackFile.size.ToString(true); + trackTheme.Text = "Theme: " + trackFile.theme.ToString(); + trackCompat.Text = "Compatibility: " + trackFile.compat.ToString(); + + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void trkxtkLoad_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Title = "Open track file"; + ofd.Filter = "Track files (*.trk, *.xtk)|*.trk;*.xtk"; + if (ofd.ShowDialog(this) != DialogResult.OK) { return; } + + trkLoad(ofd.FileName); + } + + public void cornLoad(string filename) + { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; + + CorneringFile cor; + Cornering cornering = new Cornering(); + + cor = cornering.load(filename); + + corDataGroup.Text = "Cornering data editor ( " + Path.GetFileName(filename) + " )"; + corSmall.Value = (decimal)cor.Small; + corBig.Value = (decimal)cor.Big; + corSmallSmall_Same.Value = (decimal)cor.SmallSmall_Same; + corSmallSmall_Same.Value = (decimal)cor.SmallSmall_Same; + corSmallSmall_Opp.Value = (decimal)cor.SmallSmall_Opp; + corBigBig_Same.Value = (decimal)cor.BigBig_Same; + corBigBig_Opp.Value = (decimal)cor.BigBig_Opp; + corBigSmall_Same.Value = (decimal)cor.BigSmall_Same; + corSmallBig_Same.Value = (decimal)cor.SmallBig_Same; + corBigSmall_Opp.Value = (decimal)cor.BigSmall_Opp; + corSmallBig_Opp.Value = (decimal)cor.SmallBig_Opp; + + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void cornLoadBtn_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Title = "Open cornering data file"; + ofd.Filter = "Cornering data (*.dat)|*.dat"; + if (ofd.ShowDialog(this) != DialogResult.OK) { return; } + + cornLoad(ofd.FileName); + } + + public void cornSave(string filename) + { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; + + CorneringFile cor = new CorneringFile(); + Cornering cornering = new Cornering(); + + corDataGroup.Text = "Cornering data editor ( " + Path.GetFileName(filename) + " )"; + cor.Small = (float)corSmall.Value; + cor.Big = (float)corBig.Value; + cor.SmallSmall_Same = (float)corSmallSmall_Same.Value; + cor.SmallSmall_Same = (float)corSmallSmall_Same.Value; + cor.SmallSmall_Opp = (float)corSmallSmall_Opp.Value; + cor.BigBig_Same = (float)corBigBig_Same.Value; + cor.BigBig_Opp = (float)corBigBig_Opp.Value; + cor.BigSmall_Same = (float)corBigSmall_Same.Value; + cor.SmallBig_Same = (float)corSmallBig_Same.Value; + cor.BigSmall_Opp = (float)corBigSmall_Opp.Value; + cor.SmallBig_Opp = (float)corSmallBig_Opp.Value; + + cornering.save(filename, cor); + + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void cornSaveBtn_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Title = "Save cornering data file as"; + sfd.Filter = "Cornering data (*.dat)|*.dat"; + if (sfd.ShowDialog(this) != DialogResult.OK) { return; } + + cornSave(sfd.FileName); + } + + public void aiLoad(string filename) + { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; + + AIFile ai; + AIData aidata = new AIData(); + + ai = aidata.load(filename); + + aiDataGroup.Text = "Car AI data editor ( " + Path.GetFileName(filename) + " )"; + + aiRacingLine.Value = ai.RacingLine; + aiBraking.Value = ai.Braking; + aiOvertaking.Value = ai.Overtaking; + aiSpeed.Value = ai.Speed; + aiReflex.Value = ai.Reflex; + aiBlocking.Value = ai.Blocking; + aiCutsCorners.Value = ai.CutsCorners; + aiIntelligence.Value = ai.Intelligence; + aiCraziness.Value = ai.Craziness; + + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void aiLoadBtn_Click(object sender, EventArgs e) + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Title = "Open AI data file"; + ofd.Filter = "AI data (*.bin)|*.bin"; + if (ofd.ShowDialog(this) != DialogResult.OK) { return; } + + aiLoad(ofd.FileName); + } + + public void aiSave(string filename) + { + statusBar.Style = ProgressBarStyle.Marquee; + statusState.Text = "Working..."; + + AIFile ai = new AIFile(); + AIData aidata = new AIData(); + + aiDataGroup.Text = "Car AI data editor ( " + Path.GetFileName(filename) + " )"; + + ai.RacingLine = (ushort)aiRacingLine.Value; + ai.Braking = (ushort)aiBraking.Value; + ai.Overtaking = (ushort)aiOvertaking.Value; + ai.Speed = (ushort)aiSpeed.Value; + ai.Reflex = (ushort)aiReflex.Value; + ai.Blocking = (byte)aiBlocking.Value; + ai.CutsCorners = (byte)aiCutsCorners.Value; + ai.Intelligence = (byte)aiIntelligence.Value; + ai.Craziness = (byte)aiCraziness.Value; + + aidata.save(filename, ai); + + statusBar.Style = ProgressBarStyle.Continuous; + statusState.Text = "Idle"; + } + + private void aiSaveBtn_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Title = "Save AI data file as"; + sfd.Filter = "AI data (*.bin)|*.bin"; + if (sfd.ShowDialog(this) != DialogResult.OK) { return; } + + aiSave(sfd.FileName); + } } } diff --git a/MainForm.resx b/MainForm.resx index 8c5226e..5cf5d6c 100644 --- a/MainForm.resx +++ b/MainForm.resx @@ -117,10 +117,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 126, 17 + === Credits === -Yellowberry (Zsolt Zitting) - Stunt Rally Toolbox, RFH/RFD Unpacker and Packer, RTB Viewer, XTK format +Yellowberry (Zsolt Zitting) - SRT, RFH/RFD Unpacker/Packer, RTB Viewer, Track Manger, XTK format Cyrem - RFH/RFD Unpacker Sluicer - XBF to OBJ Converter @@ -132,7 +135,7 @@ mumboking - AI Data and Cornering formats === The MIT License === -Copyright (c) 2019 Zsolt Zitting +Copyright (c) 2019-2020 Zsolt Zitting Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -146,6 +149,16 @@ This software uses icons from http://glyphish.com, licensed under the CC 4.0 Att True + + == 0.1.1 +- Fixed timestamp issue when extracting RFx files (thanks Cyrem!) +- Added AI Data and Cornering data loading and saving +- Added TRK track loading, track info display and track thumbnail +- Optimized some code, bugfixes + +== 0.1.0 +- Initial release. + 17, 17 diff --git a/Program.cs b/Program.cs index 7e360f0..da3dfb4 100644 --- a/Program.cs +++ b/Program.cs @@ -7,35 +7,34 @@ namespace SRtoolbox { - static class Program + class Program { + public static string[] arguments; + /// /// The main entry point for the application. /// /// + [STAThread] - static void Main() + static void Main(string[] cmdargs) { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); + arguments = cmdargs; + + EmbeddedAssembly.Load("SRtoolbox.lib.Ionic.Zip.dll", "Ionic.Zip.dll"); + EmbeddedAssembly.Load("SRtoolbox.lib.Pfim.dll", "Pfim.dll"); - MainForm form = new MainForm(); + AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); - // Stupid .dll resource embedding hacks - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { - string resourceName = new AssemblyName(args.Name).Name + ".dll"; - string resource = Array.Find(form.GetType().Assembly.GetManifestResourceNames(), element => element.EndsWith(resourceName)); - - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource)) - { - Byte[] assemblyData = new Byte[stream.Length]; - stream.Read(assemblyData, 0, assemblyData.Length); - return Assembly.Load(assemblyData); - } - }; - - Application.Run(form); + return EmbeddedAssembly.Get(args.Name); + } + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + Application.Run(new MainForm()); } } } diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index bb6c991..34d4fd0 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("SRtoolbox")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("Yellowberry")] [assembly: AssemblyProduct("SRtoolbox")] -[assembly: AssemblyCopyright("Copyright © Yellowberry 2019")] +[assembly: AssemblyCopyright("Copyright © Yellowberry 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // 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("0.1.0.0")] -[assembly: AssemblyFileVersion("0.1.0.0")] +[assembly: AssemblyVersion("0.1.1.0")] +[assembly: AssemblyFileVersion("0.1.1.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index b7bf0a9..f8e259d 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -60,16 +60,6 @@ internal Resources() { } } - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] Ionic_Zip { - get { - object obj = ResourceManager.GetObject("Ionic_Zip", resourceCulture); - return ((byte[])(obj)); - } - } - /// /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index f5b5da5..8f0409c 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -118,9 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\lib\Ionic.Zip.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\res\toolbox-red.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index 0c9031e..89f27dd 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace SRtoolbox.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/SRtoolbox.csproj b/SRtoolbox.csproj index e4d1f19..ac3b57e 100644 --- a/SRtoolbox.csproj +++ b/SRtoolbox.csproj @@ -8,7 +8,7 @@ WinExe SRtoolbox SRtoolbox - v4.6 + v4.6.1 512 true @@ -34,12 +34,20 @@ 4 true + + true + False lib\Ionic.Zip.dll False + + False + lib\Pfim.dll + False + @@ -53,6 +61,10 @@ + + + + Form @@ -66,6 +78,7 @@ RTBViewForm.cs + Form @@ -85,6 +98,7 @@ + MDFEditForm.cs @@ -121,12 +135,10 @@ - - - + diff --git a/Structures.cs b/Structures.cs new file mode 100644 index 0000000..b225a7d --- /dev/null +++ b/Structures.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SRtoolbox +{ + /* Resource Files */ + + public class RFxFile + { + public int pathLength; + public DateTime timestamp; + public int compressionType; + public int compressedSize; + public int offset; + public string filepath; + public byte[] data; + } + + /* Track Files */ + + public class TrackFile + { + public LSRUtil.TrackType type; + public LSRUtil.TrackCoord size; + public LSRUtil.Theme theme; + public LSRUtil.TrackTime time; + public int filesize; + public Bitmap image; + public LSRUtil.Compatibility compat; + + public List pieces; + } + + public class TRKFile : TrackFile + { + public string legoHeader; + public int crashInt; + + public byte[][][][] rawPieces; + } + + public class XTKFile : TrackFile + { + // TODO + } + + public class TrackPiece + { + public int nid; + public byte id; + public LSRUtil.Theme theme; + public LSRUtil.TrackCoord position; + public int height; + public int rotation; + } + + /* AI Files */ + + public class CorneringFile + { + public float Small; + public float Big; + public float SmallSmall_Same; + public float SmallSmall_Opp; + public float BigBig_Same; + public float BigBig_Opp; + public float BigSmall_Same; + public float SmallBig_Same; + public float BigSmall_Opp; + public float SmallBig_Opp; + } + + public class AIFile + { + public ushort Reflex; + public ushort RacingLine; + public ushort Overtaking; + public byte Blocking; + public byte CutsCorners; + public ushort Braking; + public ushort Speed; + public byte Intelligence; + public byte Craziness; + } +} diff --git a/lib/Pfim.dll b/lib/Pfim.dll new file mode 100644 index 0000000000000000000000000000000000000000..91c2f08a2110f5b1cd018ae725c1918c18788935 GIT binary patch literal 93184 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