using System; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Threading; using System.Windows.Forms; namespace D2Multi { public partial class Form1 : Form { readonly string _xmlPath; readonly string _d2rPath; BindingList _accounts; BindingSource _bindingSource; DataGridViewTextBoxColumn _colPassword; public Form1() { InitializeComponent(); var cfgXml = ConfigurationManager.AppSettings["AccountsXmlPath"]; _xmlPath = string.IsNullOrWhiteSpace(cfgXml) ? D2AccountsXmlStore.DefaultPath : cfgXml.Trim(); _d2rPath = (ConfigurationManager.AppSettings["D2RExePath"] ?? string.Empty).Trim(); } void Form1_Load(object sender, EventArgs e) { InitBattleNetServerCombo(); var loaded = D2AccountsXmlStore.Load(_xmlPath).OrderBy(a => a.Name).ToList(); _accounts = new BindingList(loaded); _bindingSource = new BindingSource { DataSource = _accounts }; dgvAccounts.AutoGenerateColumns = false; dgvAccounts.Columns.Clear(); dgvAccounts.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(D2Account.Name), HeaderText = "Name", Width = 160 }); dgvAccounts.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = nameof(D2Account.Email), HeaderText = "Email", Width = 220 }); // Unbound: two-way bind to Password is unreliable here; sync explicitly from the model and CellValidating. _colPassword = new DataGridViewTextBoxColumn { HeaderText = "Password", Width = 160, ReadOnly = false }; dgvAccounts.Columns.Add(_colPassword); dgvAccounts.DataSource = _bindingSource; dgvAccounts.DataBindingComplete += DgvAccounts_DataBindingComplete; dgvAccounts.CellValidating += DgvAccounts_CellValidating; dgvAccounts.EditingControlShowing += DgvAccounts_EditingControlShowing; dgvAccounts.CellPainting += DgvAccounts_CellPainting; } void InitBattleNetServerCombo() { cboServer.DropDownStyle = ComboBoxStyle.DropDownList; cboServer.DataSource = new[] { new { Label = "America", Host = "us.actual.battle.net" }, new { Label = "Asia", Host = "kr.actual.battle.net" }, new { Label = "Europe", Host = "eu.actual.battle.net" }, }; cboServer.DisplayMember = "Label"; cboServer.ValueMember = "Host"; } void DgvAccounts_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { PushPasswordFromAccountsToGrid(); } void DgvAccounts_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (e.RowIndex < 0 || dgvAccounts.Columns[e.ColumnIndex] != _colPassword) return; var row = dgvAccounts.Rows[e.RowIndex]; if (row.IsNewRow || !(row.DataBoundItem is D2Account acc)) return; acc.Password = e.FormattedValue == null ? string.Empty : Convert.ToString(e.FormattedValue); } void PushPasswordFromAccountsToGrid() { foreach (DataGridViewRow row in dgvAccounts.Rows) { if (row.IsNewRow || !(row.DataBoundItem is D2Account acc)) continue; if (dgvAccounts.IsCurrentCellInEditMode && dgvAccounts.CurrentCell != null && dgvAccounts.CurrentCell.RowIndex == row.Index && dgvAccounts.CurrentCell.ColumnIndex == _colPassword.Index) continue; row.Cells[_colPassword.Index].Value = acc.Password ?? string.Empty; } } void DgvAccounts_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if (e.ColumnIndex < 0 || e.RowIndex < 0) return; if (dgvAccounts.Columns[e.ColumnIndex] != _colPassword) return; if (e.RowIndex >= dgvAccounts.Rows.Count || dgvAccounts.Rows[e.RowIndex].IsNewRow) return; if (dgvAccounts.IsCurrentCellInEditMode && dgvAccounts.CurrentCell != null && dgvAccounts.CurrentCell.RowIndex == e.RowIndex && dgvAccounts.CurrentCell.ColumnIndex == e.ColumnIndex) return; var val = e.Value as string; e.Paint(e.ClipBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground); if (!string.IsNullOrEmpty(val)) { var mask = new string('\u2022', Math.Min(val.Length, 32)); var fore = e.State.HasFlag(DataGridViewElementStates.Selected) ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor; TextRenderer.DrawText(e.Graphics, mask, e.CellStyle.Font, e.CellBounds, fore, TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine); } e.Handled = true; } void DgvAccounts_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) { if (dgvAccounts.CurrentCell?.OwningColumn == _colPassword && e.Control is TextBox tb) { tb.UseSystemPasswordChar = true; } } void BtnAdd_Click(object sender, EventArgs e) { var a = new D2Account { Name = "New account" }; _accounts.Add(a); _bindingSource.Position = _accounts.Count - 1; } void BtnDelete_Click(object sender, EventArgs e) { if (_bindingSource.Current is D2Account cur) { _accounts.Remove(cur); } } void BtnSave_Click(object sender, EventArgs e) { dgvAccounts.EndEdit(); _bindingSource.EndEdit(); try { D2AccountsXmlStore.Save(_xmlPath, _accounts.ToList()); MessageBox.Show(this, "Accounts saved.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show(this, ex.Message, "Save failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } } void btnLaunch_Click(object sender, EventArgs e) { dgvAccounts.EndEdit(); _bindingSource.EndEdit(); if (!(_bindingSource.Current is D2Account cur)) { MessageBox.Show(this, "Select an account.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var email = (cur.Email ?? string.Empty).Trim(); var password = cur.Password ?? string.Empty; if (email.Length == 0 || password.Length == 0) { MessageBox.Show(this, "Email and password are required.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var host = Convert.ToString(cboServer.SelectedValue); if (string.IsNullOrWhiteSpace(host)) { MessageBox.Show(this, "Select a region.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } if (!File.Exists(_d2rPath)) { MessageBox.Show(this, "D2R.exe was not found at:\n" + _d2rPath, "Launch failed", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var d2rDir = Path.GetDirectoryName(_d2rPath); if (string.IsNullOrEmpty(d2rDir)) d2rDir = Environment.CurrentDirectory; var handle64Cfg = (ConfigurationManager.AppSettings["Handle64ExePath"] ?? string.Empty).Trim(); var handle64 = D2RInstanceCheckKiller.ResolveHandle64Path(d2rDir, handle64Cfg); var killScript = Path.Combine(d2rDir, "KillInstanceChecks.ps1"); if (handle64 != null) { if (D2RInstanceCheckKiller.TryCloseInstanceCheckHandles(handle64, d2rDir, out var handleLog)) { Thread.Sleep(4000); } else if (File.Exists(killScript)) { RunKillInstanceChecksScript(d2rDir, killScript); } else { MessageBox.Show(this, "Could not clear D2R instance check (Handle64 failed). Run D2Multi as Administrator, " + "or place KillInstanceChecks.ps1 next to D2R.exe.\n\n" + handleLog, Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); } } else if (File.Exists(killScript)) { RunKillInstanceChecksScript(d2rDir, killScript); } var extra = (txExeAddictional.Text ?? string.Empty).Trim(); var args = "-username " + QuoteProcessArg(email) + " -password " + QuoteProcessArg(password) + " -address " + QuoteProcessArg(host); if (extra.Length > 0) args += " " + extra; var psi = new ProcessStartInfo { FileName = _d2rPath, Arguments = args, WorkingDirectory = d2rDir, UseShellExecute = false, }; try { Process.Start(psi); } catch (Exception ex) { MessageBox.Show(this, ex.Message, "Launch failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } } void RunKillInstanceChecksScript(string d2rDir, string killScript) { var killPsi = new ProcessStartInfo { FileName = "powershell.exe", Arguments = "-NoProfile -ExecutionPolicy Bypass -File \"" + killScript + "\"", WorkingDirectory = d2rDir, UseShellExecute = true, }; try { using (var p = Process.Start(killPsi)) { if (p != null) p.WaitForExit(15000); } Thread.Sleep(4000); } catch (Exception ex) { MessageBox.Show(this, "Could not run KillInstanceChecks.ps1:\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); } } static string QuoteProcessArg(string value) { if (value == null) value = string.Empty; if (value.Length == 0) return "\"\""; if (value.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) return value; return "\"" + value.Replace("\"", "\\\"") + "\""; } void btnViewCharacters_Click(object sender, EventArgs e) { if (!(_bindingSource.Current is D2Account cur)) { MessageBox.Show(this, "Select an account.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } using (var dlg = new CharacterMissionsForm(cur, cur.Name)) { dlg.ShowDialog(this); } } } }