Updating PAK File does not work

A _P.pak file is opened in Unreal in such a way that it can not be deleted or renamed. This makes it impossible to update the file.
Updating is a very difficult business as it is with different Windows versions and each installation with different quirks. I finally managed to make a version that works with the client updating itself. Even the EXE file can be renamed and deleted while the process loaded from it runs.
Unreal however opens the PAK file in no-share mode which makes it impossible to delete the pack file.

What’s worse: This all works in debug mode but it does not work in a packed game for shipping.

And yes, I know that this means that the game can not continue. After an update the game is supposed to terminate and be restarted by the player.

The PAK file should be opened in share mode so that it can be deleted while it is open. You don’t have to preotect us from ourselves. When we want to delete a game file, we should be able to do so. We are all grown up here and know what we are doing and why.

Wallenstein,

Can you please provide the full reproduction steps in order for us to enter a of what you’re seeing, if it’s not implemented that way?

Have you tested this in 4.15 yet? If not, please do so and let me know the results.

Simple:

Have some C++ project packed for shipping with a PAK file.
Then alter something somewhere and create a _P.pak update file to be placed in the same directory as your pak file

Then simply copy the _p.pak file to something like dummy.bin or whatever

Run your project. Open your conted directory in your browser and try to delete the _P.pak file. It will not be possible.

As for me, i wrote my Unreal program so that it downloads updated _P.pak files from a webserver and stores them as Temp.bin and then tries to delete the old _P.pak and rename the Temp.bin to the original name of whatever_P.pak and then terminate with a message to the to restart due to an upgrade.

This does not work in a shipping version but did work in the development version for me. I now have to start a process from my Unreal program at exit which runs in a DOS window and does the rename operation when the unreal program has terminated and frees its grip on the _P.pak file.

If Unreal would open a pak file on cooperative filemode, this could be avoided. Of course the Unreal program will crash if somebody deletes the _P.pak file while the game is running, but hey, stupidity ought to be punished. If somebody does that and expects the program to continue, come on, give me a break.

So for the purpose of updating a program from the internet with new patches, being able to delete a _p.pak file is essential unless you want to shoot yourself through mouth in the foot like I had to do here with a separate tool

Didn’t try in 4.15. I don’t install Engine updates before the first few hotfixes are out

Wallenstein,

I do not work with .bin files when packaging out projects in UE4. Could you be a bit more specific in your reproduction steps?

I have ran your steps by a few people and none of us fully understand what exactly what you are doing to achieve your results.

Your patience is appreciated.

A “bin” file is just a temporary name for the downloaded file which is actually a PAK file, I just have to name it something else while I download it.

If you just run a packed for shipping application which uses a _P.pak update file and then try to delete that file you will see that it can’t be deleted because it’s opened in exclusive mode.

I will include a complete listing of my subroutine that downloads a _P.pak file from my webserver, stores it as a temporary file, attempts to delete the original _P.pak file and then attempts to rename the temporary filename as _P.pak which fails at the point where the _P.pak can not be deleted

There are several of my own functions in the code but you should be able to see what they do from the function name

unsigned long WINAPI Download_PAK_File(In LPVOID lpParameter)
{
U32 l_Update_Server_Index = 0;

while (as_update_servers[l_Update_Server_Index] != nullptr)
{

	try
	{

		std::string				l_Complete_URL;
		std::string				l_Temp_String;
		NOGS::FILES::File_Disc	l_Local_Exe_File;
		NOGS::FILES::File_Disc	l_Local_Bin_File;
		U32						l_Locale_Pak_Version;
		RETURN_TYPE				l_File_Result;
		NOGS::FILES::Directory	l_Actual_Directory;
		BOOL					l_Move_Was_Successful;


		FString			l_Game_Dir;
		FString			l_Local_Download_File;
		FString			l_Local_Old_Exe_File;
		FString			l_Local_Renamed_Old_Exe_File;




		std::wstring				l_Temp_WString;


		l_Game_Dir = FPaths::GameDir();
		l_Actual_Directory.Load_Working_Dir();
		l_Temp_WString = l_Actual_Directory.Get_String();

		l_Temp_WString += *l_Game_Dir;

		l_Temp_String = NOGS::SYSTEM::Strings::To_UTF_8(l_Temp_WString);

		l_Game_Dir = l_Temp_String.c_str();
		FPaths::NormalizeDirectoryName(l_Game_Dir);
		FPaths::CollapseRelativeDirectories(l_Game_Dir);


		

		 








		// [2017/01/23 Patric]
		/************************************************************************/
		// Initialize a session we use here
		/************************************************************************/
		l_Last_Error = 0;
		l_CURL_Handle = curl_easy_init();
		if (l_CURL_Handle == nullptr)
		{
			l_Last_Error = 1;
			l_In_Download_Routine = false;
			return -1;
		}

		curl_easy_setopt(l_CURL_Handle, CURLOPT_ERRORBUFFER, l_Error_Buffer);
		l_Error_Buffer[0] = 0;
		curl_easy_setopt(l_CURL_Handle, CURLOPT_NOPROGRESS, 0);
		curl_easy_setopt(l_CURL_Handle, CURLOPT_PROGRESSDATA, 0);
		curl_easy_setopt(l_CURL_Handle, CURLOPT_XFERINFOFUNCTION, progress_callback);

		// [2017/01/23 Patric]
		/************************************************************************/
		// Assemble the remote Exefilename
		/************************************************************************/
		l_Complete_URL = as_update_servers[l_Update_Server_Index];
		l_Complete_URL += l_Pak_Filename_Template;

		Write_Line_For_Output(std::string("Preparing download from: "));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Complete_URL);
		Wait_Until_Thread_Can_Run();



		// [2017/01/23 Patric]
		/************************************************************************/
		// Open an output file in the same directory where the Exe file is
		/************************************************************************/
		l_Local_Download_File = *(l_Game_Dir + FString("/Content/Paks/Temp.bin"));

		Write_Line_For_Output(FString("Opening temporary output file: "));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Download_File);
		Wait_Until_Thread_Can_Run();


		l_Output_File = new NOGS::FILES::File_Disc;
		*l_Output_File = *l_Local_Download_File;

		l_Output_File->Open(L"w", l_File_Result);
		if (l_File_Result != RETURN_TYPE::OK)
		{
			Write_Line_For_Output(FString("Could not open temporary file (1)"));
			Wait_Until_Thread_Can_Run();

			l_Last_Error = 6;
			curl_easy_cleanup(l_CURL_Handle);
			l_In_Download_Routine = false;
			return -1;
		}
		if (!l_Output_File->Is_Open())
		{
			Write_Line_For_Output(FString("Could not open temporary file (2)"));
			Wait_Until_Thread_Can_Run();

			l_Last_Error = 6;
			curl_easy_cleanup(l_CURL_Handle);
			l_In_Download_Routine = false;
			return -1;
		}



		// [2017/01/23 Patric]
		/************************************************************************/
		// Initiate download and write it into the output file
		/************************************************************************/
		curl_easy_setopt(l_CURL_Handle, CURLOPT_URL, l_Complete_URL.c_str());
		curl_easy_setopt(l_CURL_Handle, CURLOPT_WRITEFUNCTION, write_callback_disk_file);

		Write_Line_For_Output(FString("Commencing download..."));
		Wait_Until_Thread_Can_Run();

		if (CURLE_OK != curl_easy_perform(l_CURL_Handle))
		{
			Write_Line_For_Output(FString(">>> Download failed <<<"));
			Wait_Until_Thread_Can_Run();


			l_Output_File->Close();
			l_Output_File->Delete();
			delete l_Output_File;
			l_Output_File = nullptr;

			l_Last_Error = 3;
			curl_easy_cleanup(l_CURL_Handle);
			++l_Update_Server_Index;
			continue;
		}
		Write_Line_For_Output(FString("...success."));
		Wait_Until_Thread_Can_Run();

		l_Output_File->Close();
		curl_easy_cleanup(l_CURL_Handle);





		// [2017/01/24 Patric]
		/************************************************************************/
		// Rename the actual pak file, then rename the downloaded file to be
		// the actual pak file, then delete the old pak file.
		// However, if the original file does not exist, we just move on
		// to the second step and dont complain
		/************************************************************************/
		l_Local_Old_Exe_File = *(l_Game_Dir + FString("/Content/Paks/Client-WindowsNoEditor_P.pak") );
		l_Local_Renamed_Old_Exe_File = *(l_Game_Dir + FString("Content/Paks/Client-WindowsNoEditor_P.old") );

		// Remove the original pak file.

		Write_Line_For_Output(FString("Preparing renaming of: "));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Old_Exe_File);
		Wait_Until_Thread_Can_Run();

		Write_Line_For_Output(FString("to new name: "));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Renamed_Old_Exe_File);
		Wait_Until_Thread_Can_Run();


		// [2017/02/17 Patric]
		/*...................................................................*/
		// This will attempt to rename the original file to a .old name here
		// If that fails, a tool will be launched that will try to do the
		// renaming including the following renaming of the temp file to the
		// original name after this program terminates
                    // THIS FAILS BECAUSE THE ORIGINAL FILE IS OPEN IN EXCLUSIVE MODE. IF IT WERE OPEN IN SHARE MODE, I COULD DELETE THE OLD FILE HERE TO PREPARE FOR UPDATE
		/*...................................................................*/
		l_Move_Was_Successful = MoveFileEx(*l_Local_Old_Exe_File, *l_Local_Renamed_Old_Exe_File, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
		if (!l_Move_Was_Successful)
		{
			Write_Line_For_Output(FString(">>> Renaming failed. File will be renamed during restart<<<") );
			Wait_Until_Thread_Can_Run();

			s_Start_Renaming_Tool = true;
			s_Game_Dir				= l_Game_Dir;
			s_Existing_Pak_File		= l_Local_Old_Exe_File;
			s_Existing_Temp_File	= l_Local_Download_File;
			s_Version_File			= FString(l_Local_Bin_File.Get_UNC().Get_String().c_str());
			s_New_Version			= l_Server_Pak_Version;


			l_Last_Error = 0;
			delete l_Output_File;
			l_Output_File = nullptr;

			s_Restart_Required = true;
			l_In_Download_Routine = false;
			return 0;

		}
		else {
			Write_Line_For_Output(FString("...success."));
			Wait_Until_Thread_Can_Run();
		}

                   // oBVIOUSLY THIS IS NEVER REACHED, BUT IF THE DELETION ABOVE HAD WORKED, THEN THIS WOULD RENAME THE TEMP.BIN TO _P.PAK AND THUS ALL WOULD BE OK WITH THE NEXT RESTART. AS IT STANDS I HAVE TO RUN AN EXTERNAL CONSOLE TOOL TO DO THE RENAMING AFTER THE UNREAL PROGRAM IS FINISHED

		// Remove the downloaded temp file to the name of the previous exe file
		Write_Line_For_Output(FString("Attempting to rename temporary :"));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Download_File);
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(FString("to new name:"));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Old_Exe_File);
		Wait_Until_Thread_Can_Run();

		l_Move_Was_Successful = MoveFileEx(*l_Local_Download_File, *l_Local_Old_Exe_File, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
		if (!l_Move_Was_Successful)
		{
			Write_Line_For_Output(FString(">>> Renaming failed<<<"));
			Wait_Until_Thread_Can_Run();

			l_Output_File->Delete();
			delete l_Output_File;
			l_Output_File = nullptr;

			l_Last_Error = 8;
			curl_easy_cleanup(l_CURL_Handle);
			return -1;
		}
		Write_Line_For_Output(FString("...success."));
		Wait_Until_Thread_Can_Run();


		delete l_Output_File;
		l_Output_File = nullptr;


		// [2017/01/24 Patric]
		/************************************************************************/
		// Write a binary file with the version number of the new EXE file
		/************************************************************************/
		Write_Line_For_Output(std::wstring(L"Attempting to write to: "));
		Wait_Until_Thread_Can_Run();
		Write_Line_For_Output(l_Local_Bin_File.Get_UNC().Get_String());
		Wait_Until_Thread_Can_Run();

		l_Local_Bin_File.Open(L"w");
		if (!l_Local_Bin_File.Is_Open())
		{
			Write_Line_For_Output(FString(">>> Writing Failed <<<"));
			Wait_Until_Thread_Can_Run();

			l_Last_Error = 10;
			curl_easy_cleanup(l_CURL_Handle);
			return -1;
		}

		l_Local_Bin_File.Write_32(l_Server_Pak_Version);
		l_Local_Bin_File.Close();

		Write_Line_For_Output(FString("UPDATE COMPLETE."));
		Wait_Until_Thread_Can_Run();

		s_Restart_Required = true;
		l_In_Download_Routine = false;
		return 0;


	}
	catch (...)
	{
		l_Last_Error = 5;
		l_In_Download_Routine = false;
		return -1;
	}
}

l_In_Download_Routine = false;
return -1;

}

It sounds as though the way you’ve set up your webserver is likely the problem with your ,pak file. Please make sure you’ve looked over your code and verified there are no mistakes.

Yea, forget about it, I deal with it myself.