From 6c4de349202611bfbba3c7606144ea40109e2fed Mon Sep 17 00:00:00 2001 From: Tiger Date: Sat, 23 Sep 2023 13:11:07 +0200 Subject: [PATCH] Initial commit --- .gitignore | 364 +++++++++++++ Communication/Messages/IMessageHandler.cs | 9 + .../Incoming/Handshake/InfoRetrieveEvent.cs | 28 + .../Incoming/Handshake/SsoTicketEvent.cs | 52 ++ .../Messages/Incoming/IncomingHeaders.cs | 491 +++++++++++++++++ .../Messages/Interfaces/IMessageComposer.cs | 10 + .../Messages/Interfaces/IMessageEvent.cs | 11 + Communication/Messages/MessageHandler.cs | 46 ++ .../Alerts/HabboBroadcastMessageComposer.cs | 20 + .../AuthenticationOkMessageComposer.cs | 13 + .../Handshake/UserObjectMessageComposer.cs | 35 ++ .../Messages/Outgoing/OutgoingHeaders.cs | 507 ++++++++++++++++++ Communication/Messages/Types/ClientMessage.cs | 70 +++ Communication/Messages/Types/ServerMessage.cs | 48 ++ Game/Habbos/Habbo.cs | 39 ++ Game/Habbos/HabboDao.cs | 26 + Game/Habbos/IHabboDao.cs | 6 + Game/Settings/ISettingsDao.cs | 6 + Game/Settings/ISettingsManager.cs | 7 + Game/Settings/SettingsDao.cs | 27 + Game/Settings/SettingsManager.cs | 35 ++ Networking/Game/IWebSocketServer.cs | 6 + Networking/Game/Sessions/GameSession.cs | 28 + .../Game/Sessions/GameSessionManager.cs | 56 ++ .../Game/Sessions/IGameSessionManager.cs | 13 + Networking/Game/WebSocketServer.cs | 104 ++++ Program.cs | 44 ++ Storage/DatabaseConnection.cs | 38 ++ Storage/DatabaseHelper.cs | 31 ++ Storage/IDatabaseHelper.cs | 6 + TigerEmu.csproj | 27 + Utils/ByteUtils.cs | 29 + Utils/DependencyInjectionExtensions.cs | 25 + appsettings.yaml | 12 + 34 files changed, 2269 insertions(+) create mode 100644 .gitignore create mode 100644 Communication/Messages/IMessageHandler.cs create mode 100644 Communication/Messages/Incoming/Handshake/InfoRetrieveEvent.cs create mode 100644 Communication/Messages/Incoming/Handshake/SsoTicketEvent.cs create mode 100644 Communication/Messages/Incoming/IncomingHeaders.cs create mode 100644 Communication/Messages/Interfaces/IMessageComposer.cs create mode 100644 Communication/Messages/Interfaces/IMessageEvent.cs create mode 100644 Communication/Messages/MessageHandler.cs create mode 100644 Communication/Messages/Outgoing/Generic/Alerts/HabboBroadcastMessageComposer.cs create mode 100644 Communication/Messages/Outgoing/Handshake/AuthenticationOkMessageComposer.cs create mode 100644 Communication/Messages/Outgoing/Handshake/UserObjectMessageComposer.cs create mode 100644 Communication/Messages/Outgoing/OutgoingHeaders.cs create mode 100644 Communication/Messages/Types/ClientMessage.cs create mode 100644 Communication/Messages/Types/ServerMessage.cs create mode 100644 Game/Habbos/Habbo.cs create mode 100644 Game/Habbos/HabboDao.cs create mode 100644 Game/Habbos/IHabboDao.cs create mode 100644 Game/Settings/ISettingsDao.cs create mode 100644 Game/Settings/ISettingsManager.cs create mode 100644 Game/Settings/SettingsDao.cs create mode 100644 Game/Settings/SettingsManager.cs create mode 100644 Networking/Game/IWebSocketServer.cs create mode 100644 Networking/Game/Sessions/GameSession.cs create mode 100644 Networking/Game/Sessions/GameSessionManager.cs create mode 100644 Networking/Game/Sessions/IGameSessionManager.cs create mode 100644 Networking/Game/WebSocketServer.cs create mode 100644 Program.cs create mode 100644 Storage/DatabaseConnection.cs create mode 100644 Storage/DatabaseHelper.cs create mode 100644 Storage/IDatabaseHelper.cs create mode 100644 TigerEmu.csproj create mode 100644 Utils/ByteUtils.cs create mode 100644 Utils/DependencyInjectionExtensions.cs create mode 100644 appsettings.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7af93c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,364 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +.idea/ \ No newline at end of file diff --git a/Communication/Messages/IMessageHandler.cs b/Communication/Messages/IMessageHandler.cs new file mode 100644 index 0000000..654f85c --- /dev/null +++ b/Communication/Messages/IMessageHandler.cs @@ -0,0 +1,9 @@ +using Tiger.Communication.Messages.Types; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages; + +public interface IMessageHandler +{ + Task TryHandleAsync(GameSession session, ClientMessage request); +} \ No newline at end of file diff --git a/Communication/Messages/Incoming/Handshake/InfoRetrieveEvent.cs b/Communication/Messages/Incoming/Handshake/InfoRetrieveEvent.cs new file mode 100644 index 0000000..222e5f9 --- /dev/null +++ b/Communication/Messages/Incoming/Handshake/InfoRetrieveEvent.cs @@ -0,0 +1,28 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Outgoing.Handshake; +using Tiger.Communication.Messages.Types; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages.Incoming.Handshake; + +public class InfoRetrieveEvent : IMessageEvent +{ + private readonly IGameSessionManager _gameSessionManager; + + public InfoRetrieveEvent(IGameSessionManager gameSessionManager) + { + _gameSessionManager = gameSessionManager; + } + + public IncomingHeaders Header => IncomingHeaders.InfoRetrieveEvent; + public async Task HandleAsync(GameSession gameSession, ClientMessage request) + { + if (gameSession.Habbo == null) + { + await _gameSessionManager.CloseAsync("Not logged in", gameSession); + return; + } + + await gameSession.SendComposerAsync(new UserObjectMessageComposer(gameSession.Habbo)); + } +} \ No newline at end of file diff --git a/Communication/Messages/Incoming/Handshake/SsoTicketEvent.cs b/Communication/Messages/Incoming/Handshake/SsoTicketEvent.cs new file mode 100644 index 0000000..d0187a5 --- /dev/null +++ b/Communication/Messages/Incoming/Handshake/SsoTicketEvent.cs @@ -0,0 +1,52 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Outgoing.Generic.Alerts; +using Tiger.Communication.Messages.Outgoing.Handshake; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Habbos; +using Tiger.Game.Settings; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages.Incoming.Handshake; + +public class SsoTicketEvent : IMessageEvent +{ + private readonly IHabboDao _habboDao; + private readonly IGameSessionManager _gameSessionManager; + private readonly ISettingsManager _settingsManager; + + public SsoTicketEvent(IHabboDao habboDao, IGameSessionManager gameSessionManager, ISettingsManager settingsManager) + { + _habboDao = habboDao; + _gameSessionManager = gameSessionManager; + _settingsManager = settingsManager; + } + + public IncomingHeaders Header => IncomingHeaders.SSoTicketEvent; + + public async Task HandleAsync(GameSession gameSession, ClientMessage request) + { + var sso = request.ReadString(); + + if (sso == null) + { + await _gameSessionManager.CloseAsync("Malformed packet", gameSession); + return; + } + + var habbo = await _habboDao.GetHabboBySsoAsync(sso); + + if (habbo == null) + { + await _gameSessionManager.CloseAsync("User not found", gameSession); + return; + } + + gameSession.Habbo = habbo; + + await gameSession.SendComposerAsync(new AuthenticationOkMessageComposer()); + await gameSession.SendComposerAsync(new HabboBroadcastMessageComposer( + _settingsManager.GetSetting("welcome.message") + .Replace("{user}", gameSession.Habbo.Username) + )); + } +} \ No newline at end of file diff --git a/Communication/Messages/Incoming/IncomingHeaders.cs b/Communication/Messages/Incoming/IncomingHeaders.cs new file mode 100644 index 0000000..b7b04ce --- /dev/null +++ b/Communication/Messages/Incoming/IncomingHeaders.cs @@ -0,0 +1,491 @@ +namespace Tiger.Communication.Messages.Incoming; + +public enum IncomingHeaders : short +{ + AcceptFriendEvent = 137, + AcceptTradingEvent = 3863, + AddAdminRightsToMemberEvent = 2894, + AddFavouriteRoomEvent = 3817, + AddItemToTradeEvent = 3107, + AddItemsToTradeEvent = 1263, + AddJukeboxDiskEvent = 753, + AddSpamWallPostItEvent = 3283, + AmbassadorAlertEvent = 2996, + AnswerPollEvent = 3505, + ApplySnapshotEvent = 3373, + ApproveMembershipRequestEvent = 3386, + ApproveNameEvent = 2109, + AssignRightsEvent = 808, + AvatarEffectActivatedEvent = 2959, + AvatarEffectSelectedEvent = 1752, + AvatarExpressionEvent = 2456, + BanUserWithDurationEvent = 1477, + BreedPetsEvent = 1638, + BuildersClubPlaceWallItemEvent = 2462, + BuildersClubQueryFurniCountEvent = 2529, + BuyMarketplaceOfferEvent = 1603, + CallForHelpEvent = 1691, + CallForHelpFromForumMessageEvent = 1412, + CallForHelpFromForumThreadEvent = 534, + CallForHelpFromImEvent = 2950, + CallForHelpFromPhotoEvent = 2492, + CanCreateRoomEvent = 2128, + CancelMarketplaceOfferEvent = 434, + CancelPetBreedingEvent = 2713, + CancelTypingEvent = 1474, + ChangeMottoEvent = 2228, + ChangePostureEvent = 2235, + ChangeUserNameEvent = 2977, + ChatEvent = 1314, + ChatReviewGuideDecidesOnOfferEvent = 3365, + ChatReviewGuideDetachedEvent = 2501, + ChatReviewGuideVoteEvent = 3961, + ChatReviewSessionCreateEvent = 3060, + CheckUserNameEvent = 3950, + ClientHelloEvent = 4000, + CloseIssueDefaultActionEvent = 2717, + CloseIssuesEvent = 2067, + CloseTradingEvent = 2551, + CommandBotEvent = 2624, + CompleteDiffieHandshakeEvent = 773, + CompostPlantEvent = 3835, + ConfirmAcceptTradingEvent = 2760, + ConfirmDeclineTradingEvent = 2341, + ConfirmPetBreedingEvent = 3382, + ControlYoutubeDisplayPlaybackEvent = 3005, + CraftEvent = 3591, + CraftSecretEvent = 1251, + CreateFlatEvent = 2752, + CreateGuildEvent = 230, + CreditFurniRedeemEvent = 3115, + CustomizeAvatarWithFurniEvent = 3374, + CustomizePetWithFurniEvent = 1328, + DanceEvent = 2080, + DeactivateGuildEvent = 1134, + DeclineFriendEvent = 2890, + DeleteFavouriteRoomEvent = 309, + DeleteRoomEvent = 532, + DeselectFavouriteHabboGroupEvent = 1820, + DiceOffEvent = 1533, + DropCarryItemEvent = 2814, + EditEventEvent = 3991, + EnterOneWayDoorEvent = 2765, + EventLogEvent = 3457, + FindNewFriendsEvent = 516, + FollowFriendEvent = 2970, + ForwardToSomeRoomEvent = 1703, + FriendFurniConfirmLockEvent = 3775, + Game2GetAccountGameStatusEvent = 11, + GameUnloadedEvent = 3207, + GetAchievementsEvent = 219, + GetBadgePointLimitsEvent = 1371, + GetBadgesEvent = 2769, + GetBannedUsersFromRoomEvent = 2267, + GetBonusRareInfoEvent = 957, + GetBotCommandConfigurationDataEvent = 1986, + GetBotInventoryEvent = 3848, + GetBundleDiscountRulesetEvent = 223, + GetCatalogIndexEvent = 1195, + GetCatalogPageEvent = 412, + GetCfhChatlogEvent = 211, + GetCfhStatusEvent = 2746, + GetClubGiftInfo = 487, + GetClubOffersEvent = 3285, + GetCraftableProductsEvent = 633, + GetCraftingRecipeEvent = 1173, + GetCraftingRecipesAvailableEvent = 3086, + GetCreditsInfoEvent = 273, + GetCurrentTimingCodeEvent = 2912, + GetCustomRoomFilterEvent = 1911, + GetExtendedProfileEvent = 3265, + GetFlatControllersEvent = 3385, + GetForumStatsEvent = 3149, + GetForumsListEvent = 873, + GetFriendRequestsEvent = 2448, + GetFurnitureAliasesEvent = 3898, + GetGameListEvent = 741, + GetGameStatusEvent = 3171, + GetGiftWrappingConfigurationEvent = 418, + GetGuestRoomEvent = 2230, + GetGuideReportingStatusEvent = 3786, + GetGuildCreationInfoEvent = 798, + GetGuildEditInfoEvent = 1004, + GetGuildEditorDataEvent = 813, + GetGuildFurniContextMenuInfoEvent = 2651, + GetGuildMembersEvent = 312, + GetGuildMembershipsEvent = 367, + GetHabboGroupBadgesEvent = 21, + GetHabboGroupDetailsEvent = 2991, + GetIgnoredUsersEvent = 3878, + GetItemDataEvent = 3964, + GetJukeboxPlayListEvent = 1435, + GetLimitedOfferAppearingNextEvent = 410, + GetMotdEvent = 1523, + GetMarketplaceCanMakeOfferEvent = 848, + GetMarketplaceConfigurationEvent = 2597, + GetMarketplaceItemStatsEvent = 3288, + GetMarketplaceOffersEvent = 2407, + GetMarketplaceOwnOffersEvent = 2105, + GetMemberGuildItemCountEvent = 3593, + GetMessagesEvent = 232, + GetModeratorRoomInfoEvent = 707, + GetModeratorUserInfoEvent = 3295, + GetNowPlayingEvent = 1325, + GetOccupiedTilesEvent = 1687, + GetOfficialSongIdEvent = 3189, + GetPendingCallsForHelpEvent = 3267, + GetPetCommandsEvent = 2161, + GetPetInfoEvent = 2934, + GetPetInventoryEvent = 3095, + GetPopularRoomTagsEvent = 826, + GetProductOfferEvent = 2594, + GetPromoArticlesEvent = 1827, + GetRecyclerPrizesEvent = 398, + GetRecyclerStatusEvent = 1342, + GetRelationshipStatusInfoEvent = 2138, + GetResolutionAchievementsEvent = 359, + GetRoomAdPurchaseInfoEvent = 1075, + GetRoomChatlogEvent = 2587, + GetRoomEntryDataEvent = 2300, + GetRoomEntryTileEvent = 3559, + GetRoomSettingsEvent = 3129, + GetRoomVisitsEvent = 3526, + GetSelectedBadgesEvent = 2091, + GetSellablePetPalettesEvent = 1756, + GetSongInfoEvent = 3082, + GetSoundSettingsEvent = 2388, + GetTalentTrackEvent = 196, + GetTalentTrackLevelEvent = 2127, + GetThreadsEvent = 436, + GetUnreadForumsCountEvent = 2908, + GetUserChatlogEvent = 1391, + GetUserEventCatsEvent = 1782, + GetUserFlatCatsEvent = 3027, + GetUserTagsEvent = 17, + GetWardrobeEvent = 2742, + GetWeeklyGameRewardEvent = 2914, + GetWeeklyGameRewardWinnersEvent = 1054, + GetYoutubeDisplayStatusEvent = 336, + GuideSessionCreateEvent = 3338, + GuideSessionFeedbackEvent = 477, + GuideSessionGetRequesterRoomEvent = 1052, + GuideSessionGuideDecidesEvent = 1424, + GuideSessionInviteRequesterEvent = 234, + GuideSessionIsTypingEvent = 519, + GuideSessionMessageEvent = 3899, + GuideSessionOnDutyUpdateEvent = 1922, + GuideSessionReportEvent = 3969, + GuideSessionRequesterCancelsEvent = 291, + GuideSessionResolvedEvent = 887, + HabboSearchEvent = 1210, + // HotelViewClaimBadgeRewardEvent = -1, + HotelViewRequestBadgeRewardEvent = 2318, + HotelViewRequestSecondsUntilEvent = 271, + IgnoreUserEvent = 1117, + InfoRetrieveEvent = 357, + InitDiffieHandshakeEvent = 3110, + JoinHabboGroupEvent = 998, + JoinQueueEvent = 1458, + JukeBoxEventOne = 2304, + KickMemberEvent = 593, + LatencyPingRequestEvent = 295, + LetUserInEvent = 1644, + LookToEvent = 3301, + MakeOfferEvent = 3447, + MessengerInitEvent = 2781, + ModAlertEvent = 229, + ModBanEvent = 2766, + ModKickEvent = 2582, + ModMessageEvent = 1840, + ModMuteEvent = 1945, + // ModToolRequestRoomUserChatlogEvent = -1, + ModToolSanctionEvent = 1392, + // ModToolWarnEvent = -1, + ModTradingLockEvent = 3742, + ModerateMessageEvent = 286, + ModerateRoomEvent = 3260, + ModerateThreadEvent = 1397, + ModeratorActionEvent = 3842, + MountPetEvent = 1036, + MoveAvatarEvent = 3320, + MoveObjectEvent = 248, + MovePetEvent = 3449, + MoveWallItemEvent = 168, + MuteAllInRoomEvent = 3637, + MyFavouriteRoomsSearchEvent = 2578, + MyFriendsRoomsSearchEvent = 2266, + MyGuildBasesSearchEvent = 39, + MyRoomHistorySearchEvent = 2264, + MyRoomRightsSearchEvent = 272, + MyRoomsSearchEvent = 2277, + NavigatorAddCollapsedCategoryEvent = 1834, + NavigatorAddSavedSearchEvent = 2226, + NavigatorDeleteSavedSearchEvent = 1954, + NavigatorRemoveCollapsedCategoryEvent = 637, + NavigatorSetSearchCodeViewModeEvent = 1202, + NewNavigatorInitEvent = 2110, + NewNavigatorSearchEvent = 249, + NewUserExperienceGetGiftsEvent = 1822, + NewUserExperienceScriptProceedEvent = 1299, + OpenCampaignCalendarDoorEvent = 8809, + OpenCampaignCalendarDoorAsStaffEvent = 2507, + OpenFlatConnectionEvent = 2312, + OpenPetPackageEvent = 3698, + OpenTradingEvent = 1481, + PassCarryItemEvent = 2941, + PickIssuesEvent = 15, + PickupObjectEvent = 3456, + PlaceBotEvent = 1592, + PlaceObjectEvent = 1258, + PlacePetEvent = 2647, + PlacePostItEvent = 2248, + PollRejectEvent = 1773, + PollStartEvent = 109, + PongEvent = 2596, + PopularRoomsSearchEvent = 2758, + PostMessageEvent = 3529, + PresentOpenEvent = 3558, + PublishPhotoEvent = 2068, + PurchaseFromCatalogAsGiftEvent = 1411, + PurchaseFromCatalogEvent = 3492, + PurchasePhotoEvent = 2408, + PurchaseRoomAdEvent = 777, + PurchaseTargetedOfferEvent = 1826, + PurchaseVipMembershipExtensionEvent = 3407, + QuitEvent = 105, + RateFlatEvent = 3582, + RecycleItemsEvent = 2771, + RedeemMarketplaceOfferCreditsEvent = 2650, + RedeemVoucherEvent = 339, + RejectMembershipRequestEvent = 1894, + ReleaseIssuesEvent = 1572, + RemoveAdminRightsFromMemberEvent = 722, + RemoveAllRightsEvent = 2683, + RemoveBotFromFlatEvent = 3323, + RemoveFriendEvent = 1689, + RemoveItemEvent = 3336, + RemoveItemFromTradeEvent = 3845, + RemoveJukeboxDiskEvent = 3050, + RemoveOwnRoomRightsRoomEvent = 3182, + RemovePetFromFlatEvent = 1581, + RemoveRightsEvent = 2064, + RemoveSaddleFromPetEvent = 186, + RenderRoomEvent = 3226, + RenderRoomThumbnailEvent = 1982, + RentableSpaceCancelRentEvent = 1667, + RentableSpaceRentEvent = 2946, + // RequestAchievementConfigurationEvent = -1, + RequestCameraConfigurationEvent = 796, + RequestFriendEvent = 3157, + RequestFurniInventoryEvent = 3150, + RequestFurniInventoryWhenNotInRoomEvent = 3500, + RequestRoomPropertySet = 711, + RespectPetEvent = 3202, + RespectUserEvent = 2694, + RoomDimmerChangeStateEvent = 2296, + RoomDimmerGetPresetsEvent = 2813, + RoomDimmerSavePresetEvent = 1648, + RoomTextSearchEvent = 3943, + RoomUserKickEvent = 1320, + RoomUserMuteEvent = 3485, + RoomsWhereMyFriendsAreSearchEvent = 1786, + RoomsWithHighestScoreSearchEvent = 2939, + SSoTicketEvent = 2419, + SaveRoomSettingsEvent = 1969, + SaveWardrobeOutfitEvent = 800, + ScrGetKickbackInfoEvent = 869, + ScrGetUserInfoEvent = 3166, + // SearchRoomsByTagEvent = -1, + SelectClubGiftEvent = 2276, + SelectFavouriteHabboGroupEvent = 3549, + SendMsgEvent = 3567, + SendRoomInviteEvent = 1276, + SetActivatedBadgesEvent = 644, + SetChatPreferencesEvent = 1262, + SetChatStylePreferenceEvent = 1030, + SetClothingChangeDataEvent = 924, + SetCustomStackingHeightEvent = 3839, + SetIgnoreRoomInvitesEvent = 1086, + SetItemDataEvent = 3666, + SetMannequinFigureEvent = 2209, + SetMannequinNameEvent = 2850, + SetNewNavigatorWindowPreferencesEvent = 3159, + SetObjectDataEvent = 3608, + SetRandomStateEvent = 3617, + SetRelationshipStatusEvent = 3768, + SetRoomBackgroundColorDataEvent = 2880, + SetRoomCameraPreferencesEvent = 1461, + SetSoundSettingsEvent = 1367, + SetTargetedOfferStateEvent = 2041, + SetYoutubeDisplayPlaylistEvent = 2069, + ShoutEvent = 2085, + SignEvent = 1975, + SpinWheelOfFortuneEvent = 2144, + StartTypingEvent = 1597, + SubmitRoomToCompetitionEvent = 2595, + ThrowDiceEvent = 1990, + TogglePetBreedingPermissionEvent = 3379, + TogglePetRidingPermissionEvent = 1472, + ToggleStaffPickEvent = 1918, + UnacceptTradingEvent = 1444, + UnbanUserFromRoomEvent = 992, + UnignoreUserEvent = 2061, + UniqueIdEvent = 2490, + UpdateActionEvent = 2281, + UpdateConditionEvent = 3203, + UpdateFigureDataEvent = 2730, + UpdateFloorPropertiesEvent = 875, + UpdateForumSettingsEvent = 2214, + UpdateGuildBadgeEvent = 1991, + UpdateGuildColorsEvent = 1764, + UpdateGuildIdentityEvent = 3137, + UpdateGuildSettingsEvent = 3435, + UpdateHomeRoomEvent = 1740, + UpdateRoomFilterEvent = 3001, + UpdateThreadEvent = 3045, + UpdateTriggerEvent = 1520, + UpdateUiFlagsEvent = 2313, + UseFurnitureEvent = 99, + UseWallItemEvent = 210, + VisitUserEvent = 3997, + WhisperEvent = 1543, + AcceptGameInviteEvent = 3802, + AcceptQuestEvent = 3604, + ApproveAllMembershipRequestsEvent = 882, + BuildersClubPlaceRoomItemEvent = 1051, + BuyMarketplaceTokensEvent = 1866, + CallForHelpFromSelfieEvent = 2755, + CancelEventEvent = 2725, + CancelQuestEvent = 3133, + ChangeEmailEvent = 3965, + ChangeQueueEvent = 3093, + CommunityGoalVoteEvent = 3536, + CompetitionRoomsSearchEvent = 433, + DeletePendingCallsForHelpEvent = 3605, + DisconnectEvent = 2445, + ExtendRentOrBuyoutFurniEvent = 1071, + ExtendRentOrBuyoutStripItemEvent = 2115, + ForwardToACompetitionRoomEvent = 172, + ForwardToARandomPromotedRoomEvent = 10, + ForwardToRandomCompetitionRoomEvent = 865, + FriendListUpdateEvent = 1419, + FriendRequestQuestCompleteEvent = 1148, + Game2CheckGameDirectoryStatusEvent = 3259, + Game2ExitGameEvent = 1445, + Game2GameChatEvent = 2502, + Game2GetWeeklyFriendsLeaderboardEvent = 1232, + Game2GetWeeklyLeaderboardEvent = 2565, + Game2LoadStageReadyEvent = 2415, + Game2PlayAgainEvent = 3196, + Game2RequestFullStatusUpdateEvent = 1598, + GetCatalogPageExpirationEvent = 742, + GetCatalogPageWithEarliestExpiryEvent = 3135, + GetCategoriesWithUserCountEvent = 3782, + GetCommunityGoalEarnedPrizesEvent = 2688, + GetCommunityGoalHallOfFameEvent = 2167, + GetCommunityGoalProgressEvent = 1145, + GetConcurrentUsersGoalProgressEvent = 1343, + GetConcurrentUsersRewardEvent = 3872, + GetDailyQuestEvent = 2486, + GetDirectClubBuyAvailableEvent = 801, + GetEmailStatusEvent = 2557, + GetExtendedProfileByNameEvent = 2249, + GetFaqCategoryEvent = 3445, + GetFaqTextEvent = 1849, + GetGameAchievementsEvent = 2399, + GetHabboBasicMembershipExtendOfferEvent = 603, + GetInterstitialEvent = 2519, + GetIsBadgeRequestFulfilledEvent = 1364, + GetIsOfferGiftableEvent = 1347, + GetIsUserPartOfCompetitionEvent = 2077, + GetNextTargetedOfferEvent = 2487, + GetOfficialRoomsEvent = 1229, + GetQuestsEvent = 3333, + GetQuizQuestionsEvent = 1296, + GetSeasonalCalendarDailyOfferEvent = 3257, + GetSeasonalQuestsOnlyEvent = 1190, + GetSoundMachinePlayListEvent = 3498, + GetTargetedOfferEvent = 596, + GetThreadEvent = 3900, + GetUserGameAchievementsEvent = 389, + GiveSupplementToPetEvent = 749, + GoToFlatEvent = 685, + GuideAdvertisementReadEvent = 2455, + GuildBaseSearchEvent = 2930, + HarvestPetEvent = 1521, + IgnoreUserIdEvent = 3314, + InterstitialShownEvent = 1109, + LagWarningReportEvent = 3847, + LatencyPingReportEvent = 96, + LeaveQueueEvent = 2384, + ModToolPreferencesEvent = 31, + MyFrequentRoomHistorySearchEvent = 1002, + MyRecommendedRoomsEvent = 2537, + MysteryBoxWaitingCanceledEvent = 2012, + OpenMysteryTrophyEvent = 3074, + OpenQuestTrackerEvent = 2750, + OpenWelcomeGiftEvent = 2638, + PassCarryItemToPetEvent = 2768, + PeerUsersClassificationEvent = 1160, + PerformanceLogEvent = 3230, + PetSelectedEvent = 549, + PhotoCompetitionEvent = 3959, + PostQuizAnswersEvent = 3720, + PurchaseBasicMembershipExtensionEvent = 2735, + RedeemCommunityGoalPrizeEvent = 90, + RejectQuestEvent = 2397, + RentableSpaceStatusEvent = 872, + RequestABadgeEvent = 3077, + ResetPhoneNumberStateEvent = 2741, + ResetResolutionAchievementEvent = 3144, + ResetUnseenItemIdsEvent = 3493, + ResetUnseenItemsEvent = 2343, + RoomAdEventTabAdClickedEvent = 2412, + RoomAdEventTabViewedEvent = 2668, + RoomAdPurchaseInitiatedEvent = 2283, + RoomAdSearchEvent = 2809, + RoomCompetitionInitEvent = 1334, + RoomNetworkOpenConnectionEvent = 3736, + RoomUsersClassificationEvent = 2285, + SearchFaqsEvent = 2031, + SetPhoneNumberVerificationStatusEvent = 1379, + SetRoomSessionTagsEvent = 3305, + ShopTargetedOfferViewedEvent = 3483, + StartCampaignEvent = 1697, + TryPhoneNumberEvent = 790, + UnblockGroupMemberEvent = 2864, + UpdateForumReadMarkerEvent = 1855, + UpdateRoomCategoryAndTradeSettingsEvent = 1265, + UpdateRoomThumbnailEvent = 2468, + VerifyCodeEvent = 2721, + VersionCheckEvent = 1053, + VoteForRoomEvent = 143, + WelcomeGiftChangeEmailEvent = 66, + UnknownSnowStormEvent6000 = 6000, + UnknownSnowStormEvent6001 = 6001, + UnknownSnowStormEvent6002 = 6002, + UnknownSnowStormEvent6003 = 6003, + UnknownSnowStormEvent6004 = 6004, + UnknownSnowStormEvent6005 = 6005, + UnknownSnowStormEvent6006 = 6006, + UnknownSnowStormEvent6007 = 6007, + UnknownSnowStormEvent6008 = 6008, + UnknownSnowStormEvent6009 = 6009, + UnknownSnowStormEvent6010 = 6010, + UnknownSnowStormEvent6011 = 6011, + SnowStormJoinQueueEvent = 6012, + UnknownSnowStormEvent6013 = 6013, + UnknownSnowStormEvent6014 = 6014, + UnknownSnowStormEvent6015 = 6015, + UnknownSnowStormEvent6016 = 6016, + UnknownSnowStormEvent6017 = 6017, + UnknownSnowStormEvent6018 = 6018, + UnknownSnowStormEvent6019 = 6019, + UnknownSnowStormEvent6020 = 6020, + UnknownSnowStormEvent6021 = 6021, + UnknownSnowStormEvent6022 = 6022, + UnknownSnowStormEvent6023 = 6023, + UnknownSnowStormEvent6024 = 6024, + UnknownSnowStormEvent6025 = 6025, + SnowStormUserPickSnowballEvent = 6026, +} \ No newline at end of file diff --git a/Communication/Messages/Interfaces/IMessageComposer.cs b/Communication/Messages/Interfaces/IMessageComposer.cs new file mode 100644 index 0000000..ffed8a6 --- /dev/null +++ b/Communication/Messages/Interfaces/IMessageComposer.cs @@ -0,0 +1,10 @@ +using Tiger.Communication.Messages.Outgoing; +using Tiger.Communication.Messages.Types; + +namespace Tiger.Communication.Messages.Interfaces; + +public interface IMessageComposer +{ + OutgoingHeaders Header { get; } + void Compose(ServerMessage message); +} \ No newline at end of file diff --git a/Communication/Messages/Interfaces/IMessageEvent.cs b/Communication/Messages/Interfaces/IMessageEvent.cs new file mode 100644 index 0000000..774e7bf --- /dev/null +++ b/Communication/Messages/Interfaces/IMessageEvent.cs @@ -0,0 +1,11 @@ +using Tiger.Communication.Messages.Incoming; +using Tiger.Communication.Messages.Types; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages.Interfaces; + +public interface IMessageEvent +{ + IncomingHeaders Header { get; } + Task HandleAsync(GameSession gameSession, ClientMessage request); +} \ No newline at end of file diff --git a/Communication/Messages/MessageHandler.cs b/Communication/Messages/MessageHandler.cs new file mode 100644 index 0000000..c610a66 --- /dev/null +++ b/Communication/Messages/MessageHandler.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Logging; +using Tiger.Communication.Messages.Incoming; +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; +using Tiger.Networking.Game.Sessions; + +namespace Tiger.Communication.Messages; + +public class MessageHandler : IMessageHandler +{ + private readonly ILogger _logger; + private readonly IDictionary _messageEvents; + private readonly IDictionary _messageNames; + + public MessageHandler(ILogger logger, IEnumerable messageEvents) + { + _logger = logger; + _messageEvents = messageEvents.ToDictionary(messageEvent => (short)messageEvent.Header); + + _messageNames = new Dictionary(); + + foreach (var name in Enum.GetNames(typeof(IncomingHeaders))) + { + _messageNames.Add((short)Enum.Parse(typeof(IncomingHeaders), name), name); + } + } + + public async Task TryHandleAsync(GameSession session, ClientMessage request) + { + if (request.Header == null) + { + return; + } + + if (_messageEvents.TryGetValue(request.Header.Value, out var messageEvent)) + { + _logger.LogInformation("Handling header ID {Header} on class {Class}", request.Header.Value, messageEvent.GetType()); + + await messageEvent.HandleAsync(session, request); + } + else + { + _logger.LogWarning("Unregistered header {Header} {Name}", request.Header.Value, _messageNames.TryGetValue(request.Header.Value, out var messageName) ? messageName : string.Empty); + } + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/Generic/Alerts/HabboBroadcastMessageComposer.cs b/Communication/Messages/Outgoing/Generic/Alerts/HabboBroadcastMessageComposer.cs new file mode 100644 index 0000000..fae3328 --- /dev/null +++ b/Communication/Messages/Outgoing/Generic/Alerts/HabboBroadcastMessageComposer.cs @@ -0,0 +1,20 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; + +namespace Tiger.Communication.Messages.Outgoing.Generic.Alerts; + +public class HabboBroadcastMessageComposer : IMessageComposer +{ + private readonly string _welcomeMessage; + + public HabboBroadcastMessageComposer(string welcomeMessage) + { + _welcomeMessage = welcomeMessage; + } + + public OutgoingHeaders Header => OutgoingHeaders.HabboBroadcastMessageComposer; + public void Compose(ServerMessage message) + { + message.AppendString(_welcomeMessage); + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/Handshake/AuthenticationOkMessageComposer.cs b/Communication/Messages/Outgoing/Handshake/AuthenticationOkMessageComposer.cs new file mode 100644 index 0000000..cf25978 --- /dev/null +++ b/Communication/Messages/Outgoing/Handshake/AuthenticationOkMessageComposer.cs @@ -0,0 +1,13 @@ +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; + +namespace Tiger.Communication.Messages.Outgoing.Handshake; + +public class AuthenticationOkMessageComposer : IMessageComposer +{ + public OutgoingHeaders Header => OutgoingHeaders.AuthenticationOkMessageComposer; + + public void Compose(ServerMessage message) + { + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/Handshake/UserObjectMessageComposer.cs b/Communication/Messages/Outgoing/Handshake/UserObjectMessageComposer.cs new file mode 100644 index 0000000..1c251c1 --- /dev/null +++ b/Communication/Messages/Outgoing/Handshake/UserObjectMessageComposer.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Habbos; + +namespace Tiger.Communication.Messages.Outgoing.Handshake; + +public class UserObjectMessageComposer : IMessageComposer +{ + private readonly Habbo _habbo; + + public UserObjectMessageComposer(Habbo habbo) + { + _habbo = habbo; + } + + public OutgoingHeaders Header => OutgoingHeaders.UserObjectComposer; + public void Compose(ServerMessage message) + { + message.AppendUInt32(_habbo.Id); + message.AppendString(_habbo.Username); + message.AppendString(_habbo.Figure); + message.AppendString(_habbo.Gender); + message.AppendString(_habbo.Motto); + message.AppendString(string.Empty); + message.AppendBoolean(false); + message.AppendInt32(0); // respect received + message.AppendInt32(0); // respect points to give + message.AppendInt32(0); // scratch to give + message.AppendBoolean(false); + message.AppendString(_habbo.LastLogin.ToString(CultureInfo.CurrentCulture)); + message.AppendBoolean(false); // can change name + message.AppendBoolean(false); // safety locked + } +} \ No newline at end of file diff --git a/Communication/Messages/Outgoing/OutgoingHeaders.cs b/Communication/Messages/Outgoing/OutgoingHeaders.cs new file mode 100644 index 0000000..8f5c3d2 --- /dev/null +++ b/Communication/Messages/Outgoing/OutgoingHeaders.cs @@ -0,0 +1,507 @@ +namespace Tiger.Communication.Messages.Outgoing; + +public enum OutgoingHeaders +{ + AcceptFriendResultComposer = 896, + AccountPreferencesComposer = 513, + AccountSafetyLockStatusChangeMessageComposer = 1243, + AchievementComposer = 2107, + AchievementResolutionCompletedMessageComposer = 740, + AchievementResolutionProgressMessageComposer = 3370, + AchievementResolutionsMessageComposer = 66, + AchievementsComposer = 305, + AchievementsScoreComposer = 1968, + ActivityPointsMessageComposer = 2018, + ApproveNameMessageComposer = 1503, + AuthenticationOkMessageComposer = 2491, + AvailabilityStatusMessageComposer = 2033, + AvailabilityTimeMessageComposer = 600, + AvatarEffectActivatedMessageComposer = 1959, + AvatarEffectAddedMessageComposer = 2867, + AvatarEffectExpiredMessageComposer = 2228, + AvatarEffectMessageComposer = 1167, + AvatarEffectSelectedMessageComposer = 3473, + AvatarEffectsMessageComposer = 340, + BadgePointLimitsComposer = 2501, + BadgeReceivedComposer = 2493, + BadgesComposer = 717, + BannedUsersFromRoomComposer = 1869, + BonusRareInfoMessageComposer = 1533, + BotAddedToInventoryComposer = 1352, + BotCommandConfigurationComposer = 1618, + BotErrorComposer = 639, + BotForceOpenContextMenuComposer = 296, + BotInventoryComposer = 3086, + BotReceivedMessageComposer = 3684, + BotRemovedFromInventoryComposer = 233, + BotSkillListUpdateComposer = 69, + BuildersClubFurniCountMessageComposer = 3828, + BuildersClubSubscriptionStatusMessageComposer = 1452, + BundleDiscountRulesetMessageComposer = 2347, + CallForHelpDisabledNotifyMessageComposer = 1651, + CallForHelpPendingCallsDeletedMessageComposer = 77, + CallForHelpPendingCallsMessageComposer = 1121, + CallForHelpReplyMessageComposer = 3796, + CallForHelpResultMessageComposer = 3635, + CameraPublishStatusMessageComposer = 2057, + CameraPurchaseOkMessageComposer = 2783, + CameraSnapshotMessageComposer = 463, + CameraStorageUrlMessageComposer = 3696, + CampaignCalendarDataMessageComposer = 2531, + CampaignCalendarDoorOpenedMessageComposer = 2551, + CanCreateRoomComposer = 378, + CanCreateRoomEventComposer = 2599, + CantConnectMessageComposer = 899, + CarryObjectMessageComposer = 1474, + CatalogPageExpirationComposer = 2668, + CatalogPageMessageComposer = 804, + CatalogPagesListComposer = 1032, + CatalogPageWithEarliestExpiryMessageComposer = 2515, + CatalogPublishedMessageComposer = 1866, + CategoriesWithVisitorCountComposer = 1455, + CfhChatlogComposer = 607, + CfhSanctionMessageComposer = 2782, + CfhTopicsInitComposer = 325, + ChangeEmailResultComposer = 1815, + ChangeUserNameResultMessageEvent = 118, + ChatMessageComposer = 1446, + ChatReviewSessionDetachedMessageComposer = 30, + ChatReviewSessionOfferedToGuideMessageComposer = 735, + ChatReviewSessionResultsMessageComposer = 3276, + ChatReviewSessionStartedMessageComposer = 143, + ChatReviewSessionVotingStatusMessageComposer = 1829, + CheckUserNameResultMessageComposer = 563, + CitizenshipVipOfferPromoEnabledComposer = 2278, + CloseConnectionMessageComposer = 122, + ClubGiftInfoComposer = 619, + ClubGiftNotificationComposer = 2188, + ClubGiftSelectedComposer = 659, + CollapsedCategoriesComposer = 1543, + CommunityGoalEarnedPrizesMessageComposer = 3319, + CommunityGoalHallOfFameMessageComposer = 3005, + CommunityGoalProgressMessageComposer = 2525, + CommunityGoalVoteMessageComposer = 1435, + CompetitionEntrySubmitResultComposer = 1177, + CompetitionRoomsDataMessageComposer = 3954, + CompetitionStatusMessageComposer = 133, + CompetitionVotingInfoMessageComposer = 3506, + CompleteDiffieHandshakeComposer = 3885, + ConcurrentUsersGoalProgressMessageComposer = 2737, + ConfirmBreedingRequestComposer = 634, + ConfirmBreedingResultComposer = 1625, + ConvertedRoomIdComposer = 1331, + CraftableProductsComposer = 1000, + CraftingRecipeComposer = 2774, + CraftingRecipesAvailableComposer = 2124, + CraftingResultComposer = 618, + CreditBalanceComposer = 3475, + CurrentTimingCodeMessageComposer = 1745, + CustomUserNotificationMessageComposer = 909, + DanceMessageComposer = 2233, + DiceValueMessageComposer = 3431, + DirectSmsClubBuyAvailableMessageComposer = 195, + DisconnectReasonComposer = 4000, + DoorbellMessageComposer = 2309, + ElementPointerMessageComposer = 1787, + EmailStatusResultComposer = 612, + EpicPopupMessageComposer = 3945, + ErrorReportComposer = 1004, + ExpressionMessageComposer = 1631, + ExtendedProfileChangedMessageComposer = 876, + ExtendedProfileMessageComposer = 3898, + FaqCategoriesMessageComposer = 2756, + FaqCategoryMessageComposer = 2819, + FaqClientFaqsMessageComposer = 2492, + FaqSearchResultsMessageComposer = 1551, + FaqTextMessageComposer = 3292, + FavoriteMembershipUpdateMessageComposer = 3403, + FavouriteChangedComposer = 2524, + FavouritesComposer = 151, + FigureSetIdsComposer = 1450, + FigureUpdateComposer = 2429, + FindFriendsProcessResultComposer = 1210, + FlatAccessDeniedMessageComposer = 878, + FlatAccessibleMessageComposer = 3783, + FlatControllerAddedComposer = 2088, + FlatControllerRemovedComposer = 1327, + FlatControllersComposer = 1284, + FlatCreatedComposer = 1304, + FloodControlMessageComposer = 566, + FloorHeightMapComposer = 1301, + FollowFriendFailedComposer = 3048, + ForumDataMessageComposer = 3011, + ForumsListMessageComposer = 3001, + FriendFurniCancelLockMessageComposer = 770, + FriendFurniOtherLockConfirmedMessageComposer = 382, + FriendFurniStartConfirmationMessageComposer = 3753, + FriendListFragmentMessageComposer = 3130, + FriendListUpdateComposer = 2800, + FriendNotificationComposer = 3082, + FriendRequestsComposer = 280, + FurniListAddOrUpdateComposer = 104, + FurniListComposer = 994, + FurniListInvalidateComposer = 3151, + FurniListRemoveComposer = 159, + FurniRentOrBuyoutOfferMessageComposer = 35, + FurnitureAliasesMessageComposer = 1723, + Game2AccountGameStatusMessageComposer = 2893, + Game2GameDirectoryStatusMessageComposer = 2246, + Game2InArenaQueueMessageComposer = 872, + Game2JoiningGameFailedMessageComposer = 1730, + Game2StopCounterMessageComposer = 2233, + Game2UserLeftGameMessageComposer = 2383, + Game2WeeklyFriendsLeaderboardComposer = 2270, + Game2WeeklyLeaderboardComposer = 2196, + GameAchievementsMessageComposer = 1689, + GameInviteMessageComposer = 904, + GameListMessageComposer = 222, + GamePlayerValueMessageComposer = 2324, + GameStatusMessageComposer = 3805, + GenericErrorComposer = 1600, + GetGuestRoomResultComposer = 687, + GiftReceiverNotFoundComposer = 1517, + GiftWrappingConfigurationComposer = 2234, + GotMysteryBoxPrizeMessageComposer = 3712, + GoToBreedingNestFailureComposer = 2621, + GroupDetailsChangedMessageComposer = 1459, + GroupMembershipRequestedMessageComposer = 1180, + GuestRoomSearchResultComposer = 52, + GuideOnDutyStatusMessageComposer = 1548, + GuideReportingStatusMessageComposer = 3463, + GuideSessionAttachedMessageComposer = 1591, + GuideSessionDetachedMessageComposer = 138, + GuideSessionEndedMessageComposer = 1456, + GuideSessionErrorMessageComposer = 673, + GuideSessionInvitedToGuideRoomMessageComposer = 219, + GuideSessionMessageMessageComposer = 841, + GuideSessionPartnerIsTypingComposer = 1016, + GuideSessionRequesterRoomMessageComposer = 1847, + GuideSessionStartedComposer = 3209, + GuideTicketCreationResultMessageComposer = 3285, + GuideTicketResolutionMessageComposer = 2674, + GuildCreatedMessageComposer = 2808, + GuildCreationInfoMessageComposer = 2159, + GuildEditFailedMessageComposer = 3988, + GuildEditInfoMessageComposer = 3965, + GuildEditorDataMessageComposer = 2238, + GuildForumThreadsComposer = 1073, + GuildFurniContextMenuInfoMessageComposer = 3293, + GuildMemberFurniCountInHqMessageComposer = 1876, + GuildMemberMgmtFailedMessageComposer = 818, + GuildMembersComposer = 1200, + GuildMembershipRejectedMessageComposer = 2445, + GuildMembershipsMessageComposer = 420, + GuildMembershipUpdatedMessageComposer = 265, + HabboAchievementNotificationMessageComposer = 806, + HabboActivityPointNotificationMessageComposer = 2275, + HabboBroadcastMessageComposer = 3801, + HabboClubExtendOfferMessageComposer = 3964, + HabboClubOffersMessageComposer = 2405, + HabboGroupBadgesMessageComposer = 2402, + HabboGroupDeactivatedMessageComposer = 3129, + HabboGroupDetailsMessageComposer = 1702, + HabboGroupJoinFailedMessageComposer = 762, + HabboSearchResultComposer = 973, + HabboWayQuizComposer1 = 3379, + HandItemReceivedMessageComposer = 354, + HeightMapComposer = 2753, + HeightMapUpdateMessageComposer = 558, + HotelClosedAndOpensComposer = 3728, + HotelClosesAndWillOpenAtComposer = 2771, + HotelMergeNameChangeComposer = 1663, + HotelViewCatalogPageExpiringComposer = 690, + HotelViewCustomTimerComposer = -1, + HotelWillCloseInMinutesComposer = 1050, + IdentityAccountsComposer = 3523, + IgnoredUsersMessageComposer = 126, + IgnoreResultMessageComposer = 207, + InClientLinkMessageComposer = 2023, + InfoFeedEnableMessageComposer = 3284, + InitCameraMessageComposer = 3878, + InitDiffieHandshakeComposer = 1347, + InstantMessageErrorComposer = 3359, + InterstitialMessageComposer = 1808, + IsBadgeRequestFulfilledComposer = 2998, + IsFirstLoginOfDayComposer = 793, + IsOfferGiftableMessageComposer = 761, + IssueCloseNotificationMessageComposer = 934, + IssueDeletedMessageComposer = 3192, + IssueInfoMessageComposer = 3609, + IssuePickFailedMessageComposer = 3150, + IsUserPartOfCompetitionMessageComposer = 3841, + ItemAddMessageComposer = 2187, + ItemDataUpdateMessageComposer = 2202, + ItemRemoveMessageComposer = 3208, + ItemsComposer = 1369, + ItemUpdateMessageComposer = 2009, + JoinedQueueMessageComposer = 2260, + JoiningQueueFailedMessageComposer = 3035, + JukeBoxPlaylistFullMessageComposer = 105, + JukeboxSongDisksMessageComposer = 34, + LatencyPingResponseMessageComposer = 10, + LeftQueueMessageComposer = 1477, + LimitedEditionSoldOutComposer = 377, + LimitedOfferAppearingNextMessageComposer = 44, + LoadGameMessageComposer = 3654, + LoadGameUrlMessageComposer = 2624, + MaintenanceStatusMessageComposer = 1350, + MarketplaceBuyOfferResultComposer = 2032, + MarketplaceCancelOfferResultComposer = 3264, + MarketplaceCanMakeOfferResult = 54, + MarketplaceConfigurationComposer = 1823, + MarketplaceItemStatsComposer = 725, + MarketplaceMakeOfferResult = 1359, + MarketplaceOffersComposer = 680, + MarketPlaceOwnOffersComposer = 3884, + MessengerErrorComposer = 892, + MessengerInitComposer = 1605, + MiniMailNewMessageComposer = 1911, + MiniMailUnreadCountComposer = 2803, + ModeratorActionResultMessageComposer = 2335, + ModeratorCautionComposer = 1890, + ModeratorInitMessageComposer = 2696, + ModeratorMessageComposer = 2030, + ModeratorRoomInfoComposer = 1333, + ModeratorToolPreferencesComposer = 1576, + ModeratorUserInfoComposer = 2866, + MOtdNotificationComposer = 2035, + MuteAllInRoomComposer = 2533, + MysteryBoxClosingComposer = 596, + MysteryBoxKeysMessageComposer = 2833, + MysteryBoxStartComposer = 3201, + NavigatorLiftedRoomsComposer = 3104, + NavigatorMetaDataComposer = 3052, + NavigatorSavedSearchesComposer = 3984, + NavigatorSearchResultBlocksComposer = 2690, + NavigatorSettingsComposer = 2875, + NestBreedingSuccessComposer = 2527, + NewConsoleMessageComposer = 1587, + NewFriendRequestComposer = 2219, + NewNavigatorPreferencesComposer = 518, + NewUserExperienceGiftOfferComposer = 3575, + NewUserExperienceNotCompleteComposer = 3639, + NoobnessLevelMessageComposer = 3738, + NoOwnedRoomsAlertMessageComposer = 2064, + NoSuchFlatComposer = 84, + NotEnoughBalanceMessageComposer = 3914, + NotificationDialogMessageComposer = 1992, + NowPlayingMessageComposer = 469, + ObjectAddMessageComposer = 1534, + ObjectDataUpdateMessageComposer = 2547, + ObjectRemoveMessageComposer = 2703, + ObjectsDataUpdateMessageComposer = 1453, + ObjectsMessageComposer = 1778, + ObjectUpdateMessageComposer = 3776, + OfferRewardDeliveredMessageComposer = 2125, + OfficialRoomsComposer = 2726, + OfficialSongIdMessageComposer = 1381, + OneWayDoorStatusMessageComposer = 2376, + OpenComposer = 1830, + OpenConnectionMessageComposer = 758, + OpenPetPackageRequestedMessageComposer = 2380, + OpenPetPackageResultMessageComposer = 546, + PerkAllowancesComposer = 2586, + PetAddedToInventoryComposer = 2101, + PetBreedingComposer = 1746, + PetBreedingResultComposer = 1553, + PetExperienceComposer = 2156, + PetFigureUpdateComposer = 1924, + PetInfoMessageComposer = 2901, + PetInventoryComposer = 3522, + PetLevelNotificationComposer = 859, + PetLevelUpdateComposer = 2824, + PetPlacingErrorComposer = 2913, + PetReceivedMessageComposer = 1111, + PetRemovedFromInventoryComposer = 3253, + PetRespectFailedComposer = 1130, + PetRespectNotificationComposer = 2788, + PetStatusUpdateComposer = 1907, + PetSupplementedNotificationComposer = 3441, + PetTrainingPanelComposer = 1164, + PhoneCollectionStateMessageComposer = 2890, + PingMessageComposer = 3928, + PlayListMessageComposer = 1748, + PlayListSongAddedMessageComposer = 1140, + PollContentsComposer = 2997, + PollErrorComposer = 662, + PollOfferComposer = 3785, + PopularRoomTagsResultComposer = 2012, + PostItPlacedComposer = 1501, + PostMessageMessageComposer = 2049, + PostThreadMessageComposer = 1862, + PresentOpenedMessageComposer = 56, + ProductOfferComposer = 3388, + PromoArticlesMessageComposer = 286, + PurchaseErrorMessageComposer = 1404, + PurchaseNotAllowedMessageComposer = 3770, + PurchaseOkMessageComposer = 869, + QuestCancelledMessageComposer = 3027, + QuestCompletedMessageComposer = 949, + QuestDailyMessageComposer = 1878, + QuestionAnsweredComposer = 2589, + QuestionComposer = 2665, + QuestionFinishedComposer = 1066, + QuestionInfoComposer = -1, + QuestMessageComposer = 230, + QuestsMessageComposer = 3625, + QuizDataMessageComposer = 2927, + QuizResultsMessageComposer = 2772, + RecyclerFinishedComposer = 468, + RecyclerPrizesComposer = 3164, + RecyclerStatusComposer = 3433, + RelationshipStatusInfoComposer = 2016, + RemainingMutePeriodComposer = 826, + RentableSpaceRentFailedMessageComposer = 1868, + RentableSpaceRentOkMessageComposer = 2046, + RentableSpaceStatusMessageComposer = 3559, + RequestSpamWallPostItMessageComposer = 2366, + RestoreClientMessageComposer = 426, + RoomAdErrorComposer = 1759, + RoomAdPurchaseInfoComposer = 2468, + RoomChatlogComposer = 3434, + RoomChatSettingsMessageComposer = 1191, + RoomDimmerPresetsComposer = 2710, + RoomEntryInfoComposer = -1, + RoomEntryInfoMessageComposer = 749, + RoomEntryTileMessageComposer = 1664, + RoomEventCancelComposer = 3479, + RoomEventComposer = 1840, + RoomFilterSettingsMessageComposer = 2937, + RoomFloorThicknessUpdatedComposer = 3786, + RoomForwardMessageComposer = 160, + RoomInfoUpdatedComposer = 3297, + RoomInviteComposer = 3870, + RoomInviteErrorComposer = 462, + RoomMessageNotificationMessageComposer = 1634, + RoomOccupiedTilesMessageComposer = 3990, + RoomPropertyMessageComposer = 2454, + RoomQueueStatusMessageComposer = 2208, + RoomRatingComposer = 482, + RoomReadyMessageComposer = 2031, + RoomSettingsDataComposer = 1498, + RoomSettingsErrorComposer = 2897, + RoomSettingsSavedComposer = 948, + RoomSettingsSaveErrorComposer = 1555, + RoomThumbnailUpdateResultComposer = 1927, + RoomUserQuestionAnsweredComposer = -1, + RoomUserRespectComposer = 2815, + RoomVisitsComposer = 1752, + RoomVisualizationSettingsComposer = 3547, + SanctionStatusComposer = 2221, + ScrSendKickbackInfoMessageComposer = 3277, + ScrSendUserInfoComposer = 954, + SeasonalCalendarDailyOfferMessageComposer = 1889, + SeasonalQuestsMessageComposer = 1122, + SecondsUntilMessageComposer = 3926, + SellablePetPalettesMessageComposer = 3331, + ShoutMessageComposer = 1036, + ShowEnforceRoomCategoryDialogComposer = 3896, + SleepMessageComposer = 1797, + SlideObjectBundleMessageComposer = 3207, + Str16258Composer = 1660, + Str16667Composer = 3099, + Str17054Composer = 416, + TalentLevelUpComposer = 638, + TalentTrackLevelMessageEvent = 1203, + TalentTrackMessageComposer = 3406, + TargetedOfferComposer = 119, + TargetedOfferNotFoundComposer = 1237, + ThreadMessagesMessageComposer = 509, + ThumbnailStatusMessageComposer = 3595, + TradeCloseWindowComposer = 1001, + TradeCompleteComposer = 2369, + TradingAcceptComposer = 2568, + TradingCloseComposer = 1373, + TradingConfirmationComposer = 2720, + TradingItemListComposer = 2024, + TradingNoSuchItemComposer = 2873, + TradingNotOpenComposer = 3128, + TradingOpenComposer = 2505, + TradingOpenFailedComposer = 217, + TradingOtherNotAllowedComposer = 1254, + TradingYouAreNotAllowedComposer = 3058, + TraxSongInfoMessageComposer = 3365, + TryPhoneNumberResultMessageComposer = 800, + TryVerificationCodeResultMessageComposer = 91, + UniqueMachineIdComposer = 1488, + Unknowncomposer1188 = 1437, + UnloadGameMessageComposer = 1715, + UnreadForumsCountMessageComposer = 2379, + UnseenItemsComposer = 2103, + UpdateMessageMessageComposer = 324, + UpdateStackHeightTileHeightComposer = 2816, + UpdateThreadMessageComposer = 2528, + UseObjectMessageComposer = 1774, + UserBadgesComposer = 1087, + UserBannedMessageComposer = 1683, + UserBcLimitsComposer = -1, + UserChangeMessageComposer = 3920, + UserChatlogComposer = 3377, + UserClassificationMessageComposer = 966, + UserEventCatsComposer = 3244, + UserFlatCatsComposer = 1562, + UserGameAchievementsMessageComposer = 2265, + UserNameChangedMessageComposer = 2182, + UserObjectComposer = 2725, + UserRemoveMessageComposer = 2661, + UserRightsMessageComposer = 411, + UsersComposer = 374, + UserSongDisksInventoryMessageComposer = 2602, + UserTagsMessageComposer = 1255, + UserTypingMessageComposer = 1717, + UserUnbannedFromRoomComposer = 3429, + UserUpdateComposer = 1640, + VoucherRedeemErrorMessageComposer = 714, + VoucherRedeemOkMessageComposer = 3336, + WardrobeMessageComposer = 3315, + WeeklyCompetitiveFriendsLeaderboardComposer = 3560, + WeeklyCompetitiveLeaderboardComposer = 3512, + WeeklyGameRewardComposer = 2641, + WeeklyGameRewardWinnersComposer = 3097, + WelcomeGiftChangeEmailResultComposer = 2293, + WelcomeGiftStatusComposer = 2707, + WhisperMessageComposer = 2704, + WiredConditionDataComposer = 1108, + WiredEffectDataComposer = 1434, + WiredRewardResultMessageComposer = 178, + WiredSavedComposer = 1155, + WiredTriggerDataComposer = 383, + WiredValidationErrorComposer = 156, + YouAreControllerMessageComposer = 780, + YouAreNotControllerMessageComposer = 2392, + YouAreOwnerMessageComposer = 339, + YouArePlayingGameMessageComposer = 448, + YouAreSpectatorMessageEvent = 1033, + YoutubeControlVideoMessageComposer = 1554, + YoutubeDisplayPlaylistsComposer = 1112, + YoutubeDisplayVideoMessageComposer = 1411, + SnowStormGameStartedComposer = 5000, + SnowStormQuePositionComposer = 5001, + SnowStormStartBlockTickerComposer = 5002, + SnowStormStartLobbyCounterComposer = 5003, + SnowStormUnusedAlertGenericComposer = 5004, + SnowStormLongDataComposer = 5005, + SnowStormGameEndedComposer = 5006, + SnowStormQuePlayerAddedComposer = 5008, + SnowStormPlayAgainComposer = 5009, + SnowStormGamesLeftComposer = 5010, + SnowStormQuePlayerRemovedComposer = 5011, + SnowStormGamesInformationComposer = 5012, + SnowStormLongData2Composer = 5013, + UnusedSnowstorm5014 = 5014, + SnowStormGameStatusComposer = 5015, + SnowStormFullGameStatusComposer = 5016, + SnowStormOnStageStartComposer = 5017, + SnowStormIntializeGameArenaViewComposer = 5018, + SnowStormRejoinPreviousRoomComposer = 5019, + UnknownSnowstorm5020 = 5020, + SnowStormLevelDataComposer = 5021, + SnowStormOnGameEndingComposer = 5022, + SnowStormUserChatMessageComposer = 5023, + SnowStormOnStageRunningComposer = 5024, + SnowStormOnStageEndingComposer = 5025, + SnowStormIntializedPlayersComposer = 5026, + SnowStormOnPlayerExitedArenaComposer = 5027, + SnowStormGenericErrorComposer = 5028, + SnowStormUserRematchedComposer = 5029 +} \ No newline at end of file diff --git a/Communication/Messages/Types/ClientMessage.cs b/Communication/Messages/Types/ClientMessage.cs new file mode 100644 index 0000000..d37673e --- /dev/null +++ b/Communication/Messages/Types/ClientMessage.cs @@ -0,0 +1,70 @@ +using System.Text; +using Tiger.Utils; + +namespace Tiger.Communication.Messages.Types; + +public class ClientMessage +{ + private readonly byte[] _packet; + private int _readerIndex; + + public short? Header { get; } + + public ClientMessage(byte[] packet) + { + _packet = packet; + _readerIndex = 0; + Header = ReadInt16(); + } + + public short? ReadInt16() + { + var raw = ReadBytes(2); + + if (raw.Length != 2) return null; + + return ByteUtils.GetInt16(raw); + } + + public int? ReadInt32() + { + var raw = ReadBytes(4); + + if (raw.Length != 4) return null; + + return ByteUtils.GetInt32(raw); + } + + public bool? ReadBoolean() + { + if (_packet.Length - _readerIndex == 0) return null; + + var result = _packet[_readerIndex]; + _readerIndex++; + return result == 1; + } + + public string? ReadString() + { + var strlen = ReadInt16(); + + if (strlen == null) return null; + + var raw = ReadBytes(strlen.Value); + + return raw.Length != strlen ? null : Encoding.UTF8.GetString(raw); + } + + private byte[] ReadBytes(int amount) + { + if (_packet.Length - _readerIndex < amount) + { + return Array.Empty(); + } + + var data = new byte[amount]; + Array.Copy(_packet, _readerIndex, data, 0, amount); + _readerIndex += amount; + return data; + } +} \ No newline at end of file diff --git a/Communication/Messages/Types/ServerMessage.cs b/Communication/Messages/Types/ServerMessage.cs new file mode 100644 index 0000000..4abd177 --- /dev/null +++ b/Communication/Messages/Types/ServerMessage.cs @@ -0,0 +1,48 @@ +using System.Text; +using Tiger.Utils; + +namespace Tiger.Communication.Messages.Types; + +public class ServerMessage +{ + private readonly List _packet = new(); + + public ServerMessage(short header) + { + AppendInt16(header); + } + + public void AppendInt16(short value) + { + _packet.AddRange(ByteUtils.Int16ToArray(value)); + } + + public void AppendInt32(int value) + { + _packet.AddRange(ByteUtils.Int32ToArray(value)); + } + + public void AppendUInt32(uint value) + { + _packet.AddRange(ByteUtils.UInt32ToArray(value)); + } + + public void AppendBoolean(bool value) + { + _packet.Add((byte)(value ? 1 : 0)); + } + + public void AppendString(string value) + { + AppendInt16((short)value.Length); + _packet.AddRange(Encoding.UTF8.GetBytes(value)); + } + + public byte[] ToArray() + { + var finalizedPacket = new List(); + finalizedPacket.AddRange(ByteUtils.Int32ToArray(_packet.Count)); + finalizedPacket.AddRange(_packet); + return finalizedPacket.ToArray(); + } +} \ No newline at end of file diff --git a/Game/Habbos/Habbo.cs b/Game/Habbos/Habbo.cs new file mode 100644 index 0000000..1489343 --- /dev/null +++ b/Game/Habbos/Habbo.cs @@ -0,0 +1,39 @@ +using MySqlConnector; + +namespace Tiger.Game.Habbos; + +public class Habbo +{ + public uint Id { get; } + public string Username { get; } + public string Email { get; } + public DateTime AccountCreated { get; } + public DateTime LastLogin { get; } + public string Motto { get; set; } + public string Figure { get; set; } + public string Gender { get; set; } + public byte Rank { get; set; } + public uint Credits { get; set; } + public bool Online { get; set; } + public uint HomeRoom { get; set; } + public uint AchievementScore { get; set; } + public uint GroupId { get; set; } + + public Habbo(MySqlDataReader reader) + { + Id = reader.GetUInt32("id"); + Username = reader.GetString("username"); + Email = reader.GetString("email"); + AccountCreated = reader.GetDateTime("account_created"); + LastLogin = reader.GetDateTime("last_login"); + Motto = reader.GetString("motto"); + Figure = reader.GetString("figure"); + Gender = reader.GetString("gender"); + Rank = reader.GetByte("rank"); + Credits = reader.GetUInt32("credits"); + Online = reader.GetBoolean("online"); + HomeRoom = reader.GetUInt32("home_room"); + AchievementScore = reader.GetUInt32("achievement_score"); + GroupId = reader.GetUInt32("group_id"); + } +} \ No newline at end of file diff --git a/Game/Habbos/HabboDao.cs b/Game/Habbos/HabboDao.cs new file mode 100644 index 0000000..5e11cbc --- /dev/null +++ b/Game/Habbos/HabboDao.cs @@ -0,0 +1,26 @@ +using Tiger.Storage; + +namespace Tiger.Game.Habbos; + +public class HabboDao : IHabboDao +{ + private readonly IDatabaseHelper _databaseHelper; + + public HabboDao(IDatabaseHelper databaseHelper) + { + _databaseHelper = databaseHelper; + } + + public async Task GetHabboBySsoAsync(string sso) + { + await using var dbConnection = _databaseHelper.GetConnection(); + var resultSet = + await dbConnection.GetResultSet("SELECT * FROM habbos WHERE sso_ticket = @sso LIMIT 1", ("@sso", sso)); + if (await resultSet.ReadAsync()) + { + return new Habbo(resultSet); + } + + return null; + } +} \ No newline at end of file diff --git a/Game/Habbos/IHabboDao.cs b/Game/Habbos/IHabboDao.cs new file mode 100644 index 0000000..68127a1 --- /dev/null +++ b/Game/Habbos/IHabboDao.cs @@ -0,0 +1,6 @@ +namespace Tiger.Game.Habbos; + +public interface IHabboDao +{ + Task GetHabboBySsoAsync(string sso); +} \ No newline at end of file diff --git a/Game/Settings/ISettingsDao.cs b/Game/Settings/ISettingsDao.cs new file mode 100644 index 0000000..7d8f5c2 --- /dev/null +++ b/Game/Settings/ISettingsDao.cs @@ -0,0 +1,6 @@ +namespace Tiger.Game.Settings; + +public interface ISettingsDao +{ + Task> GetSettingsAsync(); +} \ No newline at end of file diff --git a/Game/Settings/ISettingsManager.cs b/Game/Settings/ISettingsManager.cs new file mode 100644 index 0000000..7f5027c --- /dev/null +++ b/Game/Settings/ISettingsManager.cs @@ -0,0 +1,7 @@ +namespace Tiger.Game.Settings; + +public interface ISettingsManager +{ + Task ReloadSettingsAsync(); + T GetSetting(string key); +} \ No newline at end of file diff --git a/Game/Settings/SettingsDao.cs b/Game/Settings/SettingsDao.cs new file mode 100644 index 0000000..2308abc --- /dev/null +++ b/Game/Settings/SettingsDao.cs @@ -0,0 +1,27 @@ +using System.Collections.ObjectModel; +using Tiger.Storage; + +namespace Tiger.Game.Settings; + +public class SettingsDao : ISettingsDao +{ + private readonly IDatabaseHelper _databaseHelper; + + public SettingsDao(IDatabaseHelper databaseHelper) + { + _databaseHelper = databaseHelper; + } + + public async Task> GetSettingsAsync() + { + var settings = new Dictionary(); + await using var dbConnection = _databaseHelper.GetConnection(); + await using var resultSet = await dbConnection.GetResultSet("SELECT * FROM settings"); + while (await resultSet.ReadAsync()) + { + settings.Add(resultSet.GetString("skey"), resultSet.GetValue(resultSet.GetOrdinal("value"))); + } + + return new ReadOnlyDictionary(settings); + } +} \ No newline at end of file diff --git a/Game/Settings/SettingsManager.cs b/Game/Settings/SettingsManager.cs new file mode 100644 index 0000000..8228984 --- /dev/null +++ b/Game/Settings/SettingsManager.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Logging; + +namespace Tiger.Game.Settings; + +public class SettingsManager : ISettingsManager +{ + private IReadOnlyDictionary _settings; + private readonly ISettingsDao _settingsDao; + private readonly ILogger _logger; + + public SettingsManager(ISettingsDao settingsDao, ILogger logger) + { + _settingsDao = settingsDao; + _logger = logger; + _settings = new Dictionary(); + } + + public async Task ReloadSettingsAsync() + { + _settings = await _settingsDao.GetSettingsAsync(); + + _logger.LogInformation("Loaded {Count} settings", _settings.Count); + } + + public T GetSetting(string key) + { + if (!_settings.TryGetValue(key, out var value)) + throw new KeyNotFoundException($"No setting found with key '{key}'"); + + if (value is T castedValue) + return castedValue; + + throw new InvalidCastException($"Setting '{key}' is not of type '{typeof(T)}'"); + } +} \ No newline at end of file diff --git a/Networking/Game/IWebSocketServer.cs b/Networking/Game/IWebSocketServer.cs new file mode 100644 index 0000000..fa91f77 --- /dev/null +++ b/Networking/Game/IWebSocketServer.cs @@ -0,0 +1,6 @@ +namespace Tiger.Networking.Game; + +public interface IWebSocketServer +{ + Task Start(string uriPrefix); +} \ No newline at end of file diff --git a/Networking/Game/Sessions/GameSession.cs b/Networking/Game/Sessions/GameSession.cs new file mode 100644 index 0000000..ff8c277 --- /dev/null +++ b/Networking/Game/Sessions/GameSession.cs @@ -0,0 +1,28 @@ +using System.Net.WebSockets; +using Tiger.Communication.Messages.Interfaces; +using Tiger.Communication.Messages.Types; +using Tiger.Game.Habbos; + +namespace Tiger.Networking.Game.Sessions; + +public class GameSession +{ + public WebSocket WebSocket { get; } + public string SessionId { get; } + public Habbo? Habbo { get; set; } + + public GameSession(WebSocket webSocket, string sessionId) + { + WebSocket = webSocket; + SessionId = sessionId; + } + + public async Task SendComposerAsync(IMessageComposer composer) + { + var message = new ServerMessage((short)composer.Header); + composer.Compose(message); + var bytes = message.ToArray(); + await WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, + CancellationToken.None); + } +} \ No newline at end of file diff --git a/Networking/Game/Sessions/GameSessionManager.cs b/Networking/Game/Sessions/GameSessionManager.cs new file mode 100644 index 0000000..3a861be --- /dev/null +++ b/Networking/Game/Sessions/GameSessionManager.cs @@ -0,0 +1,56 @@ +using System.Collections.Concurrent; +using System.Net.WebSockets; + +namespace Tiger.Networking.Game.Sessions; + +public class GameSessionManager : IGameSessionManager +{ + private readonly ConcurrentDictionary _sessions = new(); + + public GameSession AddSession(WebSocket webSocket) + { + var sessionId = Guid.NewGuid().ToString(); + var gameSession = new GameSession(webSocket, sessionId); + _sessions.TryAdd(sessionId, gameSession); + return gameSession; + } + + public void RemoveSession(WebSocket webSocket) + { + var session = GetSession(webSocket); + if (session != null) + { + _sessions.TryRemove(session.SessionId, out _); + } + } + + public void CloseAll() + { + foreach (var session in _sessions.Values) + { + session.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server shutdown", CancellationToken.None); + } + } + + public GameSession? GetSession(WebSocket webSocket) + { + return (from session in _sessions where session.Value.WebSocket == webSocket select session.Value).FirstOrDefault(); + } + + public GameSession? GetSession(string sessionId) + { + return _sessions.TryGetValue(sessionId, out var gameSession) ? gameSession : null; + } + + public async Task CloseAsync(string reason, GameSession session) + { + if (_sessions.TryGetValue(session.SessionId, out var gameSession)) + { + if (gameSession.WebSocket.State == WebSocketState.Open) + { + await gameSession.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, reason, CancellationToken.None); + } + _sessions.TryRemove(session.SessionId, out _); + } + } +} \ No newline at end of file diff --git a/Networking/Game/Sessions/IGameSessionManager.cs b/Networking/Game/Sessions/IGameSessionManager.cs new file mode 100644 index 0000000..ee46f80 --- /dev/null +++ b/Networking/Game/Sessions/IGameSessionManager.cs @@ -0,0 +1,13 @@ +using System.Net.WebSockets; + +namespace Tiger.Networking.Game.Sessions; + +public interface IGameSessionManager +{ + GameSession AddSession(WebSocket webSocket); + void RemoveSession(WebSocket webSocket); + void CloseAll(); + GameSession? GetSession(WebSocket webSocket); + GameSession? GetSession(string sessionId); + Task CloseAsync(string reason, GameSession gameSession); +} \ No newline at end of file diff --git a/Networking/Game/WebSocketServer.cs b/Networking/Game/WebSocketServer.cs new file mode 100644 index 0000000..67b3d50 --- /dev/null +++ b/Networking/Game/WebSocketServer.cs @@ -0,0 +1,104 @@ +using System.Net; +using System.Net.WebSockets; +using Tiger.Communication.Messages; +using Tiger.Communication.Messages.Types; +using Tiger.Networking.Game.Sessions; +using Tiger.Utils; + +namespace Tiger.Networking.Game; + +public class WebSocketServer : IWebSocketServer +{ + private readonly HttpListener _httpListener = new(); + private readonly IGameSessionManager _gameSessionManager; + private readonly IMessageHandler _messageHandler; + + public WebSocketServer(IGameSessionManager gameSessionManager, IMessageHandler messageHandler) + { + _gameSessionManager = gameSessionManager; + _messageHandler = messageHandler; + } + + public async Task Start(string uriPrefix) + { + _httpListener.Prefixes.Add(uriPrefix); + _httpListener.Start(); + + Console.WriteLine($"Listening on {uriPrefix}..."); + + while (true) + { + var context = await _httpListener.GetContextAsync(); + + if (context.Request.IsWebSocketRequest) + { + ProcessRequestAsync(context); + } + else + { + context.Response.StatusCode = 400; + context.Response.Close(); + } + } + } + + private async void ProcessRequestAsync(HttpListenerContext context) + { + WebSocket? webSocket = null; + + try + { + var webSocketContext = await context.AcceptWebSocketAsync(null); + webSocket = webSocketContext.WebSocket; + + var session = _gameSessionManager.AddSession(webSocket); + Console.WriteLine($"WebSocket Session {session.SessionId} has connected."); + + await ReceiveMessageAsync(session.SessionId); + } + catch (Exception e) + { + Console.WriteLine($"WebSocket Session Error: {e.Message}"); + } + finally + { + if (webSocket != null) + { + _gameSessionManager.RemoveSession(webSocket); + webSocket.Dispose(); + } + } + } + + private async Task ReceiveMessageAsync(string sessionId) + { + var buffer = new byte[1024 * 4]; + while (_gameSessionManager.GetSession(sessionId) is { WebSocket.State: WebSocketState.Open } gameSession) + { + var result = await gameSession.WebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + var offset = 0; + + while (result.MessageType == WebSocketMessageType.Binary && offset + 4 <= result.Count) + { + var length = ByteUtils.GetInt32(new[] + { + buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3] + }); + offset += 4; + + if (offset + length > result.Count) break; + + var packet = new byte[result.Count]; + Array.Copy(buffer, offset, packet, 0, length); + offset += length; + + _messageHandler.TryHandleAsync(gameSession, new ClientMessage(packet)); + } + + if (result.MessageType == WebSocketMessageType.Close) + { + await gameSession.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + } + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..a7a581f --- /dev/null +++ b/Program.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Yaml; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Tiger.Communication.Messages; +using Tiger.Game.Habbos; +using Tiger.Game.Settings; +using Tiger.Networking.Game; +using Tiger.Networking.Game.Sessions; +using Tiger.Storage; +using Tiger.Utils; + +var quitEvent = new ManualResetEvent(false); + +Console.CancelKeyPress += (sender, e) => +{ + quitEvent.Set(); + e.Cancel = true; +}; + +var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddYamlFile("appsettings.yaml") + .Build(); + +var collection = new ServiceCollection(); +collection.AddLogging(builder => builder.AddConsole()); +collection.AddSingleton(configuration); +collection.AddSingleton(); +collection.AddSingleton(); +collection.AddSingleton(); +collection.AddSingleton(); +collection.AddSingleton(); +collection.AddSingleton(); +collection.AddSingleton(); +collection.RegisterMessageEvents(); + +var provider = collection.BuildServiceProvider(); + +await provider.GetRequiredService().ReloadSettingsAsync(); + +provider.GetRequiredService().Start($"http://{configuration["Network:Game:Ip"]}:{configuration["Network:Game:Port"]}/"); + +quitEvent.WaitOne(); \ No newline at end of file diff --git a/Storage/DatabaseConnection.cs b/Storage/DatabaseConnection.cs new file mode 100644 index 0000000..2cec2e6 --- /dev/null +++ b/Storage/DatabaseConnection.cs @@ -0,0 +1,38 @@ +using MySqlConnector; + +namespace Tiger.Storage; + +public class DatabaseConnection : IAsyncDisposable +{ + private readonly MySqlConnection _connection; + private readonly MySqlCommand _command; + + public DatabaseConnection(MySqlConnection connection) + { + _connection = connection; + _command = connection.CreateCommand(); + } + + public async Task GetResultSet(string query, params (string, object)[] mysqlParams) + { + await _connection.OpenAsync(); + + _command.CommandText = query; + + foreach (var mysqlParam in mysqlParams) + { + _command.Parameters.AddWithValue(mysqlParam.Item1, mysqlParam.Item2); + } + + var reader = await _command.ExecuteReaderAsync(); + _command.Parameters.Clear(); + return reader; + } + + public async ValueTask DisposeAsync() + { + await _connection.DisposeAsync(); + await _command.DisposeAsync(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Storage/DatabaseHelper.cs b/Storage/DatabaseHelper.cs new file mode 100644 index 0000000..e679d1d --- /dev/null +++ b/Storage/DatabaseHelper.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Configuration; +using MySqlConnector; + +namespace Tiger.Storage; + +public class DatabaseHelper : IDatabaseHelper +{ + private readonly string _connectionString; + + public DatabaseHelper(IConfiguration configuration) + { + var stringBuilder = new MySqlConnectionStringBuilder + { + Server = configuration["Database:Host"], + Port = uint.Parse(configuration["Database:Port"] ?? "3306"), + UserID = configuration["Database:Username"], + Password = configuration["Database:Password"], + Database = configuration["Database:Database"], + Pooling = true, + MinimumPoolSize = uint.Parse(configuration["Database:MinPool"] ?? "5"), + MaximumPoolSize = uint.Parse(configuration["Database:MaxPool"] ?? "15") + }; + + _connectionString = stringBuilder.ToString(); + } + + public DatabaseConnection GetConnection() + { + return new DatabaseConnection(new MySqlConnection(_connectionString)); + } +} \ No newline at end of file diff --git a/Storage/IDatabaseHelper.cs b/Storage/IDatabaseHelper.cs new file mode 100644 index 0000000..2e1a72d --- /dev/null +++ b/Storage/IDatabaseHelper.cs @@ -0,0 +1,6 @@ +namespace Tiger.Storage; + +public interface IDatabaseHelper +{ + DatabaseConnection GetConnection(); +} \ No newline at end of file diff --git a/TigerEmu.csproj b/TigerEmu.csproj new file mode 100644 index 0000000..2245c75 --- /dev/null +++ b/TigerEmu.csproj @@ -0,0 +1,27 @@ + + + + Exe + net7.0 + enable + enable + Tiger + 11 + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/Utils/ByteUtils.cs b/Utils/ByteUtils.cs new file mode 100644 index 0000000..05b4a07 --- /dev/null +++ b/Utils/ByteUtils.cs @@ -0,0 +1,29 @@ +namespace Tiger.Utils; + +public static class ByteUtils +{ + public static int GetInt32(byte[] array) + { + return (array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]; + } + + public static short GetInt16(byte[] array) + { + return (short)((array[0] << 8) | array[1]); + } + + public static IEnumerable UInt32ToArray(uint value) + { + return new[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }; + } + + public static IEnumerable Int32ToArray(int value) + { + return new[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value }; + } + + public static IEnumerable Int16ToArray(short value) + { + return new[] { (byte)(value >> 8), (byte)value }; + } +} \ No newline at end of file diff --git a/Utils/DependencyInjectionExtensions.cs b/Utils/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..262ca61 --- /dev/null +++ b/Utils/DependencyInjectionExtensions.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Tiger.Communication.Messages.Interfaces; + +namespace Tiger.Utils; + +public static class DependencyInjectionExtensions +{ + public static void RegisterMessageEvents(this IServiceCollection services) + { + // Get the assembly you are interested in (this could be any Assembly) + var assembly = Assembly.GetExecutingAssembly(); + + // Get all types that implement IMessageEvent and are not abstract + var messageEventTypes = assembly.GetTypes() + .Where(t => t.GetInterfaces().Contains(typeof(IMessageEvent)) && !t.IsAbstract); + + // Register each type with AddSingleton + foreach (var type in messageEventTypes) + { + services.AddSingleton(typeof(IMessageEvent), type); + } + } + +} \ No newline at end of file diff --git a/appsettings.yaml b/appsettings.yaml new file mode 100644 index 0000000..ed00b40 --- /dev/null +++ b/appsettings.yaml @@ -0,0 +1,12 @@ +Database: + Host: 127.0.0.1 + Port: 3306 + Username: root + Password: 123 + Database: tiger_db + MinPool: 5 + MaxPool: 15 +Network: + Game: + Ip: 127.0.0.1 + Port: 2096 \ No newline at end of file