Initial commit: Brass & Sigil monorepo
Self-hosted Minecraft modpack distribution + administration system.
- launcher/ Avalonia 12 desktop client; single-file win-x64 publish.
Microsoft auth via XboxAuthNet, manifest+SHA-1 mod sync,
portable install path, sidecar settings.
- server/ brass-sigil-server daemon (.NET 8, linux-x64). Wraps the
MC subprocess, embedded Kestrel admin panel with cookie
auth + rate limiting, RCON bridge, scheduled backups,
BlueMap CLI integration with player markers + skin proxy,
friend-side whitelist request flow, world wipe with seed
selection (keep current / random / custom).
- pack/ pack.lock.json (Modrinth + manual CurseForge entries),
data-only tweak source under tweaks/, build outputs in
overrides/ (gitignored).
- scripts/ Build-Pack / Build-Tweaks / Update-Pack / Check-Updates
plus Deploy-Brass.ps1 unified one-shot deploy with
version-bump pre-flight and daemon-state detection.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using fNbt;
|
||||
|
||||
namespace ModpackLauncher.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// }
|
||||
/// }
|
||||
/// </summary>
|
||||
public sealed class ServerListService
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user