/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ // Download.cpp : Implementation of CDownload #include "DownloadDebug.h" #include "download.h" #include #include #include #include #include #include ///////////////////////////////////////////////////////////////////////////// // CDownload // // Kick off a download - copys the parameters for the download to the appropriate // data members, then sets the status flag to start the download when PumpMessages() // is called. // HRESULT CDownload::DownloadFile(LPCSTR server, LPCSTR username, LPCSTR password, LPCSTR file, LPCSTR localfile, LPCSTR regkey, bool tryresume) { // Check we're not in the middle of another download. if((m_Status != DOWNLOADSTATUS_DONE ) && (m_Status != DOWNLOADSTATUS_FINDINGFILE)) { return( DOWNLOAD_STATUSERROR ); } // If we're still connected, make sure we're on the right server if (m_Status == DOWNLOADSTATUS_FINDINGFILE) { if ((strcmp(m_Server, server)) || (strcmp(m_Login, username))) { // Damn, a server switch. Close conn & fix state m_Ftp->DisconnectFromServer(); m_Status=DOWNLOADSTATUS_DONE; } } // Check all parameters are non-null. if( ( server == NULL ) || ( username == NULL ) || ( password == NULL ) || ( file == NULL ) || ( localfile == NULL ) || ( regkey == NULL ) ) { //////////DBGMSG("Download Paramerror"); return( DOWNLOAD_PARAMERROR ); } // Make sure we have a download directory _mkdir("download"); // Copy parameters to member variables. strncpy( m_Server, server, sizeof( m_Server ) ); strncpy( m_Login, username, sizeof( m_Login ) ); strncpy( m_Password, password, sizeof( m_Password ) ); strncpy( m_File, file, sizeof( m_File ) ); strncpy( m_LocalFile, localfile, sizeof( m_LocalFile ) ); strncpy( m_LastLocalFile, localfile, sizeof( m_LastLocalFile ) ); strncpy( m_RegKey, regkey, sizeof( m_RegKey ) ); m_TryResume = tryresume; m_StartPosition=0; /********* // figure out file offset if (m_TryResume == false) { m_StartPosition=0; } else if(( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 ) { if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME ) { m_Ftp->RestartFrom( 0 ); m_StartPosition = 0; } } ************/ // Set status so we start to connect at the next PumpMessages() if (m_Status != DOWNLOADSTATUS_FINDINGFILE) m_Status = DOWNLOADSTATUS_GO; //////////DBGMSG("Ready to download"); return S_OK; } // // Get the local filename of the last file we requested to download.... // HRESULT CDownload::GetLastLocalFile(char *local_file, int maxlen) { if (local_file==0) return(E_FAIL); strncpy(local_file, m_LastLocalFile, maxlen); local_file[maxlen-1]=0; return(S_OK); } // // Abort the current download - disconnects from the server and sets the // status flag to stop PumpMessages() from doing anything. // HRESULT CDownload::Abort() { if( m_Status != DOWNLOADSTATUS_NONE ) { m_Ftp->DisconnectFromServer(); m_Status = DOWNLOADSTATUS_NONE; } m_TimeStarted = 0; m_StartPosition = 0; m_FileSize = 0; m_BytesRead = 0; m_Server[ 0 ] = '\0'; m_Login[ 0 ] = '\0'; m_Password[ 0 ] = '\0'; m_File[ 0 ] = '\0'; m_LocalFile[ 0 ] = '\0'; m_RegKey[ 0 ] = '\0'; return S_OK; } // // PumpMessages() does all the work - checks for possible resumption of a previous // download, connects to the server, finds the file and downloads it. // HRESULT CDownload::PumpMessages() { int iResult = 0; int timetaken = 0; int averagetimepredicted = -1; static int reenter = 0; /* Avoid reentrancy. */ ////DBGMSG("Download Pump"); if( reenter != 0 ) { return( DOWNLOAD_REENTERERROR ); } reenter = 1; if( m_Status == DOWNLOADSTATUS_GO ) { // Check to see whether the file already exists locally /*********** if (m_TryResume == false) { m_StartPosition = 0; } else if( ( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 ) { if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME ) { m_Ftp->RestartFrom( 0 ); m_StartPosition = 0; } } ************/ // Tell the client that we're starting to connect. Listener->OnStatusUpdate( DOWNLOADSTATUS_CONNECTING ); m_Status = DOWNLOADSTATUS_CONNECTING; } if( m_Status == DOWNLOADSTATUS_CONNECTING ) { // Connect to the server //////////DBGMSG("Connect to Server"); iResult = m_Ftp->ConnectToServer( m_Server ); ////////////DBGMSG("out of FTP connect"); if( iResult == FTP_SUCCEEDED ) { m_Status = DOWNLOADSTATUS_LOGGINGIN; } else { if( iResult == FTP_FAILED ) { // Tell the client we couldn't connect ///////////DBGMSG("Couldn't connect"); Listener->OnError( DOWNLOADEVENT_COULDNOTCONNECT ); reenter = 0; return DOWNLOAD_NETWORKERROR; } } reenter = 0; return DOWNLOAD_SUCCEEDED; } if( m_Status == DOWNLOADSTATUS_LOGGINGIN ) { // Login to the server ////////////DBGMSG("Login to server"); iResult = m_Ftp->LoginToServer( m_Login, m_Password ); if( iResult == FTP_SUCCEEDED ) { Listener->OnStatusUpdate( DOWNLOADSTATUS_FINDINGFILE ); m_Status = DOWNLOADSTATUS_FINDINGFILE; } if( iResult == FTP_FAILED ) { // Tell the client we couldn't log in Listener->OnError( DOWNLOADEVENT_LOGINFAILED ); reenter = 0; return( DOWNLOAD_NETWORKERROR ); } reenter = 0; return DOWNLOAD_SUCCEEDED; } if(( m_Status == DOWNLOADSTATUS_FINDINGFILE ) && (strlen(m_File))) { // Find the file on the server ///////////DBGMSG("Find File"); if( m_Ftp->FindFile( m_File, &m_FileSize ) == FTP_FAILED ) { // Tell the client we couldn't find the file Listener->OnError( DOWNLOADEVENT_NOSUCHFILE ); reenter = 0; return( DOWNLOAD_FILEERROR ); } if( m_FileSize > 0 ) { // First check to see if we already have the complete file in the proper dest location... // // Note, we will only skip the download if the file is a patch. Patch files should // never ever change so this is not a concern. // // We identify patches because they are written into the patches folder. struct _stat statdata; if ( (_stat(m_LocalFile, &statdata) == 0) && (statdata.st_size == m_FileSize) && (_strnicmp(m_LocalFile, "patches\\", strlen("patches\\"))==0)) { // OK, no need to download this again.... m_Status = DOWNLOADSTATUS_FINDINGFILE; // ready to find another file m_TimeStarted = 0; m_StartPosition = 0; m_FileSize = 0; m_BytesRead = 0; m_File[ 0 ] = '\0'; m_LocalFile[ 0 ] = '\0'; Listener->OnEnd(); reenter = 0; return DOWNLOAD_SUCCEEDED; } // // Check if we can do a file resume // if (m_TryResume == false) { m_StartPosition = 0; } else if( ( m_StartPosition = m_Ftp->FileRecoveryPosition( m_LocalFile, m_RegKey ) ) != 0 ) { if( Listener->OnQueryResume() == DOWNLOADEVENT_DONOTRESUME ) { m_Ftp->RestartFrom( 0 ); m_StartPosition = 0; } } // end resume check m_Status = DOWNLOADSTATUS_DOWNLOADING; // Tell the client we're starting to download Listener->OnStatusUpdate( DOWNLOADSTATUS_DOWNLOADING ); } reenter = 0; return DOWNLOAD_SUCCEEDED; } if( m_Status == DOWNLOADSTATUS_DOWNLOADING ) { // Get the next chunk of the file ///DBGMSG("Get Next File Block"); iResult = m_Ftp->GetNextFileBlock( m_LocalFile, &m_BytesRead ); if( m_TimeStarted == 0 ) { // This is the first time through here - record the starting time. m_TimeStarted = timeGetTime(); } if( iResult == FTP_SUCCEEDED ) { m_Status = DOWNLOADSTATUS_FINISHING; } else { if( iResult == FTP_FAILED ) { Listener->OnError( DOWNLOADEVENT_TCPERROR ); reenter = 0; return( DOWNLOAD_NETWORKERROR ); } } // Calculate time taken so far, and predict how long there is left. // The prediction returned is the average of the last 8 predictions. timetaken = ( timeGetTime() - m_TimeStarted ) / 1000; //////////if( m_BytesRead > 0 ) // NAK - RP said this is wrong if( ( m_BytesRead - m_StartPosition ) > 0 ) { // Not the first read. int predictionIndex = ( m_predictions++ ) & 0x7; m_predictionTimes[ predictionIndex ] = MulDiv( timetaken, (m_FileSize - m_BytesRead), (m_BytesRead - m_StartPosition) ); //__int64 numerator = ( m_FileSize - m_BytesRead ) * timetaken; //__int64 denominator = ( m_BytesRead - m_StartPosition ); //m_predictionTimes[ predictionIndex ] = numerator/denominator; //m_predictionTimes[ predictionIndex ] = ( ( m_FileSize - m_BytesRead ) * timetaken ) / ( m_BytesRead - m_StartPosition ); //m_predictionTimes[ predictionIndex ] = ( ( m_FileSize - m_BytesRead ) / ( m_BytesRead - m_StartPosition ) * timetaken ); DEBUG_LOG(("About to OnProgressUpdate() - m_FileSize=%d, m_BytesRead=%d, timetaken=%d, numerator=%d", m_FileSize, m_BytesRead, timetaken, ( ( m_FileSize - m_BytesRead ) * timetaken ))); DEBUG_LOG((", m_startPosition=%d, denominator=%d, predictionTime=%d\n", m_StartPosition, ( m_BytesRead - m_StartPosition ), predictionIndex)); DEBUG_LOG(("vals are %d %d %d %d %d %d %d %d\n", m_predictionTimes[ 0 ], m_predictionTimes[ 1 ], m_predictionTimes[ 2 ], m_predictionTimes[ 3 ], m_predictionTimes[ 4 ], m_predictionTimes[ 5 ], m_predictionTimes[ 6 ], m_predictionTimes[ 7 ])); if( m_predictions > 8 ) { // We've had enough time to build up a few predictions, so calculate // an average. averagetimepredicted = ( m_predictionTimes[ 0 ] + m_predictionTimes[ 1 ] + m_predictionTimes[ 2 ] + m_predictionTimes[ 3 ] + m_predictionTimes[ 4 ] + m_predictionTimes[ 5 ] + m_predictionTimes[ 6 ] + m_predictionTimes[ 7 ] ) / 8; } else { // Wait a while longer before passing a "real" prediction averagetimepredicted = -1; } } else { averagetimepredicted = -1; } // Update the client's progress bar (or whatever) Listener->OnProgressUpdate( m_BytesRead, m_FileSize, timetaken, averagetimepredicted + 1); } if( m_Status == DOWNLOADSTATUS_FINISHING ) { if (m_Ftp->m_iCommandSocket) m_Status = DOWNLOADSTATUS_FINDINGFILE; // ready to find another file else m_Status = DOWNLOADSTATUS_DONE; // command channel closed, connect again... m_TimeStarted = 0; m_StartPosition = 0; m_FileSize = 0; m_BytesRead = 0; m_File[ 0 ] = '\0'; m_LocalFile[ 0 ] = '\0'; Listener->OnEnd(); } reenter = 0; return DOWNLOAD_SUCCEEDED; }