From fc35537827a5cf3a24e4cff359352331a554eee3 Mon Sep 17 00:00:00 2001 From: Batfoxkid Date: Thu, 16 Feb 2023 17:19:21 -0800 Subject: [PATCH] Added support for CNetChan::RequestFile --- gamedata/filenetwork.txt | 67 ++++++++++ scripting/filenetwork.sp | 209 ++++++++++++++++++++++++++---- scripting/include/filenetwork.inc | 51 +++++++- 3 files changed, 303 insertions(+), 24 deletions(-) diff --git a/gamedata/filenetwork.txt b/gamedata/filenetwork.txt index fb5ddc3..1023832 100644 --- a/gamedata/filenetwork.txt +++ b/gamedata/filenetwork.txt @@ -39,6 +39,13 @@ "windows" "\x55\x8B\xEC\x57\x8B\xF9\x8D\x8F\x94\x00\x00\x00\xE8\x2A\x2A\x2A\x2A\x85\xC0\x75\x2A\xB0\x01\x5F\x5D\xC2\x08\x00\x56\x8B\x75\x08\x85\xF6" // "SendFile: %s (ID %i)\n" } + "CNetChan::RequestFile" + { + "library" "engine" + "linux" "@_ZN8CNetChan11RequestFileEPKc" + "windows" "\x55\x8B\xEC\x83\xEC\x14\x53\x8B\xD9\x56\x89\x5D\xF4" + // "RequestFile: %s (ID %i)\n" + } "CNetChan::IsFileInWaitingList" { "library" "engine" @@ -47,6 +54,66 @@ // "CreateFragmentsFromFile: '%s' doesn't e" -> CNetChan::CreateFragmentsFromFile // Top Call -> CNetChan::IsFileInWaitingList } + "CGameClient::FileReceived" + { + "library" "engine" + "linux" "@_ZZN11CGameClient12FileReceivedEPKcj" + "windows" "\x55\x8B\xEC\x56\x8B\x75\x0C\x33\xD2\x57\x8B\xF9" + // "CGameClient::FileReceived: %s not wanted.\n" + } + "CGameClient::FileDenied" + { + "library" "engine" + "linux" "@_ZN11CGameClient10FileDeniedEPKcj" + "windows" "\x55\x8B\xEC\x8B\x01\xFF\x50\x44\x50\xFF\x75\x08\x68" + // "Downloading file '%s' from client %s failed.\n" + } + "CBaseClient::GetNetChannel" + { + "library" "engine" + "linux" "@_ZN11CBaseClient13GetNetChannelEv" + "windows" "\x8B\x81\xC0\x00\x00\x00" + // Good Luck + } + } + "Functions" + { + "CGameClient::FileReceived" + { + "signature" "CGameClient::FileReceived" + "callconv" "thiscall" + "return" "void" + "this" "address" + "arguments" + { + "fileName" + { + "type" "charptr" + } + "transferID " + { + "type" "int" + } + } + } + "CGameClient::FileDenied" + { + "signature" "CGameClient::FileDenied" + "callconv" "thiscall" + "return" "void" + "this" "address" + "arguments" + { + "fileName" + { + "type" "charptr" + } + "transferID " + { + "type" "int" + } + } + } } } } \ No newline at end of file diff --git a/scripting/filenetwork.sp b/scripting/filenetwork.sp index c4e2696..db6b436 100644 --- a/scripting/filenetwork.sp +++ b/scripting/filenetwork.sp @@ -14,20 +14,27 @@ enum struct FileEnum Handle Plugin; Function Func; any Data; + + int Id; } Handle SDKGetPlayerNetInfo; Handle SDKSendFile; +Handle SDKRequestFile; Handle SDKIsFileInWaitingList; +Handle SDKGetNetChannel; Address EngineAddress; -int TransferID; - -ArrayList FileListing; +// Sending +int TransferID; +ArrayList SendListing; bool InQuery[MAXPLAYERS+1]; Handle SendingTimer[MAXPLAYERS+1]; char CurrentlySending[MAXPLAYERS+1][PLATFORM_MAX_PATH]; +// Requesting +ArrayList RequestListing; + methodmap CNetChan { public CNetChan(int client) @@ -39,6 +46,10 @@ methodmap CNetChan { return SDKCall(SDKSendFile, this, filename, TransferID++); } + public int RequestFile(const char[] filename) + { + return SDKCall(SDKRequestFile, this, filename); + } public bool IsFileInWaitingList(const char[] filename) { return SDKCall(SDKIsFileInWaitingList, this, filename); @@ -57,6 +68,7 @@ public Plugin myinfo = public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { CreateNative("FileNet_SendFile", Native_SendFile); + CreateNative("FileNet_RequestFile", Native_RequestFile); CreateNative("FileNet_IsFileInWaitingList", Native_IsFileInWaitingList); RegPluginLibrary("filenetwork"); @@ -111,6 +123,17 @@ public void OnPluginStart() failed = true; } + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gamedata, SDKConf_Signature, "CNetChan::RequestFile"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_ByValue); + SDKRequestFile = EndPrepSDKCall(); + if(!SDKRequestFile) + { + LogError("[Gamedata] Could not find CNetChan::RequestFile"); + failed = true; + } + StartPrepSDKCall(SDKCall_Raw); PrepSDKCall_SetFromConf(gamedata, SDKConf_Signature, "CNetChan::IsFileInWaitingList"); PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); @@ -121,15 +144,61 @@ public void OnPluginStart() LogError("[Gamedata] Could not find CNetChan::IsFileInWaitingList"); failed = true; } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gamedata, SDKConf_Signature, "CBaseClient::GetNetChannel"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_ByValue); + SDKGetNetChannel = EndPrepSDKCall(); + if(!SDKGetNetChannel) + { + LogError("[Gamedata] Could not find CBaseClient::GetNetChannel"); + failed = true; + } + + DynamicDetour detour = DynamicDetour.FromConf(gamedata, "CGameClient::FileReceived"); + if(detour) + { + if(!detour.Enable(Hook_Post, OnFileReceived)) + { + LogError("[Gamedata] Failed to enable detour: CGameClient::FileReceived"); + failed = true; + } + + delete detour; + } + else + { + LogError("[Gamedata] Could not find CGameClient::FileReceived"); + failed = true; + } + + detour = DynamicDetour.FromConf(gamedata, "CGameClient::FileDenied"); + if(detour) + { + if(!detour.Enable(Hook_Post, OnFileDenied)) + { + LogError("[Gamedata] Failed to enable detour: CGameClient::FileDenied"); + failed = true; + } + + delete detour; + } + else + { + LogError("[Gamedata] Could not find CGameClient::FileDenied"); + failed = true; + } if(failed) ThrowError("Gamedata failed, see error logs"); - FileListing = new ArrayList(sizeof(FileEnum)); - RegAdminCmd("sm_filenetworktest", Command_Test, ADMFLAG_ROOT, "Test using send file"); + SendListing = new ArrayList(sizeof(FileEnum)); + RequestListing = new ArrayList(sizeof(FileEnum)); + RegAdminCmd("sm_filenet_send", Command_TestSend, ADMFLAG_ROOT, "Test using send file"); + RegAdminCmd("sm_filenet_request", Command_TestRequest, ADMFLAG_ROOT, "Test using request file"); } -public Action Command_Test(int client, int args) +public Action Command_TestSend(int client, int args) { char buffer[PLATFORM_MAX_PATH]; GetCmdArgString(buffer, sizeof(buffer)); @@ -155,30 +224,89 @@ public Action Command_Test(int client, int args) return Plugin_Handled; } +public Action Command_TestRequest(int client, int args) +{ + char buffer[PLATFORM_MAX_PATH]; + GetCmdArgString(buffer, sizeof(buffer)); + ReplaceString(buffer, sizeof(buffer), "\"", ""); + + CNetChan chan = CNetChan(client); + if(!chan) + { + ReplyToCommand(client, "Address invalid"); + } + else + { + chan.RequestFile(buffer); + ReplyToCommand(client, "Requested file from client"); + } + return Plugin_Handled; +} + public void OnClientDisconnect_Post(int client) { static FileEnum info; int match = -1; - while((match = FileListing.FindValue(client, FileEnum::Client)) != -1) + while((match = SendListing.FindValue(client, FileEnum::Client)) != -1) { - FileListing.GetArray(match, info); + SendListing.GetArray(match, info); + SendListing.Erase(match); + CallSentFileFinish(info, false); + } + + match = -1; + while((match = RequestListing.FindValue(client, FileEnum::Client)) != -1) + { + RequestListing.GetArray(match, info); + RequestListing.Erase(match); - FileListing.Erase(match); + CallRequestFileFinish(info, false); } delete SendingTimer[client]; CurrentlySending[client][0] = 0; } -public void OnNotifyPluginUnloaded(Handle plugin) +public MRESReturn OnFileReceived(Address address, DHookParam param) { - int match = -1; - while((match = FileListing.FindValue(plugin, FileEnum::Plugin)) != -1) + int id = param.Get(2); + CNetChan chan = SDKCall(SDKGetNetChannel, address); + + int length = RequestListing.Length; + for(int i; i < length; i++) { - FileListing.Erase(match); + static FileEnum info; + RequestListing.GetArray(i, info); + if(info.Id == id && chan == CNetChan(info.Client)) + { + RequestListing.Erase(i); + CallRequestFileFinish(info, true); + break; + } } + return MRES_Ignored; +} + +public MRESReturn OnFileDenied(Address address, DHookParam param) +{ + int id = param.Get(2); + CNetChan chan = SDKCall(SDKGetNetChannel, address); + + int length = RequestListing.Length; + for(int i; i < length; i++) + { + static FileEnum info; + RequestListing.GetArray(i, info); + if(info.Id == id && chan == CNetChan(info.Client)) + { + RequestListing.Erase(i); + CallRequestFileFinish(info, false); + break; + } + } + return MRES_Ignored; } public Action Timer_SendingClient(Handle timer, int client) @@ -194,14 +322,14 @@ public Action Timer_SendingClient(Handle timer, int client) return Plugin_Continue; // We finished this file - int length = FileListing.Length; + int length = SendListing.Length; for(int i; i < length; i++) { static FileEnum info; - FileListing.GetArray(i, info); + SendListing.GetArray(i, info); if(info.Client == client && StrEqual(info.Filename, CurrentlySending[client], false)) { - FileListing.Erase(i); + SendListing.Erase(i); CallSentFileFinish(info, true); break; } @@ -210,11 +338,11 @@ public Action Timer_SendingClient(Handle timer, int client) CurrentlySending[client][0] = 0; } - int length = FileListing.Length; + int length = SendListing.Length; for(int i; i < length; i++) { static FileEnum info; - FileListing.GetArray(i, info); + SendListing.GetArray(i, info); if(info.Client == client) { if(chan.SendFile(info.Filename)) @@ -224,7 +352,7 @@ public Action Timer_SendingClient(Handle timer, int client) else { // Failed reasons tend to be bad names, bad sizes, etc. - FileListing.Erase(i); + SendListing.Erase(i); CallSentFileFinish(info, false); } @@ -250,9 +378,23 @@ static void CallSentFileFinish(const FileEnum info, bool success) } } +static void CallRequestFileFinish(const FileEnum info, bool success) +{ + if(info.Func && info.Func != INVALID_FUNCTION) + { + Call_StartFunction(info.Plugin, info.Func); + Call_PushCell(info.Client); + Call_PushString(info.Filename); + Call_PushCell(info.Id); + Call_PushCell(success); + Call_PushCell(info.Data); + Call_Finish(); + } +} + void StartNative() { - if(!FileListing) + if(!SendListing) ThrowNativeError(SP_ERROR_NATIVE, "Please wait until OnAllPluginsLoaded"); } @@ -298,11 +440,11 @@ int GetNativeClient(int param) bool FileExistsForClient(int client, const char[] filename) { - int length = FileListing.Length; + int length = SendListing.Length; for(int i; i < length; i++) { static FileEnum info; - FileListing.GetArray(i, info); + SendListing.GetArray(i, info); if(info.Client == client) { if(StrEqual(info.Filename, filename, false)) @@ -325,12 +467,33 @@ public any Native_SendFile(Handle plugin, int params) info.Func = GetNativeFunction(3); info.Data = GetNativeCell(4); - FileListing.PushArray(info); + SendListing.PushArray(info); StartSendingClient(info.Client); return true; } +public any Native_RequestFile(Handle plugin, int params) +{ + StartNative(); + + FileEnum info; + info.Client = GetNativeClient(1); + GetNativeString(2, info.Filename, sizeof(info.Filename)); + + info.Plugin = plugin; + info.Func = GetNativeFunction(3); + info.Data = GetNativeCell(4); + + CNetChan chan = CNetChan(info.Client); + if(!chan) + ThrowError("Client address is invalid"); + + info.Id = chan.RequestFile(info.Filename); + RequestListing.PushArray(info); + return info.Id; +} + public any Native_IsFileInWaitingList(Handle plugin, int params) { StartNative(); diff --git a/scripting/include/filenetwork.inc b/scripting/include/filenetwork.inc index c0aa319..a51a939 100644 --- a/scripting/include/filenetwork.inc +++ b/scripting/include/filenetwork.inc @@ -7,7 +7,7 @@ #pragma newdecls required /** - * Called when parsing is started. + * Called when file sending is finished */ typeset FileNet_SendFileResult { @@ -31,6 +31,37 @@ typeset FileNet_SendFileResult function void(int client, const char[] file, bool success); }; +/** + * Ccalled when file request is finished + */ +typeset FileNet_RequestFileResult +{ + /** + * @note May be written to the downloads folder + * + * @param client Client index + * @param file Filepath of requested file + * @param id Request ID + * @param success If the file was sent + * @param data Value that was passed + * + * @noreturn + */ + function void(int client, const char[] file, int id, bool success, any data); + + /** + * @note May be written to the downloads folder + * + * @param client Client index + * @param file Filepath of requested file + * @param id Request ID + * @param success If the file was sent + * + * @noreturn + */ + function void(int client, const char[] file, int id, bool success); +}; + /** * If a file is currently in queue for that client * @@ -55,6 +86,23 @@ native bool FileNet_IsFileInWaitingList(int client, const char[] file); */ native bool FileNet_SendFile(int client, const char[] file, FileNet_SendFileResult callback = INVALID_FUNCTION, any data = 0); +/** + * Requests a file to be sent from the client + * + * @note Callback may not always be called, this is due to missing callbacks for cases: + * @note In this case, the callback will only be called when the client disconnects + * + * @param client Client index + * @param file Filepath of requested file + * @param callback Optional callback when file is done + * @param data Optional value to pass to the callback function + * + * @error Invalid client index, client is not in game, or a fake client + * + * @return Unique request ID given by the engine per session + */ +native int FileNet_RequestFile(int client, const char[] file, FileNet_RequestFileResult callback = INVALID_FUNCTION, any data = 0); + public SharedPlugin __pl_filenetwork = { name = "filenetwork", @@ -71,5 +119,6 @@ public void __pl_filenetwork_SetNTVOptional() { MarkNativeAsOptional("FileNet_SendFile"); MarkNativeAsOptional("FileNet_IsFileInWaitingList"); + MarkNativeAsOptional("FileNet_RequestFile"); } #endif