/*
** 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;
}