2022-02-27 07:53:44 -05:00
# include <STDInclude.hpp>
2022-07-06 11:48:40 -04:00
# include "GSC/Script.hpp"
2017-01-20 08:36:13 -05:00
namespace Components
{
mg_mgr Download : : Mgr ;
Download : : ClientDownload Download : : CLDownload ;
2017-05-15 15:57:45 -04:00
std : : vector < std : : shared_ptr < Download : : ScriptDownload > > Download : : ScriptDownloads ;
2017-01-20 08:36:13 -05:00
2017-05-25 14:18:20 -04:00
std : : thread Download : : ServerThread ;
bool Download : : Terminate ;
2022-06-28 03:26:43 -04:00
bool Download : : ServerRunning ;
2017-05-25 14:18:20 -04:00
2017-01-20 08:36:13 -05:00
# pragma region Client
2018-12-17 08:29:18 -05:00
void Download : : InitiateMapDownload ( const std : : string & map , bool needPassword )
2017-04-06 16:22:47 -04:00
{
2017-06-19 15:39:48 -04:00
Download : : InitiateClientDownload ( map , needPassword , true ) ;
2017-04-06 16:22:47 -04:00
}
2018-12-17 08:29:18 -05:00
void Download : : InitiateClientDownload ( const std : : string & mod , bool needPassword , bool map )
2017-01-20 08:36:13 -05:00
{
if ( Download : : CLDownload . running ) return ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-01-22 07:44:14 -05:00
{
Dvar : : Var ( " ui_dl_timeLeft " ) . set ( Utils : : String : : FormatTimeSpan ( 0 ) ) ;
Dvar : : Var ( " ui_dl_progress " ) . set ( " (0/0) % " ) ;
Dvar : : Var ( " ui_dl_transRate " ) . set ( " 0.0 MB/s " ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-01-20 08:36:13 -05:00
Command : : Execute ( " openmenu mod_download_popmenu " , false ) ;
2017-06-19 15:39:48 -04:00
if ( needPassword )
{
std : : string pass = Dvar : : Var ( " password " ) . get < std : : string > ( ) ;
2017-06-26 07:04:30 -04:00
if ( pass . empty ( ) )
2017-06-19 15:39:48 -04:00
{
// shouldn't ever happen but this is safe
Party : : ConnectError ( " A password is required to connect to this server! " ) ;
return ;
}
2017-07-02 08:16:06 -04:00
2017-07-02 13:00:28 -04:00
Download : : CLDownload . hashedPassword = Utils : : String : : DumpHex ( Utils : : Cryptography : : SHA256 : : Compute ( pass ) , " " ) ;
2017-06-19 15:39:48 -04:00
}
2017-01-20 08:36:13 -05:00
Download : : CLDownload . running = true ;
2017-04-06 16:22:47 -04:00
Download : : CLDownload . isMap = map ;
2017-01-20 08:36:13 -05:00
Download : : CLDownload . mod = mod ;
Download : : CLDownload . terminateThread = false ;
Download : : CLDownload . totalBytes = 0 ;
Download : : CLDownload . lastTimeStamp = 0 ;
Download : : CLDownload . downBytes = 0 ;
Download : : CLDownload . timeStampBytes = 0 ;
2017-06-19 15:44:59 -04:00
Download : : CLDownload . isPrivate = needPassword ;
2017-01-20 08:36:13 -05:00
Download : : CLDownload . target = Party : : Target ( ) ;
Download : : CLDownload . thread = std : : thread ( Download : : ModDownloader , & Download : : CLDownload ) ;
}
2018-12-17 08:29:18 -05:00
bool Download : : ParseModList ( ClientDownload * download , const std : : string & list )
2017-01-20 08:36:13 -05:00
{
if ( ! download ) return false ;
download - > files . clear ( ) ;
std : : string error ;
json11 : : Json listData = json11 : : Json : : parse ( list , error ) ;
2017-04-06 16:22:47 -04:00
if ( ! error . empty ( ) | | ! listData . is_array ( ) )
{
2022-06-12 17:07:53 -04:00
Logger : : Print ( " Error: {} \n " , error ) ;
2017-04-06 16:22:47 -04:00
return false ;
}
2017-01-20 08:36:13 -05:00
download - > totalBytes = 0 ;
for ( auto & file : listData . array_items ( ) )
{
if ( ! file . is_object ( ) ) return false ;
auto hash = file [ " hash " ] ;
auto name = file [ " name " ] ;
auto size = file [ " size " ] ;
if ( ! hash . is_string ( ) | | ! name . is_string ( ) | | ! size . is_number ( ) ) return false ;
Download : : ClientDownload : : File fileEntry ;
fileEntry . name = name . string_value ( ) ;
fileEntry . hash = hash . string_value ( ) ;
fileEntry . size = static_cast < size_t > ( size . number_value ( ) ) ;
if ( ! fileEntry . name . empty ( ) )
{
download - > files . push_back ( fileEntry ) ;
download - > totalBytes + = fileEntry . size ;
}
}
return true ;
}
void Download : : DownloadHandler ( mg_connection * nc , int ev , void * ev_data )
{
http_message * hm = reinterpret_cast < http_message * > ( ev_data ) ;
Download : : FileDownload * fDownload = reinterpret_cast < Download : : FileDownload * > ( nc - > mgr - > user_data ) ;
if ( ev = = MG_EV_CONNECT )
{
if ( hm - > message . p )
{
2017-05-14 14:14:52 -04:00
fDownload - > downloading = true ;
2017-01-20 08:36:13 -05:00
return ;
}
}
2017-07-05 03:13:51 -04:00
if ( ev = = MG_EV_CLOSE )
{
fDownload - > downloading = false ;
return ;
}
2017-01-20 08:36:13 -05:00
if ( ev = = MG_EV_RECV )
{
size_t bytes = static_cast < size_t > ( * reinterpret_cast < int * > ( ev_data ) ) ;
2019-12-25 17:03:43 -05:00
Download : : DownloadProgress ( fDownload , bytes ) ;
2017-01-20 08:36:13 -05:00
}
if ( ev = = MG_EV_HTTP_REPLY )
{
nc - > flags | = MG_F_CLOSE_IMMEDIATELY ;
fDownload - > buffer = std : : string ( hm - > body . p , hm - > body . len ) ;
fDownload - > downloading = false ;
return ;
}
}
bool Download : : DownloadFile ( ClientDownload * download , unsigned int index )
{
if ( ! download | | download - > files . size ( ) < = index ) return false ;
auto file = download - > files [ index ] ;
std : : string path = download - > mod + " / " + file . name ;
2017-04-06 16:22:47 -04:00
if ( download - > isMap )
{
path = " usermaps/ " + path ;
}
2017-01-20 08:36:13 -05:00
if ( Utils : : IO : : FileExists ( path ) )
{
std : : string data = Utils : : IO : : ReadFile ( path ) ;
if ( data . size ( ) = = file . size & & Utils : : String : : DumpHex ( Utils : : Cryptography : : SHA256 : : Compute ( data ) , " " ) = = file . hash )
{
download - > totalBytes + = file . size ;
return true ;
}
}
2022-06-13 13:59:32 -04:00
auto host = " http:// " + download - > target . getString ( ) ;
auto fastHost = Dvar : : Var ( " sv_wwwBaseUrl " ) . get < std : : string > ( ) ;
2017-07-05 03:13:51 -04:00
if ( Utils : : String : : StartsWith ( fastHost , " https:// " ) )
{
download - > thread . detach ( ) ;
download - > clear ( ) ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-07-05 03:13:51 -04:00
{
Command : : Execute ( " closemenu mod_download_popmenu " ) ;
Party : : ConnectError ( " HTTPS not supported for downloading! " ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2017-07-05 03:13:51 -04:00
2022-06-13 13:59:32 -04:00
return false ;
2017-07-05 03:13:51 -04:00
}
2022-06-13 13:59:32 -04:00
if ( ! Utils : : String : : StartsWith ( fastHost , " http:// " ) )
2017-07-05 03:13:51 -04:00
{
fastHost = " http:// " + fastHost ;
}
2017-06-12 19:22:39 -04:00
std : : string url ;
// file directory for fasthost looks like this
// /-usermaps
// /-mp_test
// -mp_test.ff
// -mp_test.iwd
// /-mp_whatever
// /-mp_whatever.ff
// /-mods
// /-mod1
// -mod1.iwd
// -mod.ff
// /-mod2
// ...
if ( Dvar : : Var ( " sv_wwwDownload " ) . get < bool > ( ) )
{
2017-07-05 03:13:51 -04:00
if ( ! Utils : : String : : EndsWith ( fastHost , " / " ) ) fastHost . append ( " / " ) ;
2017-06-12 19:22:39 -04:00
url = fastHost + path ;
}
else
{
2017-06-19 16:31:58 -04:00
url = host + " /file/ " + ( download - > isMap ? " map/ " : " " ) + file . name
2017-07-02 08:16:06 -04:00
+ ( download - > isPrivate ? ( " ?password= " + download - > hashedPassword ) : " " ) ;
2017-06-12 19:22:39 -04:00
}
2017-01-20 08:36:13 -05:00
2022-06-12 17:07:53 -04:00
Logger : : Print ( " Downloading from url {} \n " , url ) ;
2017-07-05 03:13:51 -04:00
2017-01-20 08:36:13 -05:00
Download : : FileDownload fDownload ;
fDownload . file = file ;
fDownload . index = index ;
fDownload . download = download ;
fDownload . downloading = true ;
fDownload . receivedBytes = 0 ;
Utils : : String : : Replace ( url , " " , " %20 " ) ;
2019-12-25 17:03:43 -05:00
// Just a speedtest ;)
//download->totalBytes = 1048576000;
//url = "http://speed.hetzner.de/1GB.bin";
2017-01-20 08:36:13 -05:00
download - > valid = true ;
2019-12-25 17:03:43 -05:00
/*ZeroMemory(&download->mgr, sizeof download->mgr);
2017-01-20 08:36:13 -05:00
mg_mgr_init ( & download - > mgr , & fDownload ) ;
2017-01-20 16:41:03 -05:00
mg_connect_http ( & download - > mgr , Download : : DownloadHandler , url . data ( ) , nullptr , nullptr ) ;
2017-01-20 08:36:13 -05:00
while ( fDownload . downloading & & ! fDownload . download - > terminateThread )
{
2019-12-25 15:53:15 -05:00
mg_mgr_poll ( & download - > mgr , 100 ) ;
2017-01-20 08:36:13 -05:00
}
2019-12-25 17:03:43 -05:00
mg_mgr_free ( & download - > mgr ) ; */
fDownload . downloading = true ;
Utils : : WebIO webIO ;
webIO . setProgressCallback ( [ & fDownload , & webIO ] ( size_t bytes , size_t )
{
if ( ! fDownload . downloading | | fDownload . download - > terminateThread )
{
webIO . cancelDownload ( ) ;
return ;
}
Download : : DownloadProgress ( & fDownload , bytes - fDownload . receivedBytes ) ;
} ) ;
bool result = false ;
fDownload . buffer = webIO . get ( url , & result ) ;
if ( ! result ) fDownload . buffer . clear ( ) ;
fDownload . downloading = false ;
2017-01-20 08:36:13 -05:00
download - > valid = false ;
2017-07-05 03:13:51 -04:00
if ( fDownload . buffer . size ( ) ! = file . size | | Utils : : Cryptography : : SHA256 : : Compute ( fDownload . buffer , true ) ! = file . hash )
2017-01-20 08:36:13 -05:00
{
return false ;
}
2017-05-30 15:07:05 -04:00
if ( download - > isMap ) Utils : : IO : : CreateDir ( " usermaps/ " + download - > mod ) ;
2017-01-20 08:36:13 -05:00
Utils : : IO : : WriteFile ( path , fDownload . buffer ) ;
return true ;
}
void Download : : ModDownloader ( ClientDownload * download )
{
if ( ! download ) download = & Download : : CLDownload ;
std : : string host = " http:// " + download - > target . getString ( ) ;
2017-07-02 08:16:06 -04:00
std : : string listUrl = host + ( download - > isMap ? " /map " : " /list " ) + ( download - > isPrivate ? ( " ?password= " + download - > hashedPassword ) : " " ) ;
2017-06-19 15:39:48 -04:00
std : : string list = Utils : : WebIO ( " IW4x " , listUrl ) . setTimeout ( 5000 ) - > get ( ) ;
2017-01-20 08:36:13 -05:00
if ( list . empty ( ) )
{
if ( download - > terminateThread ) return ;
download - > thread . detach ( ) ;
download - > clear ( ) ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-01-20 08:36:13 -05:00
{
2017-04-06 16:22:47 -04:00
Command : : Execute ( " closemenu mod_download_popmenu " ) ;
2017-01-20 08:36:13 -05:00
Party : : ConnectError ( " Failed to download the modlist! " ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2017-01-20 08:36:13 -05:00
return ;
}
if ( download - > terminateThread ) return ;
if ( ! Download : : ParseModList ( download , list ) )
{
if ( download - > terminateThread ) return ;
download - > thread . detach ( ) ;
download - > clear ( ) ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-01-20 08:36:13 -05:00
{
2017-04-06 16:22:47 -04:00
Command : : Execute ( " closemenu mod_download_popmenu " ) ;
2017-01-20 08:36:13 -05:00
Party : : ConnectError ( " Failed to parse the modlist! " ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2017-01-20 08:36:13 -05:00
return ;
}
if ( download - > terminateThread ) return ;
2017-01-22 07:44:14 -05:00
static std : : string mod ;
mod = download - > mod ;
2017-01-20 08:36:13 -05:00
for ( unsigned int i = 0 ; i < download - > files . size ( ) ; + + i )
{
if ( download - > terminateThread ) return ;
if ( ! Download : : DownloadFile ( download , i ) )
{
if ( download - > terminateThread ) return ;
mod = Utils : : String : : VA ( " Failed to download file: %s! " , download - > files [ i ] . name . data ( ) ) ;
download - > thread . detach ( ) ;
download - > clear ( ) ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-01-20 08:36:13 -05:00
{
Dvar : : Var ( " partyend_reason " ) . set ( mod ) ;
2017-01-22 07:44:14 -05:00
mod . clear ( ) ;
2017-01-20 08:36:13 -05:00
Command : : Execute ( " closemenu mod_download_popmenu " ) ;
Command : : Execute ( " openmenu menu_xboxlive_partyended " ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2017-01-20 08:36:13 -05:00
return ;
}
}
if ( download - > terminateThread ) return ;
download - > thread . detach ( ) ;
download - > clear ( ) ;
2017-06-14 06:06:04 -04:00
if ( download - > isMap )
2017-04-06 16:22:47 -04:00
{
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-04-06 16:22:47 -04:00
{
Command : : Execute ( " reconnect " , false ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2017-04-06 16:22:47 -04:00
}
else
2017-01-20 08:36:13 -05:00
{
2017-04-06 16:22:47 -04:00
// Run this on the main thread
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2017-04-06 16:22:47 -04:00
{
auto fsGame = Dvar : : Var ( " fs_game " ) ;
fsGame . set ( mod ) ;
fsGame . get < Game : : dvar_t * > ( ) - > modified = true ;
mod . clear ( ) ;
2017-01-20 08:36:13 -05:00
2017-04-06 16:22:47 -04:00
Command : : Execute ( " closemenu mod_download_popmenu " , false ) ;
2017-01-20 08:36:13 -05:00
2017-04-06 16:22:47 -04:00
if ( Dvar : : Var ( " cl_modVidRestart " ) . get < bool > ( ) )
{
Command : : Execute ( " vid_restart " , false ) ;
}
2017-01-20 08:36:13 -05:00
2017-04-06 16:22:47 -04:00
Command : : Execute ( " reconnect " , false ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-04-06 16:22:47 -04:00
}
2017-01-20 08:36:13 -05:00
}
# pragma endregion
# pragma region Server
bool Download : : IsClient ( mg_connection * nc )
{
return ( Download : : GetClient ( nc ) ! = nullptr ) ;
}
Game : : client_t * Download : : GetClient ( mg_connection * nc )
{
Network : : Address address ( nc - > sa . sa ) ;
2022-04-12 08:34:51 -04:00
for ( int i = 0 ; i < * Game : : svs_clientCount ; + + i )
2017-01-20 08:36:13 -05:00
{
Game : : client_t * client = & Game : : svs_clients [ i ] ;
if ( client - > state > = 3 )
{
2021-09-08 05:19:30 -04:00
if ( address . getIP ( ) . full = = Network : : Address ( client - > netchan . remoteAddress ) . getIP ( ) . full )
2017-01-20 08:36:13 -05:00
{
return client ;
}
}
}
return nullptr ;
}
2019-12-25 17:03:43 -05:00
void Download : : DownloadProgress ( FileDownload * fDownload , size_t bytes )
{
fDownload - > receivedBytes + = bytes ;
fDownload - > download - > downBytes + = bytes ;
fDownload - > download - > timeStampBytes + = bytes ;
static volatile bool framePushed = false ;
if ( ! framePushed )
{
double progress = 0 ;
if ( fDownload - > download - > totalBytes )
{
progress = ( 100.0 / fDownload - > download - > totalBytes ) * fDownload - > download - > downBytes ;
}
static unsigned int dlIndex , dlSize , dlProgress ;
dlIndex = fDownload - > index + 1 ;
dlSize = fDownload - > download - > files . size ( ) ;
dlProgress = static_cast < unsigned int > ( progress ) ;
framePushed = true ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2019-12-25 17:03:43 -05:00
{
framePushed = false ;
Dvar : : Var ( " ui_dl_progress " ) . set ( Utils : : String : : VA ( " (%d/%d) %d%% " , dlIndex , dlSize , dlProgress ) ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : CLIENT ) ;
2019-12-25 17:03:43 -05:00
}
int delta = Game : : Sys_Milliseconds ( ) - fDownload - > download - > lastTimeStamp ;
if ( delta > 300 )
{
bool doFormat = fDownload - > download - > lastTimeStamp ! = 0 ;
fDownload - > download - > lastTimeStamp = Game : : Sys_Milliseconds ( ) ;
auto dataLeft = fDownload - > download - > totalBytes - fDownload - > download - > downBytes ;
int timeLeft = 0 ;
if ( fDownload - > download - > timeStampBytes )
{
double timeLeftD = ( ( 1.0 * dataLeft ) / fDownload - > download - > timeStampBytes ) * delta ;
timeLeft = static_cast < int > ( timeLeftD ) ;
}
if ( doFormat )
{
static size_t dlTsBytes ;
static int dlDelta , dlTimeLeft ;
dlTimeLeft = timeLeft ;
dlDelta = delta ;
dlTsBytes = fDownload - > download - > timeStampBytes ;
2022-06-13 13:59:32 -04:00
Scheduler : : Once ( [ ]
2019-12-25 17:03:43 -05:00
{
Dvar : : Var ( " ui_dl_timeLeft " ) . set ( Utils : : String : : FormatTimeSpan ( dlTimeLeft ) ) ;
Dvar : : Var ( " ui_dl_transRate " ) . set ( Utils : : String : : FormatBandwidth ( dlTsBytes , dlDelta ) ) ;
2022-06-13 13:59:32 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2019-12-25 17:03:43 -05:00
}
fDownload - > download - > timeStampBytes = 0 ;
}
}
2017-06-19 15:39:48 -04:00
bool Download : : VerifyPassword ( mg_connection * nc , http_message * message )
{
std : : string g_password = Dvar : : Var ( " g_password " ) . get < std : : string > ( ) ;
2017-06-26 07:04:30 -04:00
if ( g_password . empty ( ) ) return true ;
2017-06-19 15:39:48 -04:00
// sha256 hashes are 64 chars long but we're gonna be safe here
2017-06-22 04:40:39 -04:00
char buffer [ 128 ] = { 0 } ;
int passLen = mg_get_http_var ( & message - > query_string , " password " , buffer , sizeof buffer ) ;
2017-06-19 15:39:48 -04:00
2017-07-05 03:13:51 -04:00
if ( passLen < = 0 | | std : : string ( buffer , passLen ) ! = Utils : : Cryptography : : SHA256 : : Compute ( g_password , true ) )
2017-06-19 15:39:48 -04:00
{
mg_printf ( nc , ( " HTTP/1.1 403 Forbidden \r \n " s +
" Content-Type: text/html \r \n " s +
" Connection: close \r \n " s +
2017-06-19 16:31:58 -04:00
" \r \n " s +
2017-06-19 15:39:48 -04:00
( ( passLen = = 0 ) ? " Password Required " s : " Invalid Password " s ) ) . c_str ( ) ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
return false ;
}
return true ;
}
2017-01-20 08:36:13 -05:00
void Download : : Forbid ( mg_connection * nc )
{
mg_printf ( nc , " HTTP/1.1 403 Forbidden \r \n "
" Content-Type: text/html \r \n "
" Connection: close \r \n "
" \r \n "
" 403 - Forbidden " ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
2018-12-02 12:17:45 -05:00
void Download : : ServerlistHandler ( mg_connection * nc , int ev , void * /*ev_data*/ )
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
std : : vector < json11 : : Json > servers ;
// Build server list
for ( auto & node : Node : : GetNodes ( ) )
{
if ( node . isValid ( ) )
{
2018-12-03 15:48:12 -05:00
servers . push_back ( json11 : : Json { node } ) ;
2018-12-02 12:17:45 -05:00
}
}
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: application/json \r \n "
" Connection: close \r \n "
" Access-Control-Allow-Origin: * \r \n "
" \r \n "
" %s " , json11 : : Json ( servers ) . dump ( ) . data ( ) ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
2017-06-19 15:39:48 -04:00
void Download : : MapHandler ( mg_connection * nc , int ev , void * ev_data )
2017-04-06 16:22:47 -04:00
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
2017-06-19 15:39:48 -04:00
if ( ! Download : : VerifyPassword ( nc , reinterpret_cast < http_message * > ( ev_data ) ) ) return ;
2017-04-06 16:22:47 -04:00
static std : : string mapnamePre ;
static json11 : : Json jsonList ;
2017-07-12 13:36:18 -04:00
std : : string mapname = ( Party : : IsInUserMapLobby ( ) ? Dvar : : Var ( " ui_mapname " ) . get < std : : string > ( ) : Maps : : GetUserMap ( ) - > getName ( ) ) ;
if ( ! Maps : : GetUserMap ( ) - > isValid ( ) & & ! Party : : IsInUserMapLobby ( ) )
2017-04-06 16:22:47 -04:00
{
mapnamePre . clear ( ) ;
jsonList = std : : vector < json11 : : Json > ( ) ;
}
else if ( ! mapname . empty ( ) & & mapname ! = mapnamePre )
{
std : : vector < json11 : : Json > fileList ;
mapnamePre = mapname ;
std : : string path = Dvar : : Var ( " fs_basepath " ) . get < std : : string > ( ) + " \\ usermaps \\ " + mapname ;
2017-04-08 09:32:51 -04:00
for ( int i = 0 ; i < ARRAYSIZE ( Maps : : UserMapFiles ) ; + + i )
2017-04-06 16:22:47 -04:00
{
2017-04-08 09:32:51 -04:00
std : : string filename = path + " \\ " + mapname + Maps : : UserMapFiles [ i ] ;
if ( Utils : : IO : : FileExists ( filename ) )
2017-04-06 16:22:47 -04:00
{
std : : map < std : : string , json11 : : Json > file ;
std : : string fileBuffer = Utils : : IO : : ReadFile ( filename ) ;
2017-04-08 09:32:51 -04:00
file [ " name " ] = mapname + Maps : : UserMapFiles [ i ] ;
2017-04-06 16:22:47 -04:00
file [ " size " ] = static_cast < int > ( fileBuffer . size ( ) ) ;
file [ " hash " ] = Utils : : Cryptography : : SHA256 : : Compute ( fileBuffer , true ) ;
fileList . push_back ( file ) ;
}
}
jsonList = fileList ;
}
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: application/json \r \n "
" Connection: close \r \n "
" \r \n "
" %s " , jsonList . dump ( ) . data ( ) ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
2017-06-19 15:39:48 -04:00
void Download : : ListHandler ( mg_connection * nc , int ev , void * ev_data )
2017-01-20 08:36:13 -05:00
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
2017-06-19 15:39:48 -04:00
if ( ! Download : : VerifyPassword ( nc , reinterpret_cast < http_message * > ( ev_data ) ) ) return ;
2017-01-20 08:36:13 -05:00
// if (!Download::IsClient(nc))
// {
// Download::Forbid(nc);
// }
// else
{
static std : : string fsGamePre ;
static json11 : : Json jsonList ;
std : : string fsGame = Dvar : : Var ( " fs_game " ) . get < std : : string > ( ) ;
if ( ! fsGame . empty ( ) & & fsGame ! = fsGamePre )
{
std : : vector < json11 : : Json > fileList ;
fsGamePre = fsGame ;
std : : string path = Dvar : : Var ( " fs_basepath " ) . get < std : : string > ( ) + " \\ " + fsGame ;
auto list = FileSystem : : GetSysFileList ( path , " iwd " , false ) ;
list . push_back ( " mod.ff " ) ;
for ( auto i = list . begin ( ) ; i ! = list . end ( ) ; + + i )
{
std : : string filename = path + " \\ " + * i ;
2017-01-20 16:41:03 -05:00
if ( strstr ( i - > data ( ) , " _svr_ " ) = = nullptr & & Utils : : IO : : FileExists ( filename ) )
2017-01-20 08:36:13 -05:00
{
std : : map < std : : string , json11 : : Json > file ;
2017-04-06 16:22:47 -04:00
std : : string fileBuffer = Utils : : IO : : ReadFile ( filename ) ;
2017-01-20 08:36:13 -05:00
file [ " name " ] = * i ;
file [ " size " ] = static_cast < int > ( fileBuffer . size ( ) ) ;
file [ " hash " ] = Utils : : Cryptography : : SHA256 : : Compute ( fileBuffer , true ) ;
fileList . push_back ( file ) ;
}
}
jsonList = fileList ;
}
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: application/json \r \n "
" Connection: close \r \n "
" \r \n "
" %s " , jsonList . dump ( ) . data ( ) ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
}
void Download : : FileHandler ( mg_connection * nc , int ev , void * ev_data )
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
http_message * message = reinterpret_cast < http_message * > ( ev_data ) ;
2017-06-19 15:39:48 -04:00
//if (!Download::VerifyPassword(nc, message)) return;
2017-01-20 08:36:13 -05:00
// if (!Download::IsClient(nc))
// {
// Download::Forbid(nc);
// }
// else
{
std : : string url ( message - > uri . p , message - > uri . len ) ;
Utils : : String : : Replace ( url , " \\ " , " / " ) ;
2017-04-06 16:22:47 -04:00
2017-02-14 14:53:55 -05:00
if ( url . size ( ) > = 6 )
{
url = url . substr ( 6 ) ;
}
2017-01-20 08:36:13 -05:00
Utils : : String : : Replace ( url , " %20 " , " " ) ;
2017-04-06 16:22:47 -04:00
bool isMap = false ;
if ( Utils : : String : : StartsWith ( url , " map/ " ) )
2017-01-20 08:36:13 -05:00
{
2017-04-06 16:22:47 -04:00
isMap = true ;
url = url . substr ( 4 ) ;
2017-07-12 13:36:18 -04:00
std : : string mapname = ( Party : : IsInUserMapLobby ( ) ? Dvar : : Var ( " ui_mapname " ) . get < std : : string > ( ) : Maps : : GetUserMap ( ) - > getName ( ) ) ;
2017-04-08 09:32:51 -04:00
bool isValidFile = false ;
for ( int i = 0 ; i < ARRAYSIZE ( Maps : : UserMapFiles ) ; + + i )
{
2017-06-14 06:06:04 -04:00
if ( url = = ( mapname + Maps : : UserMapFiles [ i ] ) )
2017-04-08 09:32:51 -04:00
{
isValidFile = true ;
break ;
}
}
2017-07-12 13:36:18 -04:00
if ( ( ! Maps : : GetUserMap ( ) - > isValid ( ) & & ! Party : : IsInUserMapLobby ( ) ) | | ! isValidFile )
2017-04-06 16:22:47 -04:00
{
Download : : Forbid ( nc ) ;
return ;
}
url = Utils : : String : : VA ( " usermaps \\ %s \\ %s " , mapname . data ( ) , url . data ( ) ) ;
}
else
{
if ( url . find_first_of ( " / " ) ! = std : : string : : npos | | ( ! Utils : : String : : EndsWith ( url , " .iwd " ) & & url ! = " mod.ff " ) | | strstr ( url . data ( ) , " _svr_ " ) ! = nullptr )
{
Download : : Forbid ( nc ) ;
return ;
}
2017-01-20 08:36:13 -05:00
}
2017-02-10 09:18:11 -05:00
std : : string file ;
2017-01-20 08:36:13 -05:00
std : : string fsGame = Dvar : : Var ( " fs_game " ) . get < std : : string > ( ) ;
2017-04-06 16:22:47 -04:00
std : : string path = Dvar : : Var ( " fs_basepath " ) . get < std : : string > ( ) + " \\ " + ( isMap ? " " : fsGame + " \\ " ) + url ;
2017-01-20 08:36:13 -05:00
2017-04-06 16:22:47 -04:00
if ( ( ! isMap & & fsGame . empty ( ) ) | | ! Utils : : IO : : ReadFile ( path , & file ) )
2017-01-20 08:36:13 -05:00
{
mg_printf ( nc ,
" HTTP/1.1 404 Not Found \r \n "
" Content-Type: text/html \r \n "
" Connection: close \r \n "
" \r \n "
" 404 - Not Found %s " , path . data ( ) ) ;
}
else
{
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: application/octet-stream \r \n "
" Content-Length: %d \r \n "
" Connection: close \r \n "
" \r \n " , file . size ( ) ) ;
mg_send ( nc , file . data ( ) , static_cast < int > ( file . size ( ) ) ) ;
}
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
}
2017-06-22 04:35:45 -04:00
void Download : : InfoHandler ( mg_connection * nc , int ev , void * /*ev_data*/ )
2017-01-20 08:36:13 -05:00
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
2017-06-19 15:39:48 -04:00
//if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
2017-01-20 08:36:13 -05:00
Utils : : InfoString status = ServerInfo : : GetInfo ( ) ;
2019-01-09 15:40:56 -05:00
Utils : : InfoString host = ServerInfo : : GetHostInfo ( ) ;
2017-01-20 08:36:13 -05:00
std : : map < std : : string , json11 : : Json > info ;
info [ " status " ] = status . to_json ( ) ;
2019-01-09 15:40:56 -05:00
info [ " host " ] = host . to_json ( ) ;
2017-01-20 08:36:13 -05:00
std : : vector < json11 : : Json > players ;
// Build player list
for ( int i = 0 ; i < atoi ( status . get ( " sv_maxclients " ) . data ( ) ) ; + + i ) // Maybe choose 18 here?
{
std : : map < std : : string , json11 : : Json > playerInfo ;
playerInfo [ " score " ] = 0 ;
playerInfo [ " ping " ] = 0 ;
playerInfo [ " name " ] = " " ;
if ( Dvar : : Var ( " sv_running " ) . get < bool > ( ) )
{
if ( Game : : svs_clients [ i ] . state < 3 ) continue ;
playerInfo [ " score " ] = Game : : SV_GameClientNum_Score ( i ) ;
playerInfo [ " ping " ] = Game : : svs_clients [ i ] . ping ;
playerInfo [ " name " ] = Game : : svs_clients [ i ] . name ;
}
else
{
// Score and ping are irrelevant
2022-07-16 17:24:26 -04:00
const char * namePtr = Game : : PartyHost_GetMemberName ( reinterpret_cast < Game : : PartyData * > ( 0x1081C00 ) , i ) ;
2017-01-20 08:36:13 -05:00
if ( ! namePtr | | ! namePtr [ 0 ] ) continue ;
playerInfo [ " name " ] = namePtr ;
}
players . push_back ( playerInfo ) ;
}
info [ " players " ] = players ;
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: application/json \r \n "
" Connection: close \r \n "
" Access-Control-Allow-Origin: * \r \n "
" \r \n "
" %s " , json11 : : Json ( info ) . dump ( ) . data ( ) ) ;
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
void Download : : EventHandler ( mg_connection * nc , int ev , void * ev_data )
{
// Only handle http requests
if ( ev ! = MG_EV_HTTP_REQUEST ) return ;
http_message * message = reinterpret_cast < http_message * > ( ev_data ) ;
// if (message->uri.p, message->uri.len == "/"s)
// {
// mg_printf(nc,
// "HTTP/1.1 200 OK\r\n"
// "Content-Type: text/html\r\n"
// "Connection: close\r\n"
// "\r\n"
// "Hi fella!<br>You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not"));
//
// Game::client_t* client = Download::GetClient(nc);
//
// if (client)
// {
// mg_printf(nc, "<br>Hello %s!", client->name);
// }
// }
// else
{
//std::string path = (Dvar::Var("fs_basepath").get<std::string>() + "\\" BASEGAME "\\html");
//mg_serve_http_opts opts = { 0 };
//opts.document_root = path.data();
//mg_serve_http(nc, message, opts);
FileSystem : : File file ;
std : : string url = " html " + std : : string ( message - > uri . p , message - > uri . len ) ;
if ( Utils : : String : : EndsWith ( url , " / " ) )
{
url . append ( " index.html " ) ;
file = FileSystem : : File ( url ) ;
}
else
{
file = FileSystem : : File ( url ) ;
if ( ! file . exists ( ) )
{
url . append ( " /index.html " ) ;
file = FileSystem : : File ( url ) ;
}
}
std : : string mimeType = Utils : : GetMimeType ( url ) ;
if ( file . exists ( ) )
{
std : : string buffer = file . getBuffer ( ) ;
mg_printf ( nc ,
" HTTP/1.1 200 OK \r \n "
" Content-Type: %s \r \n "
" Content-Length: %d \r \n "
" Connection: close \r \n "
" \r \n " , mimeType . data ( ) , buffer . size ( ) ) ;
mg_send ( nc , buffer . data ( ) , static_cast < int > ( buffer . size ( ) ) ) ;
}
else
{
mg_printf ( nc ,
" HTTP/1.1 404 Not Found \r \n "
" Content-Type: text/html \r \n "
" Connection: close \r \n "
" \r \n "
" 404 - Not Found " ) ;
}
}
nc - > flags | = MG_F_SEND_AND_CLOSE ;
}
# pragma endregion
Download : : Download ( )
{
2018-11-04 14:16:14 -05:00
if ( Dedicated : : IsEnabled ( ) /*|| Dvar::Var("mod_force_download_server").get<bool>()*/ )
2017-01-20 08:36:13 -05:00
{
2017-05-14 14:14:52 -04:00
ZeroMemory ( & Download : : Mgr , sizeof Download : : Mgr ) ;
2017-01-20 16:41:03 -05:00
mg_mgr_init ( & Download : : Mgr , nullptr ) ;
2017-01-20 08:36:13 -05:00
2017-06-14 06:06:04 -04:00
Network : : OnStart ( [ ] ( )
2017-01-20 08:36:13 -05:00
{
2017-05-31 12:03:05 -04:00
mg_connection * nc = mg_bind ( & Download : : Mgr , Utils : : String : : VA ( " %hu " , Network : : GetPort ( ) ) , Download : : EventHandler ) ;
2017-01-20 08:36:13 -05:00
2017-02-02 15:07:40 -05:00
if ( nc )
{
// Handle special requests
mg_register_http_endpoint ( nc , " /info " , Download : : InfoHandler ) ;
mg_register_http_endpoint ( nc , " /list " , Download : : ListHandler ) ;
2017-06-14 06:06:04 -04:00
mg_register_http_endpoint ( nc , " /map " , Download : : MapHandler ) ;
2017-02-14 11:12:07 -05:00
mg_register_http_endpoint ( nc , " /file/ " , Download : : FileHandler ) ;
2018-12-02 12:17:45 -05:00
mg_register_http_endpoint ( nc , " /serverlist " , Download : : ServerlistHandler ) ;
2017-01-20 08:36:13 -05:00
2017-02-02 15:07:40 -05:00
mg_set_protocol_http_websocket ( nc ) ;
}
else
{
Logger : : Print ( " Failed to bind TCP socket, moddownload won't work! \n " ) ;
}
2017-01-20 08:36:13 -05:00
} ) ;
2022-06-28 03:26:43 -04:00
Download : : ServerRunning = true ;
2017-05-25 14:18:20 -04:00
Download : : Terminate = false ;
Download : : ServerThread = std : : thread ( [ ]
2017-01-20 08:36:13 -05:00
{
2017-05-25 14:18:20 -04:00
while ( ! Download : : Terminate )
{
mg_mgr_poll ( & Download : : Mgr , 100 ) ;
}
2017-01-20 08:36:13 -05:00
} ) ;
}
else
{
2022-05-27 06:19:28 -04:00
Scheduler : : Once ( [ ]
2017-01-22 07:44:14 -05:00
{
2022-07-02 13:52:57 -04:00
Dvar : : Register < const char * > ( " ui_dl_timeLeft " , " " , Game : : DVAR_NONE , " " ) ;
Dvar : : Register < const char * > ( " ui_dl_progress " , " " , Game : : DVAR_NONE , " " ) ;
Dvar : : Register < const char * > ( " ui_dl_transRate " , " " , Game : : DVAR_NONE , " " ) ;
2022-05-27 06:19:28 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-01-22 07:44:14 -05:00
2017-06-14 06:06:04 -04:00
UIScript : : Add ( " mod_download_cancel " , [ ] ( UIScript : : Token )
2017-01-20 08:36:13 -05:00
{
Download : : CLDownload . clear ( ) ;
} ) ;
}
2017-05-14 14:14:52 -04:00
2022-05-27 06:19:28 -04:00
Scheduler : : Once ( [ ]
2017-06-26 07:04:30 -04:00
{
2022-07-02 13:52:57 -04:00
Dvar : : Register < bool > ( " sv_wwwDownload " , false , Game : : DVAR_ARCHIVE , " Set to true to enable downloading maps/mods from an external server. " ) ;
Dvar : : Register < const char * > ( " sv_wwwBaseUrl " , " " , Game : : DVAR_ARCHIVE , " Set to the base url for the external map download. " ) ;
2018-10-22 18:58:09 -04:00
2022-03-08 09:42:41 -05:00
// Force users to enable this because we don't want to accidentally turn everyone's pc into a http server into all their files again
// not saying we are but ya know... accidents happen
// by having it saved we force the user to enable it in config_mp because it only checks the dvar on startup to see if we should init download or not
2022-07-02 13:52:57 -04:00
Dvar : : Register < bool > ( " mod_force_download_server " , false , Game : : DVAR_ARCHIVE , " Set to true to force the client to run the download server for mods (for mods in private matches). " ) ;
2022-05-27 06:19:28 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-06-26 07:04:30 -04:00
2022-05-05 10:03:14 -04:00
Scheduler : : Loop ( [ ]
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
int workingCount = 0 ;
2017-06-14 06:06:04 -04:00
for ( auto i = Download : : ScriptDownloads . begin ( ) ; i ! = Download : : ScriptDownloads . end ( ) ; )
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
auto download = * i ;
2017-05-14 14:14:52 -04:00
2017-06-14 06:06:04 -04:00
if ( download - > isDone ( ) )
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
download - > notifyDone ( ) ;
2017-05-14 14:14:52 -04:00
i = Download : : ScriptDownloads . erase ( i ) ;
2017-05-15 15:57:45 -04:00
continue ;
}
if ( download - > isWorking ( ) )
{
download - > notifyProgress ( ) ;
+ + workingCount ;
}
+ + i ;
}
2017-06-14 06:06:04 -04:00
for ( auto & download : Download : : ScriptDownloads )
2017-05-15 15:57:45 -04:00
{
if ( workingCount > 5 ) break ;
2017-06-14 06:06:04 -04:00
if ( ! download - > isWorking ( ) )
2017-05-15 15:57:45 -04:00
{
download - > startWorking ( ) ;
+ + workingCount ;
2017-05-14 14:14:52 -04:00
}
}
2022-05-05 10:03:14 -04:00
} , Scheduler : : Pipeline : : MAIN ) ;
2017-05-14 14:14:52 -04:00
2022-06-16 10:15:26 -04:00
Events : : OnVMShutdown ( [ ]
2017-05-14 14:14:52 -04:00
{
Download : : ScriptDownloads . clear ( ) ;
} ) ;
2022-05-04 07:44:45 -04:00
Script : : AddFunction ( " HttpGet " , [ ]
2017-05-14 14:14:52 -04:00
{
2022-02-01 07:15:59 -05:00
const auto * url = Game : : Scr_GetString ( 0 ) ;
2022-01-23 14:32:20 -05:00
2022-02-01 07:15:59 -05:00
if ( url = = nullptr )
2022-01-23 14:32:20 -05:00
{
2022-02-09 18:40:27 -05:00
Game : : Scr_ParamError ( 0 , " ^1HttpGet: Illegal parameter! \n " ) ;
2022-01-23 14:32:20 -05:00
return ;
}
auto object = Game : : AllocObject ( ) ;
2017-05-14 14:14:52 -04:00
2020-12-16 12:45:28 -05:00
Game : : Scr_AddObject ( object ) ;
2017-05-14 14:14:52 -04:00
2020-12-16 12:45:28 -05:00
Download : : ScriptDownloads . push_back ( std : : make_shared < ScriptDownload > ( url , object ) ) ;
Game : : RemoveRefToObject ( object ) ;
} ) ;
2017-05-14 14:14:52 -04:00
2022-05-04 07:44:45 -04:00
Script : : AddFunction ( " HttpCancel " , [ ]
2020-12-16 12:45:28 -05:00
{
2022-02-01 07:15:59 -05:00
const auto object = Game : : Scr_GetObject ( 0 ) ;
for ( const auto & download : Download : : ScriptDownloads )
2020-12-16 12:45:28 -05:00
{
if ( object = = download - > getObject ( ) )
2017-05-14 14:14:52 -04:00
{
2020-12-16 12:45:28 -05:00
download - > cancel ( ) ;
break ;
2017-05-14 14:14:52 -04:00
}
2020-12-16 12:45:28 -05:00
}
} ) ;
2017-01-20 08:36:13 -05:00
}
Download : : ~ Download ( )
{
2018-10-22 18:58:09 -04:00
if ( Download : : ServerRunning )
2017-01-20 08:36:13 -05:00
{
mg_mgr_free ( & Download : : Mgr ) ;
}
2017-01-23 16:06:50 -05:00
}
void Download : : preDestroy ( )
{
2017-06-02 06:26:08 -04:00
Download : : Terminate = true ;
if ( Download : : ServerThread . joinable ( ) )
{
Download : : ServerThread . join ( ) ;
}
2017-01-23 16:06:50 -05:00
if ( ! Dedicated : : IsEnabled ( ) )
2017-01-20 08:36:13 -05:00
{
Download : : CLDownload . clear ( ) ;
}
2017-05-14 14:14:52 -04:00
Download : : ScriptDownloads . clear ( ) ;
2017-01-20 08:36:13 -05:00
}
}