Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate netcode to use Moffat.EndlessOnline.Sdk (eolib-dotnet) #353

Merged
merged 53 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
70d6a08
1/3 checkin migrating to eolib-dotnet
ethanmoffat May 18, 2024
87a938c
2/3 checkin migrating to eolib-dotnet
ethanmoffat May 22, 2024
45b65af
3/3 checkin migrating to eolib-dotnet
ethanmoffat May 24, 2024
057b31a
Fix crash loading pub files due to uninitialized checksums
ethanmoffat May 28, 2024
695a0af
Don't use Linq to load checksum from map files
ethanmoffat May 28, 2024
33b8431
Fix sitting
ethanmoffat May 28, 2024
91e3c9b
Fix reversed condition when detecting change in map file RID
ethanmoffat May 28, 2024
a78ca84
Implement missing chair/sit packet handlers
ethanmoffat May 28, 2024
9c593dc
Add missing SFX when showing "banned" message
ethanmoffat May 28, 2024
920c325
Allow three login attempts before returning to initial state
ethanmoffat May 28, 2024
3b2bbf3
Fix login reply banned message. Fix character delete workflow.
ethanmoffat May 28, 2024
341cad2
Welcome fixes
ethanmoffat May 28, 2024
7840e9a
Remove logging from NetworkClient
ethanmoffat May 29, 2024
6cc0eee
Update to latest Moffat.EndlessOnline.Sdk
ethanmoffat May 29, 2024
20001a3
Fix UnknownEntitiesRequester clearing out of range actors
ethanmoffat May 29, 2024
bfab277
Fix Merge function for stats and rename to Apply
ethanmoffat May 29, 2024
cd845c1
Ensure that currency is always in inventory on login
ethanmoffat May 29, 2024
2bc4975
Reset request timer in UnknownEntitiesRequester
ethanmoffat May 29, 2024
212c866
Fix transparency/clipping for all hat bitmaps
ethanmoffat May 29, 2024
46151a7
Additional fixes for hat/hair clipping
ethanmoffat May 29, 2024
6582873
Use correct packet type/base class for LOCKER_REPLY handler
ethanmoffat May 29, 2024
86e8cb0
Remove crafting ingredients with item ID of 0 in SHOP_OPEN handler
ethanmoffat May 29, 2024
7287b10
Use read-only collections in ICurrentMapStateProvider for unknown pla…
ethanmoffat May 29, 2024
1a425ac
Don't attempt to request player ID 0 from the server
ethanmoffat May 29, 2024
43260b8
Don't show 0 exp gain. Parity with official client.
ethanmoffat May 30, 2024
2690dea
Don't crash on packets with no type mapping
ethanmoffat May 30, 2024
bb80214
Return to the main menu if the receive loop crashes for any reason.
ethanmoffat May 30, 2024
9af7c94
Fix item drop offset
ethanmoffat May 30, 2024
21181f8
Fix mini map renderer draw coordinates for entities
ethanmoffat May 30, 2024
a419a60
Fix crash getting next frame in NPCs
ethanmoffat May 30, 2024
bdde184
Fix event handler leaks in character/npc renderers
ethanmoffat May 30, 2024
cd8be8c
Remove map entity TryGetValue from hot paths
ethanmoffat May 30, 2024
8ba3f1a
Reduce allocations/generated garbage in renderers
ethanmoffat May 30, 2024
3063295
Update chest handlers to use correct packet types
ethanmoffat May 30, 2024
2698ea5
Change how updated party members are detected
ethanmoffat May 31, 2024
b81b488
Play death SFX for all characters
ethanmoffat May 31, 2024
bb4e900
Don't crash when sitting if the mouse cursor is out of view
ethanmoffat May 31, 2024
8bf739f
Don't crash if warp leave effect plays on a non-existent character
ethanmoffat May 31, 2024
422564e
Remove VisibleSpikeTraps from map state repository
ethanmoffat May 31, 2024
5c797bc
Fix timed spikes
ethanmoffat May 31, 2024
a4228c1
Implement missing ITEM_AGREE packet handler
ethanmoffat May 31, 2024
f6230fd
Fix Item-related handling issues
ethanmoffat May 31, 2024
817e9d2
Fix Jukebox-related handling issues
ethanmoffat May 31, 2024
5d1ccc5
Implement LOCKER_SPEC packet handler
ethanmoffat May 31, 2024
aae5fd0
Fix trade-related handling issues
ethanmoffat May 31, 2024
031f8e9
Fix EOBot so it compiles (post-sdk migration)
ethanmoffat May 31, 2024
cca60c2
Fix tests so they pass (post-SDK migration)
ethanmoffat May 31, 2024
55ada61
Remove unused PacketDecoder project
ethanmoffat May 31, 2024
01a00d1
Implement JUKEBOX_REPLY handler
ethanmoffat Jun 1, 2024
002e4cc
Implement TRADE_ADMIN handler
ethanmoffat Jun 1, 2024
6e06255
Remove sleep time when the game is inactive
ethanmoffat Jun 1, 2024
4568927
Merge branch 'master' into use_sdk
ethanmoffat Jun 5, 2024
714caf6
Update Barber code to use sdk
ethanmoffat Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
19 changes: 12 additions & 7 deletions EOBot/BotBase.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using EOLib;
using EOLib.Config;
using EOLib.Domain.Protocol;
using EOLib.Net.Communication;
using EOLib.Net.Connection;
using EOLib.Net.PacketProcessing;
using Moffat.EndlessOnline.SDK.Packet;
using Moffat.EndlessOnline.SDK.Protocol.Net.Server;

namespace EOBot
{
Expand All @@ -17,9 +20,12 @@ public abstract class BotBase : IBot

public event Action WorkCompleted;

protected Random _random;

protected BotBase(int botIndex)
{
_index = botIndex;
_random = new Random();
}

//all bots are going to want to do the init handshake with the server
Expand Down Expand Up @@ -52,17 +58,16 @@ public virtual async Task InitializeAsync(string host, int port)
connectionActions.DisconnectFromServer();
};

var handshakeResult = await connectionActions.BeginHandshake();
var handshakeResult = await connectionActions.BeginHandshake(_random.Next(Constants.MaxChallenge));

if (handshakeResult.Response != InitReply.Success)
if (handshakeResult.ReplyCode != InitReply.Ok)
throw new InvalidOperationException(string.Format("Bot {0}: Invalid response from server or connection failed! Must receive an OK reply.", _index));

var packetProcessActions = c.Resolve<IPacketProcessActions>();
var handshakeData = (InitInitServerPacket.ReplyCodeDataOk)handshakeResult.ReplyCodeData;

packetProcessActions.SetInitialSequenceNumber(handshakeResult[InitializationDataKey.SequenceByte1],
handshakeResult[InitializationDataKey.SequenceByte2]);
packetProcessActions.SetEncodeMultiples(handshakeResult[InitializationDataKey.ReceiveMultiple],
handshakeResult[InitializationDataKey.SendMultiple]);
var packetProcessActions = c.Resolve<IPacketProcessActions>();
packetProcessActions.SetSequenceStart(InitSequenceStart.FromInitValues(handshakeData.Seq1, handshakeData.Seq2));
packetProcessActions.SetEncodeMultiples(handshakeData.ServerEncryptionMultiple, handshakeData.ClientEncryptionMultiple);

connectionActions.CompleteHandshake(handshakeResult);

Expand Down
17 changes: 9 additions & 8 deletions EOBot/BotHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
using EOLib.Domain.Character;
using EOLib.Domain.Login;
using EOLib.Domain.Map;
using EOLib.Domain.Protocol;
using EOLib.IO.Actions;
using EOLib.Net.FileTransfer;
using Moffat.EndlessOnline.SDK.Protocol.Net.Client;
using Moffat.EndlessOnline.SDK.Protocol.Net.Server;
using Optional.Collections;
using System;
using System.IO;
Expand All @@ -29,7 +30,7 @@ public async Task<AccountReply> CreateAccountAsync(string name, string password)
var accountActions = DependencyMaster.TypeRegistry[_botIndex].Resolve<IAccountActions>();
var accParams = new CreateAccountParameters(name, password, password, name, name, name + "@eobot.net");
var nameResult = await accountActions.CheckAccountNameWithServer(name);
return nameResult >= AccountReply.OK_CodeRange
return nameResult >= (AccountReply)10
? await accountActions.CreateAccount(accParams, (int)nameResult)
: nameResult;
}
Expand Down Expand Up @@ -73,19 +74,19 @@ public async Task LoginToCharacterAsync(string name)
}

var fileRequestActions = DependencyMaster.TypeRegistry[_botIndex].Resolve<IFileRequestActions>();
if (unableToLoadMap || fileRequestActions.NeedsFileForLogin(InitFileType.Map, mapStateProvider.CurrentMapID))
if (unableToLoadMap || fileRequestActions.NeedsFileForLogin(FileType.Emf, mapStateProvider.CurrentMapID))
await fileRequestActions.GetMapFromServer(mapStateProvider.CurrentMapID, sessionID);

if (fileRequestActions.NeedsFileForLogin(InitFileType.Item))
if (fileRequestActions.NeedsFileForLogin(FileType.Eif))
await fileRequestActions.GetItemFileFromServer(sessionID);

if (fileRequestActions.NeedsFileForLogin(InitFileType.Npc))
if (fileRequestActions.NeedsFileForLogin(FileType.Enf))
await fileRequestActions.GetNPCFileFromServer(sessionID);

if (fileRequestActions.NeedsFileForLogin(InitFileType.Spell))
if (fileRequestActions.NeedsFileForLogin(FileType.Esf))
await fileRequestActions.GetSpellFileFromServer(sessionID);

if (fileRequestActions.NeedsFileForLogin(InitFileType.Class))
if (fileRequestActions.NeedsFileForLogin(FileType.Ecf))
await fileRequestActions.GetClassFileFromServer(sessionID);

await loginActions.CompleteCharacterLogin(sessionID);
Expand All @@ -106,7 +107,7 @@ public async Task<CharacterReply> DeleteCharacterAsync(string name, bool force)
if (!characterSelectorRepository.CharacterForDelete.HasValue)
{
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Warning, $"Character {name} could not be deleted / does not exist");
return CharacterReply.THIS_IS_WRONG;
return (CharacterReply)255;
}

var characterActions = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICharacterManagementActions>();
Expand Down
21 changes: 12 additions & 9 deletions EOBot/Interpreter/BuiltInIdentifierConfigurator.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
using EOBot.Interpreter.States;
using EOBot.Interpreter.Variables;
using EOLib;
using EOLib.Config;
using EOLib.Domain.Account;
using EOLib.Domain.Character;
using EOLib.Domain.Chat;
using EOLib.Domain.Login;
using EOLib.Domain.Map;
using EOLib.Domain.NPC;
using EOLib.Domain.Party;
using EOLib.Domain.Protocol;
using EOLib.IO.Repositories;
using EOLib.Net.Communication;
using EOLib.Net.Connection;
using EOLib.Net.PacketProcessing;
using Moffat.EndlessOnline.SDK.Packet;
using Moffat.EndlessOnline.SDK.Protocol.Net;
using Moffat.EndlessOnline.SDK.Protocol.Net.Server;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -27,13 +29,15 @@ public class BuiltInIdentifierConfigurator
private readonly int _botIndex;
private readonly ArgumentsParser _parsedArgs;
private readonly BotHelper _botHelper;
private readonly Random _random;

public BuiltInIdentifierConfigurator(ProgramState state, int botIndex, ArgumentsParser parsedArgs)
{
_state = state;
_botIndex = botIndex;
_parsedArgs = parsedArgs;
_botHelper = new BotHelper(_botIndex);
_random = new Random();
}

public void SetupBuiltInFunctions()
Expand Down Expand Up @@ -119,17 +123,16 @@ private async Task ConnectAsync(string host, int port)
var backgroundReceiveActions = c.Resolve<IBackgroundReceiveActions>();
backgroundReceiveActions.RunBackgroundReceiveLoop();

var handshakeResult = await connectionActions.BeginHandshake();
var handshakeResult = await connectionActions.BeginHandshake(_random.Next(Constants.MaxChallenge));

if (handshakeResult.Response != InitReply.Success)
if (handshakeResult.ReplyCode != InitReply.Ok)
throw new InvalidOperationException($"Bot {_botIndex}: Invalid response from server or connection failed! Must receive an OK reply.");

var packetProcessActions = c.Resolve<IPacketProcessActions>();
var handshakeData = (InitInitServerPacket.ReplyCodeDataOk)handshakeResult.ReplyCodeData;

packetProcessActions.SetInitialSequenceNumber(handshakeResult[InitializationDataKey.SequenceByte1],
handshakeResult[InitializationDataKey.SequenceByte2]);
packetProcessActions.SetEncodeMultiples(handshakeResult[InitializationDataKey.ReceiveMultiple],
handshakeResult[InitializationDataKey.SendMultiple]);
var packetProcessActions = c.Resolve<IPacketProcessActions>();
packetProcessActions.SetSequenceStart(InitSequenceStart.FromInitValues(handshakeData.Seq1, handshakeData.Seq2));
packetProcessActions.SetEncodeMultiples(handshakeData.ServerEncryptionMultiple, handshakeData.ClientEncryptionMultiple);

connectionActions.CompleteHandshake(handshakeResult);
}
Expand Down
7 changes: 4 additions & 3 deletions EOBot/TrainerBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using EOLib.IO.Repositories;
using EOLib.Net;
using EOLib.Net.Handlers;
using Moffat.EndlessOnline.SDK.Protocol.Net.Server;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -377,9 +378,9 @@ private async Task UseHealItem(IEnumerable<InventoryItem> healItems)
private async Task ToggleSit()
{
var renderProps = _characterRepository.MainCharacter.RenderProperties;
var nextState = renderProps.SitState == SitState.Standing ? "Floor" : "Stand";
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Sit, $"{nextState,7} - Toggling from: {Enum.GetName(typeof(SitState), renderProps.SitState)}");
await TrySend(_characterActions.ToggleSit);
var nextState = renderProps.SitState == EOLib.Domain.Character.SitState.Standing ? "Floor" : "Stand";
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Sit, $"{nextState,7} - Toggling from: {Enum.GetName(typeof(EOLib.Domain.Character.SitState), renderProps.SitState)}");
await TrySend(() => _characterActions.Sit(MapCoordinate.Zero));
}

private async Task TrySend(Action action, uint attempts = 3)
Expand Down
8 changes: 4 additions & 4 deletions EOLib.Graphics.Test/NativeGraphicsManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void WhenLoadTexture_MaleHat_Transparent_SetsSpecialColorToTransparent()

Texture2D resultTexture;
var bmp = LoadGFXReturnsBitmap(GFXTypes.MaleHat, requestedResource);
FillBitmapWithColor(bmp, new Color(0x08, 0x00, 0x00, 0xff));
FillBitmapWithColor(bmp, new Color(0xff000008));
resultTexture = _nativeGraphicsManager.TextureFromResource(GFXTypes.MaleHat, requestedResource, true);

var data = new Microsoft.Xna.Framework.Color[resultTexture.Width * resultTexture.Height];
Expand All @@ -171,7 +171,7 @@ public void WhenLoadTexture_FemaleHat_Transparent_SetsSpecialColorToTransparent(

Texture2D resultTexture;
var bmp = LoadGFXReturnsBitmap(GFXTypes.FemaleHat, requestedResource);
FillBitmapWithColor(bmp, new Color(0x08, 0x00, 0x00, 0xff));
FillBitmapWithColor(bmp, new Color(0xff000008));
resultTexture = _nativeGraphicsManager.TextureFromResource(GFXTypes.FemaleHat, requestedResource, true);

var data = new Color[resultTexture.Width * resultTexture.Height];
Expand Down Expand Up @@ -221,9 +221,9 @@ private static void FillBitmapWithColor(Memory<byte> image, Color color)
{
for (int i = 54; i < image.Length; i+=4)
{
image.Span[i] = color.R;
image.Span[i] = color.B;
image.Span[i + 1] = color.G;
image.Span[i + 2] = color.B;
image.Span[i + 2] = color.R;
image.Span[i + 3] = color.A;
}
}
Expand Down
51 changes: 37 additions & 14 deletions EOLib.Graphics/NativeGraphicsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,15 @@ private Texture2D LoadTexture(GFXTypes file, int resourceVal, bool transparent,

if (transparent)
{
// for all gfx: 0x000000 is transparent
processAction = data => CrossPlatformMakeTransparent(data, Color.Black);
processAction = data => CrossPlatformMakeTransparent(data);

// for hats: R=8 G=0 B=0 is transparent
// some default gfx use R=0 G=8 B=0 as black
// 0,0,0 clips pixels below it
if (file == GFXTypes.FemaleHat || file == GFXTypes.MaleHat)
if (fullTransparent)
{
processAction = data => CrossPlatformMakeTransparent(data, new Color(0xff000008));
processAction = data => CrossPlatformMakeTransparent(data, isHat: true);
}

// for hats: some hat gfx use multiple masking colors but don't have clipping behavior
if (fullTransparent)
else if (file == GFXTypes.FemaleHat || file == GFXTypes.MaleHat)
{
processAction = data => CrossPlatformMakeTransparent(data,
new Color(0xff000000),
new Color(0xff000008));
processAction = data => CrossPlatformMakeTransparent(data, checkClip: true, isHat: true);
}
}

Expand All @@ -87,8 +79,39 @@ private Texture2D LoadTexture(GFXTypes file, int resourceVal, bool transparent,
return ret;
}

private static unsafe void CrossPlatformMakeTransparent(byte[] data, params Color[] transparentColors)
private static unsafe void CrossPlatformMakeTransparent(byte[] data, bool isHat = false, bool checkClip = false)
{
var shouldClip = false;
if (checkClip)
{
fixed (byte* ptr = data)
{
for (int i = 0; i < data.Length; i += 4)
{
uint* addr = (uint*)(ptr + i);
if (*addr == 0xff000008)
{
shouldClip = true;
break;
}
}
}
}

// for all gfx: 0,0,0 is transparent

// for some hats: 8,0,0 and 0,0,0 are both transparent

// for hats: R=8 G=0 B=0 is transparent
// some default gfx use R=0 G=8 B=0 as black
// 0,0,0 clips pixels below it if 8,0,0 is present on the frame

var transparentColors = isHat
? shouldClip
? new Color[] { new Color(0xff000008) } // check clip: make ff000008 transparent only, use black for clipping if present
: new Color[] { Color.Black, new Color(0xff000008) } // isHat: make both colors transparent
: new Color[] { Color.Black }; // default: make only black transparent

fixed (byte* ptr = data)
{
for (int i = 0; i < data.Length; i += 4)
Expand Down
4 changes: 2 additions & 2 deletions EOLib.IO.Test/Map/MapFilePropertiesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void MapFileProperties_DeserializeFromByteArray_ThrowsExceptionWhenNotEMF

private static IMapFileProperties CreateMapPropertiesWithSomeTestData(IMapFileProperties props)
{
return props.WithChecksum(new byte[] {1, 2, 3, 4})
return props.WithChecksum(new List<int> {1, 2})
.WithName("Some test name")
.WithWidth(200)
.WithHeight(100)
Expand All @@ -117,7 +117,7 @@ private static byte[] CreateExpectedBytes(IMapFileProperties props)
var ret = new List<byte>();

ret.AddRange(Encoding.ASCII.GetBytes(props.FileType));
ret.AddRange(props.Checksum);
ret.AddRange(props.Checksum.SelectMany(x => numberEncoderService.EncodeNumber(x, 2)));

var fullName = Enumerable.Repeat((byte)0xFF, 24).ToArray();
var encodedName = mapStringEncoderService.EncodeMapString(props.Name, props.Name.Length);
Expand Down
24 changes: 15 additions & 9 deletions EOLib.IO.Test/Services/Serializers/PubFileSerializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ public abstract class PubFileSerializerTest<T, U>
[Test]
public void DeserializeFromByteArray_WrongLength_Throws()
{
const int ExpectedChecksum = 1234567890;
const int ExpectedChecksum1 = 12345;
const int ExpectedChecksum2 = 6789;
const int ExpectedLength = 4;

var expectedChecksum = new List<int> { ExpectedChecksum1, ExpectedChecksum2 };

var records = new[]
{
new U().WithID(1).WithName("Rec_1"),
Expand All @@ -41,16 +44,19 @@ public void DeserializeFromByteArray_WrongLength_Throws()
new U().WithID(4).WithName("Rec_4"),
};

var pubBytesShort = MakePubFileBytes(ExpectedChecksum, ExpectedLength - 1, records);
var pubBytesShort = MakePubFileBytes(expectedChecksum, ExpectedLength - 1, records);
Assert.That(() => CreateSerializer().DeserializeFromByteArray(1, pubBytesShort, () => new T()), Throws.InstanceOf<IOException>());
}

[Test]
public void DeserializeFromByteArray_HasExpectedHeader()
{
const int ExpectedChecksum = 1234567890;
const int ExpectedChecksum1 = 12345;
const int ExpectedChecksum2 = 6789;
const int ExpectedLength = 4;

var expectedChecksum = new List<int> { ExpectedChecksum1, ExpectedChecksum2 };

var records = new[]
{
new U().WithID(1).WithName("Rec_1"),
Expand All @@ -59,17 +65,17 @@ public void DeserializeFromByteArray_HasExpectedHeader()
new U().WithID(4).WithName("Rec_4"),
};

var pubBytes = MakePubFileBytes(ExpectedChecksum, ExpectedLength, records);
var pubBytes = MakePubFileBytes(expectedChecksum, ExpectedLength, records);
var file = CreateSerializer().DeserializeFromByteArray(1, pubBytes, () => new T());

Assert.That(file.CheckSum, Is.EqualTo(ExpectedChecksum));
Assert.That(file.CheckSum, Is.EqualTo(expectedChecksum));
Assert.That(file.Length, Is.EqualTo(ExpectedLength));
}

[Test]
public void SerializeToByteArray_ReturnsExpectedBytes()
{
var expectedBytes = MakePubFileBytes(55565554,
var expectedBytes = MakePubFileBytes(new List<int> { 5556, 5554 },
9,
new U().WithID(1).WithName("TestFixture"),
new U().WithID(2).WithName("Test2"),
Expand Down Expand Up @@ -104,7 +110,7 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames()
new U().WithID(8).WithName("Test8"),
new U().WithID(9).WithName("eof")
};
var bytes = MakePubFileBytes(55565554, 9, records);
var bytes = MakePubFileBytes(new List<int> { 5556, 5554 }, 9, records);

var serializer = CreateSerializer();
var file = serializer.DeserializeFromByteArray(1, bytes, () => new T());
Expand All @@ -113,14 +119,14 @@ public void DeserializeFromByteArray_HasExpectedIDAndNames()
file.Select(x => new { x.ID, x.Name }).ToList());
}

private byte[] MakePubFileBytes(int checksum, int length, params IPubRecord[] records)
private byte[] MakePubFileBytes(List<int> checksum, int length, params IPubRecord[] records)
{
var numberEncoderService = new NumberEncoderService();
var recordSerializer = new PubRecordSerializer(numberEncoderService);

var bytes = new List<byte>();
bytes.AddRange(Encoding.ASCII.GetBytes(new T().FileType));
bytes.AddRange(numberEncoderService.EncodeNumber(checksum, 4));
bytes.AddRange(checksum.SelectMany(x => numberEncoderService.EncodeNumber(x, 2)));
bytes.AddRange(numberEncoderService.EncodeNumber(length, 2));
bytes.Add(numberEncoderService.EncodeNumber(1, 1)[0]);
foreach (var record in records)
Expand Down
Loading