How to open file from packaged game directory?

Hello, everyone!

I am creating a game for HTML5 platform, in which I need to connect to the server by websocket.
I don’t want to store HOST and PORT for connecting to the server inside the game. I want to use *.ini file in format

[Server]
HOST=host_address
PORT=8000

I wrote simple C++ class to read this file

FString UBPFL_FileIO::ReadConfig(FString section, FString name, FString path) {
FString result;
if (!path.Contains(“.ini”)) {
path += “.ini”;
}
path = FPaths::LaunchDir() + path;
FPaths::RemoveDuplicateSlashes(path);
if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*path)) {
GEngine->AddOnScreenDebugMessage(-1, 50.f, FColor::Red, TEXT("Couldn’t find configuration file: " + path));
return FString();
}
GConfig->GetString(*section, *name, result, *path);
return result;
}

This code works fine for Windows platform. I’ve tried to package game for Windows and it’s read config from the folder, where *.exe file’s been placed.
But FPaths::LaunchDir() returns “/” for HTML5 platform and the game can’t find config by this folder. I’ve checked sources and here is written the following:

/** Get the directory the application was launched from (useful for commandline utilities) /
static const TCHAR
LaunchDir(); (from Engine\Source\Runtime\Core\Public\GenericPlatform\GenericPlatformMisc.h)

/** Get the current working directory (only really makes sense on desktop platforms) */
FString FWindowsPlatformProcess::GetCurrentWorkingDirectory() (from Engine\Source\Runtime\Core\Private\Windows\WindowsPlatformProcess.cpp; this method is used by FGenericPlatformMisc::LaunchDir())

How can I read my *.ini file, which must be placed in the same folder with packaged *.html file of my game?

Thank you in advance for your replies!

So, I’ve found some solution. It isn’t ideal, but…

1. My BPFL_FileIO.h

#include "FileHelper.h"
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "PlatformFilemanager.h"
#include "BPFL_FileIO.generated.h"


/**
 * The code was taken from https://www.programmersought.com/article/17736078269/
 */
UCLASS()
class VIRTUALSIMULATION_API UBPFL_FileIO : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
		
		//Configuration file read
		UFUNCTION(BlueprintCallable, Category = "Config access")
		static FString ReadConfig(FString section, FString name, FString path);
};

2. My BPFL_FileIO.cpp

#include "BPFL_FileIO.h"
#include <ConfigCacheIni.h>
#include <Engine.h>

FString UBPFL_FileIO::ReadConfig(FString section, FString name, FString path) {
	FString result;

    path = FPaths::ConvertRelativePathToFull(FPaths::LaunchDir() + path);
	FPaths::RemoveDuplicateSlashes(path);
	FPaths::NormalizeFilename(path);

	if (!path.Contains(".ini")) {
		path += ".ini";
	}

	if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*path)) {
		GEngine->AddOnScreenDebugMessage(-1, 50.f, FColor::Red, TEXT("Couldn't find configuration file: " + path));
		return FString();
	}
	GConfig->GetString(*section, *name, result, *path);
	return result;
}

3. This method is used in the following way
изображение

4. Package the project for HTML5 platform with Shipping configuration.
5. Edit <ProjectName>-HTML5-Shipping.data.js file of the packaged project.

5.1. After the line (probably, you can do it and before, but I’m not sure)

var Module = typeof Module !== 'undefined' ? Module : {};

add the following

const HOST = 'host_address';
const PORT = 8000;

const configContentTemplate =
`[Server]
HOST=${HOST}
PORT=${PORT}`;

I’ve set my host and port to separate variables to attract attention for future updates of this fields (this was my goal - easy way to change this values for already packaged project).
After that define the structure of your *.ini (the file extension isn’t important) file. You can see that I’ve defined it like above in the topic.

5.2. After that add the last additional line

FS.createDataFile(".", "config.ini", configContentTemplate, true, true);
// or Module['FS_createDataFile'](".", "config.ini", configContentTemplate, true, true);

I’ve add this after the lines, which looks like

Module['FS_createPath']('/ProjectName', 'Content', true, true);

but probably it’s not important.

Some explanation about FS.createDataFile

  • First parameter is the path to the directory of the file. I create my config in the root - “.”.
    You can define other paths (like as in Module[‘FS_createPath’]), but it will depend on the path, which you select from FPaths:: in your BPFL_FileIO.cpp. I’ve selected FPaths::LaunchDir(), and as I’ve said above it returns “/” for HTML5 platform.
  • Next parameter is the name of your file. It must be the same as that which you set in call of ReadConfig node (not only the name, but full path). So, if you call ReadConfig with configs/config.ini, you need also set the second parameter of the FS.createDataFile to “configs/config.ini”.
  • About other parameters please read docs of emscripten.
  • FS.createDataFile won’t create the file in the real file system. As I’ve understood, it will just make file visible for WebAssembly in the path, in which you expect to find them in your C++ code. Also it doesn’t need your config file be presented in the file system. However, I don’t know how it exactly works.
  • I’ve tried to use FS.readFile, but it doesn’t read the file from the file system…

So, this solution isn’t ideal, I can’t read data from some config.ini file from the directory of my packaged project, but it isn’t a problem to edit HOST and PORT variables in the *.data.js file, which can be edited by simple code redactor.

P.S. For Windows platform this C++ class will work without any problems. Just place you config file in the same folder with your .exe file (or in the subdirectory of this directory, it depends on the path, which you set in ReadConfig node).