Add automatic read/write items for all pocket items (1-40) (#51)

Decentralizes pocket injection logic from SysBotUI so that other auto-injectors can be passed in (future? like terrain/overworld items).

Adds validation to not modify items if the pocket data doesn't have the expected layout.

Closes #47 by implementing things in a more abstract & extendable way. Thanks @jfmherokiller for the example implementation which served as an inspiration!
This commit is contained in:
Kurt
2020-04-04 20:57:25 -07:00
committed by GitHub
parent 74f6157fa3
commit 1db185151d
19 changed files with 288 additions and 38 deletions

View File

@@ -0,0 +1,45 @@
using System;
namespace NHSE.Injection
{
public class AutoInjector
{
private readonly IDataInjector Injector;
private readonly Action<InjectionResult> AfterRead;
private readonly Action<InjectionResult> AfterWrite;
public bool AutoInjectEnabled { private get; set; }
public AutoInjector(IDataInjector inj, Action<InjectionResult> read, Action<InjectionResult> write)
{
Injector = inj;
AfterRead = read;
AfterWrite = write;
}
public void Validate() => Injector.Validate();
public InjectionResult Read(bool force = false)
{
if ((!AutoInjectEnabled && !force) || !Injector.Connected)
return InjectionResult.Skipped;
var result = Injector.Read();
AfterRead(result);
return result;
}
public InjectionResult Write(bool force = false)
{
if ((!AutoInjectEnabled && !force) || !Injector.Connected)
return InjectionResult.Skipped;
var result = Injector.Write();
AfterWrite(result);
return result;
}
public void SetWriteOffset(in uint offset)
{
Injector.WriteOffset = offset;
}
}
}

View File

@@ -0,0 +1,14 @@
namespace NHSE.Injection
{
public interface IDataInjector
{
bool ReadValidate(out byte[] data);
InjectionResult Read();
InjectionResult Write();
bool Validate();
bool Validate(byte[] data);
uint WriteOffset { set; }
bool Connected { get; }
}
}

View File

@@ -0,0 +1,9 @@
namespace NHSE.Injection
{
public interface IRAMReadWriter
{
bool Connected { get; }
byte[] ReadBytes(uint offset, int length);
void WriteBytes(byte[] data, uint offset);
}
}

View File

@@ -0,0 +1,10 @@
namespace NHSE.Injection
{
public enum InjectionResult
{
Skipped,
Success,
Fail,
Same,
}
}

View File

@@ -6,4 +6,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NHSE.Core\NHSE.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NHSE.Core;
namespace NHSE.Injection
{
public class PocketInjector : IDataInjector
{
private readonly IReadOnlyList<Item> Items;
private readonly IRAMReadWriter Bot;
public bool Connected => Bot.Connected;
public uint WriteOffset { private get; set; }
public PocketInjector(IReadOnlyList<Item> items, IRAMReadWriter bot)
{
Items = items;
Bot = bot;
}
private const int pocket = Item.SIZE * 20;
private const int size = (pocket * 2) + 0x18;
private const int shift = -0x18 - (Item.SIZE * 20);
private uint DataOffset => (uint)(WriteOffset + shift);
public bool ReadValidate(out byte[] data)
{
data = Bot.ReadBytes(DataOffset, size);
return Validate(data);
}
private byte[]? LastData;
public InjectionResult Read()
{
if (!ReadValidate(out var data))
return InjectionResult.Fail;
if (LastData?.SequenceEqual(data) == true)
return InjectionResult.Same;
var pocket2 = Items.Take(20).ToArray();
var pocket1 = Items.Skip(20).ToArray();
var p1 = Item.GetArray(data.Slice(0, pocket));
var p2 = Item.GetArray(data.Slice(pocket + 0x18, pocket));
for (int i = 0; i < pocket1.Length; i++)
pocket1[i].CopyFrom(p1[i]);
for (int i = 0; i < pocket2.Length; i++)
pocket2[i].CopyFrom(p2[i]);
LastData = data;
return InjectionResult.Success;
}
public InjectionResult Write()
{
if (!ReadValidate(out var data))
return InjectionResult.Fail;
var orig = (byte[])data.Clone();
var pocket2 = Items.Take(20).ToArray();
var pocket1 = Items.Skip(20).ToArray();
var p1 = Item.SetArray(pocket1);
var p2 = Item.SetArray(pocket2);
p1.CopyTo(data, 0);
p2.CopyTo(data, pocket + 0x18);
if (data.SequenceEqual(orig))
return InjectionResult.Same;
Bot.WriteBytes(data, DataOffset);
LastData = data;
return InjectionResult.Success;
}
public bool Validate() => ReadValidate(out _);
public bool Validate(byte[] data)
{
if (BitConverter.ToUInt32(data, pocket) > 20) // pouch21-39 count
return false;
for (int i = 4; i < 0x18; i += 4)
{
var val = BitConverter.ToInt32(data, pocket + i);
if (val != -1)
return false;
}
return true;
}
}
}

View File

@@ -3,12 +3,12 @@
namespace NHSE.Injection
{
public class SysBot
public class SysBot : IRAMReadWriter
{
public string IP = "192.168.1.65";
public int Port = 6000;
public Socket Connection = new Socket(SocketType.Stream, ProtocolType.Tcp);
public bool Connected;
public bool Connected { get; private set; }
private readonly object _sync = new object();

View File

@@ -16,6 +16,8 @@ public partial class ItemGridEditor : UserControl
private int Count => Items.Count;
private int Page;
private int ItemsPerPage;
public Action? ItemChanged { private get; set; }
private void ItemUpdated() => ItemChanged?.Invoke();
public ItemGridEditor(ItemEditor editor, IReadOnlyList<Item> items)
{
@@ -122,6 +124,7 @@ private void ClickSet(object sender, EventArgs e)
var index = SlotPictureBoxes.IndexOf(pb);
var item = SetItem(index);
pb.BackgroundImage = Sprites.GetImage(item, L_ItemName.Font);
ItemUpdated();
}
private void ClickDelete(object sender, EventArgs e)
@@ -133,6 +136,7 @@ private void ClickDelete(object sender, EventArgs e)
var item = GetItem(index);
item.Delete();
pb.BackgroundImage = Sprites.GetImage(item, L_ItemName.Font);
ItemUpdated();
}
private void ClickClone(object sender, EventArgs e)
@@ -149,6 +153,7 @@ private void ClickClone(object sender, EventArgs e)
var dest = GetItem(i);
dest.CopyFrom(item);
SlotPictureBoxes[i].BackgroundImage = Sprites.GetImage(item, L_ItemName.Font);
ItemUpdated();
}
System.Media.SystemSounds.Asterisk.Play();
}
@@ -200,6 +205,7 @@ public void LoadItems()
var item = GetItem(i);
SlotPictureBoxes[i].BackgroundImage = Sprites.GetImage(item, L_ItemName.Font);
}
ItemUpdated();
}
private void B_Clear_Click(object sender, EventArgs e)

View File

@@ -129,8 +129,7 @@ private void B_EditPlayerItems_Click(object sender, EventArgs e)
var p1 = pers.Pocket1;
var p2 = pers.Pocket2;
var items = p2.Concat(p1).ToArray();
int sys = p1.Count; // disjointed 20-20
using var editor = new PlayerItemEditor<Item>(items, 10, 4, sysbot: sys);
using var editor = new PlayerItemEditor<Item>(items, 10, 4, sysbot: true);
if (editor.ShowDialog() != DialogResult.OK)
return;

View File

@@ -18,6 +18,7 @@ protected override void Dispose(bool disposing)
components.Dispose();
}
base.Dispose(disposing);
ItemGrid.Dispose();
}
#region Windows Form Designer generated code

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using NHSE.Core;
using NHSE.Injection;
@@ -12,14 +12,14 @@ public partial class PlayerItemEditor<T> : Form where T : Item
{
private readonly IReadOnlyList<T> Items;
private readonly Action LoadItems;
private readonly int SysBotLength;
private readonly ItemGridEditor ItemGrid;
public PlayerItemEditor(IReadOnlyList<T> array, int width, int height, int sysbot = 0)
public PlayerItemEditor(IReadOnlyList<T> array, int width, int height, bool sysbot = false)
{
InitializeComponent();
Items = array;
var Editor = new ItemGridEditor(ItemEditor, Items) {Dock = DockStyle.Fill};
var Editor = ItemGrid = new ItemGridEditor(ItemEditor, Items) {Dock = DockStyle.Fill};
Editor.InitializeGrid(width, height);
PAN_Items.Controls.Add(Editor);
@@ -27,7 +27,7 @@ public PlayerItemEditor(IReadOnlyList<T> array, int width, int height, int sysbo
Editor.LoadItems();
DialogResult = DialogResult.Cancel;
LoadItems = () => Editor.LoadItems();
B_Inject.Visible = (SysBotLength = sysbot) > 0;
B_Inject.Visible = sysbot;
}
private void B_Cancel_Click(object sender, EventArgs e) => Close();
@@ -79,17 +79,24 @@ private void B_Inject_Click(object sender, EventArgs e)
return;
}
byte[] Write() => Items.Take(SysBotLength).SelectMany(z => z.ToBytesClass()).ToArray();
void Read(byte[] data)
void AfterRead(InjectionResult r)
{
var items = Item.GetArray(data);
for (int i = 0; i < items.Length && i < Items.Count; i++)
Items[i].CopyFrom(items[i]);
LoadItems();
if (r == InjectionResult.Success)
LoadItems();
}
static void AfterWrite(InjectionResult r)
{
Debug.WriteLine($"Write result: {r}");
System.Media.SystemSounds.Asterisk.Play();
}
var sysbot = new SysBotUI(Read, Write, InjectionType.Pouch);
var sb = new SysBotController(InjectionType.Pouch);
var pockInject = new PocketInjector(Items, sb.Bot);
var ai = new AutoInjector(pockInject, AfterRead, AfterWrite);
ItemGrid.ItemChanged = () => ai.Write(true);
var sysbot = new SysBotUI(ai, sb);
sysbot.Show();
}
}

View File

@@ -10,7 +10,7 @@ public class SysBotController
public SysBotController(InjectionType type) => Type = type;
private readonly InjectionType Type;
private readonly SysBot Bot = new SysBot();
public readonly SysBot Bot = new SysBot();
private readonly Settings Settings = Settings.Default;
public string IP => Settings.SysBotIP;

View File

@@ -28,16 +28,20 @@ protected override void Dispose(bool disposing)
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.B_Connect = new System.Windows.Forms.Button();
this.L_Port = new System.Windows.Forms.Label();
this.TB_Port = new System.Windows.Forms.TextBox();
this.L_IP = new System.Windows.Forms.Label();
this.TB_IP = new System.Windows.Forms.TextBox();
this.GB_Inject = new System.Windows.Forms.GroupBox();
this.CHK_AutoRead = new System.Windows.Forms.CheckBox();
this.CHK_AutoWrite = new System.Windows.Forms.CheckBox();
this.RamOffset = new System.Windows.Forms.TextBox();
this.L_Offset = new System.Windows.Forms.Label();
this.B_ReadCurrent = new System.Windows.Forms.Button();
this.B_WriteCurrent = new System.Windows.Forms.Button();
this.TIM_Interval = new System.Windows.Forms.Timer(this.components);
this.GB_Inject.SuspendLayout();
this.SuspendLayout();
//
@@ -89,6 +93,8 @@ private void InitializeComponent()
//
// GB_Inject
//
this.GB_Inject.Controls.Add(this.CHK_AutoRead);
this.GB_Inject.Controls.Add(this.CHK_AutoWrite);
this.GB_Inject.Controls.Add(this.RamOffset);
this.GB_Inject.Controls.Add(this.L_Offset);
this.GB_Inject.Controls.Add(this.B_ReadCurrent);
@@ -96,11 +102,33 @@ private void InitializeComponent()
this.GB_Inject.Enabled = false;
this.GB_Inject.Location = new System.Drawing.Point(16, 66);
this.GB_Inject.Name = "GB_Inject";
this.GB_Inject.Size = new System.Drawing.Size(149, 74);
this.GB_Inject.Size = new System.Drawing.Size(149, 119);
this.GB_Inject.TabIndex = 13;
this.GB_Inject.TabStop = false;
this.GB_Inject.Text = "Injector";
//
// CHK_AutoRead
//
this.CHK_AutoRead.AutoSize = true;
this.CHK_AutoRead.Location = new System.Drawing.Point(18, 93);
this.CHK_AutoRead.Name = "CHK_AutoRead";
this.CHK_AutoRead.Size = new System.Drawing.Size(74, 17);
this.CHK_AutoRead.TabIndex = 22;
this.CHK_AutoRead.Text = "AutoRead";
this.CHK_AutoRead.UseVisualStyleBackColor = true;
this.CHK_AutoRead.CheckedChanged += new System.EventHandler(this.CHK_AutoRead_CheckedChanged);
//
// CHK_AutoWrite
//
this.CHK_AutoWrite.AutoSize = true;
this.CHK_AutoWrite.Location = new System.Drawing.Point(18, 70);
this.CHK_AutoWrite.Name = "CHK_AutoWrite";
this.CHK_AutoWrite.Size = new System.Drawing.Size(73, 17);
this.CHK_AutoWrite.TabIndex = 21;
this.CHK_AutoWrite.Text = "AutoWrite";
this.CHK_AutoWrite.UseVisualStyleBackColor = true;
this.CHK_AutoWrite.CheckedChanged += new System.EventHandler(this.CHK_AutoWrite_CheckedChanged);
//
// RamOffset
//
this.RamOffset.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
@@ -110,6 +138,7 @@ private void InitializeComponent()
this.RamOffset.Size = new System.Drawing.Size(63, 20);
this.RamOffset.TabIndex = 20;
this.RamOffset.Text = "AC3B90C0";
this.RamOffset.TextChanged += new System.EventHandler(this.RamOffset_TextChanged);
//
// L_Offset
//
@@ -140,11 +169,15 @@ private void InitializeComponent()
this.B_WriteCurrent.UseVisualStyleBackColor = true;
this.B_WriteCurrent.Click += new System.EventHandler(this.B_WriteCurrent_Click);
//
// TIM_Interval
//
this.TIM_Interval.Interval = 5000;
//
// SysBotUI
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(184, 151);
this.ClientSize = new System.Drawing.Size(184, 197);
this.Controls.Add(this.GB_Inject);
this.Controls.Add(this.B_Connect);
this.Controls.Add(this.L_Port);
@@ -177,5 +210,8 @@ private void InitializeComponent()
private System.Windows.Forms.Button B_WriteCurrent;
private System.Windows.Forms.TextBox RamOffset;
private System.Windows.Forms.Label L_Offset;
private System.Windows.Forms.CheckBox CHK_AutoWrite;
private System.Windows.Forms.CheckBox CHK_AutoRead;
private System.Windows.Forms.Timer TIM_Interval;
}
}

View File

@@ -7,22 +7,25 @@ namespace NHSE.WinForms
{
public partial class SysBotUI : Form
{
private readonly Func<byte[]> ByteFetch;
private readonly Action<byte[]> ByteSet;
private readonly AutoInjector Injector;
private readonly SysBotController Bot;
public SysBotUI(Action<byte[]> read, Func<byte[]> write, InjectionType type)
public SysBotUI(AutoInjector injector, SysBotController c)
{
InitializeComponent();
Bot = new SysBotController(type);
ByteFetch = write;
ByteSet = read;
RamOffset.Text = Bot.GetDefaultOffset().ToString("X8");
Bot = c;
Injector = injector;
var offset = Bot.GetDefaultOffset();
Injector.SetWriteOffset(offset);
RamOffset.Text = offset.ToString("X8");
TB_IP.Text = Bot.IP;
TB_Port.Text = Bot.Port;
Bot.PopPrompt();
TIM_Interval.Tick += (s, e) => injector.Read();
}
private void B_Connect_Click(object sender, EventArgs e)
@@ -41,10 +44,13 @@ private void B_WriteCurrent_Click(object sender, EventArgs e)
return;
}
Injector.SetWriteOffset(offset);
try
{
var data = ByteFetch();
Bot.WriteBytes(data, offset);
var result = Injector.Write(true);
if (result != InjectionResult.Success)
WinFormsUtil.Alert(result.ToString());
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
@@ -56,18 +62,11 @@ private void B_WriteCurrent_Click(object sender, EventArgs e)
private void B_ReadCurrent_Click(object sender, EventArgs e)
{
var offset = StringUtil.GetHexValue(RamOffset.Text);
if (offset == 0)
{
WinFormsUtil.Error("Incorrect hex offset.");
return;
}
try
{
var data = ByteFetch();
var result = Bot.ReadBytes(offset, data.Length);
ByteSet(result);
var result = Injector.Read(true);
if (result != InjectionResult.Success)
WinFormsUtil.Alert(result.ToString());
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
@@ -76,5 +75,20 @@ private void B_ReadCurrent_Click(object sender, EventArgs e)
WinFormsUtil.Error(ex.Message);
}
}
private void CHK_AutoWrite_CheckedChanged(object sender, EventArgs e) => Injector.AutoInjectEnabled = CHK_AutoWrite.Checked;
private void CHK_AutoRead_CheckedChanged(object sender, EventArgs e) => TIM_Interval.Enabled = CHK_AutoRead.Checked;
private void RamOffset_TextChanged(object sender, EventArgs e)
{
var offset = StringUtil.GetHexValue(RamOffset.Text);
if (offset == 0)
{
WinFormsUtil.Error("Incorrect hex offset.");
return;
}
Injector.SetWriteOffset(offset);
}
}
}

View File

@@ -117,4 +117,7 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="TIM_Interval.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>