using System;
using System.IO;
using System.Linq;
using fNbt;
namespace ModpackLauncher.Services;
///
/// Pre-populates Minecraft's multiplayer server list (`servers.dat`) so the
/// modpack's server is one click away on first launch.
///
/// servers.dat is uncompressed NBT (unlike level.dat which is gzipped). Schema:
/// compound {
/// servers : list[compound] {
/// name : string
/// ip : string
/// acceptTextures : byte (optional, 1 = enabled)
/// hidden : byte (optional, 0 = visible)
/// icon : string (optional, base64 PNG)
/// }
/// }
///
public sealed class ServerListService
{
///
/// Add or update an entry. Match-by-IP: if an entry with the same IP exists,
/// update its name; otherwise prepend a new entry so the friend's first
/// glance at multiplayer shows our server at the top.
///
public void EnsureServer(string gameDir, string name, string ip)
{
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(ip)) return;
Directory.CreateDirectory(gameDir);
var path = Path.Combine(gameDir, "servers.dat");
NbtCompound root;
NbtList servers;
if (File.Exists(path))
{
try
{
var nbt = new NbtFile();
nbt.LoadFromFile(path, NbtCompression.None, _ => true);
root = nbt.RootTag;
if (root.TryGet("servers", out NbtList? existingList) && existingList is not null)
{
servers = existingList;
}
else
{
servers = new NbtList("servers", NbtTagType.Compound);
root.Add(servers);
}
}
catch
{
// Existing file unreadable -- start fresh rather than crashing the install.
root = new NbtCompound("");
servers = new NbtList("servers", NbtTagType.Compound);
root.Add(servers);
}
}
else
{
root = new NbtCompound("");
servers = new NbtList("servers", NbtTagType.Compound);
root.Add(servers);
}
// Match-by-IP. If found, update the display name; otherwise prepend.
for (int i = 0; i < servers.Count; i++)
{
if (servers[i] is not NbtCompound entry) continue;
if (entry.TryGet("ip", out NbtString? ipTag) && ipTag?.Value == ip)
{
entry["name"] = new NbtString("name", name);
Save(root, path);
return;
}
}
var newEntry = new NbtCompound
{
new NbtString("name", name),
new NbtString("ip", ip),
// acceptTextures = 1 lets the server send its resource pack without prompting
// (the player still gets the prompt; this just allows the option). Default
// value matches what vanilla MC writes when you click "Done" in the UI.
new NbtByte("acceptTextures", 1),
};
servers.Insert(0, newEntry);
Save(root, path);
}
private static void Save(NbtCompound root, string path)
{
var file = new NbtFile(root);
file.SaveToFile(path, NbtCompression.None);
}
}