basic account auth handlers, bug fixes

This commit is contained in:
raphaeIl 2024-04-25 21:32:21 -04:00
parent 9b0a4075f6
commit 14e13bc9d7
15 changed files with 2406 additions and 2061 deletions

View File

@ -1,13 +1,39 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using SCHALE.Common.FlatData;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver.Core.Servers;
namespace SCHALE.Common.Database.Models.Game namespace SCHALE.Common.Database.Models.Game
{ {
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public class Account : AccountDB public class Account
{ {
[Key] [Key]
[Column("_id")] [Column("_id")]
public new uint ServerId { get; set; } public uint ServerId { get; set; }
public AccountDB AccountDB { get; set; }
public static Account Create(uint guest_account_uid) // make sure ServerId matches GuestAccount UID
{
Account account = new()
{
ServerId = guest_account_uid,
AccountDB = new AccountDB()
{
ServerId = guest_account_uid,
State = AccountState.Normal,
Level = 0,
Exp = 0,
RepresentCharacterServerId = 1037810385, // i think this is the default
LastConnectTime = DateTime.Now,
CreateDate = DateTime.Now,
}
};
return account;
}
} }
} }

View File

@ -6,10 +6,12 @@ namespace SCHALE.Common.Database.Models
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public class GuestAccount public class GuestAccount
{ {
public uint Uid { get; set; }
public string DeviceId { get; set; }
[Key] [Key]
[Column("_id")] [Column("_id")]
public uint Uid { get; set; }
public string DeviceId { get; set; }
public string Token { get; set; } public string Token { get; set; }
} }
} }

View File

@ -19,23 +19,16 @@ namespace SCHALE.Common.Database
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
modelBuilder.Entity<GuestAccount>().Property(x => x.Uid).HasValueGenerator<GuestAccountAutoIncrementValueGenerator>(); modelBuilder.Entity<GuestAccount>().Property(x => x.Uid).HasValueGenerator<GuestAccountAutoIncrementValueGenerator>();
modelBuilder.Entity<GuestAccount>().ToCollection("guest_accounts"); modelBuilder.Entity<GuestAccount>().ToCollection("guest_accounts");
modelBuilder.Entity<Account>().Property(x => x.ServerId).HasValueGenerator<AccountAutoIncrementValueGenerator>();
modelBuilder.Entity<Account>().ToCollection("accounts"); modelBuilder.Entity<Account>().ToCollection("accounts");
modelBuilder.Entity<Counter>().ToCollection("counters"); modelBuilder.Entity<Counter>().ToCollection("counters");
} }
private class AccountAutoIncrementValueGenerator : AutoIncrementValueGenerator
{
protected override string Collection => "account";
}
private class GuestAccountAutoIncrementValueGenerator : AutoIncrementValueGenerator private class GuestAccountAutoIncrementValueGenerator : AutoIncrementValueGenerator
{ {
protected override string Collection => "guest_account"; protected override string Collection => "guest_account";
@ -68,19 +61,5 @@ namespace SCHALE.Common.Database
return counter.Seq; return counter.Seq;
} }
} }
public override void Dispose()
{
GC.SuppressFinalize(this);
SaveChanges();
base.Dispose();
}
public override ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
SaveChanges();
return base.DisposeAsync();
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
using SCHALE.Common.Database; using SCHALE.Common.Database;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using SCHALE.Common.Parcel; using SCHALE.Common.Parcel;
using SCHALE.Common.FlatData;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SCHALE.Common.NetworkProtocol namespace SCHALE.Common.NetworkProtocol
{ {
@ -137,16 +140,16 @@ namespace SCHALE.Common.NetworkProtocol
} }
//public class AccountAuth2Request : AccountAuthRequest public class AccountAuth2Request : AccountAuthRequest
//{ {
// public override Protocol Protocol public override Protocol Protocol
// { {
// get get
// { {
// return NetworkProtocol.Protocol.Account_Auth2; return NetworkProtocol.Protocol.Account_Auth2;
// } }
// } }
//} }
public class AccountAuth2Response : AccountAuthResponse public class AccountAuth2Response : AccountAuthResponse
@ -7980,8 +7983,8 @@ namespace SCHALE.Common.NetworkProtocol
return NetworkProtocol.Protocol.ProofToken_RequestQuestion; return NetworkProtocol.Protocol.ProofToken_RequestQuestion;
} }
} }
public long Hint; public long Hint { get; set; }
public string Question; public string Question { get; set; }
} }
@ -8855,7 +8858,80 @@ namespace SCHALE.Common.NetworkProtocol
public long StageUniqueId { get; set; } public long StageUniqueId { get; set; }
} }
public interface IMissionConstraint
{
bool CanComplete(DateTime serverTime);
bool CanReceiveReward(DateTime serverTime);
}
public class MissionInfo : IMissionConstraint
{
public long Id { get; set; }
public MissionCategory Category { get; set; }
public MissionResetType ResetType { get; set; }
public MissionToastDisplayConditionType ToastDisplayType { get; set; }
public string Description { get; set; }
public bool IsVisible { get; set; }
public bool IsLimited { get; set; }
public DateTime StartDate { get; set; }
public DateTime StartableEndDate { get; set; }
public DateTime EndDate { get; set; }
public long EndDday { get; set; }
public AccountState AccountState { get; set; }
public long AccountLevel { get; set; }
public List<long> PreMissionIds { get; set; }
public long NextMissionId { get; set; }
public SuddenMissionContentType[] SuddenMissionContentTypes { get; set; }
public MissionCompleteConditionType CompleteConditionType { get; set; }
public long CompleteConditionCount { get; set; }
public List<long> CompleteConditionParameters { get; set; }
public string RewardIcon { get; set; }
public List<ParcelInfo> Rewards { get; set; }
public ContentType DateAutoRefer { get; set; }
public string ToastImagePath { get; set; }
public long DisplayOrder { get; set; }
public bool HasFollowingMission { get; set; }
public string[] Shortcuts { get; set; }
public long ChallengeStageId { get; set; }
public virtual bool CanComplete(DateTime serverTime)
{
return true;
}
public virtual bool CanReceiveReward(DateTime serverTime)
{
return false;
}
}
public class MissionListResponse : ResponsePacket public class MissionListResponse : ResponsePacket
{ {
public override Protocol Protocol public override Protocol Protocol
@ -8867,7 +8943,7 @@ namespace SCHALE.Common.NetworkProtocol
} }
public List<long> MissionHistoryUniqueIds { get; set; } public List<long> MissionHistoryUniqueIds { get; set; }
public List<MissionProgressDB> ProgressDBs { get; set; } public List<MissionProgressDB> ProgressDBs { get; set; }
//public MissionInfo DailySuddenMissionInfo { get; set; } public MissionInfo DailySuddenMissionInfo { get; set; }
public List<long> ClearedOrignalMissionIds { get; set; } public List<long> ClearedOrignalMissionIds { get; set; }
} }

View File

@ -1,5 +1,6 @@
 
using SCHALE.Common.Database; using SCHALE.Common.Database;
using SCHALE.Common.FlatData;
namespace SCHALE.Common.Parcel namespace SCHALE.Common.Parcel
{ {
@ -96,16 +97,53 @@ namespace SCHALE.Common.Parcel
public ParcelChangeType ParcelChangeType { get; set; } public ParcelChangeType ParcelChangeType { get; set; }
} }
public struct BasisPoint : IEquatable<BasisPoint>, IComparable<BasisPoint>
public class ParcelInfo
{ {
public ParcelKeyPair Key { get; set; } public long RawValue { get; set; }
public long Amount { get; set; }
//public BasisPoint Multiplier { get; set; } public static readonly double DoubleEpsilon;
public long MultipliedAmount { get; set; }
//public BasisPoint Probability { get; set; } public static readonly BasisPoint Epsilon;
private static readonly long Multiplier;
public static readonly BasisPoint One;
private static readonly double OneOver10_4;
private long rawValue;
public static readonly BasisPoint Zero;
public bool Equals(BasisPoint other)
{
throw new NotImplementedException();
} }
public int CompareTo(BasisPoint other)
{
throw new NotImplementedException();
}
}
public class ParcelInfo : IEquatable<ParcelInfo>
{
public ParcelKeyPair Key { get; set; }
public long Amount { get; set; }
public BasisPoint Multiplier { get; set; }
public long MultipliedAmount { get; set; }
public BasisPoint Probability { get; set; }
public bool Equals(ParcelInfo? other)
{
return this.Key.Id.Equals(other.Key.Id);
}
}
public class ParcelKeyPair public class ParcelKeyPair
{ {

View File

@ -44,6 +44,7 @@ namespace SCHALE.GameServer.Controllers.Api
var payloadStr = Encoding.UTF8.GetString(payloadMs.ToArray()); var payloadStr = Encoding.UTF8.GetString(payloadMs.ToArray());
var jsonNode = JsonSerializer.Deserialize<JsonNode>(payloadStr); var jsonNode = JsonSerializer.Deserialize<JsonNode>(payloadStr);
var protocol = (Protocol?)jsonNode?["Protocol"]?.GetValue<int?>() ?? Protocol.None; var protocol = (Protocol?)jsonNode?["Protocol"]?.GetValue<int?>() ?? Protocol.None;
if (protocol == Protocol.None) if (protocol == Protocol.None)
{ {
logger.LogWarning("Failed to read protocol from JsonNode, {Payload:j}", payloadStr); logger.LogWarning("Failed to read protocol from JsonNode, {Payload:j}", payloadStr);
@ -60,6 +61,8 @@ namespace SCHALE.GameServer.Controllers.Api
var payload = (JsonSerializer.Deserialize(payloadStr, requestType) as RequestPacket)!; var payload = (JsonSerializer.Deserialize(payloadStr, requestType) as RequestPacket)!;
var rsp = protocolHandlerFactory.Invoke(protocol, payload); var rsp = protocolHandlerFactory.Invoke(protocol, payload);
if (rsp is null) if (rsp is null)
{ {
logger.LogDebug("{Protocol} {Payload:j}", payload.Protocol, payloadStr); logger.LogDebug("{Protocol} {Payload:j}", payload.Protocol, payloadStr);
@ -71,7 +74,7 @@ namespace SCHALE.GameServer.Controllers.Api
return Results.Json(new return Results.Json(new
{ {
packet = JsonSerializer.Serialize(rsp), packet = JsonSerializer.Serialize(rsp),
protocol = payload.Protocol.ToString() protocol = ((BasePacket)rsp).Protocol.ToString()
}); });
protocolErrorRet: protocolErrorRet:

View File

@ -0,0 +1,22 @@
using SCHALE.Common.NetworkProtocol;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public class Academy : ProtocolHandlerBase
{
public Academy(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.Academy_GetInfo)]
public ResponsePacket GetInfoHandler(AcademyGetInfoRequest req)
{
return new AcademyGetInfoResponse()
{
AcademyDB = new()
{
AccountId = 1
},
AcademyLocationDBs = [],
};
}
}
}

View File

@ -1,4 +1,8 @@
using SCHALE.Common.NetworkProtocol; using SCHALE.Common.Database;
using SCHALE.Common.FlatData;
using SCHALE.Common.NetworkProtocol;
using SCHALE.Common.Parcel;
using Serilog;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{ {
@ -6,10 +10,14 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{ {
public Account(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { } public Account(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
// most handlers empty
[ProtocolHandler(Protocol.Account_CheckYostar)] [ProtocolHandler(Protocol.Account_CheckYostar)]
public ResponsePacket CheckYostarHandler(AccountCheckYostarRequest req) public ResponsePacket CheckYostarHandler(AccountCheckYostarRequest req)
{ {
var account = Context.GuestAccounts.SingleOrDefault(x => x.Uid == uint.Parse(req.EnterTicket.Split(":", StringSplitOptions.None).First()) && x.Token == req.EnterTicket.Split(":", StringSplitOptions.None).Last()); string[] uid_token = req.EnterTicket.Split(':');
var account = Context.GuestAccounts.SingleOrDefault(x => x.Uid == uint.Parse(uid_token[0]) && x.Token == uid_token[1]);
if (account is null) if (account is null)
{ {
return new AccountCheckYostarResponse() return new AccountCheckYostarResponse()
@ -24,19 +32,160 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
ResultState = 1, ResultState = 1,
SessionKey = new() SessionKey = new()
{ {
AccountServerId = account.Uid,
MxToken = req.EnterTicket, MxToken = req.EnterTicket,
AccountServerId = account.Uid
} }
}; };
} }
[ProtocolHandler(Protocol.Account_Auth)] [ProtocolHandler(Protocol.Account_Auth)]
public ResponsePacket AuthHandler(AccountAuthRequest req) public ResponsePacket AuthHandler(AccountAuthRequest req)
{
var account = Context.Accounts.SingleOrDefault(x => x.ServerId == req.AccountId);
if (account == null)
{ {
return new ErrorPacket() return new ErrorPacket()
{ {
ErrorCode = WebAPIErrorCode.AccountAuthNotCreated ErrorCode = WebAPIErrorCode.AccountAuthNotCreated
}; };
} }
else
{
return new AccountAuthResponse()
{
CurrentVersion = req.Version,
AccountDB = account.AccountDB,
SessionKey = new()
{
AccountServerId = account.ServerId,
MxToken = req.SessionKey.MxToken,
}
};
} }
} }
[ProtocolHandler(Protocol.Account_Create)]
public ResponsePacket CreateHandler(AccountCreateRequest req)
{
var account = Common.Database.Models.Game.Account.Create((uint)req.AccountId);
Context.Accounts.Add(account);
Context.SaveChanges();
Log.Information("Account Created " + Context.Accounts.Count());
return new AccountCreateResponse()
{
SessionKey = new()
{
AccountServerId = account.ServerId,
MxToken = req.SessionKey.MxToken,
},
};
}
[ProtocolHandler(Protocol.Account_Nickname)]
public ResponsePacket NicknameHandler(AccountNicknameRequest req)
{
var account = Context.Accounts.SingleOrDefault(x => x.ServerId == req.AccountId);
account.AccountDB.Nickname = req.Nickname;
Context.SaveChanges();
return new AccountNicknameResponse()
{
AccountDB = account.AccountDB
};
}
[ProtocolHandler(Protocol.Account_LoginSync)]
public ResponsePacket LoginSyncHandler(AccountLoginSyncRequest req)
{
return new AccountLoginSyncResponse()
{
};
}
[ProtocolHandler(Protocol.Account_GetTutorial)]
public ResponsePacket GetTutorialHandler(AccountGetTutorialRequest req)
{
return new AccountGetTutorialResponse()
{
};
}
[ProtocolHandler(Protocol.Account_SetTutorial)]
public ResponsePacket SetTutorialHandler(AccountSetTutorialRequest req)
{
return new AccountSetTutorialResponse()
{
};
}
// others handlers, move to different handler group later
[ProtocolHandler(Protocol.Item_List)]
public ResponsePacket Item_ListRequestHandler(ItemListRequest req)
{
return new ItemListResponse()
{
ItemDBs = [],
ExpiryItemDBs = [],
ServerNotification = ServerNotificationFlag.HasUnreadMail,
};
}
[ProtocolHandler(Protocol.NetworkTime_Sync)]
public ResponsePacket NetworkTime_Sync(NetworkTimeSyncRequest req)
{
long received_tick = DateTimeOffset.Now.Ticks;
return new NetworkTimeSyncResponse()
{
ReceiveTick = received_tick,
EchoSendTick = DateTimeOffset.Now.Ticks,
ServerTimeTicks = received_tick,
};
}
[ProtocolHandler(Protocol.ContentSave_Get)]
public ResponsePacket ContentSave_Get(ContentSaveGetRequest req)
{
return new ContentSaveGetResponse()
{
ServerNotification = ServerNotificationFlag.HasUnreadMail,
};
}
[ProtocolHandler(Protocol.Shop_BeforehandGachaGet)]
public ResponsePacket Shop_BeforehandGachaGet(ShopBeforehandGachaGetRequest req)
{
return new ShopBeforehandGachaGetResponse()
{
SessionKey = new ()
{
MxToken = req.SessionKey.MxToken,
AccountServerId = req.SessionKey.AccountServerId,
},
};
}
[ProtocolHandler(Protocol.Toast_List)]
public ResponsePacket ToastListHandler(ToastListRequest req)
{
return new ToastListResponse()
{
};
}
}
}

View File

@ -0,0 +1,27 @@
using SCHALE.Common.NetworkProtocol;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public class Mission : ProtocolHandlerBase
{
public Mission(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.Mission_List)]
public ResponsePacket ListHandler(MissionListRequest req)
{
return new MissionListResponse()
{
};
}
[ProtocolHandler(Protocol.Mission_GuideMissionSeasonList)]
public ResponsePacket GuideMissionSeasonListHandler(GuideMissionSeasonListRequest req)
{
return new GuideMissionSeasonListResponse()
{
};
}
}
}

View File

@ -0,0 +1,37 @@
using SCHALE.Common.NetworkProtocol;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public class ProofToken : ProtocolHandlerBase
{
public ProofToken(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.ProofToken_RequestQuestion)]
public ResponsePacket RequestQuestionHandler(ProofTokenRequestQuestionRequest req)
{
return new ProofTokenRequestQuestionResponse()
{
Hint = 69,
Question = "seggs",
SessionKey = new()
{
MxToken = req.SessionKey.MxToken,
AccountServerId = req.SessionKey.AccountServerId,
},
};
}
[ProtocolHandler(Protocol.ProofToken_Submit)]
public ResponsePacket SubmitHandler(ProofTokenSubmitRequest req)
{
return new ProofTokenSubmitResponse()
{
SessionKey = new()
{
MxToken = req.SessionKey.MxToken,
AccountServerId = req.SessionKey.AccountServerId,
},
};
}
}
}

View File

@ -18,7 +18,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
public interface IProtocolHandlerFactory public interface IProtocolHandlerFactory
{ {
public ResponsePacket? Invoke(Protocol protocol, RequestPacket? req); public object? Invoke(Protocol protocol, RequestPacket? req);
public MethodInfo? GetProtocolHandler(Protocol protocol); public MethodInfo? GetProtocolHandler(Protocol protocol);
public Type? GetRequestPacketTypeByProtocol(Protocol protocol); public Type? GetRequestPacketTypeByProtocol(Protocol protocol);
public void RegisterInstance(Type t, object? inst); public void RegisterInstance(Type t, object? inst);
@ -62,7 +62,7 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
} }
} }
public ResponsePacket? Invoke(Protocol protocol, RequestPacket? req) public object? Invoke(Protocol protocol, RequestPacket? req)
{ {
var handler = GetProtocolHandler(protocol); var handler = GetProtocolHandler(protocol);
if (handler is null) if (handler is null)
@ -91,6 +91,8 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{ {
private readonly IServiceScopeFactory scopeFactory; private readonly IServiceScopeFactory scopeFactory;
private IServiceScope scope;
public ProtocolHandlerBase(IServiceScopeFactory _scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) public ProtocolHandlerBase(IServiceScopeFactory _scopeFactory, IProtocolHandlerFactory protocolHandlerFactory)
{ {
scopeFactory = _scopeFactory; scopeFactory = _scopeFactory;
@ -100,23 +102,23 @@ namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
public SCHALEContext Context public SCHALEContext Context
{ {
get get
{
using (var scope = scopeFactory.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<SCHALEContext>(); var db = scope.ServiceProvider.GetRequiredService<SCHALEContext>();
return db; return db;
} }
} }
}
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
scope = scopeFactory.CreateScope();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
scope.Dispose();
Context.Dispose();
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@ -0,0 +1,31 @@
using SCHALE.Common.NetworkProtocol;
namespace SCHALE.GameServer.Controllers.Api.ProtocolHandlers
{
public class Scenario : ProtocolHandlerBase
{
public Scenario(IServiceScopeFactory scopeFactory, IProtocolHandlerFactory protocolHandlerFactory) : base(scopeFactory, protocolHandlerFactory) { }
[ProtocolHandler(Protocol.Scenario_Skip)]
public ResponsePacket SkipHandler(ScenarioSkipRequest req)
{
// skip story doesn't work yet, probably need to implement missiondb
return new ScenarioSkipResponse()
{
};
}
[ProtocolHandler(Protocol.Scenario_Select)]
public ResponsePacket SelectHandler(ScenarioSelectRequest req)
{
return new ScenarioSelectResponse()
{
};
}
}
}

View File

@ -48,6 +48,10 @@ namespace SCHALE.GameServer
builder.Services.AddProtocolHandlerFactory(); builder.Services.AddProtocolHandlerFactory();
builder.Services.AddProtocolHandlerGroup<Account>(); builder.Services.AddProtocolHandlerGroup<Account>();
builder.Services.AddProtocolHandlerGroup<Queuing>(); builder.Services.AddProtocolHandlerGroup<Queuing>();
builder.Services.AddProtocolHandlerGroup<Academy>();
builder.Services.AddProtocolHandlerGroup<Mission>();
builder.Services.AddProtocolHandlerGroup<ProofToken>();
builder.Services.AddProtocolHandlerGroup<Scenario>();
var app = builder.Build(); var app = builder.Build();

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SCHALE.GameServer", "SCHALE.GameServer.csproj", "{73DF585D-E422-4642-8026-C55F031B3D07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{73DF585D-E422-4642-8026-C55F031B3D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73DF585D-E422-4642-8026-C55F031B3D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73DF585D-E422-4642-8026-C55F031B3D07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73DF585D-E422-4642-8026-C55F031B3D07}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC05112F-B591-4DB5-8D12-F56FBF1FA838}
EndGlobalSection
EndGlobal