Helios - Experience the 2013 era! [C#, R63, Entity Framework]

Quackster

Administrator
Staff member
Administrator
Helios
Recreating the 2013 experience of Habbo Hotel
This is the development thread for Helios, a private Habbo Hotel server designed to emulate the 2013 version of Habbo using C# and .NET Core. The server currently uses the SWF release RELEASE63-201302211227-193109692, but it may be updated in the future. It has been in development in 2020, but I've been going off/on from it, development time spent on it hasn't been consistent, but I am to speed up development a lot this year.

Helios utilises DotNetty for asynchronous TCP sockets and networking. It also make use of Entity Framework for convenient database access and SQL queries without the need for manual query writing. In addition, we use Newtonsoft.Json for efficient JSON serializing and deserializing of various custom item attributes. This has been written from scratch and is all my work, it is not based off any existing source code.

This project comes in two, there's the server and there's also the website. The website is written in ASP.NET Core and will be recreating what Habbo Hotel was like back in 2013 - there's not much difference from what you remember of PHPRetro, but some notable changes were the multiple avatars per user, the quick registration, and the new login page.

The reason why I'm doing 2013 is because releases at the time did not do a very good job faithfully emulating this version, if you've heard of SwiftEmu/BcStorm then you will know what I'm talking about. I am to fully correct this and recreate this version to the best of my ability.

Source Code

The source code will be available eventually at the GitHub organisation released under GPL v3:

There will be three repositories, all using .NET 6.
  • Helios.Server
    • DotNetty
  • Helios.Web
    • ASP.NET Core
  • Helios.Storage
    • Entity Framework Core
Server

Features

User
  • User login with SSO including SSO expiry
  • User logout
  • User information retrieval
  • Seasonal currency support
Messenger
  • Add friend
  • Remove friend
  • Send friend request
  • Remove friend request
  • Accept friend request
  • Instant message friend
  • Update friend status when logging in/out/entering rooms
  • Follow friend
Rooms
  • Promotable room display supported
  • Random promotion to display on other navigator tabs
  • Enter room
  • Walk in room
  • Leave room
  • Multiple user support in room
  • Chatting in room
  • Item walking collision
  • Allow walkthrough user setting (can't save settings yet)
Catalogue
  • Catalogue discounts
  • Purchase items (including bulk purchase and discount calculation)
  • Trophy purchase supported
  • Post-its supported
  • Seasonal currency supported
Items
  • Place floor items
  • Place wall items
  • Move floor items
  • Move wall items
  • Pick up floor items
  • Pickup wall items
Habbo Club
  • Purchase Habbo Club
  • Renew Habbo Club
  • Habbo Club Gift system (relies on MySQL scheduler!)
Special Item Interactions
  • Stickies/Post-Its
  • Trophies
  • Rollers
  • Teleporters
  • Mannequins
I will eventually add a plugin system that is inspired by the Minecraft Spigot/Bukkit plugin system, where it has events and event priority etc!

Code Snippets

C#:
namespace Helios.Game
{
    public class StickieInteractor : Interactor
    {
        #region Overridden Properties

        public override ExtraDataType ExtraDataType => ExtraDataType.StringData;

        #endregion

        public StickieInteractor(Item item) : base(item) { }

        public override object GetJsonObject()
        {
            StickieExtraData extraData = null;

            try
            {
                extraData =  JsonConvert.DeserializeObject<StickieExtraData>(Item.Data.ExtraData);
            } catch { }

            if (extraData == null)
            {
                extraData = new StickieExtraData
                {
                    Message = string.Empty,
                    Colour = "FFFF33"
                };
            }

            return extraData;
        }

        public override object GetExtraData(bool inventoryView = false)
        {
            if (NeedsExtraDataUpdate)
            {
                NeedsExtraDataUpdate = false;
                ExtraData = ((StickieExtraData)GetJsonObject()).Colour;
            }

            return ExtraData;
        }
    }
}
C#:
using Helios.Game;
using Helios.Network.Streams;
using Helios.Storage.Database.Access;
using Helios.Storage.Database.Data;

namespace Helios.Messages.Incoming
{
    public class AcceptRequestsMessageEvent : IMessageEvent
    {
        public void Handle(Player player, Request request)
        {
            int friendsAccepted = request.ReadInt();
            var messenger = player.Messenger;

            for (int i = 0; i < friendsAccepted; i++)
            {
                int userId = request.ReadInt();

                if (!messenger.HasRequest(userId))
                    continue;

                if (messenger.Friends.Count >= messenger.MaxFriendsAllowed)
                    continue;

                var playerData = PlayerManager.Instance.GetDataById(userId);

                if (playerData == null)
                    continue;

                var targetMessenger = Messenger.GetMessengerData(userId);
                var targetFriend = new MessengerUser(playerData);

                targetMessenger.Friends.Add(messenger.MessengerUser);
                messenger.Friends.Add(targetFriend);

                targetMessenger.RemoveRequest(player.Details.Id);
                messenger.RemoveRequest(userId);

                var targetPlayer = PlayerManager.Instance.GetPlayerById(userId);

                if (targetPlayer != null)
                {
                    targetPlayer.Messenger.QueueUpdate(MessengerUpdateType.AddFriend, messenger.MessengerUser);
                    targetPlayer.Messenger.ForceUpdate();
                }

                MessengerDao.DeleteRequests(player.Details.Id, userId);
                MessengerDao.SaveFriend(new MessengerFriendData
                {
                    FriendId = userId,
                    UserId = player.Details.Id
                });
                MessengerDao.SaveFriend(new MessengerFriendData
                {
                    UserId = userId,
                    FriendId = player.Details.Id
                });

                messenger.QueueUpdate(MessengerUpdateType.AddFriend, targetFriend);
            }

            messenger.ForceUpdate();
        }
    }
}

Images

om4s9J0.png


gtUk57s.png

Website

Progress on the website has been slow, because I've been browsing old projects and repositories to get the HTML and steps exactly the same as what it once was. I've also been using YouTube videos and Habborator etc for first-hand sources of what 2013 was like, to get an accurate depiction and recreation. Anything I can't exactly recreate 1:1 - I've been faithful with my changes.

Features

Homepage
  • Login page

Register
  • Quick register steps
  • Suggest random but appropriate user figures
  • Input validation

Code Snippets

C#:
        [Route("/quickregister/step2")]
        public IActionResult Step2()
        {
            if (Request.Query.ContainsKey("p") &&
                Request.Query["p"] == "register")
            {
                return RedirectToAction("Start");
            }

            if (TempData.ContainsKey("Error"))
                ViewBag.Error = TempData["Error"];


            if (!HttpContext.Contains("registerYear") ||
                !HttpContext.Contains("registerMonth") ||
                !HttpContext.Contains("registerDay") ||
                !HttpContext.Contains("registerGender"))
            {
                TempData["Error"] = "fields";
                return RedirectToAction("Start");
            }

            return View("Step2");
        }

Images

SISGUyc.png


N6LV4oy.png


cZ5CfZp.png


KKZ6b9k.png
 
Last edited:
Will be interesting to see the first emulator competing with Hebbo/Privilege's proprietary (never released, only leaked, and never working due to licensing server..) flash server in feature-completeness! Have fun and good luck!

Are there new ways of undoing $ulake's AS3 shuffling/obfuscation, btw?

P.S.: consider using Postgres as database and the JSON Types instead for extra item data. Then upon data retrieval and storing you can leverage the built-in Postgres JSON functions for faster lookups.

efcore.pg handles this transparently.
 
Last edited:
Will be interesting to see the first emulator competing with Hebbo/Privilege's proprietary (never released, only leaked, and never working due to licensing server..) flash server in feature-completeness! Have fun and good luck!

Are there new ways of undoing $ulake's AS3 shuffling/obfuscation, btw?

P.S.: consider using Postgres as database and the JSON Types instead for extra item data. Then upon data retrieval and storing you can leverage the built-in Postgres JSON functions for faster lookups.

efcore.pg handles this transparently.

Well, you know that MySQL/MariaDB has built in JSON functions too? Since MySQL 8 iirc.

And yeah, very keen to have this incredibly complete, thanks everyone :)
 
Last edited:
Well, you know that MySQL/MariaDB has built in JSON functions too? Since MySQL 8 iirc.
Shit, you're right. Derp.

Do use the custom MySQL EF JSON helpers though, more documentation here, it'll drop the need to decode/encode JSON at application level (bypassing Newtonsoft.Json). Paired with ProxySQL cache that'll basicly result in direct RAM load without wasting any cycles*.

(* = on Linux platforms. NTFS performance compared to modern filesystems is a joke. .. that was back in '16 too, it's even better on Linux now with more optimizations and Zen 3 cores.)
 
Last edited:
I made the joke in Discord but I will repeat it here, soon we will be able to setup a virtual Habbo museum where users can access any client version and it will run entirely on servers built by qwek :3

Looking great so far, can't wait to eventually poke around in this. All the early Flash revisions are like a fever dream to me because i played the game so little during these years yet still have some vivid memories of it.
 
I'm kinda excited for this project to come out. I never really got into the beta versions/early flash creations of retros minus playing on them. I remember setting up a few early versions with Uber (I think) but could never get the client to work. Once this comes out it will be my first version of flash that I'm going to play around with. Seriously hyped!
 
I gotta be honest, I'm kind of not happy with this project - I've made a lot of mistakes and requires rewriting major parts of the server.

One of the problems is that I don't wait after sending packets which could lead to race conditions happening, and as a consequence I'd need to change large swarthes of the base to ensure packets are sent synchronously (using 'await' keyword).

Another problem is that I didn't make classes for incoming packets to easily identify client request data, I should have done this for the future plugin system I intend to make in future.

Oh well, that's what recoding/refactoring is for. 😅

I'm kinda excited for this project to come out. I never really got into the beta versions/early flash creations of retros minus playing on them. I remember setting up a few early versions with Uber (I think) but could never get the client to work. Once this comes out it will be my first version of flash that I'm going to play around with. Seriously hyped!
I'm glad that you're excited!

Once the website part of it is presentable I aim to have a demo hotel setup, but it will periodically reset as to not turn into a full-fledged hotel like what Classic Habbo turned into (it was orignally a test hotel for Havana that morphed into an actual hotel).
 
Last edited:
One of the problems is that I don't wait after sending packets which could lead to race conditions happening, and as a consequence I'd need to change large swarthes of the base to ensure packets are sent synchronously (using 'await' keyword)

As long as you always append full complete packets to the outgoing TCP buffer at once, this should be fine, right?
 
As long as you always append full complete packets to the outgoing TCP buffer at once, this should be fine, right?

I meant client-side race conditions.

For example, there was an issue where the rollers would inconsistently have an item zoom around for no reason whatsoever, even when the ticks are the same, and I realise that it's because the server doesn't wait until the packet is sent before it's written and flushed before the next roller packet. That's one problem that has arisen which I suspect is to do with my current implementation of sending packets.
 
Append the packets to a ConcurrentQueue<T> (y)

.. or BlockingCollection<T>, might be more efficient.

AFAICT both are FIFO, so no client-side race conditions due to unordered outgoing packets.
 
Last edited:
Append the packets to a ConcurrentQueue<T> (y)

.. or BlockingCollection<T>, might be more efficient.

AFAICT both are FIFO, so no client-side race conditions due to unordered outgoing packets.
I don't think you understand, I'll be rewriting the packet part of my server.
 
I don't think you understand, I'll be rewriting the packet part of my server.
Ah, oops. Read over the "before it's written and flushed" part. That's a weird issue. Is DotNetty deciding to flush the buffer without an explicit call? The only workaround for that, probably, is to only write fully complete packets to the DotNetty buffer.
 
Last edited:
Ah, oops. Read over the "before it's written and flushed" part. That's a weird issue. Is DotNetty deciding to flush the buffer without an explicit call? The only workaround for that, probably, is to only write fully complete packets to the DotNetty buffer.

It's because I'm don't "await" on the WriteAndFlushAsync() method, I think.
 
Some updates:
  • Currently recoding my emulator to support multiple characters per-user, which wasn't done in hindsight, so I can code support for this on the Helios Web system I am creating.
  • Figured out I can just make the Send method run synchronously to eliminate some client-sided race conditions.
Code:
/// <summary>
/// Send message composer
/// </summary>
public void Send(IMessageComposer composer)
{
    if (!composer.Composed)
    {
        composer.Composed = true;
        composer.Write();
    }

    try
    {
        Channel.WriteAndFlushAsync(composer)
            .RunSynchronously();
    }
    catch { }
}

  • Fixed the roller task having... weird behaviour.
Before:

Q2aN0dD.gif


After:

VNW8sQF.gif
 
Back
Top