diff --git a/Minecraft-Realms-Emulator/Helpers/MinecraftServerQuery.cs b/Minecraft-Realms-Emulator/Helpers/MinecraftServerQuery.cs new file mode 100644 index 0000000..beeb6d8 --- /dev/null +++ b/Minecraft-Realms-Emulator/Helpers/MinecraftServerQuery.cs @@ -0,0 +1,98 @@ +using Minecraft_Realms_Emulator.Responses; +using Newtonsoft.Json; +using System.Net.Sockets; +using System.Text; + +namespace Minecraft_Realms_Emulator.Helpers +{ + public class MinecraftServerQuery + { + public MinecraftServerQueryRepsonse? Query(string address) + { + string server = address.Split(':')[0]; + int port = address.Contains(':') ? int.Parse(address.Split(':')[1]) : 25565; + + try + { + using (TcpClient client = new()) + { + client.Connect(server, port); + + using (NetworkStream stream = client.GetStream()) + using (BinaryWriter writer = new(stream)) + using (BinaryReader reader = new(stream)) + { + // Send Handshake packet (https://wiki.vg/Protocol#Handshake) + byte[] handshakePacket = CreateHandshakePacket(server, port); + writer.Write((byte)handshakePacket.Length); // Packet length + writer.Write(handshakePacket); // Packet data + + // Send Status Request packet (https://wiki.vg/Protocol#Request) + writer.Write((byte)0x01); // Packet length + writer.Write((byte)0x00); // Packet ID (Request) + + // Read the response length + int length = ReadVarInt(reader); + // Read the response packet ID + int packetId = ReadVarInt(reader); + if (packetId != 0x00) throw new Exception("Invalid packet ID"); + + // Read the JSON length + int jsonLength = ReadVarInt(reader); + // Read the JSON response + byte[] jsonData = reader.ReadBytes(jsonLength); + string json = Encoding.UTF8.GetString(jsonData); + + return JsonConvert.DeserializeObject(json); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + return null; + } + } + + private static byte[] CreateHandshakePacket(string server, int port) + { + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(ms)) + { + WriteVarInt(writer, 0x00); // Packet ID (Handshake) + WriteVarInt(writer, 754); // Protocol version (754 for Minecraft 1.16.5) + WriteVarInt(writer, server.Length); // Server address length + writer.Write(Encoding.UTF8.GetBytes(server)); // Server address + writer.Write((ushort)port); // Server port + WriteVarInt(writer, 1); // Next state (1 for status) + + return ms.ToArray(); + } + } + } + + private static int ReadVarInt(BinaryReader reader) + { + int value = 0; + int size = 0; + int b; + while (((b = reader.ReadByte()) & 0x80) == 0x80) + { + value |= (b & 0x7F) << (size++ * 7); + if (size > 5) throw new Exception("VarInt is too big"); + } + return value | (b << (size * 7)); + } + + private static void WriteVarInt(BinaryWriter writer, int value) + { + while ((value & 0xFFFFFF80) != 0) + { + writer.Write((byte)((value & 0x7F) | 0x80)); + value >>= 7; + } + writer.Write((byte)(value & 0x7F)); + } + } +} diff --git a/Minecraft-Realms-Emulator/Modes/External/ActivitiesController.cs b/Minecraft-Realms-Emulator/Modes/External/ActivitiesController.cs new file mode 100644 index 0000000..d862b77 --- /dev/null +++ b/Minecraft-Realms-Emulator/Modes/External/ActivitiesController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using Minecraft_Realms_Emulator.Attributes; +using Minecraft_Realms_Emulator.Responses; + +namespace Minecraft_Realms_Emulator.Modes.External +{ + [Route("modes/external/[controller]")] + [ApiController] + [RequireMinecraftCookie] + public class ActivitiesController : ControllerBase + { + [HttpGet("liveplayerlist")] + public ActionResult GetLivePlayerList() + { + LivePlayerListsResponse response = new() + { + Lists = [] + }; + + return Ok(response); + } + } +} diff --git a/Minecraft-Realms-Emulator/Modes/Realms/Controllers/ActivitiesController.cs b/Minecraft-Realms-Emulator/Modes/Realms/Controllers/ActivitiesController.cs new file mode 100644 index 0000000..96bf19d --- /dev/null +++ b/Minecraft-Realms-Emulator/Modes/Realms/Controllers/ActivitiesController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Mvc; +using Minecraft_Realms_Emulator.Attributes; +using Minecraft_Realms_Emulator.Data; +using Minecraft_Realms_Emulator.Enums; +using Minecraft_Realms_Emulator.Helpers; +using Minecraft_Realms_Emulator.Responses; +using Newtonsoft.Json; + +namespace Minecraft_Realms_Emulator.Modes.Realms +{ + [Route("modes/realms/[controller]")] + [ApiController] + [RequireMinecraftCookie] + public class ActivitiesController : ControllerBase + { + private readonly DataContext _context; + + public ActivitiesController(DataContext context) + { + _context = context; + } + + [HttpGet("liveplayerlist")] + public ActionResult GetLivePlayerList() + { + string cookie = Request.Headers.Cookie; + string playerUUID = cookie.Split(";")[0].Split(":")[2]; + + List lists = []; + + var worlds = _context.Worlds.Where(w => w.State == nameof(StateEnum.OPEN) && w.OwnerUUID == playerUUID || w.State == nameof(StateEnum.OPEN) && w.Players.Any(p => p.Uuid == playerUUID && p.Accepted)).ToList(); + + foreach (var world in worlds) + { + var connection = _context.Connections.Where(c => c.World.Id == world.Id).FirstOrDefault(); + var query = new MinecraftServerQuery().Query(connection.Address); + + if (query == null) continue; + + List players = []; + + if (query.Players.Sample == null) continue; + + foreach (var player in query.Players.Sample) + { + players.Add(new + { + playerId = player.Id.Replace("-", ""), + }); + } + + LivePlayerList list = new() + { + ServerId = world.Id, + PlayerList = JsonConvert.SerializeObject(players), + }; + + lists.Add(list); + }; + + LivePlayerListsResponse response = new() + { + Lists = lists + }; + + return Ok(response); + } + } +} diff --git a/Minecraft-Realms-Emulator/Responses/LivePlayerListsResponse.cs b/Minecraft-Realms-Emulator/Responses/LivePlayerListsResponse.cs new file mode 100644 index 0000000..01c78a5 --- /dev/null +++ b/Minecraft-Realms-Emulator/Responses/LivePlayerListsResponse.cs @@ -0,0 +1,13 @@ +namespace Minecraft_Realms_Emulator.Responses +{ + public class LivePlayerListsResponse + { + public List Lists { get; set; } = []; + } + + public class LivePlayerList + { + public int ServerId { get; set; } + public string PlayerList { get; set; } = null!; + } +} diff --git a/Minecraft-Realms-Emulator/Responses/MinecraftServerQueryRepsonse.cs b/Minecraft-Realms-Emulator/Responses/MinecraftServerQueryRepsonse.cs new file mode 100644 index 0000000..1b6006e --- /dev/null +++ b/Minecraft-Realms-Emulator/Responses/MinecraftServerQueryRepsonse.cs @@ -0,0 +1,29 @@ +namespace Minecraft_Realms_Emulator.Responses +{ + public class MinecraftServerQueryRepsonse + { + public MinecraftServerQueryVersionObject Version { get; set; } = null!; + public bool EnforcesSecureChat { get; set; } + public string Description { get; set; } = null!; + public MinecraftServerQueryPlayersObject Players { get; set; } = null!; + } + + public class MinecraftServerQueryVersionObject + { + public string Name { get; set; } = null!; + public string Protocol { get; set; } = null!; + } + + public class MinecraftServerQueryPlayersObject + { + public int Max { get; set; } + public int Online { get; set; } + public List Sample { get; set; } = null!; + } + + public class MinecraftServerQueryPlayersSampleObject + { + public string Id { get; set; } = null!; + public string Name { get; set; } = null!; + } +} \ No newline at end of file