diff --git a/GT.RText-Release/GT.NDB0.Core.dll b/GT.RText-Release/GT.NDB0.Core.dll new file mode 100644 index 0000000..8215334 Binary files /dev/null and b/GT.RText-Release/GT.NDB0.Core.dll differ diff --git a/GT.RText-Release/GT.RText.Core.dll b/GT.RText-Release/GT.RText.Core.dll new file mode 100644 index 0000000..6b63981 Binary files /dev/null and b/GT.RText-Release/GT.RText.Core.dll differ diff --git a/GT.RText-Release/GT.RText.exe b/GT.RText-Release/GT.RText.exe new file mode 100644 index 0000000..c60959a Binary files /dev/null and b/GT.RText-Release/GT.RText.exe differ diff --git a/GT.RText-Release/GT.RText.exe.config b/GT.RText-Release/GT.RText.exe.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/GT.RText-Release/GT.RText.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GT.RText-Release/GT.Shared.dll b/GT.RText-Release/GT.Shared.dll new file mode 100644 index 0000000..284b7f8 Binary files /dev/null and b/GT.RText-Release/GT.Shared.dll differ diff --git a/GT.RText-Release/README.txt b/GT.RText-Release/README.txt new file mode 100644 index 0000000..aa973bf --- /dev/null +++ b/GT.RText-Release/README.txt @@ -0,0 +1,72 @@ +GT.RText v1.9.4 - Enhanced Edition +===================================== + +IMPORTANT NOTICE +---------------- +This is a MODIFIED version of the original GT.RText by xfileFIN +Original author: xfileFIN - Copyright © 2022 +Enhanced by: Derek W after requested by a friend - 2025 +This version includes additional features not present in the original + +OVERVIEW +-------- +Gran Turismo RText Editor with enhanced features including: +- CSV Import/Export functionality for bulk editing +- Pure black dark theme with dark title bar +- Custom icon support +- English interface and improved usability + +CREDITS +------- +• Original GT.RText: xfileFIN - 2022 +• Enhanced Edition: Derek W - 2025 +• Additional Features: CSV Import/Export, Dark Theme, UI Improvements + +SYSTEM REQUIREMENTS +------------------- +- Windows 10/11 +- .NET Framework 4.7.2 or higher +- 50MB free disk space + +INSTALLATION +------------ +1. Extract all files to a folder of your choice +2. Ensure all DLL files are in the same folder as GT.RText.exe +3. Run GT.RText.exe + +FEATURES +-------- + Dark Theme: Pure black interface with dark window title bar + CSV Import: Import text entries for bulk editing from CSV files + CSV Export: Export category data to CSV format for backup/editing + Icon Support: Custom application icon (app.ico) + English Interface: Fully translated interface and comments + Enhanced Compatibility: Works with Excel CSV formats + +USAGE +----- +1. Open a Gran Turismo RText file (.rt03, .rt04, .rt05, ._50tr) +2. Right-click on a category to access Import/Export options +3. Use "Import this category from CSV" for bulk updates +4. Use "Export this category to CSV" for data backup +5. CSV format: RecNo,Label,String (Excel compatible) + +CSV IMPORT FORMAT +----------------- +RecNo,Label,String +1,LABEL_NAME,Display Text Here +2,ANOTHER_LABEL,Another Text Here + +TROUBLESHOOTING +--------------- +- If the app doesn't start, install .NET Framework 4.7.2 +- Ensure all DLL files are in the same folder +- Run as Administrator if file access issues occur + +VERSION HISTORY +======================================== +Build Date: August 26, 2025 +Build Version: v1.9.4 +Original Author: xfileFIN (eventHorizon) © 2022 +Enhanced by: Derek W © 2025 +Built with: Visual Studio 2022, MSBuild 17.13 diff --git a/GT.RText-Release/app.ico b/GT.RText-Release/app.ico new file mode 100644 index 0000000..0d17c9a Binary files /dev/null and b/GT.RText-Release/app.ico differ diff --git a/GT.RText.sln b/GT.RText.sln index bebc187..7372ea2 100644 --- a/GT.RText.sln +++ b/GT.RText.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33103.184 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.RText", "GT.RText\GT.RText.csproj", "{9E8B67DF-F7CD-458C-848B-2B148466224E}" EndProject @@ -14,9 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GT.NDB0.Core", "GT.NDB0\GT.NDB0.Core.csproj", "{EE1ACABD-0580-41F9-A56D-2676F2E6D608}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.RText.Comparer", "GT.RText.Comparer\GT.RText.Comparer.csproj", "{B4F8A315-F0EA-4B86-AE62-681AA5CD873E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GT.NDB0.Core", "GT.NDB0\GT.NDB0.Core.csproj", "{EE1ACABD-0580-41F9-A56D-2676F2E6D608}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -40,10 +38,6 @@ Global {EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE1ACABD-0580-41F9-A56D-2676F2E6D608}.Release|Any CPU.Build.0 = Release|Any CPU - {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4F8A315-F0EA-4B86-AE62-681AA5CD873E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GT.RText/DarkTheme.cs b/GT.RText/DarkTheme.cs new file mode 100644 index 0000000..064a29a --- /dev/null +++ b/GT.RText/DarkTheme.cs @@ -0,0 +1,260 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace GT.RText +{ + /// + /// Class to apply dark theme to Windows Forms controls + /// + public static class DarkTheme + { + // Dark theme colors + public static readonly Color DarkBackground = Color.Black; + public static readonly Color DarkSecondaryBackground = Color.FromArgb(20, 20, 20); + public static readonly Color DarkControlBackground = Color.FromArgb(15, 15, 15); + public static readonly Color DarkForeground = Color.White; + public static readonly Color DarkBorder = Color.FromArgb(40, 40, 40); + public static readonly Color DarkSelection = Color.FromArgb(0, 122, 204); + public static readonly Color DarkMenuBackground = Color.FromArgb(20, 20, 20); + public static readonly Color DarkMenuForeground = Color.White; + public static readonly Color DarkStatusBackground = Color.FromArgb(0, 122, 204); + + /// + /// Applies dark theme to the main form and all its controls + /// + /// Form to be modified + public static void ApplyDarkTheme(Form form) + { + // Configure main form + form.BackColor = DarkBackground; + form.ForeColor = DarkForeground; + + // Apply theme to all controls recursively + ApplyDarkThemeToControls(form.Controls); + } + + /// + /// Applies dark theme to a collection of controls recursively + /// + /// Control collection + private static void ApplyDarkThemeToControls(Control.ControlCollection controls) + { + foreach (Control control in controls) + { + ApplyDarkThemeToControl(control); + + // Apply recursively to child controls + if (control.HasChildren) + { + ApplyDarkThemeToControls(control.Controls); + } + } + } + + /// + /// Applies dark theme to a specific control + /// + /// Control to be modified + private static void ApplyDarkThemeToControl(Control control) + { + try + { + switch (control) + { + case MenuStrip menuStrip: + ApplyDarkThemeToMenuStrip(menuStrip); + break; + + case ContextMenuStrip contextMenu: + ApplyDarkThemeToContextMenu(contextMenu); + break; + + case StatusStrip statusStrip: + ApplyDarkThemeToStatusStrip(statusStrip); + break; + + case TabControl tabControl: + ApplyDarkThemeToTabControl(tabControl); + break; + + case ListView listView: + ApplyDarkThemeToListView(listView); + break; + + case TextBox textBox: + ApplyDarkThemeToTextBox(textBox); + break; + + case Button button: + ApplyDarkThemeToButton(button); + break; + + case Panel panel: + ApplyDarkThemeToPanel(panel); + break; + + case GroupBox groupBox: + ApplyDarkThemeToGroupBox(groupBox); + break; + + case Label label: + ApplyDarkThemeToLabel(label); + break; + + default: + // Apply basic theme for non-specific controls + control.BackColor = DarkBackground; + control.ForeColor = DarkForeground; + break; + } + } + catch (Exception) + { + // Ignore errors from controls that don't support color changes + } + } + + private static void ApplyDarkThemeToMenuStrip(MenuStrip menuStrip) + { + menuStrip.BackColor = DarkMenuBackground; + menuStrip.ForeColor = DarkMenuForeground; + menuStrip.Renderer = new DarkMenuRenderer(); + + foreach (ToolStripItem item in menuStrip.Items) + { + ApplyDarkThemeToMenuItem(item); + } + } + + private static void ApplyDarkThemeToContextMenu(ContextMenuStrip contextMenu) + { + contextMenu.BackColor = DarkMenuBackground; + contextMenu.ForeColor = DarkMenuForeground; + contextMenu.Renderer = new DarkMenuRenderer(); + + foreach (ToolStripItem item in contextMenu.Items) + { + ApplyDarkThemeToMenuItem(item); + } + } + + private static void ApplyDarkThemeToMenuItem(ToolStripItem item) + { + item.BackColor = DarkMenuBackground; + item.ForeColor = DarkMenuForeground; + + if (item is ToolStripMenuItem menuItem && menuItem.HasDropDownItems) + { + foreach (ToolStripItem subItem in menuItem.DropDownItems) + { + ApplyDarkThemeToMenuItem(subItem); + } + } + } + + private static void ApplyDarkThemeToStatusStrip(StatusStrip statusStrip) + { + statusStrip.BackColor = DarkSecondaryBackground; + statusStrip.ForeColor = DarkForeground; + statusStrip.Renderer = new DarkStatusStripRenderer(); + + foreach (ToolStripItem item in statusStrip.Items) + { + item.BackColor = DarkSecondaryBackground; + item.ForeColor = DarkForeground; + } + } + + private static void ApplyDarkThemeToTabControl(TabControl tabControl) + { + tabControl.BackColor = DarkBackground; + tabControl.ForeColor = DarkForeground; + + foreach (TabPage tabPage in tabControl.TabPages) + { + tabPage.BackColor = DarkBackground; + tabPage.ForeColor = DarkForeground; + } + } + + private static void ApplyDarkThemeToListView(ListView listView) + { + listView.BackColor = DarkControlBackground; + listView.ForeColor = DarkForeground; + listView.BorderStyle = BorderStyle.FixedSingle; + } + + private static void ApplyDarkThemeToTextBox(TextBox textBox) + { + textBox.BackColor = DarkControlBackground; + textBox.ForeColor = DarkForeground; + textBox.BorderStyle = BorderStyle.FixedSingle; + } + + private static void ApplyDarkThemeToButton(Button button) + { + button.BackColor = DarkSecondaryBackground; + button.ForeColor = DarkForeground; + button.FlatStyle = FlatStyle.Flat; + button.FlatAppearance.BorderColor = DarkBorder; + button.FlatAppearance.MouseOverBackColor = DarkSelection; + } + + private static void ApplyDarkThemeToPanel(Panel panel) + { + panel.BackColor = DarkBackground; + panel.ForeColor = DarkForeground; + } + + private static void ApplyDarkThemeToGroupBox(GroupBox groupBox) + { + groupBox.BackColor = DarkBackground; + groupBox.ForeColor = DarkForeground; + } + + private static void ApplyDarkThemeToLabel(Label label) + { + label.BackColor = Color.Transparent; + label.ForeColor = DarkForeground; + } + } + + /// + /// Renderer personalizado para menus em tema escuro + /// + public class DarkMenuRenderer : ToolStripProfessionalRenderer + { + public DarkMenuRenderer() : base(new DarkMenuColorTable()) { } + } + + /// + /// Renderer personalizado para status strip em tema escuro + /// + public class DarkStatusStripRenderer : ToolStripProfessionalRenderer + { + public DarkStatusStripRenderer() : base(new DarkMenuColorTable()) { } + } + + /// + /// Tabela de cores personalizada para menus em tema escuro + /// + public class DarkMenuColorTable : ProfessionalColorTable + { + public override Color MenuItemSelected => DarkTheme.DarkSelection; + public override Color MenuItemSelectedGradientBegin => DarkTheme.DarkSelection; + public override Color MenuItemSelectedGradientEnd => DarkTheme.DarkSelection; + public override Color MenuItemPressedGradientBegin => DarkTheme.DarkSelection; + public override Color MenuItemPressedGradientEnd => DarkTheme.DarkSelection; + public override Color MenuItemBorder => DarkTheme.DarkBorder; + public override Color MenuBorder => DarkTheme.DarkBorder; + public override Color MenuStripGradientBegin => DarkTheme.DarkMenuBackground; + public override Color MenuStripGradientEnd => DarkTheme.DarkMenuBackground; + public override Color ToolStripDropDownBackground => DarkTheme.DarkMenuBackground; + public override Color ImageMarginGradientBegin => DarkTheme.DarkMenuBackground; + public override Color ImageMarginGradientMiddle => DarkTheme.DarkMenuBackground; + public override Color ImageMarginGradientEnd => DarkTheme.DarkMenuBackground; + public override Color SeparatorDark => DarkTheme.DarkBorder; + public override Color SeparatorLight => DarkTheme.DarkBorder; + } +} diff --git a/GT.RText/GT.RText.csproj b/GT.RText/GT.RText.csproj index a9f3c28..62fd18a 100644 --- a/GT.RText/GT.RText.csproj +++ b/GT.RText/GT.RText.csproj @@ -35,16 +35,7 @@ 4 - - ..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll - - - ..\packages\Microsoft-WindowsAPICodePack-Core.1.1.4\lib\net472\Microsoft.WindowsAPICodePack.dll - - - ..\packages\Microsoft-WindowsAPICodePack-Shell.1.1.4\lib\net472\Microsoft.WindowsAPICodePack.Shell.dll - @@ -63,6 +54,7 @@ + @@ -107,6 +99,9 @@ + + Always + diff --git a/GT.RText/Main.Designer.cs b/GT.RText/Main.Designer.cs index d5aff52..335d222 100644 --- a/GT.RText/Main.Designer.cs +++ b/GT.RText/Main.Designer.cs @@ -34,6 +34,7 @@ private void InitializeComponent() { this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.editToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); this.addEditFromCSVFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.importExcelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.addToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -116,7 +117,8 @@ private void InitializeComponent() { // editToolStripMenuItem1 // this.editToolStripMenuItem1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.addEditFromCSVFileToolStripMenuItem}); + this.addEditFromCSVFileToolStripMenuItem, + this.importExcelToolStripMenuItem}); this.editToolStripMenuItem1.Name = "editToolStripMenuItem1"; this.editToolStripMenuItem1.Size = new System.Drawing.Size(39, 20); this.editToolStripMenuItem1.Text = "Edit"; @@ -128,6 +130,13 @@ private void InitializeComponent() { this.addEditFromCSVFileToolStripMenuItem.Text = "Add/Edit Current Category from CSV File (Key, Value)"; this.addEditFromCSVFileToolStripMenuItem.Click += new System.EventHandler(this.addEditFromCSVFileToolStripMenuItem_Click); // + // importExcelToolStripMenuItem + // + this.importExcelToolStripMenuItem.Name = "importExcelToolStripMenuItem"; + this.importExcelToolStripMenuItem.Size = new System.Drawing.Size(353, 22); + this.importExcelToolStripMenuItem.Text = "Import CSV for Current Category (RecNo, Label, String)"; + this.importExcelToolStripMenuItem.Click += new System.EventHandler(this.importExcelToolStripMenuItem_Click); + // // contextMenuStrip // this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -239,7 +248,7 @@ private void InitializeComponent() { this.Controls.Add(this.menuStrip); this.MainMenuStrip = this.menuStrip; this.Name = "Main"; - this.Text = "RT2 Editor by xfileFIN"; + this.Text = "GT.R Text Editor (Modified by Derek W 🏴‍☠️)"; this.SizeChanged += new System.EventHandler(this.Main_SizeChanged); this.menuStrip.ResumeLayout(false); this.menuStrip.PerformLayout(); @@ -274,6 +283,7 @@ private void InitializeComponent() { private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem1; private System.Windows.Forms.ToolStripMenuItem addEditFromCSVFileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem importExcelToolStripMenuItem; private System.Windows.Forms.OpenFileDialog csvOpenFileDialog; } } diff --git a/GT.RText/Main.cs b/GT.RText/Main.cs index d66b795..9cb3bf1 100644 --- a/GT.RText/Main.cs +++ b/GT.RText/Main.cs @@ -1,21 +1,43 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Windows.Forms; +using System.Runtime.InteropServices; // Required for the non crappy folder picker // https://stackoverflow.com/q/11624298 -using Microsoft.WindowsAPICodePack.Dialogs; +// using Microsoft.WindowsAPICodePack.Dialogs; using GT.RText.Core; using GT.RText.Core.Exceptions; using GT.Shared.Logging; +using GT.Shared; using System.Linq; namespace GT.RText { public partial class Main : Form { + // Windows API declarations for dark title bar + [DllImport("dwmapi.dll", PreserveSig = true)] + private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); + + [DllImport("dwmapi.dll")] + private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset); + + [StructLayout(LayoutKind.Sequential)] + private struct MARGINS + { + public int leftWidth; + public int rightWidth; + public int topHeight; + public int bottomHeight; + } + + private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; + private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; + /// /// Designates whether the currently loaded content is a project folder. /// @@ -33,6 +55,7 @@ public partial class Main : Form private List _rTexts; private ListViewColumnSorter _columnSorter; + private ContextMenuStrip _categoriesContextMenu; public RTextParser CurrentRText => _rTexts[tabControlLocalFiles.SelectedIndex]; public RTextPageBase CurrentPage { get; set; } @@ -47,6 +70,54 @@ public Main() _columnSorter = new ListViewColumnSorter(); this.listViewEntries.ListViewItemSorter = _columnSorter; this.listViewEntries.Sorting = SortOrder.Ascending; + + InitializeExcelImportFeature(); + + // Apply dark theme + DarkTheme.ApplyDarkTheme(this); + + // Apply dark title bar + this.Load += (s, e) => ApplyDarkTitleBar(); + + // Load icon if exists + try + { + string iconPath = Path.Combine(Application.StartupPath, "app.ico"); + if (File.Exists(iconPath)) + { + this.Icon = new Icon(iconPath); + } + } + catch + { + // Ignore if unable to load the icon + } + } + + /// + /// Applies dark title bar using Windows API + /// + private void ApplyDarkTitleBar() + { + try + { + if (this.Handle != IntPtr.Zero) + { + int value = 1; + // Try first the newest API version + int result = DwmSetWindowAttribute(this.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref value, sizeof(int)); + + // If it fails, try the older version + if (result != 0) + { + DwmSetWindowAttribute(this.Handle, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, ref value, sizeof(int)); + } + } + } + catch + { + // Ignore if API is not available + } } #region Events @@ -71,13 +142,11 @@ private void openToolStripMenuItem_Click(object sender, EventArgs e) private void openFolderToolStripMenuItem_Click(object sender, EventArgs e) { - var dialog = new CommonOpenFileDialog(); - dialog.EnsureFileExists = true; - dialog.EnsurePathExists = true; - - dialog.IsFolderPicker = true; + var dialog = new FolderBrowserDialog(); + dialog.Description = "Selecione a pasta com os arquivos RT"; + dialog.ShowNewFolderButton = false; - if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return; + if (dialog.ShowDialog() != DialogResult.OK) return; _rTexts.Clear(); @@ -87,7 +156,7 @@ private void openFolderToolStripMenuItem_Click(object sender, EventArgs e) ClearTabs(); bool firstTab = true; - string[] files = Directory.GetFiles(dialog.FileName, "*", SearchOption.TopDirectoryOnly); + string[] files = Directory.GetFiles(dialog.SelectedPath, "*", SearchOption.TopDirectoryOnly); if (files.Any(f => RTextParser.Locales.ContainsKey(Path.GetFileNameWithoutExtension(f)))) { @@ -118,7 +187,7 @@ private void openFolderToolStripMenuItem_Click(object sender, EventArgs e) else { // Locale files are located per-UI project, in their own folder (i.e arcade/US/rtext.rt2) - string[] folders = Directory.GetDirectories(dialog.FileName, "*", SearchOption.TopDirectoryOnly); + string[] folders = Directory.GetDirectories(dialog.SelectedPath, "*", SearchOption.TopDirectoryOnly); foreach (var folder in folders) { string actualDirName = Path.GetFileName(folder); @@ -152,24 +221,22 @@ private void saveToolStripMenuItem_Click(object sender, EventArgs e) { if (_isUiFolderProject) { - var dialog = new CommonOpenFileDialog(); - dialog.EnsureFileExists = true; - dialog.EnsurePathExists = true; + var dialog = new FolderBrowserDialog(); + dialog.Description = "Selecione a pasta para salvar os arquivos"; + dialog.ShowNewFolderButton = true; - dialog.IsFolderPicker = true; - - if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return; + if (dialog.ShowDialog() != DialogResult.OK) return; foreach (var rtext in _rTexts) { if (_isGT6AndAboveProjectStyle) { - string localePath = Path.Combine(dialog.FileName, $"{rtext.LocaleCode}.rt2"); + string localePath = Path.Combine(dialog.SelectedPath, $"{rtext.LocaleCode}.rt2"); rtext.RText.Save(localePath); } else { - string localePath = Path.Combine(dialog.FileName, rtext.LocaleCode); + string localePath = Path.Combine(dialog.SelectedPath, rtext.LocaleCode); Directory.CreateDirectory(localePath); rtext.RText.Save(Path.Combine(localePath, "rtext.rt2")); @@ -485,6 +552,19 @@ private void addEditFromCSVFileToolStripMenuItem_Click(object sender, EventArgs DisplayEntries(CurrentPage); } + + private void importExcelToolStripMenuItem_Click(object sender, EventArgs e) + { + if (!_rTexts.Any() || CurrentRText is null || CurrentPage is null) + { + MessageBox.Show("No file loaded or category selected.", "Warning", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + ImportExcelForCategory_Click(sender, e); + } + #endregion private void ClearTabs() @@ -618,5 +698,311 @@ private void SortEntriesListView(int columnIndex) // Perform the sort with these new sort options. this.listViewEntries.Sort(); } + + #region Excel Import Feature + + private void InitializeExcelImportFeature() + { + // Criar menu de contexto para as categorias + _categoriesContextMenu = new ContextMenuStrip(); + + var importCsvItem = new ToolStripMenuItem("Import CSV to this category"); + importCsvItem.Click += ImportExcelForCategory_Click; + importCsvItem.Image = null; // Pode adicionar um ícone se desejar + + var exportCsvItem = new ToolStripMenuItem("Export this category to CSV"); + exportCsvItem.Click += ExportCategoryToCsv_Click; + exportCsvItem.Image = null; // Pode adicionar um ícone se desejar + + var createSampleItem = new ToolStripMenuItem("Create a CSV in the correct format"); + createSampleItem.Click += CreateSampleCsv_Click; + + _categoriesContextMenu.Items.Add(importCsvItem); + _categoriesContextMenu.Items.Add(exportCsvItem); + _categoriesContextMenu.Items.Add(new ToolStripSeparator()); + _categoriesContextMenu.Items.Add(createSampleItem); + + // Associar o menu de contexto ao ListView de páginas/categorias + listViewPages.ContextMenuStrip = _categoriesContextMenu; + } + + private void CreateSampleCsv_Click(object sender, EventArgs e) + { + var saveFileDialog = new SaveFileDialog + { + Filter = "CSV Files (*.csv)|*.csv", + Title = "Save sample CSV file", + FileName = "sample_import.csv" + }; + + if (saveFileDialog.ShowDialog(this) == DialogResult.OK) + { + try + { + ExcelImporter.CreateSampleCsv(saveFileDialog.FileName); + MessageBox.Show($"Sample file created successfully!\n\nLocation: {saveFileDialog.FileName}\n\n" + + "You can edit this file and use it to import data.", + "File created", MessageBoxButtons.OK, MessageBoxIcon.Information); + + toolStripStatusLabel.Text = "Sample CSV file created successfully"; + } + catch (Exception ex) + { + MessageBox.Show($"Error creating sample file: {ex.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private void ImportExcelForCategory_Click(object sender, EventArgs e) + { + // Verificar se há uma categoria selecionada + if (listViewPages.SelectedItems.Count <= 0 || listViewPages.SelectedItems[0] == null) + { + MessageBox.Show("Please select a category first.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var selectedItem = listViewPages.SelectedItems[0]; + var page = (RTextPageBase)selectedItem.Tag; + var categoryName = page.Name; + + var openFileDialog = new OpenFileDialog + { + Filter = "CSV Files (*.csv)|*.csv|All files (*.*)|*.*", + Title = "Select CSV file for import", + CheckFileExists = true, + CheckPathExists = true + }; + + if (openFileDialog.ShowDialog(this) == DialogResult.OK) + { + ImportCsvData(openFileDialog.FileName, page, categoryName); + } + } + + private void ImportCsvData(string filePath, RTextPageBase page, string categoryName) + { + try + { + // Mostrar cursor de espera + this.Cursor = Cursors.WaitCursor; + toolStripStatusLabel.Text = "Importing CSV data..."; + + var importResult = ExcelImporter.ImportFromCsv(filePath); + + if (!importResult.Success) + { + MessageBox.Show($"Import error: {importResult.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + // Mostrar prévia e confirmar importação + var previewMessage = $"File: {Path.GetFileName(filePath)}\n" + + $"Category: {categoryName}\n" + + $"Records found: {importResult.ImportedEntries.Count}\n\n" + + "First records:\n"; + + // Mostrar os primeiros 5 registros como prévia + var preview = importResult.ImportedEntries.Take(5); + foreach (var entry in preview) + { + var truncatedString = entry.String.Length > 50 ? entry.String.Substring(0, 50) + "..." : entry.String; + previewMessage += $"• {entry.RecNo} | {entry.Label} | {truncatedString}\n"; + } + + if (importResult.ImportedEntries.Count > 5) + { + previewMessage += $"... and {importResult.ImportedEntries.Count - 5} more records.\n"; + } + + previewMessage += "\nThis operation will:\n" + + "• Replace existing texts with same Label\n" + + "• Add new records if Label doesn't exist\n" + + "• Keep existing records not in the file\n\n" + + "Do you want to continue with the import?"; + + var confirmResult = MessageBox.Show(previewMessage, "Confirm Import", + MessageBoxButtons.YesNo, MessageBoxIcon.Question); + + if (confirmResult == DialogResult.Yes) + { + ApplyImportedData(importResult.ImportedEntries, page, categoryName); + + MessageBox.Show($"Import completed successfully!\n\n{importResult.Message}", + "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + + // Atualizar a interface + DisplayEntries(page); + toolStripStatusLabel.Text = $"Import completed: {importResult.ImportedEntries.Count} records processed"; + } + else + { + toolStripStatusLabel.Text = "Import cancelled by user"; + } + } + catch (Exception ex) + { + MessageBox.Show($"Error during import: {ex.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + toolStripStatusLabel.Text = "Error during import"; + } + finally + { + this.Cursor = Cursors.Default; + } + } + + private void ApplyImportedData(List entries, RTextPageBase page, string categoryName) + { + int updatedCount = 0; + int addedCount = 0; + + foreach (var entry in entries) + { + if (_isUiFolderProject) + { + // Apply to all locales if it's a folder project + foreach (var rt in _rTexts) + { + try + { + var rtPage = rt.RText.GetPages()[categoryName]; + if (rtPage.PairExists(entry.Label)) + { + // For existing records, preserve original ID and only update the value + var existingPair = rtPage.PairUnits[entry.Label]; + rtPage.EditRow(existingPair.ID, entry.Label, entry.String); + updatedCount++; + } + else + { + // For new records, use a unique ID based on last ID + 1 + int newId = rtPage.PairUnits.Count > 0 ? rtPage.GetLastId() + 1 : 1; + rtPage.AddRow(newId, entry.Label, entry.String); + addedCount++; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error applying data to locale {rt.LocaleCode}: {ex.Message}"); + } + } + } + else + { + // Apply only to current file + if (page.PairExists(entry.Label)) + { + // For existing records, preserve original ID and only update the value + var existingPair = page.PairUnits[entry.Label]; + page.EditRow(existingPair.ID, entry.Label, entry.String); + updatedCount++; + } + else + { + // For new records, use a unique ID based on last ID + 1 + int newId = page.PairUnits.Count > 0 ? page.GetLastId() + 1 : 1; + page.AddRow(newId, entry.Label, entry.String); + addedCount++; + } + } + } + + var summary = ""; + if (_isUiFolderProject) + { + summary = $"Aplicado para {_rTexts.Count} locales: "; + } + + summary += $"{updatedCount} registros atualizados, {addedCount} registros adicionados"; + + Console.WriteLine($"Importação completada: {summary}"); + } + + private void ExportCategoryToCsv_Click(object sender, EventArgs e) + { + // Verificar se há uma categoria selecionada + if (listViewPages.SelectedItems.Count <= 0 || listViewPages.SelectedItems[0] == null) + { + MessageBox.Show("Please select a category first.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var selectedItem = listViewPages.SelectedItems[0]; + var page = (RTextPageBase)selectedItem.Tag; + var categoryName = page.Name; + + var saveFileDialog = new SaveFileDialog + { + Filter = "CSV Files (*.csv)|*.csv|All files (*.*)|*.*", + Title = "Export category to CSV", + FileName = $"{categoryName}_export.csv", + CheckPathExists = true + }; + + if (saveFileDialog.ShowDialog(this) == DialogResult.OK) + { + ExportCategoryData(saveFileDialog.FileName, page, categoryName); + } + } + + private void ExportCategoryData(string filePath, RTextPageBase page, string categoryName) + { + try + { + // Mostrar cursor de espera + this.Cursor = Cursors.WaitCursor; + toolStripStatusLabel.Text = "Exporting category data..."; + + // Converter os dados da página para o formato de exportação + var exportEntries = new List(); + + foreach (var pair in page.PairUnits.Values.OrderBy(p => p.ID)) + { + exportEntries.Add(new ImportedEntry + { + RecNo = pair.ID, + Label = pair.Label, + String = pair.Value + }); + } + + // Exportar para CSV + bool success = ExcelImporter.ExportToCsv(filePath, exportEntries); + + if (success) + { + var successMessage = $"Category '{categoryName}' exported successfully!\n\n" + + $"File: {Path.GetFileName(filePath)}\n" + + $"Records exported: {exportEntries.Count}\n" + + $"Format: RecNo,Label,String"; + + MessageBox.Show(successMessage, "Export Successful", + MessageBoxButtons.OK, MessageBoxIcon.Information); + + toolStripStatusLabel.Text = $"Export completed: {exportEntries.Count} records exported to {Path.GetFileName(filePath)}"; + } + else + { + MessageBox.Show("Failed to export category data. Please check the file path and try again.", + "Export Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + toolStripStatusLabel.Text = "Export failed"; + } + } + catch (Exception ex) + { + MessageBox.Show($"Error during export: {ex.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + toolStripStatusLabel.Text = "Error during export"; + } + finally + { + this.Cursor = Cursors.Default; + } + } + + #endregion } } diff --git a/GT.RText/Properties/AssemblyInfo.cs b/GT.RText/Properties/AssemblyInfo.cs index 003ad58..fcc2454 100644 --- a/GT.RText/Properties/AssemblyInfo.cs +++ b/GT.RText/Properties/AssemblyInfo.cs @@ -9,8 +9,8 @@ [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("eventHorizon")] -[assembly: AssemblyProduct("GT.RText")] -[assembly: AssemblyCopyright("Copyright © xfileFIN 2022")] +[assembly: AssemblyProduct("GT.RText Enhanced Edition")] +[assembly: AssemblyCopyright("Original © xfileFIN 2022 | Enhanced by Derek W 2025")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.9.3.0")] -[assembly: AssemblyFileVersion("1.9.3.0")] +[assembly: AssemblyVersion("1.9.4.0")] +[assembly: AssemblyFileVersion("1.9.4.0")] diff --git a/GT.RText/RowEditor.Designer.cs b/GT.RText/RowEditor.Designer.cs index 3ca60c7..90e9b9a 100644 --- a/GT.RText/RowEditor.Designer.cs +++ b/GT.RText/RowEditor.Designer.cs @@ -141,7 +141,7 @@ private void InitializeComponent() this.Controls.Add(this.label_id); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.Name = "RowEditor"; - this.Text = "Editor"; + this.Text = "GT.RText - Row Editor"; ((System.ComponentModel.ISupportInitialize)(this.numericUpDown_id)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); diff --git a/GT.RText/RowEditor.cs b/GT.RText/RowEditor.cs index ab62ff1..9758d54 100644 --- a/GT.RText/RowEditor.cs +++ b/GT.RText/RowEditor.cs @@ -1,4 +1,6 @@ using System; +using System.Drawing; +using System.IO; using System.Windows.Forms; namespace GT.RText @@ -15,6 +17,12 @@ public partial class RowEditor : Form public RowEditor() { InitializeComponent(); + + // Aplicar tema escuro + DarkTheme.ApplyDarkTheme(this); + + // Carregar ícone se existir + LoadIcon(); } public RowEditor(bool isWithoutId, bool isUiProject) @@ -26,6 +34,12 @@ public RowEditor(bool isWithoutId, bool isUiProject) _isUiProject = isUiProject; if (isUiProject) applyAllLocalesCheckBox.Visible = true; + + // Aplicar tema escuro + DarkTheme.ApplyDarkTheme(this); + + // Carregar ícone se existir + LoadIcon(); } public RowEditor(int id, string label, string data, bool isUiProject) @@ -44,6 +58,28 @@ public RowEditor(int id, string label, string data, bool isUiProject) richTextBox_data.Text = data; HandleId(id); + + // Aplicar tema escuro + DarkTheme.ApplyDarkTheme(this); + + // Carregar ícone se existir + LoadIcon(); + } + + private void LoadIcon() + { + try + { + string iconPath = Path.Combine(Application.StartupPath, "app.ico"); + if (File.Exists(iconPath)) + { + this.Icon = new Icon(iconPath); + } + } + catch + { + // Ignorar se não conseguir carregar o ícone + } } private void HandleId(int id) diff --git a/GT.RText/app.ico b/GT.RText/app.ico new file mode 100644 index 0000000..0d17c9a Binary files /dev/null and b/GT.RText/app.ico differ diff --git a/GT.RText/icon.txt b/GT.RText/icon.txt new file mode 100644 index 0000000..4380b8f --- /dev/null +++ b/GT.RText/icon.txt @@ -0,0 +1 @@ +AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DDw8PAw8PDwMPDw8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= diff --git a/GT.RText/packages.config b/GT.RText/packages.config index aaf6587..902666d 100644 --- a/GT.RText/packages.config +++ b/GT.RText/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/GT.Shared/ExcelImporter.cs b/GT.Shared/ExcelImporter.cs new file mode 100644 index 0000000..83bd80e --- /dev/null +++ b/GT.Shared/ExcelImporter.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace GT.Shared +{ + public class ExcelImportResult + { + public bool Success { get; set; } + public string Message { get; set; } + public List ImportedEntries { get; set; } = new List(); + } + + public class ImportedEntry + { + public int RecNo { get; set; } + public string Label { get; set; } + public string String { get; set; } + } + + public static class ExcelImporter + { + /// + /// Imports data from a CSV file with format: RecNo, Label, String + /// + public static ExcelImportResult ImportFromCsv(string filePath) + { + var result = new ExcelImportResult(); + + try + { + if (!File.Exists(filePath)) + { + result.Message = "Arquivo não encontrado."; + return result; + } + + var lines = File.ReadAllLines(filePath); + + if (lines.Length < 2) + { + result.Message = "The file must contain at least one header line and one data line."; + return result; + } + + // Check header (first line) + var headerLine = lines[0].ToLower(); + if (!headerLine.Contains("recno") || !headerLine.Contains("label") || !headerLine.Contains("string")) + { + result.Message = "The file must have columns in order: RecNo, Label, String.\n" + + $"Header found: '{lines[0]}'"; + return result; + } + + // Process data lines (skip header) + for (int i = 1; i < lines.Length; i++) + { + var line = lines[i].Trim(); + + // Pular linhas vazias + if (string.IsNullOrEmpty(line)) + continue; + + var parts = ParseCsvLine(line); + + if (parts.Length < 3) + { + Console.WriteLine($"Linha {i + 1}: Formato inválido '{line}', ignorando linha."); + continue; + } + + // Clean and process fields + var recNoText = parts[0].Trim().Trim('"'); // Remove extra quotes from Excel + var label = parts[1].Trim().Trim('"'); // Remove extra quotes from Excel + var stringValue = parts[2].Trim().Trim('"'); // Remove extra quotes from Excel + + // If there are still quotes in the middle of text, restore them correctly + label = label.Replace("\"\"", "\""); + stringValue = stringValue.Replace("\"\"", "\""); + + if (int.TryParse(recNoText, out int recNo)) + { + result.ImportedEntries.Add(new ImportedEntry + { + RecNo = recNo, + Label = label, + String = stringValue + }); + } + else + { + Console.WriteLine($"Linha {i + 1}: RecNo inválido '{recNoText}', ignorando linha."); + } + } + + if (result.ImportedEntries.Count == 0) + { + result.Message = "Nenhum registro válido foi encontrado no arquivo."; + return result; + } + + result.Success = true; + result.Message = $"Importados {result.ImportedEntries.Count} registros com sucesso."; + } + catch (Exception ex) + { + result.Success = false; + result.Message = $"Erro ao importar arquivo: {ex.Message}"; + } + + return result; + } + + /// + /// Parses a CSV line considering quotes and commas inside fields + /// Compatible with Excel format that adds quotes to all fields + /// + private static string[] ParseCsvLine(string line) + { + var result = new List(); + var current = ""; + bool inQuotes = false; + + for (int i = 0; i < line.Length; i++) + { + char c = line[i]; + + if (c == '"') + { + if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') + { + // Two consecutive quotes = one literal quote + current += '"'; + i++; // Skip next quote + } + else + { + // Toggle quote state + inQuotes = !inQuotes; + } + } + else if (c == ',' && !inQuotes) + { + // Comma outside quotes = field separator + result.Add(current.Trim('"')); // Remove aspas extras do Excel + current = ""; + } + else + { + current += c; + } + } + + // Add last field, removing extra quotes + result.Add(current.Trim('"')); + + return result.ToArray(); + } + + /// + /// Creates a sample CSV file for the user + /// + public static void CreateSampleCsv(string filePath) + { + var sampleContent = @"RecNo,Label,String +1,the exact same label must be here,This is an example string for entry 1. +2,the exact same label must be here,This is an example string for entry 2."; + + File.WriteAllText(filePath, sampleContent); + } + + /// + /// Exports category entries to CSV format + /// + public static bool ExportToCsv(string filePath, List entries) + { + try + { + var csvContent = "RecNo,Label,String\n"; + + foreach (var entry in entries) + { + // Escape quotes in the data by doubling them + var label = entry.Label.Replace("\"", "\"\""); + var stringValue = entry.String.Replace("\"", "\"\""); + + // Add quotes around fields that contain commas, quotes, or newlines + if (label.Contains(",") || label.Contains("\"") || label.Contains("\n")) + label = $"\"{label}\""; + + if (stringValue.Contains(",") || stringValue.Contains("\"") || stringValue.Contains("\n")) + stringValue = $"\"{stringValue}\""; + + csvContent += $"{entry.RecNo},{label},{stringValue}\n"; + } + + File.WriteAllText(filePath, csvContent); + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/README.md b/README.md index 87db355..90ec990 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,86 @@ -# GT.RText -Supports editing RT03, RT04, RT05 and 50TR files. +# GT.RText v1.9.4 - Enhanced Edition +==================================== -![Preview](https://repository-images.githubusercontent.com/284490993/b578fa00-d4f2-11ea-93f0-e4cbfbbdfadd) + GT RText_91r9j1yscx -### Credits: -[Nenkai](https://github.com/Nenkai)
+## IMPORTANT NOTICE +This is a **MODIFIED** version of the original GT.RText by xfileFIN +Original author: **xfileFIN** © 2022 +Enhanced by: **Derek W** after requested by a friend - 2025 +This version includes additional features not present in the original. -- solving which cipher was being called to decipher the RT05 and 50TR strings.
-- Adding support for editing whole UI folder
+--- -[flatz](https://github.com/flatz/gttool)
+## OVERVIEW +Gran Turismo RText Editor with enhanced features, including: +- CSV Import/Export functionality for bulk editing +- Pure black dark theme with dark title bar +- Custom icon support +- English interface and improved usability -- for reverse engineering the said cipher (Salsa20).
+--- + +## CREDITS +• Original GT.RText: xfileFIN - 2022 +• Enhanced Edition: Derek W - 2025 +• Additional Features: CSV Import/Export, Dark Theme, UI Improvements + +--- + +## SYSTEM REQUIREMENTS +- Windows 10/11 +- .NET Framework 4.7.2 or higher +- 50MB free disk space + +--- + +## INSTALLATION +1. Extract all files to a folder of your choice +2. Ensure all DLL files are in the same folder as GT.RText.exe +3. Run GT.RText.exe + +--- + +## FEATURES +- **Dark Theme:** Pure black interface with dark window title bar +- **CSV Import:** Import text entries for bulk editing from CSV files +- **CSV Export:** Export category data to CSV format for backup/editing +- **Icon Support:** Custom application icon (app.ico) +- **English Interface:** Fully translated interface and comments +- **Enhanced Compatibility:** Works with Excel CSV formats + +--- + +## USAGE +1. Open a Gran Turismo RText file (`.rt03`, `.rt04`, `.rt05`, `._50tr`) +2. Right-click on a category to access Import/Export options +3. Use "Import this category from CSV" for bulk updates +4. Use "Export this category to CSV" for data backup +5. CSV format: `RecNo,Label,String` (Excel compatible) + +--- + +## CSV IMPORT FORMAT +``` +RecNo,Label,String +1,LABEL_NAME,Display Text Here +2,ANOTHER_LABEL,Another Text Here +``` + +--- + +## TROUBLESHOOTING +- If the app doesn't start, install .NET Framework 4.7.2 +- Ensure all DLL files are in the same folder +- Run as Administrator if file access issues occur + +--- + +## VERSION HISTORY +Build Date: August 26, 2025 +Build Version: v1.9.4 +Original Author: xfileFIN © 2022 +Enhanced by: Derek W © 2025 +Built with: Visual Studio 2022, MSBuild 17.13