Skip to content

Commit

Permalink
Merge pull request #109 from ethanmoffat/gameserver_support
Browse files Browse the repository at this point in the history
Login path compatibility with GameServer.exe
  • Loading branch information
ethanmoffat committed Feb 25, 2022
2 parents 92ee845 + 9498d6e commit 96d472e
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 49 deletions.
50 changes: 37 additions & 13 deletions EOLib.Test/Net/FileTransfer/FileRequestServiceTest.cs
Expand Up @@ -42,15 +42,15 @@ public void SetUp()
public void RequestFile_ResponsePacketHasInvalidHeader_ThrowsEmptyPacketReceivedException()
{
Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Account, PacketAction.Accept);
Assert.ThrowsAsync<EmptyPacketReceivedException>(async () => await _fileRequestService.RequestFile(InitFileType.Item));
Assert.ThrowsAsync<EmptyPacketReceivedException>(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1));
}

[Test]
public void RequestFile_ResponsePacketInvalidExtraByte_ThrowsMalformedPacketException()
{
Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Init, PacketAction.Init, (byte)InitReply.ItemFile, 33);

Assert.ThrowsAsync<MalformedPacketException>(async () => await _fileRequestService.RequestFile(InitFileType.Item));
Assert.ThrowsAsync<MalformedPacketException>(async () => await _fileRequestService.RequestFile(InitFileType.Item, 1));
}

[Test]
Expand All @@ -64,7 +64,7 @@ public void RequestFile_SendsPacket_BasedOnSpecifiedType()
Mock.Get(_packetSendService).Setup(x => x.SendEncodedPacketAndWaitAsync(It.IsAny<IPacket>()))
.Callback((IPacket packet) => packetIsCorrect = IsCorrectFileRequestPacket(packet, localType));

_fileRequestService.RequestFile(type);
_fileRequestService.RequestFile(type, 1);

Assert.IsTrue(packetIsCorrect, "Incorrect packet for {0}", type);
}
Expand All @@ -81,10 +81,10 @@ public void RequestFile_CorrectResponse_ExecutesWithoutFault()
AggregateException aggEx = null;
switch (type)
{
case InitFileType.Item: aggEx = _fileRequestService.RequestFile(type).Exception; break;
case InitFileType.Npc: aggEx = _fileRequestService.RequestFile(type).Exception; break;
case InitFileType.Spell: aggEx = _fileRequestService.RequestFile(type).Exception; break;
case InitFileType.Class: aggEx = _fileRequestService.RequestFile(type).Exception; break;
case InitFileType.Item: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break;
case InitFileType.Npc: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break;
case InitFileType.Spell: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break;
case InitFileType.Class: aggEx = _fileRequestService.RequestFile(type, 1).Exception; break;
}

if (aggEx != null)
Expand All @@ -100,14 +100,14 @@ public void RequestFile_CorrectResponse_ExecutesWithoutFault()
public void RequestMapFile_ResponsePacketHasInvalidHeader_ThrowsEmptyPacketReceivedException()
{
Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Account, PacketAction.Accept);
Assert.ThrowsAsync<EmptyPacketReceivedException>(async () => await _fileRequestService.RequestMapFile(1));
Assert.ThrowsAsync<EmptyPacketReceivedException>(async () => await _fileRequestService.RequestMapFile(1, 1));
}

[Test]
public void RequestMapFile_ResponsePacketHasIncorrectFileType_ThrowsMalformedPacketException()
{
Mock.Get(_packetSendService).SetupReceivedPacketHasHeader(PacketFamily.Init, PacketAction.Init, (byte) InitReply.SpellFile, 33);
Assert.ThrowsAsync<MalformedPacketException>(async () => await _fileRequestService.RequestMapFile(1));
Assert.ThrowsAsync<MalformedPacketException>(async () => await _fileRequestService.RequestMapFile(1, 1));
}

[Test]
Expand All @@ -116,18 +116,42 @@ public void RequestMapFile_SendsPacket_BasedOnSpecifiedMap()
var packetIsCorrect = false;
Mock.Get(_packetSendService).Setup(x => x.SendEncodedPacketAndWaitAsync(It.IsAny<IPacket>())).Callback((IPacket packet) => packetIsCorrect = IsCorrectFileRequestPacket(packet, InitFileType.Map));

_fileRequestService.RequestMapFile(1);
_fileRequestService.RequestMapFile(1, 1);

Assert.IsTrue(packetIsCorrect, "Incorrect packet for Map");
Assert.That(packetIsCorrect, Is.True);
}

[Test]
public void RequestMapFile_HasPlayerAndMapID()
{
const short PlayerID = 1234;
const short MapID = 333;

var packetIsCorrect = false;
Mock.Get(_packetSendService)
.Setup(x => x.SendEncodedPacketAndWaitAsync(It.IsAny<IPacket>()))
.Callback((IPacket p) => packetIsCorrect = IsCorrectFileRequestPacket(p, InitFileType.Map, PlayerID, MapID));

_fileRequestService.RequestMapFile(MapID, PlayerID);

Assert.That(packetIsCorrect, Is.True);
}

#endregion

#region Helper Methods

private static bool IsCorrectFileRequestPacket(IPacket packet, InitFileType type)
private static bool IsCorrectFileRequestPacket(IPacket packet, InitFileType type, short playerId = 0, short mapId = 0)
{
return packet.Family == PacketFamily.Welcome && packet.Action == PacketAction.Agree && packet.ReadChar() == (byte) type;
var correctTyping = packet.Family == PacketFamily.Welcome && packet.Action == PacketAction.Agree && packet.ReadChar() == (byte) type;

var correctData = true;
if (mapId > 0 && playerId > 0)
{
correctData = packet.ReadShort() == playerId && packet.ReadShort() == mapId;
}

return correctTyping && correctData;
}

private static byte[] CreateFilePacket(InitFileType type)
Expand Down
5 changes: 3 additions & 2 deletions EOLib/Domain/Login/CharacterLoginReply.cs
Expand Up @@ -2,7 +2,8 @@ namespace EOLib.Domain.Login
{
public enum CharacterLoginReply : short
{
RequestGranted = 1, //response from welcome_request
RequestCompleted = 2, //response from welcome_message
RequestGranted = 1, // response from welcome_request
RequestCompleted = 2, // response from welcome_message
RequestDenied = 3, // response from welcome_message if the client sends invalid player ID
}
}
4 changes: 4 additions & 0 deletions EOLib/Domain/Login/ILoginRequestCompletedData.cs
Expand Up @@ -24,6 +24,8 @@ public interface ILoginRequestCompletedData : ITranslatedData

IReadOnlyList<IItem> MapItems { get; }

CharacterLoginReply Error { get; }

ILoginRequestCompletedData WithNews(IEnumerable<string> newsStrings);

ILoginRequestCompletedData WithWeight(byte weight);
Expand All @@ -39,5 +41,7 @@ public interface ILoginRequestCompletedData : ITranslatedData
ILoginRequestCompletedData WithNPCs(IEnumerable<INPC> npcs);

ILoginRequestCompletedData WithItems(IEnumerable<IItem> items);

ILoginRequestCompletedData WithError(CharacterLoginReply error);
}
}
13 changes: 10 additions & 3 deletions EOLib/Domain/Login/LoginActions.cs
Expand Up @@ -131,10 +131,10 @@ public async Task RequestCharacterLogin(ICharacter character)
_loginFileChecksumRepository.ECFLength = data.EcfLen;
}

public async Task CompleteCharacterLogin()
public async Task<CharacterLoginReply> CompleteCharacterLogin()
{
var packet = new PacketBuilder(PacketFamily.Welcome, PacketAction.Message)
.AddThree(0x00123456) //?
.AddThree(_playerInfoRepository.PlayerID)
.AddInt(_characterRepository.MainCharacter.ID)
.Build();

Expand All @@ -144,6 +144,11 @@ public async Task CompleteCharacterLogin()

var data = _loginRequestCompletedPacketTranslator.TranslatePacket(response);

if (data.Error == CharacterLoginReply.RequestDenied)
{
return data.Error;
}

_newsRepository.NewsHeader = data.News.First();
_newsRepository.NewsText = data.News.Except(new[] { data.News.First() }).ToList();

Expand Down Expand Up @@ -175,6 +180,8 @@ public async Task CompleteCharacterLogin()
_currentMapStateRepository.MapItems = new HashSet<IItem>(data.MapItems);

_playerInfoRepository.PlayerIsInGame = true;

return CharacterLoginReply.RequestCompleted;
}

private bool IsInvalidResponse(IPacket response)
Expand All @@ -197,6 +204,6 @@ public interface ILoginActions

Task RequestCharacterLogin(ICharacter character);

Task CompleteCharacterLogin();
Task<CharacterLoginReply> CompleteCharacterLogin();
}
}
15 changes: 13 additions & 2 deletions EOLib/Domain/Login/LoginRequestCompletedData.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using EOLib.Domain.Character;
using EOLib.Domain.Map;
Expand All @@ -24,6 +25,8 @@ public class LoginRequestCompletedData : ILoginRequestCompletedData

public IReadOnlyList<IItem> MapItems { get; private set; }

public CharacterLoginReply Error { get; private set; }

public ILoginRequestCompletedData WithNews(IEnumerable<string> newsStrings)
{
var copy = MakeCopy(this);
Expand Down Expand Up @@ -80,6 +83,13 @@ public ILoginRequestCompletedData WithItems(IEnumerable<IItem> items)
return copy;
}

public ILoginRequestCompletedData WithError(CharacterLoginReply error)
{
var copy = MakeCopy(this);
copy.Error = error;
return copy;
}

private static LoginRequestCompletedData MakeCopy(ILoginRequestCompletedData source)
{
return new LoginRequestCompletedData
Expand All @@ -91,7 +101,8 @@ private static LoginRequestCompletedData MakeCopy(ILoginRequestCompletedData sou
CharacterSpellInventory = source.CharacterSpellInventory,
MapCharacters = source.MapCharacters,
MapNPCs = source.MapNPCs,
MapItems = source.MapItems
MapItems = source.MapItems,
Error = source.Error,
};
}
}
Expand Down
6 changes: 3 additions & 3 deletions EOLib/Domain/Login/PlayerInfoRepository.cs
Expand Up @@ -8,7 +8,7 @@ public interface IPlayerInfoRepository

string PlayerPassword { get; set; }

int PlayerID { get; set; }
short PlayerID { get; set; }

bool IsFirstTimePlayer { get; set; }

Expand All @@ -21,7 +21,7 @@ public interface IPlayerInfoProvider

string PlayerPassword { get; }

int PlayerID { get; }
short PlayerID { get; }

bool IsFirstTimePlayer { get; }

Expand All @@ -35,7 +35,7 @@ public sealed class PlayerInfoRepository : IPlayerInfoRepository, IPlayerInfoPro

public string PlayerPassword { get; set; }

public int PlayerID { get; set; }
public short PlayerID { get; set; }

public bool IsFirstTimePlayer { get; set; }

Expand Down
4 changes: 1 addition & 3 deletions EOLib/Net/Communication/AsyncSocket.cs
Expand Up @@ -117,9 +117,7 @@ private bool BlockingIsConnected()
{
try
{
var pollResult = !_socket.Poll(1000, SelectMode.SelectRead);
var dataAvailable = _socket.Available != 0;
return _connected && (pollResult || dataAvailable);
return _connected && _socket.Connected;
}
catch (ObjectDisposedException)
{
Expand Down
16 changes: 10 additions & 6 deletions EOLib/Net/FileTransfer/FileRequestActions.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using AutomaticTypeMapper;
using EOLib.Domain.Login;
using EOLib.Domain.Protocol;
using EOLib.IO;
using EOLib.IO.Map;
Expand All @@ -20,14 +21,16 @@ public class FileRequestActions : IFileRequestActions
private readonly ILoginFileChecksumProvider _loginFileChecksumProvider;
private readonly IPubFileRepository _pubFileRepository;
private readonly IMapFileRepository _mapFileRepository;
private readonly IPlayerInfoProvider _playerInfoProvider;

public FileRequestActions(INumberEncoderService numberEncoderService,
IFileRequestService fileRequestService,
IPubFileSaveService pubFileSaveService,
IMapFileSaveService mapFileSaveService,
ILoginFileChecksumProvider loginFileChecksumProvider,
IPubFileRepository pubFileRepository,
IMapFileRepository mapFileRepository)
IMapFileRepository mapFileRepository,
IPlayerInfoProvider playerInfoProvider)
{
_numberEncoderService = numberEncoderService;
_fileRequestService = fileRequestService;
Expand All @@ -36,6 +39,7 @@ public class FileRequestActions : IFileRequestActions
_loginFileChecksumProvider = loginFileChecksumProvider;
_pubFileRepository = pubFileRepository;
_mapFileRepository = mapFileRepository;
_playerInfoProvider = playerInfoProvider;
}

public bool NeedsFileForLogin(InitFileType fileType, short optionalID = 0)
Expand All @@ -56,7 +60,7 @@ public bool NeedsMapForWarp(short mapID, byte[] mapRid, int fileSize)

public async Task GetMapFromServer(short mapID)
{
var mapFile = await _fileRequestService.RequestMapFile(mapID);
var mapFile = await _fileRequestService.RequestMapFile(mapID, _playerInfoProvider.PlayerID);
SaveAndCacheMapFile(mapID, mapFile);
}

Expand All @@ -68,28 +72,28 @@ public async Task GetMapForWarp(short mapID)

public async Task GetItemFileFromServer()
{
var itemFile = await _fileRequestService.RequestFile(InitFileType.Item);
var itemFile = await _fileRequestService.RequestFile(InitFileType.Item, _playerInfoProvider.PlayerID);
_pubFileSaveService.SaveFile(PubFileNameConstants.PathToEIFFile, itemFile);
_pubFileRepository.EIFFile = (EIFFile)itemFile;
}

public async Task GetNPCFileFromServer()
{
var npcFile = await _fileRequestService.RequestFile(InitFileType.Npc);
var npcFile = await _fileRequestService.RequestFile(InitFileType.Npc, _playerInfoProvider.PlayerID);
_pubFileSaveService.SaveFile(PubFileNameConstants.PathToENFFile, npcFile);
_pubFileRepository.ENFFile = (ENFFile)npcFile;
}

public async Task GetSpellFileFromServer()
{
var spellFile = await _fileRequestService.RequestFile(InitFileType.Spell);
var spellFile = await _fileRequestService.RequestFile(InitFileType.Spell, _playerInfoProvider.PlayerID);
_pubFileSaveService.SaveFile(PubFileNameConstants.PathToESFFile, spellFile);
_pubFileRepository.ESFFile = (ESFFile)spellFile;
}

public async Task GetClassFileFromServer()
{
var classFile = await _fileRequestService.RequestFile(InitFileType.Class);
var classFile = await _fileRequestService.RequestFile(InitFileType.Class, _playerInfoProvider.PlayerID);
_pubFileSaveService.SaveFile(PubFileNameConstants.PathToECFFile, classFile);
_pubFileRepository.ECFFile = (ECFFile)classFile;
}
Expand Down
16 changes: 10 additions & 6 deletions EOLib/Net/FileTransfer/FileRequestService.cs
Expand Up @@ -26,10 +26,12 @@ public class FileRequestService : IFileRequestService
_mapFileSerializer = mapFileSerializer;
}

public async Task<IMapFile> RequestMapFile(short mapID)
public async Task<IMapFile> RequestMapFile(short mapID, short playerID)
{
var request = new PacketBuilder(PacketFamily.Welcome, PacketAction.Agree)
.AddChar((byte) InitFileType.Map)
.AddChar((byte)InitFileType.Map)
.AddShort(playerID)
.AddShort(mapID)
.Build();

return await GetMapFile(request, mapID, false);
Expand All @@ -41,10 +43,12 @@ public async Task<IMapFile> RequestMapFileForWarp(short mapID)
return await GetMapFile(request, mapID, true);
}

public async Task<IPubFile> RequestFile(InitFileType fileType)
public async Task<IPubFile> RequestFile(InitFileType fileType, short playerID)
{
var request = new PacketBuilder(PacketFamily.Welcome, PacketAction.Agree)
.AddChar((byte) fileType)
.AddChar((byte)fileType)
.AddShort(playerID)
.AddChar(1) // file id (for chunking oversize pub files)
.Build();

var response = await _packetSendService.SendEncodedPacketAndWaitAsync(request);
Expand Down Expand Up @@ -101,10 +105,10 @@ private static bool PacketIsValid(IPacket packet)

public interface IFileRequestService
{
Task<IMapFile> RequestMapFile(short mapID);
Task<IMapFile> RequestMapFile(short mapID, short playerID);

Task<IMapFile> RequestMapFileForWarp(short mapID);

Task<IPubFile> RequestFile(InitFileType fileType);
Task<IPubFile> RequestFile(InitFileType fileType, short playerID);
}
}
12 changes: 11 additions & 1 deletion EOLib/Net/Translators/LoginRequestCompletedPacketTranslator.cs
Expand Up @@ -17,8 +17,18 @@ public LoginRequestCompletedPacketTranslator(ICharacterFromPacketFactory charact
public override ILoginRequestCompletedData TranslatePacket(IPacket packet)
{
var reply = (CharacterLoginReply)packet.ReadShort();
if (reply != CharacterLoginReply.RequestCompleted)

if (reply == CharacterLoginReply.RequestDenied)
{
if (packet.ReadEndString() != "NO")
throw new MalformedPacketException("Expected NO bytes in CharacterLoginReply login", packet);

return new LoginRequestCompletedData().WithError(reply);
}
else if (reply != CharacterLoginReply.RequestCompleted)
{
throw new MalformedPacketException("Unexpected welcome response in packet: " + reply, packet);
}

if (packet.ReadByte() != 255)
throw new MalformedPacketException("Missing 255 byte separator after CharacterLoginReply type", packet);
Expand Down

0 comments on commit 96d472e

Please sign in to comment.