Initial commit

main
Tiger 2024-02-17 21:23:33 +01:00
commit d5efddd10d
22 changed files with 3029 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.idea/
vendor/

1
Readme.md Normal file
View File

@ -0,0 +1 @@
# Tiger's Habbo Emulator written in PHP

11
composer.json Normal file
View File

@ -0,0 +1,11 @@
{
"require": {
"react/event-loop": "^1.5",
"react/http": "^1.9",
"react/stream": "^1.3",
"react/async": "^4.2",
"react/promise": "^3.1",
"react/socket": "^1.15",
"cboden/ratchet": "^0.4.4"
}
}

1653
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
src/Boot.php Normal file
View File

@ -0,0 +1,11 @@
<?php
require_once 'vendor/autoload.php';
spl_autoload_register(function(string $class) {
require_once str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
});
use Emulator\HabboEnvironment;
HabboEnvironment::getInstance()->initialize();

View File

@ -0,0 +1,10 @@
<?php
namespace Emulator\Core\Logging;
enum LogLevel {
case Debug;
case Info;
case Warn;
case Fatal;
}

View File

@ -0,0 +1,37 @@
<?php
namespace Emulator\Core\Logging;
class Logger {
private const array colorCodes = [
'r' => 31,
'b' => 34
];
public static function log(LogLevel $level, string $text): void {
if (preg_match_all('/\[([a-z]{1,2})]/', $text, $matches)) {
foreach ($matches[1] as $code) {
$colorCode = self::colorCodes[$code];
$text = str_replace(["[$code]", "[/$code]"], ["\033[{$colorCode}m", "\033[0m"], $text);
}
}
echo "[$level->name] $text" . PHP_EOL;
}
public static function debug(string $text): void {
self::log(LogLevel::Debug, $text);
}
public static function info(string $text): void {
self::log(LogLevel::Info, $text);
}
public static function warn(string $text): void {
self::log(LogLevel::Warn, $text);
}
public static function fatal(string $text): void {
self::log(LogLevel::Fatal, $text);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Emulator\Encoding;
class ByteEncoding {
public static function getInt32(array $raw): int{
return ($raw[0] << 24) | ($raw[1] << 16) | ($raw[2] << 8) | $raw[3];
}
public static function getInt16(array $raw): int {
return (($raw[0] << 8) | $raw[1]);
}
public static function int32ToArray(int $value): array {
return [$value >> 24, $value >> 16, $value >> 8, $value];
}
public static function int16ToArray(int $value): array {
return [$value >> 8, $value];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Emulator;
use Emulator\Core\Logging\Logger;
use Emulator\Messages\MessageHandler;
use Emulator\Network\Game\GameNetworkServer;
class HabboEnvironment {
private static ?HabboEnvironment $instance = null;
public function initialize(): void {
Logger::info("Starting up [b]HabboEmulator[/b] (PHP Edition) for [r]$_SERVER[USER][/r]");
MessageHandler::getInstance();
GameNetworkServer::getInstance()->start(2096);
}
public static function getInstance(): HabboEnvironment {
if (self::$instance === null)
self::$instance = new self();
return self::$instance;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Emulator\Messages\Incoming;
use Emulator\Encoding\ByteEncoding;
class ClientMessage {
private readonly array $packet;
private readonly int $header;
private int $readerIndex;
public function __construct(array $packet) {
$this->packet = $packet;
$this->readerIndex = 0;
$this->header = $this->readInt16();
}
private function readBytes(int $amount): array {
$bytes = [];
for ($i = 0; $i < $amount && $this->readerIndex < count($this->packet); $i++) {
$bytes[$i] = $this->packet[$this->readerIndex++];
}
return $bytes;
}
public function readInt16(): int {
return ByteEncoding::getInt16($this->readBytes(2));
}
public function readInt32(): int {
return ByteEncoding::getInt32($this->readBytes(4));
}
public function readBoolean(): bool {
return $this->packet[$this->readerIndex++] === 1;
}
public function readString(): string {
return call_user_func_array('pack', array_merge(['C*'], $this->readBytes($this->readInt16())));
}
public function getHeader(): int{
return $this->header;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Emulator\Messages\Incoming\Handshake;
use Emulator\Messages\Incoming\ClientMessage;
use Emulator\Messages\Incoming\Header;
use Emulator\Messages\Incoming\IMessageEvent;
use Emulator\Network\Game\Sessions\Session;
use Override;
class ReleaseVersionMessageEvent implements IMessageEvent {
#[Override] function handle(Session $session, ClientMessage $clientMessage): void {
}
#[Override] function getHeader(): Header {
return Header::ReleaseVersion;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Emulator\Messages\Incoming\Handshake;
use Emulator\Messages\Incoming\ClientMessage;
use Emulator\Messages\Incoming\Header;
use Emulator\Messages\Incoming\IMessageEvent;
use Emulator\Messages\Outgoing\Handshake\AuthenticatedComposer;
use Emulator\Network\Game\Sessions\Session;
use Override;
class SecurityTicketMessageEvent implements IMessageEvent {
#[Override] function handle(Session $session, ClientMessage $clientMessage): void {
$session->sendComposer(new AuthenticatedComposer());
}
#[Override] function getHeader(): Header {
return Header::ReleaseVersion;
}
}

View File

@ -0,0 +1,471 @@
<?php
namespace Emulator\Messages\Incoming;
enum Header : int
{
case AchievementList = 219;
case Authentication = -1;
case BotConfiguration = 1986;
case BotPickup = 3323;
case BotPlace = 1592;
case BotSkillSave = 2624;
case GetClubOffers = 3285;
case GetClubGiftInfo = 487;
case GetCatalogIndex = 1195;
case GetCatalogPage = 412;
case CatalogPurchase = 3492;
case CatalogPurchaseGift = 1411;
case GetProductOffer = 2594;
case ClientLatency = 295;
case ClientLatencyMeasure = 96;
case ClientPolicy = 26979;
case ClientPong = 2596;
case ClientToolbarToggle = 2313;
case ClientVariables = 1053;
case GetCurrentTimingCode = 2912;
case DesktopView = 105;
case GetBundleDiscountRuleset = 223;
case EventTracker = 3457;
case FindNewFriends = 516;
case FurnitureAliases = 3898;
case FurnitureFloorUpdate = 248;
case FurnitureMultistate = 99;
case FurniturePickup = 3456;
case FurniturePlace = 1258;
case FurniturePostitPlace = 2248;
case FurniturePostitSaveStickyPole = 3283;
case FurnitureRandomstate = 3617;
case FurnitureWallMultistate = 210;
case FurnitureWallUpdate = 168;
case GamesInit = 2914;
case GamesList = 741;
case Acceptgameinvite = 3802;
case Gameunloadedmessage = 3207;
case Getgameachievementsmessage = 2399;
case Getgamestatusmessage = 3171;
case Getusergameachievementsmessage = 389;
case Joinqueuemessage = 1458;
case Leavequeuemessage = 2384;
case Resetresolutionachievementmessage = 3144;
case Getweeklygamerewardwinners = 1054;
case Game2Getaccountgamestatusmessage = 11;
case Game2Checkgamedirectorystatusmessage = 3259;
case Game2Exitgamemessage = 1445;
case Game2Gamechatmessage = 2502;
case Game2Loadstagereadymessage = 2415;
case Game2Playagainmessage = 3196;
case Game2Requestfullstatusupdatemessage = 1598;
case Game2Getweeklyfriendsleaderboard = 1232;
case Game2Getweeklyleaderboard = 2565;
case GetGiftWrappingConfig = 418;
case GroupAdminAdd = 2894;
case GroupAdminRemove = 722;
case GroupCreateOptions = 798;
case GroupFavorite = 3549;
case GetForumStats = 3149;
case GetForumThreads = 873;
case GetForumsList = 436;
case GetForumMessages = 232;
case GetForumThread = 3900;
case GetUnreadForumsCount = 2908;
case ForumModerateMessage = 286;
case ForumModerateThread = 1397;
case ForumPostMessage = 3529;
case UpdateForumReadMarker = 1855;
case UpdateForumSettings = 2214;
case ForumUpdateThread = 3045;
case GroupInfo = 2991;
case GroupDelete = 1134;
case GroupMemberRemoveConfirm = 3593;
case GroupMemberRemove = 593;
case GroupMembers = 312;
case GroupMemberships = 367;
case GroupRequest = 998;
case GroupRequestAccept = 3386;
case GroupRequestDecline = 1894;
case GroupSettings = 1004;
case GroupParts = 813;
case GroupBuy = 230;
case GroupSaveInformation = 3137;
case GroupSaveBadge = 1991;
case GroupSaveColors = 1764;
case GroupSavePreferences = 3435;
case GroupBadges = 21;
case GroupUnblockMember = 2864;
case GetBadgePointsLimits = 1371;
case Requestabadge = 3077;
case Getisbadgerequestfulfilled = 1364;
case ItemClothingRedeem = 3374;
case ItemColorWheelClick = 2144;
case ItemDiceClick = 1990;
case ItemDiceClose = 1533;
case ItemDimmerSave = 1648;
case ItemDimmerSettings = 2813;
case ItemDimmerToggle = 2296;
case ItemExchangeRedeem = 3115;
case ItemPaint = 711;
case SetObjectData = 3608;
case ItemStackHelper = 3839;
case MarketplaceConfig = 2597;
case AcceptFriend = 137;
case MessengerChat = 3567;
case DeclineFriend = 2890;
case FollowFriend = 3997;
case MessengerFriends = 1523;
case MessengerInit = 2781;
case MessengerRelationships = 2138;
case SetRelationshipStatus = 3768;
case RemoveFriend = 1689;
case RequestFriend = 3157;
case GetFriendRequests = 2448;
case SendRoomInvite = 1276;
case HabboSearch = 1210;
case FriendListUpdate = 1419;
case ModToolUserInfo = 3295;
case GetUserFlatCats = 3027;
case NavigatorInit = 2110;
case NavigatorSearch = 249;
case NavigatorSearchClose = 1834;
case NavigatorSearchOpen = 637;
case NavigatorSearchSave = 2226;
case GetUserEventCats = 1782;
case NavigatorSettingsSave = 3159;
case NavigatorCategoryListMode = 1202;
case NavigatorDeleteSavedSearch = 1954;
case PetInfo = 2934;
case PetPickup = 1581;
case PetPlace = 2647;
case PetRespect = 3202;
case PetRide = 1036;
case PetMove = 3449;
case PetOpenPackage = 3698;
case PetSelected = 549;
case PetsBreed = 1638;
case PetCancelBreeding = 2713;
case PetConfirmBreeding = 3382;
case GetPetTrainingPanel = 2161;
case RecyclerPrizes = 398;
case RecyclerStatus = 1342;
case RecyclerItems = 2771;
case ReleaseVersion = 4000;
case CallForHelp = 1691;
case RoomAmbassadorAlert = 2996;
case RoomBanGive = 1477;
case RoomBanList = 2267;
case RoomBanRemove = 992;
case RoomCreate = 2752;
case RoomDelete = 532;
case RoomDoorbell = 1644;
case RoomEnter = 2312;
case RoomFavorite = 3817;
case RoomFavoriteRemove = 309;
case CanCreateRoom = 2128;
case CancelRoomEvent = 2725;
case EditRoomEvent = 3991;
case CompetitionRoomSearch = 433;
case ForwardToRandomPromotedRoom = 10;
case ForwardToSomeRoom = 1703;
case GetCategoriesWithUserCount = 3782;
case GetGuestRoom = 2230;
case GetOfficialRooms = 1229;
case GetPopularRoomTags = 826;
case GuildBaseSearch = 2930;
case MyFavouriteRoomsSearch = 2578;
case MyFrequentRoomHistorySearch = 1002;
case MyFriendsRoomSearch = 2266;
case MyGuildBasesSearch = 39;
case MyRecommendedRooms = 2537;
case MyRoomHistorySearch = 2264;
case MyRoomRightsSearch = 272;
case MyRoomsSearch = 2277;
case PopularRoomsSearch = 2758;
case RoomAdEventTabClicked = 2412;
case RoomAdEventTabViewed = 2668;
case RoomAdSearch = 2809;
case RoomTextSearch = 3943;
case RoomsWhereMyFriendsAre = 1786;
case RoomsWithHighestScoreSearch = 2939;
case SetRoomSessionTags = 3305;
case UpdateRoomThumbnail = 2468;
case RoomKick = 1320;
case RoomLike = 3582;
case RoomModel = 2300;
case GetOccupiedTiles = 1687;
case GetRoomEntryTile = 3559;
case RoomModelSave = 875;
case RoomMute = 3637;
case RoomMuteUser = 3485;
case RoomRightsGive = 808;
case RoomRightsList = 3385;
case RoomRightsRemove = 2064;
case RoomRightsRemoveAll = 2683;
case RoomRightsRemoveOwn = 3182;
case RoomSettings = 3129;
case RoomSettingsSave = 1969;
case RoomSettingsUpdateRoomCategoryAndTrade = 1265;
case RoomStaffPick = 1918;
case RoomFilterWords = 1911;
case RoomFilterWordsModify = 3001;
case Mysteryboxwaitingcanceledmessage = 2012;
case MysteryboxOpenTrophy = 3074;
case SecurityMachine = 2490;
case SecurityTicket = 2419;
case Trade = 1481;
case TradeAccept = 3863;
case TradeCancel = 2341;
case TradeClose = 2551;
case TradeConfirm = 2760;
case TradeItem = 3107;
case TradeItemRemove = 3845;
case TradeItems = 1263;
case TradeUnaccept = 1444;
case UnitAction = 2456;
case UnitChat = 1314;
case UnitChatShout = 2085;
case UnitChatWhisper = 1543;
case UnitDance = 2080;
case UnitDropHandItem = 2814;
case UnitGiveHanditem = 2941;
case UnitLook = 3301;
case UnitPosture = 2235;
case UnitSign = 1975;
case UnitTyping = 1597;
case UnitTypingStop = 1474;
case UnitWalk = 3320;
case UserBadges = 2769;
case UserBadgesCurrent = 2091;
case UserBadgesCurrentUpdate = 644;
case UserBots = 3848;
case UserCurrency = 273;
case UserEffectActivate = 2959;
case UserEffectEnable = 1752;
case UserFigure = 2730;
case UserFurniture = 3150; // sent when in room
case Requestfurniinventorywhennotinroom = 3500; // sent when not in room
case UserHomeRoom = 1740;
case UserInfo = 357;
case UserMotto = 2228;
case UserIgnored = 3878;
case UserPets = 3095;
case UserProfile = 3265;
case UserProfileByName = 2249;
case UserRespect = 2694;
case GetSoundSettings = 2388;
case UserSettingsCamera = 1461;
case UserSettingsChatStyle = 1030;
case UserSettingsInvites = 1086;
case UserSettingsOldChat = 1262;
case UserSettingsVolume = 1367;
case UserSubscription = 3166;
case GetWardrobe = 2742;
case SaveWardrobeOutfit = 800;
case UserTags = 17;
case PeerUsersClassification = 1160;
case UserClassification = 2285;
case VisitUser = 2970;
case WiredActionSave = 2281;
case WiredApplySnapshot = 3373;
case WiredConditionSave = 3203;
case WiredOpen = 768;
case WiredTriggerSave = 1520;
case GetItemData = 3964;
case OneWayDoorClick = 2765;
case RemoveWallItem = 3336;
case SetItemData = 3666;
case CatalogRedeemVoucher = 339;
case RoomTonerApply = 2880;
case FriendFurniConfirmLock = 3775;
case MannequinSaveName = 2850;
case MannequinSaveLook = 2209;
case PresentOpenPresent = 3558;
case CatalogSelectVipGift = 2276;
case UserIgnoreId = 3314;
case UserIgnore = 1117;
case UserUnignore = 2061;
case ModtoolRequestRoomInfo = 707;
case ModtoolChangeRoomSettings = 3260;
case ModtoolRequestUserChatlog = 1391;
case ModtoolRequestRoomChatlog = 2587;
case ModtoolSanctionAlert = 229;
case ModtoolSanctionBan = 2766;
case ModtoolSanctionKick = 2582;
case ModtoolSanctionTradelock = 3742;
case ModtoolAlertevent = 1840;
case ModtoolSanctionMute = 1945;
case ModtoolRequestUserRooms = 3526;
case ModtoolRoomAlert = 3842;
case ModtoolPreferences = 31;
case CloseIssueDefaultAction = 2717;
case CloseIssues = 2067;
case DefaultSanction = 1681;
case GetCfhChatlog = 211;
case ModtoolSanction = 1392;
case PickIssues = 15;
case ReleaseIssues = 1572;
case ConvertGlobalRoomId = 314;
case RequestSellItem = 848;
case RequestMarketplaceItemStats = 3288;
case MarketplaceSellItem = 3447;
case MarketplaceRequestOwnItems = 2105;
case MarketplaceTakeBackItem = 434;
case MarketplaceRedeemCredits = 2650;
case MarketplaceRequestOffers = 2407;
case MarketplaceBuyOffer = 1603;
case MarketplaceBuyTokens = 1866;
case CatalogRequesetPetBreeds = 1756;
case ApproveName = 2109;
case UnitGiveHanditemPet = 2768;
case PetSupplement = 749;
case FurnitureGroupInfo = 2651;
case AchievementResolutionOpen = 359;
case UsePetProduct = 1328;
case RemovePetSaddle = 186;
case TogglePetRiding = 1472;
case TogglePetBreeding = 3379;
case UnseenResetCategory = 3493;
case UnseenResetItems = 2343;
case CommunityGoalVoteComposer = 3536;
case GetPromoArticles = 1827;
case AcceptQuest = 3604;
case ActivateQuest = 793;
case CancelQuest = 3133;
case FriendRequestQuestComplete = 1148;
case GetCommunityGoalEarnedPrizes = 2688;
case GetCommunityGoalHallOfFame = 2167;
case GetCommunityGoalProgress = 1145;
case GetConcurrentUsersGoalProgress = 1343;
case GetConcurrentUsersReward = 3872;
case GetDailyQuest = 2486;
case GetQuests = 3333;
case GetSeasonalQuestsOnly = 1190;
case OpenQuestTracker = 2750;
case RedeemCommunityGoalPrize = 90;
case RejectQuest = 2397;
case StartCampaign = 1697;
case GetBonusRareInfo = 957;
case Craft = 3591;
case CraftSecret = 1251;
case GetCraftableProducts = 633;
case GetCraftingRecipe = 1173;
case GetCraftingRecipesAvailable = 3086;
case PhotoCompetition = 3959;
case PublishPhoto = 2068;
case PurchasePhoto = 2408;
case RenderRoom = 3226;
case RenderRoomThumbnail = 1982;
case RequestCameraConfiguration = 796;
case AddJukeboxDisk = 753;
case GetJukeboxPlaylist = 1435;
case GetNowPlaying = 1325;
case GetOfficialSongId = 3189;
case GetSongInfo = 3082;
case GetSoundMachinePlaylist = 3498;
case GetUserSongDisks = 2304;
case RemoveJukeboxDisk = 3050;
case InterstitialShown = 1109;
case GetInterstitial = 2519;
case ChangeUsername = 2977;
case CheckUsername = 3950;
case OpenCampaignCalendarDoorStaff = 3889;
case OpenCampaignCalendarDoor = 2257;
case BuildersClubPlaceRoomItem = 1051;
case BuildersClubPlaceWallItem = 462;
case BuildersClubQueryFurniCount = 2529;
case GetCatalogPageExpiration = 742;
case GetCatalogPageWithEarliestExp = 3135;
case GetDirectClubBuyAvailable = 801;
case GetHabboBasicMembershipExtendOffer = 603;
case GetHabboClubExtendOffer = 2462;
case GetIsOfferGiftable = 1347;
case GetLimitedOfferAppearingNext = 410;
case GetNextTargetedOffer = 596;
case GetRoomAdPurchaseInfo = 1075;
case GetSeasonalCalendarDailyOffer = 3257;
case GetTargetedOffer = 2487;
case MarkCatalogNewAdditionsPageOpened = 2150;
case PurchaseBasicMembershipExtension = 2735;
case PurchaseRoomAd = 777;
case PurchaseTargetedOffer = 1826;
case PurchaseVipMembershipExtension = 3407;
case RoomAdPurchaseInitiated = 2283;
case SetTargettedOfferState = 2041;
case ShopTargetedOfferViewed = 3483;
case HelperTalentTrack = 196;
case TalentTrackGetLevel = 2127;
case ForwardToACompetitionRoom = 172;
case ForwardToASubmittableRoom = 1450;
case ForwardToRandomCompetitionRoom = 865;
case GetIsUserPartOfCompetition = 2077;
case GetSecondsUntil = 271;
case RoomCompetitionInit = 1334;
case SubmitRoomToCompetition = 2595;
case VoteForRoom = 143;
case GetGift = 2436;
case ResetPhoneNumberState = 2741;
case SetPhoneNumberVerificationStatus = 1379;
case TryPhoneNumber = 790;
case VerifyCode = 2721;
case ControlYoutubeDisplayPlayback = 3005;
case GetYoutubeDisplayStatus = 336;
case SetYoutubeDisplayPlaylist = 2069;
case GoToFlat = 685;
case ChangeQueue = 3093;
case CallForHelpFromForumMessage = 1412;
case CallForHelpFromForumThread = 534;
case CallForHelpFromIm = 2950;
case CallForHelpFromPhoto = 2492;
case CallForHelpFromSelfie = 2755;
case ChatReviewGuideDecides = 3365;
case ChatReviewGuideDetached = 2501;
case ChatReviewGuideVote = 3961;
case ChatReviewSessionCreate = 3060;
case DeletePendingCallsForHelp = 3605;
case GetCfhStatus = 2746;
case GetFaqCategory = 3445;
case GetFaqText = 1849;
case GetGuideReportingStatus = 3786;
case GetPendingCallsForHelp = 3267;
case GetQuizQuestions = 1296;
case GuideSessionCreate = 3338;
case GuideSessionFeedback = 477;
case GuideSessionGetRequesterRoom = 1052;
case GuideSessionGuideDecides = 1424;
case GuideSessionInviteRequester = 234;
case GuideSessionIsTyping = 519;
case GuideSessionMessage = 3899;
case GuideSessionOnDutyUpdate = 1922;
case GuideSessionReport = 3969;
case GuideSessionRequesterCancels = 291;
case GuideSessionResolved = 887;
case PostQuizAnswers = 3720;
case SearchFaqs = 2031;
case PollAnswer = 3505;
case PollReject = 1773;
case PollStart = 109;
case PollVoteCounter = 6200;
case Disconnect = 2445;
case ScrGetKickbackInfo = 869;
case CompostPlant = 3835;
case HarvestPet = 1521;
case SetClothingChangeData = 924;
case GroupUnfavorite = 1820;
case NewUserExperienceGetGifts = 1822;
case NewUserExperienceScriptProceed = 1299;
case HandshakeInitDiffie = 3110;
case HandshakeCompleteDiffie = 773;
case WelcomeOpenGift = 2638;
case WelcomeGiftChangeEmail = 66;
case EmailGetStatus = 2557;
case EmailChange = 3965;
case ApproveAllMembershipRequests = 882;
case RentableSpaceCancelRent = 1667;
case RentableSpaceRent = 2946;
case RentableSpaceStatus = 872;
case TrackingPerformanceLog = 3230;
case TrackingLagWarningReport = 3847;
case RoomDirectoryRoomNetworkOpenConnection = 3736;
case RentableExtendRentOrBuyoutStripItem = 2115;
case RentableExtendRentOrBuyoutFurni = 1071;
case RentableGetRentOrBuyoutOffer = 2518;
}

View File

@ -0,0 +1,10 @@
<?php
namespace Emulator\Messages\Incoming;
use Emulator\Network\Game\Sessions\Session;
interface IMessageEvent {
function handle(Session $session, ClientMessage $clientMessage): void;
function getHeader(): Header;
}

View File

@ -0,0 +1,41 @@
<?php
namespace Emulator\Messages;
use Emulator\Core\Logging\Logger;
use Emulator\Messages\Incoming\ClientMessage;
use Emulator\Messages\Incoming\Handshake\ReleaseVersionMessageEvent;
use Emulator\Messages\Incoming\Handshake\SecurityTicketMessageEvent;
use Emulator\Messages\Incoming\Header;
use Emulator\Network\Game\Sessions\Session;
use SplObjectStorage;
class MessageHandler {
private static ?MessageHandler $instance = null;
private SplObjectStorage $handlers;
public function __construct() {
$this->handlers = new SplObjectStorage();
$this->handlers[Header::ReleaseVersion] = new ReleaseVersionMessageEvent();
$this->handlers[Header::SecurityTicket] = new SecurityTicketMessageEvent();
}
public function handlePacket(Session $session, ClientMessage $request): void {
if (($header = Header::tryFrom($request->getHeader()))) {
if ($this->handlers->contains($header)) {
$this->handlers[$header]->handle($session, $request);
} else {
Logger::warn("[{$request->getHeader()}] [$header->name] - Unregistered!");
}
} else {
Logger::warn("[{$request->getHeader()}] - Unregistered!");
}
}
public static function getInstance(): MessageHandler {
if (self::$instance === null)
self::$instance = new self();
return self::$instance;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Emulator\Messages\Outgoing\Handshake;
use Emulator\Messages\Outgoing\Header;
use Emulator\Messages\Outgoing\IMessageComposer;
use Emulator\Messages\Outgoing\ServerMessage;
use Override;
class AuthenticatedComposer implements IMessageComposer
{
#[Override] function compose(ServerMessage $message): void {
}
#[Override] function getHeader(): Header {
return Header::Authenticated;
}
}

View File

@ -0,0 +1,467 @@
<?php
namespace Emulator\Messages\Outgoing;
enum Header : int {
case AchievementList = 305;
case Authenticated = 2491;
case Authentication = -1;
case AvailabilityStatus = 2033;
case ClubOffers = 2405;
case CatalogPage = 804;
case CatalogPageList = 1032;
case CatalogPurchaseOk = 869;
case CatalogPurchaseError = 1404;
case CatalogPurchaseNotAllowed = 3770;
case ProductOffer = 3388;
case LimitedSoldOut = 377;
case CatalogPublished = 1866;
case CfhResultMessage = 3635;
case ClientLatency = 10;
case ClientPing = 3928;
case DesktopView = 122;
case BundleDiscountRuleset = 2347;
case FirstLoginOfDay = 793;
case FurnitureAliases = 1723;
case FurnitureData = 2547;
case FurnitureFloor = 1778;
case FurnitureFloorAdd = 1534;
case FurnitureFloorRemove = 2703;
case FurnitureFloorUpdate = 3776;
case FurnitureItemdata = 2202;
case FurnitureState = 2376;
case FurnitureGroupContextMenuInfo = 3293;
case FurniturePostitStickyPoleOpen = 2366;
case GameCenterAchievements = 2265;
case GameCenterGameList = 222;
case GameCenterStatus = 2893;
case GameCenterInArenaQueue = 872;
case GameCenterStopCounter = 3191;
case GameCenterUserLeftGame = 3138;
case GameCenterDirectoryStatus = 2246;
case GameCenterStartingGameFailed = 2142;
case GameCenterJoiningFailed = 1730;
case Gamestatusmessage = 3805;
case Gameachievements = 1689;
case Gameinvite = 904;
case Joiningqueuefailed = 3035;
case Joinedqueuemessage = 2260;
case Leftqueue = 1477;
case LoadGameUrl = 2624;
case Loadgame = 3654;
case Unloadgame = 1715;
case Achievementresolutioncompleted = 740;
case Achievementresolutionprogress = 3370;
case Achievementresolutions = 66;
case GenericAlert = 3801;
case ModeratorMessage = 2030;
case GenericError = 1600;
case GiftWrapperConfig = 2234;
case GroupBadges = 2402;
case GroupCreateOptions = 2159;
case GroupForumData = 3011;
case GroupForumList = 3001;
case GroupForumThreads = 1073;
case GroupForumPost = 2049;
case GroupForumPostThread = 1862;
case GroupForumThreadMessages = 509;
case GroupForumUnreadCount = 2379;
case GroupForumUpdateMessage = 324;
case GroupForumUpdateThread = 2528;
case GroupInfo = 1702;
case GroupList = 420;
case GroupMember = 265;
case GroupMembers = 1200;
case GroupMembersRefresh = 2445;
case GroupMemberRemoveConfirm = 1876;
case GroupPurchased = 2808;
case GroupSettings = 3965;
case GroupBadgeParts = 2238;
case GroupMembershipRequested = 1180;
case GroupDetailsChanged = 1459;
case GroupHabboJoinFailed = 762;
case GuildEditFailed = 3988;
case GuildMemberMgmtFailed = 818;
case ItemDimmerSettings = 2710;
case ItemStackHelper = 2816;
case ItemWall = 1369;
case ItemWallAdd = 2187;
case ItemWallRemove = 3208;
case ItemWallUpdate = 2009;
case MarketplaceConfig = 1823;
case MessengerAcceptFriends = 896;
case MessengerChat = 1587;
case MessengerFindFriends = 1210;
case MessengerFollowFailed = 3048;
case MessengerFriendNotification = 3082;
case MessengerFriends = 3130;
case MessengerInit = 1605;
case MessengerInstanceMessageError = 3359;
case MessengerInvite = 3870;
case MessengerInviteError = 462;
case MessengerMinimailCount = 2803;
case MessengerMinimailNew = 1911;
case MessengerRelationships = 2016;
case MessengerRequest = 2219;
case MessengerRequestError = 892;
case MessengerRequests = 280;
case MessengerSearch = 973;
case MessengerUpdate = 2800;
case ModerationTool = 2696;
case ModerationUserInfo = 2866;
case MotdMessages = 2035;
case NavigatorCategories = 1562;
case NavigatorCollapsed = 1543;
case NavigatorEventCategories = 3244;
case NavigatorLifted = 3104;
case NavigatorMetadata = 3052;
case NavigatorOpenRoomCreator = 2064;
case NavigatorSearch = 2690;
case NavigatorSearches = 3984;
case NavigatorSettings = 518;
case ThumbnailUpdateResult = 1927;
case CanCreateRoom = 378;
case CategoriesWithVisitorCount = 1455;
case CompetitionRoomsData = 3954;
case ConvertedRoomId = 1331;
case GuestRoomSearchResult = 52;
case NotificationList = 1992;
case NotificationOfferRewardDelivered = 2125;
case NotificationSimpleAlert = 5100;
case NotificationElementPointer = 1787;
case PetFigureUpdate = 1924;
case PetInfo = 2901;
case PetTrainingPanel = 1164;
case PetLevelUpdate = 2824;
case PetScratchFailed = 1130;
case PetOpenPackageRequested = 2380;
case PetOpenPackageResult = 546;
case PetBreeding = 1746;
case PetConfirmBreedingResult = 1625;
case PetGoToBreedingNestFailure = 2621;
case PetNestBreedingSuccess = 2527;
case PetConfirmBreedingRequest = 634;
case PetBreedingResult = 1553;
case RecyclerPrizes = 3164;
case RecyclerStatus = 3433;
case RecyclerFinished = 468;
case RoomBanList = 1869;
case RoomBanRemove = 3429;
case RoomCreated = 1304;
case RoomDoorbell = 2309;
case RoomDoorbellAccepted = 3783;
case RoomDoorbellRejected = 878;
case RoomEnter = 758;
case RoomEnterError = 899;
case RoomForward = 160;
case RoomHeightMap = 2753;
case RoomHeightMapUpdate = 558;
case RoomInfo = 687;
case RoomInfoOwner = 749;
case RoomModel = 1301;
case RoomModelBlockedTiles = 3990;
case RoomModelDoor = 1664;
case RoomModelName = 2031;
case RoomMuted = 2533;
case RoomPaint = 2454;
case RoomPromotion = 2274;
case RoomQueueStatus = 2208;
case RoomRights = 780;
case RoomRightsClear = 2392;
case RoomRightsList = 1284;
case RoomRightsListAdd = 2088;
case RoomRightsListRemove = 1327;
case RoomRightsOwner = 339;
case RoomRolling = 3207;
case RoomScore = 482;
case RoomSettings = 1498;
case RoomSettingsChat = 1191;
case RoomSettingsSave = 948;
case RoomSettingsSaveError = 1555;
case RoomInfoUpdated = 3297;
case RoomSpectator = 1033;
case RoomThickness = 3547;
case RoomGetFilterWords = 2937;
case RoomMessageNotification = 1634;
case RoomPopularTagsResult = 2012;
case InfoFeedEnable = 3284;
case SecurityMachine = 1488;
case MysteryBoxKeys = 2833;
case Gotmysteryboxprizemessage = 3712;
case Cancelmysteryboxwaitmessage = 596;
case Showmysteryboxwaitmessage = 3201;
case TradeAccepted = 2568;
case TradeClosed = 1373;
case TradeCompleted = 1001;
case TradeConfirmation = 2720;
case TradeListItem = 2024;
case TradeNotOpen = 3128;
case TradeOpen = 2505;
case TradeOpenFailed = 217;
case TradeOtherNotAllowed = 1254;
case TradeYouNotAllowed = 3058;
case TradeNoSuchItem = 2873;
case Unit = 374;
case UnitChangeName = 2182;
case UnitChat = 1446;
case UnitChatShout = 1036;
case UnitChatWhisper = 2704;
case UnitDance = 2233;
case UnitEffect = 1167;
case UnitExpression = 1631;
case UnitHandItem = 1474;
case UnitIdle = 1797;
case UnitInfo = 3920;
case UnitNumber = 2324;
case UnitRemove = 2661;
case UnitStatus = 1640;
case UnitTyping = 1717;
case UnseenItems = 2103;
case UserAchievementScore = 1968;
case UserBadges = 717;
case UserBadgesAdd = 2493;
case UserBadgesCurrent = 1087;
case UserBots = 3086;
case UserChangeName = 118;
case UserClothing = 1450;
case UserCredits = 3475;
case UserCurrency = 2018;
case ActivityPointNotification = 2275;
case UserFavoriteRoom = 2524;
case UserFavoriteRoomCount = 151;
case UserFigure = 2429;
case UserFurniture = 994;
case UserFurnitureAdd = 104;
case UserFurniturePostitPlaced = 1501;
case UserFurnitureRefresh = 3151;
case UserFurnitureRemove = 159;
case UserHomeRoom = 2875;
case RoomEventCancel = 3479;
case RoomEvent = 1840;
case UserIgnored = 126;
case UserIgnoredResult = 207;
case UserInfo = 2725;
case UserPerks = 2586;
case UserPermissions = 411;
case UserPetAdd = 2101;
case UserPetRemove = 3253;
case UserPets = 3522;
case UserProfile = 3898;
case UserRespect = 2815;
case UserSanctionStatus = 3679;
case UserSettings = 513;
case UserSubscription = 954;
case UserWardrobePage = 3315;
case UserClassification = 966;
case GetUserTags = 1255;
case WiredAction = 1434;
case WiredCondition = 1108;
case WiredError = 156;
case WiredOpen = 1830;
case WiredReward = 178;
case WiredSave = 1155;
case WiredTrigger = 383;
case PlayingGame = 448;
case FurnitureState2 = 3431;
case RemoveBotFromInventory = 233;
case AddBotToInventory = 1352;
case AchievementProgressed = 2107;
case ModtoolRoomInfo = 1333;
case ModtoolUserChatlog = 3377;
case ModtoolRoomChatlog = 3434;
case ModtoolVisitedRoomsUser = 1752;
case ModeratorActionResult = 2335;
case IssueDeleted = 3192;
case IssueInfo = 3609;
case IssuePickFailed = 3150;
case CfhChatlog = 607;
case ModeratorToolPreferences = 1576;
case LovelockFurniStart = 3753;
case LovelockFurniFriendComfirmed = 382;
case LovelockFurniFinished = 770;
case GiftReceiverNotFound = 1517;
case GiftOpened = 56;
case FloodControl = 566;
case RemainingMute = 826;
case UserEffectList = 340;
case UserEffectListAdd = 2867;
case UserEffectListRemove = 2228;
case UserEffectActivate = 1959;
case AvatarEffectSelected = 3473;
case ClubGiftInfo = 619;
case RedeemVoucherError = 714;
case RedeemVoucherOk = 3336;
case InClientLink = 2023;
case BotCommandConfiguration = 1618;
case BotSkillListUpdate = 69;
case BotForceOpenContextMenu = 296;
case HandItemReceived = 354;
case PetPlacingError = 2913;
case BotError = 639;
case MarketplaceSellItem = 54;
case MarketplaceItemStats = 725;
case MarketplaceOwnItems = 3884;
case MarketplaceCancelSale = 3264;
case MarketplaceItemPosted = 1359;
case MarketplaceItemsSearched = 680;
case MarketplaceAfterOrderStatus = 2032;
case CatalogReceivePetBreeds = 3331;
case CatalogApproveNameResult = 1503;
case ObjectsDataUpdate = 1453;
case PetExperience = 2156;
case CommunityGoalVoteEvent = 1435;
case PromoArticles = 286;
case CommunityGoalEarnedPrizes = 3319;
case CommunityGoalProgress = 2525;
case ConcurrentUsersGoalProgress = 2737;
case QuestDaily = 1878;
case QuestCancelled = 3027;
case QuestCompleted = 949;
case CommunityGoalHallOfFame = 3005;
case EpicPopup = 3945;
case SeasonalQuests = 1122;
case Quests = 3625;
case Quest = 230;
case BonusRareInfo = 1533;
case CraftableProducts = 1000;
case CraftingRecipe = 2774;
case CraftingRecipesAvailable = 2124;
case CraftingResult = 618;
case CameraPublishStatus = 2057;
case CameraPurchaseOk = 2783;
case CameraStorageUrl = 3696;
case CameraSnapshot = 463;
case CompetitionStatus = 133;
case InitCamera = 3878;
case ThumbnailStatus = 3595;
case AchievementNotification = 806;
case ClubGiftNotification = 2188;
case InterstitialMessage = 1808;
case RoomAdError = 1759;
case AvailabilityTime = 600;
case HotelClosedAndOpens = 3728;
case HotelClosesAndOpensAt = 2771;
case HotelWillCloseMinutes = 1050;
case HotelMaintenance = 1350;
case JukeboxPlaylistFull = 105;
case JukeboxSongDisks = 34;
case NowPlaying = 469;
case OfficialSongId = 1381;
case Playlist = 1748;
case PlaylistSongAdded = 1140;
case TraxSongInfo = 3365;
case UserSongDisksInventory = 2602;
case CheckUserName = 563;
case CfhSanction = 2782;
case CfhTopics = 325;
case CfhSanctionStatus = 2221;
case CampaignCalendarData = 2531;
case CampaignCalendarDoorOpened = 2551;
case BuildersClubFurniCount = 3828;
case BuildersClubSubscription = 1452;
case CatalogPageExpiration = 2668;
case CatalogEarliestExpiry = 2515;
case ClubGiftSelected = 659;
case TargetOfferNotFound = 1237;
case TargetOffer = 119;
case DirectSmsClubBuy = 195;
case RoomAdPurchase = 2468;
case NotEnoughBalance = 3914;
case LimitedOfferAppearingNext = 44;
case IsOfferGiftable = 761;
case ClubExtendedOffer = 3964;
case SeasonalCalendarOffer = 1889;
case CompetitionEntrySubmit = 1177;
case CompetitionVotingInfo = 3506;
case CompetitionTimingCode = 1745;
case CompetitionUserPartOf = 3841;
//case CompetitionNoOwnedRooms = 2064;
case CompetitionSecondsUntil = 3926;
case BadgePointLimits = 2501;
case BadgeRequestFulfilled = 2998;
case HelperTalentTrack = 3406;
case TalentTrackLevel = 1203;
case TalentTrackLevelUp = 638;
case UserBanned = 1683;
case BotReceived = 3684;
case PetLevelNotification = 859;
case PetReceived = 1111;
case ModerationCaution = 1890;
case YoutubeControlVideo = 1554;
case YoutubeDisplayPlaylists = 1112;
case YoutubeDisplayVideo = 1411;
case CfhDisabledNotify = 1651;
case Question = 2665;
case PollContents = 2997;
case PollError = 662;
case PollOffer = 3785;
case PollRoomResult = 5201;
case PollStartRoom = 5200;
case QuestionAnswered = 2589;
case QuestionFinished = 1066;
case CfhPendingCalls = 1121;
case GuideOnDutyStatus = 1548;
case GuideSessionAttached = 1591;
case GuideSessionDetached = 138;
case GuideSessionEnded = 1456;
case GuideSessionError = 673;
case GuideSessionInvitedToGuideRoom = 219;
case GuideSessionMessage = 841;
case GuideSessionPartnerIsTyping = 1016;
case GuideSessionRequesterRoom = 1847;
case GuideSessionStarted = 3209;
case GuideTicketCreationResult = 3285;
case GuideTicketResolution = 2674;
case GuideReportingStatus = 3463;
case HotelMergeNameChange = 1663;
case IssueCloseNotification = 934;
case QuizData = 2927;
case QuizResults = 2772;
case CfhPendingCallsDeleted = 77;
case CfhReply = 3796;
case ChatReviewSessionDetached = 30;
case ChatReviewSessionOfferedToGuide = 735;
case ChatReviewSessionResults = 3276;
case ChatReviewSessionStarted = 143;
case ChatReviewSessionVotingStatus = 1829;
case ScrSendKickbackInfo = 3277;
case PetStatus = 1907;
case GroupDeactivate = 3129;
case PetRespected = 2788;
case PetSupplement = 3441;
case NoobnessLevel = 3738;
case DisconnectReason = 4000;
case CanCreateRoomEvent = 2599;
case FavoriteGroupUdpate = 3403;
case NoSuchFlat = 84;
case RoomSettingsError = 2897;
case ShowEnforceRoomCategory = 3896;
case CustomUserNotification = 909;
case NewUserExperienceGiftOffer = 3575;
case RestoreClient = 426;
case FireworkChargeData = 5210;
case NewUserExperienceNotComplete = 3639;
case ConnectionError = 1004;
case AccountSafetyLockStatusChange = 1243;
case PhoneCollectionState = 2890;
case PhoneTryNumberResult = 800;
case PhoneTryVerificationCodeResult = 91;
case ExtendedProfileChanged = 876;
case WelcomeGiftChangeEmailResult = 2293;
case WelcomeGiftStatus = 2707;
case HandshakeInitDiffie = 1347;
case HandshakeCompleteDiffie = 3885;
case RentableSpaceRentOk = 2046;
case RentableSpaceStatus = 3559;
case RentableSpaceRentFailed = 1868;
case EmailStatus = 612;
case ChangeEmailResult = 1815;
case WeeklyGameReward = 2641;
case WeeklyGameRewardWinners = 3097;
case WeeklyCompetitiveLeaderboard = 3512;
case WeeklyCompetitiveFriendsLeaderboard = 3560;
case WeeklyGame2FriendsLeaderboard = 2270;
case WeeklyGame2Leaderboard = 2196;
case RentableFurniRentOrBuyoutOffer = 35;
case HandshakeIdentityAccount = 3523;
}

View File

@ -0,0 +1,8 @@
<?php
namespace Emulator\Messages\Outgoing;
interface IMessageComposer {
function compose(ServerMessage $message): void;
function getHeader(): Header;
}

View File

@ -0,0 +1,34 @@
<?php
namespace Emulator\Messages\Outgoing;
use Emulator\Encoding\ByteEncoding;
class ServerMessage {
private array $packet = [];
function __construct(int $header) {
$this->writeInt16($header);
}
public function writeInt16(int $value): void {
$this->packet = array_merge($this->packet, ByteEncoding::int16ToArray($value));
}
public function writeInt32(int $value): void {
$this->packet = array_merge($this->packet, ByteEncoding::int32ToArray($value));
}
public function writeBoolean(bool $value): void {
$this->packet[] = ($value ? 1 : 0);
}
public function writeString(string $value): void {
$this->writeInt16(strlen($value));
$this->packet = array_merge($this->packet, unpack('C*', $value));
}
public function getPacket(): array {
return $this->packet;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Emulator\Network\Game;
use Emulator\Core\Logging\Logger;
use Emulator\Encoding\ByteEncoding;
use Emulator\Messages\Incoming\ClientMessage;
use Emulator\Messages\MessageHandler;
use Emulator\Network\Game\Sessions\Session;
use Override;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\RFC6455\Messaging\MessageInterface;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\MessageComponentInterface;
use Ratchet\WebSocket\WsConnection;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Loop;
use React\Socket\SocketServer;
class GameNetworkServer implements MessageComponentInterface {
private static ?GameNetworkServer $instance = null;
private array $sessions;
public function start(int $port): void {
$loop = Loop::get();
$this->sessions = [];
$socketServer = new SocketServer("127.0.0.1:{$port}", loop: $loop);
new IoServer(
new HttpServer(
new WsServer($this)
),
$socketServer,
$loop
);
}
public static function getInstance(): GameNetworkServer {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
#[Override] function onOpen(ConnectionInterface $conn): void {
$this->sessions[$conn->resourceId] = new Session(new WsConnection($conn));
}
#[Override] function onClose(ConnectionInterface $conn): void {
unset($this->sessions[$conn->resourceId]);
}
#[Override] function onError(ConnectionInterface $conn, \Exception $e): void {
Logger::fatal($e->getMessage());
}
#[Override] public function onMessage(ConnectionInterface $conn, MessageInterface $msg): void {
$raw = $msg->getPayload();
$data = unpack('C*', $raw);
if (count($data) >= 6) {
$length = ByteEncoding::getInt32(array_slice($data, 1, 4));
$packet = array_slice($data, 4, $length);
MessageHandler::getInstance()->handlePacket($this->sessions[$conn->resourceId], new ClientMessage($packet));
}
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Emulator\Network\Game\Sessions;
use Emulator\Encoding\ByteEncoding;
use Emulator\Messages\Outgoing\IMessageComposer;
use Emulator\Messages\Outgoing\ServerMessage;
use Ratchet\RFC6455\Messaging\Frame;
use Ratchet\RFC6455\Messaging\Message;
use Ratchet\WebSocket\WsConnection;
readonly class Session {
private WsConnection $connection;
public function __construct(WsConnection $connection) {
$this->connection = $connection;
}
public function sendComposer(IMessageComposer $composer): void {
$headerData = pack('n', 2491);
$length = strlen($headerData);
$lengthData = pack('N', $length);
$binaryMessage = $lengthData . $headerData;
$frame = new Frame($binaryMessage, true, Frame::OP_BINARY);
$this->connection->send($frame);
}
function encode($text) {
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if ($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)
$header = pack('CCn', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCNN', $b1, 127, $length);
return $header.$text;
}
}

12
start.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
clear
MIN_VERSION="8.3.0";
PHP_VERSION=$(php -v | head -n 1 | awk '{print $2}')
if [[ "$PHP_VERSION" < "$MIN_VERSION" ]]; then
echo "Please make sure you have at least PHP ${MIN_VERSION}";
else
php src/Boot.php
fi