Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mtasa:// protocol connect args #3211

Open
wants to merge 31 commits into
base: master
Choose a base branch
from

Conversation

Lpsd
Copy link
Member

@Lpsd Lpsd commented Oct 15, 2023

Changes

  • Allows arguments to be supplied to the mtasa:// protocol and received by the server in Lua via onPlayerConnect event parameters.
  • Adds an extra argument to redirectPlayer which allows you to pass a table to achieve the same result (all keys, values are automatically converted to strings, in order to form the 'argument path').
  • A fix for some of the SharedUtil tests (Use MTA temp path for client tests #3209) has been modified and merged into this PR.

Additional Info

Total length of arguments is limited to 1024 including / delimiter per key/value pair (arguments are treated as path string for 99% of lifetime)

Subject to change for typical url parameters e.g ?a=1&b=2

This feature is useful for the Discord RPC functionality, servers can create a party system using this or give rewards for joining via Discord (of course this can extend to other platforms).


Example

Have serverside script running:

SERVER_IP, SERVER_PORT, SERVER_PASS = "localhost", 22003, ""
DEFAULT_ARGS = {["foo"] = "abc", ["bar"] = "def"}

local storedArgs

function connect(nick, ip, username, serial, version, versionString, args)
    storedArgs = args
    iprint(args)
end
addEventHandler("onPlayerConnect", root, connect)

function redirect(player)
    redirectPlayer(player, SERVER_IP, SERVER_PORT, SERVER_PASS, (type(storedArgs) == "table") and storedArgs or DEFAULT_ARGS)
end
addCommandHandler("redirect", redirect)
Action Result
Player connects with mtasa://123.456.78.9:22003/foo/123/bar/456/baz/789 image
Player uses redirect command Same as above

Notes

  • To test mtasa:// protocol with debug build, edit registry Computer\HKEY_CLASSES_ROOT\mtasa\shell\open\command and set to your mtasa build path e.g "C:\mtasa-blue\Bin\Multi Theft Auto_d.exe"%1

To-do

  • Handle / in user provided keys/values, either remove character or URL encode? (/ is our delimiter so would affect converting to pairs)
    • / will be removed from user provided args
  • Fix argument parser issue when using std::unordered_map<type, type> for Lua args
  • Determine whether keys/values (protocol parameters) should be changed to ?a=1&b=2 format

@Inder00
Copy link
Contributor

Inder00 commented Oct 19, 2023

marge :shipit:

Server/mods/deathmatch/logic/CPlayer.h Outdated Show resolved Hide resolved
Server/mods/deathmatch/logic/CPlayer.h Outdated Show resolved Hide resolved
Client/core/CCommandFuncs.cpp Outdated Show resolved Hide resolved
Client/core/CCore.cpp Outdated Show resolved Hide resolved
Client/core/CCore.h Outdated Show resolved Hide resolved
Client/core/CCore.h Outdated Show resolved Hide resolved
@fresholia
Copy link
Contributor

Also it should work with connect console command, as I see i can't connect the game with connect command.

@Fernando-A-Rocha
Copy link
Contributor

What about also changing redirectPlayer?

@Fernando-A-Rocha
Copy link
Contributor

well done, lgtm

@Lpsd
Copy link
Member Author

Lpsd commented Nov 16, 2023

the redirectPlayer functionality has uncovered an issue with the argument parser which needs resolving before this can be merged. See discussion on dev discord

@Lpsd
Copy link
Member Author

Lpsd commented Nov 16, 2023

Also it should work with connect console command, as I see i can't connect the game with connect command.

Works fine for me, can you confirm how you're using the command and what errors you're seeing? @fresholia

@Lpsd
Copy link
Member Author

Lpsd commented Nov 18, 2023

We can't use std::unordered_map<std::string, std::string> to retrieve table arguments in a Lua definition (because a script providing the wrong type ie {1, 2, 3} will cause a crash, and I couldn't find a solution for this).

For this reason I've changed to use CLuaArgument in the unordered_map, as it can handle any value from the Lua side, then convert this to a string using the already available CLuaArgument::GetAsString method

However, I did have to implement a hash function for CLuaArgument in order to use it with std::unordered_map - could do with some opinions on this; not sure if it's correct (approach and implementation-wise). This is an issue we inevitably need to tackle at some point anyway (accepting tables of any value using new arg parser)...

struct Hash
{
size_t operator()(const CLuaArgument& a) const
{
SString str;
if (!a.GetAsString(str)) // If we can't convert to a string, then use pointer address instead
str = std::to_string((unsigned long long)(void**)&a);
return std::hash<int>{}(a.GetType()) ^ (std::hash<std::string>{}(str) << 1);
}
};

Client/core/CCore.cpp Outdated Show resolved Hide resolved
@tederis
Copy link
Member

tederis commented Nov 19, 2023

We can't use std::unordered_map<std::string, std::string> to retrieve table arguments in a Lua definition (because a script providing the wrong type ie {1, 2, 3} will cause a crash, and I couldn't find a solution for this).

For this reason I've changed to use CLuaArgument in the unordered_map, as it can handle any value from the Lua side, then convert this to a string using the already available CLuaArgument::GetAsString method

However, I did have to implement a hash function for CLuaArgument in order to use it with std::unordered_map - could do with some opinions on this; not sure if it's correct (approach and implementation-wise). This is an issue we inevitably need to tackle at some point anyway (accepting tables of any value using new arg parser)...

struct Hash
{
size_t operator()(const CLuaArgument& a) const
{
SString str;
if (!a.GetAsString(str)) // If we can't convert to a string, then use pointer address instead
str = std::to_string((unsigned long long)(void**)&a);
return std::hash<int>{}(a.GetType()) ^ (std::hash<std::string>{}(str) << 1);
}
};

I think it would be better to fix a crash instead of constructing hash related complicated things. I hope my PR #3246 will help.

@@ -286,6 +288,9 @@ class CCore : public CCoreInterface, public CSingleton<CCore>
bool IsUsingCustomStreamingMemorySize();
size_t GetStreamingMemory();

void SetProtocolConnectArgs(const std::string&& args) { m_strProtocolConnectArgs = args; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, const rvalue reference doesn't make sense. Even without a const qualifier it wont work as expected because in the function's body args is actually lvalue reference. So you should remove const and cast args to the rvalue explicitly: SetProtocolConnectArgs(std::string&& args) { m_strProtocolConnectArgs = std::move(args); }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this.
Also, that custom hash function for CLuaArgument isn't good at all, and should be avoided at all costs.
Actually, and kind of comparasion/hashing of it should be avoided.

@@ -190,6 +190,9 @@ class CCoreInterface
virtual bool IsUsingCustomStreamingMemorySize() = 0;
virtual size_t GetStreamingMemory() = 0;

virtual void SetProtocolConnectArgs(const std::string&& args) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same: const rvalue has no sense

@@ -44,6 +44,9 @@ class CPlayerJoinDataPacket final : public CPacket

bool IsOptionalUpdateInfoRequired() { return m_bOptionalUpdateInfoRequired; }

const char* GetConnectArgs() { return m_strConnectArgs; };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you return a string object reference?

@@ -1735,6 +1735,11 @@ void CGame::Packet_PlayerJoinData(CPlayerJoinDataPacket& Packet)
pPlayer->SetBitStreamVersion(Packet.GetBitStreamVersion());
g_pNetServer->SetClientBitStreamVersion(Packet.GetSourceSocket(), Packet.GetBitStreamVersion());

const char* connectArgs = Packet.GetConnectArgs();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This related to a CPlayerJoinDataPacket class problem: it's probably better to use a string.

@@ -335,6 +335,9 @@ class CPlayer final : public CPed, public CClient
const SString& GetQuitReasonForLog() { return m_strQuitReasonForLog; }
void SetQuitReasonForLog(const SString& strReason) { m_strQuitReasonForLog = strReason; }

void SetConnectArgs(const SString&& args) { m_strConnectArgs = args; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same problem with const rvalue ref

const char* connectArgs = Packet.GetConnectArgs();

if (std::strlen(connectArgs) > 0)
pPlayer->SetConnectArgs(std::move(Packet.GetConnectArgs()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to remove std::move here because Packet_PlayerJoinData(and even CGame) method doesn't own this string.

@@ -240,6 +240,20 @@ void CInputRPCs::ForceReconnect(NetBitStreamInterface& bitStream)

if (bitStream.Read(usPort))
{
char* szArgs = new char[MAX_PROTOCOL_CONNECT_ARGS_LENGTH + 1];
Copy link
Member

@tederis tederis Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we have a memory leak here. Because szArgs isn't deleted below.
Maybe a preallocated std::string is the solution? It's null terminated and guaranteed to contiguous since C++11 and we have non-const ::data member since C++17(please, use it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's even better to use bitStream.ReadString() (with the modification of a packet itself).

@StrixG StrixG added the enhancement New feature or request label Dec 21, 2023
@Dutchman101
Copy link
Member

Here's the last status update on this PR:

Dutchman101 — 01/07/2024 1:42 AM
If you address the final Dec 1 code reviews on #3211, it can be merged

lopsi — 01/07/2024 3:04 PM
there is more to do than that
I would like to use other arg format, ?foo=bar&baz=boz
I know it's possible, I already started working on it, but ran into few issues
MTA updater also uses this format in some weird way, I would like to change it too

@Nico8340
Copy link
Contributor

Nico8340 commented Apr 7, 2024

Any updates on this? Would be a really nice feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants