add generated world, territory registries and serverside verification for only legit territories and worlds defined by server

This commit is contained in:
azyges
2025-11-08 07:38:35 +09:00
parent 7cfe29e511
commit cf5135f598
8 changed files with 1290 additions and 5 deletions

View File

@@ -94,6 +94,16 @@ public partial class LightlessHub
throw new HubException("Unsupported chat channel."); throw new HubException("Unsupported chat channel.");
} }
if (channel.WorldId == 0 || !WorldRegistry.IsKnownWorld(channel.WorldId))
{
throw new HubException("Unsupported chat channel.");
}
if (presence.TerritoryId == 0 || !definition.TerritoryIds.Contains(presence.TerritoryId))
{
throw new HubException("Zone chat is only available in supported territories.");
}
string? hashedCid = null; string? hashedCid = null;
var isLightfinder = false; var isLightfinder = false;
if (IsValidHashedCid(UserCharaIdent)) if (IsValidHashedCid(UserCharaIdent))
@@ -110,6 +120,7 @@ public partial class LightlessHub
UserUID, UserUID,
definition, definition,
channel.WorldId, channel.WorldId,
presence.TerritoryId,
hashedCid, hashedCid,
isLightfinder, isLightfinder,
isActive: true); isActive: true);

View File

@@ -43,7 +43,8 @@ public readonly record struct ZoneChannelDefinition(
string Key, string Key,
string DisplayName, string DisplayName,
ChatChannelDescriptor Descriptor, ChatChannelDescriptor Descriptor,
IReadOnlyList<string> TerritoryNames); IReadOnlyList<string> TerritoryNames,
IReadOnlySet<ushort> TerritoryIds);
public readonly record struct ChannelKey(ChatChannelType Type, ushort WorldId, string CustomKey) public readonly record struct ChannelKey(ChatChannelType Type, ushort WorldId, string CustomKey)
{ {

View File

@@ -22,7 +22,10 @@ internal static class ChatZoneDefinitions
{ {
"Limsa Lominsa Lower Decks", "Limsa Lominsa Lower Decks",
"Limsa Lominsa Upper Decks" "Limsa Lominsa Upper Decks"
}), },
TerritoryIds: TerritoryRegistry.GetIds(
"Limsa Lominsa Lower Decks",
"Limsa Lominsa Upper Decks")),
new ZoneChannelDefinition( new ZoneChannelDefinition(
Key: "gridania", Key: "gridania",
DisplayName: "Gridania", DisplayName: "Gridania",
@@ -37,7 +40,10 @@ internal static class ChatZoneDefinitions
{ {
"New Gridania", "New Gridania",
"Old Gridania" "Old Gridania"
}), },
TerritoryIds: TerritoryRegistry.GetIds(
"New Gridania",
"Old Gridania")),
new ZoneChannelDefinition( new ZoneChannelDefinition(
Key: "uldah", Key: "uldah",
DisplayName: "Ul'dah", DisplayName: "Ul'dah",
@@ -52,6 +58,10 @@ internal static class ChatZoneDefinitions
{ {
"Ul'dah - Steps of Nald", "Ul'dah - Steps of Nald",
"Ul'dah - Steps of Thal" "Ul'dah - Steps of Thal"
}) },
TerritoryIds: TerritoryRegistry.GetIds(
"Ul'dah - Steps of Nald",
"Ul'dah - Steps of Thal"))
}; };
} }

View File

@@ -0,0 +1,5 @@
namespace LightlessSyncServer.Models;
internal readonly record struct TerritoryDefinition(
ushort TerritoryId,
string Name);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
namespace LightlessSyncServer.Models;
internal readonly record struct WorldDefinition(
ushort WorldId,
string Name,
string Region,
string DataCenter);

View File

@@ -0,0 +1,117 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace LightlessSyncServer.Models;
internal static class WorldRegistry
{
private static readonly WorldDefinition[] WorldArray = new[]
{
new WorldDefinition(80, "Cerberus", "Europe", "Chaos"),
new WorldDefinition(83, "Louisoix", "Europe", "Chaos"),
new WorldDefinition(71, "Moogle", "Europe", "Chaos"),
new WorldDefinition(39, "Omega", "Europe", "Chaos"),
new WorldDefinition(401, "Phantom", "Europe", "Chaos"),
new WorldDefinition(97, "Ragnarok", "Europe", "Chaos"),
new WorldDefinition(400, "Sagittarius", "Europe", "Chaos"),
new WorldDefinition(85, "Spriggan", "Europe", "Chaos"),
new WorldDefinition(402, "Alpha", "Europe", "Light"),
new WorldDefinition(36, "Lich", "Europe", "Light"),
new WorldDefinition(66, "Odin", "Europe", "Light"),
new WorldDefinition(56, "Phoenix", "Europe", "Light"),
new WorldDefinition(403, "Raiden", "Europe", "Light"),
new WorldDefinition(67, "Shiva", "Europe", "Light"),
new WorldDefinition(33, "Twintania", "Europe", "Light"),
new WorldDefinition(42, "Zodiark", "Europe", "Light"),
new WorldDefinition(90, "Aegis", "Japan", "Elemental"),
new WorldDefinition(68, "Atomos", "Japan", "Elemental"),
new WorldDefinition(45, "Carbuncle", "Japan", "Elemental"),
new WorldDefinition(58, "Garuda", "Japan", "Elemental"),
new WorldDefinition(94, "Gungnir", "Japan", "Elemental"),
new WorldDefinition(49, "Kujata", "Japan", "Elemental"),
new WorldDefinition(72, "Tonberry", "Japan", "Elemental"),
new WorldDefinition(50, "Typhon", "Japan", "Elemental"),
new WorldDefinition(43, "Alexander", "Japan", "Gaia"),
new WorldDefinition(69, "Bahamut", "Japan", "Gaia"),
new WorldDefinition(92, "Durandal", "Japan", "Gaia"),
new WorldDefinition(46, "Fenrir", "Japan", "Gaia"),
new WorldDefinition(59, "Ifrit", "Japan", "Gaia"),
new WorldDefinition(98, "Ridill", "Japan", "Gaia"),
new WorldDefinition(76, "Tiamat", "Japan", "Gaia"),
new WorldDefinition(51, "Ultima", "Japan", "Gaia"),
new WorldDefinition(44, "Anima", "Japan", "Mana"),
new WorldDefinition(23, "Asura", "Japan", "Mana"),
new WorldDefinition(70, "Chocobo", "Japan", "Mana"),
new WorldDefinition(47, "Hades", "Japan", "Mana"),
new WorldDefinition(48, "Ixion", "Japan", "Mana"),
new WorldDefinition(96, "Masamune", "Japan", "Mana"),
new WorldDefinition(28, "Pandaemonium", "Japan", "Mana"),
new WorldDefinition(61, "Titan", "Japan", "Mana"),
new WorldDefinition(24, "Belias", "Japan", "Meteor"),
new WorldDefinition(82, "Mandragora", "Japan", "Meteor"),
new WorldDefinition(60, "Ramuh", "Japan", "Meteor"),
new WorldDefinition(29, "Shinryu", "Japan", "Meteor"),
new WorldDefinition(30, "Unicorn", "Japan", "Meteor"),
new WorldDefinition(52, "Valefor", "Japan", "Meteor"),
new WorldDefinition(31, "Yojimbo", "Japan", "Meteor"),
new WorldDefinition(32, "Zeromus", "Japan", "Meteor"),
new WorldDefinition(73, "Adamantoise", "North America", "Aether"),
new WorldDefinition(79, "Cactuar", "North America", "Aether"),
new WorldDefinition(54, "Faerie", "North America", "Aether"),
new WorldDefinition(63, "Gilgamesh", "North America", "Aether"),
new WorldDefinition(40, "Jenova", "North America", "Aether"),
new WorldDefinition(65, "Midgardsormr", "North America", "Aether"),
new WorldDefinition(99, "Sargatanas", "North America", "Aether"),
new WorldDefinition(57, "Siren", "North America", "Aether"),
new WorldDefinition(91, "Balmung", "North America", "Crystal"),
new WorldDefinition(34, "Brynhildr", "North America", "Crystal"),
new WorldDefinition(74, "Coeurl", "North America", "Crystal"),
new WorldDefinition(62, "Diabolos", "North America", "Crystal"),
new WorldDefinition(81, "Goblin", "North America", "Crystal"),
new WorldDefinition(75, "Malboro", "North America", "Crystal"),
new WorldDefinition(37, "Mateus", "North America", "Crystal"),
new WorldDefinition(41, "Zalera", "North America", "Crystal"),
new WorldDefinition(408, "Cuchulainn", "North America", "Dynamis"),
new WorldDefinition(411, "Golem", "North America", "Dynamis"),
new WorldDefinition(406, "Halicarnassus", "North America", "Dynamis"),
new WorldDefinition(409, "Kraken", "North America", "Dynamis"),
new WorldDefinition(407, "Maduin", "North America", "Dynamis"),
new WorldDefinition(404, "Marilith", "North America", "Dynamis"),
new WorldDefinition(410, "Rafflesia", "North America", "Dynamis"),
new WorldDefinition(405, "Seraph", "North America", "Dynamis"),
new WorldDefinition(78, "Behemoth", "North America", "Primal"),
new WorldDefinition(93, "Excalibur", "North America", "Primal"),
new WorldDefinition(53, "Exodus", "North America", "Primal"),
new WorldDefinition(35, "Famfrit", "North America", "Primal"),
new WorldDefinition(95, "Hyperion", "North America", "Primal"),
new WorldDefinition(55, "Lamia", "North America", "Primal"),
new WorldDefinition(64, "Leviathan", "North America", "Primal"),
new WorldDefinition(77, "Ultros", "North America", "Primal"),
new WorldDefinition(22, "Bismarck", "Oceania", "Materia"),
new WorldDefinition(21, "Ravana", "Oceania", "Materia"),
new WorldDefinition(86, "Sephirot", "Oceania", "Materia"),
new WorldDefinition(87, "Sophia", "Oceania", "Materia"),
new WorldDefinition(88, "Zurvan", "Oceania", "Materia"),
};
public static IReadOnlyList<WorldDefinition> All { get; } = Array.AsReadOnly(WorldArray);
public static IReadOnlyDictionary<ushort, WorldDefinition> ById { get; } = new ReadOnlyDictionary<ushort, WorldDefinition>(WorldArray.ToDictionary(w => w.WorldId));
public static IReadOnlyDictionary<string, IReadOnlyList<WorldDefinition>> ByDataCenter { get; } = new ReadOnlyDictionary<string, IReadOnlyList<WorldDefinition>>(WorldArray
.GroupBy(w => w.DataCenter, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => (IReadOnlyList<WorldDefinition>)g.OrderBy(w => w.Name, StringComparer.Ordinal).ToArray(),
StringComparer.OrdinalIgnoreCase));
public static IReadOnlyDictionary<string, IReadOnlyList<WorldDefinition>> ByRegion { get; } = new ReadOnlyDictionary<string, IReadOnlyList<WorldDefinition>>(WorldArray
.GroupBy(w => w.Region, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => (IReadOnlyList<WorldDefinition>)g.OrderBy(w => w.Name, StringComparer.Ordinal).ToArray(),
StringComparer.OrdinalIgnoreCase));
public static bool TryGet(ushort worldId, out WorldDefinition definition) => ById.TryGetValue(worldId, out definition);
public static bool IsKnownWorld(ushort worldId) => ById.ContainsKey(worldId);
}

View File

@@ -49,11 +49,24 @@ public sealed class ChatChannelService
string userUid, string userUid,
ZoneChannelDefinition definition, ZoneChannelDefinition definition,
ushort worldId, ushort worldId,
ushort territoryId,
string? hashedCid, string? hashedCid,
bool isLightfinder, bool isLightfinder,
bool isActive) bool isActive)
{ {
var descriptor = definition.Descriptor with { WorldId = worldId }; if (worldId == 0 || !WorldRegistry.IsKnownWorld(worldId))
{
_logger.LogWarning("Rejected zone chat presence for {User} in {Zone}: unknown world {WorldId}", userUid, definition.Key, worldId);
return null;
}
if (!definition.TerritoryIds.Contains(territoryId))
{
_logger.LogWarning("Rejected zone chat presence for {User} in {Zone}: invalid territory {TerritoryId}", userUid, definition.Key, territoryId);
return null;
}
var descriptor = definition.Descriptor with { WorldId = worldId, ZoneId = territoryId };
var participant = new ChatParticipantInfo( var participant = new ChatParticipantInfo(
Token: string.Empty, Token: string.Empty,
UserUid: userUid, UserUid: userUid,