From 3d3815692aa4040bf3350fba91f7f76e4b44d558 Mon Sep 17 00:00:00 2001 From: Bl4DiEDiEBL4 Date: Tue, 10 Feb 2026 10:41:51 +0100 Subject: [PATCH] Implemented Smart Save logic for profiles --- 2026-02-08_12-18-46.png | Bin 54607 -> 0 bytes README.md | 4 +- Shackle/MainForm.Designer.cs | 331 ++++++++-------- Shackle/MainForm.cs | 717 ++++++++++++++++++++++++++--------- 4 files changed, 714 insertions(+), 338 deletions(-) delete mode 100644 2026-02-08_12-18-46.png diff --git a/2026-02-08_12-18-46.png b/2026-02-08_12-18-46.png deleted file mode 100644 index 34105c9bcc1ab082165ef633e2f5a297acb2675e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54607 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 Hm_7d={jr*G diff --git a/README.md b/README.md index 13b6625..ffe3736 100644 --- a/README.md +++ b/README.md @@ -1,3 +1 @@ -# Shackle - -![picture](https://blvckout.foo/Bl4ckOut/Schackle/raw/branch/master/2026-02-08_12-18-46.png) \ No newline at end of file +# Shackle \ No newline at end of file diff --git a/Shackle/MainForm.Designer.cs b/Shackle/MainForm.Designer.cs index e1628d4..84e1004 100644 --- a/Shackle/MainForm.Designer.cs +++ b/Shackle/MainForm.Designer.cs @@ -14,7 +14,7 @@ private System.Windows.Forms.Button btnRun; private System.Windows.Forms.Button btnStop; private System.Windows.Forms.Button btnOpenCookies; - private System.Windows.Forms.Button btnEditServiceConfig; // New + private System.Windows.Forms.Button btnEditServiceConfig; private System.Windows.Forms.Button btnEditYaml; private System.Windows.Forms.Button btnBrowse; private System.Windows.Forms.Button btnClearLog; @@ -22,219 +22,244 @@ private System.Windows.Forms.PropertyGrid pgProfile; private System.Windows.Forms.Label lblPath; - protected override void Dispose(bool disposing) { if (disposing && (components != null)) components.Dispose(); base.Dispose(disposing); } + // FIXED: Correct variable name (was bntnSaveProfile) + private System.Windows.Forms.Button btnSaveProfile; + + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) components.Dispose(); + base.Dispose(disposing); + } private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); - txtRootPath = new TextBox(); - txtBinName = new TextBox(); - comboProfiles = new ComboBox(); - btnAddProfile = new Button(); - btnRemoveProfile = new Button(); - comboService = new ComboBox(); - txtURL = new TextBox(); - txtCommandPreview = new TextBox(); - btnRun = new Button(); - btnStop = new Button(); - btnOpenCookies = new Button(); - btnEditServiceConfig = new Button(); - btnEditYaml = new Button(); - btnBrowse = new Button(); - btnClearLog = new Button(); - txtLog = new TextBox(); - pgProfile = new PropertyGrid(); - lblPath = new Label(); - SuspendLayout(); + this.txtRootPath = new System.Windows.Forms.TextBox(); + this.txtBinName = new System.Windows.Forms.TextBox(); + this.comboProfiles = new System.Windows.Forms.ComboBox(); + this.btnAddProfile = new System.Windows.Forms.Button(); + this.btnRemoveProfile = new System.Windows.Forms.Button(); + this.comboService = new System.Windows.Forms.ComboBox(); + this.txtURL = new System.Windows.Forms.TextBox(); + this.txtCommandPreview = new System.Windows.Forms.TextBox(); + this.btnRun = new System.Windows.Forms.Button(); + this.btnStop = new System.Windows.Forms.Button(); + this.btnOpenCookies = new System.Windows.Forms.Button(); + this.btnEditServiceConfig = new System.Windows.Forms.Button(); + this.btnEditYaml = new System.Windows.Forms.Button(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.btnClearLog = new System.Windows.Forms.Button(); + this.txtLog = new System.Windows.Forms.TextBox(); + this.pgProfile = new System.Windows.Forms.PropertyGrid(); + this.lblPath = new System.Windows.Forms.Label(); + this.btnSaveProfile = new System.Windows.Forms.Button(); + this.SuspendLayout(); // // txtRootPath // - txtRootPath.Location = new Point(12, 35); - txtRootPath.Name = "txtRootPath"; - txtRootPath.Size = new Size(280, 27); - txtRootPath.TabIndex = 0; + this.txtRootPath.Location = new System.Drawing.Point(12, 35); + this.txtRootPath.Name = "txtRootPath"; + this.txtRootPath.Size = new System.Drawing.Size(280, 27); + this.txtRootPath.TabIndex = 0; // // txtBinName // - txtBinName.Location = new Point(340, 35); - txtBinName.Name = "txtBinName"; - txtBinName.Size = new Size(60, 27); - txtBinName.TabIndex = 2; + this.txtBinName.Location = new System.Drawing.Point(340, 35); + this.txtBinName.Name = "txtBinName"; + this.txtBinName.Size = new System.Drawing.Size(60, 27); + this.txtBinName.TabIndex = 2; // // comboProfiles // - comboProfiles.Anchor = AnchorStyles.Top | AnchorStyles.Right; - comboProfiles.Location = new Point(1177, 35); - comboProfiles.Name = "comboProfiles"; - comboProfiles.Size = new Size(240, 28); - comboProfiles.TabIndex = 4; + this.comboProfiles.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.comboProfiles.Location = new System.Drawing.Point(1177, 35); + this.comboProfiles.Name = "comboProfiles"; + this.comboProfiles.Size = new System.Drawing.Size(240, 28); + this.comboProfiles.TabIndex = 4; // // btnAddProfile // - btnAddProfile.Anchor = AnchorStyles.Top | AnchorStyles.Right; - btnAddProfile.Location = new Point(1422, 34); - btnAddProfile.Name = "btnAddProfile"; - btnAddProfile.Size = new Size(35, 29); - btnAddProfile.TabIndex = 5; - btnAddProfile.Text = "+"; + this.btnAddProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAddProfile.Location = new System.Drawing.Point(1422, 34); + this.btnAddProfile.Name = "btnAddProfile"; + this.btnAddProfile.Size = new System.Drawing.Size(35, 29); + this.btnAddProfile.TabIndex = 5; + this.btnAddProfile.Text = "+"; // // btnRemoveProfile // - btnRemoveProfile.Anchor = AnchorStyles.Top | AnchorStyles.Right; - btnRemoveProfile.Location = new Point(1462, 34); - btnRemoveProfile.Name = "btnRemoveProfile"; - btnRemoveProfile.Size = new Size(35, 29); - btnRemoveProfile.TabIndex = 6; - btnRemoveProfile.Text = "-"; + this.btnRemoveProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnRemoveProfile.Location = new System.Drawing.Point(1462, 34); + this.btnRemoveProfile.Name = "btnRemoveProfile"; + this.btnRemoveProfile.Size = new System.Drawing.Size(35, 29); + this.btnRemoveProfile.TabIndex = 6; + this.btnRemoveProfile.Text = "-"; // // comboService // - comboService.Location = new Point(12, 80); - comboService.Name = "comboService"; - comboService.Size = new Size(100, 28); - comboService.TabIndex = 7; + this.comboService.Location = new System.Drawing.Point(12, 80); + this.comboService.Name = "comboService"; + this.comboService.Size = new System.Drawing.Size(100, 28); + this.comboService.TabIndex = 7; // // txtURL // - txtURL.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - txtURL.Location = new Point(115, 80); - txtURL.Name = "txtURL"; - txtURL.PlaceholderText = "Paste ID / URL here..."; - txtURL.Size = new Size(801, 27); - txtURL.TabIndex = 8; + this.txtURL.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.txtURL.Location = new System.Drawing.Point(115, 80); + this.txtURL.Name = "txtURL"; + this.txtURL.PlaceholderText = "Paste ID / URL here..."; + this.txtURL.Size = new System.Drawing.Size(801, 27); + this.txtURL.TabIndex = 8; // // txtCommandPreview // - txtCommandPreview.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - txtCommandPreview.BackColor = Color.FromArgb(20, 20, 20); - txtCommandPreview.Font = new Font("Consolas", 10F, FontStyle.Bold); - txtCommandPreview.ForeColor = Color.Cyan; - txtCommandPreview.Location = new Point(12, 115); - txtCommandPreview.Name = "txtCommandPreview"; - txtCommandPreview.Size = new Size(1130, 27); - txtCommandPreview.TabIndex = 9; + this.txtCommandPreview.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.txtCommandPreview.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(20)))), ((int)(((byte)(20)))), ((int)(((byte)(20))))); + this.txtCommandPreview.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Bold); + this.txtCommandPreview.ForeColor = System.Drawing.Color.Cyan; + this.txtCommandPreview.Location = new System.Drawing.Point(12, 115); + this.txtCommandPreview.Name = "txtCommandPreview"; + this.txtCommandPreview.Size = new System.Drawing.Size(1130, 27); + this.txtCommandPreview.TabIndex = 9; // // btnRun // - btnRun.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - btnRun.Location = new Point(12, 150); - btnRun.Name = "btnRun"; - btnRun.Size = new Size(1130, 45); - btnRun.TabIndex = 10; - btnRun.Text = "RUN"; + this.btnRun.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.btnRun.Location = new System.Drawing.Point(12, 150); + this.btnRun.Name = "btnRun"; + this.btnRun.Size = new System.Drawing.Size(1130, 45); + this.btnRun.TabIndex = 10; + this.btnRun.Text = "RUN"; // // btnStop // - btnStop.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; - btnStop.BackColor = Color.Maroon; - btnStop.Location = new Point(12, 150); - btnStop.Name = "btnStop"; - btnStop.Size = new Size(1130, 45); - btnStop.TabIndex = 11; - btnStop.Text = "STOP"; - btnStop.UseVisualStyleBackColor = false; - btnStop.Visible = false; + this.btnStop.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.btnStop.BackColor = System.Drawing.Color.Maroon; + this.btnStop.Location = new System.Drawing.Point(12, 150); + this.btnStop.Name = "btnStop"; + this.btnStop.Size = new System.Drawing.Size(1130, 45); + this.btnStop.TabIndex = 11; + this.btnStop.Text = "STOP"; + this.btnStop.UseVisualStyleBackColor = false; + this.btnStop.Visible = false; // // btnOpenCookies // - btnOpenCookies.Anchor = AnchorStyles.Top | AnchorStyles.Right; - btnOpenCookies.Location = new Point(922, 79); - btnOpenCookies.Name = "btnOpenCookies"; - btnOpenCookies.Size = new Size(115, 30); - btnOpenCookies.TabIndex = 12; - btnOpenCookies.Text = "🍪 Cookies"; + this.btnOpenCookies.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnOpenCookies.Location = new System.Drawing.Point(922, 79); + this.btnOpenCookies.Name = "btnOpenCookies"; + this.btnOpenCookies.Size = new System.Drawing.Size(115, 30); + this.btnOpenCookies.TabIndex = 12; + this.btnOpenCookies.Text = "🍪 Cookies"; // // btnEditServiceConfig // - btnEditServiceConfig.Anchor = AnchorStyles.Top | AnchorStyles.Right; - btnEditServiceConfig.Location = new Point(1043, 79); - btnEditServiceConfig.Name = "btnEditServiceConfig"; - btnEditServiceConfig.Size = new Size(99, 30); - btnEditServiceConfig.TabIndex = 13; - btnEditServiceConfig.Text = "⚙️ Service"; + this.btnEditServiceConfig.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnEditServiceConfig.Location = new System.Drawing.Point(1043, 79); + this.btnEditServiceConfig.Name = "btnEditServiceConfig"; + this.btnEditServiceConfig.Size = new System.Drawing.Size(99, 30); + this.btnEditServiceConfig.TabIndex = 13; + this.btnEditServiceConfig.Text = "⚙️ Service"; // // btnEditYaml // - btnEditYaml.Location = new Point(410, 34); - btnEditYaml.Name = "btnEditYaml"; - btnEditYaml.Size = new Size(144, 29); - btnEditYaml.TabIndex = 3; - btnEditYaml.Text = "📝 Main Config"; + this.btnEditYaml.Location = new System.Drawing.Point(410, 34); + this.btnEditYaml.Name = "btnEditYaml"; + this.btnEditYaml.Size = new System.Drawing.Size(144, 29); + this.btnEditYaml.TabIndex = 3; + this.btnEditYaml.Text = "📝 Main Config"; // // btnBrowse // - btnBrowse.Location = new Point(295, 34); - btnBrowse.Name = "btnBrowse"; - btnBrowse.Size = new Size(35, 29); - btnBrowse.TabIndex = 1; - btnBrowse.Text = "📂"; + this.btnBrowse.Location = new System.Drawing.Point(295, 34); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(35, 29); + this.btnBrowse.TabIndex = 1; + this.btnBrowse.Text = "📂"; // // btnClearLog // - btnClearLog.Anchor = AnchorStyles.Top | AnchorStyles.Right; - btnClearLog.Location = new Point(1022, 201); - btnClearLog.Name = "btnClearLog"; - btnClearLog.Size = new Size(120, 36); - btnClearLog.TabIndex = 14; - btnClearLog.Text = "Clear Log"; + this.btnClearLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnClearLog.Location = new System.Drawing.Point(1022, 201); + this.btnClearLog.Name = "btnClearLog"; + this.btnClearLog.Size = new System.Drawing.Size(120, 36); + this.btnClearLog.TabIndex = 14; + this.btnClearLog.Text = "Clear Log"; // // txtLog // - txtLog.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - txtLog.BackColor = Color.Black; - txtLog.Font = new Font("Consolas", 9F); - txtLog.ForeColor = Color.Lime; - txtLog.Location = new Point(12, 243); - txtLog.Multiline = true; - txtLog.Name = "txtLog"; - txtLog.ScrollBars = ScrollBars.Vertical; - txtLog.Size = new Size(1130, 550); - txtLog.TabIndex = 15; + this.txtLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.txtLog.BackColor = System.Drawing.Color.Black; + this.txtLog.Font = new System.Drawing.Font("Consolas", 9F); + this.txtLog.ForeColor = System.Drawing.Color.Lime; + this.txtLog.Location = new System.Drawing.Point(12, 243); + this.txtLog.Multiline = true; + this.txtLog.Name = "txtLog"; + this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtLog.Size = new System.Drawing.Size(1130, 550); + this.txtLog.TabIndex = 15; // // pgProfile // - pgProfile.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; - pgProfile.CategoryForeColor = SystemColors.ActiveCaption; - pgProfile.Location = new Point(1177, 80); - pgProfile.Name = "pgProfile"; - pgProfile.Size = new Size(320, 713); - pgProfile.TabIndex = 16; + this.pgProfile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); + this.pgProfile.CategoryForeColor = System.Drawing.SystemColors.ActiveCaption; + this.pgProfile.Location = new System.Drawing.Point(1177, 80); + this.pgProfile.Name = "pgProfile"; + this.pgProfile.Size = new System.Drawing.Size(320, 713); + this.pgProfile.TabIndex = 16; // // lblPath // - lblPath.Location = new Point(12, 12); - lblPath.Name = "lblPath"; - lblPath.Size = new Size(150, 23); - lblPath.TabIndex = 17; - lblPath.Text = "Root Path & Binary:"; + this.lblPath.Location = new System.Drawing.Point(12, 12); + this.lblPath.Name = "lblPath"; + this.lblPath.Size = new System.Drawing.Size(150, 23); + this.lblPath.TabIndex = 17; + this.lblPath.Text = "Root Path & Binary:"; + // + // btnSaveProfile + // + this.btnSaveProfile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnSaveProfile.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.btnSaveProfile.FlatAppearance.BorderColor = System.Drawing.Color.Gray; + this.btnSaveProfile.ForeColor = System.Drawing.Color.White; + // FIXED: Calculated position to be right next to the Remove (-) button + this.btnSaveProfile.Location = new System.Drawing.Point(1502, 34); + this.btnSaveProfile.Name = "btnSaveProfile"; + this.btnSaveProfile.Size = new System.Drawing.Size(35, 29); + this.btnSaveProfile.TabIndex = 18; + this.btnSaveProfile.Text = "💾"; + this.btnSaveProfile.UseVisualStyleBackColor = true; + this.btnSaveProfile.Click += new System.EventHandler(this.btnSaveProfile_Click); // // MainForm // - ClientSize = new Size(1519, 808); - Controls.Add(txtRootPath); - Controls.Add(btnBrowse); - Controls.Add(txtBinName); - Controls.Add(btnEditYaml); - Controls.Add(comboProfiles); - Controls.Add(btnAddProfile); - Controls.Add(btnRemoveProfile); - Controls.Add(comboService); - Controls.Add(txtURL); - Controls.Add(txtCommandPreview); - Controls.Add(btnRun); - Controls.Add(btnStop); - Controls.Add(btnOpenCookies); - Controls.Add(btnEditServiceConfig); - Controls.Add(btnClearLog); - Controls.Add(txtLog); - Controls.Add(pgProfile); - Controls.Add(lblPath); - Icon = (Icon)resources.GetObject("$this.Icon"); - MinimumSize = new Size(960, 680); - Name = "MainForm"; - Text = "Unshackle Master GUI"; - ResumeLayout(false); - PerformLayout(); + this.ClientSize = new System.Drawing.Size(1550, 808); // Slightly widened to fit new button + this.Controls.Add(this.txtRootPath); + this.Controls.Add(this.btnBrowse); + this.Controls.Add(this.txtBinName); + this.Controls.Add(this.btnEditYaml); + this.Controls.Add(this.comboProfiles); + this.Controls.Add(this.btnAddProfile); + this.Controls.Add(this.btnRemoveProfile); + // FIXED: Added btnSaveProfile to controls list + this.Controls.Add(this.btnSaveProfile); + this.Controls.Add(this.comboService); + this.Controls.Add(this.txtURL); + this.Controls.Add(this.txtCommandPreview); + this.Controls.Add(this.btnRun); + this.Controls.Add(this.btnStop); + this.Controls.Add(this.btnOpenCookies); + this.Controls.Add(this.btnEditServiceConfig); + this.Controls.Add(this.btnClearLog); + this.Controls.Add(this.txtLog); + this.Controls.Add(this.pgProfile); + this.Controls.Add(this.lblPath); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(960, 680); + this.Name = "MainForm"; + this.Text = "Unshackle Master GUI"; + this.ResumeLayout(false); + this.PerformLayout(); } } } \ No newline at end of file diff --git a/Shackle/MainForm.cs b/Shackle/MainForm.cs index dfcddd4..6810a02 100644 --- a/Shackle/MainForm.cs +++ b/Shackle/MainForm.cs @@ -13,40 +13,84 @@ namespace UnshackleGUI { public partial class MainForm : Form { + // ========================================================= + // 1. FILE PATHS & SETTINGS + // ========================================================= private string _configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.json"); private string _paramsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "params.json"); + private string _profilesDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"); + // Data Storage private AppSettings _appSettings = new AppSettings(); private List _parameterDefinitions = new List(); private Dictionary _propertyValues = new Dictionary(); + // State Variables private Process? _currentProcess; - + private bool _isLoaded = false; // Flag to prevent saving while the app is starting up + private string _lastLoadedProfile = ""; // Tracks the original name + // ========================================================= + // 2. CONSTRUCTOR & INITIALIZATION + // ========================================================= public MainForm() { InitializeComponent(); ApplyDarkTheme(); - LoadParamsAndConfig(); - // EVENT: This updates the command line the moment a value changes + // Ensure the Profiles folder exists + if (!Directory.Exists(_profilesDir)) + { + Directory.CreateDirectory(_profilesDir); + } + + // Step 1: Set up all button clicks and text changes + SetupEventHandlers(); + + // Step 2: Load data from disk (Config, Params, Profiles) + LoadData(); + } + + private void SetupEventHandlers() + { + // --- Property Grid (The Settings List) --- + // Whenever a value changes in the grid, update the command text and save the profile immediately. pgProfile.PropertyValueChanged += (s, e) => { UpdateCommandPreview(); - - if (comboProfiles.SelectedItem != null) - { - string selected = comboProfiles.SelectedItem.ToString(); - _appSettings.Profiles[selected] = - new Dictionary(_propertyValues); - } - - SaveConfig(); + SaveCurrentProfile(); }; - comboService.SelectedIndexChanged += (s, e) => UpdateCommandPreview(); - txtURL.TextChanged += (s, e) => UpdateCommandPreview(); - txtBinName.TextChanged += (s, e) => UpdateCommandPreview(); + // --- Service Dropdown (Netflix, Amazon, etc.) --- + comboService.SelectedIndexChanged += (s, e) => + { + // Don't run this logic if the app is still starting up + if (!_isLoaded) return; + if (comboService.SelectedItem != null) + { + // Update the "Service" value in our data dictionary + _propertyValues["Service"] = comboService.SelectedItem.ToString(); + + // Update the command preview + UpdateCommandPreview(); + + // Save this change to the JSON file immediately + SaveCurrentProfile(); + } + }; + + // --- Text Box Changes --- + txtURL.TextChanged += (s, e) => UpdateCommandPreview(); + + txtBinName.TextChanged += (s, e) => + { + UpdateCommandPreview(); + SaveGlobalConfig(); + }; + + txtRootPath.TextChanged += (s, e) => SaveGlobalConfig(); + + // --- Button Clicks --- btnRun.Click += btnRun_Click; btnStop.Click += btnStop_Click; btnBrowse.Click += btnBrowse_Click; @@ -56,130 +100,317 @@ namespace UnshackleGUI btnEditYaml.Click += btnEditYaml_Click; btnAddProfile.Click += btnAddProfile_Click; btnRemoveProfile.Click += btnRemoveProfile_Click; + + // This line connects the click to the function below + btnSaveProfile.Click += btnSaveProfile_Click; } - private void LoadParamsAndConfig() + // ========================================================= + // 3. DATA LOADING LOGIC + // ========================================================= + private void LoadData() { - // 1. Load Parameters (Rules) + _isLoaded = false; // Stop events from firing while we load + + // A. Load Parameters (The definitions for flags like -q, -v, etc.) if (File.Exists(_paramsPath)) { var json = File.ReadAllText(_paramsPath); _parameterDefinitions = JsonConvert.DeserializeObject>(json) ?? new List(); - - foreach (var p in _parameterDefinitions) - { - // Ensure every parameter has a value in the dictionary - if (!_propertyValues.ContainsKey(p.Name)) - _propertyValues[p.Name] = p.Default ?? ""; - } + } + else + { + MessageBox.Show("Error: params.json not found! The grid will be empty."); } - // 2. Load Config (Paths/Profiles) + // B. Load Global Config (Root Path, Binary Name) if (File.Exists(_configPath)) { var json = File.ReadAllText(_configPath); _appSettings = JsonConvert.DeserializeObject(json) ?? new AppSettings(); + txtRootPath.Text = _appSettings.RootPath; txtBinName.Text = _appSettings.BinaryName; } - if (_appSettings.Profiles == null || _appSettings.Profiles.Count == 0) - { - _appSettings.Profiles = new Dictionary>(); - _appSettings.Profiles["Default"] = new Dictionary(_propertyValues); - } - - comboProfiles.DataSource = null; - comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList(); - - comboProfiles.SelectedIndexChanged += (s, e) => - { - if (comboProfiles.SelectedItem == null) return; - - string selected = comboProfiles.SelectedItem.ToString(); - - if (_appSettings.Profiles.TryGetValue(selected, out var values)) - { - _propertyValues = new Dictionary(values); - - pgProfile.SelectedObject = - new DynamicObject(_propertyValues, _parameterDefinitions); - - UpdateCommandPreview(); - } - }; - - - // 3. Bind to Grid using the FIXED Descriptor - pgProfile.SelectedObject = new DynamicObject(_propertyValues, _parameterDefinitions); - - + // C. Populate the Services Dropdown (Read folders from disk) RefreshFolders(); + + // D. Load Profiles and select the default one + RefreshProfileList(); + + _isLoaded = true; // Loading done, enable events + + // Force an initial update of the command text UpdateCommandPreview(); } - private void UpdateCommandPreview() + private void RefreshProfileList() { - StringBuilder sb = new StringBuilder(); - sb.Append($"{txtBinName.Text} run unshackle dl "); + // 1. Temporarily stop listening to the selection event to prevent glitches + comboProfiles.SelectedIndexChanged -= ComboProfiles_SelectedIndexChanged; + // 2. Find all .json files in the Profiles folder + var files = Directory.GetFiles(_profilesDir, "*.json"); + var profileNames = files.Select(Path.GetFileNameWithoutExtension).ToList(); + + // 3. If no profiles exist, create a "Default" one + if (profileNames.Count == 0) + { + CreateDefaultProfile(); + profileNames.Add("Default"); + } + + // 4. Update the ComboBox + comboProfiles.DataSource = null; + comboProfiles.DataSource = profileNames; + + // 5. Select "Default" or the first available profile + string targetProfile = profileNames.Contains("Default") ? "Default" : profileNames[0]; + comboProfiles.SelectedItem = targetProfile; + + // 6. Manually trigger the load for this profile + LoadProfile(targetProfile); + + // 7. Start listening to events again + comboProfiles.SelectedIndexChanged += ComboProfiles_SelectedIndexChanged; + } + + private void CreateDefaultProfile() + { + _propertyValues = new Dictionary(); + + // Fill with default values from params.json foreach (var p in _parameterDefinitions) { + _propertyValues[p.Name] = p.Default ?? ""; + } + + // Set default service + if (comboService.Items.Count > 0) + { + _propertyValues["Service"] = comboService.Items[0].ToString(); + } + + SaveProfileFile("Default"); + } + + // ========================================================= + // 4. PROFILE SWITCHING & LOADING + // ========================================================= + private void ComboProfiles_SelectedIndexChanged(object? sender, EventArgs e) + { + if (comboProfiles.SelectedItem == null) return; + + string selectedProfile = comboProfiles.SelectedItem.ToString(); + LoadProfile(selectedProfile); + } + + private void LoadProfile(string profileName) + { + + _lastLoadedProfile = profileName; + + string path = Path.Combine(_profilesDir, $"{profileName}.json"); + + if (!File.Exists(path)) return; + + try + { + var json = File.ReadAllText(path); + var loadedValues = JsonConvert.DeserializeObject>(json); + + if (loadedValues != null) + { + _propertyValues = loadedValues; + + // --- FIX 1: Add missing parameters --- + // If we added new options to params.json, old profiles won't have them. + // This adds them with default values. + foreach (var p in _parameterDefinitions) + { + if (!_propertyValues.ContainsKey(p.Name)) + { + _propertyValues[p.Name] = p.Default ?? ""; + } + } + + // --- FIX 2: Handle the Service Dropdown --- + // Check if this profile has a saved Service (e.g., "AMZN") + if (_propertyValues.TryGetValue("Service", out var serviceObj)) + { + string serviceName = serviceObj.ToString(); + + // Only try to switch if the service actually exists in the list + if (comboService.Items.Contains(serviceName)) + { + // Temporarily disable _isLoaded to change dropdown without triggering a save loop + bool wasLoaded = _isLoaded; + _isLoaded = false; + + comboService.SelectedItem = serviceName; + + _isLoaded = wasLoaded; + } + } + else if (comboService.SelectedItem != null) + { + // If profile has NO service saved, save the current one to it + _propertyValues["Service"] = comboService.SelectedItem.ToString(); + } + + // --- FIX 3: Bind to PropertyGrid and Refresh --- + pgProfile.SelectedObject = new DynamicObject(_propertyValues, _parameterDefinitions); + pgProfile.Refresh(); // Crucial to prevent grey box + + UpdateCommandPreview(); + } + } + catch (Exception ex) + { + MessageBox.Show($"Error loading profile: {ex.Message}"); + } + } + + // ========================================================= + // 5. SAVING LOGIC + // ========================================================= + private void SaveCurrentProfile() + { + // Don't save if we are loading or if nothing is selected + if (!_isLoaded || comboProfiles.SelectedItem == null) return; + + string name = comboProfiles.SelectedItem.ToString(); + SaveProfileFile(name); + } + + private void SaveProfileFile(string name) + { + try + { + // Ensure the currently selected service is saved into the dictionary + if (comboService.SelectedItem != null) + { + _propertyValues["Service"] = comboService.SelectedItem.ToString(); + } + + string path = Path.Combine(_profilesDir, $"{name}.json"); + File.WriteAllText(path, JsonConvert.SerializeObject(_propertyValues, Formatting.Indented)); + } + catch (Exception ex) + { + AppendLog($"[Error Saving Profile]: {ex.Message}"); + } + } + + private void SaveGlobalConfig() + { + if (!_isLoaded) return; + + _appSettings.RootPath = txtRootPath.Text; + _appSettings.BinaryName = txtBinName.Text; + + try + { + File.WriteAllText(_configPath, JsonConvert.SerializeObject(_appSettings, Formatting.Indented)); + } + catch + { + // Ignore errors while typing (file might be busy) + } + } + + // ========================================================= + // 6. COMMAND GENERATION + // ========================================================= + private void UpdateCommandPreview() + { + if (!_isLoaded) return; + + StringBuilder sb = new StringBuilder(); + + // 1. Start with binary and command + // e.g., "uv run unshackle dl " + sb.Append($"{txtBinName.Text} run unshackle dl "); + + // 2. Loop through all parameters in the grid + foreach (var p in _parameterDefinitions) + { + // If value doesn't exist in our data, skip it if (!_propertyValues.TryGetValue(p.Name, out object val)) continue; - // Handle Booleans - if (p.Type == "Bool" && val is bool b && b) + // Case A: Boolean Flags (e.g., --no-mux) + if (p.Type == "Bool" && val is bool isTrue && isTrue) { sb.Append($"{p.Flag} "); } - // Handle Text/Selection/Numbers + // Case B: Text / Selection / Number else if (p.Type != "Bool" && val != null) { string sVal = val.ToString(); - // Skip empty, "any", or "0" (for default bitrates) + + // Validation: + // 1. Not empty + // 2. Not "any" (Default) + // 3. Not "0" (Default for numbers) if (!string.IsNullOrWhiteSpace(sVal) && sVal != "any" && sVal != "0") { - if (sVal.Contains(" ")) sVal = $"\"{sVal}\""; + // Quote the value if it has spaces + if (sVal.Contains(" ")) + { + sVal = $"\"{sVal}\""; + } + sb.Append($"{p.Flag} {sVal} "); } } } + // 3. Append Service and URL + // e.g., "AMZN https://..." sb.Append($"{comboService.Text} {txtURL.Text}"); + + // 4. Update UI txtCommandPreview.Text = sb.ToString(); } - private void btnEditServiceConfig_Click(object? sender, EventArgs e) - { - string yamlPath = Path.Combine(txtRootPath.Text, "unshackle", "services", comboService.Text, "config.yaml"); - if (File.Exists(yamlPath)) - Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true }); - else - MessageBox.Show($"No config.yaml found for {comboService.Text}"); - } - + // ========================================================= + // 7. CLI EXECUTION (The Run Button) + // ========================================================= private async void btnRun_Click(object? sender, EventArgs e) { string finalCmd = txtCommandPreview.Text; + if (string.IsNullOrWhiteSpace(finalCmd)) return; + // Disable buttons while running ToggleUI(true); + txtLog.AppendText($"> Executing: {finalCmd}{Environment.NewLine}"); - try { await Task.Run(() => RunCli(finalCmd)); } - catch (Exception ex) { AppendLog($"[ERROR]: {ex.Message}"); } - finally { ToggleUI(false); } + try + { + // Run in background thread to keep UI responsive + await Task.Run(() => RunCli(finalCmd)); + } + catch (Exception ex) + { + AppendLog($"[ERROR]: {ex.Message}"); + } + finally + { + // Re-enable buttons + ToggleUI(false); + } } private void RunCli(string cmd) { + // We use CMD.EXE instead of PowerShell to avoid Antivirus false positives ProcessStartInfo psi = new ProcessStartInfo { FileName = "cmd.exe", - // /c = Run command and then terminate - // chcp 65001 = Force console to use UTF-8 encoding - // && = Run the actual command immediately after setting encoding - Arguments = $"/c chcp 65001 && {cmd}", + Arguments = $"/c chcp 65001 && {cmd}", // Force UTF-8 WorkingDirectory = txtRootPath.Text, RedirectStandardOutput = true, RedirectStandardError = true, @@ -188,20 +419,24 @@ namespace UnshackleGUI StandardOutputEncoding = Encoding.UTF8 }; - // Ensure Python scripts know to use UTF-8 + // Set Environment variable for Python psi.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8"; using (_currentProcess = Process.Start(psi)) { if (_currentProcess == null) return; + // Handle Output _currentProcess.OutputDataReceived += (s, e) => { - // Filter out the noisy "Active code page: 65001" message from CMD + // Filter out the "Active code page: 65001" noise if (!string.IsNullOrEmpty(e.Data) && !e.Data.Contains("Active code page")) + { AppendLog(e.Data); + } }; + // Handle Errors _currentProcess.ErrorDataReceived += (s, e) => AppendLog(e.Data); _currentProcess.BeginOutputReadLine(); @@ -210,104 +445,39 @@ namespace UnshackleGUI } } - private void AppendLog(string? text) - { - if (string.IsNullOrEmpty(text)) return; - this.BeginInvoke(new Action(() => { txtLog.AppendText(text + Environment.NewLine); })); - } - - private void ApplyDarkTheme() - { - this.BackColor = Color.FromArgb(30, 30, 30); - this.ForeColor = Color.White; - foreach (Control c in this.Controls) UpdateControlTheme(c); - } - - private void UpdateControlTheme(Control c) - { - c.BackColor = Color.FromArgb(45, 45, 48); - c.ForeColor = Color.White; - if (c is TextBox tb) tb.BorderStyle = BorderStyle.FixedSingle; - if (c is Button btn) { btn.FlatStyle = FlatStyle.Flat; btn.FlatAppearance.BorderColor = Color.Gray; } - if (c is PropertyGrid pg) - { - pg.BackColor = Color.FromArgb(37, 37, 38); - pg.ViewBackColor = Color.FromArgb(37, 37, 38); - pg.ViewForeColor = Color.White; - pg.LineColor = Color.FromArgb(45, 45, 48); - } - foreach (Control child in c.Controls) UpdateControlTheme(child); - } - - private void SaveConfig() - { - _appSettings.RootPath = txtRootPath.Text; - _appSettings.BinaryName = txtBinName.Text; - File.WriteAllText(_configPath, JsonConvert.SerializeObject(_appSettings, Formatting.Indented)); - } - - private void RefreshFolders() - { - comboService.Items.Clear(); - string path = Path.Combine(txtRootPath.Text, "unshackle", "services"); - if (Directory.Exists(path)) - { - var dirs = Directory.GetDirectories(path).Select(Path.GetFileName).ToArray(); - comboService.Items.AddRange(dirs.Cast().ToArray()); - if (comboService.Items.Count > 0) comboService.SelectedIndex = 0; - } - } - - - private void ComboProfiles_SelectedIndexChanged(object? sender, EventArgs e) - { - if (comboProfiles.SelectedItem == null) return; - - string selected = comboProfiles.SelectedItem.ToString(); - - if (_appSettings.Profiles.TryGetValue(selected, out var values)) - { - _propertyValues = new Dictionary(values); - - pgProfile.SelectedObject = - new DynamicObject(_propertyValues, _parameterDefinitions); - - UpdateCommandPreview(); - } - } - - + // ========================================================= + // 8. PROFILE MANAGEMENT (Add/Remove/Save Buttons) + // ========================================================= private void btnAddProfile_Click(object? sender, EventArgs e) { - string name = Microsoft.VisualBasic.Interaction.InputBox( - "Enter profile name:", - "New Profile", - "NewProfile"); + string name = Microsoft.VisualBasic.Interaction.InputBox("Enter profile name:", "New Profile", ""); - if (string.IsNullOrWhiteSpace(name)) - return; + if (string.IsNullOrWhiteSpace(name)) return; - if (_appSettings.Profiles.ContainsKey(name)) + // Sanitize filename (remove illegal characters like / \ : *) + foreach (char c in Path.GetInvalidFileNameChars()) + { + name = name.Replace(c, '_'); + } + + // Check if exists + if (File.Exists(Path.Combine(_profilesDir, $"{name}.json"))) { MessageBox.Show("Profile already exists."); return; } - // Clone current settings into new profile - _appSettings.Profiles[name] = - new Dictionary(_propertyValues); + // Save current settings as the new profile + SaveProfileFile(name); - comboProfiles.DataSource = null; - comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList(); + // Reload list and select new profile + RefreshProfileList(); comboProfiles.SelectedItem = name; - - SaveConfig(); } private void btnRemoveProfile_Click(object? sender, EventArgs e) { - if (comboProfiles.SelectedItem == null) - return; + if (comboProfiles.SelectedItem == null) return; string selected = comboProfiles.SelectedItem.ToString(); @@ -317,50 +487,233 @@ namespace UnshackleGUI return; } - if (!_appSettings.Profiles.ContainsKey(selected)) + string path = Path.Combine(_profilesDir, $"{selected}.json"); + + if (File.Exists(path)) + { + File.Delete(path); + } + + RefreshProfileList(); + } + + private void btnSaveProfile_Click(object? sender, EventArgs e) + { + string newName = comboProfiles.Text.Trim(); + + // 1. Validation + if (string.IsNullOrWhiteSpace(newName)) + { + MessageBox.Show("Please enter a profile name."); return; + } - _appSettings.Profiles.Remove(selected); + foreach (char c in Path.GetInvalidFileNameChars()) newName = newName.Replace(c, '_'); - comboProfiles.DataSource = null; - comboProfiles.DataSource = _appSettings.Profiles.Keys.ToList(); + // 2. CHECK: Did the name change? + if (newName != _lastLoadedProfile && !string.IsNullOrEmpty(_lastLoadedProfile)) + { + // Special Case: Cannot rename Default + if (_lastLoadedProfile == "Default") + { + // Just create new, don't ask to rename Default + SaveProfileFile(newName); + RefreshProfileList(); + comboProfiles.SelectedItem = newName; + MessageBox.Show($"Created new profile '{newName}' from Default.", "New Profile Created"); + return; + } - comboProfiles.SelectedIndex = 0; + // Ask the user what to do + DialogResult choice = MessageBox.Show( + $"You changed the name from '{_lastLoadedProfile}' to '{newName}'.\n\n" + + "Click YES to RENAME (Delete old).\n" + + "Click NO to CREATE COPY (Keep old).", + "Rename or Copy?", + MessageBoxButtons.YesNoCancel, + MessageBoxIcon.Question); - SaveConfig(); + if (choice == DialogResult.Cancel) return; + + if (choice == DialogResult.Yes) // RENAME + { + // Save New + SaveProfileFile(newName); + + // Delete Old + string oldPath = Path.Combine(_profilesDir, $"{_lastLoadedProfile}.json"); + if (File.Exists(oldPath)) File.Delete(oldPath); + + RefreshProfileList(); + comboProfiles.SelectedItem = newName; // This updates _lastLoadedProfile automatically + MessageBox.Show($"Renamed to '{newName}'."); + } + else // CREATE COPY + { + SaveProfileFile(newName); + RefreshProfileList(); + comboProfiles.SelectedItem = newName; + MessageBox.Show($"Created copy '{newName}'."); + } + } + else + { + // 3. Name didn't change (Normal Save) + SaveProfileFile(newName); + MessageBox.Show($"Profile '{newName}' saved!", "Saved", MessageBoxButtons.OK, MessageBoxIcon.Information); + } } - private void ToggleUI(bool r) => this.BeginInvoke(new Action(() => { btnRun.Enabled = !r; btnStop.Visible = r; })); - private void btnStop_Click(object? sender, EventArgs e) { _currentProcess?.Kill(true); } - private void btnClearLog_Click(object? sender, EventArgs e) => txtLog.Clear(); + // ========================================================= + // 9. HELPER METHODS & THEME + // ========================================================= + + private void AppendLog(string? text) + { + if (string.IsNullOrEmpty(text)) return; + + // Ensure we update the UI on the main thread + this.BeginInvoke(new Action(() => + { + txtLog.AppendText(text + Environment.NewLine); + })); + } + + private void ToggleUI(bool isRunning) + { + this.BeginInvoke(new Action(() => + { + btnRun.Enabled = !isRunning; + btnStop.Visible = isRunning; + })); + } + + private void btnStop_Click(object? sender, EventArgs e) + { + if (_currentProcess != null && !_currentProcess.HasExited) + { + _currentProcess.Kill(true); + } + } + + private void btnClearLog_Click(object? sender, EventArgs e) + { + txtLog.Clear(); + } + private void btnBrowse_Click(object? sender, EventArgs e) { - using (var fbd = new FolderBrowserDialog()) if (fbd.ShowDialog() == DialogResult.OK) { txtRootPath.Text = fbd.SelectedPath; SaveConfig(); RefreshFolders(); } + using (var fbd = new FolderBrowserDialog()) + { + if (fbd.ShowDialog() == DialogResult.OK) + { + txtRootPath.Text = fbd.SelectedPath; + SaveGlobalConfig(); + RefreshFolders(); + } + } } + private void btnOpenCookies_Click(object? sender, EventArgs e) { - string p = Path.Combine(txtRootPath.Text, "unshackle", "cookies", comboService.Text); - Directory.CreateDirectory(p); Process.Start("explorer.exe", p); + string path = Path.Combine(txtRootPath.Text, "unshackle", "cookies", comboService.Text); + Directory.CreateDirectory(path); + Process.Start("explorer.exe", path); } + private void btnEditYaml_Click(object? sender, EventArgs e) { string yamlPath = Path.Combine(txtRootPath.Text, "unshackle/unshackle.yaml"); - if (File.Exists(yamlPath)) Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true }); + if (File.Exists(yamlPath)) + { + Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true }); + } + } + + private void btnEditServiceConfig_Click(object? sender, EventArgs e) + { + string yamlPath = Path.Combine(txtRootPath.Text, "unshackle", "services", comboService.Text, "config.yaml"); + if (File.Exists(yamlPath)) + { + Process.Start(new ProcessStartInfo(yamlPath) { UseShellExecute = true }); + } + else + { + MessageBox.Show($"No config.yaml found for {comboService.Text}"); + } + } + + private void RefreshFolders() + { + comboService.Items.Clear(); + string path = Path.Combine(txtRootPath.Text, "unshackle", "services"); + + if (Directory.Exists(path)) + { + var dirs = Directory.GetDirectories(path).Select(Path.GetFileName).ToArray(); + comboService.Items.AddRange(dirs.Cast().ToArray()); + + if (comboService.Items.Count > 0) + { + comboService.SelectedIndex = 0; + } + } + } + + private void ApplyDarkTheme() + { + this.BackColor = Color.FromArgb(30, 30, 30); + this.ForeColor = Color.White; + + foreach (Control c in this.Controls) + { + UpdateControlTheme(c); + } + } + + private void UpdateControlTheme(Control c) + { + c.BackColor = Color.FromArgb(45, 45, 48); + c.ForeColor = Color.White; + + if (c is TextBox tb) + { + tb.BorderStyle = BorderStyle.FixedSingle; + } + + if (c is Button btn) + { + btn.FlatStyle = FlatStyle.Flat; + btn.FlatAppearance.BorderColor = Color.Gray; + } + + if (c is PropertyGrid pg) + { + pg.BackColor = Color.FromArgb(37, 37, 38); + pg.ViewBackColor = Color.FromArgb(37, 37, 38); + pg.ViewForeColor = Color.White; + pg.LineColor = Color.FromArgb(45, 45, 48); + } + + // Recursive call for panels and group boxes + foreach (Control child in c.Controls) + { + UpdateControlTheme(child); + } } } + // ========================================================= + // 10. DATA MODELS + // ========================================================= - // --- DATA MODELS --- public class AppSettings { public string RootPath { get; set; } = @"C:\DEVINE\unshackle"; public string BinaryName { get; set; } = "uv"; - - public Dictionary> Profiles { get; set; } - = new Dictionary>(); } public class UnshackleParameter