UnrealVersionSelector

UnrealVersionSelector是 UE 的一个工具,用来关联 uproject 文件、注册引擎、选择引擎版本、生成解决方案等功能的一个工具。
在对 uproject 文件点击右键时,Switch Unreal Engine Version等选项就是 UnrealVersionSelector 提供的:

UnrealVersionSelector 的参数

  • -register:Add the current directory to the list of installations
  • -fileassociations:Update all the settings.
  • -switchversion:Associate with an engine label
  • -switchversionsilent:Associate with a specific engine label
  • -editor:Open a project with the editor
  • -projectlist:Open the editor
  • -game:Play a game using the editor executable
  • -projectfiles:Generate Visual Studio project files

注册 UnrealVersionSelector

如果遇到 uproject 没有文件关联的情况可以使用下列注册表修复文件关联。

保存为.reg,双击合并即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.uproject]
@="Unreal.ProjectFile"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile]
@="Unreal Engine Project File"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\DefaultIcon]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell]

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\open]
@="Open"

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\open\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /editor \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\run]
@="Launch game"
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\run\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /game \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj]
@="Generate Visual Studio project files"
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\rungenproj\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /projectfiles \"%1\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\switchversion]
@="Switch Unreal Engine version..."
"Icon"="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\""

[HKEY_CLASSES_ROOT\Unreal.ProjectFile\shell\switchversion\command]
@="\"C:\\Program Files (x86)\\Epic Games\\Launcher\\Engine\\Binaries\\Win64\\UnrealVersionSelector.exe\" /switchversion \"%1\""

获取所有已注册的引擎

可以使用 IDesktopPlatform::EnumerateEngineInstallations 来获取当前系统中所有从启动器安装、以及用户使用 UnrealVersionSelector 注册的引擎版本。

1
2
TMap<FString,FString> Installations;
FDesktopPlatformModule::Get()->EnumerateEngineInstallations(Installations)

注册引擎

使用 UnrealVersionSelector 注册引擎时,会将注册的引擎路径写入到注册表:

1
HKEY_CURRENT_USER\SOFTWARE\Epic Games\Unreal Engine\Builds

其注册表项的 lpValueName 则是生成的一个 GUID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DesktopPlatfromWindows.cpp
bool FDesktopPlatformWindows::RegisterEngineInstallation(const FString &RootDir, FString &OutIdentifier)
{
bool bRes = false;
if(IsValidRootDirectory(RootDir))
{
HKEY hRootKey;
if(RegCreateKeyEx(HKEY_CURRENT_USER, InstallationsSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hRootKey, NULL) == ERROR_SUCCESS)
{
FString NewIdentifier = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphensInBraces);
LRESULT SetResult = RegSetValueEx(hRootKey, *NewIdentifier, 0, REG_SZ, (const BYTE*)*RootDir, (RootDir.Len() + 1) * sizeof(TCHAR));
RegCloseKey(hRootKey);

if(SetResult == ERROR_SUCCESS)
{
OutIdentifier = NewIdentifier;
bRes = true;
}
}
}
return bRes;
}

UE 并没有直接在 IDesktopPlatfrom单独提供 获取用户通过使用 UnrealVersionSelector 注册的引擎列表。
但是在 EnumerateEngineInstallations 中写了从注册表获取 per-user installations 引擎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void FDesktopPlatformWindows::EnumerateEngineInstallations(TMap<FString, FString> &OutInstallations)
{
// Enumerate the binary installations
EnumerateLauncherEngineInstallations(OutInstallations);

// Enumerate the per-user installations
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, InstallationsSubKey, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
{
// Get a list of all the directories
TArray<FString> UniqueDirectories;
OutInstallations.GenerateValueArray(UniqueDirectories);

// Enumerate all the installations
TArray<FString> InvalidKeys;
for (::DWORD Index = 0;; Index++)
{
TCHAR ValueName[256];
TCHAR ValueData[MAX_PATH];
::DWORD ValueType = 0;
::DWORD ValueNameLength = sizeof(ValueName) / sizeof(ValueName[0]);
::DWORD ValueDataSize = sizeof(ValueData);

LRESULT Result = RegEnumValue(hKey, Index, ValueName, &ValueNameLength, NULL, &ValueType, (BYTE*)&ValueData[0], &ValueDataSize);
if(Result == ERROR_SUCCESS)
{
int32 ValueDataLength = ValueDataSize / sizeof(TCHAR);
if(ValueDataLength > 0 && ValueData[ValueDataLength - 1] == 0) ValueDataLength--;

FString NormalizedInstalledDirectory(ValueDataLength, ValueData);
FPaths::NormalizeDirectoryName(NormalizedInstalledDirectory);
FPaths::CollapseRelativeDirectories(NormalizedInstalledDirectory);

if(IsValidRootDirectory(NormalizedInstalledDirectory) && !UniqueDirectories.Contains(NormalizedInstalledDirectory))
{
OutInstallations.Add(ValueName, NormalizedInstalledDirectory);
UniqueDirectories.Add(NormalizedInstalledDirectory);
}
else
{
InvalidKeys.Add(ValueName);
}
}
else if(Result == ERROR_NO_MORE_ITEMS)
{
break;
}
}

// Remove all the keys which weren't valid
for(const FString InvalidKey: InvalidKeys)
{
RegDeleteValue(hKey, *InvalidKey);
}

RegCloseKey(hKey);
}
}

Launcher 版引擎注册表路径

上面提到,如果非源码版引擎,使用 UnrealVersionSelector 注册时会写入到注册表:

1
HKEY_CURRENT_USER\SOFTWARE\Epic Games\Unreal Engine\Builds

从 EpicGameLauncher 安装的版本则会写入到另一个注册表路径下:

1
HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine

其值为(从注册表导出的):

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\Unreal Engine\4.18]
"InstalledDirectory"="C:\\Program Files\\Epic Games\\UE_4.18"

UE 提供了方法来得到从 EpicGameLauncher 安装的引擎 (通过IDesktopPlatfrom 接口):

1
2
TMap<FString,FString> Installations;
FDesktopPlatformModule::Get()->EnumerateLauncherEngineInstallations(OutInstallations);

注册编译版引擎的安装路径

UE 提供了UnrealVersionSelector 工具用来选择引擎版本 / 生成 sln 等功能,其也可以用来注册本地的引擎 (非 Launcher 安装的引擎,如自己编译的源码版引擎) 到项目文件的右键 Select Unreal Engine Version... 列表中。
简单分析了一下 UnrealVersionSelector 的代码,它的注册流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WinMain_Func=>start: WinMain
Main_Func=>operation: Main(Arguments)
RegisterCurrentEngineDirectoryWithPrompt_Func=>operation: RegisterCurrentEngineDirectoryWithPrompt()
RegisterCurrentEngineDirectory_Func=>operation: RegisterCurrentEngineDirectory(false)
GetEngineIdentifierFromRootDir_Func=>operation: FDesktopPlatformModule::Get()->GetEngineIdentifierFromRootDir(EngineRootDir, Identifier)
RegisterEngineInstallation_Func=>operation: RegisterEngineInstallation(RootDir, OutIdentifier)
end=>end: End

WinMain_Func->Main_Func
Main_Func->RegisterCurrentEngineDirectoryWithPrompt_Func
RegisterCurrentEngineDirectoryWithPrompt_Func->RegisterCurrentEngineDirectory_Func
RegisterCurrentEngineDirectory_Func->GetEngineIdentifierFromRootDir_Func
GetEngineIdentifierFromRootDir_Func->RegisterEngineInstallation_Func
RegisterEngineInstallation_Func->end

向注册表写入的操作在 FDesktopPlatformWindows::RegisterEngineInstallation 这个函数中。
操作为将注册的引擎目录写入到 HKEY_CURRENT_USER\Software\Epic Games\Unreal Engine\Builds 中的字符串项中。
当右键 Switch Unreal Engine Version... 的时候,会修改 .uproject 文件中的 EngineAssociation 值:

其调用流如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WinMain_Func=>start: WinMain
Main_Func=>operation: Main(Arguments)
SwitchVersion_Func=>operation: SwitchVersion(const FString& ProjectFileName)
GetEngineIdentifierForProject_fUNC=>operation: FDesktopPlatformModule::Get()->GetEngineIdentifierForProject(ProjectFileName, Identifier) // 获取当前选择的引擎
SelectEngineInstallation_Func=>operation: FPlatformInstallation::SelectEngineInstallation(Identifier) // 弹出选择引擎列表框,当前选择的为默认
SetEngineIdentifierForProject_func=>operation: FDesktopPlatformModule::Get()->SetEngineIdentifierForProject(ProjectFileName, Identifier) // 设置选择的引擎,并写入 uproject 文件
ContentOnly=>condition: Is Content Only Project?
GenerateProjectFiles_func=>operation: GenerateProjectFiles(ProjectFileName)
end=>end: End


WinMain_Func->Main_Func
Main_Func->SwitchVersion_Func
SwitchVersion_Func->GetEngineIdentifierForProject_fUNC
GetEngineIdentifierForProject_fUNC->SelectEngineInstallation_Func
SelectEngineInstallation_Func->SetEngineIdentifierForProject_func
SetEngineIdentifierForProject_func->ContentOnly
ContentOnly(yes)->end
ContentOnly(no)->GenerateProjectFiles_func
GenerateProjectFiles_func->end

执行项目文件写入的操作在 FDesktopPlatformBase::SetEngineIdentifierForProject 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool FDesktopPlatformBase::SetEngineIdentifierForProject(const FString &ProjectFileName, const FString &InIdentifier)
{
// Load the project file
TSharedPtr<FJsonObject> ProjectFile = LoadProjectFile(ProjectFileName);
if (!ProjectFile.IsValid())
{
return false;
}

// Check if the project is a non-foreign project of the given engine installation. If so, blank the identifier
// string to allow portability between source control databases. GetEngineIdentifierForProject will translate
// the association back into a local identifier on other machines or syncs.
FString Identifier = InIdentifier;
if(Identifier.Len() > 0)
{
FString RootDir;
if(GetEngineRootDirFromIdentifier(Identifier, RootDir))
{
const FUProjectDictionary &Dictionary = GetCachedProjectDictionary(RootDir);
if(!Dictionary.IsForeignProject(ProjectFileName))
{
Identifier.Empty();
}
}
}

// Set the association on the project and save it
ProjectFile->SetStringField(TEXT("EngineAssociation"), Identifier);
return SaveProjectFile(ProjectFileName, ProjectFile);
}

所以如果你选择了一个项目使用编译版引擎,可以使用文本编辑器打开该项目的 .uproject 文件,可以看到其中的 EngineAssociation 项的值就是注册表中的引擎版本值。

注:UnrealVersionSelector所支持的命令行参数及其用途在UnrealVersionSelector.cpp#L224:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int Main(const TArray<FString>& Arguments)
{
bool bRes = false;
if (Arguments.Num() == 0)
{
// Add the current directory to the list of installations
bRes = RegisterCurrentEngineDirectoryWithPrompt();
}
else if (Arguments.Num() == 1 && Arguments[0] == TEXT("-register"))
{
// Add the current directory to the list of installations
bRes = RegisterCurrentEngineDirectory(true);
}
else if (Arguments.Num() == 1 && Arguments[0] == TEXT("-fileassociations"))
{
// Update all the settings.
bRes = UpdateFileAssociations();
}
else if (Arguments.Num() == 2 && Arguments[0] == TEXT("-switchversion"))
{
// Associate with an engine label
bRes = SwitchVersion(Arguments[1]);
}
else if (Arguments.Num() == 3 && Arguments[0] == TEXT("-switchversionsilent"))
{
// Associate with a specific engine label
bRes = SwitchVersionSilent(Arguments[1], Arguments[2]);
}
else if (Arguments.Num() == 2 && Arguments[0] == TEXT("-editor"))
{
// Open a project with the editor
bRes = LaunchEditor(Arguments[1], L"");
}
else if (Arguments.Num() == 2 && Arguments[0] == TEXT("-game"))
{
// Play a game using the editor executable
bRes = LaunchEditor(Arguments[1], L"-game");
}
else if (Arguments.Num() == 2 && Arguments[0] == TEXT("-projectfiles"))
{
// Generate Visual Studio project files
bRes = GenerateProjectFiles(Arguments[1]);
}
else
{
// Invalid command line
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Invalid command line"), NULL);
}
return bRes ? 0 : 1;
}

解决.uproject 右键生成失败

首先要查看右键菜单所使用的 UnrealVersionSelector.exe 的路径,可以打开注册表:

1
HKEY_CLASSES_ROOT\Unreal.ProjectFile

然后找到 shell\open\command 条目查看 UnrealVersionSelector.exe 的位置(如果没有安装过源码版引擎则默认在 EpicGamesLauncher 的 Binaries 路径下),如:

1
"C:\Program Files (x86)\Epic Games\Launcher\Engine\Binaries\Win64\UnrealVersionSelector.exe" /editor "%1"

然后打开 C:\Program Files (x86)\Epic Games\Launcher\Engine\Binaries\Win64,将UnrealVersionSelector.exe 设置为以管理员权限启动即可(右键 - 属性 - 兼容性 - 以管理员身份运行此程序)。