- Joined
- Oct 26, 2012
- Messages
- 2,357
- Reaction score
- 1,086
Hello,
(Please, read the whole thread before asking questions already in the thread, thank you)
Due to stuff happening (refer to http://forum.ragezone.com/f331/php-r38-project-aurora-1132388-post8920734/#post8920734 to read the story, questions about it can be asked in a PM or Discord, NOT in this thread!) I decided to rewrite Aurora to be better. The old source was messy and in some parts I was even confused by how things were done. Also, it was using SOME dependency injection stuff but it was still terrible. This doesn't mean the old source isn't used as much code is taken from the old source, making the time spent on it not for nothing.
The functionality isn't as big as it was before, but I decided to at least make a thread as things are going alright now. This project is a bit bigger than before, so I decided to call it Aurora PRO (10 cookies for anybody getting the reference... hint hint). All jokes aside, the project contains 3 versions, based on the same base (my own base I wrote myself), a v7 (there has never been a released v7 server) and R38 (both shockwave and flash compatible since this was the original project and the main project). The R38 is the main focus but the v7 one is something extra @Quackster, don't make Kepler working on v7 thanks) Okay, let's divide all the parts up:
Aurora Pro R38
Features:
- Login with SSO is working
- Catalog pages are working including items and deals (offers / packages whatever you want to call them)
- Friendlist is working (with incorrect user online check)
- Navigator frontpage is partly working (besides rooms, and categories)
CMS:
Will contain a CMS written from scratch in ASP DotNet Core (C#). Contain the style used around this time (HoloCMS/PHPRetro style). Might be multi template system and multi language system supported. No PHP cuz it's overused.
Extra information / cool stuff:
- Shockwave will have camera thanks to @Quackster for telling me how to enable it
- Shockwave might get battleball / snowstorm (maybe, just maybe...)
Screens:
Snippets:
FlashSSOTicketMessageEvent (flash packet 415):
PHP:class FlashSSOTicketMessageEvent : IPacket { public int Header => 415; private readonly IPlayerController _playerController; public FlashSSOTicketMessageEvent(IPlayerController playerController) { _playerController = playerController; } public async Task Handle(Session session, Event message) { string sso = message.GetString(); Player player = await _playerController.GetPlayerBySSO(sso); if (player != null) { session.Player = player; session.QueueMessage(AuthenticationOKMessageComposer.Compose()); session.Flush(); } else { await session.Disconnect(); } } }
PlayerController:
PHP:public class PlayerController : IPlayerController { private readonly IPlayerDao _dao; private readonly Dictionary<int, Player> _players; public PlayerController(IPlayerDao dao) { _dao = dao; _players = new Dictionary<int, Player>(); } public async Task<Player> GetPlayerById(int playerId) { if (_players.TryGetValue(playerId, out Player player)) { return player; } player = await _dao.GetPlayerById(playerId); _players.Add(playerId, player); return player; } public async Task<Player> GetPlayerBySSO(string ssoTicket) { return await _dao.GetPlayerBySSO(ssoTicket); } }
PlayerDao:
PHP:public class PlayerDao : IPlayerDao { private readonly DatabaseFactory _databaseFactory; public PlayerDao(DatabaseFactory databaseFactory) { _databaseFactory = databaseFactory; } public async Task<Player> GetPlayerById(int playerId) { Player player = null; await _databaseFactory.Select( "SELECT * FROM players WHERE id = [USER=1235]Player[/USER]Id LIMIT 1", async (reader) => { if (await reader.ReadAsync()) { player = new Player(reader); } }, ( [USER=1235]Player[/USER]Id", playerId)); return player; } public async Task<Player> GetPlayerBySSO(string ssoTicket) { Player player = null; await _databaseFactory.Select( "SELECT * FROM players WHERE sso_ticket = @ssoTicket LIMIT 1", async (reader) => { if (await reader.ReadAsync()) { player = new Player(reader); } }, ("@ssoTicket", ssoTicket)); return player; } }
Aurora v7
Features:
- Login is working
- Navigator nodes are working
- Own rooms list is working
- Going to private rooms is working (not 100% finished though)
- Loading items in a room is working (both floor and wall items)
- Catalog pages are working without items yet
CMS:
Won't contain a CMS, will purely be HTML with the layout used at that time. No login stuff or anything, v7 don't support SSO! (I might try to implement it if people really want to see it).
Extra information / cool stuff:
- Might be updated eventually to v9 with battleball
- Packets figured out reading the Lingo code (not going to mention names who helped me to prevent them being annoyed)
- Will contain a decently big CCT pack containing all the texts from various languages, badges and most of the room CCTs
Screens:
(I use the Dutch external texts; the emulator itself and database is English! Catalog is made by myself as close to Habbo's one was in terms of texts etc.)
Snippets:
None for now, no proirity. Most code besides packets is similar to R38 ones.
So, the main focus is on the R38 project. I might make a git repository soon, just not sure. Any other questions feel free to ask, ideas: feel free to ask (even if this means editing game files) and code tips feel free to point out.
Again: this is a rewrite which still uses lots of code from the old source, just done in a better way and slightly more clean.
dude, i wish you the best. finish this pls, dont keep opening projects and let them die
[COLOR=#000000][COLOR=#0000ff]public[/COLOR] [COLOR=#0000ff]class[/COLOR] [COLOR=#267f99]PlayerDao[/COLOR] : [COLOR=#267f99]IPlayerDao[/COLOR]
{
[COLOR=#0000ff]private[/COLOR] [COLOR=#0000ff]readonly[/COLOR] [COLOR=#267f99]IDatabaseFactory[/COLOR] [COLOR=#001080]_dbFactory[/COLOR];
[COLOR=#0000ff]public[/COLOR] [COLOR=#795e26]PlayerDao[/COLOR]([COLOR=#267f99]IDatabaseFactory[/COLOR] [COLOR=#001080]dbFactory[/COLOR])
{
[COLOR=#001080]_dbFactory[/COLOR] = [COLOR=#001080]dbFactory[/COLOR];
}
[COLOR=#0000ff]public[/COLOR] [COLOR=#0000ff]async[/COLOR] [COLOR=#267f99]Task[/COLOR]<[COLOR=#267f99]Player[/COLOR]> [COLOR=#795e26]GetPlayerById[/COLOR]([COLOR=#0000ff]int[/COLOR] [COLOR=#001080]playerId[/COLOR])
{
[COLOR=#0000ff]using[/COLOR] ([COLOR=#0000ff]var[/COLOR] [COLOR=#001080]connection[/COLOR] = [COLOR=#0000ff]await[/COLOR] [COLOR=#001080]_dbFactory[/COLOR].[COLOR=#795e26]GetOpenConnection[/COLOR]())
{
[COLOR=#af00db]return[/COLOR] [COLOR=#0000ff]await[/COLOR] [COLOR=#001080]connection[/COLOR].[COLOR=#795e26]QueryAsync[/COLOR]<[COLOR=#267f99]Player[/COLOR]>(
[COLOR=#a31515]"SELECT * FROM players WHERE id = [USER=1235]Player[/USER]Id"[/COLOR],
[COLOR=#0000ff]new[/COLOR] {[COLOR=#001080]playerId[/COLOR] = [COLOR=#001080]playerId[/COLOR]});
}
}
[COLOR=#0000ff]public[/COLOR] [COLOR=#0000ff]async[/COLOR] [COLOR=#267f99]Task[/COLOR]<[COLOR=#267f99]Player[/COLOR]> [COLOR=#795e26]GetPlayerBySSO[/COLOR]([COLOR=#0000ff]string[/COLOR] [COLOR=#001080]ssoTicket[/COLOR])
{
[COLOR=#0000ff]using[/COLOR] ([COLOR=#0000ff]var[/COLOR] [COLOR=#001080]connection[/COLOR] = [COLOR=#0000ff]await[/COLOR] [COLOR=#001080]_dbFactory[/COLOR].[COLOR=#795e26]GetOpenConnection[/COLOR]())
{
[COLOR=#af00db]return[/COLOR] [COLOR=#0000ff]await[/COLOR] [COLOR=#001080]connection[/COLOR].[COLOR=#795e26]QueryAsync[/COLOR]<[COLOR=#267f99]Player[/COLOR]>(
[COLOR=#a31515]"SELECT * FROM players WHERE sso_ticket = @ssoTicket"[/COLOR],
[COLOR=#0000ff]new[/COLOR] {[COLOR=#001080]ssoTicket[/COLOR] = [COLOR=#001080]ssoTicket[/COLOR]});
}
}
}
[/COLOR]
What database framework are you using? Imo this looks like an really bloaty and awful API just to fetch a user object from the database. I suggest using something like Dapper (You must be registered to see links), it would make your code a lot cleaner:
Code:... not gonna quote code
Removed the overload of async/await operations to speed up the emulator massively. This evening I will try to finish most of the Flash navigator maybe start on room entry.
I'd like to know how the "overload" of async/await operations can decrease the speed "massively". Could you explain it?
Also, if you're working with async methods. Name them accordingly, like: GetPlayerByIdAsync etc.
Care to give me some clear examples why you would want everything async...?
Care to give me some clear examples why you would want everything async...?
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;
namespace Nordlys.DependencyInjection
{
public class DependencyRegistrar
{
private readonly IServiceCollection serviceDescriptors;
public ServiceProvider ServiceProvider
{
get;
}
public DependencyRegistrar()
{
serviceDescriptors = new ServiceCollection();
serviceDescriptors.AddLogging(builder => builder.AddConsole());
AddClasses();
ServiceProvider = serviceDescriptors.BuildServiceProvider();
}
private void AddClasses()
{
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
if (type.GetCustomAttributes(typeof(SingletonAttribute), true).Length > 0)
{
serviceDescriptors.AddSingleton(type);
}
else if (type.GetCustomAttributes(typeof(TransientAttribute), true).Length > 0)
{
serviceDescriptors.AddTransient(type);
}
}
}
public T Resolve<T>()
{
return ServiceProvider.GetService<T>();
}
}
}
[Singleton]
public class SessionFactory
{
private int sessionIdCounter;
public SessionFactory()
{
sessionIdCounter = 0;
}
public Session CreateSession(IChannel channel)
{
return new Session(channel, Interlocked.Increment(ref sessionIdCounter));
}
}
[Singleton]
public class GameNetworkListener
{
private readonly ILogger<GameNetworkListener> logger;
private readonly ILogger<GameNetworkHandler> handlerLogger;
private readonly SessionFactory sessionFactory;
private readonly SessionController sessionController;
private ServerBootstrap bootstrap;
private IEventLoopGroup bossGroup;
private IEventLoopGroup workerGroup;
public GameNetworkListener(ILogger<GameNetworkListener> logger, ILogger<GameNetworkHandler> handlerLogger, SessionFactory sessionFactory, SessionController sessionController)
{
this.logger = logger;
this.handlerLogger = handlerLogger;
this.sessionFactory = sessionFactory;
this.sessionController = sessionController;
}
public async Task Start(int port)
{
bossGroup = new MultithreadEventLoopGroup(1);
workerGroup = new MultithreadEventLoopGroup(10);
bootstrap = new ServerBootstrap();
bootstrap
.Group(bossGroup, workerGroup)
.Channel<TcpServerSocketChannel>()
.ChildHandler(new ActionChannelInitializer<IChannel>(channel => {
channel.Pipeline.AddLast("decoder", new Decoder());
channel.Pipeline.AddLast("encoder", new Encoder());
channel.Pipeline.AddLast("handler", new GameNetworkHandler(handlerLogger, sessionFactory, sessionController));
}))
.ChildOption(ChannelOption.TcpNodelay, true)
.ChildOption(ChannelOption.SoKeepalive, true)
.ChildOption(ChannelOption.SoReuseaddr, true)
.ChildOption(ChannelOption.SoRcvbuf, 1024)
.ChildOption(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator(1024))
.ChildOption(ChannelOption.Allocator, UnpooledByteBufferAllocator.Default);
await bootstrap.BindAsync(port);
logger.LogInformation("Bound GameNetworkListener on port {0}, ready for connections", port);
}
public void Stop()
{
try
{
workerGroup.ShutdownGracefullyAsync().RunSynchronously();
bossGroup.ShutdownGracefullyAsync().RunSynchronously();
}
catch (ObjectDisposedException e)
{
Console.WriteLine(e);
}
}
}
public class GameNetworkHandler : SimpleChannelInboundHandler<ClientMessage>
{
private readonly ILogger<GameNetworkHandler> logger;
private readonly SessionFactory sessionFactory;
private readonly SessionController sessionController;
public GameNetworkHandler(ILogger<GameNetworkHandler> logger, SessionFactory sessionFactory, SessionController sessionController)
{
this.logger = logger;
this.sessionFactory = sessionFactory;
this.sessionController = sessionController;
}
public override void ChannelActive(IChannelHandlerContext context)
{
Session session = sessionFactory.CreateSession(context.Channel);
sessionController.AddSession(session);
logger.LogInformation("New connection from {0}", session.Channel.RemoteAddress);
}
public override void ChannelInactive(IChannelHandlerContext context)
{
sessionController.RemoveSession(context.Channel);
logger.LogInformation("New connection from {0}", context.Channel.RemoteAddress);
}
protected override void ChannelRead0(IChannelHandlerContext ctx, ClientMessage msg)
{
logger.LogInformation("Unregistered header {0}", msg.Id);
}
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
throw exception;
}
}
public static class Nordlys
{
static void Main(string[] args)
{
DependencyRegistrar dependencyRegistrar = new DependencyRegistrar();
GameNetworkListener listener = dependencyRegistrar.Resolve<GameNetworkListener>();
listener.Start(30000).Wait();
while (true) { }
}
}
Creating dependency injection scope settings with meta-programming is not a great idea.
I also believe you shouldn't handle Header IDs like that, which takes away your power to handle data in a multi-header scenario.
Good to see you picked up the development again! I suggest you to work on the version YOU like. It shouldn't matter if there's already an good emulator for version X. But that's just my opinion.
Now back on your code I do have a few points that should/could be improved.
- You have an asynchronous method in your GameNetworkListener that's no Async postfix. All async methods should've Async as a postfix (there are a few exceptions like events, HTTP controller methods and tests). I also suggest you to add ".ConfigureAwait(false)" on all async-calls too. Here is a nice article about it:You must be registered to see links.
- In your GameNetworkListener the Start method is async, but your Stop method isn't, why? You do call two async methods in it...
- The main method in Nordlys class runs the Start method, but then waits for it? Why? What's the reason to use async if you're going to block it anyway? If you do async, go async all the way. Tip: the main method can be marked as async too, then just do "await listener.Start'Async'(30000).ConfigureAwait(false);".
- Try to abstract away the use of new. It will make your life easier when unit testing (if you actually do such thing).
- There is a nice extension package that Microsoft provides for configuration purposes, see here:You must be registered to see links.
- Minor improvement, you can use ".Any()" on arrays (everything that implements IEnumerable) to validate if it has items.
- You catch an exception but are still using "Console.Writeline" to print it on the console. Why not use your logger that you used a few lines above it?
- Isn't it strange you have to give a single class two logger instances? I know the reason, but it weird.
- On the subject of constructor parameters. Having more than 3-4+ dependencies generaly means it does and needs too much. Make the class smaller.
Please read up on task-based async methods and how they work. It'll avoid problems, like deadlocks and other strange behavior, in the late run. Keep also in mind that debugging will and can be a pain in the butt when using async methods, because of jumping from one to the other place.