" }, StringSplitOptions.None)[
58 | 1
59 | ].Split('>');
60 | Author = GetVal(auth[0]);
61 |
62 | var datestr = auth[3];
63 | var date = GetVal(datestr);
64 | Description = $"Pastebin created on: {date}";
65 | }
66 |
67 | private static PasteSource GetSource(string url)
68 | {
69 | if (url.Contains("pokepast.es/"))
70 | return PasteSource.PokePaste;
71 | if (url.Contains("pastebin.com/"))
72 | return PasteSource.Pastebin;
73 | return PasteSource.None;
74 | }
75 |
76 | private string GetRawURL(string url)
77 | {
78 | return Source switch
79 | {
80 | PasteSource.PokePaste => url.EndsWith("/raw") ? url : url + "/raw",
81 | PasteSource.Pastebin
82 | => url.Contains("/raw/")
83 | ? url
84 | : url.Replace("pastebin.com/", "pastebin.com/raw/"),
85 | _ => url, // This should never happen
86 | };
87 | }
88 |
89 | private void LoadMetadata()
90 | {
91 | // Passed URL must be non raw
92 | switch (Source)
93 | {
94 | case PasteSource.PokePaste:
95 | {
96 | var url = URL.Replace("/raw", "");
97 | GetFromPokePaste(url);
98 | return;
99 | }
100 | case PasteSource.Pastebin:
101 | {
102 | var url = URL.Replace("/raw/", "/");
103 | GetFromPasteBin(url);
104 | return;
105 | }
106 | default:
107 | return; // This should never happen
108 | }
109 | }
110 |
111 | private static string GetVal(string s, char c = '<') => s.Split(c)[0].Trim();
112 |
113 | public TeamPasteInfo(string url)
114 | {
115 | URL = url;
116 | var isUri = Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute);
117 | if (!isUri)
118 | return;
119 | Source = GetSource(url);
120 | if (Source == PasteSource.None)
121 | return;
122 |
123 | url = GetRawURL(url);
124 | Sets = NetUtil.GetPageText(url).Trim();
125 | LoadMetadata();
126 | Valid = true;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BlockData.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public record BlockData
4 | {
5 | public string Name { get; set; } = string.Empty;
6 | public string Display { get; set; } = string.Empty;
7 | public uint SCBKey { get; set; }
8 | public string Pointer { get; set; } = string.Empty;
9 | public ulong Offset { get; set; }
10 | public SCTypeCode Type { get; set; } = SCTypeCode.None;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BotController/Controllers/NTRClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace PKHeX.Core.Injection
5 | {
6 | public class NTRClient : ICommunicator
7 | {
8 | private string IP = "192.168.1.106";
9 | private int Port = 8000;
10 | private static readonly NTR clientNTR = new();
11 |
12 | private const int timeout = 10;
13 | private bool Connected;
14 |
15 | private readonly object _sync = new();
16 | private byte[]? _lastMemoryRead;
17 |
18 | public void Connect()
19 | {
20 | clientNTR.Connect(IP, Port);
21 | if (clientNTR.IsConnected)
22 | Connected = true;
23 | }
24 |
25 | bool ICommunicator.Connected
26 | {
27 | get => Connected;
28 | set => Connected = value;
29 | }
30 | int ICommunicator.Port
31 | {
32 | get => Port;
33 | set => Port = value;
34 | }
35 | string ICommunicator.IP
36 | {
37 | get => IP;
38 | set => IP = value;
39 | }
40 |
41 | public void Disconnect()
42 | {
43 | lock (_sync)
44 | {
45 | clientNTR.Disconnect();
46 | Connected = false;
47 | }
48 | }
49 |
50 | private void HandleMemoryRead(object argsObj)
51 | {
52 | DataReadyWaiting args = (DataReadyWaiting)argsObj;
53 | _lastMemoryRead = args.Data;
54 | }
55 |
56 | public byte[] ReadBytes(ulong offset, int length)
57 | {
58 | lock (_sync)
59 | {
60 | if (!Connected)
61 | Connect();
62 |
63 | WriteLastLog("");
64 | DataReadyWaiting myArgs = new(new byte[length], HandleMemoryRead, null);
65 | while (clientNTR.PID == -1)
66 | {
67 | Thread.Sleep(10);
68 | }
69 | clientNTR.AddWaitingForData(
70 | clientNTR.Data((uint)offset, (uint)length, clientNTR.PID),
71 | myArgs
72 | );
73 |
74 | for (int readcount = 0; readcount < timeout * 100; readcount++)
75 | {
76 | Thread.Sleep(10);
77 | if (CompareLastLog("finished"))
78 | break;
79 | }
80 |
81 | byte[] result = _lastMemoryRead ?? [];
82 | _lastMemoryRead = null;
83 | return result;
84 | }
85 | }
86 |
87 | private static void WriteLastLog(string str) => clientNTR.Lastlog = str;
88 |
89 | private static bool CompareLastLog(string str) => clientNTR.Lastlog.Contains(str);
90 |
91 | public void WriteBytes(ReadOnlySpan
data, ulong offset)
92 | {
93 | lock (_sync)
94 | {
95 | if (!Connected)
96 | Connect();
97 | while (clientNTR.PID == -1)
98 | {
99 | Thread.Sleep(10);
100 | }
101 | clientNTR.Write((uint)offset, data, clientNTR.PID);
102 | int waittimeout;
103 | for (waittimeout = 0; waittimeout < timeout * 100; waittimeout++)
104 | {
105 | WriteLastLog("");
106 | Thread.Sleep(10);
107 | if (CompareLastLog("finished"))
108 | break;
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BotController/Decoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PKHeX.Core.Injection
4 | {
5 | public static class Decoder
6 | {
7 | private static bool IsNum(char c) => (uint)(c - '0') <= 9;
8 |
9 | private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5;
10 |
11 | public static byte[] ConvertHexByteStringToBytes(byte[] bytes)
12 | {
13 | var dest = new byte[bytes.Length / 2];
14 | for (int i = 0; i < dest.Length; i++)
15 | {
16 | var _0 = (char)bytes[(i * 2) + 0];
17 | var _1 = (char)bytes[(i * 2) + 1];
18 | dest[i] = DecodeTuple(_0, _1);
19 | }
20 | return dest;
21 | }
22 |
23 | public static byte[] StringToByteArray(string hex)
24 | {
25 | int NumberChars = hex.Length;
26 | byte[] bytes = new byte[NumberChars / 2];
27 | for (int i = 0; i < NumberChars; i += 2)
28 | bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
29 | return bytes;
30 | }
31 |
32 | private static byte DecodeTuple(char _0, char _1)
33 | {
34 | byte result;
35 | if (IsNum(_0))
36 | result = (byte)((_0 - '0') << 4);
37 | else if (IsHexUpper(_0))
38 | result = (byte)((_0 - 'A' + 10) << 4);
39 | else
40 | throw new ArgumentOutOfRangeException(nameof(_0));
41 |
42 | if (IsNum(_1))
43 | result |= (byte)(_1 - '0');
44 | else if (IsHexUpper(_1))
45 | result |= (byte)(_1 - 'A' + 10);
46 | else
47 | throw new ArgumentOutOfRangeException(nameof(_1));
48 | return result;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BotController/ICommunicator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace PKHeX.Core.Injection
5 | {
6 | public enum InjectorCommunicationType
7 | {
8 | SocketNetwork = 0,
9 | USB = 1,
10 | }
11 |
12 | public interface ICommunicator
13 | {
14 | void Connect();
15 | void Disconnect();
16 | void WriteBytes(ReadOnlySpan data, ulong offset);
17 | byte[] ReadBytes(ulong offset, int length);
18 | bool Connected { get; set; }
19 | int Port { get; set; }
20 | string IP { get; set; }
21 | }
22 |
23 | public interface ICommunicatorNX : ICommunicator
24 | {
25 | InjectorCommunicationType Protocol { get; set; }
26 | byte[] ReadBytesMain(ulong offset, int length);
27 | byte[] ReadBytesAbsolute(ulong offset, int length);
28 | ulong GetHeapBase();
29 | string GetBotbaseVersion();
30 | string GetTitleID();
31 | string GetGameInfo(string info);
32 | bool IsProgramRunning(ulong pid);
33 | void WriteBytesMain(ReadOnlySpan data, ulong offset);
34 | void WriteBytesAbsolute(ReadOnlySpan data, ulong offset);
35 | byte[] ReadBytesAbsoluteMulti(Dictionary offsets);
36 | }
37 |
38 | public interface IPokeBlocks : ICommunicator;
39 | }
40 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BotController/LiveHexController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PKHeX.Core.Injection
4 | {
5 | public class LiveHeXController
6 | {
7 | private readonly ISaveFileProvider SAV;
8 | public readonly IPKMView Editor;
9 | public PokeSysBotMini Bot;
10 |
11 | public LiveHeXController(
12 | ISaveFileProvider boxes,
13 | IPKMView editor,
14 | InjectorCommunicationType ict,
15 | bool useCache = false
16 | )
17 | {
18 | SAV = boxes;
19 | Editor = editor;
20 | var ValidVers = RamOffsets.GetValidVersions(boxes.SAV);
21 | var com = RamOffsets.GetCommunicator(SAV.SAV, ict);
22 | Bot = new PokeSysBotMini(ValidVers[0], com, useCache);
23 | }
24 |
25 | public void ChangeBox(int box)
26 | {
27 | if (!Bot.Connected)
28 | return;
29 |
30 | var sav = SAV.SAV;
31 | if ((uint)box >= sav.BoxCount)
32 | return;
33 |
34 | ReadBox(box);
35 | }
36 |
37 | public void ReadBox(int box)
38 | {
39 | var sav = SAV.SAV;
40 | var len =
41 | sav.BoxSlotCount
42 | * (RamOffsets.GetSlotSize(Bot.Version) + RamOffsets.GetGapSize(Bot.Version));
43 | var data = Bot.ReadBox(box, len).AsSpan();
44 | sav.SetBoxBinary(data, box);
45 | SAV.ReloadSlots();
46 | }
47 |
48 | public void WriteBox(int box)
49 | {
50 | var boxData = SAV.SAV.GetBoxBinary(box);
51 | Bot.SendBox(boxData, box);
52 | }
53 |
54 | public void WriteActiveSlot(int box, int slot)
55 | {
56 | var pkm = Editor.PreparePKM();
57 | pkm.ResetPartyStats();
58 | var data = RamOffsets.WriteBoxData(Bot.Version)
59 | ? pkm.EncryptedBoxData
60 | : pkm.EncryptedPartyData;
61 | Bot.SendSlot(data, box, slot);
62 | }
63 |
64 | public void ReadActiveSlot(int box, int slot)
65 | {
66 | var data = Bot.ReadSlot(box, slot);
67 | var pkm = SAV.SAV.GetDecryptedPKM(data);
68 | Editor.PopulateFields(pkm);
69 | }
70 |
71 | public bool ReadOffset(ulong offset, RWMethod method = RWMethod.Heap)
72 | {
73 | var data = ReadData(offset, method);
74 | var pkm = SAV.SAV.GetDecryptedPKM(data);
75 |
76 | // Since data might not actually exist at the user-specified offset, double check that the pkm data is valid.
77 | if (!pkm.ChecksumValid)
78 | return false;
79 |
80 | Editor.PopulateFields(pkm);
81 | return true;
82 | }
83 |
84 | private byte[] ReadData(ulong offset, RWMethod method)
85 | {
86 | if (Bot.com is not ICommunicatorNX nx)
87 | return Bot.ReadOffset(offset);
88 | return method switch
89 | {
90 | RWMethod.Heap => Bot.ReadOffset(offset),
91 | RWMethod.Main => nx.ReadBytesMain(offset, Bot.SlotSize),
92 | RWMethod.Absolute => nx.ReadBytesAbsolute(offset, Bot.SlotSize),
93 | _ => Bot.ReadOffset(offset),
94 | };
95 | }
96 |
97 | public byte[] ReadRAM(ulong offset, int size) => Bot.com.ReadBytes(offset, size);
98 |
99 | public void WriteRAM(ulong offset, byte[] data) => Bot.com.WriteBytes(data, offset);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/BotController/PokeSysBotMini.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace PKHeX.Core.Injection
5 | {
6 | public class PokeSysBotMini(LiveHeXVersion lv, ICommunicator communicator, bool useCache)
7 | : InjectionBase(lv, useCache)
8 | {
9 | public readonly long BoxStart = RamOffsets.GetB1S1Offset(lv);
10 | public readonly int SlotSize = RamOffsets.GetSlotSize(lv);
11 | public readonly int SlotCount = RamOffsets.GetSlotCount(lv);
12 | public readonly int GapSize = RamOffsets.GetGapSize(lv);
13 | public readonly LiveHeXVersion Version = lv;
14 | public readonly ICommunicator com = communicator;
15 | public readonly InjectionBase Injector = GetInjector(lv, useCache);
16 | public bool Connected => com.Connected;
17 |
18 | public ulong GetSlotOffset(int box, int slot) =>
19 | GetBoxOffset(box) + (ulong)((SlotSize + GapSize) * slot);
20 |
21 | public ulong GetBoxOffset(int box) =>
22 | (ulong)BoxStart + (ulong)((SlotSize + GapSize) * SlotCount * box);
23 |
24 | public byte[] ReadSlot(int box, int slot) => Injector.ReadSlot(this, box, slot);
25 |
26 | public byte[] ReadBox(int box, int len)
27 | {
28 | var allpkm = new List();
29 | return Injector.ReadBox(this, box, len, allpkm);
30 | }
31 |
32 | public void SendSlot(byte[] data, int box, int slot) =>
33 | Injector.SendSlot(this, data, box, slot);
34 |
35 | public void SendBox(byte[] boxData, int box)
36 | {
37 | ReadOnlySpan bytes = boxData;
38 | byte[][] pkmData = bytes.Split(SlotSize);
39 | for (int i = 0; i < SlotCount; i++)
40 | SendSlot(pkmData[i], box, i);
41 |
42 | Injector.SendBox(this, boxData, box);
43 | }
44 |
45 | public byte[] ReadOffset(ulong offset) => com.ReadBytes(offset, SlotSize);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Enums/LiveHeXValidation.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public enum LiveHeXValidation
4 | {
5 | None = 0,
6 | Botbase = 1,
7 | BlankSAV = 2,
8 | GameVersion = 3,
9 | RAMShift = 4,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Enums/LiveHeXVersion.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public enum LiveHeXVersion
4 | {
5 | Unknown = -1,
6 | XY_v150 = 0,
7 | ORAS_v140 = 1,
8 | SM_v120 = 2,
9 | US_v120 = 3,
10 | UM_v120 = 4,
11 |
12 | SWSH_v111 = 5,
13 | SWSH_v121 = 6,
14 | SWSH_v132 = 7,
15 |
16 | LGPE_v102 = 8,
17 |
18 | SP_v100 = 9,
19 | SP_v110 = 10,
20 | SP_v111 = 11,
21 | SP_v130 = 12,
22 |
23 | BD_v100 = 13,
24 | BD_v110 = 14,
25 | BD_v111 = 15,
26 | BD_v130 = 16,
27 |
28 | BDSP_v112 = 17,
29 | BDSP_v113 = 18,
30 | BDSP_v120 = 19,
31 |
32 | LA_v100 = 20,
33 | LA_v101 = 21,
34 | LA_v102 = 22,
35 | LA_v111 = 23,
36 |
37 | SV_v101 = 24,
38 | SV_v110 = 25,
39 | SV_v120 = 26,
40 | SV_v130 = 27,
41 | SV_v131 = 28,
42 | SV_v132 = 29,
43 | SV_v201 = 30,
44 | SV_v202 = 31,
45 | SV_v300 = 32,
46 | SV_v301 = 33,
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Enums/SwitchButton.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | ///
4 | /// Controller Buttons
5 | ///
6 | public enum SwitchButton
7 | {
8 | A,
9 | B,
10 | X,
11 | Y,
12 | RSTICK,
13 | LSTICK,
14 | L,
15 | R,
16 | ZL,
17 | ZR,
18 | PLUS,
19 | MINUS,
20 | DUP,
21 | DDOWN,
22 | DLEFT,
23 | DRIGHT,
24 | HOME,
25 | CAPTURE,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Enums/SwitchStick.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | ///
4 | /// Controller Stick differentiation
5 | ///
6 | public enum SwitchStick
7 | {
8 | LEFT,
9 | RIGHT,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/LiveHeXBlockClasses/ICustomBlock.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public interface ICustomBlock
4 | {
5 | // static byte[]? Getter(PokeSysBotMini psb);
6 | void Setter(PokeSysBotMini psb, byte[] data);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/LiveHeXOffsets/LiveHeXBlocks.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public static class LiveHeXBlocks
4 | {
5 | ///
6 | /// Check if a special form needs to be open to handle the block
7 | ///
8 | /// saveblock
9 | /// LiveHeX version being edited
10 | /// string value of the form to open
11 | /// Boolean indicating if a special form needs to be opened
12 | public static bool IsSpecialBlock(this string block, PokeSysBotMini psb, out string? value)
13 | {
14 | return psb.Injector.SpecialBlocks.TryGetValue(block, out value);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/LiveHeXOffsets/Offsets8.cs:
--------------------------------------------------------------------------------
1 | namespace PKHeX.Core.Injection
2 | {
3 | public class Offsets8
4 | {
5 | public uint BoxInfo;
6 | public uint PartyInfo;
7 | public uint Items;
8 | public uint MyStatus;
9 | public uint Misc;
10 | public uint KZukan; // SCBlocks
11 | public uint KZukanR1; // SCBlocks
12 | public uint KZukanR2; // SCBlocks
13 | public uint KRentalTeam1; // SCBlocks
14 |
15 | // public uint KRentalTeam2; // SCBlocks
16 | public uint KRentalTeam3; // SCBlocks
17 | public uint KRentalTeam4; // SCBlocks
18 | public uint KRentalTeam5; // SCBlocks
19 | public uint KRentalTeam6; // SCBlocks
20 | public uint BoxLayout;
21 | public uint Played;
22 | public uint Fused;
23 | public uint Daycare;
24 | public uint Records;
25 | public uint TrainerCard;
26 | public uint Fashion;
27 | public uint Raid;
28 | public uint RaidArmor;
29 | public uint RaidCrown;
30 | public uint TitleScreen;
31 | public uint TeamIndexes;
32 | public uint FameTime;
33 |
34 | ///
35 | /// Rigel2 offsets in the ram that correspond to the same data that is found in the saveblock
36 | ///
37 | public static readonly Offsets8 Rigel2 =
38 | new()
39 | {
40 | MyStatus = 0x45068F18,
41 | Items = 0x45067A98,
42 | Raid = 0x450C8A70,
43 | RaidArmor = 0x450C94D8,
44 | RaidCrown = 0x450C9F40,
45 | Misc = 0x45072DF0,
46 | TrainerCard = 0x45127098,
47 | Fashion = 0x450748E8,
48 | KZukan = 0x45069120,
49 | KZukanR1 = 0x4506DC20,
50 | KZukanR2 = 0x450703B0,
51 | };
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/PKHeX.Core.Injection.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/PointerCache/PointerCache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace PKHeX.Core.Injection
4 | {
5 | public abstract class PointerCache(LiveHeXVersion version, bool useCache = false)
6 | {
7 | private readonly Dictionary> Cache = [];
8 |
9 | public ulong GetCachedPointer(ICommunicatorNX com, string ptr, bool relative = true)
10 | {
11 | ulong pointer = 0;
12 | bool hasEntry = Cache.TryGetValue(version, out var cache);
13 | bool hasPointer = cache is not null && cache.TryGetValue(ptr, out pointer);
14 | if (hasPointer && useCache)
15 | return pointer;
16 |
17 | pointer = com.GetPointerAddress(ptr, relative);
18 | if (!useCache)
19 | return pointer;
20 |
21 | if (!hasEntry)
22 | Cache.Add(version, new() { { ptr, pointer } });
23 | else
24 | Cache[version].Add(ptr, pointer);
25 |
26 | return pointer;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Protocols/LPLGPE.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace PKHeX.Core.Injection
6 | {
7 | public class LPLGPE(LiveHeXVersion lv, bool useCache) : InjectionBase(lv, useCache)
8 | {
9 | private static readonly LiveHeXVersion[] SupportedVersions = [LiveHeXVersion.LGPE_v102];
10 |
11 | public static LiveHeXVersion[] GetVersions() => SupportedVersions;
12 |
13 | public override byte[] ReadBox(PokeSysBotMini psb, int box, int len, List allpkm)
14 | {
15 | var bytes = psb.com.ReadBytes(psb.GetBoxOffset(box), len);
16 | if (psb.GapSize == 0)
17 | return bytes;
18 | var currofs = 0;
19 | for (int i = 0; i < psb.SlotCount; i++)
20 | {
21 | var StoredLength = psb.SlotSize - 0x1C;
22 | var stored = bytes.AsSpan(currofs, psb.SlotSize).ToArray();
23 | var party = bytes.AsSpan(StoredLength + 0x70, 0x1C).ToArray();
24 | allpkm.Add([.. stored, .. party]);
25 | currofs += psb.SlotSize + psb.GapSize;
26 | }
27 | return [..allpkm.SelectMany(z => z)];
28 | }
29 |
30 | public override byte[] ReadSlot(PokeSysBotMini psb, int box, int slot)
31 | {
32 | ReadOnlySpan bytes = psb.com.ReadBytes(psb.GetSlotOffset(box, slot), psb.SlotSize + psb.GapSize);
33 | var StoredLength = psb.SlotSize - 0x1C;
34 | var stored = bytes[..StoredLength];
35 | var party = bytes.Slice(StoredLength + 0x70, 0x1C);
36 | return [..stored, ..party];
37 | }
38 |
39 | public override void SendSlot(PokeSysBotMini psb, byte[] data, int box, int slot)
40 | {
41 | var slotofs = psb.GetSlotOffset(box, slot);
42 | var StoredLength = psb.SlotSize - 0x1C;
43 | psb.com.WriteBytes(data[..StoredLength], slotofs);
44 | psb.com.WriteBytes(data.AsSpan(StoredLength).ToArray(), slotofs + (ulong)StoredLength + 0x70);
45 | }
46 |
47 | public override void SendBox(PokeSysBotMini psb, byte[] boxData, int box)
48 | {
49 | ReadOnlySpan bytes = boxData;
50 | byte[][] pkmData = bytes.Split(psb.SlotSize);
51 | for (int i = 0; i < psb.SlotCount; i++)
52 | SendSlot(psb, pkmData[i], box, i);
53 | }
54 |
55 | public static readonly Func GetTrainerData = psb =>
56 | {
57 | var lv = psb.Version;
58 | var ofs = RamOffsets.GetTrainerBlockOffset(lv);
59 | var size = RamOffsets.GetTrainerBlockSize(lv);
60 | if (size <= 0 || ofs == 0)
61 | return null;
62 | var data = psb.com.ReadBytes(ofs, size);
63 | return data;
64 | };
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Util/ArrayUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace PKHeX.Core.Injection
6 | {
7 | ///
8 | /// Array reusable logic
9 | ///
10 | public static class ArrayUtil
11 | {
12 | internal static T[] ConcatAll(params T[][] arr)
13 | {
14 | int len = 0;
15 | foreach (var a in arr)
16 | len += a.Length;
17 |
18 | var result = new T[len];
19 |
20 | int ctr = 0;
21 | foreach (var a in arr)
22 | {
23 | a.CopyTo(result, ctr);
24 | ctr += a.Length;
25 | }
26 |
27 | return result;
28 | }
29 |
30 | public static T[][] Split(this ReadOnlySpan data, int size)
31 | {
32 | var result = new T[data.Length / size][];
33 | for (int i = 0; i < data.Length; i += size)
34 | result[i / size] = data.Slice(i, size).ToArray();
35 | return result;
36 | }
37 |
38 | public static IEnumerable EnumerateSplit(T[] bin, int size, int start = 0)
39 | {
40 | for (int i = start; i < bin.Length; i += size)
41 | yield return bin.AsSpan(i, size).ToArray();
42 | }
43 |
44 | internal static T[] ConcatAll(T[] arr1, T[] arr2)
45 | {
46 | int len = arr1.Length + arr2.Length;
47 | var result = new T[len];
48 | arr1.CopyTo(result, 0);
49 | arr2.CopyTo(result, arr1.Length);
50 | return result;
51 | }
52 |
53 | internal static T[] ConcatAll(T[] arr1, T[] arr2, T[] arr3)
54 | {
55 | int len = arr1.Length + arr2.Length + arr3.Length;
56 | var result = new T[len];
57 | arr1.CopyTo(result, 0);
58 | arr2.CopyTo(result, arr1.Length);
59 | arr3.CopyTo(result, arr1.Length + arr2.Length);
60 | return result;
61 | }
62 |
63 | internal static T? ToClass(this byte[] bytes)
64 | where T : class
65 | {
66 | var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
67 | try
68 | {
69 | return Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)) as T;
70 | }
71 | finally
72 | {
73 | handle.Free();
74 | }
75 | }
76 |
77 | internal static byte[] ToBytesClass(this T obj)
78 | where T : class
79 | {
80 | int size = Marshal.SizeOf(obj);
81 | byte[] arr = new byte[size];
82 |
83 | IntPtr ptr = Marshal.AllocHGlobal(size);
84 | Marshal.StructureToPtr(obj, ptr, true);
85 | Marshal.Copy(ptr, arr, 0, size);
86 | Marshal.FreeHGlobal(ptr);
87 | return arr;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/PKHeX.Core.Injection/Util/InjectionUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace PKHeX.Core.Injection
5 | {
6 | public static class InjectionUtil
7 | {
8 | public const ulong INVALID_PTR = 0;
9 |
10 | public static ulong GetPointerAddress(
11 | this ICommunicatorNX sb,
12 | string ptr,
13 | bool heapRelative = true
14 | )
15 | {
16 | if (string.IsNullOrWhiteSpace(ptr) || ptr.IndexOfAny(['-', '/', '*']) != -1)
17 | return INVALID_PTR;
18 | while (ptr.Contains("]]"))
19 | ptr = ptr.Replace("]]", "]+0]");
20 | uint finadd = 0;
21 | if (!ptr.EndsWith(']'))
22 | {
23 | finadd = Util.GetHexValue(ptr.Split('+').Last());
24 | ptr = ptr[..ptr.LastIndexOf('+')];
25 | }
26 | var jumps = ptr.Replace("main", "")
27 | .Replace("[", "")
28 | .Replace("]", "")
29 | .Split('+', StringSplitOptions.RemoveEmptyEntries);
30 | if (jumps.Length == 0)
31 | return INVALID_PTR;
32 |
33 | var initaddress = Util.GetHexValue(jumps[0].Trim());
34 | ulong address = BitConverter.ToUInt64(sb.ReadBytesMain(initaddress, 0x8), 0);
35 | foreach (var j in jumps)
36 | {
37 | var val = Util.GetHexValue(j.Trim());
38 | if (val == initaddress)
39 | continue;
40 | address = BitConverter.ToUInt64(sb.ReadBytesAbsolute(address + val, 0x8), 0);
41 | }
42 | address += finadd;
43 | if (heapRelative)
44 | {
45 | ulong heap = sb.GetHeapBase();
46 | address -= heap;
47 | }
48 | return address;
49 | }
50 |
51 | public static string ExtendPointer(this string pointer, params uint[] jumps)
52 | {
53 | foreach (var jump in jumps)
54 | pointer = $"[{pointer}]+{jump:X}";
55 | return pointer;
56 | }
57 |
58 | public static ulong SearchSaveKey(this PokeSysBotMini psb, string saveblocks, uint key)
59 | {
60 | if (psb.com is not ICommunicatorNX nx)
61 | return 0;
62 |
63 | var ptr = psb.GetCachedPointer(nx, saveblocks, false);
64 | var dt = nx.ReadBytesAbsolute(ptr + 8, 16);
65 | var start = BitConverter.ToUInt64(dt.AsSpan()[..8]);
66 | var end = BitConverter.ToUInt64(dt.AsSpan()[8..]);
67 | var size = (ulong)GetBlockSizeSV(psb.Version);
68 |
69 | while (start < end)
70 | {
71 | var block_ct = (end - start) / size;
72 | var mid = start + ((block_ct >> 1) * size);
73 | var found = BitConverter.ToUInt32(nx.ReadBytesAbsolute(mid, 4));
74 | if (found == key)
75 | return mid;
76 | if (found >= key)
77 | end = mid;
78 | else
79 | start = mid + size;
80 | }
81 | return 0;
82 | }
83 |
84 | private static int GetBlockSizeSV(LiveHeXVersion version) =>
85 | version switch
86 | {
87 | >= LiveHeXVersion.SV_v130 => 48, // Thanks, santacrab!
88 | _ => 32,
89 | };
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # .NET Desktop
2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions.
3 | # Add steps that publish symbols, save build artifacts, and more:
4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
5 |
6 | trigger:
7 | - master
8 |
9 | pool:
10 | vmImage: 'windows-2022'
11 |
12 | variables:
13 | solution: '$(Build.SourcesDirectory)/PKHeX-Plugins.sln'
14 | buildPlatform: 'Any CPU'
15 | buildConfiguration: 'Release'
16 |
17 | steps:
18 | - task: UseDotNet@2
19 | displayName: Use .NET 8.0
20 | inputs:
21 | packageType: 'sdk'
22 | version: '8.0.x'
23 |
24 | - task: NuGetToolInstaller@1
25 | inputs:
26 | versionSpec: '>=6.4.0'
27 | checkLatest: true
28 |
29 | - task: NuGetCommand@2
30 | inputs:
31 | restoreSolution: '$(solution)'
32 |
33 | - powershell: |
34 | git clone -q --depth=5 --branch=master https://github.com/kwsch/PKHeX.git $(Build.SourcesDirectory)/PKHeX
35 |
36 | - task: NuGetCommand@2
37 | inputs:
38 | command: 'restore'
39 | restoreSolution: '$(Build.SourcesDirectory)/PKHeX/PKHeX.sln'
40 | noCache: true
41 |
42 | - task: MSBuild@1
43 | inputs:
44 | solution: '$(Build.SourcesDirectory)/PKHeX/PKHeX.Core/PKHeX.Core.csproj'
45 | platform: '$(buildPlatform)'
46 | configuration: '$(buildConfiguration)'
47 | createLogFile: true
48 |
49 | - powershell: |
50 | $nugetver = (Get-ChildItem "$(UserProfile)/.nuget/packages/pkhex.core" | Sort-Object -Property LastWriteTime -Descending)[0].Name
51 | Copy-Item "$(Build.SourcesDirectory)\pkhex\PKHeX.Core\bin\Any CPU\Release\net8.0\PKHeX.Core.dll" "$(UserProfile)\.nuget\packages\pkhex.core\$nugetver\lib\net8.0"
52 |
53 | - task: VSBuild@1
54 | inputs:
55 | solution: '$(solution)'
56 | platform: '$(buildPlatform)'
57 | configuration: '$(buildConfiguration)'
58 | createLogFile: true
59 |
60 | - task: CmdLine@2
61 | inputs:
62 | script: |
63 | mkdir bin
64 | xcopy $(Agent.BuildDirectory)\s\AutoLegalityMod\bin\Release\net8.0-windows\AutoModPlugins.dll $(Agent.BuildDirectory)\s\bin
65 |
66 | - task: PublishBuildArtifacts@1
67 | inputs:
68 | PathtoPublish: '$(Agent.BuildDirectory)\s\bin'
69 | ArtifactName: 'PKHeX-Plugins'
70 | publishLocation: 'Container'
71 |
--------------------------------------------------------------------------------
/setup_bleedingedge.ps1:
--------------------------------------------------------------------------------
1 | # Setting the TLS protocol to 1.2 instead of the default 1.0
2 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
3 |
4 | # Powershell script to download PKHeX and Plugins (latest)
5 | Write-Host "PKHeX and PKHeX-Plugins downloader (latest releases)"
6 | Write-Host "Please report any issues with this setup file via GitHub issues at https://github.com/architdate/PKHeX-Plugins/issues"
7 | Write-Host ""
8 | Write-Host ""
9 |
10 | # check network path locations
11 | $networkPaths = @("OneDrive", "Dropbox", "Mega")
12 | for ($i=0; $i -lt $networkPaths.Length; $i++) {
13 | $path = $networkPaths[$i]
14 | $currDir = Get-Location
15 | if ($currDir.Path.Contains($path)) {
16 | Write-Host "WARNING: $path is detected on your system. Please move the setup file to a different location before running the program."
17 | Read-Host "Press Enter to exit"
18 | exit
19 | }
20 | }
21 |
22 | # set headers
23 | $headers = @{
24 | "method"="GET"
25 | "authority"="projectpokemon.org"
26 | "scheme"="https"
27 | "path"="/home/files/file/1-pkhex/"
28 | "cache-control"="max-age=0"
29 | "upgrade-insecure-requests"="1"
30 | "user-agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"
31 | "accept"="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
32 | "sec-fetch-site"="same-origin"
33 | "sec-fetch-mode"="navigate"
34 | "sec-fetch-user"="?1"
35 | "sec-fetch-dest"="document"
36 | "referer"="https://projectpokemon.org/home/files/"
37 | "accept-encoding"="gzip, deflate, br"
38 | "accept-language"="en-US,en;q=0.9"
39 | };
40 |
41 | # close any open instances of PKHeX that may prevent updating the file
42 | if ((get-process "pkhex" -ea SilentlyContinue) -ne $Null) {Stop-Process -processname "pkhex"}
43 |
44 | # get the ddl with csrf token
45 | $BasePKHeX = ((Invoke-WebRequest -Uri "https://projectpokemon.org/home/files/file/2445-pkhex-development-build/" -Headers $headers -UseBasicParsing -SessionVariable "Session").Links | Where-Object {$_.href -like "https://projectpokemon.org/home/files/file/2445-pkhex-development-build/?do=download*"}).href.replace("&", "&")
46 |
47 | # get cookies after making a webrequest to the official download site
48 | $url = $BasePKHeX
49 | $cookie = $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/2445-pkhex-development-build/")[0].Name + "=" + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/2445-pkhex-development-build/")[0].Value + "; " + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/2445-pkhex-development-build/")[1].Name + "=" + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/2445-pkhex-development-build/")[1].Value + "; ips4_ipsTimezone=Asia/Singapore; ips4_hasJS=true"
50 |
51 | # set cookies and ddl path to the header
52 | $headers.cookie = $cookie
53 | $headers.path = $url
54 |
55 | # download as PKHeX.zip
56 | Write-Host "Downloading latest PKHeX Build (latest) from https://projectpokemon.org/home/files/file/2445-pkhex-development-build/ ..."
57 | Invoke-WebRequest $url -OutFile "PKHeX.zip" -Headers $headers -WebSession $session -Method Get -ContentType "application/zip" -UseBasicParsing
58 |
59 | # get latest plugins build
60 | $j = Invoke-WebRequest 'https://dev.azure.com/architdate/40cccbb5-1611-4da1-aa70-c9cc0fba36e2/_apis/build/builds?definitions=1&$top=1&resultFilter=succeeded&api-version=6.0' -UseBasicParsing | ConvertFrom-Json
61 | $buildid = $j.value.id
62 | $aurl = "https://dev.azure.com/architdate/40cccbb5-1611-4da1-aa70-c9cc0fba36e2/_apis/build/builds/$buildid/artifacts?artifactName=PKHeX-Plugins&api-version=6.0"
63 | $a = Invoke-WebRequest $aurl -UseBasicParsing | ConvertFrom-Json
64 | $download = $a.resource.downloadUrl
65 | $file = "PKHeX-Plugins.zip"
66 |
67 | Invoke-WebRequest $download -OutFile $file -ContentType "application/zip" -UseBasicParsing
68 |
69 | # cleanup old files if they exist
70 | Write-Host Cleaning up previous releases if they exist ...
71 | Remove-Item plugins/AutoModPlugins.* -ErrorAction Ignore
72 | Remove-Item plugins/PKHeX.Core.AutoMod.* -ErrorAction Ignore
73 | Remove-Item plugins/QRPlugins.* -ErrorAction Ignore
74 | Remove-Item PKHeX.exe -ErrorAction Ignore
75 | Remove-Item PKHeX.Core.* -ErrorAction Ignore
76 | Remove-Item PKHeX.exe.* -ErrorAction Ignore
77 | Remove-Item PKHeX.pdb -ErrorAction Ignore
78 | Remove-Item PKHeX.Drawing.* -ErrorAction Ignore
79 | Remove-Item QRCoder.dll -ErrorAction Ignore
80 |
81 | # Extract pkhex
82 | Write-Host Extracting PKHeX ...
83 | Expand-Archive -Path PKHeX.zip -DestinationPath $pwd -Force
84 |
85 | # Delete zip file
86 | Write-Host Deleting PKHeX.zip ...
87 | Remove-Item PKHeX.zip
88 |
89 | # Unblock plugins and extract
90 | dir PKHeX-Plugins*.zip | Unblock-File
91 | New-Item -ItemType Directory -Force -Path plugins | Out-Null
92 | Write-Host Extracting Plugins ...
93 | Expand-Archive -Path PKHeX-Plugins*.zip -DestinationPath $pwd -Force
94 | Move-Item -Path PKHeX-Plugins\*.dll -Destination plugins -Force
95 | Write-Host Deleting Plugins ...
96 | Remove-Item PKHeX-Plugins -Recurse
97 | Remove-Item PKHeX-Plugins*.zip
98 |
99 | #Finish up script
100 | Read-Host -Prompt "Press Enter to exit"
101 |
--------------------------------------------------------------------------------
/setup_stable.ps1:
--------------------------------------------------------------------------------
1 | # Setting the TLS protocol to 1.2 instead of the default 1.0
2 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
3 |
4 | # Powershell script to download PKHeX and Plugins (stable)
5 | Write-Host "PKHeX and PKHeX-Plugins downloader (stable releases)"
6 | Write-Host "Please report any issues with this setup file via GitHub issues at https://github.com/architdate/PKHeX-Plugins/issues"
7 | Write-Host ""
8 | Write-Host ""
9 |
10 | # check network path locations
11 | $networkPaths = @("OneDrive", "Dropbox", "Mega")
12 | for ($i=0; $i -lt $networkPaths.Length; $i++) {
13 | $path = $networkPaths[$i]
14 | $currDir = Get-Location
15 | if ($currDir.Path.Contains($path)) {
16 | Write-Host "WARNING: $path is detected on your system. Please move the setup file to a different location before running the program."
17 | Read-Host "Press Enter to exit"
18 | exit
19 | }
20 | }
21 |
22 | # set headers
23 | $headers = @{
24 | "method"="GET"
25 | "authority"="projectpokemon.org"
26 | "scheme"="https"
27 | "path"="/home/files/file/1-pkhex/"
28 | "cache-control"="max-age=0"
29 | "upgrade-insecure-requests"="1"
30 | "user-agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"
31 | "accept"="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
32 | "sec-fetch-site"="same-origin"
33 | "sec-fetch-mode"="navigate"
34 | "sec-fetch-user"="?1"
35 | "sec-fetch-dest"="document"
36 | "referer"="https://projectpokemon.org/home/files/"
37 | "accept-encoding"="gzip, deflate, br"
38 | "accept-language"="en-US,en;q=0.9"
39 | };
40 |
41 | # close any open instances of PKHeX that may prevent updating the file
42 | if ((get-process "pkhex" -ea SilentlyContinue) -ne $Null) {Stop-Process -processname "pkhex"}
43 |
44 | # get latest stable plugins
45 | $pluginsrepo = "architdate/PKHeX-Plugins"
46 | $baserepo = "kwsch/PKHeX"
47 | $file = "PKHeX-Plugins.zip"
48 | $releases = "https://api.github.com/repos/$pluginsrepo/releases"
49 | $basereleases = "https://api.github.com/repos/$baserepo/releases"
50 |
51 | Write-Host "Determining latest plugin release ..."
52 | $tag = (Invoke-WebRequest $releases -UseBasicParsing | ConvertFrom-Json)[0].tag_name
53 | $basetag = (Invoke-WebRequest $basereleases -UseBasicParsing | ConvertFrom-Json)[0].tag_name
54 | if (!$tag.Equals($basetag)) {
55 | Write-Host "Auto-Legality Mod for the latest stable PKHeX has not been released yet."
56 | Write-Host "Please wait for a new PKHeX-Plugins release before using this setup file."
57 | Write-Host "Alternatively, consider reading the wiki to manually setup ALM with an older PKHeX build."
58 | Read-Host "Press Enter to exit"
59 | exit
60 | }
61 |
62 | # get the correct page for the tag
63 | $BasePKHeX = ((Invoke-WebRequest -Uri "https://projectpokemon.org/home/files/file/1-pkhex/" -Headers $headers -UseBasicParsing -SessionVariable "Session").Links | Where-Object {$_.href -like "https://projectpokemon.org/home/files/file/1-pkhex/?do=download*"}).href.replace("&", "&")
64 |
65 | # get cookies after making a webrequest to the official download site
66 | $url = $BasePKHeX
67 | $cookie = $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/1-pkhex/")[0].Name + "=" + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/1-pkhex/")[0].Value + "; " + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/1-pkhex/")[1].Name + "=" + $Session.Cookies.GetCookies("https://projectpokemon.org/home/files/file/1-pkhex/")[1].Value + "; ips4_ipsTimezone=Asia/Singapore; ips4_hasJS=true"
68 |
69 | # set cookies and ddl path to the header
70 | $headers.cookie = $cookie
71 | $headers.path = $url
72 |
73 | # download as PKHeX.zip
74 | Write-Host "Downloading latest PKHeX Release (stable) from https://projectpokemon.org/home/files/file/1-pkhex/ ..."
75 | Invoke-WebRequest $url -OutFile "PKHeX.zip" -Headers $headers -WebSession $session -Method Get -ContentType "application/zip" -UseBasicParsing
76 |
77 | # download as PKHeX-Plugins.zip
78 | Write-Host Downloading latest PKHeX-Plugin Release: $tag
79 | $download = "https://github.com/$pluginsrepo/releases/download/$tag/$file"
80 |
81 | Invoke-WebRequest $download -OutFile $file -ContentType "application/zip" -UseBasicParsing
82 |
83 | # cleanup old files if they exist
84 | Write-Host Cleaning up previous releases if they exist ...
85 | Remove-Item plugins/AutoModPlugins.* -ErrorAction Ignore
86 | Remove-Item plugins/PKHeX.Core.AutoMod.* -ErrorAction Ignore
87 | Remove-Item plugins/QRPlugins.* -ErrorAction Ignore
88 | Remove-Item PKHeX.exe -ErrorAction Ignore
89 | Remove-Item PKHeX.Core.* -ErrorAction Ignore
90 | Remove-Item PKHeX.exe.* -ErrorAction Ignore
91 | Remove-Item PKHeX.pdb -ErrorAction Ignore
92 | Remove-Item PKHeX.Drawing.* -ErrorAction Ignore
93 | Remove-Item QRCoder.dll -ErrorAction Ignore
94 |
95 |
96 | # Extract pkhex
97 | Write-Host Extracting PKHeX ...
98 | Expand-Archive -Path PKHeX.zip -DestinationPath $pwd
99 |
100 | # Delete zip file
101 | Write-Host Deleting PKHeX.zip ...
102 | Remove-Item PKHeX.zip
103 |
104 | # Unblock plugins and extract
105 | dir PKHeX-Plugins*.zip | Unblock-File
106 | New-Item -ItemType Directory -Force -Path plugins | Out-Null
107 | Write-Host Extracting Plugins ...
108 | Expand-Archive -Path PKHeX-Plugins*.zip -Force -DestinationPath plugins
109 | Write-Host Deleting Plugins ...
110 | Remove-Item PKHeX-Plugins*.zip
111 |
112 | #Finish up script
113 | Read-Host -Prompt "Press Enter to exit"
--------------------------------------------------------------------------------