From 75b02591f2566648537854b71f3ecb067a948caa Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 8 Jan 2024 00:36:31 +1100 Subject: [PATCH 01/13] fix string table not updating correctly --- OpenLocoToolGui/MainForm.Designer.cs | 280 +++++++++--------- OpenLocoToolGui/MainForm.cs | 83 +++--- OpenLocoToolGui/MainForm.resx | 14 +- .../StringTableUserControl.Designer.cs | 17 +- OpenLocoToolGui/StringTableUserControl.cs | 37 ++- 5 files changed, 237 insertions(+), 194 deletions(-) diff --git a/OpenLocoToolGui/MainForm.Designer.cs b/OpenLocoToolGui/MainForm.Designer.cs index f4c04b06..0e5fb76b 100644 --- a/OpenLocoToolGui/MainForm.Designer.cs +++ b/OpenLocoToolGui/MainForm.Designer.cs @@ -37,13 +37,10 @@ private void InitializeComponent() saveFileDialog1 = new SaveFileDialog(); flpImageTable = new FlowLayoutPanel(); scObjectAndLogs = new SplitContainer(); + pnObjectEditor = new Panel(); + toolStrip1 = new ToolStrip(); + toolStripButton1 = new ToolStripButton(); tcObjectOverview = new TabControl(); - tpObjectFileHeaders = new TabPage(); - tcFileHeaders = new TabControl(); - tpObjectS5Header = new TabPage(); - pgS5Header = new PropertyGrid(); - tpObjectObjHeader = new TabPage(); - pgObjHeader = new PropertyGrid(); tpObjectView = new TabPage(); tcSubObjectView = new TabControl(); tpObjectObject = new TabPage(); @@ -55,6 +52,9 @@ private void InitializeComponent() tbCurrentPage = new TextBox(); btnPagePrevious = new Button(); btnPageNext = new Button(); + scHeaders = new SplitContainer(); + pgS5Header = new PropertyGrid(); + pgObjHeader = new PropertyGrid(); tpObjectDump = new TabPage(); scAnnnotationDump = new SplitContainer(); tvDATDumpAnnotations = new TreeView(); @@ -77,7 +77,6 @@ private void InitializeComponent() toolStripSeparator2 = new ToolStripSeparator(); setPaletteToolStripMenuItem = new ToolStripMenuItem(); recreateIndexToolStripMenuItem = new ToolStripMenuItem(); - saveChangesToolStripMenuItem = new ToolStripMenuItem(); imgContextMenu = new ContextMenuStrip(components); imgContextMenuSave = new ToolStripMenuItem(); goToHeaderInDumpToolStripMenuItem = new ToolStripMenuItem(); @@ -88,11 +87,9 @@ private void InitializeComponent() scObjectAndLogs.Panel1.SuspendLayout(); scObjectAndLogs.Panel2.SuspendLayout(); scObjectAndLogs.SuspendLayout(); + pnObjectEditor.SuspendLayout(); + toolStrip1.SuspendLayout(); tcObjectOverview.SuspendLayout(); - tpObjectFileHeaders.SuspendLayout(); - tcFileHeaders.SuspendLayout(); - tpObjectS5Header.SuspendLayout(); - tpObjectObjHeader.SuspendLayout(); tpObjectView.SuspendLayout(); tcSubObjectView.SuspendLayout(); tpObjectObject.SuspendLayout(); @@ -102,6 +99,10 @@ private void InitializeComponent() scImageViewer.Panel1.SuspendLayout(); scImageViewer.Panel2.SuspendLayout(); scImageViewer.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)scHeaders).BeginInit(); + scHeaders.Panel1.SuspendLayout(); + scHeaders.Panel2.SuspendLayout(); + scHeaders.SuspendLayout(); tpObjectDump.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)scAnnnotationDump).BeginInit(); scAnnnotationDump.Panel1.SuspendLayout(); @@ -131,7 +132,7 @@ private void InitializeComponent() lbLogs.Location = new Point(0, 0); lbLogs.Name = "lbLogs"; lbLogs.SelectionMode = SelectionMode.None; - lbLogs.Size = new Size(893, 115); + lbLogs.Size = new Size(1415, 186); lbLogs.TabIndex = 17; lbLogs.DrawItem += lbLogs_DrawItem; // @@ -142,7 +143,7 @@ private void InitializeComponent() tbFileFilter.Location = new Point(4, 27); tbFileFilter.Margin = new Padding(4); tbFileFilter.Name = "tbFileFilter"; - tbFileFilter.Size = new Size(294, 23); + tbFileFilter.Size = new Size(469, 23); tbFileFilter.TabIndex = 24; tbFileFilter.TextChanged += tbFileFilter_TextChanged; // @@ -152,7 +153,7 @@ private void InitializeComponent() lblFilenameRegex.Location = new Point(4, 4); lblFilenameRegex.Margin = new Padding(4); lblFilenameRegex.Name = "lblFilenameRegex"; - lblFilenameRegex.Size = new Size(294, 23); + lblFilenameRegex.Size = new Size(469, 23); lblFilenameRegex.TabIndex = 25; lblFilenameRegex.Text = "Filename Filter"; lblFilenameRegex.TextAlign = ContentAlignment.MiddleLeft; @@ -165,7 +166,7 @@ private void InitializeComponent() flpImageTable.FlowDirection = FlowDirection.TopDown; flpImageTable.Location = new Point(0, 0); flpImageTable.Name = "flpImageTable"; - flpImageTable.Size = new Size(867, 421); + flpImageTable.Size = new Size(1381, 584); flpImageTable.TabIndex = 30; // // scObjectAndLogs @@ -177,99 +178,65 @@ private void InitializeComponent() // // scObjectAndLogs.Panel1 // - scObjectAndLogs.Panel1.Controls.Add(tcObjectOverview); + scObjectAndLogs.Panel1.Controls.Add(pnObjectEditor); // // scObjectAndLogs.Panel2 // scObjectAndLogs.Panel2.Controls.Add(lbLogs); - scObjectAndLogs.Size = new Size(893, 634); - scObjectAndLogs.SplitterDistance = 515; + scObjectAndLogs.Size = new Size(1415, 1009); + scObjectAndLogs.SplitterDistance = 819; scObjectAndLogs.TabIndex = 31; // + // pnObjectEditor + // + pnObjectEditor.BackColor = SystemColors.ControlDark; + pnObjectEditor.Controls.Add(toolStrip1); + pnObjectEditor.Controls.Add(tcObjectOverview); + pnObjectEditor.Dock = DockStyle.Fill; + pnObjectEditor.Location = new Point(0, 0); + pnObjectEditor.Name = "pnObjectEditor"; + pnObjectEditor.Padding = new Padding(4); + pnObjectEditor.Size = new Size(1415, 819); + pnObjectEditor.TabIndex = 35; + // + // toolStrip1 + // + toolStrip1.Items.AddRange(new ToolStripItem[] { toolStripButton1 }); + toolStrip1.Location = new Point(4, 4); + toolStrip1.Name = "toolStrip1"; + toolStrip1.Size = new Size(1407, 25); + toolStrip1.TabIndex = 35; + toolStrip1.Text = "toolStrip1"; + // + // toolStripButton1 + // + toolStripButton1.DisplayStyle = ToolStripItemDisplayStyle.Text; + toolStripButton1.Image = (Image)resources.GetObject("toolStripButton1.Image"); + toolStripButton1.ImageTransparentColor = Color.Magenta; + toolStripButton1.Name = "toolStripButton1"; + toolStripButton1.Size = new Size(84, 22); + toolStripButton1.Text = "Save Changes"; + toolStripButton1.Click += toolStripButton1_Click; + // // tcObjectOverview // - tcObjectOverview.Controls.Add(tpObjectFileHeaders); tcObjectOverview.Controls.Add(tpObjectView); tcObjectOverview.Controls.Add(tpObjectDump); tcObjectOverview.Dock = DockStyle.Fill; - tcObjectOverview.Location = new Point(0, 0); + tcObjectOverview.Location = new Point(4, 4); tcObjectOverview.Name = "tcObjectOverview"; tcObjectOverview.SelectedIndex = 0; - tcObjectOverview.Size = new Size(893, 515); + tcObjectOverview.Size = new Size(1407, 811); tcObjectOverview.TabIndex = 34; // - // tpObjectFileHeaders - // - tpObjectFileHeaders.Controls.Add(tcFileHeaders); - tpObjectFileHeaders.Location = new Point(4, 24); - tpObjectFileHeaders.Name = "tpObjectFileHeaders"; - tpObjectFileHeaders.Size = new Size(885, 487); - tpObjectFileHeaders.TabIndex = 2; - tpObjectFileHeaders.Text = "File Headers"; - tpObjectFileHeaders.UseVisualStyleBackColor = true; - // - // tcFileHeaders - // - tcFileHeaders.Controls.Add(tpObjectS5Header); - tcFileHeaders.Controls.Add(tpObjectObjHeader); - tcFileHeaders.Dock = DockStyle.Fill; - tcFileHeaders.Location = new Point(0, 0); - tcFileHeaders.Name = "tcFileHeaders"; - tcFileHeaders.SelectedIndex = 0; - tcFileHeaders.Size = new Size(885, 487); - tcFileHeaders.TabIndex = 0; - // - // tpObjectS5Header - // - tpObjectS5Header.Controls.Add(pgS5Header); - tpObjectS5Header.Location = new Point(4, 24); - tpObjectS5Header.Margin = new Padding(2, 1, 2, 1); - tpObjectS5Header.Name = "tpObjectS5Header"; - tpObjectS5Header.Padding = new Padding(2, 1, 2, 1); - tpObjectS5Header.Size = new Size(877, 459); - tpObjectS5Header.TabIndex = 0; - tpObjectS5Header.Text = "S5 Header"; - tpObjectS5Header.UseVisualStyleBackColor = true; - // - // pgS5Header - // - pgS5Header.Dock = DockStyle.Fill; - pgS5Header.HelpVisible = false; - pgS5Header.Location = new Point(2, 1); - pgS5Header.Name = "pgS5Header"; - pgS5Header.Size = new Size(873, 457); - pgS5Header.TabIndex = 22; - pgS5Header.ToolbarVisible = false; - // - // tpObjectObjHeader - // - tpObjectObjHeader.Controls.Add(pgObjHeader); - tpObjectObjHeader.Location = new Point(4, 24); - tpObjectObjHeader.Margin = new Padding(2, 1, 2, 1); - tpObjectObjHeader.Name = "tpObjectObjHeader"; - tpObjectObjHeader.Padding = new Padding(2, 1, 2, 1); - tpObjectObjHeader.Size = new Size(877, 459); - tpObjectObjHeader.TabIndex = 1; - tpObjectObjHeader.Text = "Object Header"; - tpObjectObjHeader.UseVisualStyleBackColor = true; - // - // pgObjHeader - // - pgObjHeader.Dock = DockStyle.Fill; - pgObjHeader.HelpVisible = false; - pgObjHeader.Location = new Point(2, 1); - pgObjHeader.Name = "pgObjHeader"; - pgObjHeader.Size = new Size(873, 457); - pgObjHeader.TabIndex = 23; - pgObjHeader.ToolbarVisible = false; - // // tpObjectView // tpObjectView.Controls.Add(tcSubObjectView); + tpObjectView.Controls.Add(scHeaders); tpObjectView.Location = new Point(4, 24); tpObjectView.Name = "tpObjectView"; tpObjectView.Padding = new Padding(3); - tpObjectView.Size = new Size(885, 487); + tpObjectView.Size = new Size(1399, 783); tpObjectView.TabIndex = 0; tpObjectView.Text = "Object View"; tpObjectView.UseVisualStyleBackColor = true; @@ -280,11 +247,11 @@ private void InitializeComponent() tcSubObjectView.Controls.Add(tpObjectStringTable); tcSubObjectView.Controls.Add(tpObjectGraphicsTable); tcSubObjectView.Dock = DockStyle.Fill; - tcSubObjectView.Location = new Point(3, 3); + tcSubObjectView.Location = new Point(3, 123); tcSubObjectView.Margin = new Padding(2, 1, 2, 1); tcSubObjectView.Name = "tcSubObjectView"; tcSubObjectView.SelectedIndex = 0; - tcSubObjectView.Size = new Size(879, 481); + tcSubObjectView.Size = new Size(1393, 657); tcSubObjectView.TabIndex = 33; // // tpObjectObject @@ -294,7 +261,7 @@ private void InitializeComponent() tpObjectObject.Margin = new Padding(2, 1, 2, 1); tpObjectObject.Name = "tpObjectObject"; tpObjectObject.Padding = new Padding(2, 1, 2, 1); - tpObjectObject.Size = new Size(871, 453); + tpObjectObject.Size = new Size(1385, 629); tpObjectObject.TabIndex = 2; tpObjectObject.Text = "Object"; tpObjectObject.UseVisualStyleBackColor = true; @@ -305,7 +272,7 @@ private void InitializeComponent() pgObject.HelpVisible = false; pgObject.Location = new Point(2, 1); pgObject.Name = "pgObject"; - pgObject.Size = new Size(867, 451); + pgObject.Size = new Size(1381, 627); pgObject.TabIndex = 24; pgObject.ToolbarVisible = false; // @@ -316,7 +283,7 @@ private void InitializeComponent() tpObjectStringTable.Margin = new Padding(2, 1, 2, 1); tpObjectStringTable.Name = "tpObjectStringTable"; tpObjectStringTable.Padding = new Padding(2, 1, 2, 1); - tpObjectStringTable.Size = new Size(871, 453); + tpObjectStringTable.Size = new Size(1385, 629); tpObjectStringTable.TabIndex = 3; tpObjectStringTable.Text = "Strings"; tpObjectStringTable.UseVisualStyleBackColor = true; @@ -327,7 +294,7 @@ private void InitializeComponent() ucStringTable.Location = new Point(2, 1); ucStringTable.Margin = new Padding(1, 0, 1, 0); ucStringTable.Name = "ucStringTable"; - ucStringTable.Size = new Size(867, 451); + ucStringTable.Size = new Size(1381, 627); ucStringTable.TabIndex = 0; // // tpObjectGraphicsTable @@ -337,7 +304,7 @@ private void InitializeComponent() tpObjectGraphicsTable.Margin = new Padding(2, 1, 2, 1); tpObjectGraphicsTable.Name = "tpObjectGraphicsTable"; tpObjectGraphicsTable.Padding = new Padding(2, 1, 2, 1); - tpObjectGraphicsTable.Size = new Size(871, 453); + tpObjectGraphicsTable.Size = new Size(1385, 629); tpObjectGraphicsTable.TabIndex = 4; tpObjectGraphicsTable.Text = "Graphics"; tpObjectGraphicsTable.UseVisualStyleBackColor = true; @@ -359,8 +326,8 @@ private void InitializeComponent() scImageViewer.Panel2.Controls.Add(tbCurrentPage); scImageViewer.Panel2.Controls.Add(btnPagePrevious); scImageViewer.Panel2.Controls.Add(btnPageNext); - scImageViewer.Size = new Size(867, 451); - scImageViewer.SplitterDistance = 421; + scImageViewer.Size = new Size(1381, 627); + scImageViewer.SplitterDistance = 584; scImageViewer.TabIndex = 32; // // tbCurrentPage @@ -368,9 +335,9 @@ private void InitializeComponent() tbCurrentPage.BorderStyle = BorderStyle.FixedSingle; tbCurrentPage.Dock = DockStyle.Bottom; tbCurrentPage.Enabled = false; - tbCurrentPage.Location = new Point(253, 3); + tbCurrentPage.Location = new Point(253, 16); tbCurrentPage.Name = "tbCurrentPage"; - tbCurrentPage.Size = new Size(386, 23); + tbCurrentPage.Size = new Size(900, 23); tbCurrentPage.TabIndex = 33; tbCurrentPage.TextAlign = HorizontalAlignment.Center; // @@ -379,7 +346,7 @@ private void InitializeComponent() btnPagePrevious.Dock = DockStyle.Left; btnPagePrevious.Location = new Point(0, 0); btnPagePrevious.Name = "btnPagePrevious"; - btnPagePrevious.Size = new Size(253, 26); + btnPagePrevious.Size = new Size(253, 39); btnPagePrevious.TabIndex = 0; btnPagePrevious.Text = "Previous Page"; btnPagePrevious.UseVisualStyleBackColor = true; @@ -388,21 +355,58 @@ private void InitializeComponent() // btnPageNext // btnPageNext.Dock = DockStyle.Right; - btnPageNext.Location = new Point(639, 0); + btnPageNext.Location = new Point(1153, 0); btnPageNext.Name = "btnPageNext"; - btnPageNext.Size = new Size(228, 26); + btnPageNext.Size = new Size(228, 39); btnPageNext.TabIndex = 31; btnPageNext.Text = "Next Page"; btnPageNext.UseVisualStyleBackColor = true; btnPageNext.Click += btnPageNext_Click; // + // scHeaders + // + scHeaders.Dock = DockStyle.Top; + scHeaders.Location = new Point(3, 3); + scHeaders.Name = "scHeaders"; + // + // scHeaders.Panel1 + // + scHeaders.Panel1.Controls.Add(pgS5Header); + // + // scHeaders.Panel2 + // + scHeaders.Panel2.Controls.Add(pgObjHeader); + scHeaders.Size = new Size(1393, 120); + scHeaders.SplitterDistance = 695; + scHeaders.TabIndex = 34; + // + // pgS5Header + // + pgS5Header.Dock = DockStyle.Fill; + pgS5Header.HelpVisible = false; + pgS5Header.Location = new Point(0, 0); + pgS5Header.Name = "pgS5Header"; + pgS5Header.Size = new Size(695, 120); + pgS5Header.TabIndex = 22; + pgS5Header.ToolbarVisible = false; + // + // pgObjHeader + // + pgObjHeader.Dock = DockStyle.Fill; + pgObjHeader.HelpVisible = false; + pgObjHeader.Location = new Point(0, 0); + pgObjHeader.Name = "pgObjHeader"; + pgObjHeader.Size = new Size(694, 120); + pgObjHeader.TabIndex = 23; + pgObjHeader.ToolbarVisible = false; + // // tpObjectDump // tpObjectDump.Controls.Add(scAnnnotationDump); tpObjectDump.Location = new Point(4, 24); tpObjectDump.Name = "tpObjectDump"; tpObjectDump.Padding = new Padding(3); - tpObjectDump.Size = new Size(885, 487); + tpObjectDump.Size = new Size(1399, 783); tpObjectDump.TabIndex = 1; tpObjectDump.Text = "Object Dump"; tpObjectDump.UseVisualStyleBackColor = true; @@ -421,8 +425,8 @@ private void InitializeComponent() // scAnnnotationDump.Panel2 // scAnnnotationDump.Panel2.Controls.Add(rtbDATDumpView); - scAnnnotationDump.Size = new Size(879, 481); - scAnnnotationDump.SplitterDistance = 303; + scAnnnotationDump.Size = new Size(1393, 777); + scAnnnotationDump.SplitterDistance = 479; scAnnnotationDump.TabIndex = 2; // // tvDATDumpAnnotations @@ -430,7 +434,7 @@ private void InitializeComponent() tvDATDumpAnnotations.Dock = DockStyle.Fill; tvDATDumpAnnotations.Location = new Point(0, 0); tvDATDumpAnnotations.Name = "tvDATDumpAnnotations"; - tvDATDumpAnnotations.Size = new Size(303, 481); + tvDATDumpAnnotations.Size = new Size(479, 777); tvDATDumpAnnotations.TabIndex = 1; tvDATDumpAnnotations.AfterSelect += dataDumpAnnotations_AfterSelect; // @@ -443,7 +447,7 @@ private void InitializeComponent() rtbDATDumpView.Name = "rtbDATDumpView"; rtbDATDumpView.ReadOnly = true; rtbDATDumpView.ShowSelectionMargin = true; - rtbDATDumpView.Size = new Size(572, 481); + rtbDATDumpView.Size = new Size(910, 777); rtbDATDumpView.TabIndex = 0; rtbDATDumpView.Text = ""; rtbDATDumpView.WordWrap = false; @@ -462,8 +466,8 @@ private void InitializeComponent() // scTop.Panel2 // scTop.Panel2.Controls.Add(scObjectAndLogs); - scTop.Size = new Size(1199, 634); - scTop.SplitterDistance = 302; + scTop.Size = new Size(1896, 1009); + scTop.SplitterDistance = 477; scTop.TabIndex = 33; // // tcFileSelector @@ -474,7 +478,7 @@ private void InitializeComponent() tcFileSelector.Location = new Point(0, 74); tcFileSelector.Name = "tcFileSelector"; tcFileSelector.SelectedIndex = 0; - tcFileSelector.Size = new Size(302, 560); + tcFileSelector.Size = new Size(477, 935); tcFileSelector.TabIndex = 29; // // tabPage2 @@ -483,7 +487,7 @@ private void InitializeComponent() tabPage2.Location = new Point(4, 24); tabPage2.Name = "tabPage2"; tabPage2.Padding = new Padding(3); - tabPage2.Size = new Size(294, 532); + tabPage2.Size = new Size(469, 907); tabPage2.TabIndex = 1; tabPage2.Text = "Category"; tabPage2.UseVisualStyleBackColor = true; @@ -493,7 +497,7 @@ private void InitializeComponent() tvObjType.Dock = DockStyle.Fill; tvObjType.Location = new Point(3, 3); tvObjType.Name = "tvObjType"; - tvObjType.Size = new Size(288, 526); + tvObjType.Size = new Size(463, 901); tvObjType.TabIndex = 26; tvObjType.AfterSelect += tv_AfterSelect; // @@ -503,7 +507,7 @@ private void InitializeComponent() tabPage1.Location = new Point(4, 24); tabPage1.Name = "tabPage1"; tabPage1.Padding = new Padding(3); - tabPage1.Size = new Size(294, 532); + tabPage1.Size = new Size(469, 907); tabPage1.TabIndex = 0; tabPage1.Text = "File"; tabPage1.UseVisualStyleBackColor = true; @@ -513,7 +517,7 @@ private void InitializeComponent() tvFileTree.Dock = DockStyle.Fill; tvFileTree.Location = new Point(3, 3); tvFileTree.Name = "tvFileTree"; - tvFileTree.Size = new Size(288, 526); + tvFileTree.Size = new Size(463, 901); tvFileTree.TabIndex = 1; tvFileTree.AfterSelect += tv_AfterSelect; // @@ -527,7 +531,7 @@ private void InitializeComponent() pnFileFilter.Margin = new Padding(4); pnFileFilter.Name = "pnFileFilter"; pnFileFilter.Padding = new Padding(4); - pnFileFilter.Size = new Size(302, 74); + pnFileFilter.Size = new Size(477, 74); pnFileFilter.TabIndex = 30; // // cbVanillaObjects @@ -536,7 +540,7 @@ private void InitializeComponent() cbVanillaObjects.Dock = DockStyle.Top; cbVanillaObjects.Location = new Point(4, 50); cbVanillaObjects.Name = "cbVanillaObjects"; - cbVanillaObjects.Size = new Size(294, 19); + cbVanillaObjects.Size = new Size(469, 19); cbVanillaObjects.TabIndex = 26; cbVanillaObjects.Text = "Vanilla Objects Only"; cbVanillaObjects.UseVisualStyleBackColor = true; @@ -548,13 +552,13 @@ private void InitializeComponent() menuStrip.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem }); menuStrip.Location = new Point(4, 4); menuStrip.Name = "menuStrip"; - menuStrip.Size = new Size(1199, 24); + menuStrip.Size = new Size(1896, 24); menuStrip.TabIndex = 23; menuStrip.Text = "menuStrip1"; // // fileToolStripMenuItem // - fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, setPaletteToolStripMenuItem, recreateIndexToolStripMenuItem, saveChangesToolStripMenuItem }); + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, setPaletteToolStripMenuItem, recreateIndexToolStripMenuItem }); fileToolStripMenuItem.Name = "fileToolStripMenuItem"; fileToolStripMenuItem.Size = new Size(37, 20); fileToolStripMenuItem.Text = "File"; @@ -563,26 +567,26 @@ private void InitializeComponent() // objectDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setObjectDirectoryToolStripMenuItem, toolStripSeparator1 }); objectDirectoriesToolStripMenuItem.Name = "objectDirectoriesToolStripMenuItem"; - objectDirectoriesToolStripMenuItem.Size = new Size(217, 22); + objectDirectoriesToolStripMenuItem.Size = new Size(180, 22); objectDirectoriesToolStripMenuItem.Text = "ObjData Directories"; // // setObjectDirectoryToolStripMenuItem // setObjectDirectoryToolStripMenuItem.Name = "setObjectDirectoryToolStripMenuItem"; - setObjectDirectoryToolStripMenuItem.Size = new Size(123, 22); + setObjectDirectoryToolStripMenuItem.Size = new Size(180, 22); setObjectDirectoryToolStripMenuItem.Text = "Add New"; setObjectDirectoryToolStripMenuItem.Click += setObjectDirectoryToolStripMenuItem_Click; // // toolStripSeparator1 // toolStripSeparator1.Name = "toolStripSeparator1"; - toolStripSeparator1.Size = new Size(120, 6); + toolStripSeparator1.Size = new Size(177, 6); // // dataDirectoriesToolStripMenuItem // dataDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setDataDirectoryToolStripMenuItem, toolStripSeparator2 }); dataDirectoriesToolStripMenuItem.Name = "dataDirectoriesToolStripMenuItem"; - dataDirectoriesToolStripMenuItem.Size = new Size(217, 22); + dataDirectoriesToolStripMenuItem.Size = new Size(180, 22); dataDirectoriesToolStripMenuItem.Text = "Data Directories"; // // setDataDirectoryToolStripMenuItem @@ -600,24 +604,17 @@ private void InitializeComponent() // setPaletteToolStripMenuItem // setPaletteToolStripMenuItem.Name = "setPaletteToolStripMenuItem"; - setPaletteToolStripMenuItem.Size = new Size(217, 22); + setPaletteToolStripMenuItem.Size = new Size(180, 22); setPaletteToolStripMenuItem.Text = "Load Palette Bitmap"; setPaletteToolStripMenuItem.Click += setPaletteToolStripMenuItem_Click; // // recreateIndexToolStripMenuItem // recreateIndexToolStripMenuItem.Name = "recreateIndexToolStripMenuItem"; - recreateIndexToolStripMenuItem.Size = new Size(217, 22); + recreateIndexToolStripMenuItem.Size = new Size(180, 22); recreateIndexToolStripMenuItem.Text = "Recreate Index"; recreateIndexToolStripMenuItem.Click += recreateIndexToolStripMenuItem_Click; // - // saveChangesToolStripMenuItem - // - saveChangesToolStripMenuItem.Name = "saveChangesToolStripMenuItem"; - saveChangesToolStripMenuItem.Size = new Size(217, 22); - saveChangesToolStripMenuItem.Text = "Save Object Changes (WIP)"; - saveChangesToolStripMenuItem.Click += saveChangesToolStripMenuItem_Click; - // // imgContextMenu // imgContextMenu.ImageScalingSize = new Size(32, 32); @@ -661,7 +658,7 @@ private void InitializeComponent() // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1207, 666); + ClientSize = new Size(1904, 1041); Controls.Add(scTop); Controls.Add(menuStrip); Icon = (Icon)resources.GetObject("$this.Icon"); @@ -674,11 +671,11 @@ private void InitializeComponent() scObjectAndLogs.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)scObjectAndLogs).EndInit(); scObjectAndLogs.ResumeLayout(false); + pnObjectEditor.ResumeLayout(false); + pnObjectEditor.PerformLayout(); + toolStrip1.ResumeLayout(false); + toolStrip1.PerformLayout(); tcObjectOverview.ResumeLayout(false); - tpObjectFileHeaders.ResumeLayout(false); - tcFileHeaders.ResumeLayout(false); - tpObjectS5Header.ResumeLayout(false); - tpObjectObjHeader.ResumeLayout(false); tpObjectView.ResumeLayout(false); tcSubObjectView.ResumeLayout(false); tpObjectObject.ResumeLayout(false); @@ -689,6 +686,10 @@ private void InitializeComponent() scImageViewer.Panel2.PerformLayout(); ((System.ComponentModel.ISupportInitialize)scImageViewer).EndInit(); scImageViewer.ResumeLayout(false); + scHeaders.Panel1.ResumeLayout(false); + scHeaders.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)scHeaders).EndInit(); + scHeaders.ResumeLayout(false); tpObjectDump.ResumeLayout(false); scAnnnotationDump.Panel1.ResumeLayout(false); scAnnnotationDump.Panel2.ResumeLayout(false); @@ -725,7 +726,6 @@ private void InitializeComponent() private ToolStripMenuItem fileToolStripMenuItem; private ToolStripMenuItem objectDirectoriesToolStripMenuItem; private ToolStripMenuItem recreateIndexToolStripMenuItem; - private ToolStripMenuItem saveChangesToolStripMenuItem; private ToolStripMenuItem setPaletteToolStripMenuItem; private ToolStripMenuItem dataDirectoriesToolStripMenuItem; private ContextMenuStrip imgContextMenu; @@ -754,8 +754,6 @@ private void InitializeComponent() private ToolStripMenuItem setDataDirectoryToolStripMenuItem; private ToolStripSeparator toolStripSeparator2; private TabControl tcSubObjectView; - private TabPage tpObjectS5Header; - private TabPage tpObjectObjHeader; private TabPage tpObjectObject; private TabPage tpObjectStringTable; private TabPage tpObjectGraphicsTable; @@ -764,7 +762,9 @@ private void InitializeComponent() private PropertyGrid pgObjHeader; private PropertyGrid pgObject; private StringTableUserControl ucStringTable; - private TabPage tpObjectFileHeaders; - private TabControl tcFileHeaders; + private SplitContainer scHeaders; + private Panel pnObjectEditor; + private ToolStrip toolStrip1; + private ToolStripButton toolStripButton1; } } \ No newline at end of file diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 09f7a25b..7381bee5 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -333,46 +333,7 @@ void InitToolStripMenuItems() private void saveChangesToolStripMenuItem_Click(object sender, EventArgs e) { - if (CurrentUIObject is not UiLocoObject obj) - { - return; - } - if (!SaveEnabledObjects.Types.Contains(obj.DatFileInfo.S5Header.ObjectType)) - { - var msg = $"Saving is not currently implemented for {obj.DatFileInfo.S5Header.ObjectType} objects"; - logger.Error(msg); - logger.Info($"Saving is currently supported for the following objects: [{SaveEnabledObjects.Types.Aggregate("", (a, b) => a + ", " + b)}]"); - MessageBox.Show(msg); - return; - } - - saveFileDialog1.InitialDirectory = model.Settings.ObjDataDirectory; - saveFileDialog1.DefaultExt = "dat"; - saveFileDialog1.Filter = "Locomotion DAT files (.dat)|*.dat"; - if (saveFileDialog1.ShowDialog() == DialogResult.OK) - { - var filename = saveFileDialog1.FileName; - - try - { - var exists = File.Exists(filename); - MainFormModel.SaveFile(filename, obj); - - if (!exists) - { - // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it - InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); - } - - logger.Info($"File \"{filename}\" saved successfully"); - } - catch (Exception ex) - { - logger.Error($"Error saving \"{filename}\": {ex.Message}"); - MessageBox.Show($"Error saving \"{filename}\": {ex.Message}"); - } - } } private void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs e) @@ -865,5 +826,49 @@ private void lbLogs_DrawItem(object sender, DrawItemEventArgs e) //e.DrawFocusRectangle(); } + + private void toolStripButton1_Click(object sender, EventArgs e) + { + if (CurrentUIObject is not UiLocoObject obj) + { + return; + } + + if (!SaveEnabledObjects.Types.Contains(obj.DatFileInfo.S5Header.ObjectType)) + { + var msg = $"Saving is not currently implemented for {obj.DatFileInfo.S5Header.ObjectType} objects"; + logger.Error(msg); + logger.Info($"Saving is currently supported for the following objects: [{SaveEnabledObjects.Types.Aggregate("", (a, b) => a + ", " + b)}]"); + MessageBox.Show(msg); + return; + } + + saveFileDialog1.InitialDirectory = model.Settings.ObjDataDirectory; + saveFileDialog1.DefaultExt = "dat"; + saveFileDialog1.Filter = "Locomotion DAT files (.dat)|*.dat"; + if (saveFileDialog1.ShowDialog() == DialogResult.OK) + { + var filename = saveFileDialog1.FileName; + + try + { + var exists = File.Exists(filename); + MainFormModel.SaveFile(filename, obj); + + if (!exists) + { + // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it + InitUI(cbVanillaObjects.Checked, tbFileFilter.Text); + } + + logger.Info($"File \"{filename}\" saved successfully"); + } + catch (Exception ex) + { + logger.Error($"Error saving \"{filename}\": {ex.Message}"); + MessageBox.Show($"Error saving \"{filename}\": {ex.Message}"); + } + } + } } } diff --git a/OpenLocoToolGui/MainForm.resx b/OpenLocoToolGui/MainForm.resx index 67473720..b6655435 100644 --- a/OpenLocoToolGui/MainForm.resx +++ b/OpenLocoToolGui/MainForm.resx @@ -123,6 +123,19 @@ 162, 17 + + 859, 11 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACRSURBVDhPY/j27dt/SjDYACcnJ7IwigEf3n8kCZNswPNb + J/+f6DYF0yA+yQac6Db5f6hWCmwIiE+mC0wIu2DS2Vf/F1x6DefjwlgNyNr34r/0wkdgTMgQDAOQNRNj + CIoBOg0rMTTDMLIhIHbriZeYBmDTiIxBGkEYxge5liQDsGGQqykyAISpZwAlmIEywMAAAAc1/Jwvt6sN + AAAAAElFTkSuQmCC + + 298, 17 @@ -135,7 +148,6 @@ 48 - AAABAAEAAAAAAAEAIAC/4gEAFgAAAIlQTkcNChoKAAAADUlIRFIAAAIAAAACAAgGAAAA9HjU+gAAAAFz diff --git a/OpenLocoToolGui/StringTableUserControl.Designer.cs b/OpenLocoToolGui/StringTableUserControl.Designer.cs index 46bed5c7..1a0a5e46 100644 --- a/OpenLocoToolGui/StringTableUserControl.Designer.cs +++ b/OpenLocoToolGui/StringTableUserControl.Designer.cs @@ -38,32 +38,35 @@ private void InitializeComponent() dgvLanguageSelector.AllowUserToResizeRows = false; dgvLanguageSelector.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; dgvLanguageSelector.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dgvLanguageSelector.Location = new Point(397, 3); + dgvLanguageSelector.Location = new Point(214, 1); + dgvLanguageSelector.Margin = new Padding(2, 1, 2, 1); dgvLanguageSelector.MultiSelect = false; dgvLanguageSelector.Name = "dgvLanguageSelector"; dgvLanguageSelector.RowHeadersWidth = 82; dgvLanguageSelector.RowTemplate.Height = 41; - dgvLanguageSelector.Size = new Size(1028, 622); + dgvLanguageSelector.Size = new Size(554, 292); dgvLanguageSelector.TabIndex = 1; // // lbStringSelector // lbStringSelector.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left; lbStringSelector.FormattingEnabled = true; - lbStringSelector.ItemHeight = 32; - lbStringSelector.Location = new Point(3, 3); + lbStringSelector.ItemHeight = 15; + lbStringSelector.Location = new Point(2, 1); + lbStringSelector.Margin = new Padding(2, 1, 2, 1); lbStringSelector.Name = "lbStringSelector"; - lbStringSelector.Size = new Size(388, 612); + lbStringSelector.Size = new Size(211, 289); lbStringSelector.TabIndex = 2; // // StringTableUserControl // - AutoScaleDimensions = new SizeF(13F, 32F); + AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; Controls.Add(lbStringSelector); Controls.Add(dgvLanguageSelector); + Margin = new Padding(2, 1, 2, 1); Name = "StringTableUserControl"; - Size = new Size(1428, 628); + Size = new Size(769, 294); ((System.ComponentModel.ISupportInitialize)dgvLanguageSelector).EndInit(); ResumeLayout(false); } diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/OpenLocoToolGui/StringTableUserControl.cs index 05d5c24f..51cc182c 100644 --- a/OpenLocoToolGui/StringTableUserControl.cs +++ b/OpenLocoToolGui/StringTableUserControl.cs @@ -9,10 +9,27 @@ public partial class StringTableUserControl : UserControl public StringTableUserControl() => InitializeComponent(); + StringTable _data; + StringTable Data + { + get => _data; + set + { + _data = value; + BindingSource.DataSource = _data; + } + } + BindingSource BindingSource = new(); + public void SetDataBinding(StringTable data) { + Data = data; + //dgvLanguageSelector.DataSource = BindingSource; + + //DataContext = Data; + blKeys.Clear(); - foreach (var key in data.Keys) + foreach (var key in Data.Keys) { blKeys.Add(key); } @@ -21,13 +38,19 @@ public void SetDataBinding(StringTable data) lbStringSelector.DataSource = blKeys; // Subscribe to the SelectionChanged event to populate the inner DataGridView. - lbStringSelector.SelectedValueChanged += (sender, e) => + lbStringSelector.SelectedValueChanged += (sender, e) => UpdateDGVSource(); + + // update the DGV now as well + UpdateDGVSource(); + + } + + void UpdateDGVSource() + { + if (lbStringSelector.SelectedValue != null && Data.table.ContainsKey((string)lbStringSelector.SelectedValue)) { - if (lbStringSelector.SelectedValue != null) - { - dgvLanguageSelector.DataSource = new BindingSource(data[(string)lbStringSelector.SelectedValue], null); - } - }; + dgvLanguageSelector.DataSource = new BindingSource(Data[(string)lbStringSelector.SelectedValue], null); + } } } } From 4617dcc4728058dcca3e2bb79e6daa227c263fda Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 8 Jan 2024 10:24:36 +1100 Subject: [PATCH 02/13] cleanup sawyer stream stuff --- .../DatFileParsing/SawyerStreamReader.cs | 154 +++++++----------- .../DatFileParsing/SawyerStreamUtils.cs | 27 +++ OpenLocoTool/ObjectManager.cs | 24 +-- OpenLocoToolGui/MainFormModel.cs | 2 +- OpenLocoToolTests/ObjectLoadingTests.cs | 52 +++--- 5 files changed, 116 insertions(+), 143 deletions(-) create mode 100644 OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 67f55f5c..0944fa6e 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Numerics; using System.Text; using OpenLocoTool.Headers; using OpenLocoTool.Objects; @@ -7,29 +6,6 @@ namespace OpenLocoTool.DatFileParsing { - public static class SawyerStreamUtils - { - public static uint ComputeObjectChecksum(ReadOnlySpan headerFlagByte, ReadOnlySpan name, ReadOnlySpan data) - { - static uint32_t ComputeChecksum(ReadOnlySpan data, uint32_t seed) - { - var checksum = seed; - foreach (var d in data) - { - checksum = BitOperations.RotateLeft(checksum ^ d, 11); - } - - return checksum; - } - - const uint32_t objectChecksumMagic = 0xF369A75B; - var checksum = ComputeChecksum(headerFlagByte, objectChecksumMagic); - checksum = ComputeChecksum(name, checksum); - checksum = ComputeChecksum(data, checksum); - return checksum; - } - } - public static class SawyerStreamReader { public static List LoadVariableHeaders(ReadOnlySpan data, int count) @@ -49,20 +25,40 @@ public static List LoadVariableHeaders(ReadOnlySpan data, int co return result; } - public static G1Dat LoadG1(string filename, ILogger? logger = null) + public static S5Header LoadS5Header(string filename, ILogger? logger = null) { - ReadOnlySpan fullData = LoadBytesFromFile(filename); - var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(fullData); - logger?.Info($"FileLength={new FileInfo(filename).Length} NumEntries={g1Header.NumEntries} TotalSize={g1Header.TotalSize} ImageTableLength={imageTableBytesRead}"); - return new G1Dat(g1Header, imageTable); + if (!File.Exists(filename)) + { + logger?.Error($"Path doesn't exist: {filename}"); + } + + logger?.Info($"Loading header for {filename}"); + + using var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, 32, FileOptions.SequentialScan | FileOptions.Asynchronous); + using var reader = new BinaryReader(fileStream); + var data = reader.ReadBytes(S5Header.StructLength); + return data.Length != S5Header.StructLength + ? throw new InvalidOperationException($"bytes read ({data.Length}) didn't match bytes expected ({S5Header.StructLength})") + : S5Header.Read(data); + } + + public static byte[] LoadBytesFromFile(string filename, ILogger? logger = null) + { + if (!File.Exists(filename)) + { + var ex = new InvalidOperationException($"File doesn't exist: {filename}"); + logger?.Error(ex); + } + + logger?.Info($"Loading {filename}"); + return File.ReadAllBytes(filename); } // load file - public static byte[] LoadDecode(string filename) + public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadDecode(string filename, ILogger? logger = null) { ReadOnlySpan fullData = LoadBytesFromFile(filename); - // make openlocotool useful objects var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]); var remainingData = fullData[S5Header.StructLength..]; @@ -70,9 +66,17 @@ public static byte[] LoadDecode(string filename) remainingData = remainingData[ObjectHeader.StructLength..]; var decodedData = Decode(objectHeader.Encoding, remainingData); - remainingData = decodedData; + //remainingData = decodedData; + + var headerFlag = BitConverter.GetBytes(s5Header.Flags).AsSpan()[0..1]; + var checksum = SawyerStreamUtils.ComputeObjectChecksum(headerFlag, fullData[4..12], decodedData); + + if (checksum != s5Header.Checksum) + { + logger?.Error($"{s5Header.Name} had incorrect checksum. expected={s5Header.Checksum} actual={checksum}"); + } - return [.. fullData[0..(S5Header.StructLength + ObjectHeader.StructLength)], .. decodedData]; + return (s5Header, objectHeader, decodedData); } // load file @@ -80,17 +84,8 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFull(string { logger?.Info($"Full-loading \"{filename}\" with loadExtra={loadExtra}"); - ReadOnlySpan fullData = LoadBytesFromFile(filename); - - // make openlocotool useful objects - var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]); - var remainingData = fullData[S5Header.StructLength..]; - - var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]); - remainingData = remainingData[ObjectHeader.StructLength..]; - - var decodedData = Decode(objectHeader.Encoding, remainingData); - remainingData = decodedData; + var (s5Header, objectHeader, decodedData) = LoadDecode(filename, logger); + ReadOnlySpan remainingData = decodedData; var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData); @@ -104,14 +99,6 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFull(string var locoStructSize = structSize!.Size; remainingData = remainingData[locoStructSize..]; - var headerFlag = BitConverter.GetBytes(s5Header.Flags).AsSpan()[0..1]; - var checksum = SawyerStreamUtils.ComputeObjectChecksum(headerFlag, fullData[4..12], decodedData); - - if (checksum != s5Header.Checksum) - { - logger?.Error($"{s5Header.Name} had incorrect checksum. expected={s5Header.Checksum} actual={checksum}"); - } - // every object has a string table var (stringTable, stringTableBytesRead) = LoadStringTable(remainingData, locoStruct); remainingData = remainingData[stringTableBytesRead..]; @@ -123,19 +110,19 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFull(string } LocoObject? newObj; - //try - //{ - // some objects have graphics data - var (_, imageTable, imageTableBytesRead) = LoadImageTable(remainingData); - logger?.Info($"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); - - newObj = new LocoObject(locoStruct, stringTable, imageTable); - //} - //catch (Exception ex) - //{ - // newObj = new LocoObject(locoStruct, stringTable); - // logger?.Error(ex, "Error loading graphics table"); - //} + try + { + // some objects have graphics data + var (_, imageTable, imageTableBytesRead) = LoadImageTable(remainingData); + logger?.Info($"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}"); + + newObj = new LocoObject(locoStruct, stringTable, imageTable); + } + catch (Exception ex) + { + newObj = new LocoObject(locoStruct, stringTable); + logger?.Error(ex, "Error loading graphics table"); + } // add to object manager SObjectManager.Add(newObj); @@ -190,6 +177,14 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan fullData = LoadBytesFromFile(filename); + var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(fullData); + logger?.Info($"FileLength={new FileInfo(filename).Length} NumEntries={g1Header.NumEntries} TotalSize={g1Header.TotalSize} ImageTableLength={imageTableBytesRead}"); + return new G1Dat(g1Header, imageTable); + } + public static (G1Header header, List table, int bytesRead) LoadImageTable(ReadOnlySpan data) { var g1Element32s = new List(); @@ -315,35 +310,6 @@ public static byte[] DecodeRLEImageData(G1Element32 img) return dstBuf; } - public static byte[] LoadBytesFromFile(string filename) - { - if (!File.Exists(filename)) - { - //Logger.Log(LogLevel.Error, $"Path doesn't exist: {filename}"); - throw new InvalidOperationException($"File doesn't exist: {filename}"); - } - - //Logger.Log(LogLevel.Info, $"Loading {filename}"); - return File.ReadAllBytes(filename); - } - - public static S5Header LoadHeader(string filename, ILogger? logger = null) - { - if (!File.Exists(filename)) - { - logger?.Error($"Path doesn't exist: {filename}"); - } - - logger?.Info($"Loading header for {filename}"); - - using var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, 32, FileOptions.SequentialScan | FileOptions.Asynchronous); - using var reader = new BinaryReader(fileStream); - var data = reader.ReadBytes(S5Header.StructLength); - return data.Length != S5Header.StructLength - ? throw new InvalidOperationException($"bytes read ({data.Length}) didn't match bytes expected ({S5Header.StructLength})") - : S5Header.Read(data); - } - public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan data) => objectType switch { diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs b/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs new file mode 100644 index 00000000..fa24849f --- /dev/null +++ b/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs @@ -0,0 +1,27 @@ +using System.Numerics; + +namespace OpenLocoTool.DatFileParsing +{ + public static class SawyerStreamUtils + { + public static uint ComputeObjectChecksum(ReadOnlySpan headerFlagByte, ReadOnlySpan name, ReadOnlySpan data) + { + static uint32_t ComputeChecksum(ReadOnlySpan data, uint32_t seed) + { + var checksum = seed; + foreach (var d in data) + { + checksum = BitOperations.RotateLeft(checksum ^ d, 11); + } + + return checksum; + } + + const uint32_t objectChecksumMagic = 0xF369A75B; + var checksum = ComputeChecksum(headerFlagByte, objectChecksumMagic); + checksum = ComputeChecksum(name, checksum); + checksum = ComputeChecksum(data, checksum); + return checksum; + } + } +} diff --git a/OpenLocoTool/ObjectManager.cs b/OpenLocoTool/ObjectManager.cs index 67bd9662..ba90699a 100644 --- a/OpenLocoTool/ObjectManager.cs +++ b/OpenLocoTool/ObjectManager.cs @@ -15,30 +15,10 @@ static SObjectManager() } } - public static List Get(ObjectType type) - where T : ILocoStruct => Objects[type].Select(a => a.Object).Cast().ToList(); + public static IEnumerable Get(ObjectType type) + where T : ILocoStruct => Objects[type].Select(a => a.Object).Cast(); public static void Add(T obj) where T : ILocoObject => Objects[ObjectAttributes.ObjectType(obj.Object)].Add(obj); } - - // unused for now - //public class ObjectManager - //{ - // readonly Dictionary> Objects = new(); - - // public ObjectManager() - // { - // foreach (var v in Enum.GetValues(typeof(ObjectType))) - // { - // Objects.Add((ObjectType)v, new List()); - // } - // } - - // public List Get(ObjectType type) - // where T : ILocoStruct => Objects[type].Select(a => a.Object).Cast().ToList(); - - // public void Add(T obj) where T : ILocoObject - // => Objects[obj.S5Header.ObjectType].Add(obj); - //} } diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 723e6fb4..7277372d 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -193,7 +193,7 @@ void CreateIndex(string[] allFiles, IProgress progress) { logger.Error($"Failed to load \"{file}\"", ex); - var obj = SawyerStreamReader.LoadHeader(file); + var obj = SawyerStreamReader.LoadS5Header(file); var indexObjectHeader = new IndexObjectHeader(obj.Name, obj.ObjectType, null); ccHeaderIndex.TryAdd(file, indexObjectHeader); } diff --git a/OpenLocoToolTests/ObjectLoadingTests.cs b/OpenLocoToolTests/ObjectLoadingTests.cs index e428402f..0602f117 100644 --- a/OpenLocoToolTests/ObjectLoadingTests.cs +++ b/OpenLocoToolTests/ObjectLoadingTests.cs @@ -457,39 +457,39 @@ public void SaveStreetLightObject() }); } - [Test] - public void SaveStreetLightObject2() - { - // load - const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT"; + //[Test] + //public void SaveStreetLightObject2() + //{ + // // load + // const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT"; - var (obj, struc) = LoadObject(testFile); + // var (obj, struc) = LoadObject(testFile); - // save - var bytesSource = SawyerStreamReader.LoadDecode(testFile); - var bytesDest = SawyerStreamWriter.WriteLocoObject("ObjName_", obj).ToArray(); + // // save + // var bytesSource = SawyerStreamReader.LoadDecode(testFile); + // var bytesDest = SawyerStreamWriter.WriteLocoObject("ObjName_", obj).ToArray(); - var saveA = "Q:\\Games\\Locomotion\\ExperimentalObjects\\original.dat"; - var saveB = "Q:\\Games\\Locomotion\\ExperimentalObjects\\saved.dat"; + // var saveA = "Q:\\Games\\Locomotion\\ExperimentalObjects\\original.dat"; + // var saveB = "Q:\\Games\\Locomotion\\ExperimentalObjects\\saved.dat"; - File.WriteAllBytes(saveA, bytesSource); - File.WriteAllBytes(saveB, bytesDest); + // File.WriteAllBytes(saveA, bytesSource); + // File.WriteAllBytes(saveB, bytesDest); - var headerA = SawyerStreamReader.LoadHeader(saveA); - var headerB = SawyerStreamReader.LoadHeader(saveB); + // var headerA = SawyerStreamReader.LoadHeader(saveA); + // var headerB = SawyerStreamReader.LoadHeader(saveB); - //Assert.Multiple(() => - //{ - // for (int i = 0; i < Math.Min(bytesSource.Length, bytesDest.Length); ++i) - // { - // Assert.AreEqual(bytesSource[i], bytesDest[i], $"[{i}] {bytesSource[i]} {bytesDest[i]}"); - // } - //}); + // //Assert.Multiple(() => + // //{ + // // for (int i = 0; i < Math.Min(bytesSource.Length, bytesDest.Length); ++i) + // // { + // // Assert.AreEqual(bytesSource[i], bytesDest[i], $"[{i}] {bytesSource[i]} {bytesDest[i]}"); + // // } + // //}); - CollectionAssert.AreEqual(bytesSource[0..16], bytesDest[0..16]); - // skip object header - CollectionAssert.AreEqual(bytesSource[21..], bytesDest[21..]); - } + // CollectionAssert.AreEqual(bytesSource[0..16], bytesDest[0..16]); + // // skip object header + // CollectionAssert.AreEqual(bytesSource[21..], bytesDest[21..]); + //} [Test] public void LoadTownNamesObject() => Assert.Fail(); From bb4eafe801c6f819066a1bebcc924a8a29fd6019 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 8 Jan 2024 12:13:35 +1100 Subject: [PATCH 03/13] small optimisations --- .../DatFileParsing/ObjectAnnotator.cs | 2 +- .../DatFileParsing/SawyerStreamReader.cs | 35 +++++++++---------- OpenLocoTool/Objects/Vehicle/VehicleObject.cs | 8 ++++- OpenLocoToolGui/MainFormModel.cs | 29 ++++++++++++--- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs b/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs index 3f4d10bb..9aff12cb 100644 --- a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs +++ b/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs @@ -34,7 +34,7 @@ public static IList Annotate(byte[] bytelist, out byte[] fullData) fullData = [ .. bytelist[..runningCount], - .. SawyerStreamReader.Decode(objectHeader.Encoding, bytelist.AsSpan()[runningCount..(int)(runningCount + objectHeader.DataLength)]), + .. SawyerStreamReader.Decode(objectHeader.Encoding, bytelist[runningCount..(int)(runningCount + objectHeader.DataLength)]), ]; var locoStruct = SawyerStreamReader.GetLocoStruct(s5Header.ObjectType, fullData.AsSpan()[runningCount..]); diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 0944fa6e..2dea77aa 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -65,7 +65,7 @@ public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) Lo var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]); remainingData = remainingData[ObjectHeader.StructLength..]; - var decodedData = Decode(objectHeader.Encoding, remainingData); + var decodedData = Decode(objectHeader.Encoding, remainingData.ToArray()); //remainingData = decodedData; var headerFlag = BitConverter.GetBytes(s5Header.Flags).AsSpan()[0..1]; @@ -351,9 +351,9 @@ public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan data) => encoding switch + public static byte[] Decode(SawyerEncoding encoding, byte[] data) => encoding switch { - SawyerEncoding.Uncompressed => data.ToArray(), + SawyerEncoding.Uncompressed => data, SawyerEncoding.RunLengthSingle => DecodeRunLengthSingle(data), SawyerEncoding.RunLengthMulti => DecodeRunLengthMulti(DecodeRunLengthSingle(data)), SawyerEncoding.Rotate => DecodeRotate(data), @@ -361,9 +361,9 @@ public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan data) + private static byte[] DecodeRunLengthSingle(byte[] data) { - List buffer = []; + var ms = new MemoryStream(); for (var i = 0; i < data.Length; ++i) { @@ -376,7 +376,11 @@ private static byte[] DecodeRunLengthSingle(ReadOnlySpan data) throw new ArgumentException("Invalid RLE run"); } - buffer.AddRange(Enumerable.Repeat(data[i], 257 - rleCodeByte)); + var count = 257 - rleCodeByte; + + var arr = new byte[count]; + Array.Fill(arr, data[i]); + ms.Write(arr); } else { @@ -387,24 +391,17 @@ private static byte[] DecodeRunLengthSingle(ReadOnlySpan data) var copyLen = rleCodeByte + 1; - for (var j = 0; j < copyLen; ++j) - { - buffer.Add(data[i + 1 + j]); - } + ms.Write(data, i + 1, copyLen); + //for (var j = 0; j < copyLen; ++j) + //{ + // ms.WriteByte(data[i + 1 + j]); + //} i += rleCodeByte + 1; } } - // convert to span - var decodedSpan = new byte[buffer.Count]; - var counter = 0; - foreach (var b in buffer) - { - decodedSpan[counter++] = b; - } - - return decodedSpan; + return ms.ToArray(); } // taken from openloco SawyerStreamReader::decodeRunLengthMulti diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs index 0fc80250..90abfb55 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs +++ b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics; using OpenLocoTool.DatFileParsing; using OpenLocoTool.Headers; @@ -197,8 +198,13 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) remainingData = remainingData[1..]; // uint8_t CompatibleCargoCategories[index].Add(vehicleCargoCategory); - CargoTypeSpriteOffsets.Add(vehicleCargoCategory, cargoTypeSpriteOffset); + if (!CargoTypeSpriteOffsets.TryAdd(vehicleCargoCategory, cargoTypeSpriteOffset)) + { + // invalid object - shouldn't have 2 cargo types that are the same + } + + // advance ptr ptr = BitConverter.ToUInt16(remainingData[0..2]); } diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 7277372d..1a2df8f4 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -155,22 +155,25 @@ public void SaveSettings() } // this method loads every single object entirely. it takes a long time to run - void CreateIndex(string[] allFiles, IProgress progress) + void CreateIndex(string[] allFiles, IProgress? progress) { ConcurrentDictionary ccHeaderIndex = new(); // key is full path/filename ConcurrentDictionary ccObjectCache = new(); // key is full path/filename var count = 0; + ConcurrentDictionary timePerFile = new(); + logger.Info($"Creating index on {allFiles.Length} files"); var sw = new Stopwatch(); sw.Start(); - Parallel.ForEach(allFiles, (file) => + Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, (file) => //foreach (var file in allFiles) { try { + var startTime = sw.Elapsed; var (fileInfo, locoObject) = SawyerStreamReader.LoadFull(file); if (!ccObjectCache.TryAdd(file, new UiLocoObject { DatFileInfo = fileInfo, LocoObject = locoObject })) { @@ -188,6 +191,11 @@ void CreateIndex(string[] allFiles, IProgress progress) { logger.Warning($"Didn't add file {file} to index - already exists (how???)"); } + else + { + var elapsed = sw.Elapsed - startTime; + timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed); + } } catch (Exception ex) { @@ -200,7 +208,7 @@ void CreateIndex(string[] allFiles, IProgress progress) finally { Interlocked.Increment(ref count); - progress.Report(count / (float)allFiles.Length); + progress?.Report(count / (float)allFiles.Length); } //} }); @@ -209,7 +217,18 @@ void CreateIndex(string[] allFiles, IProgress progress) ObjectCache = ccObjectCache.OrderBy(kvp => kvp.Key).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); sw.Stop(); - logger.Info($"Finished creating index. Time={sw.Elapsed}"); + logger.Info("Finished creating index"); + + logger.Debug($"Time time={sw.Elapsed}"); + + var slowest = timePerFile.MaxBy(x => x.Value.Ticks); + logger.Debug($"Slowest file={slowest.Key} Time={slowest.Value}"); + + var average = timePerFile.Average(x => x.Value.TotalMilliseconds); + logger.Debug($"Average time={average}ms"); + + var median = timePerFile.OrderBy(x => x.Value).Skip(timePerFile.Count / 2).Take(1).Single(); + logger.Debug($"Median time={median.Value}ms"); } public static void SaveFile(string path, UiLocoObject obj) @@ -234,7 +253,7 @@ public bool LoadDataDirectory(string directory) return true; } - public void LoadObjDirectory(string directory, IProgress progress, bool useExistingIndex) + public void LoadObjDirectory(string directory, IProgress? progress, bool useExistingIndex) { if (!Directory.Exists(directory)) { From 0213e954ffc01373aedcad62271aa16093ea246b Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Wed, 10 Jan 2024 16:43:45 +1100 Subject: [PATCH 04/13] some stream optimisations, file cleanup --- .../DatFileParsing/ObjectAnnotator.cs | 2 +- .../DatFileParsing/SawyerStreamReader.cs | 69 +++++-------------- OpenLocoTool/IEnumerableExtensions.cs | 21 ++++++ OpenLocoTool/Objects/BuildingObject.cs | 4 +- OpenLocoTool/Objects/DockObject.cs | 1 + OpenLocoTool/Objects/IndustryObject.cs | 25 +------ OpenLocoTool/Objects/RegionObject.cs | 2 +- OpenLocoTool/Objects/RoadObject.cs | 10 +-- OpenLocoTool/Objects/RoadStationObject.cs | 2 +- OpenLocoTool/Objects/TrackObject.cs | 12 ++-- OpenLocoTool/Objects/TrainSignalObject.cs | 2 +- OpenLocoTool/Objects/TrainStationObject.cs | 2 +- OpenLocoTool/Objects/Vehicle/VehicleObject.cs | 4 +- .../{DatFileParsing => Types}/DatFileInfo.cs | 0 .../{DatFileParsing => Types}/G1Dat.cs | 0 .../{Headers => Types}/G1Element32.cs | 0 OpenLocoTool/{Headers => Types}/G1Header.cs | 0 .../{DatFileParsing => Types}/ILocoObject.cs | 1 + .../{DatFileParsing => Types}/ILocoStruct.cs | 0 .../ILocoStructVariableData.cs | 0 .../{DatFileParsing => Types}/LocoObject.cs | 1 + .../{Headers => Types}/ObjectHeader.cs | 0 OpenLocoTool/{Headers => Types}/S5Header.cs | 0 .../{DatFileParsing => Types}/StringTable.cs | 0 .../{DatFileParsing => Types}/Typedefs.cs | 0 OpenLocoToolGui/ImageHelpers.cs | 30 ++++++++ OpenLocoToolGui/MainForm.Designer.cs | 6 +- OpenLocoToolGui/MainFormModel.cs | 8 +-- OpenLocoToolGui/PaletteHelpers.cs | 28 +------- OpenLocoToolGui/StringTableUserControl.cs | 2 +- OpenLocoToolTests/ObjectLoadingTests.cs | 2 +- OpenLocoToolTests/ObjectSavingTests.cs | 4 +- 32 files changed, 109 insertions(+), 129 deletions(-) create mode 100644 OpenLocoTool/IEnumerableExtensions.cs rename OpenLocoTool/{DatFileParsing => Types}/DatFileInfo.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/G1Dat.cs (100%) rename OpenLocoTool/{Headers => Types}/G1Element32.cs (100%) rename OpenLocoTool/{Headers => Types}/G1Header.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/ILocoObject.cs (92%) rename OpenLocoTool/{DatFileParsing => Types}/ILocoStruct.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/ILocoStructVariableData.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/LocoObject.cs (96%) rename OpenLocoTool/{Headers => Types}/ObjectHeader.cs (100%) rename OpenLocoTool/{Headers => Types}/S5Header.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/StringTable.cs (100%) rename OpenLocoTool/{DatFileParsing => Types}/Typedefs.cs (100%) create mode 100644 OpenLocoToolGui/ImageHelpers.cs diff --git a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs b/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs index 9aff12cb..fce38712 100644 --- a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs +++ b/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs @@ -137,7 +137,7 @@ public static IList AnnotateG1Data(byte[] fullData, int runningCount return annotations; } - public static int AnnotateStringTable(byte[] fullData, int runningCount, ILocoStruct locoStruct, IList annotations) + static int AnnotateStringTable(byte[] fullData, int runningCount, ILocoStruct locoStruct, IList annotations) { var root = new Annotation("String Table", runningCount, 1); annotations.Add(root); diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 2dea77aa..29389e32 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -8,7 +8,7 @@ namespace OpenLocoTool.DatFileParsing { public static class SawyerStreamReader { - public static List LoadVariableHeaders(ReadOnlySpan data, int count) + public static List LoadVariableCountS5Headers(ReadOnlySpan data, int count) { List result = []; for (var i = 0; i < count; ++i) @@ -25,7 +25,7 @@ public static List LoadVariableHeaders(ReadOnlySpan data, int co return result; } - public static S5Header LoadS5Header(string filename, ILogger? logger = null) + public static S5Header LoadS5HeaderFromFile(string filename, ILogger? logger = null) { if (!File.Exists(filename)) { @@ -54,8 +54,7 @@ public static byte[] LoadBytesFromFile(string filename, ILogger? logger = null) return File.ReadAllBytes(filename); } - // load file - public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadDecode(string filename, ILogger? logger = null) + public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadAndDecodeFromFile(string filename, ILogger? logger = null) { ReadOnlySpan fullData = LoadBytesFromFile(filename); @@ -80,11 +79,11 @@ public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) Lo } // load file - public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFull(string filename, ILogger? logger = null, bool loadExtra = true) + public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFullObjectFromFile(string filename, bool loadExtra = true, ILogger? logger = null) { logger?.Info($"Full-loading \"{filename}\" with loadExtra={loadExtra}"); - var (s5Header, objectHeader, decodedData) = LoadDecode(filename, logger); + var (s5Header, objectHeader, decodedData) = LoadAndDecodeFromFile(filename, logger); ReadOnlySpan remainingData = decodedData; var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData); @@ -130,7 +129,7 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFull(string return new(new DatFileInfo(s5Header, objectHeader), newObj); } - public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan data, string[] stringNames) + public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan data, string[] stringNames, ILogger? logger = null) { var stringTable = new StringTable(); @@ -156,7 +155,7 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan table, int bytesRead) LoadImag { var g1Element32s = new List(); - if (data.Length == 0) + if (data.Length < ObjectAttributes.StructSize()) { return (new G1Header(0, 0), g1Element32s, 0); } @@ -215,7 +214,6 @@ public static (G1Header header, List table, int bytesRead) LoadImag var currElement = g1Element32s[i]; var nextOffset = i < g1Header.NumEntries - 1 ? g1Element32s[i + 1].Offset - // : g1Header.TotalSize; : (uint)g1Header.ImageData.Length; currElement.ImageData = imageData[(int)currElement.Offset..(int)nextOffset].ToArray(); @@ -234,7 +232,7 @@ public static byte[] DecodeRLEImageData(G1Element32 img) // not sure why this happens, but this seems 'legit'; airport files have these if (img.ImageData.Length == 0) { - return img.ImageData; + return []; } var width = img.Width; @@ -392,11 +390,6 @@ private static byte[] DecodeRunLengthSingle(byte[] data) var copyLen = rleCodeByte + 1; ms.Write(data, i + 1, copyLen); - //for (var j = 0; j < copyLen; ++j) - //{ - // ms.WriteByte(data[i + 1 + j]); - //} - i += rleCodeByte + 1; } } @@ -424,7 +417,6 @@ private static byte[] DecodeRunLengthMulti(ReadOnlySpan data) else { var offset = (data[i] >> 3) - 32; - //assert(offset < 0); if (-offset > buffer.Count) { throw new ArgumentException("Invalid RLE run"); @@ -433,54 +425,31 @@ private static byte[] DecodeRunLengthMulti(ReadOnlySpan data) var copySrc = 0 + buffer.Count + offset; var copyLen = (data[i] & 7) + 1; - // Copy it to temp buffer first as we can't copy buffer to itself due to potential - // realloc in between reserve and push - //uint8_t copyBuffer[32]; - //assert(copyLen <= sizeof(copyBuffer)); - //std::memcpy(copyBuffer, copySrc, copyLen); - //buffer.push_back(copyBuffer, copyLen); - buffer.AddRange(buffer.GetRange(copySrc, copyLen)); } } - // convert to span - var decodedSpan = new byte[buffer.Count]; - var counter = 0; - foreach (var b in buffer) - { - decodedSpan[counter++] = b; - } - - return decodedSpan; + return [.. buffer]; } private static byte[] DecodeRotate(ReadOnlySpan data) { - List buffer = []; + static byte Ror(byte x, byte shift) + { + const byte byteDigits = 8; + return (byte)((x >> shift) | (x << (byteDigits - shift))); + } + + var buffer = new byte[data.Length]; byte code = 1; for (var i = 0; i < data.Length; i++) { - buffer.Add(Ror(data[i], code)); + buffer[i] = Ror(data[i], code); code = (byte)((code + 2) & 7); } - // convert to span - var decodedSpan = new byte[buffer.Count]; - var counter = 0; - foreach (var b in buffer) - { - decodedSpan[counter++] = b; - } - - return decodedSpan; - } - - private static byte Ror(byte x, byte shift) - { - const byte byteDigits = 8; - return (byte)((x >> shift) | (x << (byteDigits - shift))); + return buffer; } } } diff --git a/OpenLocoTool/IEnumerableExtensions.cs b/OpenLocoTool/IEnumerableExtensions.cs new file mode 100644 index 00000000..8c56bc44 --- /dev/null +++ b/OpenLocoTool/IEnumerableExtensions.cs @@ -0,0 +1,21 @@ +namespace OpenLocoTool +{ + public static class IEnumerableExtensions + { + public static IEnumerable Fill(this IEnumerable source, int minLength, T fillValue = default) + { + var i = 0; + foreach (var item in source) + { + i++; + yield return item; + } + + while (i < minLength) + { + i++; + yield return fillValue; + } + } + } +} diff --git a/OpenLocoTool/Objects/BuildingObject.cs b/OpenLocoTool/Objects/BuildingObject.cs index 47510301..22fe526b 100644 --- a/OpenLocoTool/Objects/BuildingObject.cs +++ b/OpenLocoTool/Objects/BuildingObject.cs @@ -87,12 +87,12 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) // produced cargo ProducedCargo.Clear(); - ProducedCargo = SawyerStreamReader.LoadVariableHeaders(remainingData, MaxProducedCargoType); + ProducedCargo = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxProducedCargoType); remainingData = remainingData[(S5Header.StructLength * MaxProducedCargoType)..]; // required cargo RequiredCargo.Clear(); - RequiredCargo = SawyerStreamReader.LoadVariableHeaders(remainingData, MaxRequiredCargoType); + RequiredCargo = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxRequiredCargoType); remainingData = remainingData[(S5Header.StructLength * MaxRequiredCargoType)..]; // load ?? diff --git a/OpenLocoTool/Objects/DockObject.cs b/OpenLocoTool/Objects/DockObject.cs index 7bb82a80..d8ebdc94 100644 --- a/OpenLocoTool/Objects/DockObject.cs +++ b/OpenLocoTool/Objects/DockObject.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using OpenLocoTool.DatFileParsing; using OpenLocoTool.Headers; +using OpenLocoTool.Types; namespace OpenLocoTool.Objects { diff --git a/OpenLocoTool/Objects/IndustryObject.cs b/OpenLocoTool/Objects/IndustryObject.cs index 9d2e0c42..f5f91381 100644 --- a/OpenLocoTool/Objects/IndustryObject.cs +++ b/OpenLocoTool/Objects/IndustryObject.cs @@ -188,17 +188,17 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) // produced cargo ProducedCargo.Clear(); - ProducedCargo.AddRange(SawyerStreamReader.LoadVariableHeaders(remainingData, MaxProducedCargoType)); + ProducedCargo.AddRange(SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxProducedCargoType)); remainingData = remainingData[(S5Header.StructLength * MaxProducedCargoType)..]; // required cargo RequiredCargo.Clear(); - RequiredCargo.AddRange(SawyerStreamReader.LoadVariableHeaders(remainingData, MaxRequiredCargoType)); + RequiredCargo.AddRange(SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxRequiredCargoType)); remainingData = remainingData[(S5Header.StructLength * MaxRequiredCargoType)..]; // wall types WallTypes.Clear(); - WallTypes.AddRange(SawyerStreamReader.LoadVariableHeaders(remainingData, WallTypeCount)); + WallTypes.AddRange(SawyerStreamReader.LoadVariableCountS5Headers(remainingData, WallTypeCount)); remainingData = remainingData[(S5Header.StructLength * WallTypeCount)..]; // wall type @@ -284,23 +284,4 @@ public ReadOnlySpan Save() return ms.ToArray(); } } - - public static class IEnumerableExtensions - { - public static IEnumerable Fill(this IEnumerable source, int minLength, T fillValue = default) - { - var i = 0; - foreach (var item in source) - { - i++; - yield return item; - } - - while (i < minLength) - { - i++; - yield return fillValue; - } - } - } } diff --git a/OpenLocoTool/Objects/RegionObject.cs b/OpenLocoTool/Objects/RegionObject.cs index b5b60eb8..2af5fd9a 100644 --- a/OpenLocoTool/Objects/RegionObject.cs +++ b/OpenLocoTool/Objects/RegionObject.cs @@ -28,7 +28,7 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) { // unk RequiredObjects.Clear(); - RequiredObjects = SawyerStreamReader.LoadVariableHeaders(remainingData, RequiredObjectCount); + RequiredObjects = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, RequiredObjectCount); remainingData = remainingData[(S5Header.StructLength * RequiredObjectCount)..]; // dependent objects diff --git a/OpenLocoTool/Objects/RoadObject.cs b/OpenLocoTool/Objects/RoadObject.cs index b14708e3..bc4cf6de 100644 --- a/OpenLocoTool/Objects/RoadObject.cs +++ b/OpenLocoTool/Objects/RoadObject.cs @@ -92,23 +92,23 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) Stations.Clear(); // compatible roads/tracks - Compatible = SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatible); + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; // mods - Mods = SawyerStreamReader.LoadVariableHeaders(remainingData, NumMods); + Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods); remainingData = remainingData[(S5Header.StructLength * NumMods)..]; // tunnel - Tunnel = SawyerStreamReader.LoadVariableHeaders(remainingData, NumTunnels)[0]; + Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0]; remainingData = remainingData[(S5Header.StructLength * NumTunnels)..]; // bridges - Bridges = SawyerStreamReader.LoadVariableHeaders(remainingData, NumBridges); + Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges); remainingData = remainingData[(S5Header.StructLength * NumBridges)..]; // stations - Stations = SawyerStreamReader.LoadVariableHeaders(remainingData, NumStations); + Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations); remainingData = remainingData[(S5Header.StructLength * NumStations)..]; return remainingData; diff --git a/OpenLocoTool/Objects/RoadStationObject.cs b/OpenLocoTool/Objects/RoadStationObject.cs index c9332f32..592f31cc 100644 --- a/OpenLocoTool/Objects/RoadStationObject.cs +++ b/OpenLocoTool/Objects/RoadStationObject.cs @@ -57,7 +57,7 @@ public class RoadStationObject( public ReadOnlySpan Load(ReadOnlySpan remainingData) { // compatible - Compatible = SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatible); + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; // cargo diff --git a/OpenLocoTool/Objects/TrackObject.cs b/OpenLocoTool/Objects/TrackObject.cs index 80c24294..8e548812 100644 --- a/OpenLocoTool/Objects/TrackObject.cs +++ b/OpenLocoTool/Objects/TrackObject.cs @@ -95,27 +95,27 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) Stations.Clear(); // compatible roads/tracks - Compatible = SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatible); + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; // mods - Mods = SawyerStreamReader.LoadVariableHeaders(remainingData, NumMods); + Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods); remainingData = remainingData[(S5Header.StructLength * NumMods)..]; // signals - Signals = SawyerStreamReader.LoadVariableHeaders(remainingData, NumSignals); + Signals = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumSignals); remainingData = remainingData[(S5Header.StructLength * NumSignals)..]; // tunnel - Tunnel = SawyerStreamReader.LoadVariableHeaders(remainingData, NumTunnels)[0]; + Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0]; remainingData = remainingData[(S5Header.StructLength * NumTunnels)..]; // bridges - Bridges = SawyerStreamReader.LoadVariableHeaders(remainingData, NumBridges); + Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges); remainingData = remainingData[(S5Header.StructLength * NumBridges)..]; // stations - Stations = SawyerStreamReader.LoadVariableHeaders(remainingData, NumStations); + Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations); remainingData = remainingData[(S5Header.StructLength * NumStations)..]; return remainingData; diff --git a/OpenLocoTool/Objects/TrainSignalObject.cs b/OpenLocoTool/Objects/TrainSignalObject.cs index 085c1dd0..f85f120f 100644 --- a/OpenLocoTool/Objects/TrainSignalObject.cs +++ b/OpenLocoTool/Objects/TrainSignalObject.cs @@ -52,7 +52,7 @@ public class TrainSignalObject( public ReadOnlySpan Load(ReadOnlySpan remainingData) { Mods.Clear(); - Mods = SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatible); + Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); return remainingData[(S5Header.StructLength * NumCompatible)..]; } diff --git a/OpenLocoTool/Objects/TrainStationObject.cs b/OpenLocoTool/Objects/TrainStationObject.cs index b9b86388..c8cda884 100644 --- a/OpenLocoTool/Objects/TrainStationObject.cs +++ b/OpenLocoTool/Objects/TrainStationObject.cs @@ -62,7 +62,7 @@ public class TrainStationObject( public ReadOnlySpan Load(ReadOnlySpan remainingData) { // compatible - Compatible = SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatible); + Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible); remainingData = remainingData[(S5Header.StructLength * NumCompatible)..]; // cargo offsets (for drawing the cargo on the station) diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs index 90abfb55..63ae03fc 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs +++ b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs @@ -158,7 +158,7 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) // track extras RequiredTrackExtras.Clear(); - RequiredTrackExtras.AddRange(SawyerStreamReader.LoadVariableHeaders(remainingData, NumTrackExtras)); + RequiredTrackExtras.AddRange(SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTrackExtras)); remainingData = remainingData[(S5Header.StructLength * NumTrackExtras)..]; // compatible cargo types @@ -236,7 +236,7 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData) // numCompat CompatibleVehicles.Clear(); - CompatibleVehicles.AddRange(SawyerStreamReader.LoadVariableHeaders(remainingData, NumCompatibleVehicles)); + CompatibleVehicles.AddRange(SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatibleVehicles)); remainingData = remainingData[(S5Header.StructLength * NumCompatibleVehicles)..]; // rack rail diff --git a/OpenLocoTool/DatFileParsing/DatFileInfo.cs b/OpenLocoTool/Types/DatFileInfo.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/DatFileInfo.cs rename to OpenLocoTool/Types/DatFileInfo.cs diff --git a/OpenLocoTool/DatFileParsing/G1Dat.cs b/OpenLocoTool/Types/G1Dat.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/G1Dat.cs rename to OpenLocoTool/Types/G1Dat.cs diff --git a/OpenLocoTool/Headers/G1Element32.cs b/OpenLocoTool/Types/G1Element32.cs similarity index 100% rename from OpenLocoTool/Headers/G1Element32.cs rename to OpenLocoTool/Types/G1Element32.cs diff --git a/OpenLocoTool/Headers/G1Header.cs b/OpenLocoTool/Types/G1Header.cs similarity index 100% rename from OpenLocoTool/Headers/G1Header.cs rename to OpenLocoTool/Types/G1Header.cs diff --git a/OpenLocoTool/DatFileParsing/ILocoObject.cs b/OpenLocoTool/Types/ILocoObject.cs similarity index 92% rename from OpenLocoTool/DatFileParsing/ILocoObject.cs rename to OpenLocoTool/Types/ILocoObject.cs index 857666a0..6e799627 100644 --- a/OpenLocoTool/DatFileParsing/ILocoObject.cs +++ b/OpenLocoTool/Types/ILocoObject.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using OpenLocoTool.Headers; +using OpenLocoTool.Types; namespace OpenLocoTool.DatFileParsing { diff --git a/OpenLocoTool/DatFileParsing/ILocoStruct.cs b/OpenLocoTool/Types/ILocoStruct.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/ILocoStruct.cs rename to OpenLocoTool/Types/ILocoStruct.cs diff --git a/OpenLocoTool/DatFileParsing/ILocoStructVariableData.cs b/OpenLocoTool/Types/ILocoStructVariableData.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/ILocoStructVariableData.cs rename to OpenLocoTool/Types/ILocoStructVariableData.cs diff --git a/OpenLocoTool/DatFileParsing/LocoObject.cs b/OpenLocoTool/Types/LocoObject.cs similarity index 96% rename from OpenLocoTool/DatFileParsing/LocoObject.cs rename to OpenLocoTool/Types/LocoObject.cs index 5383e597..a50e3ff9 100644 --- a/OpenLocoTool/DatFileParsing/LocoObject.cs +++ b/OpenLocoTool/Types/LocoObject.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using OpenLocoTool.Headers; +using OpenLocoTool.Types; namespace OpenLocoTool.DatFileParsing { diff --git a/OpenLocoTool/Headers/ObjectHeader.cs b/OpenLocoTool/Types/ObjectHeader.cs similarity index 100% rename from OpenLocoTool/Headers/ObjectHeader.cs rename to OpenLocoTool/Types/ObjectHeader.cs diff --git a/OpenLocoTool/Headers/S5Header.cs b/OpenLocoTool/Types/S5Header.cs similarity index 100% rename from OpenLocoTool/Headers/S5Header.cs rename to OpenLocoTool/Types/S5Header.cs diff --git a/OpenLocoTool/DatFileParsing/StringTable.cs b/OpenLocoTool/Types/StringTable.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/StringTable.cs rename to OpenLocoTool/Types/StringTable.cs diff --git a/OpenLocoTool/DatFileParsing/Typedefs.cs b/OpenLocoTool/Types/Typedefs.cs similarity index 100% rename from OpenLocoTool/DatFileParsing/Typedefs.cs rename to OpenLocoTool/Types/Typedefs.cs diff --git a/OpenLocoToolGui/ImageHelpers.cs b/OpenLocoToolGui/ImageHelpers.cs new file mode 100644 index 00000000..193527a5 --- /dev/null +++ b/OpenLocoToolGui/ImageHelpers.cs @@ -0,0 +1,30 @@ +using System.Drawing.Imaging; + +namespace OpenLocoToolGui +{ + public static class ImageHelpers + { + public static unsafe Color GetPixel(BitmapData d, int X, int Y) + { + var ptr = GetPtrToFirstPixel(d, X, Y); + return Color.FromArgb(ptr[3], ptr[2], ptr[1], ptr[0]); + } + + public static unsafe void SetPixel(BitmapData d, Point p, Color c) + => SetPixel(d, p.X, p.Y, c); + + public static unsafe void SetPixel(BitmapData d, int X, int Y, Color c) + => SetPixel(GetPtrToFirstPixel(d, X, Y), c); + + public static unsafe byte* GetPtrToFirstPixel(BitmapData d, int X, int Y) + => (byte*)d.Scan0.ToPointer() + (Y * d.Stride) + (X * (Image.GetPixelFormatSize(d.PixelFormat) / 8)); + + public static unsafe void SetPixel(byte* ptr, Color c) + { + ptr[0] = c.B; // Blue + ptr[1] = c.G; // Green + ptr[2] = c.R; // Red + ptr[3] = c.A; // Alpha + } + } +} diff --git a/OpenLocoToolGui/MainForm.Designer.cs b/OpenLocoToolGui/MainForm.Designer.cs index 0e5fb76b..cb7e4cae 100644 --- a/OpenLocoToolGui/MainForm.Designer.cs +++ b/OpenLocoToolGui/MainForm.Designer.cs @@ -1,4 +1,6 @@ -namespace OpenLocoToolGui +using OpenLocoTool.Types; + +namespace OpenLocoToolGui { partial class MainForm { @@ -652,7 +654,7 @@ private void InitializeComponent() // // locoObjectBindingSource // - locoObjectBindingSource.DataSource = typeof(OpenLocoTool.DatFileParsing.LocoObject); + locoObjectBindingSource.DataSource = typeof(LocoObject); // // MainForm // diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 1a2df8f4..447d1ec8 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -83,7 +83,7 @@ public MainFormModel(ILogger logger, string settingsFile) { //try //{ - SawyerStreamReader.LoadFull(dep.Key); + SawyerStreamReader.LoadFullObjectFromFile(dep.Key); //} //catch (Exception ex) //{ @@ -174,7 +174,7 @@ void CreateIndex(string[] allFiles, IProgress? progress) try { var startTime = sw.Elapsed; - var (fileInfo, locoObject) = SawyerStreamReader.LoadFull(file); + var (fileInfo, locoObject) = SawyerStreamReader.LoadFullObjectFromFile(file); if (!ccObjectCache.TryAdd(file, new UiLocoObject { DatFileInfo = fileInfo, LocoObject = locoObject })) { logger.Warning($"Didn't add file {file} to cache - already exists (how???)"); @@ -201,7 +201,7 @@ void CreateIndex(string[] allFiles, IProgress? progress) { logger.Error($"Failed to load \"{file}\"", ex); - var obj = SawyerStreamReader.LoadS5Header(file); + var obj = SawyerStreamReader.LoadS5HeaderFromFile(file); var indexObjectHeader = new IndexObjectHeader(obj.Name, obj.ObjectType, null); ccHeaderIndex.TryAdd(file, indexObjectHeader); } @@ -328,7 +328,7 @@ static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, } else { - var obj = SawyerStreamReader.LoadFull(filename); + var obj = SawyerStreamReader.LoadFullObjectFromFile(filename); var uiObj = new UiLocoObject { DatFileInfo = obj.Item1, LocoObject = obj.Item2 }; ObjectCache.TryAdd(filename, uiObj); return uiObj; diff --git a/OpenLocoToolGui/PaletteHelpers.cs b/OpenLocoToolGui/PaletteHelpers.cs index f6b44279..695a9c28 100644 --- a/OpenLocoToolGui/PaletteHelpers.cs +++ b/OpenLocoToolGui/PaletteHelpers.cs @@ -43,30 +43,4 @@ public static byte[] Palettise(Bitmap img) static byte ColorToPaletteIndex(Color c) => 0; } - - public static class ImageHelpers - { - public static unsafe Color GetPixel(BitmapData d, int X, int Y) - { - var ptr = GetPtrToFirstPixel(d, X, Y); - return Color.FromArgb(ptr[3], ptr[2], ptr[1], ptr[0]); - } - - public static unsafe void SetPixel(BitmapData d, Point p, Color c) - => SetPixel(d, p.X, p.Y, c); - - public static unsafe void SetPixel(BitmapData d, int X, int Y, Color c) - => SetPixel(GetPtrToFirstPixel(d, X, Y), c); - - public static unsafe byte* GetPtrToFirstPixel(BitmapData d, int X, int Y) - => (byte*)d.Scan0.ToPointer() + (Y * d.Stride) + (X * (Image.GetPixelFormatSize(d.PixelFormat) / 8)); - - public static unsafe void SetPixel(byte* ptr, Color c) - { - ptr[0] = c.B; // Blue - ptr[1] = c.G; // Green - ptr[2] = c.R; // Red - ptr[3] = c.A; // Alpha - } - } -} +} \ No newline at end of file diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/OpenLocoToolGui/StringTableUserControl.cs index 51cc182c..261e94c8 100644 --- a/OpenLocoToolGui/StringTableUserControl.cs +++ b/OpenLocoToolGui/StringTableUserControl.cs @@ -1,4 +1,4 @@ -using OpenLocoTool.DatFileParsing; +using OpenLocoTool.Types; using System.ComponentModel; namespace OpenLocoToolGui diff --git a/OpenLocoToolTests/ObjectLoadingTests.cs b/OpenLocoToolTests/ObjectLoadingTests.cs index 0602f117..734fded7 100644 --- a/OpenLocoToolTests/ObjectLoadingTests.cs +++ b/OpenLocoToolTests/ObjectLoadingTests.cs @@ -15,7 +15,7 @@ public class ObjectLoadingTests { var fileSize = new FileInfo(filename).Length; var logger = new OpenLocoToolCommon.Logger(); - var loaded = SawyerStreamReader.LoadFull(filename, logger); + var loaded = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: logger); Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(fileSize - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct"); diff --git a/OpenLocoToolTests/ObjectSavingTests.cs b/OpenLocoToolTests/ObjectSavingTests.cs index f06c03ae..c8c02129 100644 --- a/OpenLocoToolTests/ObjectSavingTests.cs +++ b/OpenLocoToolTests/ObjectSavingTests.cs @@ -16,7 +16,7 @@ public void WriteLocoStruct() const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SIGC3.DAT"; var fileSize = new FileInfo(testFile).Length; var logger = new Logger(); - var loaded = SawyerStreamReader.LoadFull(testFile); + var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile); // load data in raw bytes for test ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile); @@ -48,7 +48,7 @@ public void Industry() const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BREWERY.DAT"; var fileSize = new FileInfo(testFile).Length; var logger = new Logger(); - var loaded = SawyerStreamReader.LoadFull(testFile); + var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile); // load data in raw bytes for test ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile); From 42021686d5eb4b6a0866ebf207692219ac9e5984 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Wed, 10 Jan 2024 18:01:09 +1100 Subject: [PATCH 05/13] fix string table ui and parsing --- .../DatFileParsing/SawyerStreamReader.cs | 17 +++- .../DatFileParsing/SawyerStreamWriter.cs | 6 +- OpenLocoTool/Types/LocoObject.cs | 4 +- OpenLocoTool/Types/StringTable.cs | 12 +-- OpenLocoTool/Types/Typedefs.cs | 3 +- .../StringTableUserControl.Designer.cs | 38 ++++----- OpenLocoToolGui/StringTableUserControl.cs | 78 ++++++++++++------- 7 files changed, 91 insertions(+), 67 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 29389e32..3eb0f0fa 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -2,6 +2,7 @@ using System.Text; using OpenLocoTool.Headers; using OpenLocoTool.Objects; +using OpenLocoTool.Types; using OpenLocoToolCommon; namespace OpenLocoTool.DatFileParsing @@ -142,9 +143,15 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan()) + { + languageDict.Add(language, string.Empty); + } + for (; ptr < data.Length && data[ptr] != 0xFF;) { var lang = (LanguageId)data[ptr++]; @@ -152,12 +159,14 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj objStream.Write(objBytes); // string table - foreach (var ste in obj.StringTable.table) + foreach (var ste in obj.StringTable.Table) { - foreach (var language in ste.Value) + foreach (var language in ste.Value.Where(str => !string.IsNullOrEmpty(str.Value))) // skip strings with empty content { objStream.WriteByte((byte)language.Key); - var strBytes = Encoding.ASCII.GetBytes(language.Value); + var strBytes = Encoding.Latin1.GetBytes(language.Value); objStream.Write(strBytes, 0, strBytes.Length); objStream.WriteByte((byte)'\0'); } diff --git a/OpenLocoTool/Types/LocoObject.cs b/OpenLocoTool/Types/LocoObject.cs index a50e3ff9..7566c2f2 100644 --- a/OpenLocoTool/Types/LocoObject.cs +++ b/OpenLocoTool/Types/LocoObject.cs @@ -1,8 +1,8 @@ using System.ComponentModel; +using OpenLocoTool.DatFileParsing; using OpenLocoTool.Headers; -using OpenLocoTool.Types; -namespace OpenLocoTool.DatFileParsing +namespace OpenLocoTool.Types { [TypeConverter(typeof(ExpandableObjectConverter))] diff --git a/OpenLocoTool/Types/StringTable.cs b/OpenLocoTool/Types/StringTable.cs index 44e80340..f279b21b 100644 --- a/OpenLocoTool/Types/StringTable.cs +++ b/OpenLocoTool/Types/StringTable.cs @@ -1,20 +1,16 @@ using System.ComponentModel; -namespace OpenLocoTool.DatFileParsing +namespace OpenLocoTool.Types { [TypeConverter(typeof(ExpandableObjectConverter))] public class StringTable { - public Dictionary> table { get; set; } = []; - - public void Add(string key, Dictionary value) => table.Add(key, value); + public Dictionary> Table { get; set; } = []; public Dictionary this[string key] { - get => table[key]; - set => table[key] = value; + get => Table[key]; + set => Table[key] = value; } - - public Dictionary>.KeyCollection Keys => table.Keys; } } diff --git a/OpenLocoTool/Types/Typedefs.cs b/OpenLocoTool/Types/Typedefs.cs index 29ef3255..20a076bc 100644 --- a/OpenLocoTool/Types/Typedefs.cs +++ b/OpenLocoTool/Types/Typedefs.cs @@ -10,8 +10,9 @@ global using MicroZ = System.Byte; global using SoundObjectId = System.Byte; using System.ComponentModel; +using OpenLocoTool.DatFileParsing; -namespace OpenLocoTool.DatFileParsing +namespace OpenLocoTool.Types { [TypeConverter(typeof(ExpandableObjectConverter))] [LocoStructSize(0x04)] diff --git a/OpenLocoToolGui/StringTableUserControl.Designer.cs b/OpenLocoToolGui/StringTableUserControl.Designer.cs index 1a0a5e46..46f92f85 100644 --- a/OpenLocoToolGui/StringTableUserControl.Designer.cs +++ b/OpenLocoToolGui/StringTableUserControl.Designer.cs @@ -28,51 +28,45 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - dgvLanguageSelector = new DataGridView(); lbStringSelector = new ListBox(); - ((System.ComponentModel.ISupportInitialize)dgvLanguageSelector).BeginInit(); + flpLanguageStrings = new FlowLayoutPanel(); SuspendLayout(); // - // dgvLanguageSelector - // - dgvLanguageSelector.AllowUserToResizeRows = false; - dgvLanguageSelector.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - dgvLanguageSelector.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dgvLanguageSelector.Location = new Point(214, 1); - dgvLanguageSelector.Margin = new Padding(2, 1, 2, 1); - dgvLanguageSelector.MultiSelect = false; - dgvLanguageSelector.Name = "dgvLanguageSelector"; - dgvLanguageSelector.RowHeadersWidth = 82; - dgvLanguageSelector.RowTemplate.Height = 41; - dgvLanguageSelector.Size = new Size(554, 292); - dgvLanguageSelector.TabIndex = 1; - // // lbStringSelector // - lbStringSelector.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left; + lbStringSelector.Dock = DockStyle.Left; lbStringSelector.FormattingEnabled = true; lbStringSelector.ItemHeight = 15; - lbStringSelector.Location = new Point(2, 1); + lbStringSelector.Location = new Point(0, 0); lbStringSelector.Margin = new Padding(2, 1, 2, 1); lbStringSelector.Name = "lbStringSelector"; - lbStringSelector.Size = new Size(211, 289); + lbStringSelector.Size = new Size(211, 294); lbStringSelector.TabIndex = 2; // + // flpLanguageStrings + // + flpLanguageStrings.BorderStyle = BorderStyle.Fixed3D; + flpLanguageStrings.Dock = DockStyle.Fill; + flpLanguageStrings.FlowDirection = FlowDirection.TopDown; + flpLanguageStrings.Location = new Point(211, 0); + flpLanguageStrings.Name = "flpLanguageStrings"; + flpLanguageStrings.Size = new Size(558, 294); + flpLanguageStrings.TabIndex = 3; + // // StringTableUserControl // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; + Controls.Add(flpLanguageStrings); Controls.Add(lbStringSelector); - Controls.Add(dgvLanguageSelector); Margin = new Padding(2, 1, 2, 1); Name = "StringTableUserControl"; Size = new Size(769, 294); - ((System.ComponentModel.ISupportInitialize)dgvLanguageSelector).EndInit(); ResumeLayout(false); } #endregion - private DataGridView dgvLanguageSelector; private ListBox lbStringSelector; + private FlowLayoutPanel flpLanguageStrings; } } diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/OpenLocoToolGui/StringTableUserControl.cs index 261e94c8..fad1ce4f 100644 --- a/OpenLocoToolGui/StringTableUserControl.cs +++ b/OpenLocoToolGui/StringTableUserControl.cs @@ -1,41 +1,22 @@ -using OpenLocoTool.Types; +using OpenLocoTool; +using OpenLocoTool.Types; using System.ComponentModel; namespace OpenLocoToolGui { public partial class StringTableUserControl : UserControl { - private readonly BindingList blKeys = []; - public StringTableUserControl() => InitializeComponent(); - StringTable _data; - StringTable Data - { - get => _data; - set - { - _data = value; - BindingSource.DataSource = _data; - } - } - BindingSource BindingSource = new(); + StringTable _data { get; set; } public void SetDataBinding(StringTable data) { - Data = data; - //dgvLanguageSelector.DataSource = BindingSource; - - //DataContext = Data; - - blKeys.Clear(); - foreach (var key in Data.Keys) - { - blKeys.Add(key); - } + _data = data; + lbStringSelector.DataSource = null; // Set up data binding for the outer dictionary DataGridView. - lbStringSelector.DataSource = blKeys; + lbStringSelector.DataSource = data.Table.Keys.ToList(); // Subscribe to the SelectionChanged event to populate the inner DataGridView. lbStringSelector.SelectedValueChanged += (sender, e) => UpdateDGVSource(); @@ -47,10 +28,53 @@ public void SetDataBinding(StringTable data) void UpdateDGVSource() { - if (lbStringSelector.SelectedValue != null && Data.table.ContainsKey((string)lbStringSelector.SelectedValue)) + flpLanguageStrings.SuspendLayout(); + flpLanguageStrings.Controls.Clear(); + + var sel = lbStringSelector.SelectedValue as string; + if (sel != null && _data.Table.ContainsKey(sel)) { - dgvLanguageSelector.DataSource = new BindingSource(Data[(string)lbStringSelector.SelectedValue], null); + foreach (var language in _data.Table[sel]) + { + var lblLanguage = new Label + { + BorderStyle = BorderStyle.FixedSingle, + Text = language.Key.ToString(), + Dock = DockStyle.Left, + Height = 24, + Width = 128, + TextAlign = ContentAlignment.MiddleLeft, + }; + + var pn = new Panel + { + //AutoSize = true, + //Dock = DockStyle.Top, + Height = 24, + Width = flpLanguageStrings.Width, + Margin = new Padding(0, 0, 0, 0), + Padding = new Padding(0, 0, 0, 0), + }; + + var tbText = new TextBox + { + BorderStyle = BorderStyle.FixedSingle, + Text = language.Value, + Dock = DockStyle.Left, + Height = 24, + Width = pn.Width - lblLanguage.Width - 4, + TextAlign = HorizontalAlignment.Left, + }; + tbText.TextChanged += (a, b) => _data.Table[sel][Enum.Parse(lblLanguage.Text)] = tbText.Text; + + pn.Controls.Add(tbText); + pn.Controls.Add(lblLanguage); + + flpLanguageStrings.Controls.Add(pn); + } } + + flpLanguageStrings.ResumeLayout(true); } } } From 7ca8a2eca02ea0ebd9df5f5b02462195b484ffb9 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Fri, 12 Jan 2024 17:39:35 +1100 Subject: [PATCH 06/13] add viewing of other data files, and playing of music tracks --- OpenLocoTool/Data/OriginalDataFiles.cs | 54 +++++--- OpenLocoTool/Objects/SoundObject.cs | 4 +- OpenLocoToolGui/GuiSettings.cs | 7 +- OpenLocoToolGui/MainForm.cs | 181 ++++++++++++++++++++++++- OpenLocoToolGui/MainFormModel.cs | 18 ++- 5 files changed, 224 insertions(+), 40 deletions(-) diff --git a/OpenLocoTool/Data/OriginalDataFiles.cs b/OpenLocoTool/Data/OriginalDataFiles.cs index a2fb6396..b8707d1d 100644 --- a/OpenLocoTool/Data/OriginalDataFiles.cs +++ b/OpenLocoTool/Data/OriginalDataFiles.cs @@ -2,18 +2,8 @@ { public static class OriginalDataFiles { - public static readonly HashSet Names = + public static readonly HashSet Music = [ - "Data/g1.DAT", - "plugin.dat", - "plugin2.dat", - "Data/CSS1.DAT", - "Data/CSS2.DAT", - "Data/CSS3.DAT", - "Data/CSS4.DAT", - "Data/CSS5.DAT", - "game.cfg", - "Data/KANJI.DAT", "Data/20s1.DAT", "Data/20s2.DAT", "Data/20s4.DAT", @@ -43,22 +33,44 @@ public static class OriginalDataFiles "Data/80s4.DAT", "Data/20s5.DAT", "Data/20s6.DAT", - "Data/title.dat", - "scores.dat", - "Scenarios/Boulder Breakers.SC5", + ]; + + public static readonly HashSet SoundEffects = + [ + "Data/CSS1.DAT", + "Data/CSS2.DAT", + "Data/CSS3.DAT", + "Data/CSS4.DAT", + "Data/CSS5.DAT", + ]; + + public static readonly HashSet Tutorials = + [ "Data/TUT1024_1.DAT", "Data/TUT1024_2.DAT", "Data/TUT1024_3.DAT", "Data/TUT800_1.DAT", "Data/TUT800_2.DAT", "Data/TUT800_3.DAT", - "openloco.yml", - "language", - "save", - "save/autosave", - "1.TMP", - "ObjData", - "Scenarios", + ]; + + public static readonly HashSet Uncategorised = + [ + "Data/g1.DAT", + "plugin.dat", + "plugin2.dat", + "game.cfg", + "Data/KANJI.DAT", + "Data/title.dat", + "scores.dat", + //"Scenarios/Boulder Breakers.SC5", + //"openloco.yml", + //"language", + //"save", + //"save/autosave", + //"1.TMP", + //"ObjData", + //"Scenarios", ]; } } diff --git a/OpenLocoTool/Objects/SoundObject.cs b/OpenLocoTool/Objects/SoundObject.cs index 77e97613..09cbd2f1 100644 --- a/OpenLocoTool/Objects/SoundObject.cs +++ b/OpenLocoTool/Objects/SoundObject.cs @@ -30,8 +30,8 @@ public record SoundObjectData( [LocoStructType(ObjectType.Sound)] [LocoStringTable("Name")] public record SoundObject( - //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, - [property: LocoStructOffset(0x02)] uint32_t SoundObjectDataPtr, + [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name, + [property: LocoStructOffset(0x02), Browsable(false)] uint32_t SoundObjectDataPtr, [property: LocoStructOffset(0x06)] uint8_t var_06, [property: LocoStructOffset(0x07)] uint8_t pad_07, [property: LocoStructOffset(0x08)] uint32_t Volume diff --git a/OpenLocoToolGui/GuiSettings.cs b/OpenLocoToolGui/GuiSettings.cs index b84e9d9d..c6ace3f4 100644 --- a/OpenLocoToolGui/GuiSettings.cs +++ b/OpenLocoToolGui/GuiSettings.cs @@ -39,10 +39,7 @@ public string DataDirectory public string G1DatFileName { get; set; } = "g1.DAT"; - [JsonIgnore] - public string IndexFilePath => Path.Combine(ObjDataDirectory, IndexFileName); - - [JsonIgnore] - public string G1Path => Path.Combine(DataDirectory, G1DatFileName); + public string GetObjDataFullPath(string fileName) => Path.Combine(ObjDataDirectory, fileName); + public string GetDataFullPath(string fileName) => Path.Combine(DataDirectory, fileName); } } diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 7381bee5..e5bc436b 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -5,7 +5,9 @@ using OpenLocoTool.Headers; using OpenLocoTool.Objects; using OpenLocoToolCommon; +using System; using System.Drawing.Imaging; +using System.Reflection.PortableExecutable; namespace OpenLocoToolGui { @@ -225,14 +227,76 @@ void InitCategoryTreeView(bool vanillaOnly, string fileFilter) { var dataNode = new TreeNode("Data"); - // if we want to add every file - //foreach (var file in Directory.GetFiles(model.Settings.DataDirectory)) - //{ - // AddObjectNode(file, Path.GetFileName(file), file, dataNode); - //} + var musicNode = new TreeNode("Music"); + var sfxNode = new TreeNode("Sound Effects"); + var tutorialsNode = new TreeNode("Tutorials"); + var miscNode = new TreeNode("Uncategorised"); - var g1 = model.Settings.G1DatFileName; - AddObjectNode(g1, Path.GetFileName(g1), g1, dataNode); + var unknownNode = new TreeNode("Unknown"); + + var allDataFiles = Directory.GetFiles(model.Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower()); + + // music + var expectedMusicFiles = OriginalDataFiles.Music.Select(f => f.ToLower().Replace("data/", string.Empty)); + foreach (var music in expectedMusicFiles) + { + if (allDataFiles.Any(f => f.EndsWith(music))) + { + musicNode.Nodes.Add(music, music); + tvUniqueLoadValues[music] = LoadBinaryMusic; + } + else + { + musicNode.Nodes.Add($" {music}"); + } + } + + // sound effects + var expectedSfxFiles = OriginalDataFiles.SoundEffects.Select(f => f.ToLower().Replace("data/", string.Empty)); + foreach (var sfx in expectedSfxFiles) + { + if (allDataFiles.Any(f => f.EndsWith(sfx))) + { + sfxNode.Nodes.Add(sfx); + } + else + { + sfxNode.Nodes.Add($" {sfx}"); + } + } + + // tutorials + var expectedTutorialFiles = OriginalDataFiles.Tutorials.Select(f => f.ToLower().Replace("data/", string.Empty)); + foreach (var tut in expectedTutorialFiles) + { + if (allDataFiles.Any(f => f.EndsWith(tut))) + { + tutorialsNode.Nodes.Add(tut); + } + else + { + tutorialsNode.Nodes.Add($" {tut}"); + } + } + + // uncategorised + var expectedMiscFiles = OriginalDataFiles.Uncategorised.Select(f => f.ToLower().Replace("data/", string.Empty)); + foreach (var tut in expectedMiscFiles) + { + if (allDataFiles.Any(f => f.EndsWith(tut))) + { + miscNode.Nodes.Add(tut); + } + else + { + miscNode.Nodes.Add($" {tut}"); + } + } + + dataNode.Nodes.Add(musicNode); + dataNode.Nodes.Add(sfxNode); + dataNode.Nodes.Add(tutorialsNode); + dataNode.Nodes.Add(miscNode); tvObjType.Nodes.Add(dataNode); @@ -448,6 +512,104 @@ static string constructAnnotationText(Annotation annotation) } } + void LoadBinaryMusic(string filename) + { + var path = model.Settings.GetDataFullPath(filename); + if (!File.Exists(path)) + { + return; + } + + var bytes = File.ReadAllBytes(path); + + model.Music.Add(filename, bytes); + + // play as music to test + PlayMusic(bytes); + } + + static bool isMusicPlaying; + + private static void PlayMusic(byte[] bytes) + { + if (isMusicPlaying) + { + isMusicPlaying = false; + Thread.Sleep(100); + } + + _ = Task.Run(() => + { + using (var ms = new MemoryStream(bytes)) + { + var br = new BinaryReader(ms); + var sig = br.ReadUInt32(); + if (sig != 0x46464952) // "RIFF" + { + // invalid signature + return; + } + + _ = br.ReadUInt32(); // size + + var riffType = br.ReadUInt32(); + if (riffType != 0x45564157) // "WAVE" + { + // invalid format + return; + } + + var formatMarker = br.ReadUInt32(); + if (formatMarker != 0x20746d66 && formatMarker != 0x00746d66) // "fmt\0" or "fmt" + { + // invalid format marker + return; + } + + _ = br.ReadUInt32(); // headersize + + var typeFormat = br.ReadUInt16(); + if (typeFormat != 1) + { + // invalid format type; expected PCM + return; + } + + var channels = br.ReadUInt16(); + var sampleRate = br.ReadUInt32(); + + _ = br.ReadUInt32(); + _ = br.ReadUInt16(); + + var bits = br.ReadUInt16(); + + var dataMarker = br.ReadUInt32(); + if (dataMarker != 0x61746164) // data + { + // invalid data marker + return; + } + + var pcmLength = br.ReadUInt32(); + var pcmData = new byte[pcmLength]; + br.Read(pcmData); + + using (var ms2 = new MemoryStream(pcmData)) + using (var rs = new RawSourceWaveStream(ms2, new WaveFormat((int)sampleRate, 16, channels))) + using (var wo = new WaveOutEvent()) + { + wo.Init(rs); + wo.Play(); + isMusicPlaying = true; + while (wo.PlaybackState == PlaybackState.Playing && isMusicPlaying) + { + Thread.Sleep(50); + } + } + } + }); + } + void LoadG1(string filename) { pgS5Header.SelectedObject = model.G1; @@ -456,6 +618,11 @@ void LoadG1(string filename) LoadDataDump(filename, true); } + void LoadMusic(string filename) + { + + } + void tv_AfterSelect(object sender, TreeViewEventArgs e) { if (e?.Node == null) diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 447d1ec8..b2a5ea1b 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -9,6 +9,7 @@ using OpenLocoToolCommon; using OpenLocoTool.Headers; using System.Diagnostics; +using OpenLocoTool; namespace OpenLocoToolGui { @@ -63,6 +64,12 @@ private void LoadPalette() public G1Dat G1 { get; set; } + public Dictionary Music { get; set; } = []; + + public Dictionary SoundEffects { get; set; } = []; + + public Dictionary Tutorials { get; set; } = []; + public MainFormModel(ILogger logger, string settingsFile) { this.logger = logger; @@ -115,7 +122,7 @@ public void LoadSettings(string settingsFile) return; } - if (File.Exists(Settings.IndexFilePath)) + if (File.Exists(Settings.GetObjDataFullPath(Settings.IndexFileName))) { logger.Info($"Loading header index from \"{Settings.IndexFileName}\""); LoadObjDirectory(Settings.ObjDataDirectory, new Progress(), true); @@ -245,7 +252,8 @@ public bool LoadDataDirectory(string directory) Settings.DataDirectory = directory; // load G1 only for now - G1 = SawyerStreamReader.LoadG1(Settings.G1Path); + G1 = SawyerStreamReader.LoadG1(Settings.GetDataFullPath(Settings.G1DatFileName)); + LoadPalette(); // update palette from g1 SaveSettings(); @@ -262,9 +270,9 @@ public void LoadObjDirectory(string directory, IProgress? progress, bool Settings.ObjDataDirectory = directory; var allFiles = Directory.GetFiles(directory, "*.dat", SearchOption.AllDirectories); - if (useExistingIndex && File.Exists(Settings.IndexFilePath)) + if (useExistingIndex && File.Exists(Settings.GetObjDataFullPath(Settings.IndexFileName))) { - HeaderIndex = DeserialiseHeaderIndexFromFile(Settings.IndexFilePath) ?? HeaderIndex; + HeaderIndex = DeserialiseHeaderIndexFromFile(Settings.GetObjDataFullPath(Settings.IndexFileName)) ?? HeaderIndex; var a = HeaderIndex.Keys.Except(allFiles); var b = allFiles.Except(HeaderIndex.Keys); @@ -288,7 +296,7 @@ public void LoadObjDirectory(string directory, IProgress? progress, bool { logger.Info("Recreating index file"); CreateIndex(allFiles, progress); - SerialiseHeaderIndexToFile(Settings.IndexFilePath, HeaderIndex, GetOptions()); + SerialiseHeaderIndexToFile(Settings.GetObjDataFullPath(Settings.IndexFileName), HeaderIndex, GetOptions()); } SaveSettings(); From d17308464ad77e9273df3d235509d271e51aa89f Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 15 Jan 2024 00:35:05 +1100 Subject: [PATCH 07/13] improve music loading --- .../DatFileParsing/SawyerStreamReader.cs | 66 ++++ OpenLocoTool/Objects/Vehicle/VehicleObject.cs | 1 - OpenLocoToolGui/GuiSettings.cs | 5 +- OpenLocoToolGui/MainForm.cs | 307 +++++++----------- OpenLocoToolGui/MainFormModel.cs | 26 +- OpenLocoToolGui/StringTableUserControl.cs | 6 +- 6 files changed, 222 insertions(+), 189 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 3eb0f0fa..f1f6e067 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -7,6 +7,58 @@ namespace OpenLocoTool.DatFileParsing { + [LocoStructSize(0x2C)] + public record MusicHeader( + [property: LocoStructOffset(0x00)] uint32_t Signature, + [property: LocoStructOffset(0x04)] uint32_t Size, + [property: LocoStructOffset(0x08)] uint32_t RiffType, + [property: LocoStructOffset(0x0C)] uint32_t FormatMarker, + [property: LocoStructOffset(0x10)] uint32_t HeaderSize, + [property: LocoStructOffset(0x14)] uint16_t FormatType, + [property: LocoStructOffset(0x16)] uint16_t Channels, + [property: LocoStructOffset(0x18)] uint32_t SampleRate, + [property: LocoStructOffset(0x1C)] uint32_t unk1, + [property: LocoStructOffset(0x20)] uint16_t unk2, + [property: LocoStructOffset(0x22)] uint16_t Bits, + [property: LocoStructOffset(0x24)] uint32_t DataMarker, + [property: LocoStructOffset(0x28)] uint32_t PcmDataLength) : ILocoStruct + { + public bool Validate() + { + if (Signature != 0x46464952) // "RIFF" + { + return false; + } + + if (RiffType != 0x45564157) // "WAVE" + { + return false; + } + + if (FormatMarker != 0x20746d66 && FormatMarker != 0x00746d66) // "fmt\0" or "fmt" + { + return false; + } + + if (FormatType != 1) // expected PCM + { + return false; + } + + if (Bits != 16) + { + return false; + } + + if (DataMarker != 0x61746164) + { + return false; + } + + return true; + } + } + public static class SawyerStreamReader { public static List LoadVariableCountS5Headers(ReadOnlySpan data, int count) @@ -367,6 +419,20 @@ public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan throw new InvalidDataException("Unknown chunk encoding scheme"), }; + public static (MusicHeader header, byte[] data) LoadMusicTrack(byte[] data) + { + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + var headerBytes = br.ReadBytes(ObjectAttributes.StructSize()); + var header = ByteReader.ReadLocoStruct(headerBytes); + + var pcmData = new byte[header.PcmDataLength]; + br.Read(pcmData); + return (header, pcmData); + } + } + // taken from openloco SawyerStreamReader::decodeRunLengthSingle private static byte[] DecodeRunLengthSingle(byte[] data) { diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs index 63ae03fc..9e2e4601 100644 --- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs +++ b/OpenLocoTool/Objects/Vehicle/VehicleObject.cs @@ -1,5 +1,4 @@ using System.ComponentModel; -using System.Diagnostics; using OpenLocoTool.DatFileParsing; using OpenLocoTool.Headers; diff --git a/OpenLocoToolGui/GuiSettings.cs b/OpenLocoToolGui/GuiSettings.cs index c6ace3f4..7a430a55 100644 --- a/OpenLocoToolGui/GuiSettings.cs +++ b/OpenLocoToolGui/GuiSettings.cs @@ -1,7 +1,4 @@ - -using System.Text.Json.Serialization; - -namespace OpenLocoToolGui +namespace OpenLocoToolGui { public class GuiSettings { diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index e5bc436b..6e4380cc 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -5,9 +5,7 @@ using OpenLocoTool.Headers; using OpenLocoTool.Objects; using OpenLocoToolCommon; -using System; using System.Drawing.Imaging; -using System.Reflection.PortableExecutable; namespace OpenLocoToolGui { @@ -225,131 +223,118 @@ void InitCategoryTreeView(bool vanillaOnly, string fileFilter) if (Directory.Exists(model.Settings.DataDirectory)) { - var dataNode = new TreeNode("Data"); + InitDataCategoryTree(); + } - var musicNode = new TreeNode("Music"); - var sfxNode = new TreeNode("Sound Effects"); - var tutorialsNode = new TreeNode("Tutorials"); - var miscNode = new TreeNode("Uncategorised"); + if (Directory.Exists(model.Settings.ObjDataDirectory)) + { + InitObjectCategoryTree(vanillaOnly, fileFilter); + } - var unknownNode = new TreeNode("Unknown"); + tvObjType.ResumeLayout(true); + } - var allDataFiles = Directory.GetFiles(model.Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower()); + private void InitDataCategoryTree() + { + var dataNode = new TreeNode("Data"); - // music - var expectedMusicFiles = OriginalDataFiles.Music.Select(f => f.ToLower().Replace("data/", string.Empty)); - foreach (var music in expectedMusicFiles) - { - if (allDataFiles.Any(f => f.EndsWith(music))) - { - musicNode.Nodes.Add(music, music); - tvUniqueLoadValues[music] = LoadBinaryMusic; - } - else - { - musicNode.Nodes.Add($" {music}"); - } - } + var musicNode = new TreeNode("Music"); + var sfxNode = new TreeNode("Sound Effects"); + var tutorialsNode = new TreeNode("Tutorials"); + var miscNode = new TreeNode("Uncategorised"); - // sound effects - var expectedSfxFiles = OriginalDataFiles.SoundEffects.Select(f => f.ToLower().Replace("data/", string.Empty)); - foreach (var sfx in expectedSfxFiles) - { - if (allDataFiles.Any(f => f.EndsWith(sfx))) - { - sfxNode.Nodes.Add(sfx); - } - else - { - sfxNode.Nodes.Add($" {sfx}"); - } - } + //var unknownNode = new TreeNode("Unknown"); - // tutorials - var expectedTutorialFiles = OriginalDataFiles.Tutorials.Select(f => f.ToLower().Replace("data/", string.Empty)); - foreach (var tut in expectedTutorialFiles) + var allDataFiles = Directory.GetFiles(model.Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower()); + + // music + foreach (var music in model.Music) + { + musicNode.Nodes.Add(music.Key, music.Key, 1, 1); + tvUniqueLoadValues[music.Key] = LoadMusic; + } + + // sound effects + foreach (var sfx in model.SoundEffects) + { + sfxNode.Nodes.Add(sfx.Key, sfx.Key, 1, 1); + tvUniqueLoadValues[sfx.Key] = LoadNull; + } + + // tutorials + foreach (var tut in model.Tutorials) + { + tutorialsNode.Nodes.Add(tut.Key, tut.Key, 1, 1); + tvUniqueLoadValues[tut.Key] = LoadNull; + } + + // uncategorised + foreach (var misc in model.MiscFiles) + { + miscNode.Nodes.Add(misc, misc, 1, 1); + + if (misc == "g1.dat") { - if (allDataFiles.Any(f => f.EndsWith(tut))) - { - tutorialsNode.Nodes.Add(tut); - } - else - { - tutorialsNode.Nodes.Add($" {tut}"); - } + tvUniqueLoadValues["g1.dat"] = LoadG1; } - - // uncategorised - var expectedMiscFiles = OriginalDataFiles.Uncategorised.Select(f => f.ToLower().Replace("data/", string.Empty)); - foreach (var tut in expectedMiscFiles) + else { - if (allDataFiles.Any(f => f.EndsWith(tut))) - { - miscNode.Nodes.Add(tut); - } - else - { - miscNode.Nodes.Add($" {tut}"); - } + tvUniqueLoadValues[misc] = LoadNull; } + } - dataNode.Nodes.Add(musicNode); - dataNode.Nodes.Add(sfxNode); - dataNode.Nodes.Add(tutorialsNode); - dataNode.Nodes.Add(miscNode); - - tvObjType.Nodes.Add(dataNode); + dataNode.Nodes.Add(musicNode); + dataNode.Nodes.Add(sfxNode); + dataNode.Nodes.Add(tutorialsNode); + dataNode.Nodes.Add(miscNode); - tvUniqueLoadValues["g1.dat"] = LoadG1; - } + tvObjType.Nodes.Add(dataNode); + } - if (Directory.Exists(model.Settings.ObjDataDirectory)) - { - var filteredFiles = string.IsNullOrEmpty(fileFilter) - ? model.HeaderIndex - : model.HeaderIndex.Where(hdr => hdr.Key.Contains(fileFilter, StringComparison.InvariantCultureIgnoreCase)); + private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter) + { + var filteredFiles = string.IsNullOrEmpty(fileFilter) + ? model.HeaderIndex + : model.HeaderIndex.Where(hdr => hdr.Key.Contains(fileFilter, StringComparison.InvariantCultureIgnoreCase)); - filteredFiles = filteredFiles.Where(f => !vanillaOnly || OriginalObjectFiles.Names.Contains(f.Value.Name.Trim())); + filteredFiles = filteredFiles.Where(f => !vanillaOnly || OriginalObjectFiles.Names.Contains(f.Value.Name.Trim())); - tvObjType.ImageList = MakeImageList(model); + tvObjType.ImageList = MakeImageList(model); - var nodesToAdd = new List(); - foreach (var group in filteredFiles.GroupBy(kvp => kvp.Value.ObjectType)) + var nodesToAdd = new List(); + foreach (var group in filteredFiles.GroupBy(kvp => kvp.Value.ObjectType)) + { + var imageListOffset = model.G1 == null ? 0 : ((int)group.Key) + 2; // + 2 because we have a vanilla+custom image first + var objTypeNode = new TreeNode(group.Key.ToString(), imageListOffset, imageListOffset); + if (group.Key != ObjectType.Vehicle) { - var imageListOffset = model.G1 == null ? 0 : ((int)group.Key) + 2; // + 2 because we have a vanilla+custom image first - var objTypeNode = new TreeNode(group.Key.ToString(), imageListOffset, imageListOffset); - if (group.Key != ObjectType.Vehicle) + foreach (var obj in group) { - foreach (var obj in group) - { - AddObjectNode(obj.Key, obj.Value.Name, obj.Value.Name, objTypeNode); - } + AddObjectNode(obj.Key, obj.Value.Name, obj.Value.Name, objTypeNode); } - else + } + else + { + var vehicleGroup = group.GroupBy(o => o.Value.VehicleType); + foreach (var vehicleType in vehicleGroup) { - var vehicleGroup = group.GroupBy(o => o.Value.VehicleType); - foreach (var vehicleType in vehicleGroup) + var vehicleTypeNode = new TreeNode(vehicleType.Key.ToString()); + foreach (var veh in vehicleType) { - var vehicleTypeNode = new TreeNode(vehicleType.Key.ToString()); - foreach (var veh in vehicleType) - { - AddObjectNode(veh.Key, veh.Value.Name, veh.Value.Name, vehicleTypeNode); - } - - objTypeNode.Nodes.Add(vehicleTypeNode); + AddObjectNode(veh.Key, veh.Value.Name, veh.Value.Name, vehicleTypeNode); } - } - nodesToAdd.Add(objTypeNode); + objTypeNode.Nodes.Add(vehicleTypeNode); + } } - var objDataNode = new TreeNode("ObjData"); - objDataNode.Nodes.AddRange([.. nodesToAdd]); - tvObjType.Nodes.Add(objDataNode); - tvObjType.Sort(); + nodesToAdd.Add(objTypeNode); } - tvObjType.ResumeLayout(true); + var objDataNode = new TreeNode("ObjData"); + objDataNode.Nodes.AddRange([.. nodesToAdd]); + tvObjType.Nodes.Add(objDataNode); + tvObjType.Sort(); } void InitToolStripMenuItems() @@ -512,25 +497,25 @@ static string constructAnnotationText(Annotation annotation) } } - void LoadBinaryMusic(string filename) + static bool isMusicPlaying { get; set; } = false; + + private void LoadMusic(string dataKey) { - var path = model.Settings.GetDataFullPath(filename); - if (!File.Exists(path)) + var music = model.Music[dataKey]; + var (header, pcmData) = SawyerStreamReader.LoadMusicTrack(music); + + pgS5Header.SelectedObject = header; + + if (!header.Validate()) { + // invalid music file return; } - var bytes = File.ReadAllBytes(path); - - model.Music.Add(filename, bytes); - - // play as music to test - PlayMusic(bytes); + PlayMusic(header, pcmData); } - static bool isMusicPlaying; - - private static void PlayMusic(byte[] bytes) + private static void PlayMusic(MusicHeader header, byte[] pcmData) { if (isMusicPlaying) { @@ -540,76 +525,27 @@ private static void PlayMusic(byte[] bytes) _ = Task.Run(() => { - using (var ms = new MemoryStream(bytes)) + using (var ms2 = new MemoryStream(pcmData)) + using (var rs = new RawSourceWaveStream(ms2, new WaveFormat((int)header.SampleRate, header.Bits, header.Channels))) + using (var wo = new WaveOutEvent()) { - var br = new BinaryReader(ms); - var sig = br.ReadUInt32(); - if (sig != 0x46464952) // "RIFF" - { - // invalid signature - return; - } - - _ = br.ReadUInt32(); // size - - var riffType = br.ReadUInt32(); - if (riffType != 0x45564157) // "WAVE" - { - // invalid format - return; - } - - var formatMarker = br.ReadUInt32(); - if (formatMarker != 0x20746d66 && formatMarker != 0x00746d66) // "fmt\0" or "fmt" - { - // invalid format marker - return; - } - - _ = br.ReadUInt32(); // headersize - - var typeFormat = br.ReadUInt16(); - if (typeFormat != 1) - { - // invalid format type; expected PCM - return; - } - - var channels = br.ReadUInt16(); - var sampleRate = br.ReadUInt32(); - - _ = br.ReadUInt32(); - _ = br.ReadUInt16(); - - var bits = br.ReadUInt16(); - - var dataMarker = br.ReadUInt32(); - if (dataMarker != 0x61746164) // data + wo.Init(rs); + wo.Play(); + isMusicPlaying = true; + while (wo.PlaybackState == PlaybackState.Playing && isMusicPlaying) { - // invalid data marker - return; - } - - var pcmLength = br.ReadUInt32(); - var pcmData = new byte[pcmLength]; - br.Read(pcmData); - - using (var ms2 = new MemoryStream(pcmData)) - using (var rs = new RawSourceWaveStream(ms2, new WaveFormat((int)sampleRate, 16, channels))) - using (var wo = new WaveOutEvent()) - { - wo.Init(rs); - wo.Play(); - isMusicPlaying = true; - while (wo.PlaybackState == PlaybackState.Playing && isMusicPlaying) - { - Thread.Sleep(50); - } + Thread.Sleep(50); } } }); } + void LoadNull(string dataKey) + { + CurrentUIObject = null; + CurrentUIImages = new List(); + } + void LoadG1(string filename) { pgS5Header.SelectedObject = model.G1; @@ -618,11 +554,6 @@ void LoadG1(string filename) LoadDataDump(filename, true); } - void LoadMusic(string filename) - { - - } - void tv_AfterSelect(object sender, TreeViewEventArgs e) { if (e?.Node == null) @@ -830,17 +761,29 @@ private void setPaletteToolStripMenuItem_Click(object sender, EventArgs e) private void RefreshObjectUI() { - flpImageTable.SuspendLayout(); - flpImageTable.Controls.Clear(); + isMusicPlaying = false; if (CurrentUIObject == null) { + pgS5Header.SelectedObject = null; + pgObjHeader.SelectedObject = null; + pgObject.SelectedObject = null; + ucStringTable.SetDataBinding(null); + + if (tvFileTree.SelectedNode == null) + { + return; + } + var selectedFile = tvFileTree.SelectedNode.Text; MessageBox.Show($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index."); logger.Error($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index."); return; } + flpImageTable.SuspendLayout(); + flpImageTable.Controls.Clear(); + if (CurrentUIObject.LocoObject.G1Elements != null && CurrentUIObject.LocoObject?.G1Elements.Count != 0) { if (model.Palette is null) diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index b2a5ea1b..34341769 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -70,6 +70,8 @@ private void LoadPalette() public Dictionary Tutorials { get; set; } = []; + public List MiscFiles { get; set; } = []; + public MainFormModel(ILogger logger, string settingsFile) { this.logger = logger; @@ -251,7 +253,29 @@ public bool LoadDataDirectory(string directory) Settings.DataDirectory = directory; - // load G1 only for now + var allDataFiles = Directory.GetFiles(Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower()).ToHashSet(); + + void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames, Dictionary dict) + { + var expectedMusicFiles = knownFilenames.Select(f => f.ToLower().Replace("data/", string.Empty)); + foreach (var music in expectedMusicFiles) + { + var matching = allFilesInDir.Where(f => f.EndsWith(music)); + if (matching.Any()) + { + dict.Add(music, File.ReadAllBytes(Path.Combine(Settings.DataDirectory, music))); + allFilesInDir.RemoveWhere(f => f.EndsWith(music)); + } + } + } + + LoadKnownData(allDataFiles, OriginalDataFiles.Music, Music); + LoadKnownData(allDataFiles, OriginalDataFiles.SoundEffects, SoundEffects); + LoadKnownData(allDataFiles, OriginalDataFiles.Tutorials, Tutorials); + + MiscFiles = [.. allDataFiles]; + + // load G1 only for now since we need it for palette G1 = SawyerStreamReader.LoadG1(Settings.GetDataFullPath(Settings.G1DatFileName)); LoadPalette(); // update palette from g1 diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/OpenLocoToolGui/StringTableUserControl.cs index fad1ce4f..d8002593 100644 --- a/OpenLocoToolGui/StringTableUserControl.cs +++ b/OpenLocoToolGui/StringTableUserControl.cs @@ -1,6 +1,5 @@ using OpenLocoTool; using OpenLocoTool.Types; -using System.ComponentModel; namespace OpenLocoToolGui { @@ -15,6 +14,11 @@ public void SetDataBinding(StringTable data) _data = data; lbStringSelector.DataSource = null; + if (data == null) + { + return; + } + // Set up data binding for the outer dictionary DataGridView. lbStringSelector.DataSource = data.Table.Keys.ToList(); From 85bc539d6c38cf1d18aa131415d18e13632362b0 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 15 Jan 2024 01:49:26 +1100 Subject: [PATCH 08/13] add scuffed audio rendering --- .../DatFileParsing/SawyerStreamReader.cs | 6 +- OpenLocoTool/Objects/SoundObject.cs | 4 +- OpenLocoToolGui/MainForm.cs | 174 ++++++++++++------ 3 files changed, 127 insertions(+), 57 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index f1f6e067..95eb1565 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -15,11 +15,11 @@ public record MusicHeader( [property: LocoStructOffset(0x0C)] uint32_t FormatMarker, [property: LocoStructOffset(0x10)] uint32_t HeaderSize, [property: LocoStructOffset(0x14)] uint16_t FormatType, - [property: LocoStructOffset(0x16)] uint16_t Channels, + [property: LocoStructOffset(0x16)] uint16_t NumberOfChannels, [property: LocoStructOffset(0x18)] uint32_t SampleRate, [property: LocoStructOffset(0x1C)] uint32_t unk1, [property: LocoStructOffset(0x20)] uint16_t unk2, - [property: LocoStructOffset(0x22)] uint16_t Bits, + [property: LocoStructOffset(0x22)] uint16_t BitsPerSample, [property: LocoStructOffset(0x24)] uint32_t DataMarker, [property: LocoStructOffset(0x28)] uint32_t PcmDataLength) : ILocoStruct { @@ -45,7 +45,7 @@ public bool Validate() return false; } - if (Bits != 16) + if (BitsPerSample != 16) { return false; } diff --git a/OpenLocoTool/Objects/SoundObject.cs b/OpenLocoTool/Objects/SoundObject.cs index 09cbd2f1..b405793f 100644 --- a/OpenLocoTool/Objects/SoundObject.cs +++ b/OpenLocoTool/Objects/SoundObject.cs @@ -8,8 +8,8 @@ namespace OpenLocoTool.Objects [LocoStructSize(0x12)] public record WaveFormatEx( [property: LocoStructOffset(0x00)] int16_t WaveFormatTag, - [property: LocoStructOffset(0x02)] int16_t NumberChannels, - [property: LocoStructOffset(0x04)] int32_t SamplesPerSecond, + [property: LocoStructOffset(0x02)] int16_t NumberOfChannels, + [property: LocoStructOffset(0x04)] int32_t SampleRate, [property: LocoStructOffset(0x08)] int32_t AverageBytesPerSecond, [property: LocoStructOffset(0x0B)] int16_t BlockAlign, [property: LocoStructOffset(0x0D)] int16_t BitsPerSample, diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 6e4380cc..5baec58b 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -1,3 +1,4 @@ +using NAudio.Gui; using NAudio.Wave; using OpenLocoTool; using OpenLocoTool.Data; @@ -6,6 +7,7 @@ using OpenLocoTool.Objects; using OpenLocoToolCommon; using System.Drawing.Imaging; +using System.Xml.Linq; namespace OpenLocoToolGui { @@ -497,9 +499,9 @@ static string constructAnnotationText(Annotation annotation) } } - static bool isMusicPlaying { get; set; } = false; + static bool MusicIsPlaying { get; set; } = false; - private void LoadMusic(string dataKey) + void LoadMusic(string dataKey) { var music = model.Music[dataKey]; var (header, pcmData) = SawyerStreamReader.LoadMusicTrack(music); @@ -515,31 +517,135 @@ private void LoadMusic(string dataKey) PlayMusic(header, pcmData); } - private static void PlayMusic(MusicHeader header, byte[] pcmData) + void PlayMusic(MusicHeader hdr, byte[] pcmData) { - if (isMusicPlaying) + CreateSoundUI(pcmData, (int)hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); + } + + void CreateSounds(SoundObject soundObject) + { + var hdr = soundObject.SoundObjectData.PcmHeader; + CreateSoundUI(soundObject.RawPcmData, hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); + } + + void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfChannels) + { + flpImageTable.SuspendLayout(); + flpImageTable.Controls.Clear(); + + // for some reason the SoundObject files have the wrong bitspersample set + if (bits != 16) { - isMusicPlaying = false; + bits = 16; + } + + if (MusicIsPlaying) + { + MusicIsPlaying = false; Thread.Sleep(100); } - _ = Task.Run(() => + var playButton = new Button + { + Size = new Size(64, 64), + Text = "Play", + }; + + var stopButton = new Button + { + Size = new Size(64, 64), + Text = "Stop", + }; + stopButton.Click += (args, sender) => CurrentWOEvent?.Stop(); + + var pauseButton = new Button { - using (var ms2 = new MemoryStream(pcmData)) - using (var rs = new RawSourceWaveStream(ms2, new WaveFormat((int)header.SampleRate, header.Bits, header.Channels))) - using (var wo = new WaveOutEvent()) + Size = new Size(64, 64), + Text = "Pause", + }; + pauseButton.Click += (args, sender) => CurrentWOEvent?.Pause(); + + var waveViewer = new WaveViewer + { + BorderStyle = BorderStyle.FixedSingle, + WaveStream = new RawSourceWaveStream(new MemoryStream(pcmData), new WaveFormat(samplesPerSecond, bits, numberOfChannels)), + Size = new Size(1024, 128), + }; + waveViewer.SamplesPerPixel = pcmData.Length / waveViewer.Width / numberOfChannels / 2, // dunno why i need /2 + + playButton.Click += (args, sender) => + { + if (CurrentWOEvent != null) { - wo.Init(rs); - wo.Play(); - isMusicPlaying = true; - while (wo.PlaybackState == PlaybackState.Playing && isMusicPlaying) + if (CurrentWOEvent.PlaybackState == PlaybackState.Playing) + { + return; + } + + if (CurrentWOEvent.PlaybackState == PlaybackState.Paused) { - Thread.Sleep(50); + CurrentWOEvent.Play(); + return; } } - }); + + // do it asyncly to a) give user ui control and b) allow multiple sounds to play at once + _ = Task.Run(() => + { + if (CurrentWOEvent?.PlaybackState == PlaybackState.Stopped) + { + Thread.Sleep(100); // give time to wait until previous sound is disposed + } + CurrentWOEvent?.Dispose(); + + using (var ms = new MemoryStream(pcmData)) + using (var rs = new RawSourceWaveStream(ms, new WaveFormat(samplesPerSecond, bits, numberOfChannels))) + using (CurrentWOEvent = new WaveOutEvent()) + using (var transparentBrush = new SolidBrush(Color.FromArgb(27, 0, 0, 0))) + { + var g = waveViewer.CreateGraphics(); + + // clear the previously-drawn progress overlay + g.Clear(Color.White); + waveViewer.Invoke(() => waveViewer.Refresh()); + + MusicIsPlaying = true; + CurrentWOEvent.Init(rs); + CurrentWOEvent.Play(); + + var prevX = 0; + + while (CurrentWOEvent?.PlaybackState != PlaybackState.Stopped && MusicIsPlaying) + { + if (CurrentWOEvent == null) + { + break; + } + + var progressInBytes = CurrentWOEvent.GetPosition(); + var percentPlayed = progressInBytes / (double)pcmData.Length; + var newX = (int)(percentPlayed * waveViewer.Width); + var diff = newX - prevX; + g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, (int)diff, waveViewer.Height)); + prevX = newX; + + Thread.Sleep(50); + } + } + CurrentWOEvent = null; + MusicIsPlaying = false; + }); + }; + + flpImageTable.Controls.Add(waveViewer); + flpImageTable.Controls.Add(playButton); + flpImageTable.Controls.Add(pauseButton); + flpImageTable.Controls.Add(stopButton); + flpImageTable.ResumeLayout(true); } + WaveOutEvent? CurrentWOEvent { get; set; } + void LoadNull(string dataKey) { CurrentUIObject = null; @@ -582,42 +688,6 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e) } } - void CreateSounds(SoundObject soundObject) - { - flpImageTable.SuspendLayout(); - flpImageTable.Controls.Clear(); - - var pcmHeader = soundObject.SoundObjectData.PcmHeader; - - var soundButton = new Button - { - Size = new Size(100, 100), - Text = "Play sound", - }; - - soundButton.Click += (args, sender) => - { - // do it asyncly to a) give user ui control and b) allow multiple sounds to play at once - Task.Run(() => - { - using (var ms = new MemoryStream(soundObject.RawPcmData)) - using (var rs = new RawSourceWaveStream(ms, new WaveFormat(pcmHeader.SamplesPerSecond, 16, pcmHeader.NumberChannels))) - using (var wo = new WaveOutEvent()) - { - wo.Init(rs); - wo.Play(); - while (wo.PlaybackState == PlaybackState.Playing) - { - Thread.Sleep(50); - } - } - }); - }; - - flpImageTable.Controls.Add(soundButton); - flpImageTable.ResumeLayout(true); - } - IEnumerable CreateImageControls(IEnumerable images, List g1Elements) // g1Elements is simply used for metadata at this stage { // on these controls we could add a right_click handler to replace image with user-created one @@ -761,7 +831,7 @@ private void setPaletteToolStripMenuItem_Click(object sender, EventArgs e) private void RefreshObjectUI() { - isMusicPlaying = false; + MusicIsPlaying = false; if (CurrentUIObject == null) { From 9a9cc84113aa4e3a187a2ec707fa05b1607cbf9f Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 15 Jan 2024 02:07:44 +1100 Subject: [PATCH 09/13] figured out the /2 is the bytes per sample --- OpenLocoToolGui/MainForm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 5baec58b..cca511ae 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -571,7 +571,7 @@ void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfC WaveStream = new RawSourceWaveStream(new MemoryStream(pcmData), new WaveFormat(samplesPerSecond, bits, numberOfChannels)), Size = new Size(1024, 128), }; - waveViewer.SamplesPerPixel = pcmData.Length / waveViewer.Width / numberOfChannels / 2, // dunno why i need /2 + waveViewer.SamplesPerPixel = pcmData.Length / waveViewer.Width / numberOfChannels / (bits / 8); playButton.Click += (args, sender) => { From fc847d7a2f8669ac809cb0d00919a47a6f1db1e4 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Mon, 15 Jan 2024 02:18:39 +1100 Subject: [PATCH 10/13] clean up riff code --- .../DatFileParsing/SawyerStreamReader.cs | 59 ++----------------- OpenLocoTool/Types/RiffWavHeader.cs | 57 ++++++++++++++++++ OpenLocoToolGui/MainForm.cs | 3 +- 3 files changed, 63 insertions(+), 56 deletions(-) create mode 100644 OpenLocoTool/Types/RiffWavHeader.cs diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 95eb1565..292e0cfb 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -7,57 +7,6 @@ namespace OpenLocoTool.DatFileParsing { - [LocoStructSize(0x2C)] - public record MusicHeader( - [property: LocoStructOffset(0x00)] uint32_t Signature, - [property: LocoStructOffset(0x04)] uint32_t Size, - [property: LocoStructOffset(0x08)] uint32_t RiffType, - [property: LocoStructOffset(0x0C)] uint32_t FormatMarker, - [property: LocoStructOffset(0x10)] uint32_t HeaderSize, - [property: LocoStructOffset(0x14)] uint16_t FormatType, - [property: LocoStructOffset(0x16)] uint16_t NumberOfChannels, - [property: LocoStructOffset(0x18)] uint32_t SampleRate, - [property: LocoStructOffset(0x1C)] uint32_t unk1, - [property: LocoStructOffset(0x20)] uint16_t unk2, - [property: LocoStructOffset(0x22)] uint16_t BitsPerSample, - [property: LocoStructOffset(0x24)] uint32_t DataMarker, - [property: LocoStructOffset(0x28)] uint32_t PcmDataLength) : ILocoStruct - { - public bool Validate() - { - if (Signature != 0x46464952) // "RIFF" - { - return false; - } - - if (RiffType != 0x45564157) // "WAVE" - { - return false; - } - - if (FormatMarker != 0x20746d66 && FormatMarker != 0x00746d66) // "fmt\0" or "fmt" - { - return false; - } - - if (FormatType != 1) // expected PCM - { - return false; - } - - if (BitsPerSample != 16) - { - return false; - } - - if (DataMarker != 0x61746164) - { - return false; - } - - return true; - } - } public static class SawyerStreamReader { @@ -419,15 +368,15 @@ public static ILocoStruct GetLocoStruct(ObjectType objectType, ReadOnlySpan throw new InvalidDataException("Unknown chunk encoding scheme"), }; - public static (MusicHeader header, byte[] data) LoadMusicTrack(byte[] data) + public static (RiffWavHeader header, byte[] data) LoadMusicTrack(byte[] data) { using (var ms = new MemoryStream(data)) using (var br = new BinaryReader(ms)) { - var headerBytes = br.ReadBytes(ObjectAttributes.StructSize()); - var header = ByteReader.ReadLocoStruct(headerBytes); + var headerBytes = br.ReadBytes(ObjectAttributes.StructSize()); + var header = ByteReader.ReadLocoStruct(headerBytes); - var pcmData = new byte[header.PcmDataLength]; + var pcmData = new byte[header.DataLength]; br.Read(pcmData); return (header, pcmData); } diff --git a/OpenLocoTool/Types/RiffWavHeader.cs b/OpenLocoTool/Types/RiffWavHeader.cs new file mode 100644 index 00000000..2fbf78e7 --- /dev/null +++ b/OpenLocoTool/Types/RiffWavHeader.cs @@ -0,0 +1,57 @@ +using OpenLocoTool.DatFileParsing; + +namespace OpenLocoTool.Types +{ + // http://www.topherlee.com/software/pcm-tut-wavformat.html#:~:text=The%20header%20is%20used%20to,well%20as%20its%20overall%20length.&text=Marks%20the%20file%20as%20a,are%20each%201%20byte%20long.&text=Size%20of%20the%20overall%20file,(32%2Dbit%20integer) + [LocoStructSize(0x2C)] + public record RiffWavHeader( + [property: LocoStructOffset(0x00)] uint32_t Signature, // ascii, "RIFF" + [property: LocoStructOffset(0x04)] uint32_t Size, + [property: LocoStructOffset(0x08)] uint32_t RiffType, // ascii, eg "WAVE" + [property: LocoStructOffset(0x0C)] uint32_t FormatMarker, // either "fmt" or "fmt\0" + [property: LocoStructOffset(0x10)] uint32_t FormatSize, + [property: LocoStructOffset(0x14)] uint16_t FormatType, // 1 is PCM + [property: LocoStructOffset(0x16)] uint16_t NumberOfChannels, + [property: LocoStructOffset(0x18)] uint32_t SampleRate, + [property: LocoStructOffset(0x1C)] uint32_t unk1, // SampleRate * BitsPerSample * Channels / 8 + [property: LocoStructOffset(0x20)] uint16_t unk2, // (BitsPerSample* Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo + [property: LocoStructOffset(0x22)] uint16_t BitsPerSample, + [property: LocoStructOffset(0x24)] uint32_t DataMarker, + [property: LocoStructOffset(0x28)] uint32_t DataLength) : ILocoStruct + { + public bool Validate() + { + if (Signature != 0x46464952) // "RIFF" + { + return false; + } + + if (RiffType != 0x45564157) // "WAVE" + { + return false; + } + + if (FormatMarker != 0x20746d66 && FormatMarker != 0x00746d66) // "fmt\0" or "fmt" + { + return false; + } + + if (FormatType != 1) // expected PCM + { + return false; + } + + if (BitsPerSample != 16) + { + return false; + } + + if (DataMarker != 0x61746164) + { + return false; + } + + return true; + } + } +} diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index cca511ae..92ae34a1 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -5,6 +5,7 @@ using OpenLocoTool.DatFileParsing; using OpenLocoTool.Headers; using OpenLocoTool.Objects; +using OpenLocoTool.Types; using OpenLocoToolCommon; using System.Drawing.Imaging; using System.Xml.Linq; @@ -517,7 +518,7 @@ void LoadMusic(string dataKey) PlayMusic(header, pcmData); } - void PlayMusic(MusicHeader hdr, byte[] pcmData) + void PlayMusic(RiffWavHeader hdr, byte[] pcmData) { CreateSoundUI(pcmData, (int)hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); } From 8dc8ad77b09cac94fa07c0cd164bf342541c2822 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Tue, 16 Jan 2024 17:27:48 +1100 Subject: [PATCH 11/13] add loading of misc tracks and sound effects file (css1) --- .../DatFileParsing/SawyerStreamReader.cs | 28 +++++ OpenLocoTool/Data/OriginalDataFiles.cs | 111 ++++++++-------- OpenLocoToolGui/MainForm.Designer.cs | 10 +- OpenLocoToolGui/MainForm.cs | 119 +++++++++++++----- OpenLocoToolGui/MainFormModel.cs | 9 +- 5 files changed, 184 insertions(+), 93 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs index 292e0cfb..e31aaf7d 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs @@ -382,6 +382,34 @@ public static (RiffWavHeader header, byte[] data) LoadMusicTrack(byte[] data) } } + public static List<(WaveFormatEx header, byte[] data)> LoadSoundEffectsFromCSS(byte[] data) + { + var result = new List<(WaveFormatEx, byte[])>(); + + using (var ms = new MemoryStream(data)) + using (var br = new BinaryReader(ms)) + { + var numSounds = br.ReadUInt32(); + var soundOffsets = new uint32_t[numSounds]; + for (var i = 0; i < numSounds; ++i) + { + soundOffsets[i] = br.ReadUInt32(); + } + + for (var i = 0; i < numSounds; ++i) + { + br.BaseStream.Position = soundOffsets[i]; + var pcmLen = br.ReadUInt32(); + var format = ByteReader.ReadLocoStruct(br.ReadBytes(ObjectAttributes.StructSize())); + + var pcmData = br.ReadBytes((int)pcmLen); + result.Add((format, pcmData)); + } + } + + return result; + } + // taken from openloco SawyerStreamReader::decodeRunLengthSingle private static byte[] DecodeRunLengthSingle(byte[] data) { diff --git a/OpenLocoTool/Data/OriginalDataFiles.cs b/OpenLocoTool/Data/OriginalDataFiles.cs index b8707d1d..b118fe23 100644 --- a/OpenLocoTool/Data/OriginalDataFiles.cs +++ b/OpenLocoTool/Data/OriginalDataFiles.cs @@ -2,75 +2,80 @@ { public static class OriginalDataFiles { - public static readonly HashSet Music = - [ - "Data/20s1.DAT", - "Data/20s2.DAT", - "Data/20s4.DAT", - "Data/50s1.DAT", - "Data/50s2.DAT", - "Data/70s1.DAT", - "Data/70s2.DAT", - "Data/70s3.DAT", - "Data/80s1.DAT", - "Data/90s1.DAT", - "Data/90s2.DAT", - "Data/rag3.DAT", - "Data/Chrysanthemum.DAT", - "Data/Eugenia.DAT", - "Data/Rag2.DAT", - "Data/Rag1.DAT", - "Data/20s3.DAT", - "Data/40s1.DAT", - "Data/40s2.DAT", - "Data/50s3.DAT", - "Data/40s3.DAT", - "Data/80s2.DAT", - "Data/60s1.DAT", - "Data/80s3.DAT", - "Data/60s2.DAT", - "Data/60s3.DAT", - "Data/80s4.DAT", - "Data/20s5.DAT", - "Data/20s6.DAT", - ]; + public static readonly Dictionary Music = new() + { + { "chrysanthemum.dat", "Chrysanthemum" }, + { "eugenia.dat", "Eugenia" }, + { "rag1.dat", "Easy Winners" }, + { "rag2.dat", "The Ragtime Dance" }, + { "rag3.dat", "Solace" }, + // + { "20s1.dat", "Chuggin' Along" }, + { "20s2.dat", "Long Dusty Road" }, + { "20s3.dat", "Setting Off" }, + { "20s4.dat", "Flying High" }, + { "20s5.dat", "Get Me To Gladstone Bay" }, + { "20s6.dat", "Sandy Track Blues" }, + { "40s1.dat", "A Traveller's Serenade" }, // in loco its misspelt - Seranade + { "40s2.dat", "Latino Trip" }, + { "40s3.dat", "Head To The Bop" }, + { "50s1.dat", "Gettin' On The Gas" }, + { "50s2.dat", "Jumpin' The Rails" }, + { "50s3.dat", "A Good Head Of Steam" }, + { "60s1.dat", "Steamin' Down Town" }, + { "60s2.dat", "Mo' Station" }, + { "60s3.dat", "Far Out" }, + { "70s1.dat", "Smooth Running" }, + { "70s2.dat", "Traffic Jam" }, + { "70s3.dat", "Never Stop 'til You Get There" }, + { "80s1.dat", "Soaring Away" }, + { "80s2.dat", "The City Lights" }, + { "80s3.dat", "Bright Expectations" }, + { "80s4.dat", "Running On Time" }, + { "90s1.dat", "Techno Torture" }, + { "90s2.dat", "Everlasting High-Rise" }, + }; - public static readonly HashSet SoundEffects = - [ - "Data/CSS1.DAT", - "Data/CSS2.DAT", - "Data/CSS3.DAT", - "Data/CSS4.DAT", - "Data/CSS5.DAT", - ]; + public static readonly Dictionary SoundEffects = new() + { + { "css1.dat", "Sound Effects" }, + }; + + public static readonly Dictionary MiscellaneousTracks = new() + { + { "css2.dat", "Ambient Wind" }, + { "css3.dat", "Ambient Ocean" }, + { "css4.dat", "Ambient Forest" }, + { "css5.dat", "Main Theme" }, + }; public static readonly HashSet Tutorials = [ - "Data/TUT1024_1.DAT", - "Data/TUT1024_2.DAT", - "Data/TUT1024_3.DAT", - "Data/TUT800_1.DAT", - "Data/TUT800_2.DAT", - "Data/TUT800_3.DAT", + "tut1024_1.dat", + "tut1024_2.dat", + "tut1024_3.dat", + "tut800_1.dat", + "tut800_2.dat", + "tut800_3.dat", ]; public static readonly HashSet Uncategorised = [ - "Data/g1.DAT", + "data/g1.dat", "plugin.dat", "plugin2.dat", "game.cfg", - "Data/KANJI.DAT", - "Data/title.dat", + "data/kanji.dat", + "data/title.dat", "scores.dat", - //"Scenarios/Boulder Breakers.SC5", + //"scenarios/boulder breakers.sc5", //"openloco.yml", //"language", //"save", //"save/autosave", - //"1.TMP", - //"ObjData", - //"Scenarios", + //"1.tmp", + //"objdata", + //"scenarios", ]; } } diff --git a/OpenLocoToolGui/MainForm.Designer.cs b/OpenLocoToolGui/MainForm.Designer.cs index cb7e4cae..4586c125 100644 --- a/OpenLocoToolGui/MainForm.Designer.cs +++ b/OpenLocoToolGui/MainForm.Designer.cs @@ -31,7 +31,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { components = new System.ComponentModel.Container(); - var resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); lbLogs = new ListBox(); objectDirBrowser = new FolderBrowserDialog(); tbFileFilter = new TextBox(); @@ -287,7 +287,7 @@ private void InitializeComponent() tpObjectStringTable.Padding = new Padding(2, 1, 2, 1); tpObjectStringTable.Size = new Size(1385, 629); tpObjectStringTable.TabIndex = 3; - tpObjectStringTable.Text = "Strings"; + tpObjectStringTable.Text = "String Table"; tpObjectStringTable.UseVisualStyleBackColor = true; // // ucStringTable @@ -308,7 +308,7 @@ private void InitializeComponent() tpObjectGraphicsTable.Padding = new Padding(2, 1, 2, 1); tpObjectGraphicsTable.Size = new Size(1385, 629); tpObjectGraphicsTable.TabIndex = 4; - tpObjectGraphicsTable.Text = "Graphics"; + tpObjectGraphicsTable.Text = "Graphics/Sounds"; tpObjectGraphicsTable.UseVisualStyleBackColor = true; // // scImageViewer @@ -575,14 +575,14 @@ private void InitializeComponent() // setObjectDirectoryToolStripMenuItem // setObjectDirectoryToolStripMenuItem.Name = "setObjectDirectoryToolStripMenuItem"; - setObjectDirectoryToolStripMenuItem.Size = new Size(180, 22); + setObjectDirectoryToolStripMenuItem.Size = new Size(123, 22); setObjectDirectoryToolStripMenuItem.Text = "Add New"; setObjectDirectoryToolStripMenuItem.Click += setObjectDirectoryToolStripMenuItem_Click; // // toolStripSeparator1 // toolStripSeparator1.Name = "toolStripSeparator1"; - toolStripSeparator1.Size = new Size(177, 6); + toolStripSeparator1.Size = new Size(120, 6); // // dataDirectoriesToolStripMenuItem // diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 92ae34a1..82ddea5a 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -7,8 +7,8 @@ using OpenLocoTool.Objects; using OpenLocoTool.Types; using OpenLocoToolCommon; +using System.Data; using System.Drawing.Imaging; -using System.Xml.Linq; namespace OpenLocoToolGui { @@ -242,6 +242,7 @@ private void InitDataCategoryTree() var dataNode = new TreeNode("Data"); var musicNode = new TreeNode("Music"); + var miscTrackNode = new TreeNode("Misc Tracks"); var sfxNode = new TreeNode("Sound Effects"); var tutorialsNode = new TreeNode("Tutorials"); var miscNode = new TreeNode("Uncategorised"); @@ -253,15 +254,22 @@ private void InitDataCategoryTree() // music foreach (var music in model.Music) { - musicNode.Nodes.Add(music.Key, music.Key, 1, 1); - tvUniqueLoadValues[music.Key] = LoadMusic; + var displayName = OriginalDataFiles.Music[music.Key]; + musicNode.Nodes.Add(music.Key, displayName, 1, 1); + } + + //misc tracks + foreach (var miscTrack in model.MiscellaneousTracks) + { + var displayName = OriginalDataFiles.MiscellaneousTracks[miscTrack.Key]; + miscTrackNode.Nodes.Add(miscTrack.Key, displayName, 1, 1); } // sound effects foreach (var sfx in model.SoundEffects) { - sfxNode.Nodes.Add(sfx.Key, sfx.Key, 1, 1); - tvUniqueLoadValues[sfx.Key] = LoadNull; + var displayName = OriginalDataFiles.SoundEffects[sfx.Key]; + sfxNode.Nodes.Add(sfx.Key, displayName, 1, 1); } // tutorials @@ -287,6 +295,7 @@ private void InitDataCategoryTree() } dataNode.Nodes.Add(musicNode); + dataNode.Nodes.Add(miscTrackNode); dataNode.Nodes.Add(sfxNode); dataNode.Nodes.Add(tutorialsNode); dataNode.Nodes.Add(miscNode); @@ -502,38 +511,46 @@ static string constructAnnotationText(Annotation annotation) static bool MusicIsPlaying { get; set; } = false; - void LoadMusic(string dataKey) + private void LoadAndPlaySound(byte[] data) { - var music = model.Music[dataKey]; - var (header, pcmData) = SawyerStreamReader.LoadMusicTrack(music); + var (header, pcmData) = SawyerStreamReader.LoadMusicTrack(data); pgS5Header.SelectedObject = header; if (!header.Validate()) { - // invalid music file + // invalid file + logger?.Warning($"Invalid music track"); return; } - PlayMusic(header, pcmData); + var pn = CreateSoundUI(pcmData, (int)header.SampleRate, header.BitsPerSample, header.NumberOfChannels); + flpImageTable.Controls.Add(pn); } - void PlayMusic(RiffWavHeader hdr, byte[] pcmData) + void LoadAndPlaySoundEffect(byte[] data) { - CreateSoundUI(pcmData, (int)hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); - } + var sfxs = SawyerStreamReader.LoadSoundEffectsFromCSS(data); - void CreateSounds(SoundObject soundObject) - { - var hdr = soundObject.SoundObjectData.PcmHeader; - CreateSoundUI(soundObject.RawPcmData, hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); + pgS5Header.SelectedObject = sfxs; + + var flp = new FlowLayoutPanel(); + flp.FlowDirection = FlowDirection.TopDown; + flp.Dock = DockStyle.Fill; + flp.AutoSize = true; + flp.WrapContents = false; + + foreach (var (header, pcmData) in sfxs) + { + var pn = CreateSoundUI(pcmData, header.SampleRate, header.BitsPerSample, header.NumberOfChannels); + flp.Controls.Add(pn); + } + + flpImageTable.Controls.Add(flp); } - void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfChannels) + FlowLayoutPanel CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfChannels) { - flpImageTable.SuspendLayout(); - flpImageTable.Controls.Clear(); - // for some reason the SoundObject files have the wrong bitspersample set if (bits != 16) { @@ -549,20 +566,20 @@ void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfC var playButton = new Button { Size = new Size(64, 64), - Text = "Play", + Text = "\u23f5 Play", }; var stopButton = new Button { Size = new Size(64, 64), - Text = "Stop", + Text = "\u23f9 Stop", }; stopButton.Click += (args, sender) => CurrentWOEvent?.Stop(); var pauseButton = new Button { Size = new Size(64, 64), - Text = "Pause", + Text = "\u23f8 Pause", }; pauseButton.Click += (args, sender) => CurrentWOEvent?.Pause(); @@ -570,7 +587,7 @@ void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfC { BorderStyle = BorderStyle.FixedSingle, WaveStream = new RawSourceWaveStream(new MemoryStream(pcmData), new WaveFormat(samplesPerSecond, bits, numberOfChannels)), - Size = new Size(1024, 128), + Size = new Size(512, 64), }; waveViewer.SamplesPerPixel = pcmData.Length / waveViewer.Width / numberOfChannels / (bits / 8); @@ -602,7 +619,7 @@ void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfC using (var ms = new MemoryStream(pcmData)) using (var rs = new RawSourceWaveStream(ms, new WaveFormat(samplesPerSecond, bits, numberOfChannels))) using (CurrentWOEvent = new WaveOutEvent()) - using (var transparentBrush = new SolidBrush(Color.FromArgb(27, 0, 0, 0))) + using (var transparentBrush = new SolidBrush(Color.FromArgb(63, 0, 0, 0))) { var g = waveViewer.CreateGraphics(); @@ -632,17 +649,28 @@ void CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfC Thread.Sleep(50); } + + // complete overlay to the very end + g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, waveViewer.Width - prevX, waveViewer.Height)); } CurrentWOEvent = null; MusicIsPlaying = false; }); }; - flpImageTable.Controls.Add(waveViewer); - flpImageTable.Controls.Add(playButton); - flpImageTable.Controls.Add(pauseButton); - flpImageTable.Controls.Add(stopButton); - flpImageTable.ResumeLayout(true); + var pn = new FlowLayoutPanel(); + pn.BorderStyle = BorderStyle.Fixed3D; + pn.FlowDirection = FlowDirection.LeftToRight; + //pn.BackColor = Color.LightCoral; + pn.Dock = DockStyle.Fill; + pn.AutoSize = true; + //pn.WrapContents = false; + //pn.AutoScroll = true; + pn.Controls.Add(waveViewer); + pn.Controls.Add(playButton); + pn.Controls.Add(pauseButton); + pn.Controls.Add(stopButton); + return pn; } WaveOutEvent? CurrentWOEvent { get; set; } @@ -668,13 +696,36 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e) return; } + flpImageTable.SuspendLayout(); + flpImageTable.Controls.Clear(); + var nodeText = e.Node.Text.ToLower(); if (tvUniqueLoadValues.TryGetValue(nodeText, out var value)) // for custom functions for the individual data files { + logger.Debug($"Loading special object {e.Node.Name}"); value.Invoke(e.Node.Name); } + else if (OriginalDataFiles.Music.ContainsKey(e.Node.Name.ToLower())) + { + logger.Debug($"Loading music for {e.Node.Name}"); + var music = model.Music[e.Node.Name]; + LoadAndPlaySound(music); + } + else if (OriginalDataFiles.MiscellaneousTracks.ContainsKey(e.Node.Name.ToLower())) + { + logger.Debug($"Loading miscellaneous track {e.Node.Name}"); + var misc = model.MiscellaneousTracks[e.Node.Name]; + LoadAndPlaySound(misc); + } + else if (OriginalDataFiles.SoundEffects.ContainsKey(e.Node.Name.ToLower())) + { + logger.Debug($"Loading sound effects for {e.Node.Name}"); + var sfx = model.SoundEffects[e.Node.Name]; + LoadAndPlaySoundEffect(sfx); + } else if (Path.GetExtension(e.Node.Name).Equals(".dat", StringComparison.CurrentCultureIgnoreCase)) { + logger.Debug($"Loading object {e.Node.Name}"); var filename = e.Node.Name; CurrentUIObject = model.LoadAndCacheObject(filename); @@ -687,6 +738,8 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e) // logger?.Error(ex, $"Unable to annotate file \"{filename}\""); } } + + flpImageTable.ResumeLayout(true); } IEnumerable CreateImageControls(IEnumerable images, List g1Elements) // g1Elements is simply used for metadata at this stage @@ -875,7 +928,9 @@ private void RefreshObjectUI() if (CurrentUIObject?.LocoObject.Object is SoundObject soundObject) { - CreateSounds(soundObject); + var hdr = soundObject.SoundObjectData.PcmHeader; + var pn = CreateSoundUI(soundObject.RawPcmData, hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); + flpImageTable.Controls.Add(pn); } flpImageTable.ResumeLayout(true); diff --git a/OpenLocoToolGui/MainFormModel.cs b/OpenLocoToolGui/MainFormModel.cs index 34341769..2777a7ca 100644 --- a/OpenLocoToolGui/MainFormModel.cs +++ b/OpenLocoToolGui/MainFormModel.cs @@ -66,6 +66,8 @@ private void LoadPalette() public Dictionary Music { get; set; } = []; + public Dictionary MiscellaneousTracks { get; set; } = []; + public Dictionary SoundEffects { get; set; } = []; public Dictionary Tutorials { get; set; } = []; @@ -257,7 +259,7 @@ public bool LoadDataDirectory(string directory) void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames, Dictionary dict) { - var expectedMusicFiles = knownFilenames.Select(f => f.ToLower().Replace("data/", string.Empty)); + var expectedMusicFiles = knownFilenames.Select(f => f.ToLower()); foreach (var music in expectedMusicFiles) { var matching = allFilesInDir.Where(f => f.EndsWith(music)); @@ -269,8 +271,9 @@ void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames } } - LoadKnownData(allDataFiles, OriginalDataFiles.Music, Music); - LoadKnownData(allDataFiles, OriginalDataFiles.SoundEffects, SoundEffects); + LoadKnownData(allDataFiles, [.. OriginalDataFiles.Music.Keys], Music); + LoadKnownData(allDataFiles, [.. OriginalDataFiles.MiscellaneousTracks.Keys], MiscellaneousTracks); + LoadKnownData(allDataFiles, [.. OriginalDataFiles.SoundEffects.Keys], SoundEffects); LoadKnownData(allDataFiles, OriginalDataFiles.Tutorials, Tutorials); MiscFiles = [.. allDataFiles]; From b63d7409addb25ad60e2450a92af543d778d7f1b Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Wed, 17 Jan 2024 01:04:31 +1100 Subject: [PATCH 12/13] add exporting music (buggy with sound effects though) --- .../DatFileParsing/SawyerStreamWriter.cs | 24 +++++ OpenLocoTool/Types/RiffWavHeader.cs | 4 +- OpenLocoToolGui/MainForm.cs | 100 +++++++++++++++++- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs b/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs index 6a8db1f4..68b6f81d 100644 --- a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs +++ b/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs @@ -1,12 +1,36 @@ using System.Text; using OpenLocoTool.Data; using OpenLocoTool.Headers; +using OpenLocoTool.Objects; +using OpenLocoTool.Types; using OpenLocoToolCommon; namespace OpenLocoTool.DatFileParsing { public static class SawyerStreamWriter { + public static void ExportSoundEffectAsWave(string filename, WaveFormatEx header, byte[] pcmData) + { + using (var stream = File.Create(filename)) + { + stream.Write(ByteWriter.WriteLocoStruct(header)); + stream.Write(pcmData); + stream.Flush(); + stream.Close(); + } + } + + public static void ExportMusicAsWave(string filename, RiffWavHeader header, byte[] pcmData) + { + using (var stream = File.Create(filename)) + { + stream.Write(ByteWriter.WriteLocoStruct(header)); + stream.Write(pcmData); + stream.Flush(); + stream.Close(); + } + } + public static void Save(string filepath, string objName, ILocoObject locoObject, ILogger? logger = null) { ArgumentNullException.ThrowIfNull(locoObject); diff --git a/OpenLocoTool/Types/RiffWavHeader.cs b/OpenLocoTool/Types/RiffWavHeader.cs index 2fbf78e7..0e19e6a1 100644 --- a/OpenLocoTool/Types/RiffWavHeader.cs +++ b/OpenLocoTool/Types/RiffWavHeader.cs @@ -13,8 +13,8 @@ public record RiffWavHeader( [property: LocoStructOffset(0x14)] uint16_t FormatType, // 1 is PCM [property: LocoStructOffset(0x16)] uint16_t NumberOfChannels, [property: LocoStructOffset(0x18)] uint32_t SampleRate, - [property: LocoStructOffset(0x1C)] uint32_t unk1, // SampleRate * BitsPerSample * Channels / 8 - [property: LocoStructOffset(0x20)] uint16_t unk2, // (BitsPerSample* Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo + [property: LocoStructOffset(0x1C)] uint32_t ByteRate, // SampleRate * BitsPerSample * Channels / 8 + [property: LocoStructOffset(0x20)] uint16_t BlockAlign, // (BitsPerSample* Channels) / 8.1 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo [property: LocoStructOffset(0x22)] uint16_t BitsPerSample, [property: LocoStructOffset(0x24)] uint32_t DataMarker, [property: LocoStructOffset(0x28)] uint32_t DataLength) : ILocoStruct diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 82ddea5a..413d28a5 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -524,7 +524,7 @@ private void LoadAndPlaySound(byte[] data) return; } - var pn = CreateSoundUI(pcmData, (int)header.SampleRate, header.BitsPerSample, header.NumberOfChannels); + var pn = CreateSoundUI(pcmData, header); flpImageTable.Controls.Add(pn); } @@ -542,15 +542,91 @@ void LoadAndPlaySoundEffect(byte[] data) foreach (var (header, pcmData) in sfxs) { - var pn = CreateSoundUI(pcmData, header.SampleRate, header.BitsPerSample, header.NumberOfChannels); + var pn = CreateSoundUI(pcmData, header); flp.Controls.Add(pn); } flpImageTable.Controls.Add(flp); } - FlowLayoutPanel CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, int numberOfChannels) + public void ExportMusic(byte[] pcmData, object waveHeader) { + using (var saveFileDialog = new SaveFileDialog()) + { + saveFileDialog.InitialDirectory = Directory.GetCurrentDirectory(); + saveFileDialog.Filter = "WAV Files(*.wav)|*.wav|All files (*.*)|*.*"; + saveFileDialog.FilterIndex = 1; + saveFileDialog.RestoreDirectory = true; + + // suggested filename for the save dialog + if (OriginalDataFiles.Music.TryGetValue(tvObjType.SelectedNode.Name, out string? value)) + { + saveFileDialog.FileName = value; + } + else + { + saveFileDialog.FileName = "export.wav"; + } + + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + if (waveHeader is RiffWavHeader riffHeader) + { + SawyerStreamWriter.ExportMusicAsWave(saveFileDialog.FileName, riffHeader, pcmData); + logger.Info($"Saved music to {saveFileDialog.FileName}"); + } + else if (waveHeader is WaveFormatEx waveFHeader) + { + // make riff header + var newRiffHeader = new RiffWavHeader( + 0x46464952, // "RIFF" + (uint)(pcmData.Length + 36), // file size + 0x45564157, // "WAVE" + 0x20746d66, // "fmt " + 16, // size of fmt chunk + 1, // format tag + (ushort)waveFHeader.NumberOfChannels, + (uint)waveFHeader.SampleRate, + (uint)waveFHeader.AverageBytesPerSecond, + 4, //(ushort)waveFHeader.BlockAlign, + 16, //(ushort)waveFHeader.BitsPerSample, + 0x61746164, // "data" + (uint)pcmData.Length // data size + ); + + SawyerStreamWriter.ExportMusicAsWave(saveFileDialog.FileName, newRiffHeader, pcmData); + logger.Info($"Saved sound effect to {saveFileDialog.FileName}"); + } + else + { + logger.Error($"Selected UI object was not a sound file"); + } + } + } + } + + FlowLayoutPanel CreateSoundUI(byte[] pcmData, object waveHeader) + { + int samplesPerSecond = 0; + int bits = 0; + int numberOfChannels = 0; + bool isRiff = false; + + if (waveHeader is RiffWavHeader riffHeader) + { + samplesPerSecond = (int)riffHeader.SampleRate; + bits = riffHeader.BitsPerSample; + numberOfChannels = riffHeader.NumberOfChannels; + isRiff = true; + } + else if (waveHeader is WaveFormatEx waveFHeader) + { + samplesPerSecond = waveFHeader.SampleRate; + bits = waveFHeader.BitsPerSample; + numberOfChannels = waveFHeader.NumberOfChannels; + isRiff = false; + } + // for some reason the SoundObject files have the wrong bitspersample set if (bits != 16) { @@ -583,6 +659,20 @@ FlowLayoutPanel CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, in }; pauseButton.Click += (args, sender) => CurrentWOEvent?.Pause(); + var exportButton = new Button + { + Size = new Size(64, 64), + Text = "Export", + }; + exportButton.Click += (args, sender) => ExportMusic(pcmData, waveHeader); // isRiff ? ExportMusic(pcmData, waveHeader) : ExportSoundEffect(pcmData, waveHeader); + + var importButton = new Button + { + Size = new Size(64, 64), + Text = "Import (not implemented)", + Enabled = false, + }; + var waveViewer = new WaveViewer { BorderStyle = BorderStyle.FixedSingle, @@ -670,6 +760,8 @@ FlowLayoutPanel CreateSoundUI(byte[] pcmData, int samplesPerSecond, int bits, in pn.Controls.Add(playButton); pn.Controls.Add(pauseButton); pn.Controls.Add(stopButton); + pn.Controls.Add(exportButton); + pn.Controls.Add(importButton); return pn; } @@ -929,7 +1021,7 @@ private void RefreshObjectUI() if (CurrentUIObject?.LocoObject.Object is SoundObject soundObject) { var hdr = soundObject.SoundObjectData.PcmHeader; - var pn = CreateSoundUI(soundObject.RawPcmData, hdr.SampleRate, hdr.BitsPerSample, hdr.NumberOfChannels); + var pn = CreateSoundUI(soundObject.RawPcmData, hdr); flpImageTable.Controls.Add(pn); } From 6229255c1720c23b9b8e7ada87fd70161b503875 Mon Sep 17 00:00:00 2001 From: Benjamin Sutas Date: Wed, 17 Jan 2024 17:13:23 +1100 Subject: [PATCH 13/13] add sound ids to ui --- OpenLocoTool/Data/SoundId.cs | 39 ++++++++++++++++++++++++ OpenLocoToolGui/MainForm.cs | 58 +++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 OpenLocoTool/Data/SoundId.cs diff --git a/OpenLocoTool/Data/SoundId.cs b/OpenLocoTool/Data/SoundId.cs new file mode 100644 index 00000000..7c118833 --- /dev/null +++ b/OpenLocoTool/Data/SoundId.cs @@ -0,0 +1,39 @@ +namespace OpenLocoTool +{ + public enum SoundId : uint16_t + { + clickDown = 0, + clickUp = 1, + clickPress = 2, + construct = 3, + demolish = 4, + income = 5, + crash = 6, + water = 7, + splash1 = 8, + splash2 = 9, + waypoint = 10, + notification = 11, + openWindow = 12, + applause1 = 13, + error = 14, + multiplayerConnected = 15, + unk_16 = 16, + demolishTree = 17, + demolishBuilding = 18, + unk_19 = 19, + vehiclePickup = 20, + constructShip = 21, + ticker = 22, + applause2 = 23, + newsOooh = 24, + newsAwww = 25, + breakdown1 = 26, + breakdown2 = 27, + breakdown3 = 28, + breakdown4 = 29, + breakdown5 = 30, + breakdown6 = 31, + null_id = 0xFF + }; +} diff --git a/OpenLocoToolGui/MainForm.cs b/OpenLocoToolGui/MainForm.cs index 413d28a5..6a8201bc 100644 --- a/OpenLocoToolGui/MainForm.cs +++ b/OpenLocoToolGui/MainForm.cs @@ -511,7 +511,7 @@ static string constructAnnotationText(Annotation annotation) static bool MusicIsPlaying { get; set; } = false; - private void LoadAndPlaySound(byte[] data) + private void LoadAndPlaySound(byte[] data, string soundName) { var (header, pcmData) = SawyerStreamReader.LoadMusicTrack(data); @@ -524,7 +524,7 @@ private void LoadAndPlaySound(byte[] data) return; } - var pn = CreateSoundUI(pcmData, header); + var pn = CreateSoundUI(pcmData, header, soundName); flpImageTable.Controls.Add(pn); } @@ -540,9 +540,10 @@ void LoadAndPlaySoundEffect(byte[] data) flp.AutoSize = true; flp.WrapContents = false; + var i = 0; foreach (var (header, pcmData) in sfxs) { - var pn = CreateSoundUI(pcmData, header); + var pn = CreateSoundUI(pcmData, header, $"{(SoundId)i++}"); flp.Controls.Add(pn); } @@ -605,7 +606,7 @@ public void ExportMusic(byte[] pcmData, object waveHeader) } } - FlowLayoutPanel CreateSoundUI(byte[] pcmData, object waveHeader) + Panel CreateSoundUI(byte[] pcmData, object waveHeader, string soundName) { int samplesPerSecond = 0; int bits = 0; @@ -748,20 +749,35 @@ FlowLayoutPanel CreateSoundUI(byte[] pcmData, object waveHeader) }); }; - var pn = new FlowLayoutPanel(); - pn.BorderStyle = BorderStyle.Fixed3D; - pn.FlowDirection = FlowDirection.LeftToRight; - //pn.BackColor = Color.LightCoral; + // text + var tb = new TextBox(); + tb.Text = soundName; + tb.Enabled = false; + tb.Width = 128; + tb.Height = 32; + tb.Dock = DockStyle.Top; + + // object controls + var flp = new FlowLayoutPanel(); + flp.FlowDirection = FlowDirection.LeftToRight; + flp.Dock = DockStyle.Fill; + flp.AutoSize = true; + + flp.Controls.Add(tb); + flp.Controls.Add(waveViewer); + flp.Controls.Add(playButton); + flp.Controls.Add(pauseButton); + flp.Controls.Add(stopButton); + flp.Controls.Add(exportButton); + flp.Controls.Add(importButton); + + var pn = new Panel(); pn.Dock = DockStyle.Fill; + pn.BorderStyle = BorderStyle.Fixed3D; pn.AutoSize = true; - //pn.WrapContents = false; - //pn.AutoScroll = true; - pn.Controls.Add(waveViewer); - pn.Controls.Add(playButton); - pn.Controls.Add(pauseButton); - pn.Controls.Add(stopButton); - pn.Controls.Add(exportButton); - pn.Controls.Add(importButton); + pn.Controls.Add(flp); + pn.Controls.Add(tb); + return pn; } @@ -799,15 +815,15 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e) } else if (OriginalDataFiles.Music.ContainsKey(e.Node.Name.ToLower())) { - logger.Debug($"Loading music for {e.Node.Name}"); + logger.Debug($"Loading music for {e.Node.Name} ({e.Node.Text})"); var music = model.Music[e.Node.Name]; - LoadAndPlaySound(music); + LoadAndPlaySound(music, $"{e.Node.Name} ({e.Node.Text})"); } else if (OriginalDataFiles.MiscellaneousTracks.ContainsKey(e.Node.Name.ToLower())) { - logger.Debug($"Loading miscellaneous track {e.Node.Name}"); + logger.Debug($"Loading miscellaneous track {e.Node.Name} ({e.Node.Text})"); var misc = model.MiscellaneousTracks[e.Node.Name]; - LoadAndPlaySound(misc); + LoadAndPlaySound(misc, $"{e.Node.Name} ({e.Node.Text})"); } else if (OriginalDataFiles.SoundEffects.ContainsKey(e.Node.Name.ToLower())) { @@ -1021,7 +1037,7 @@ private void RefreshObjectUI() if (CurrentUIObject?.LocoObject.Object is SoundObject soundObject) { var hdr = soundObject.SoundObjectData.PcmHeader; - var pn = CreateSoundUI(soundObject.RawPcmData, hdr); + var pn = CreateSoundUI(soundObject.RawPcmData, hdr, CurrentUIObject?.LocoObject.StringTable.Table["Name"][LanguageId.english_uk] ?? ""); flpImageTable.Controls.Add(pn); }