~ruther/NosTale-Anonymizer

879aafdb3aca7ebba88206efa406901fe9a7f4c6 — František Boháček 2 years ago fd1290a
feat: add basic anonymizer function
38 files changed, 1245 insertions(+), 0 deletions(-)

A src/Anonymizer/DefaultAnonymizer.cs
A src/Anonymizer/Extensions/ServiceCollectionExtensions.cs
A src/Anonymizer/Filters/HeaderFilter.cs
A src/Anonymizer/Filters/HeaderFilterOptions.cs
A src/Anonymizer/Filters/IFilter.cs
A src/Anonymizer/IAnonymizer.cs
A src/Anonymizer/Movers/AbstractMover.cs
A src/Anonymizer/Movers/Basic/BsPacketMover.cs
A src/Anonymizer/Movers/Basic/CInfoPacketMover.cs
A src/Anonymizer/Movers/Basic/CListPacketMover.cs
A src/Anonymizer/Movers/Basic/CModePacketMover.cs
A src/Anonymizer/Movers/Basic/CondPacketMover.cs
A src/Anonymizer/Movers/Basic/DropPacketMover.cs
A src/Anonymizer/Movers/Basic/GetPacketMover.cs
A src/Anonymizer/Movers/Basic/GidxPacketMover.cs
A src/Anonymizer/Movers/Basic/InPacketMover.cs
A src/Anonymizer/Movers/Basic/MvPacketMover.cs
A src/Anonymizer/Movers/Basic/NRunPacketMover.cs
A src/Anonymizer/Movers/Basic/OutPacketMover.cs
A src/Anonymizer/Movers/Basic/PairyPacketMover.cs
A src/Anonymizer/Movers/Basic/PinitPacketMover.cs
A src/Anonymizer/Movers/Basic/PstPacketMover.cs
A src/Anonymizer/Movers/Basic/RaidPacketMover.cs
A src/Anonymizer/Movers/Basic/RbossPacketMover.cs
A src/Anonymizer/Movers/Basic/RdlstPacketMover.cs
A src/Anonymizer/Movers/Basic/SayPacketMover.cs
A src/Anonymizer/Movers/Basic/StPacketMover.cs
A src/Anonymizer/Movers/Basic/SuPacketMover.cs
A src/Anonymizer/Movers/Basic/TwkPacketMover.cs
A src/Anonymizer/Movers/IMover.cs
A src/Anonymizer/Movers/RegisteredMovers.cs
A src/Anonymizer/PacketInfo.cs
A src/Anonymizer/PacketProcessor.cs
A src/Anonymizer/ProcessedPacket.cs
A src/Anonymizer/Sinks/FileSink.cs
A src/Anonymizer/Sinks/FileSinkOptions.cs
A src/Anonymizer/Sinks/IPacketDestination.cs
A src/Anonymizer/Sinks/IPacketSource.cs
A src/Anonymizer/DefaultAnonymizer.cs => src/Anonymizer/DefaultAnonymizer.cs +105 -0
@@ 0,0 1,105 @@
//
//  DefaultAnonymizer.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Anonymizer;

/// <inheritdoc />
public class DefaultAnonymizer : IAnonymizer
{
    private readonly Random _random;
    private readonly HashSet<long> _generated;
    private readonly Dictionary<long, long> _ids;
    private readonly Dictionary<string, string> _names;

    /// <summary>
    /// Initializes a new instance of the <see cref="DefaultAnonymizer"/> class.
    /// </summary>
    public DefaultAnonymizer()
    {
        _random = new Random();
        _generated = new HashSet<long>();
        _ids = new Dictionary<long, long>();
        _names = new Dictionary<string, string>();
    }

    /// <inheritdoc />
    public string AnonymizeName(string name)
    {
        if (!_names.ContainsKey(name))
        {
            var generatedName = string.Empty;
            for (var i = 0; i < 10; i++)
            {
                generatedName += (char)_random.Next('A', 'Z');
            }

            _names[name] = generatedName;
        }

        return _names[name];
    }

    /// <inheritdoc />
    public long AnonymizeId(long id)
    {
        if (!_ids.ContainsKey(id))
        {
            var generated = _random.Next();
            if (_generated.Contains(generated))
            {
                return AnonymizeId(id);
            }

            _ids[id] = generated;
            _generated.Add(generated);

        }

        return _ids[id];
    }

    /// <inheritdoc />
    public int AnonymizeId(int id)
    {
        if (!_ids.ContainsKey(id))
        {
            var generated = _random.Next() & 0xFFFFFFFF;
            if (_generated.Contains(generated))
            {
                return AnonymizeId(id);
            }

            _ids[id] = generated;
            _generated.Add(generated);

        }

        return (int)_ids[id];
    }

    /// <inheritdoc />
    public short AnonymizeId(short id)
    {
        if (!_ids.ContainsKey(id))
        {
            var generated = _random.Next() & 0xFFFF;
            if (_generated.Contains(generated))
            {
                return AnonymizeId(id);
            }

            _ids[id] = generated;
            _generated.Add(generated);

        }

        return (short)_ids[id];
    }

    /// <inheritdoc />
    public (long Id, string Name) Anonymize(long id, string name)
        => (AnonymizeId(id), AnonymizeName(name));
}
\ No newline at end of file

A src/Anonymizer/Extensions/ServiceCollectionExtensions.cs => src/Anonymizer/Extensions/ServiceCollectionExtensions.cs +100 -0
@@ 0,0 1,100 @@
//
//  ServiceCollectionExtensions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Anonymizer.Filters;
using Anonymizer.Movers;
using Anonymizer.Movers.Basic;
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Packets;
using NosSmooth.Packets.Client;
using NosSmooth.Packets.Client.Inventory;
using NosSmooth.Packets.Server.Battle;
using NosSmooth.Packets.Server.Character;
using NosSmooth.Packets.Server.Chat;
using NosSmooth.Packets.Server.Entities;
using NosSmooth.Packets.Server.Families;
using NosSmooth.Packets.Server.Groups;
using NosSmooth.Packets.Server.Inventory;
using NosSmooth.Packets.Server.Login;
using NosSmooth.Packets.Server.Maps;
using NosSmooth.Packets.Server.Raids;
using NosSmooth.Packets.Server.UI;

namespace Anonymizer.Extensions;

/// <summary>
/// An extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds anonymizer's <see cref="PacketProcessor"/>, <see cref="IAnonymizer"/>'s default implementation,
    /// <see cref="HeaderFilter"/> and basic movers (moving entity ids and names).
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddAnonymizer(this IServiceCollection serviceCollection)
        => serviceCollection
            .AddSingleton<PacketProcessor>()
            .AddSingleton<IAnonymizer, DefaultAnonymizer>()
            .AddTransient<IFilter, HeaderFilter>()
            .AddBasicMovers();

    /// <summary>
    /// Adds movers that move entity id and name.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddBasicMovers(this IServiceCollection serviceCollection)
        => serviceCollection
            .AddMover<BsPacketMover, BsPacket>()
            .AddMover<CInfoPacketMover, CInfoPacket>()
            .AddMover<CModePacketMover, CModePacket>()
            .AddMover<CondPacketMover, CondPacket>()
            .AddMover<DropPacketMover, DropPacket>()
            .AddMover<GetPacketMover, GetPacket>()
            .AddMover<GidxPacketMover, GidxPacket>()
            .AddMover<InPacketMover, InPacket>()
            .AddMover<MvPacketMover, MovePacket>()
            .AddMover<NRunPacketMover, NRunPacket>()
            .AddMover<OutPacketMover, OutPacket>()
            .AddMover<PairyPacketMover, PairyPacket>()
            .AddMover<PinitPacketMover, PinitPacket>()
            .AddMover<PstPacketMover, PstPacket>()
            .AddMover<RaidPacketMover, RaidPacket>()
            .AddMover<RbossPacketMover, RbossPacket>()
            .AddMover<RdlstPacketMover, RdlstPacket>()
            .AddMover<SayPacketMover, SayPacket>()
            .AddMover<StPacketMover, StPacket>()
            .AddMover<SuPacketMover, SuPacket>()
            .AddMover<TwkPacketMover, TwkPacket>()
            .AddMover<CListPacketMover, CListPacket>();

    /// <summary>
    /// Adds movers that move players morphs, classes, sex, stats, weapons.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddAdvancedMovers(this IServiceCollection serviceCollection)
    {
        return serviceCollection;
    }

    /// <summary>
    /// Add mover for the given type.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TMover">The mover type.</typeparam>
    /// <typeparam name="TPacket">The packet type.</typeparam>
    /// <returns>The same service collection.</returns>
    public static IServiceCollection AddMover<TMover, TPacket>(this IServiceCollection serviceCollection)
        where TMover : class, IMover<TPacket>
        where TPacket : class, IPacket
        => serviceCollection
            .Configure<RegisteredMovers>(rm => rm.AddMover<TPacket>())
            .AddTransient<IMover, TMover>()
            .AddTransient<IMover<TPacket>, TMover>();
}
\ No newline at end of file

A src/Anonymizer/Filters/HeaderFilter.cs => src/Anonymizer/Filters/HeaderFilter.cs +37 -0
@@ 0,0 1,37 @@
//
//  HeaderFilter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.Options;

namespace Anonymizer.Filters;

/// <inheritdoc />
public class HeaderFilter : IFilter
{
    private readonly HeaderFilterOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="HeaderFilter"/> class.
    /// </summary>
    /// <param name="options">The header filter options.</param>
    public HeaderFilter(IOptions<HeaderFilterOptions> options)
    {
        _options = options.Value;
    }

    /// <inheritdoc />
    public bool Filter(PacketInfo packetInfo)
    {
        var splitted = packetInfo.Packet.Split(' ');
        if (splitted.Length < 1)
        {
            return true;
        }

        var header = splitted[0];
        return !_options.RemoveHeaders.Contains(header);
    }
}
\ No newline at end of file

A src/Anonymizer/Filters/HeaderFilterOptions.cs => src/Anonymizer/Filters/HeaderFilterOptions.cs +12 -0
@@ 0,0 1,12 @@
//
//  HeaderFilterOptions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Anonymizer.Filters;

public record HeaderFilterOptions
(
    IReadOnlyList<string> RemoveHeaders
);
\ No newline at end of file

A src/Anonymizer/Filters/IFilter.cs => src/Anonymizer/Filters/IFilter.cs +20 -0
@@ 0,0 1,20 @@
//
//  IFilter.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Anonymizer.Filters;

/// <summary>
/// Filters packet strings.
/// </summary>
public interface IFilter
{
    /// <summary>
    /// Check the given string, return whether to keep it.
    /// </summary>
    /// <param name="packetInfo">The packet info to check.</param>
    /// <returns>Whether to keep the packet.</returns>
    public bool Filter(PacketInfo packetInfo);
}
\ No newline at end of file

A src/Anonymizer/IAnonymizer.cs => src/Anonymizer/IAnonymizer.cs +49 -0
@@ 0,0 1,49 @@
//
//  IAnonymizer.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Anonymizer;

/// <summary>
/// An anonymizer used in movers, to anonymize.
/// </summary>
public interface IAnonymizer
{
    /// <summary>
    /// Anonymize the given name.
    /// </summary>
    /// <param name="name">The name to anonymize.</param>
    /// <returns>An anonymized name.</returns>
    public string AnonymizeName(string name);

    /// <summary>
    /// Anonymize the given id.
    /// </summary>
    /// <param name="id">The id to anonymize.</param>
    /// <returns>An anonymized id.</returns>
    public long AnonymizeId(long id);

    /// <summary>
    /// Anonymize the given id.
    /// </summary>
    /// <param name="id">The id to anonymize.</param>
    /// <returns>An anonymized id.</returns>
    public int AnonymizeId(int id);

    /// <summary>
    /// Anonymize the given id.
    /// </summary>
    /// <param name="id">The id to anonymize.</param>
    /// <returns>An anonymized id.</returns>
    public short AnonymizeId(short id);

    /// <summary>
    /// Anonymize id and a name.
    /// </summary>
    /// <param name="id">The id to anonymize.</param>
    /// <param name="name">The name to anonymize.</param>
    /// <returns>An anonymized tuple, name and id.</returns>
    public (long Id, string Name) Anonymize(long id, string name);
}
\ No newline at end of file

A src/Anonymizer/Movers/AbstractMover.cs => src/Anonymizer/Movers/AbstractMover.cs +25 -0
@@ 0,0 1,25 @@
//
//  AbstractMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets;

namespace Anonymizer.Movers;

/// <inheritdoc />
public abstract class AbstractMover<TPacket> : IMover<TPacket>
    where TPacket : IPacket
{
    /// <inheritdoc />
    public abstract TPacket Move(IAnonymizer anonymizer, TPacket packet);

    /// <inheritdoc />
    public bool ShouldHandle(IPacket packet)
        => packet is TPacket;

    /// <inheritdoc />
    public IPacket Move(IAnonymizer anonymizer, IPacket packet)
        => Move(anonymizer, (TPacket)packet);
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/BsPacketMover.cs => src/Anonymizer/Movers/Basic/BsPacketMover.cs +21 -0
@@ 0,0 1,21 @@
//
//  BsPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using NosSmooth.Packets.Server.Battle;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class BsPacketMover : AbstractMover<BsPacket>
{
    /// <inheritdoc />
    public override BsPacket Move(IAnonymizer anonymizer, BsPacket packet)
        => packet with
        {
            CasterEntityId = anonymizer.AnonymizeId(packet.CasterEntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/CInfoPacketMover.cs => src/Anonymizer/Movers/Basic/CInfoPacketMover.cs +23 -0
@@ 0,0 1,23 @@
//
//  CInfoPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Character;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class CInfoPacketMover : AbstractMover<CInfoPacket>
{
    /// <inheritdoc />
    public override CInfoPacket Move(IAnonymizer anonymizer, CInfoPacket packet)
        => packet with
        {
            CharacterId = anonymizer.AnonymizeId(packet.CharacterId),
            FamilyId = packet.FamilyId is null ? null : anonymizer.AnonymizeId(long.Parse(packet.FamilyId)).ToString(),
            FamilyName = packet.FamilyName is null ? null : anonymizer.AnonymizeName(packet.FamilyName),
            GroupId = packet.GroupId is null ? null : anonymizer.AnonymizeId(packet.GroupId.Value)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/CListPacketMover.cs => src/Anonymizer/Movers/Basic/CListPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  CListPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Login;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class CListPacketMover : AbstractMover<CListPacket>
{
    /// <inheritdoc />
    public override CListPacket Move(IAnonymizer anonymizer, CListPacket packet)
        => packet with
        {
            Name = anonymizer.AnonymizeName(packet.Name)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/CModePacketMover.cs => src/Anonymizer/Movers/Basic/CModePacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  CModePacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Character;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class CModePacketMover : AbstractMover<CModePacket>
{
    /// <inheritdoc />
    public override CModePacket Move(IAnonymizer anonymizer, CModePacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/CondPacketMover.cs => src/Anonymizer/Movers/Basic/CondPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  CondPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Entities;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class CondPacketMover : AbstractMover<CondPacket>
{
    /// <inheritdoc />
    public override CondPacket Move(IAnonymizer anonymizer, CondPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/DropPacketMover.cs => src/Anonymizer/Movers/Basic/DropPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  DropPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Maps;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class DropPacketMover : AbstractMover<DropPacket>
{
    /// <inheritdoc />
    public override DropPacket Move(IAnonymizer anonymizer, DropPacket packet)
        => packet with
        {
            DropId = anonymizer.AnonymizeId(packet.DropId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/GetPacketMover.cs => src/Anonymizer/Movers/Basic/GetPacketMover.cs +21 -0
@@ 0,0 1,21 @@
//
//  GetPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Client.Inventory;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class GetPacketMover : AbstractMover<GetPacket>
{
    /// <inheritdoc />
    public override GetPacket Move(IAnonymizer anonymizer, GetPacket packet)
        => packet with
        {
            GroundItemId = anonymizer.AnonymizeId(packet.GroundItemId),
            PickerEntityId = anonymizer.AnonymizeId(packet.PickerEntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/GidxPacketMover.cs => src/Anonymizer/Movers/Basic/GidxPacketMover.cs +29 -0
@@ 0,0 1,29 @@
//
//  GidxPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Families;
using NosSmooth.PacketSerializer.Abstractions.Common;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class GidxPacketMover : AbstractMover<GidxPacket>
{
    /// <inheritdoc />
    public override GidxPacket Move(IAnonymizer anonymizer, GidxPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId),
            FamilyName = packet.FamilyName is null ? null : (NameString)anonymizer.AnonymizeName(packet.FamilyName.Name),
            FamilySubPacket = packet.FamilySubPacket with
            {
                Value = packet.FamilySubPacket.Value is null ? null : packet.FamilySubPacket.Value with
                {
                    FamilyId = anonymizer.AnonymizeName(packet.FamilySubPacket.Value.FamilyId)
                }
            }
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/InPacketMover.cs => src/Anonymizer/Movers/Basic/InPacketMover.cs +45 -0
@@ 0,0 1,45 @@
//
//  InPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Maps;
using NosSmooth.PacketSerializer.Abstractions.Common;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class InPacketMover : AbstractMover<InPacket>
{
    /// <inheritdoc />
    public override InPacket Move(IAnonymizer anonymizer, InPacket packet)
    {
        return packet with
        {
            EntityId = packet.EntityId,
            Name = packet.Name is null ? null : (NameString)anonymizer.AnonymizeName(packet.Name),
            PlayerSubPacket = packet.PlayerSubPacket is null
                ? null
                : packet.PlayerSubPacket with
                {
                    GroupId = packet.PlayerSubPacket.GroupId is null
                        ? null
                        : anonymizer.AnonymizeId(packet.PlayerSubPacket.GroupId.Value),
                    FamilyName = packet.PlayerSubPacket.FamilyName is null
                        ? null
                        : anonymizer.AnonymizeName(packet.PlayerSubPacket.FamilyName),
                    FamilySubPacket = packet.PlayerSubPacket.FamilySubPacket with
                    {
                        Value = packet.PlayerSubPacket.FamilySubPacket.Value is null
                            ? null
                            : packet.PlayerSubPacket.FamilySubPacket.Value with
                            {
                                FamilyId = anonymizer.AnonymizeName
                                    (packet.PlayerSubPacket.FamilySubPacket.Value.FamilyId)
                            }
                    }
                }
        };
    }
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/MvPacketMover.cs => src/Anonymizer/Movers/Basic/MvPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  MvPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Entities;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class MvPacketMover : AbstractMover<MovePacket>
{
    /// <inheritdoc />
    public override MovePacket Move(IAnonymizer anonymizer, MovePacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/NRunPacketMover.cs => src/Anonymizer/Movers/Basic/NRunPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  NRunPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Client;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class NRunPacketMover : AbstractMover<NRunPacket>
{
    /// <inheritdoc />
    public override NRunPacket Move(IAnonymizer anonymizer, NRunPacket packet)
        => packet with
        {
            EntityId = packet.EntityId
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/OutPacketMover.cs => src/Anonymizer/Movers/Basic/OutPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  OutPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Maps;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class OutPacketMover : AbstractMover<OutPacket>
{
    /// <inheritdoc />
    public override OutPacket Move(IAnonymizer anonymizer, OutPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/PairyPacketMover.cs => src/Anonymizer/Movers/Basic/PairyPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  PairyPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Inventory;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class PairyPacketMover : AbstractMover<PairyPacket>
{
    /// <inheritdoc />
    public override PairyPacket Move(IAnonymizer anonymizer, PairyPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/PinitPacketMover.cs => src/Anonymizer/Movers/Basic/PinitPacketMover.cs +48 -0
@@ 0,0 1,48 @@
//
//  PinitPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Groups;
using NosSmooth.PacketSerializer.Abstractions.Common;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class PinitPacketMover : AbstractMover<PinitPacket>
{
    /// <inheritdoc />
    public override PinitPacket Move(IAnonymizer anonymizer, PinitPacket packet)
    {
        return packet with
        {
            PinitSubPackets = packet.PinitSubPackets?.Select
            (
                x => x with
                {
                    EntityId = anonymizer.AnonymizeId(x.EntityId),
                    MateSubPacket = x.MateSubPacket is null
                        ? null
                        : x.MateSubPacket with
                        {
                            Name = x.MateSubPacket.Name is null
                                ? null
                                : (NameString)anonymizer.AnonymizeName(x.MateSubPacket.Name)
                        },
                    PlayerSubPacket = x.PlayerSubPacket is null
                        ? null
                        : x.PlayerSubPacket with
                        {
                            GroupId = x.PlayerSubPacket.GroupId is null
                                ? null
                                : anonymizer.AnonymizeId(x.PlayerSubPacket.GroupId.Value),
                            Name = x.PlayerSubPacket.Name is null
                                ? null
                                : (NameString)anonymizer.AnonymizeName(x.PlayerSubPacket.Name)
                        }
                }
            ).ToArray()
        };
    }
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/PstPacketMover.cs => src/Anonymizer/Movers/Basic/PstPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  PstPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Groups;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class PstPacketMover : AbstractMover<PstPacket>
{
    /// <inheritdoc />
    public override PstPacket Move(IAnonymizer anonymizer, PstPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/RaidPacketMover.cs => src/Anonymizer/Movers/Basic/RaidPacketMover.cs +28 -0
@@ 0,0 1,28 @@
//
//  RaidPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Raids;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class RaidPacketMover : AbstractMover<RaidPacket>
{
    /// <inheritdoc />
    public override RaidPacket Move(IAnonymizer anonymizer, RaidPacket packet)
        => packet with
        {
            LeaderId = packet.LeaderId is null ? null : anonymizer.AnonymizeId(packet.LeaderId.Value),
            ListMembersPlayerIds = packet.ListMembersPlayerIds?.Select(anonymizer.AnonymizeId).ToArray(),
            PlayerHealths = packet.PlayerHealths?.Select
            (
                x => x with
                {
                    PlayerId = anonymizer.AnonymizeId(x.PlayerId)
                }
            ).ToArray()
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/RbossPacketMover.cs => src/Anonymizer/Movers/Basic/RbossPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  RbossPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Raids;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class RbossPacketMover : AbstractMover<RbossPacket>
{
    /// <inheritdoc />
    public override RbossPacket Move(IAnonymizer anonymizer, RbossPacket packet)
        => packet with
        {
            EntityId = packet.EntityId is null ? null : anonymizer.AnonymizeId(packet.EntityId.Value)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/RdlstPacketMover.cs => src/Anonymizer/Movers/Basic/RdlstPacketMover.cs +28 -0
@@ 0,0 1,28 @@
//
//  RdlstPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Raids;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class RdlstPacketMover : AbstractMover<RdlstPacket>
{
    /// <inheritdoc />
    public override RdlstPacket Move(IAnonymizer anonymizer, RdlstPacket packet)
        => packet with
        {
            Players = packet.Players.Select
                (
                    p => p with
                    {
                        Id = anonymizer.AnonymizeId(p.Id),
                        Name = anonymizer.AnonymizeName(p.Name)
                    }
                )
                .ToArray()
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/SayPacketMover.cs => src/Anonymizer/Movers/Basic/SayPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  SayPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Chat;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class SayPacketMover : AbstractMover<SayPacket>
{
    /// <inheritdoc />
    public override SayPacket Move(IAnonymizer anonymizer, SayPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/StPacketMover.cs => src/Anonymizer/Movers/Basic/StPacketMover.cs +20 -0
@@ 0,0 1,20 @@
//
//  StPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Entities;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class StPacketMover : AbstractMover<StPacket>
{
    /// <inheritdoc />
    public override StPacket Move(IAnonymizer anonymizer, StPacket packet)
        => packet with
        {
            EntityId = anonymizer.AnonymizeId(packet.EntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/SuPacketMover.cs => src/Anonymizer/Movers/Basic/SuPacketMover.cs +21 -0
@@ 0,0 1,21 @@
//
//  SuPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.Battle;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class SuPacketMover : AbstractMover<SuPacket>
{
    /// <inheritdoc />
    public override SuPacket Move(IAnonymizer anonymizer, SuPacket packet)
        => packet with
        {
            CasterEntityId = anonymizer.AnonymizeId(packet.CasterEntityId),
            TargetEntityId = anonymizer.AnonymizeId(packet.TargetEntityId)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/Basic/TwkPacketMover.cs => src/Anonymizer/Movers/Basic/TwkPacketMover.cs +23 -0
@@ 0,0 1,23 @@
//
//  TwkPacketMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets.Server.UI;

namespace Anonymizer.Movers.Basic;

/// <inheritdoc />
public class TwkPacketMover : AbstractMover<TwkPacket>
{
    /// <inheritdoc />
    public override TwkPacket Move(IAnonymizer anonymizer, TwkPacket packet)
        => packet with
        {
            AccountName = anonymizer.AnonymizeName(packet.AccountName),
            CharacterName = anonymizer.AnonymizeName(packet.CharacterName),
            EntityId = anonymizer.AnonymizeId(packet.EntityId),
            Salt = anonymizer.AnonymizeName(packet.Salt)
        };
}
\ No newline at end of file

A src/Anonymizer/Movers/IMover.cs => src/Anonymizer/Movers/IMover.cs +46 -0
@@ 0,0 1,46 @@
//
//  IMover.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets;

namespace Anonymizer.Movers;

/// <summary>
/// Moves a packet, anonymizes it.
/// </summary>
public interface IMover
{
    /// <summary>
    /// Check whether to handle given packet.
    /// </summary>
    /// <param name="packet">The packet to check.</param>
    /// <returns>Whether to handle that packet.</returns>
    public bool ShouldHandle(IPacket packet);

    /// <summary>
    /// Move the packet, anonymize it.
    /// </summary>
    /// <param name="anonymizer">The anonymizer to use.</param>
    /// <param name="packet">The packet.</param>
    /// <returns>Moved packet.</returns>
    public IPacket Move(IAnonymizer anonymizer, IPacket packet);
}

/// <summary>
/// Moves a packet, anonymizes it.
/// </summary>
/// <typeparam name="TPacket">The type of the packet.</typeparam>
public interface IMover<TPacket> : IMover
    where TPacket : IPacket
{
    /// <summary>
    /// Move the packet, anonymize it.
    /// </summary>
    /// <param name="anonymizer">The anonymizer.</param>
    /// <param name="packet">The packet to anonymize.</param>
    /// <returns>The new packet.</returns>
    public TPacket Move(IAnonymizer anonymizer, TPacket packet);
}
\ No newline at end of file

A src/Anonymizer/Movers/RegisteredMovers.cs => src/Anonymizer/Movers/RegisteredMovers.cs +56 -0
@@ 0,0 1,56 @@
//
//  RegisteredMovers.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Reflection;
using Microsoft.Extensions.Options;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer.Abstractions.Attributes;

namespace Anonymizer.Movers;

/// <summary>
/// A class containing all of the registered movers,
/// initialized as <see cref="IOptions{TOptions}"/>
/// by the service extension methods. Used inside of
/// <see cref="PacketProcessor"/>.
/// </summary>
public class RegisteredMovers
{
    private readonly HashSet<string> _packetHeaders;

    /// <summary>
    /// Initializes a new instance of the <see cref="RegisteredMovers"/> class.
    /// </summary>
    public RegisteredMovers()
    {
        _packetHeaders = new HashSet<string>();
    }

    /// <summary>
    /// Add the given mover packet type.
    /// </summary>
    /// <typeparam name="TPacket">The packet to add.</typeparam>
    public void AddMover<TPacket>()
        where TPacket : IPacket
    {
        var header = typeof(TPacket).GetCustomAttribute<PacketHeaderAttribute>();

        if (header?.Identifier is not null)
        {
            _packetHeaders.Add(header.Identifier);
        }
    }

    /// <summary>
    /// Checks whether the given packet header has a mover registered.
    /// </summary>
    /// <param name="packetHeader">The packet header.</param>
    /// <returns>Whether to try to move the packet.</returns>
    public bool ShouldMove(string packetHeader)
    {
        return _packetHeaders.Contains(packetHeader);
    }
}
\ No newline at end of file

A src/Anonymizer/PacketInfo.cs => src/Anonymizer/PacketInfo.cs +16 -0
@@ 0,0 1,16 @@
//
//  PacketInfo.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.PacketSerializer.Abstractions.Attributes;

namespace Anonymizer;

/// <summary>
/// Information about a packet.
/// </summary>
/// <param name="Source"></param>
/// <param name="Packet"></param>
public record PacketInfo(PacketSource Source, string Packet);
\ No newline at end of file

A src/Anonymizer/PacketProcessor.cs => src/Anonymizer/PacketProcessor.cs +124 -0
@@ 0,0 1,124 @@
//
//  PacketProcessor.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using Anonymizer.Filters;
using Anonymizer.Movers;
using Anonymizer.Sinks;
using Microsoft.Extensions.Options;
using NosSmooth.Packets;
using NosSmooth.PacketSerializer;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using Remora.Results;

namespace Anonymizer;

/// <summary>
/// Processes packets, anonymizes or filters them.
/// </summary>
public class PacketProcessor
{
    private readonly IPacketSerializer _packetSerializer;
    private readonly IAnonymizer _anonymizer;
    private readonly RegisteredMovers _registeredMovers;
    private readonly IReadOnlyList<IFilter> _filters;
    private readonly IReadOnlyList<IMover> _movers;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketProcessor"/> class.
    /// </summary>
    /// <param name="packetSerializer">The packet serializer.</param>
    /// <param name="anonymizer">The anonymizer.</param>
    /// <param name="filters">The filters.</param>
    /// <param name="movers">The movers.</param>
    /// <param name="registeredMovers">The registered movers.</param>
    public PacketProcessor
    (
        IPacketSerializer packetSerializer,
        IAnonymizer anonymizer,
        IEnumerable<IFilter> filters,
        IEnumerable<IMover> movers,
        IOptions<RegisteredMovers> registeredMovers
    )
    {
        _packetSerializer = packetSerializer;
        _anonymizer = anonymizer;
        _registeredMovers = registeredMovers.Value;
        _filters = filters.ToList();
        _movers = movers.ToList();
    }

    /// <summary>
    /// Process one packet, anonymize it.
    /// </summary>
    /// <param name="packetInfo">The packet to anonymize.</param>
    /// <returns>The processed packet.</returns>
    public Result<ProcessedPacket> ProcessPacket(PacketInfo packetInfo)
    {
        foreach (var filter in _filters)
        {
            if (!filter.Filter(packetInfo))
            {
                return new ProcessedPacket(packetInfo.Packet, packetInfo.Packet, false);
            }
        }

        var header = packetInfo.Packet.Split(' ')[0];
        if (!_registeredMovers.ShouldMove(header))
        {
            return new ProcessedPacket(packetInfo.Packet, packetInfo.Packet, true);
        }

        var packetResult = _packetSerializer.Deserialize(packetInfo.Packet, packetInfo.Source);
        if (!packetResult.IsDefined(out var packet))
        {
            return Result<ProcessedPacket>.FromError(packetResult);
        }

        foreach (var mover in _movers)
        {
            if (mover.ShouldHandle(packet))
            {
                var movedPacket = mover.Move(_anonymizer, packet);
                var serializedResult = _packetSerializer.Serialize(movedPacket);
                if (!serializedResult.IsDefined(out var serialized))
                {
                    return Result<ProcessedPacket>.FromError(serializedResult);
                }

                return new ProcessedPacket(packetInfo.Packet, serialized, true);
            }
        }

        return new ProcessedPacket(packetInfo.Packet, packetInfo.Packet, true);
    }

    /// <summary>
    /// Process the whole source and put the processed packets into the given destination.
    /// </summary>
    /// <param name="source">The source to get packets from.</param>
    /// <param name="destination">The destination to put processed packets into.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
    public async Task<Result> ProcessSourceDestination(IPacketSource source, IPacketDestination destination, CancellationToken ct = default)
    {
        while (await source.TryGetNextPacketAsync(out var packet, ct))
        {
            var processedPacketResult = ProcessPacket(packet!);
            if (!processedPacketResult.IsDefined(out var processedPacket))
            {
                return Result.FromError(processedPacketResult);
            }

            if (processedPacket.Keep)
            {
                await destination.WritePacketAsync(processedPacket.NewPacketString);
            }
        }

        return Result.FromSuccess();
    }
}
\ No newline at end of file

A src/Anonymizer/ProcessedPacket.cs => src/Anonymizer/ProcessedPacket.cs +11 -0
@@ 0,0 1,11 @@
//
//  ProcessedPacket.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Packets;

namespace Anonymizer;

public record ProcessedPacket(string OriginalPacketString, string NewPacketString, bool Keep);
\ No newline at end of file

A src/Anonymizer/Sinks/FileSink.cs => src/Anonymizer/Sinks/FileSink.cs +55 -0
@@ 0,0 1,55 @@
//
//  FileSink.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Text.RegularExpressions;
using NosSmooth.PacketSerializer.Abstractions.Attributes;

namespace Anonymizer.Sinks;

/// <summary>
/// A sink that supports reading from a file and writing to a file.
/// </summary>
public class FileSink : IDisposable, IPacketSource, IPacketDestination
{
    private readonly FileSinkOptions _options;
    private readonly FileStream _sourceStream;
    private readonly FileStream _destinationStream;

    /// <summary>
    /// Initializes a new instance of the <see cref="FileSink"/> class.
    /// </summary>
    /// <param name="sourceFile">The source file path.</param>
    /// <param name="destinationFile">The destination file path.</param>
    /// <param name="options">The options.</param>
    public FileSink(string sourceFile, string destinationFile, FileSinkOptions options)
    {
        _options = options;
        _sourceStream = File.OpenRead(sourceFile);
        _destinationStream = File.OpenWrite(destinationFile);
    }

    /// <inheritdoc />
    public long Cursor { get; private set; }

    /// <inheritdoc />
    public Task<bool> TryGetNextPacketAsync(out PacketInfo packetInfo, CancellationToken ct = default)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc />
    public Task WritePacketAsync(string packetString)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc />
    public void Dispose()
    {
        _sourceStream.Dispose();
        _destinationStream.Dispose();
    }
}
\ No newline at end of file

A src/Anonymizer/Sinks/FileSinkOptions.cs => src/Anonymizer/Sinks/FileSinkOptions.cs +11 -0
@@ 0,0 1,11 @@
//
//  FileSinkOptions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Text.RegularExpressions;

namespace Anonymizer.Sinks;

public record FileSinkOptions(Regex lineRegex, string recvString, string sendString);
\ No newline at end of file

A src/Anonymizer/Sinks/IPacketDestination.cs => src/Anonymizer/Sinks/IPacketDestination.cs +20 -0
@@ 0,0 1,20 @@
//
//  IPacketDestination.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Anonymizer.Sinks;

/// <summary>
/// An interface for sending packets to an arbitrary destination.
/// </summary>
public interface IPacketDestination
{
    /// <summary>
    /// Write the given packet string into the destination.
    /// </summary>
    /// <param name="packetString">The packet string to write.</param>
    /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
    public Task WritePacketAsync(string packetString);
}
\ No newline at end of file

A src/Anonymizer/Sinks/IPacketSource.cs => src/Anonymizer/Sinks/IPacketSource.cs +31 -0
@@ 0,0 1,31 @@
//
//  IPacketSource.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;

namespace Anonymizer.Sinks;

/// <summary>
/// An interface for receiving packets from an arbitrary source.
/// </summary>
public interface IPacketSource
{
    /// <summary>
    /// The current cursor position (current packet index).
    /// </summary>
    public long Cursor { get; }

    /// <summary>
    /// Tries to get next packet, if there is any.
    /// </summary>
    /// <remarks>
    /// Moves the cursor.
    /// </remarks>
    /// <param name="packetInfo">The information about next packet.</param>
    /// <param name="ct">The cancellation token used for cancelling the operation.</param>
    /// <returns>Whether next packet was loaded and cursor moved. If false, there are no more packets.</returns>
    public Task<bool> TryGetNextPacketAsync([NotNullWhen(true)] out PacketInfo? packetInfo, CancellationToken ct = default);
}
\ No newline at end of file

Do not follow this link