diff --git a/.vs/Ledybot/v14/.suo b/.vs/Ledybot/v14/.suo new file mode 100644 index 0000000..83ea308 Binary files /dev/null and b/.vs/Ledybot/v14/.suo differ diff --git a/Ledybot.sln b/Ledybot.sln new file mode 100644 index 0000000..d613d3a --- /dev/null +++ b/Ledybot.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ledybot", "Ledybot\Ledybot.csproj", "{3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Ledybot/App.config b/Ledybot/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/Ledybot/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Ledybot/Form1.Designer.cs b/Ledybot/Form1.Designer.cs new file mode 100644 index 0000000..1d5221a --- /dev/null +++ b/Ledybot/Form1.Designer.cs @@ -0,0 +1,311 @@ +namespace Ledybot +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tb_IP = new System.Windows.Forms.TextBox(); + this.tb_Port = new System.Windows.Forms.TextBox(); + this.l_dummy = new System.Windows.Forms.Label(); + this.tb_PID = new System.Windows.Forms.TextBox(); + this.lb_dummy2 = new System.Windows.Forms.Label(); + this.btn_Connect = new System.Windows.Forms.Button(); + this.tb_PokemonToFind = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.tb_GiveAway = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.tb_Level = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.tb_Default = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tb_Folder = new System.Windows.Forms.TextBox(); + this.btn_Start = new System.Windows.Forms.Button(); + this.btn_Stop = new System.Windows.Forms.Button(); + this.lv_log = new System.Windows.Forms.ListView(); + this.Time = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Trainer = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Ditto = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Country = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.SubCountry = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.SuspendLayout(); + // + // tb_IP + // + this.tb_IP.Location = new System.Drawing.Point(13, 13); + this.tb_IP.Name = "tb_IP"; + this.tb_IP.Size = new System.Drawing.Size(100, 20); + this.tb_IP.TabIndex = 0; + this.tb_IP.Text = "192.168.178.48"; + // + // tb_Port + // + this.tb_Port.Location = new System.Drawing.Point(135, 12); + this.tb_Port.Name = "tb_Port"; + this.tb_Port.Size = new System.Drawing.Size(34, 20); + this.tb_Port.TabIndex = 1; + this.tb_Port.Text = "8000"; + // + // l_dummy + // + this.l_dummy.AutoSize = true; + this.l_dummy.Location = new System.Drawing.Point(119, 16); + this.l_dummy.Name = "l_dummy"; + this.l_dummy.Size = new System.Drawing.Size(10, 13); + this.l_dummy.TabIndex = 2; + this.l_dummy.Text = ":"; + // + // tb_PID + // + this.tb_PID.Location = new System.Drawing.Point(191, 12); + this.tb_PID.Name = "tb_PID"; + this.tb_PID.Size = new System.Drawing.Size(19, 20); + this.tb_PID.TabIndex = 3; + this.tb_PID.Text = "2b"; + // + // lb_dummy2 + // + this.lb_dummy2.AutoSize = true; + this.lb_dummy2.Location = new System.Drawing.Point(175, 16); + this.lb_dummy2.Name = "lb_dummy2"; + this.lb_dummy2.Size = new System.Drawing.Size(10, 13); + this.lb_dummy2.TabIndex = 4; + this.lb_dummy2.Text = "-"; + // + // btn_Connect + // + this.btn_Connect.Location = new System.Drawing.Point(216, 9); + this.btn_Connect.Name = "btn_Connect"; + this.btn_Connect.Size = new System.Drawing.Size(75, 23); + this.btn_Connect.TabIndex = 5; + this.btn_Connect.Text = "Connect"; + this.btn_Connect.UseVisualStyleBackColor = true; + this.btn_Connect.Click += new System.EventHandler(this.btn_Connect_Click); + // + // tb_PokemonToFind + // + this.tb_PokemonToFind.Location = new System.Drawing.Point(13, 70); + this.tb_PokemonToFind.Name = "tb_PokemonToFind"; + this.tb_PokemonToFind.Size = new System.Drawing.Size(100, 20); + this.tb_PokemonToFind.TabIndex = 6; + this.tb_PokemonToFind.Text = "Ledyba"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(13, 51); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(106, 13); + this.label1.TabIndex = 7; + this.label1.Text = "Deposited Pokemon:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 106); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(106, 13); + this.label2.TabIndex = 8; + this.label2.Text = "GiveAway Pokemon:"; + // + // tb_GiveAway + // + this.tb_GiveAway.Location = new System.Drawing.Point(12, 122); + this.tb_GiveAway.Name = "tb_GiveAway"; + this.tb_GiveAway.Size = new System.Drawing.Size(100, 20); + this.tb_GiveAway.TabIndex = 9; + this.tb_GiveAway.Text = "Ditto"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(125, 106); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(36, 13); + this.label3.TabIndex = 10; + this.label3.Text = "Level:"; + // + // tb_Level + // + this.tb_Level.Location = new System.Drawing.Point(128, 122); + this.tb_Level.Name = "tb_Level"; + this.tb_Level.Size = new System.Drawing.Size(33, 20); + this.tb_Level.TabIndex = 11; + this.tb_Level.Text = "91"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(13, 159); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(68, 13); + this.label4.TabIndex = 12; + this.label4.Text = "Default .pk7:"; + // + // tb_Default + // + this.tb_Default.Location = new System.Drawing.Point(12, 176); + this.tb_Default.Name = "tb_Default"; + this.tb_Default.Size = new System.Drawing.Size(354, 20); + this.tb_Default.TabIndex = 13; + this.tb_Default.Text = "C:/GiveAway/6IV.pk7"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(16, 203); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(101, 13); + this.label5.TabIndex = 14; + this.label5.Text = "Specific .pk7 folder:"; + // + // tb_Folder + // + this.tb_Folder.Location = new System.Drawing.Point(12, 219); + this.tb_Folder.Name = "tb_Folder"; + this.tb_Folder.Size = new System.Drawing.Size(354, 20); + this.tb_Folder.TabIndex = 15; + this.tb_Folder.Text = "C:/GiveAway/"; + // + // btn_Start + // + this.btn_Start.Location = new System.Drawing.Point(167, 120); + this.btn_Start.Name = "btn_Start"; + this.btn_Start.Size = new System.Drawing.Size(58, 23); + this.btn_Start.TabIndex = 16; + this.btn_Start.Text = "Start"; + this.btn_Start.UseVisualStyleBackColor = true; + this.btn_Start.Click += new System.EventHandler(this.btn_Start_Click); + // + // btn_Stop + // + this.btn_Stop.Location = new System.Drawing.Point(233, 120); + this.btn_Stop.Name = "btn_Stop"; + this.btn_Stop.Size = new System.Drawing.Size(58, 23); + this.btn_Stop.TabIndex = 17; + this.btn_Stop.Text = "Stop"; + this.btn_Stop.UseVisualStyleBackColor = true; + this.btn_Stop.Click += new System.EventHandler(this.btn_Stop_Click); + // + // lv_log + // + this.lv_log.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.Time, + this.Trainer, + this.Ditto, + this.Country, + this.SubCountry}); + this.lv_log.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.lv_log.Location = new System.Drawing.Point(13, 246); + this.lv_log.Name = "lv_log"; + this.lv_log.Size = new System.Drawing.Size(353, 262); + this.lv_log.TabIndex = 18; + this.lv_log.UseCompatibleStateImageBehavior = false; + this.lv_log.View = System.Windows.Forms.View.Details; + // + // Time + // + this.Time.Text = "Time"; + this.Time.Width = 40; + // + // Trainer + // + this.Trainer.Text = "Trainer"; + // + // Ditto + // + this.Ditto.Text = "Ditto"; + this.Ditto.Width = 50; + // + // Country + // + this.Country.Text = "Country"; + // + // SubCountry + // + this.SubCountry.Text = "SubCountry"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(378, 520); + this.Controls.Add(this.lv_log); + this.Controls.Add(this.btn_Stop); + this.Controls.Add(this.btn_Start); + this.Controls.Add(this.tb_Folder); + this.Controls.Add(this.label5); + this.Controls.Add(this.tb_Default); + this.Controls.Add(this.label4); + this.Controls.Add(this.tb_Level); + this.Controls.Add(this.label3); + this.Controls.Add(this.tb_GiveAway); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.tb_PokemonToFind); + this.Controls.Add(this.btn_Connect); + this.Controls.Add(this.lb_dummy2); + this.Controls.Add(this.tb_PID); + this.Controls.Add(this.l_dummy); + this.Controls.Add(this.tb_Port); + this.Controls.Add(this.tb_IP); + this.Name = "MainForm"; + this.Text = "Ledybot v1.0"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox tb_IP; + private System.Windows.Forms.TextBox tb_Port; + private System.Windows.Forms.Label l_dummy; + private System.Windows.Forms.TextBox tb_PID; + private System.Windows.Forms.Label lb_dummy2; + private System.Windows.Forms.Button btn_Connect; + private System.Windows.Forms.TextBox tb_PokemonToFind; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox tb_GiveAway; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox tb_Level; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox tb_Default; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox tb_Folder; + private System.Windows.Forms.Button btn_Start; + private System.Windows.Forms.Button btn_Stop; + private System.Windows.Forms.ListView lv_log; + private System.Windows.Forms.ColumnHeader Time; + private System.Windows.Forms.ColumnHeader Trainer; + private System.Windows.Forms.ColumnHeader Ditto; + private System.Windows.Forms.ColumnHeader Country; + private System.Windows.Forms.ColumnHeader SubCountry; + } +} + diff --git a/Ledybot/Form1.cs b/Ledybot/Form1.cs new file mode 100644 index 0000000..4040ab8 --- /dev/null +++ b/Ledybot/Form1.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Ledybot +{ + public partial class MainForm : Form + { + public MainForm() + { + InitializeComponent(); + } + + private void btn_Connect_Click(object sender, EventArgs e) + { + string szIp = tb_IP.Text; + string szPort = tb_Port.Text; + + int iPort = Convert.ToInt32(szPort); + + if (!Program.Connected) + { + Program.Connected = Program.scriptHelper.connect(szIp, iPort); + } + } + + Thread workerThread = null; + Worker workerObject = null; + + private void btn_Start_Click(object sender, EventArgs e) + { + if (workerThread == null && workerObject == null) + { + workerObject = new Worker(); + workerObject.setValues(tb_PokemonToFind.Text, tb_GiveAway.Text, tb_Default.Text, tb_Folder.Text, tb_Level.Text, tb_PID.Text); + workerThread = new Thread(workerObject.DoWork); + workerThread.Start(); + } + } + + public void AppendListViewItem(string szTrainerName, string szNickname, string szCountry, string szSubCountry) + { + if (InvokeRequired) + { + this.Invoke(new Action(AppendListViewItem), new object[] { szTrainerName, szNickname, szCountry, szSubCountry }); + return; + } + string[] row = { DateTime.Now.ToString("h:mm:ss"), szTrainerName, szNickname, szCountry, szSubCountry }; + var listViewItem = new ListViewItem(row); + + lv_log.Items.Add(listViewItem); + lv_log.Items[lv_log.Items.Count - 1].EnsureVisible(); + } + + private void btn_Stop_Click(object sender, EventArgs e) + { + if(workerThread != null && workerObject != null) + { + workerObject.RequestStop(); + workerThread.Join(); + + workerObject = null; + workerThread = null; + } + } + } +} diff --git a/Ledybot/Ledybot.csproj b/Ledybot/Ledybot.csproj new file mode 100644 index 0000000..aa07698 --- /dev/null +++ b/Ledybot/Ledybot.csproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + {3A95B46E-CD57-4F68-AB2E-29ED7BF9F8A6} + WinExe + Properties + Ledybot + Ledybot + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + \ No newline at end of file diff --git a/Ledybot/NTRClient.cs b/Ledybot/NTRClient.cs new file mode 100644 index 0000000..a565f04 --- /dev/null +++ b/Ledybot/NTRClient.cs @@ -0,0 +1,325 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace Ledybot +{ + public class NTRClient + { + public String host; + public int port; + public TcpClient tcp; + public NetworkStream netStream; + public Thread packetRecvThread; + UInt32 lastReadMemSeq; + string lastReadMemFileName; + private object syncLock = new object(); + public object retValLock = new object(); + int heartbeatSendable; + int timeout; + public delegate void logHandler(string msg); + UInt32 currentSeq; + public volatile int progress = -1; + + public string retVal; + public bool retDone; + + + int readNetworkStream(NetworkStream stream, byte[] buf, int length) + { + int index = 0; + bool useProgress = false; + + if (length > 100000) + { + useProgress = true; + } + do + { + if (useProgress) + { + progress = (int)(((double)(index) / length) * 100); + } + int len = stream.Read(buf, index, length - index); + if (len == 0) + { + return 0; + } + index += len; + } while (index < length); + progress = -1; + return length; + } + + void packetRecvThreadStart() + { + byte[] buf = new byte[84]; + UInt32[] args = new UInt32[16]; + int ret; + NetworkStream stream = netStream; + + while (true) + { + try + { + ret = readNetworkStream(stream, buf, buf.Length); + if (ret == 0) + { + break; + } + int t = 0; + UInt32 magic = BitConverter.ToUInt32(buf, t); + t += 4; + UInt32 seq = BitConverter.ToUInt32(buf, t); + t += 4; + UInt32 type = BitConverter.ToUInt32(buf, t); + t += 4; + UInt32 cmd = BitConverter.ToUInt32(buf, t); + for (int i = 0; i < args.Length; i++) + { + t += 4; + args[i] = BitConverter.ToUInt32(buf, t); + } + t += 4; + UInt32 dataLen = BitConverter.ToUInt32(buf, t); + + if (magic != 0x12345678) + { + break; + } + + if (cmd == 0) + { + if (dataLen != 0) + { + byte[] dataBuf = new byte[dataLen]; + readNetworkStream(stream, dataBuf, dataBuf.Length); + string logMsg = Encoding.UTF8.GetString(dataBuf); + //Console.WriteLine(logMsg); + } + lock (syncLock) + { + heartbeatSendable = 1; + } + continue; + } + if (dataLen != 0) + { + byte[] dataBuf = new byte[dataLen]; + readNetworkStream(stream, dataBuf, dataBuf.Length); + handlePacket(cmd, seq, dataBuf); + } + } + catch + { + break; + } + } + disconnect(false); + } + + void handlePacket(UInt32 cmd, UInt32 seq, byte[] dataBuf) + { + if (cmd == 9) + { + handleReadMem(seq, dataBuf); + } + } + + void handleReadMem(UInt32 seq, byte[] dataBuf) + { + if (seq != lastReadMemSeq) + { + //log("seq != lastReadMemSeq, ignored"); + return; + } + lastReadMemSeq = 0; + string fileName = lastReadMemFileName; + if (fileName != null) + { + FileStream fs = new FileStream(fileName, FileMode.Create); + fs.Write(dataBuf, 0, dataBuf.Length); + fs.Close(); + //log("dump saved into " + fileName + " successfully"); + + int i = 0; + string szResult = ""; + for (i = 0; i < dataBuf.Length; i++) + { + if (i % 2 == 0) + { + if (dataBuf[i] == 0x00) + { + break; + } + else + { + //this is what it looks like when you have no clue about unicode: + szResult = szResult + (char)dataBuf[i]; + } + } + } + + //string szResult = Encoding.Unicode.GetString(dataBuf); + + //t.BeginInvoke((MethodInvoker)delegate () { t.Text = szResult; ; }); + lock (retValLock) + { + retVal = szResult; + retDone = true; + } + return; + } + //log(byteToHex(dataBuf, 0)); + + } + + public void sendReadMemPacket(UInt32 addr, UInt32 size, UInt32 pid, string fileName) + { + sendEmptyPacket(9, pid, addr, size); + lastReadMemSeq = currentSeq; + lastReadMemFileName = fileName; + } + + public void sendEmptyPacket(UInt32 cmd, UInt32 arg0 = 0, UInt32 arg1 = 0, UInt32 arg2 = 0, UInt32 arg3 = 0, UInt32 arg4 = 0) + { + UInt32[] args = new UInt32[16]; + + args[0] = arg0; + args[1] = arg1; + args[2] = arg2; + args[3] = arg3; + args[4] = arg4; + sendPacket(0, cmd, args, 0); + } + + public void setServer(String serverHost, int serverPort) + { + host = serverHost; + port = serverPort; + } + + public Boolean connectToServer() + { + if (tcp != null) + { + disconnect(); + } + tcp = new TcpClient(); + tcp.NoDelay = true; + try + { + if (tcp.ConnectAsync(host, port).Wait(1000)) + { + currentSeq = 0; + netStream = tcp.GetStream(); + heartbeatSendable = 1; + packetRecvThread = new Thread(new ThreadStart(packetRecvThreadStart)); + packetRecvThread.Start(); + Program.Connected = true; + } + else + { + Program.Connected = false; + } + } + catch + { + Program.Connected = false; + } + + return Program.Connected; + } + + public void disconnect(bool waitPacketThread = true) + { + try + { + if (tcp != null) + { + tcp.Close(); + } + if (waitPacketThread) + { + if (packetRecvThread != null) + { + packetRecvThread.Join(); + } + } + } + catch { } + tcp = null; + Program.Connected = false; + } + + public void sendPacket(UInt32 type, UInt32 cmd, UInt32[] args, UInt32 dataLen) + { + int t = 0; + currentSeq += 1000; + byte[] buf = new byte[84]; + BitConverter.GetBytes(0x12345678).CopyTo(buf, t); + t += 4; + BitConverter.GetBytes(currentSeq).CopyTo(buf, t); + t += 4; + BitConverter.GetBytes(type).CopyTo(buf, t); + t += 4; + BitConverter.GetBytes(cmd).CopyTo(buf, t); + for (int i = 0; i < 16; i++) + { + t += 4; + UInt32 arg = 0; + if (args != null) + { + arg = args[i]; + } + BitConverter.GetBytes(arg).CopyTo(buf, t); + } + t += 4; + BitConverter.GetBytes(dataLen).CopyTo(buf, t); + try + { + netStream.Write(buf, 0, buf.Length); + } + catch (Exception) + { + } + } + + public void sendWriteMemPacket(UInt32 addr, UInt32 pid, byte[] buf) + { + UInt32[] args = new UInt32[16]; + args[0] = pid; + args[1] = addr; + args[2] = (UInt32)buf.Length; + sendPacket(1, 10, args, args[2]); + netStream.Write(buf, 0, buf.Length); + } + + public void sendHeartbeatPacket() + { + if (Program.Connected) + { + lock (syncLock) + { + if (heartbeatSendable == 1) + { + heartbeatSendable = 0; + sendPacket(0, 0, null, 0); + } + else + { + timeout++; + if (timeout == 5) + { + disconnect(false); + } + } + } + } + + } + } +} \ No newline at end of file diff --git a/Ledybot/PKHeX.cs b/Ledybot/PKHeX.cs new file mode 100644 index 0000000..0ebd663 --- /dev/null +++ b/Ledybot/PKHeX.cs @@ -0,0 +1,145 @@ + +/// I do not own the code in this class. +/// All rights and credits for the code in this class belong to Kaphotics. +/// All code within this class is taken from PKHeX https://github.com/kwsch/PKHeX + + +using System; +using System.Linq; +using System.Text; + +namespace Ledybot +{ + public class PKHeX + { + + public static uint LCRNG(uint seed) + { + const uint a = 0x41C64E6D; + const uint c = 0x00006073; + + return seed * a + c; + } + + public static uint LCRNG(ref uint seed) + { + const uint a = 0x41C64E6D; + const uint c = 0x00006073; + + return seed = seed * a + c; + } + + public static readonly byte[][] blockPosition = +{ + new byte[] {0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3}, + new byte[] {1, 1, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0, 2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2}, + new byte[] {2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2, 0, 0, 0, 0, 0, 0, 3, 2, 3, 2, 1, 1}, + new byte[] {3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0}, + }; + + public static readonly byte[] blockPositionInvert = + { + 0, 1, 2, 4, 3, 5, 6, 7, 12, 18, 13, 19, 8, 10, 14, 20, 16, 22, 9, 11, 15, 21, 17, 23 + }; + + public static byte[] shuffleArray(byte[] data, uint sv) + { + byte[] sdata = new byte[data.Length]; + Array.Copy(data, sdata, 8); // Copy unshuffled bytes + + // Shuffle Away! + for (int block = 0; block < 4; block++) + Array.Copy(data, 8 + 56 * blockPosition[block][sv], sdata, 8 + 56 * block, 56); + + // Fill the Battle Stats back + if (data.Length > 232) + Array.Copy(data, 232, sdata, 232, 28); + + return sdata; + } + + public static byte[] decryptArray(byte[] ekx) + { + byte[] pkx = (byte[])ekx.Clone(); + + uint pv = BitConverter.ToUInt32(pkx, 0); + uint sv = (pv >> 0xD & 0x1F) % 24; + + uint seed = pv; + + // Decrypt Blocks with RNG Seed + for (int i = 8; i < 232; i += 2) + BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(pkx, i) ^ LCRNG(ref seed) >> 16)).CopyTo(pkx, i); + + // Deshuffle + pkx = shuffleArray(pkx, sv); + + // Decrypt the Party Stats + seed = pv; + if (pkx.Length <= 232) return pkx; + for (int i = 232; i < 260; i += 2) + BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(pkx, i) ^ LCRNG(ref seed) >> 16)).CopyTo(pkx, i); + + return pkx; + } + public static byte[] encryptArray(byte[] pkx) + { + // Shuffle + uint pv = BitConverter.ToUInt32(pkx, 0); + uint sv = (pv >> 0xD & 0x1F) % 24; + + byte[] ekx = (byte[])pkx.Clone(); + + ekx = shuffleArray(ekx, blockPositionInvert[sv]); + + uint seed = pv; + + // Encrypt Blocks with RNG Seed + for (int i = 8; i < 232; i += 2) + BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(ekx, i) ^ LCRNG(ref seed) >> 16)).CopyTo(ekx, i); + + // If no party stats, return. + if (ekx.Length <= 232) return ekx; + + // Encrypt the Party Stats + seed = pv; + for (int i = 232; i < 260; i += 2) + BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(ekx, i) ^ LCRNG(ref seed) >> 16)).CopyTo(ekx, i); + + // Done + return ekx; + } + + public static ushort getCHK(byte[] data) + { + ushort chk = 0; + for (int i = 8; i < 232; i += 2) // Loop through the entire PKX + chk += BitConverter.ToUInt16(data, i); + + return chk; + } + + public static readonly int[,] hpivs = + { + { 1, 1, 0, 0, 0, 0 }, // Fighting + { 0, 0, 0, 0, 0, 1 }, // Flying + { 1, 1, 0, 0, 0, 1 }, // Poison + { 1, 1, 1, 0, 0, 1 }, // Ground + { 1, 1, 0, 1, 0, 0 }, // Rock + { 1, 0, 0, 1, 0, 1 }, // Bug + { 1, 0, 1, 1, 0, 1 }, // Ghost + { 1, 1, 1, 1, 0, 1 }, // Steel + { 1, 0, 1, 0, 1, 0 }, // Fire + { 1, 0, 0, 0, 1, 1 }, // Water + { 1, 0, 1, 0, 1, 1 }, // Grass + { 1, 1, 1, 0, 1, 1 }, // Electric + { 1, 0, 1, 1, 1, 0 }, // Psychic + { 1, 0, 0, 1, 1, 1 }, // Ice + { 1, 0, 1, 1, 1, 1 }, // Dragon + { 1, 1, 1, 1, 1, 1 }, // Dark + }; + + public byte[] Data { get; set; } + + } +} \ No newline at end of file diff --git a/Ledybot/Program.cs b/Ledybot/Program.cs new file mode 100644 index 0000000..6afc13e --- /dev/null +++ b/Ledybot/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Ledybot +{ + static class Program + { + public volatile static NTRClient ntrClient; + public volatile static ScriptHelper scriptHelper; + public static Boolean Connected = false; + public volatile static MainForm f1; + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + ntrClient = new NTRClient(); + scriptHelper = new ScriptHelper(); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + f1 = new MainForm(); + Application.Run(f1); + } + } +} diff --git a/Ledybot/Properties/AssemblyInfo.cs b/Ledybot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2b17ae8 --- /dev/null +++ b/Ledybot/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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("Ledybot")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ledybot")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[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("3a95b46e-cd57-4f68-ab2e-29ed7bf9f8a6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Ledybot/Properties/Resources.Designer.cs b/Ledybot/Properties/Resources.Designer.cs new file mode 100644 index 0000000..063c9d3 --- /dev/null +++ b/Ledybot/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Ledybot.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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("Ledybot.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Ledybot/Properties/Resources.resx b/Ledybot/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Ledybot/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Ledybot/Properties/Settings.Designer.cs b/Ledybot/Properties/Settings.Designer.cs new file mode 100644 index 0000000..4881098 --- /dev/null +++ b/Ledybot/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Ledybot.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; + } + } + } +} diff --git a/Ledybot/Properties/Settings.settings b/Ledybot/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Ledybot/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Ledybot/ScriptHelper.cs b/Ledybot/ScriptHelper.cs new file mode 100644 index 0000000..489c6c1 --- /dev/null +++ b/Ledybot/ScriptHelper.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading; + +namespace Ledybot +{ + public class ScriptHelper + { + public uint left = 0xFDF; + public uint right = 0xFEF; + public uint up = 0xFBF; + public uint down = 0xF7F; + public uint start = 0xFF7; + public uint Abtn = 0xFFE; + public uint Bbtn = 0xFFD; + + public uint searchBtn = 0x01B618E0; + + public Boolean connect(string host, int port) + { + Program.ntrClient.setServer(host, port); + return Program.ntrClient.connectToServer(); + } + + public void disconnect() + { + Program.ntrClient.disconnect(); + } + + public void write(uint addr, byte[] buf, int pid = -1) + { + Program.ntrClient.sendWriteMemPacket(addr, (uint)pid, buf); + } + + public void press(uint buttons) + { + byte[] data = new byte[12]; + uint oldbuttons = buttons; + uint oldtouch = 0x2000000; + uint oldcpad = 0x800800; + data[0x00] = (byte)(oldbuttons & 0xFF); + data[0x01] = (byte)((oldbuttons >> 0x08) & 0xFF); + data[0x02] = (byte)((oldbuttons >> 0x10) & 0xFF); + data[0x03] = (byte)((oldbuttons >> 0x18) & 0xFF); + + //Touch + data[0x04] = (byte)(oldtouch & 0xFF); + data[0x05] = (byte)((oldtouch >> 0x08) & 0xFF); + data[0x06] = (byte)((oldtouch >> 0x10) & 0xFF); + data[0x07] = (byte)((oldtouch >> 0x18) & 0xFF); + + //CPad + data[0x08] = (byte)(oldcpad & 0xFF); + data[0x09] = (byte)((oldcpad >> 0x08) & 0xFF); + data[0x0A] = (byte)((oldcpad >> 0x10) & 0xFF); + data[0x0B] = (byte)((oldcpad >> 0x18) & 0xFF); + + Program.scriptHelper.write(0x10DF20, data, 0x10); + Thread.Sleep(150); + oldbuttons = 0xFFF; + data[0x00] = (byte)(oldbuttons & 0xFF); + data[0x01] = (byte)((oldbuttons >> 0x08) & 0xFF); + data[0x02] = (byte)((oldbuttons >> 0x10) & 0xFF); + data[0x03] = (byte)((oldbuttons >> 0x18) & 0xFF); + + this.write(0x10DF20, data, 0x10); + } + + public void touch(uint touch) + { + byte[] data = new byte[12]; + uint oldbuttons = 0xFFF; + uint oldtouch = touch; + uint oldcpad = 0x800800; + data[0x00] = (byte)(oldbuttons & 0xFF); + data[0x01] = (byte)((oldbuttons >> 0x08) & 0xFF); + data[0x02] = (byte)((oldbuttons >> 0x10) & 0xFF); + data[0x03] = (byte)((oldbuttons >> 0x18) & 0xFF); + + //Touch + data[0x04] = (byte)(oldtouch & 0xFF); + data[0x05] = (byte)((oldtouch >> 0x08) & 0xFF); + data[0x06] = (byte)((oldtouch >> 0x10) & 0xFF); + data[0x07] = (byte)((oldtouch >> 0x18) & 0xFF); + + //CPad + data[0x08] = (byte)(oldcpad & 0xFF); + data[0x09] = (byte)((oldcpad >> 0x08) & 0xFF); + data[0x0A] = (byte)((oldcpad >> 0x10) & 0xFF); + data[0x0B] = (byte)((oldcpad >> 0x18) & 0xFF); + + Program.scriptHelper.write(0x10DF20, data, 0x10); + Thread.Sleep(150); + oldtouch = 0x2000000; + data[0x04] = (byte)(oldtouch & 0xFF); + data[0x05] = (byte)((oldtouch >> 0x08) & 0xFF); + data[0x06] = (byte)((oldtouch >> 0x10) & 0xFF); + data[0x07] = (byte)((oldtouch >> 0x18) & 0xFF); + + Program.scriptHelper.write(0x10DF20, data, 0x10); + + } + + public string readSafe(UInt32 addr, UInt32 byteCount, int pid) + { + lock (Program.ntrClient.retValLock) + { + Program.ntrClient.retDone = false; + } + bool waiting = true; + Program.ntrClient.sendReadMemPacket(addr, byteCount, (uint)pid, "C:/temp.txt"); + string szReturn = ""; + while (waiting) + { + Thread.Sleep(100); + lock (Program.ntrClient.retValLock) + { + if (Program.ntrClient.retDone) + { + waiting = false; + szReturn = Program.ntrClient.retVal; + } + } + } + return szReturn; + } + + } +} \ No newline at end of file diff --git a/Ledybot/Worker.cs b/Ledybot/Worker.cs new file mode 100644 index 0000000..45f5c54 --- /dev/null +++ b/Ledybot/Worker.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Ledybot +{ + public class Worker + { + + private volatile string szPokemonToFind = ""; + private volatile string szPokemonToGive = ""; + private volatile string szDefaultPk7 = ""; + private volatile string szPk7Folder = ""; + private volatile string szLevel = ""; + private volatile string szPID = ""; + private volatile int iPID = 0; + private volatile bool _shouldStop = false; + public void DoWork() + { + bool bPokemonFound = false; + ScriptHelper h = Program.scriptHelper; + + while (!_shouldStop) + { + Thread.Sleep(2000); + bool bSent = false; + if (!bPokemonFound) + { + h.press(h.Abtn); + Thread.Sleep(1000); + h.press(h.Abtn); + Thread.Sleep(1000); + h.press(h.up); + Thread.Sleep(1000); + //What Pokemon? + h.press(h.Abtn); + //black screen + Thread.Sleep(3000); + + //"type" the name of the pokemon we want to find + byte[] name = new byte[this.szPokemonToFind.Length * 2]; + for (int i = 0; i < szPokemonToFind.Length; i++) + { + name[i * 2] = (byte)szPokemonToFind[i]; + name[i * 2 + 1] = 0x00; + } + //I like this solution so much + Program.scriptHelper.write(0x301118D4, name, iPID); + Thread.Sleep(1000); + h.press(h.start); + Thread.Sleep(1000); + h.press(h.Abtn); + //black screen + Thread.Sleep(4000); + bPokemonFound = true; + } + else + { + h.press(h.Abtn); + Thread.Sleep(2000); + } + + h.touch(h.searchBtn); + //finding pokemon + Thread.Sleep(4000); + + for(int i = 0; i < 25; i++) + { + string szReqPokemon = h.readSafe(0x30784ef4, 20, iPID); + if(szReqPokemon == this.szPokemonToGive) + { + string szLevel = h.readSafe(0x307879B4, 6, iPID); + szLevel = szLevel.ToLower(); + if (szLevel.Contains(this.szLevel) || szLevel.Contains("any")) + { + string szNickname = h.readSafe(0x3077c514, 20, iPID); + + string szPath = this.szDefaultPk7; + string szFileToFind = this.szPk7Folder + szNickname + ".pk7"; + if (File.Exists(szFileToFind)) + { + szPath = szFileToFind; + } + + byte[] pkmEncrypted = System.IO.File.ReadAllBytes(szPath); + byte[] cloneshort = PKHeX.encryptArray(pkmEncrypted.Take(232).ToArray()); + string ek7 = BitConverter.ToString(cloneshort).Replace("-", ", 0x"); + + //optional: grab some trainer data + string szTrainerName = h.readSafe(0x305F1864, 20, iPID); + string szCountry = h.readSafe(0x305F2A14, 20, iPID); + string szSubCountry = h.readSafe(0x305F76A4, 20, iPID); + + Program.f1.AppendListViewItem(szTrainerName, szNickname, szCountry, szSubCountry); + //Inject the Pokemon to box1slot1 + Program.scriptHelper.write(0x330d9838, cloneshort, iPID); + Thread.Sleep(1000); + h.press(h.Abtn); + Thread.Sleep(3000); + h.press(h.Abtn); + Thread.Sleep(1000); + h.press(h.Abtn); + Thread.Sleep(1000); + h.press(h.Abtn); + //trade starts here + Thread.Sleep(10000); + //if the pokemon has been traded we have 35 seconds to get back to the starting spot for the bot by spamming b a bit + h.press(h.Abtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + //if the trade is still going on wait for it to finish + Thread.Sleep(30000); + bSent = true; + break; + } + } + h.press(h.right); + Thread.Sleep(500); + } + if (!bSent) + { + //no tradable pokemon in the last X pokemon, start from the front of the list again + h.press(h.Bbtn); + Thread.Sleep(1000); + h.press(h.Bbtn); + Thread.Sleep(1000); + } + } + } + public void RequestStop() + { + _shouldStop = true; + } + + public void setValues(string szPtF, string szPtG, string szD, string szF, string szL, string szP) + { + this.szPokemonToFind = szPtF; + this.szPokemonToGive = szPtG; + this.szDefaultPk7 = szD; + this.szPk7Folder = szF; + this.szLevel = szL; + this.szPID = szP; + + + this.iPID = int.Parse(szPID, NumberStyles.HexNumber); + } + + } +} diff --git a/README.md b/README.md index 41c4d10..94129f3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ # Ledybot A pokemon SM giveaway bot for the n3ds. Needs NTR and Input Redirection to work. + +Thanks to kwsch and the guys from PKHeX for their work on the .pk7 format! +Also thanks to Kazo for his NTR Input Redirection Client as well as Stary2001 for the actual .cia! + +1. Boot your n3ds +2. Start the Boot NTR selector +3. Start the Input Redirection.cia +4. Start Pokemon SM and connect online in the Festival Plaza +5. Open the GTS until you see the "Seek Pokemon / Deposit Pokemon" screen +6. Start Ledybot, fill in your n3ds ip, ntr port and pokemon pid +7. Type in which pokemon the bot shall find in the GTS +8. Type in which pokemon you are giving away (this is used to check if we can actually trade the deposited pokemon) +9. Type in the lower level range of the pokemon you are trading. The bot will trade all "Any"-level pokemon as well as pokemon asking for this level+. This means if you want to trade a lvl 100 Ditto you have to put 91 in here (since the range goes 91 - 100). +10. Select a default .pk7 file, this file will be injected and traded by the bot on default. +11. Select a specific .pk7 folder. The bot will check the deposited Pokemons name, see if the name matches any .pk7 file in the folder and inject this one instead (if it exists). +12. Press Start + +## Todo: + +Lots of things! +- Comments and Documentation! +- Unicode support +- There may be some stability issues at times. I've had it run for 10h+ in the past but since the bot can't really restart itself when it crashes you still need to have an eye on it from time to time. +- 1-10 level range probably won't work yet because of lazy coding (You'd have to type in "1" in the field, but I use contains() to check... and 91 also contains 1 so yeaa...) +- Giving away different pokemon. This would need a simple change to check the requested pokemon based on the name of the deposited pokemon. Mostly a UI thing and I'm not really a fan of that, will come in the future. +- An actual working stop button (it will stop... eventually... maybe) +- Speed optimizations +- Prevent the same people from requesting more than once every X min. I already have the data of who requests (at least trainer name, country and subcountry), I'd just need to add them to a list and block them for a while. This would also block people from the same region with the same name though (unless I find a better way to identify who is requesting) +- Add an option to search from the back of the queue? +- Add an option for several deposited pokemon (to prevent people from redepositing the same pokemon all the time)? +- Add an option to select how many gts entries we look at max (currently hardcoded 25) +- ... and more! \ No newline at end of file