Compare commits
No commits in common. "main" and "master" have entirely different histories.
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
45
.gitignore
vendored
45
.gitignore
vendored
@ -1,8 +1,7 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
@ -30,6 +29,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
@ -83,8 +83,6 @@ StyleCopReport.xml
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
@ -93,7 +91,6 @@ StyleCopReport.xml
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
@ -297,17 +294,6 @@ node_modules/
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
@ -364,9 +350,6 @@ ASALocalRun/
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
@ -377,26 +360,4 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
FodyWeavers.xsd
|
||||
13
App.config
Normal file
13
App.config
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
<appSettings>
|
||||
<!-- Empty = %AppData%\D2Multi\accounts.xml (see D2AccountsXmlStore.DefaultPath) -->
|
||||
<add key="AccountsXmlPath" value="" />
|
||||
<add key="D2RExePath" value="E:\Games2\Diablo II Resurrected\D2R.exe" />
|
||||
<!-- Optional: full path to Sysinternals handle64.exe. Empty = D2R folder\handle64.exe -->
|
||||
<add key="Handle64ExePath" value="" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
180
CharacterMissionGridRow.cs
Normal file
180
CharacterMissionGridRow.cs
Normal file
@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
/// <summary>Grid row binding for <see cref="D2Character"/> mission flags across difficulties.</summary>
|
||||
public sealed class CharacterMissionGridRow : INotifyPropertyChanged
|
||||
{
|
||||
readonly D2Character _character;
|
||||
|
||||
public CharacterMissionGridRow(D2Character character)
|
||||
{
|
||||
_character = character ?? throw new ArgumentNullException(nameof(character));
|
||||
D2CharacterMissionHelper.EnsureAllMissions(_character);
|
||||
}
|
||||
|
||||
public D2Character Character => _character;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _character.Name ?? string.Empty;
|
||||
set
|
||||
{
|
||||
var v = value ?? string.Empty;
|
||||
if (string.Equals(_character.Name, v, StringComparison.Ordinal))
|
||||
return;
|
||||
_character.Name = v;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
|
||||
public string Class
|
||||
{
|
||||
get => _character.Class ?? string.Empty;
|
||||
set
|
||||
{
|
||||
var v = value ?? string.Empty;
|
||||
if (string.Equals(_character.Class, v, StringComparison.Ordinal))
|
||||
return;
|
||||
_character.Class = v;
|
||||
OnPropertyChanged(nameof(Class));
|
||||
}
|
||||
}
|
||||
|
||||
public bool NormalA1
|
||||
{
|
||||
get => Mission(D2Level.Normal).A1_Enpowered;
|
||||
set => SetA1(D2Level.Normal, value);
|
||||
}
|
||||
|
||||
public bool NormalA5
|
||||
{
|
||||
get => Mission(D2Level.Normal).A5_Socket;
|
||||
set => SetA5(D2Level.Normal, value);
|
||||
}
|
||||
|
||||
public bool NightmareA1
|
||||
{
|
||||
get => Mission(D2Level.Nightmare).A1_Enpowered;
|
||||
set => SetA1(D2Level.Nightmare, value);
|
||||
}
|
||||
|
||||
public bool NightmareA5
|
||||
{
|
||||
get => Mission(D2Level.Nightmare).A5_Socket;
|
||||
set => SetA5(D2Level.Nightmare, value);
|
||||
}
|
||||
|
||||
public bool HellA1
|
||||
{
|
||||
get => Mission(D2Level.Hell).A1_Enpowered;
|
||||
set => SetA1(D2Level.Hell, value);
|
||||
}
|
||||
|
||||
public bool HellA5
|
||||
{
|
||||
get => Mission(D2Level.Hell).A5_Socket;
|
||||
set => SetA5(D2Level.Hell, value);
|
||||
}
|
||||
|
||||
/// <summary>Count of quest rewards not yet claimed (false = still available).</summary>
|
||||
public int MissionsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
var n = 0;
|
||||
foreach (var lvl in AllLevels)
|
||||
{
|
||||
var m = Mission(lvl);
|
||||
if (!m.A1_Enpowered) n++;
|
||||
if (!m.A5_Socket) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly D2Level[] AllLevels = { D2Level.Normal, D2Level.Nightmare, D2Level.Hell };
|
||||
|
||||
D2Mission Mission(D2Level level) => D2CharacterMissionHelper.GetMission(_character, level);
|
||||
|
||||
void SetA1(D2Level level, bool value)
|
||||
{
|
||||
var m = Mission(level);
|
||||
if (m.A1_Enpowered == value) return;
|
||||
m.A1_Enpowered = value;
|
||||
OnPropertyChanged(PropForA1(level));
|
||||
OnPropertyChanged(nameof(MissionsAvailable));
|
||||
}
|
||||
|
||||
void SetA5(D2Level level, bool value)
|
||||
{
|
||||
var m = Mission(level);
|
||||
if (m.A5_Socket == value) return;
|
||||
m.A5_Socket = value;
|
||||
OnPropertyChanged(PropForA5(level));
|
||||
OnPropertyChanged(nameof(MissionsAvailable));
|
||||
}
|
||||
|
||||
static string PropForA1(D2Level level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case D2Level.Normal: return nameof(NormalA1);
|
||||
case D2Level.Nightmare: return nameof(NightmareA1);
|
||||
default: return nameof(HellA1);
|
||||
}
|
||||
}
|
||||
|
||||
static string PropForA5(D2Level level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case D2Level.Normal: return nameof(NormalA5);
|
||||
case D2Level.Nightmare: return nameof(NightmareA5);
|
||||
default: return nameof(HellA5);
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
void OnPropertyChanged(string name) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
|
||||
public static List<CharacterMissionGridRow> FromAccount(D2Account account)
|
||||
{
|
||||
if (account?.Characters == null || account.Characters.Count == 0)
|
||||
return new List<CharacterMissionGridRow>();
|
||||
return account.Characters.Select(c => new CharacterMissionGridRow(c)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
static class D2CharacterMissionHelper
|
||||
{
|
||||
public static void EnsureAllMissions(D2Character character)
|
||||
{
|
||||
if (character.Missions == null)
|
||||
character.Missions = new List<D2Mission>();
|
||||
foreach (D2Level lvl in Enum.GetValues(typeof(D2Level)))
|
||||
{
|
||||
if (character.Missions.All(m => m.Level != lvl))
|
||||
character.Missions.Add(new D2Mission { Level = lvl });
|
||||
}
|
||||
}
|
||||
|
||||
public static D2Mission GetMission(D2Character character, D2Level level)
|
||||
{
|
||||
EnsureAllMissions(character);
|
||||
return character.Missions.First(m => m.Level == level);
|
||||
}
|
||||
|
||||
public static D2Character CreateNewCharacter()
|
||||
{
|
||||
var c = new D2Character { Name = "New character", Class = string.Empty, Missions = new List<D2Mission>() };
|
||||
EnsureAllMissions(c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
164
CharacterMissionsForm.Designer.cs
generated
Normal file
164
CharacterMissionsForm.Designer.cs
generated
Normal file
@ -0,0 +1,164 @@
|
||||
namespace D2Multi
|
||||
{
|
||||
partial class CharacterMissionsForm
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
components.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
void InitializeComponent()
|
||||
{
|
||||
this.dgvCharacters = new System.Windows.Forms.DataGridView();
|
||||
this.btnAddCharacter = new System.Windows.Forms.Button();
|
||||
this.btnRemoveCharacter = new System.Windows.Forms.Button();
|
||||
this.btnOk = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.panelBottom = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.flowLeftButtons = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.flowRightButtons = new System.Windows.Forms.FlowLayoutPanel();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCharacters)).BeginInit();
|
||||
this.panelBottom.SuspendLayout();
|
||||
this.flowLeftButtons.SuspendLayout();
|
||||
this.flowRightButtons.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// dgvCharacters
|
||||
//
|
||||
this.dgvCharacters.AllowUserToAddRows = false;
|
||||
this.dgvCharacters.AllowUserToDeleteRows = false;
|
||||
this.dgvCharacters.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvCharacters.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dgvCharacters.Location = new System.Drawing.Point(0, 0);
|
||||
this.dgvCharacters.Name = "dgvCharacters";
|
||||
this.dgvCharacters.RowHeadersWidth = 40;
|
||||
this.dgvCharacters.RowTemplate.Height = 24;
|
||||
this.dgvCharacters.Size = new System.Drawing.Size(1183, 401);
|
||||
this.dgvCharacters.TabIndex = 0;
|
||||
//
|
||||
// btnAddCharacter
|
||||
//
|
||||
this.btnAddCharacter.AutoSize = true;
|
||||
this.btnAddCharacter.Location = new System.Drawing.Point(3, 3);
|
||||
this.btnAddCharacter.Name = "btnAddCharacter";
|
||||
this.btnAddCharacter.Size = new System.Drawing.Size(120, 28);
|
||||
this.btnAddCharacter.TabIndex = 1;
|
||||
this.btnAddCharacter.Text = "Add character";
|
||||
this.btnAddCharacter.UseVisualStyleBackColor = true;
|
||||
this.btnAddCharacter.Click += new System.EventHandler(this.BtnAddCharacter_Click);
|
||||
//
|
||||
// btnRemoveCharacter
|
||||
//
|
||||
this.btnRemoveCharacter.AutoSize = true;
|
||||
this.btnRemoveCharacter.Location = new System.Drawing.Point(129, 3);
|
||||
this.btnRemoveCharacter.Name = "btnRemoveCharacter";
|
||||
this.btnRemoveCharacter.Size = new System.Drawing.Size(140, 28);
|
||||
this.btnRemoveCharacter.TabIndex = 2;
|
||||
this.btnRemoveCharacter.Text = "Remove character";
|
||||
this.btnRemoveCharacter.UseVisualStyleBackColor = true;
|
||||
this.btnRemoveCharacter.Click += new System.EventHandler(this.BtnRemoveCharacter_Click);
|
||||
//
|
||||
// btnOk
|
||||
//
|
||||
this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.btnOk.Location = new System.Drawing.Point(505, 3);
|
||||
this.btnOk.Name = "btnOk";
|
||||
this.btnOk.Size = new System.Drawing.Size(75, 28);
|
||||
this.btnOk.TabIndex = 3;
|
||||
this.btnOk.Text = "OK";
|
||||
this.btnOk.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.btnCancel.Location = new System.Drawing.Point(424, 3);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(75, 28);
|
||||
this.btnCancel.TabIndex = 4;
|
||||
this.btnCancel.Text = "Cancel";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// panelBottom
|
||||
//
|
||||
this.panelBottom.ColumnCount = 2;
|
||||
this.panelBottom.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.panelBottom.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
||||
this.panelBottom.Controls.Add(this.flowLeftButtons, 0, 0);
|
||||
this.panelBottom.Controls.Add(this.flowRightButtons, 1, 0);
|
||||
this.panelBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.panelBottom.Location = new System.Drawing.Point(0, 401);
|
||||
this.panelBottom.Name = "panelBottom";
|
||||
this.panelBottom.RowCount = 1;
|
||||
this.panelBottom.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.panelBottom.Size = new System.Drawing.Size(1183, 40);
|
||||
this.panelBottom.TabIndex = 5;
|
||||
//
|
||||
// flowLeftButtons
|
||||
//
|
||||
this.flowLeftButtons.AutoSize = true;
|
||||
this.flowLeftButtons.Controls.Add(this.btnAddCharacter);
|
||||
this.flowLeftButtons.Controls.Add(this.btnRemoveCharacter);
|
||||
this.flowLeftButtons.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.flowLeftButtons.Location = new System.Drawing.Point(3, 3);
|
||||
this.flowLeftButtons.Name = "flowLeftButtons";
|
||||
this.flowLeftButtons.Size = new System.Drawing.Size(585, 34);
|
||||
this.flowLeftButtons.TabIndex = 0;
|
||||
this.flowLeftButtons.WrapContents = false;
|
||||
//
|
||||
// flowRightButtons
|
||||
//
|
||||
this.flowRightButtons.AutoSize = true;
|
||||
this.flowRightButtons.Controls.Add(this.btnOk);
|
||||
this.flowRightButtons.Controls.Add(this.btnCancel);
|
||||
this.flowRightButtons.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.flowRightButtons.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
|
||||
this.flowRightButtons.Location = new System.Drawing.Point(594, 3);
|
||||
this.flowRightButtons.Name = "flowRightButtons";
|
||||
this.flowRightButtons.Padding = new System.Windows.Forms.Padding(0, 0, 3, 0);
|
||||
this.flowRightButtons.Size = new System.Drawing.Size(586, 34);
|
||||
this.flowRightButtons.TabIndex = 1;
|
||||
this.flowRightButtons.WrapContents = false;
|
||||
//
|
||||
// CharacterMissionsForm
|
||||
//
|
||||
this.AcceptButton = this.btnOk;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.btnCancel;
|
||||
this.ClientSize = new System.Drawing.Size(1183, 441);
|
||||
this.Controls.Add(this.dgvCharacters);
|
||||
this.Controls.Add(this.panelBottom);
|
||||
this.MinimizeBox = false;
|
||||
this.MinimumSize = new System.Drawing.Size(700, 400);
|
||||
this.Name = "CharacterMissionsForm";
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Characters";
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvCharacters)).EndInit();
|
||||
this.panelBottom.ResumeLayout(false);
|
||||
this.panelBottom.PerformLayout();
|
||||
this.flowLeftButtons.ResumeLayout(false);
|
||||
this.flowLeftButtons.PerformLayout();
|
||||
this.flowRightButtons.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.DataGridView dgvCharacters;
|
||||
private System.Windows.Forms.Button btnAddCharacter;
|
||||
private System.Windows.Forms.Button btnRemoveCharacter;
|
||||
private System.Windows.Forms.Button btnOk;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.TableLayoutPanel panelBottom;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowLeftButtons;
|
||||
private System.Windows.Forms.FlowLayoutPanel flowRightButtons;
|
||||
}
|
||||
}
|
||||
141
CharacterMissionsForm.cs
Normal file
141
CharacterMissionsForm.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
public partial class CharacterMissionsForm : Form
|
||||
{
|
||||
readonly D2Account _account;
|
||||
readonly List<D2Character> _editingCharacters;
|
||||
BindingList<CharacterMissionGridRow> _rows;
|
||||
BindingSource _source;
|
||||
|
||||
public CharacterMissionsForm(D2Account account, string accountLabel)
|
||||
{
|
||||
if (account == null)
|
||||
throw new ArgumentNullException(nameof(account));
|
||||
_account = account;
|
||||
_editingCharacters = CloneCharacterList(account.Characters);
|
||||
InitializeComponent();
|
||||
Text = "Characters — " + (accountLabel ?? account.Name ?? "Account");
|
||||
BuildGrid();
|
||||
}
|
||||
|
||||
static List<D2Character> CloneCharacterList(IEnumerable<D2Character> src)
|
||||
{
|
||||
var list = new List<D2Character>();
|
||||
if (src == null)
|
||||
return list;
|
||||
foreach (var c in src)
|
||||
{
|
||||
if (c == null)
|
||||
continue;
|
||||
list.Add(CloneCharacter(c));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static D2Character CloneCharacter(D2Character c)
|
||||
{
|
||||
var missions = c.Missions == null
|
||||
? new List<D2Mission>()
|
||||
: c.Missions.Where(m => m != null).Select(m => new D2Mission
|
||||
{
|
||||
Level = m.Level,
|
||||
A1_Enpowered = m.A1_Enpowered,
|
||||
A5_Socket = m.A5_Socket,
|
||||
}).ToList();
|
||||
return new D2Character { Name = c.Name, Class = c.Class, Missions = missions };
|
||||
}
|
||||
|
||||
void BuildGrid()
|
||||
{
|
||||
var wrapAccount = new D2Account { Characters = _editingCharacters };
|
||||
_rows = new BindingList<CharacterMissionGridRow>(CharacterMissionGridRow.FromAccount(wrapAccount));
|
||||
_source = new BindingSource { DataSource = _rows };
|
||||
dgvCharacters.AutoGenerateColumns = false;
|
||||
dgvCharacters.Columns.Clear();
|
||||
|
||||
void AddText(string prop, string header, int width)
|
||||
{
|
||||
dgvCharacters.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = prop,
|
||||
HeaderText = header,
|
||||
Width = width,
|
||||
});
|
||||
}
|
||||
|
||||
void AddBool(string prop, string header, string tip, int width)
|
||||
{
|
||||
var col = new DataGridViewCheckBoxColumn
|
||||
{
|
||||
DataPropertyName = prop,
|
||||
HeaderText = header,
|
||||
Width = width,
|
||||
ThreeState = false,
|
||||
};
|
||||
col.ToolTipText = tip;
|
||||
dgvCharacters.Columns.Add(col);
|
||||
}
|
||||
|
||||
AddText(nameof(CharacterMissionGridRow.Name), "Character", 140);
|
||||
AddText(nameof(CharacterMissionGridRow.Class), "Class", 100);
|
||||
const string tipDone = "Checked = reward taken (no longer available). Unchecked = still available.";
|
||||
AddBool(nameof(CharacterMissionGridRow.NormalA1), "Normal A1", tipDone, 85);
|
||||
AddBool(nameof(CharacterMissionGridRow.NormalA5), "Normal A5", tipDone, 85);
|
||||
AddBool(nameof(CharacterMissionGridRow.NightmareA1), "NM A1", tipDone, 70);
|
||||
AddBool(nameof(CharacterMissionGridRow.NightmareA5), "NM A5", tipDone, 70);
|
||||
AddBool(nameof(CharacterMissionGridRow.HellA1), "Hell A1", tipDone, 75);
|
||||
AddBool(nameof(CharacterMissionGridRow.HellA5), "Hell A5", tipDone, 75);
|
||||
dgvCharacters.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CharacterMissionGridRow.MissionsAvailable),
|
||||
HeaderText = "Available",
|
||||
ReadOnly = true,
|
||||
Width = 80,
|
||||
ToolTipText = "Count of rewards not yet claimed (A1 + A5 across all difficulties).",
|
||||
});
|
||||
|
||||
dgvCharacters.DataSource = _source;
|
||||
}
|
||||
|
||||
void BtnAddCharacter_Click(object sender, EventArgs e)
|
||||
{
|
||||
var c = D2CharacterMissionHelper.CreateNewCharacter();
|
||||
_editingCharacters.Add(c);
|
||||
_rows.Add(new CharacterMissionGridRow(c));
|
||||
if (_rows.Count > 0)
|
||||
dgvCharacters.CurrentCell = dgvCharacters.Rows[_rows.Count - 1].Cells[0];
|
||||
}
|
||||
|
||||
void BtnRemoveCharacter_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (_source.Current is CharacterMissionGridRow row)
|
||||
{
|
||||
_editingCharacters.Remove(row.Character);
|
||||
_rows.Remove(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
if (DialogResult == DialogResult.OK)
|
||||
{
|
||||
dgvCharacters.EndEdit();
|
||||
_source.EndEdit();
|
||||
if (_editingCharacters.Any(c => string.IsNullOrWhiteSpace(c?.Name)))
|
||||
{
|
||||
e.Cancel = true;
|
||||
MessageBox.Show(this, "Each character needs a name.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
_account.Characters = _editingCharacters;
|
||||
}
|
||||
base.OnFormClosing(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
CharacterMissionsForm.resx
Normal file
120
CharacterMissionsForm.resx
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
67
D2Account.cs
Normal file
67
D2Account.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
public class D2Account : INotifyPropertyChanged
|
||||
{
|
||||
private string _name;
|
||||
private string _email;
|
||||
private string _encryptedPassword;
|
||||
private string _password;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set { if (_name == value) return; _name = value; OnPropertyChanged(nameof(Name)); }
|
||||
}
|
||||
|
||||
public string Email
|
||||
{
|
||||
get => _email;
|
||||
set { if (_email == value) return; _email = value; OnPropertyChanged(nameof(Email)); }
|
||||
}
|
||||
|
||||
/// <summary>DPAPI-protected secret, Base64. Persisted to XML only.</summary>
|
||||
public string EncryptedPassword
|
||||
{
|
||||
get => _encryptedPassword;
|
||||
set { if (_encryptedPassword == value) return; _encryptedPassword = value; OnPropertyChanged(nameof(EncryptedPassword)); }
|
||||
}
|
||||
|
||||
/// <summary>Transient plain text for editing; never written to XML.</summary>
|
||||
[XmlIgnore]
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set { if (_password == value) return; _password = value; OnPropertyChanged(nameof(Password)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
void OnPropertyChanged(string name) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
|
||||
public List<D2Character> Characters { get; set; }
|
||||
}
|
||||
|
||||
public class D2Character
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Class { get; set; }
|
||||
public List<D2Mission> Missions { get; set; }
|
||||
}
|
||||
public enum D2Level
|
||||
{
|
||||
Normal,
|
||||
Nightmare,
|
||||
Hell
|
||||
}
|
||||
public class D2Mission
|
||||
{
|
||||
public D2Level Level { get; set; }
|
||||
public bool A1_Enpowered { get; set; }
|
||||
public bool A5_Socket { get; set; }
|
||||
}
|
||||
}
|
||||
36
D2AccountCrypto.cs
Normal file
36
D2AccountCrypto.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
static class D2AccountCrypto
|
||||
{
|
||||
static readonly byte[] Entropy = Encoding.UTF8.GetBytes("D2Multi.Accounts.v1");
|
||||
|
||||
public static string Protect(string plain)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plain))
|
||||
return string.Empty;
|
||||
var bytes = Encoding.UTF8.GetBytes(plain);
|
||||
var protectedBytes = ProtectedData.Protect(bytes, Entropy, DataProtectionScope.CurrentUser);
|
||||
return Convert.ToBase64String(protectedBytes);
|
||||
}
|
||||
|
||||
public static string Unprotect(string base64Cipher)
|
||||
{
|
||||
if (string.IsNullOrEmpty(base64Cipher))
|
||||
return string.Empty;
|
||||
try
|
||||
{
|
||||
var protectedBytes = Convert.FromBase64String(base64Cipher);
|
||||
var bytes = ProtectedData.Unprotect(protectedBytes, Entropy, DataProtectionScope.CurrentUser);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
D2AccountPersisted.cs
Normal file
34
D2AccountPersisted.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
/// <summary>XML shape only; encryption is applied in <see cref="D2AccountsXmlStore"/> when saving.</summary>
|
||||
public class D2AccountPersisted
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
[XmlElement("EncryptedPassword")]
|
||||
public string EncryptedPassword { get; set; }
|
||||
|
||||
[XmlElement("Character")]
|
||||
public List<D2CharacterPersisted> Characters { get; set; }
|
||||
}
|
||||
|
||||
public class D2CharacterPersisted
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Class { get; set; }
|
||||
|
||||
[XmlElement("Mission")]
|
||||
public List<D2MissionPersisted> Missions { get; set; }
|
||||
}
|
||||
|
||||
public class D2MissionPersisted
|
||||
{
|
||||
public D2Level Level { get; set; }
|
||||
public bool A1_Enpowered { get; set; }
|
||||
public bool A5_Socket { get; set; }
|
||||
}
|
||||
}
|
||||
144
D2AccountsXmlStore.cs
Normal file
144
D2AccountsXmlStore.cs
Normal file
@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
[XmlRoot("Accounts")]
|
||||
public class D2AccountsRoot
|
||||
{
|
||||
[XmlElement("Account")]
|
||||
public List<D2AccountPersisted> AccountList { get; set; } = new List<D2AccountPersisted>();
|
||||
}
|
||||
|
||||
static class D2AccountsXmlStore
|
||||
{
|
||||
public static string DefaultPath =>
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "accounts.xml");
|
||||
|
||||
public static List<D2Account> Load(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return new List<D2Account>();
|
||||
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
var ser = new XmlSerializer(typeof(D2AccountsRoot));
|
||||
var root = (D2AccountsRoot)ser.Deserialize(stream);
|
||||
if (root?.AccountList == null)
|
||||
return new List<D2Account>();
|
||||
|
||||
var result = new List<D2Account>(root.AccountList.Count);
|
||||
foreach (var p in root.AccountList)
|
||||
{
|
||||
var cipher = p?.EncryptedPassword ?? string.Empty;
|
||||
result.Add(new D2Account
|
||||
{
|
||||
Name = p?.Name,
|
||||
Email = p?.Email,
|
||||
EncryptedPassword = cipher,
|
||||
Password = D2AccountCrypto.Unprotect(cipher),
|
||||
Characters = MapCharactersFromPersisted(p?.Characters),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Builds XML from accounts; encrypts only here from <see cref="D2Account.Password"/> when non-empty.</summary>
|
||||
public static void Save(string path, IEnumerable<D2Account> accounts)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var persisted = (accounts ?? Enumerable.Empty<D2Account>())
|
||||
.Select(ToPersistedForXml)
|
||||
.ToList();
|
||||
|
||||
var root = new D2AccountsRoot { AccountList = persisted };
|
||||
using (var stream = File.Create(path))
|
||||
{
|
||||
var ser = new XmlSerializer(typeof(D2AccountsRoot));
|
||||
ser.Serialize(stream, root);
|
||||
}
|
||||
}
|
||||
|
||||
static D2AccountPersisted ToPersistedForXml(D2Account a)
|
||||
{
|
||||
string encrypted;
|
||||
if (!string.IsNullOrEmpty(a?.Password))
|
||||
encrypted = D2AccountCrypto.Protect(a.Password);
|
||||
else
|
||||
encrypted = a?.EncryptedPassword ?? string.Empty;
|
||||
|
||||
return new D2AccountPersisted
|
||||
{
|
||||
Name = a?.Name,
|
||||
Email = a?.Email,
|
||||
EncryptedPassword = encrypted,
|
||||
Characters = MapCharactersToPersisted(a?.Characters),
|
||||
};
|
||||
}
|
||||
|
||||
static List<D2Character> MapCharactersFromPersisted(List<D2CharacterPersisted> list)
|
||||
{
|
||||
if (list == null || list.Count == 0)
|
||||
return new List<D2Character>();
|
||||
var result = new List<D2Character>(list.Count);
|
||||
foreach (var cp in list)
|
||||
{
|
||||
if (cp == null)
|
||||
continue;
|
||||
var missions = new List<D2Mission>();
|
||||
if (cp.Missions != null)
|
||||
{
|
||||
foreach (var mp in cp.Missions)
|
||||
{
|
||||
if (mp == null)
|
||||
continue;
|
||||
missions.Add(new D2Mission
|
||||
{
|
||||
Level = mp.Level,
|
||||
A1_Enpowered = mp.A1_Enpowered,
|
||||
A5_Socket = mp.A5_Socket,
|
||||
});
|
||||
}
|
||||
}
|
||||
result.Add(new D2Character { Name = cp.Name, Class = cp.Class, Missions = missions });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<D2CharacterPersisted> MapCharactersToPersisted(List<D2Character> list)
|
||||
{
|
||||
if (list == null || list.Count == 0)
|
||||
return null;
|
||||
var result = new List<D2CharacterPersisted>(list.Count);
|
||||
foreach (var c in list)
|
||||
{
|
||||
if (c == null)
|
||||
continue;
|
||||
var mpList = new List<D2MissionPersisted>();
|
||||
if (c.Missions != null)
|
||||
{
|
||||
foreach (var m in c.Missions)
|
||||
{
|
||||
if (m == null)
|
||||
continue;
|
||||
mpList.Add(new D2MissionPersisted
|
||||
{
|
||||
Level = m.Level,
|
||||
A1_Enpowered = m.A1_Enpowered,
|
||||
A5_Socket = m.A5_Socket,
|
||||
});
|
||||
}
|
||||
}
|
||||
result.Add(new D2CharacterPersisted { Name = c.Name, Class = c.Class, Missions = mpList });
|
||||
}
|
||||
return result.Count == 0 ? null : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
D2Multi.csproj
Normal file
103
D2Multi.csproj
Normal file
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3555BB82-5288-4726-B3D7-306E4D2A55F7}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>D2Multi</RootNamespace>
|
||||
<AssemblyName>D2Multi</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>D2.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="D2Account.cs" />
|
||||
<Compile Include="D2AccountPersisted.cs" />
|
||||
<Compile Include="D2AccountCrypto.cs" />
|
||||
<Compile Include="D2AccountsXmlStore.cs" />
|
||||
<Compile Include="D2RInstanceCheckKiller.cs" />
|
||||
<Compile Include="CharacterMissionGridRow.cs" />
|
||||
<Compile Include="CharacterMissionsForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CharacterMissionsForm.Designer.cs">
|
||||
<DependentUpon>CharacterMissionsForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Form1.Designer.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="D2.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
25
D2Multi.sln
Normal file
25
D2Multi.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36623.8 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D2Multi", "D2Multi.csproj", "{3555BB82-5288-4726-B3D7-306E4D2A55F7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3555BB82-5288-4726-B3D7-306E4D2A55F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3555BB82-5288-4726-B3D7-306E4D2A55F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3555BB82-5288-4726-B3D7-306E4D2A55F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3555BB82-5288-4726-B3D7-306E4D2A55F7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {4B4E4A93-4265-44F8-8043-49BB854F59B7}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
174
D2RInstanceCheckKiller.cs
Normal file
174
D2RInstanceCheckKiller.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
/// <summary>
|
||||
/// Closes D2R "DiabloII Check For Other Instances" event handles using Sysinternals Handle64
|
||||
/// (same approach as Scripts/KillInstanceChecks.ps1). Requires administrator rights for Handle64.
|
||||
/// </summary>
|
||||
static class D2RInstanceCheckKiller
|
||||
{
|
||||
static readonly Regex PidLine = new Regex(
|
||||
@"^D2R\.exe\s+pid:\s*(?<pid>\d+)",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
static readonly Regex EventHandleLine = new Regex(
|
||||
@"^\s*(?<handle>\S+)\s*:\s*Event.*DiabloII Check For Other Instances",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
|
||||
public static bool IsRunningElevated()
|
||||
{
|
||||
using (var id = WindowsIdentity.GetCurrent())
|
||||
{
|
||||
var p = new WindowsPrincipal(id);
|
||||
return p.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves Handle64 path: explicit config path if set and exists, else handle64.exe next to D2R folder.
|
||||
/// </summary>
|
||||
public static string ResolveHandle64Path(string d2rDirectory, string configuredPath)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(configuredPath))
|
||||
{
|
||||
var t = configuredPath.Trim();
|
||||
if (File.Exists(t))
|
||||
return t;
|
||||
}
|
||||
if (string.IsNullOrEmpty(d2rDirectory))
|
||||
return null;
|
||||
var nextToD2r = Path.Combine(d2rDirectory, "handle64.exe");
|
||||
return File.Exists(nextToD2r) ? nextToD2r : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates D2R.exe handles and closes matching instance-check events. Returns true if Handle64 ran and exited successfully (including when nothing needed closing).
|
||||
/// </summary>
|
||||
public static bool TryCloseInstanceCheckHandles(string handle64Path, string d2rDirectory, out string detailLog)
|
||||
{
|
||||
var log = new StringBuilder();
|
||||
if (string.IsNullOrEmpty(handle64Path) || !File.Exists(handle64Path))
|
||||
{
|
||||
detailLog = "handle64.exe not found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string listOutput;
|
||||
int listExit;
|
||||
if (!RunHandle64(handle64Path, d2rDirectory, "-accepteula -a -p D2R.exe", out listOutput, out listExit, log))
|
||||
{
|
||||
detailLog = log.ToString();
|
||||
return false;
|
||||
}
|
||||
if (listExit != 0)
|
||||
{
|
||||
log.AppendLine("List pass exit code: " + listExit);
|
||||
detailLog = log.ToString();
|
||||
if(listOutput.Contains("No matching handles found"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentPid = "";
|
||||
var closed = 0;
|
||||
using (var reader = new StringReader(listOutput ?? string.Empty))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
var pm = PidLine.Match(line);
|
||||
if (pm.Success)
|
||||
{
|
||||
currentPid = pm.Groups["pid"].Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
var hm = EventHandleLine.Match(line);
|
||||
if (!hm.Success)
|
||||
continue;
|
||||
|
||||
var handleId = hm.Groups["handle"].Value.Trim();
|
||||
if (string.IsNullOrEmpty(currentPid))
|
||||
{
|
||||
log.AppendLine("Skip handle " + handleId + " (no prior D2R.exe pid line).");
|
||||
continue;
|
||||
}
|
||||
|
||||
log.AppendLine("Closing pid=" + currentPid + " handle=" + handleId);
|
||||
string closeOut;
|
||||
int closeExit;
|
||||
var closeArgs = "-accepteula -p " + currentPid + " -c " + handleId + " -y";
|
||||
if (!RunHandle64(handle64Path, d2rDirectory, closeArgs, out closeOut, out closeExit, log))
|
||||
{
|
||||
detailLog = log.ToString();
|
||||
return false;
|
||||
}
|
||||
if (closeExit != 0)
|
||||
log.AppendLine("Close exit code: " + closeExit);
|
||||
closed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (closed == 0)
|
||||
log.AppendLine("No 'DiabloII Check For Other Instances' event handles found for D2R.exe.");
|
||||
detailLog = log.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool RunHandle64(string handle64Path, string workingDirectory, string arguments,
|
||||
out string stdout, out int exitCode, StringBuilder log)
|
||||
{
|
||||
stdout = null;
|
||||
exitCode = -1;
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = handle64Path,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = string.IsNullOrEmpty(workingDirectory) ? Path.GetDirectoryName(handle64Path) : workingDirectory,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
using (var p = Process.Start(psi))
|
||||
{
|
||||
if (p == null)
|
||||
{
|
||||
log.AppendLine("Process.Start returned null.");
|
||||
return false;
|
||||
}
|
||||
var outTask = Task.Run(() => p.StandardOutput.ReadToEnd());
|
||||
var errTask = Task.Run(() => p.StandardError.ReadToEnd());
|
||||
if (!p.WaitForExit(120000))
|
||||
{
|
||||
try { p.Kill(); } catch { /* ignore */ }
|
||||
log.AppendLine("Handle64 timed out.");
|
||||
return false;
|
||||
}
|
||||
stdout = outTask.GetAwaiter().GetResult();
|
||||
var err = errTask.GetAwaiter().GetResult();
|
||||
exitCode = p.ExitCode;
|
||||
if (!string.IsNullOrEmpty(err))
|
||||
log.AppendLine("stderr: " + err.Trim());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.AppendLine(ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Form1.Designer.cs
generated
Normal file
154
Form1.Designer.cs
generated
Normal file
@ -0,0 +1,154 @@
|
||||
namespace D2Multi
|
||||
{
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
this.dgvAccounts = new System.Windows.Forms.DataGridView();
|
||||
this.btnLaunch = new System.Windows.Forms.Button();
|
||||
this.cboServer = new System.Windows.Forms.ComboBox();
|
||||
this.btnAdd = new System.Windows.Forms.Button();
|
||||
this.btnDelete = new System.Windows.Forms.Button();
|
||||
this.btnSave = new System.Windows.Forms.Button();
|
||||
this.txExeAddictional = new System.Windows.Forms.TextBox();
|
||||
this.btnViewCharacters = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvAccounts)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// dgvAccounts
|
||||
//
|
||||
this.dgvAccounts.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dgvAccounts.Location = new System.Drawing.Point(12, 12);
|
||||
this.dgvAccounts.Name = "dgvAccounts";
|
||||
this.dgvAccounts.RowHeadersWidth = 51;
|
||||
this.dgvAccounts.RowTemplate.Height = 24;
|
||||
this.dgvAccounts.Size = new System.Drawing.Size(779, 255);
|
||||
this.dgvAccounts.TabIndex = 0;
|
||||
//
|
||||
// btnLaunch
|
||||
//
|
||||
this.btnLaunch.Location = new System.Drawing.Point(796, 70);
|
||||
this.btnLaunch.Name = "btnLaunch";
|
||||
this.btnLaunch.Size = new System.Drawing.Size(158, 36);
|
||||
this.btnLaunch.TabIndex = 1;
|
||||
this.btnLaunch.Text = "Launch";
|
||||
this.btnLaunch.UseVisualStyleBackColor = true;
|
||||
this.btnLaunch.Click += new System.EventHandler(this.btnLaunch_Click);
|
||||
//
|
||||
// cboServer
|
||||
//
|
||||
this.cboServer.FormattingEnabled = true;
|
||||
this.cboServer.Location = new System.Drawing.Point(797, 12);
|
||||
this.cboServer.Name = "cboServer";
|
||||
this.cboServer.Size = new System.Drawing.Size(158, 24);
|
||||
this.cboServer.TabIndex = 2;
|
||||
//
|
||||
// btnAdd
|
||||
//
|
||||
this.btnAdd.Location = new System.Drawing.Point(12, 276);
|
||||
this.btnAdd.Name = "btnAdd";
|
||||
this.btnAdd.Size = new System.Drawing.Size(100, 32);
|
||||
this.btnAdd.TabIndex = 3;
|
||||
this.btnAdd.Text = "Add";
|
||||
this.btnAdd.UseVisualStyleBackColor = true;
|
||||
this.btnAdd.Click += new System.EventHandler(this.BtnAdd_Click);
|
||||
//
|
||||
// btnDelete
|
||||
//
|
||||
this.btnDelete.Location = new System.Drawing.Point(118, 276);
|
||||
this.btnDelete.Name = "btnDelete";
|
||||
this.btnDelete.Size = new System.Drawing.Size(100, 32);
|
||||
this.btnDelete.TabIndex = 4;
|
||||
this.btnDelete.Text = "Delete";
|
||||
this.btnDelete.UseVisualStyleBackColor = true;
|
||||
this.btnDelete.Click += new System.EventHandler(this.BtnDelete_Click);
|
||||
//
|
||||
// btnSave
|
||||
//
|
||||
this.btnSave.Location = new System.Drawing.Point(224, 276);
|
||||
this.btnSave.Name = "btnSave";
|
||||
this.btnSave.Size = new System.Drawing.Size(100, 32);
|
||||
this.btnSave.TabIndex = 5;
|
||||
this.btnSave.Text = "Save";
|
||||
this.btnSave.UseVisualStyleBackColor = true;
|
||||
this.btnSave.Click += new System.EventHandler(this.BtnSave_Click);
|
||||
//
|
||||
// txExeAddictional
|
||||
//
|
||||
this.txExeAddictional.Location = new System.Drawing.Point(797, 42);
|
||||
this.txExeAddictional.Name = "txExeAddictional";
|
||||
this.txExeAddictional.Size = new System.Drawing.Size(157, 22);
|
||||
this.txExeAddictional.TabIndex = 6;
|
||||
this.txExeAddictional.Text = "-mod PY -txt";
|
||||
//
|
||||
// btnViewCharacters
|
||||
//
|
||||
this.btnViewCharacters.Location = new System.Drawing.Point(330, 276);
|
||||
this.btnViewCharacters.Name = "btnViewCharacters";
|
||||
this.btnViewCharacters.Size = new System.Drawing.Size(158, 32);
|
||||
this.btnViewCharacters.TabIndex = 7;
|
||||
this.btnViewCharacters.Text = "View Characters";
|
||||
this.btnViewCharacters.UseVisualStyleBackColor = true;
|
||||
this.btnViewCharacters.Click += new System.EventHandler(this.btnViewCharacters_Click);
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(966, 315);
|
||||
this.Controls.Add(this.btnViewCharacters);
|
||||
this.Controls.Add(this.txExeAddictional);
|
||||
this.Controls.Add(this.btnSave);
|
||||
this.Controls.Add(this.btnDelete);
|
||||
this.Controls.Add(this.btnAdd);
|
||||
this.Controls.Add(this.cboServer);
|
||||
this.Controls.Add(this.btnLaunch);
|
||||
this.Controls.Add(this.dgvAccounts);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "Form1";
|
||||
this.Text = "D2 Accounts";
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
((System.ComponentModel.ISupportInitialize)(this.dgvAccounts)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.DataGridView dgvAccounts;
|
||||
private System.Windows.Forms.Button btnLaunch;
|
||||
private System.Windows.Forms.ComboBox cboServer;
|
||||
private System.Windows.Forms.Button btnAdd;
|
||||
private System.Windows.Forms.Button btnDelete;
|
||||
private System.Windows.Forms.Button btnSave;
|
||||
private System.Windows.Forms.TextBox txExeAddictional;
|
||||
private System.Windows.Forms.Button btnViewCharacters;
|
||||
}
|
||||
}
|
||||
|
||||
315
Form1.cs
Normal file
315
Form1.cs
Normal file
@ -0,0 +1,315 @@
|
||||
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<D2Account> _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<D2Account>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4632
Form1.resx
Normal file
4632
Form1.resx
Normal file
File diff suppressed because it is too large
Load Diff
22
Program.cs
Normal file
22
Program.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace D2Multi
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Properties/AssemblyInfo.cs
Normal file
33
Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("D2Multi")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("D2Multi")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("3555bb82-5288-4726-b3d7-306e4d2a55f7")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
71
Properties/Resources.Designer.cs
generated
Normal file
71
Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace D2Multi.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("D2Multi.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Properties/Resources.resx
Normal file
117
Properties/Resources.resx
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
30
Properties/Settings.Designer.cs
generated
Normal file
30
Properties/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace D2Multi.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Properties/Settings.settings
Normal file
7
Properties/Settings.settings
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
63
Scripts/D2R_launcher_Soc.ps1
Normal file
63
Scripts/D2R_launcher_Soc.ps1
Normal file
@ -0,0 +1,63 @@
|
||||
#== D2R singleclient transparent launcher by Chobot - https://github.com/Chobotz/D2R-multiclient-tools / https://forums.d2jsp.org/user.php?i=1208377 ==================
|
||||
$bnet_email = 'yuanson.chen@gmail.com'
|
||||
$bnet_password = 'd1124380788'
|
||||
#default_region values can be eu/us/kr - default is applied when you do not provide any input and just press enter during region selection
|
||||
$default_region = 'us'
|
||||
$launch_mode =''
|
||||
#======================================== Send me lot of FGs ==========================================================================================================
|
||||
|
||||
|
||||
#============= Check for mandatory components and folder placement =====================================================================
|
||||
|
||||
$pc_username = [System.Environment]::UserName
|
||||
|
||||
if(![System.IO.File]::Exists("$PSScriptRoot\D2R.exe"))
|
||||
{
|
||||
Write-Host "Warning: Script needs to be placed in D2R installation folder. Use lnk shortcut to start it. Follow the installation instructions."
|
||||
Write-Host "Exiting now."
|
||||
Read-host "Press ENTER to continue..."
|
||||
Exit
|
||||
}
|
||||
|
||||
if(![System.IO.File]::Exists("$PSScriptRoot\handle64.exe"))
|
||||
{
|
||||
Write-Host "Warning: handle64.exe is missing in the current folder - Follow the installation instructions. You can get it from the Microsoft Official site: https://docs.microsoft.com/en-us/sysinternals/downloads/handle"
|
||||
Write-Host "Exiting now."
|
||||
read-host "Press ENTER to continue..."
|
||||
Exit
|
||||
}
|
||||
|
||||
#============= Preset email address in Bnet launcher ============
|
||||
|
||||
$bnet_config_path = "C:\Users\"+$pc_username+"\AppData\Roaming\Battle.net\Battle.net.config"
|
||||
$new_saved_accounts = "SavedAccountNames`": `"" +$bnet_email +"`","
|
||||
|
||||
|
||||
(Get-Content -Path $bnet_config_path) -replace "SavedAccountNames`": `".+@.+`",",$new_saved_accounts | Set-Content -Path $bnet_config_path
|
||||
|
||||
|
||||
#============= Let the user specify launch mode and region ======
|
||||
|
||||
|
||||
#Do {$launch_mode = Read-Host 'D2R - Select launch mode 1 or 2 (1 - Direct client, 2 - Bnet Launcher, Empty - Direct client)'}
|
||||
#while ($launch_mode -ne '1' -and $launch_mode -ne '2' -and $launch_mode -ne '')
|
||||
|
||||
|
||||
$region = 'none yet'
|
||||
|
||||
if($launch_mode -eq "1" -or $launch_mode -eq "")
|
||||
{
|
||||
Do { $region = Read-Host 'Specify region eu/us/kr (no input -> default region)';Write-Host "Selected region: $($region)";}
|
||||
while ($region -ne 'eu' -and $region -ne 'us' -and $region -ne 'kr' -and $region -ne '')
|
||||
if($region -eq '')
|
||||
{
|
||||
$region = $default_region
|
||||
}
|
||||
& "$PSScriptRoot\D2R.exe" -username $bnet_email -password $bnet_password -address $region'.actual.battle.net' -mod PY -txt
|
||||
|
||||
Write-Host 'Starting:'$region'.actual.battle.net'
|
||||
}else {
|
||||
& "$PSScriptRoot\Diablo II Resurrected Launcher.exe"
|
||||
}
|
||||
|
||||
#read-host "Press ENTER to continue..."
|
||||
54
Scripts/KillInstanceChecks.ps1
Normal file
54
Scripts/KillInstanceChecks.ps1
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
#============= Check for mandatory components and folder placement =====================================================================
|
||||
|
||||
$pc_username = [System.Environment]::UserName
|
||||
|
||||
if(![System.IO.File]::Exists("$PSScriptRoot\D2R.exe"))
|
||||
{
|
||||
Write-Host "Warning: Script needs to be placed in D2R installation folder. Use lnk shortcut to start it. Follow the installation instructions."
|
||||
Write-Host "Exiting now."
|
||||
Read-host "Press ENTER to continue..."
|
||||
Exit
|
||||
}
|
||||
|
||||
if(![System.IO.File]::Exists("$PSScriptRoot\handle64.exe"))
|
||||
{
|
||||
Write-Host "Warning: handle64.exe is missing in the current folder - Follow the installation instructions. You can get it from the Microsoft Official site: https://docs.microsoft.com/en-us/sysinternals/downloads/handle"
|
||||
Write-Host "Exiting now."
|
||||
read-host "Press ENTER to continue..."
|
||||
Exit
|
||||
}
|
||||
|
||||
#============= Enumerate D2R process handles and close the D2R instance handle to allow to start other clients - admin required ==========
|
||||
|
||||
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }
|
||||
|
||||
& "$PSScriptRoot\handle64.exe" -accepteula -a -p D2R.exe > $PSScriptRoot\d2r_handles.txt
|
||||
|
||||
$proc_id_populated = ""
|
||||
$handle_id_populated = ""
|
||||
|
||||
foreach($line in Get-Content $PSScriptRoot\d2r_handles.txt) {
|
||||
|
||||
|
||||
$proc_id = $line | Select-String -Pattern '^D2R.exe pid\: (?<g1>.+) ' | %{$_.Matches.Groups[1].value}
|
||||
if ($proc_id)
|
||||
{
|
||||
$proc_id_populated = $proc_id
|
||||
}
|
||||
$handle_id = $line | Select-String -Pattern '^(?<g2>.+): Event.*DiabloII Check For Other Instances' | %{$_.Matches.Groups[1].value}
|
||||
if ($handle_id)
|
||||
{
|
||||
$handle_id_populated = $handle_id
|
||||
}
|
||||
|
||||
if($handle_id){
|
||||
|
||||
Write-Host "Closing" $proc_id_populated $handle_id_populated
|
||||
& "$PSScriptRoot\handle64.exe" -p $proc_id_populated -c $handle_id_populated -y
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#read-host "Press ENTER to continue..."
|
||||
Loading…
x
Reference in New Issue
Block a user