You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
20 KiB
C++
640 lines
20 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
|
|
#include "ProjectDefines.h"
|
|
|
|
#if defined(CRY_ENABLE_RC_HELPER)
|
|
|
|
#include "ResourceCompilerHelper.h"
|
|
#include "EngineSettingsManager.h"
|
|
|
|
// When complining CryTiffPlugin the mayaAssert.h is included that defined
|
|
// Assert as _Assert. This wreaks havoc with AZ_Assert since under the covers
|
|
// it calls AzCore::Debug::Trace::Assert, which gets transformed bo
|
|
// Trace::_Assert, which does not exist. Gotta love macros. Undefine Assert
|
|
// before we include semaphore so that it can compile correctly
|
|
#if defined(Assert)
|
|
#undef Assert
|
|
#endif
|
|
|
|
#include <AzCore/std/parallel/semaphore.h>
|
|
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
|
#include <AzCore/std/string/string_view.h>
|
|
#include <AzCore/Component/ComponentApplicationBus.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
#include <windows.h>
|
|
#include <shellapi.h> // ShellExecuteW()
|
|
#endif
|
|
|
|
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
|
#include "AppleSpecific.h"
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#else
|
|
#undef RC_EXECUTABLE
|
|
#define RC_EXECUTABLE "rc.exe"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <string> // lawsonn - we use std::string internally
|
|
#include <sstream>
|
|
|
|
namespace
|
|
{
|
|
class LineStreamBuffer
|
|
{
|
|
public:
|
|
template <typename T>
|
|
LineStreamBuffer(T* object, void (T::* method)(const char* line))
|
|
: m_charCount(0)
|
|
, m_bTruncated(false)
|
|
{
|
|
m_target = new Target<T>(object, method);
|
|
}
|
|
|
|
~LineStreamBuffer()
|
|
{
|
|
Flush();
|
|
delete m_target;
|
|
}
|
|
|
|
void HandleText(const char* text, int length)
|
|
{
|
|
const char* pos = text;
|
|
while (pos - text < length)
|
|
{
|
|
const char* start = pos;
|
|
|
|
while (pos - text < length && *pos != '\n' && *pos != '\r')
|
|
{
|
|
++pos;
|
|
}
|
|
|
|
size_t n = pos - start;
|
|
if (m_charCount + n > kMaxCharCount)
|
|
{
|
|
n = kMaxCharCount - m_charCount;
|
|
m_bTruncated = true;
|
|
}
|
|
memcpy(&m_buffer[m_charCount], start, n);
|
|
m_charCount += n;
|
|
|
|
if (pos - text < length)
|
|
{
|
|
Flush();
|
|
while (pos - text < length && (*pos == '\n' || *pos == '\r'))
|
|
{
|
|
++pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
if (m_charCount > 0)
|
|
{
|
|
m_buffer[m_charCount] = 0;
|
|
m_target->Call(m_buffer);
|
|
m_charCount = 0;
|
|
}
|
|
}
|
|
|
|
bool IsTruncated() const
|
|
{
|
|
return m_bTruncated;
|
|
}
|
|
|
|
private:
|
|
struct ITarget
|
|
{
|
|
virtual ~ITarget() {}
|
|
virtual void Call(const char* line) = 0;
|
|
};
|
|
template <typename T>
|
|
struct Target
|
|
: public ITarget
|
|
{
|
|
public:
|
|
Target(T* object, void (T::* method)(const char* line))
|
|
: object(object)
|
|
, method(method) {}
|
|
virtual void Call(const char* line)
|
|
{
|
|
(object->*method)(line);
|
|
}
|
|
private:
|
|
T* object;
|
|
void (T::* method)(const char* line);
|
|
};
|
|
|
|
ITarget* m_target;
|
|
size_t m_charCount;
|
|
static const size_t kMaxCharCount = 2047;
|
|
char m_buffer[kMaxCharCount + 1];
|
|
bool m_bTruncated;
|
|
};
|
|
|
|
#if !defined(AZ_PLATFORM_WINDOWS)
|
|
void MessageBoxW(int, const wchar_t* header, const wchar_t* message, unsigned long)
|
|
{
|
|
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
|
CFStringEncoding encoding = (CFByteOrderLittleEndian == CFByteOrderGetCurrent()) ?
|
|
kCFStringEncodingUTF32LE : kCFStringEncodingUTF32BE;
|
|
CFStringRef header_ref = CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(header), wcslen(header) * sizeof(wchar_t), encoding, false);
|
|
CFStringRef message_ref = CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(message), wcslen(message) * sizeof(wchar_t), encoding, false);
|
|
|
|
CFOptionFlags result; //result code from the message box
|
|
|
|
CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, 0, 0, 0, header_ref, message_ref, 0, 0, 0, &result);
|
|
|
|
CFRelease(header_ref);
|
|
CFRelease(message_ref);
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
namespace
|
|
{
|
|
class RcLock
|
|
{
|
|
public:
|
|
RcLock()
|
|
: m_cs(0u, 1u)
|
|
{
|
|
m_cs.release();
|
|
}
|
|
~RcLock()
|
|
{
|
|
}
|
|
|
|
void Lock()
|
|
{
|
|
m_cs.acquire();
|
|
}
|
|
void Unlock()
|
|
{
|
|
m_cs.release();
|
|
}
|
|
|
|
private:
|
|
AZStd::semaphore m_cs;
|
|
};
|
|
|
|
|
|
template<class LockClass>
|
|
class RcAutoLock
|
|
{
|
|
public:
|
|
RcAutoLock(LockClass& lock)
|
|
: m_lock(lock)
|
|
{
|
|
m_lock.Lock();
|
|
}
|
|
~RcAutoLock()
|
|
{
|
|
m_lock.Unlock();
|
|
}
|
|
|
|
private:
|
|
RcAutoLock();
|
|
RcAutoLock(const RcAutoLock<LockClass>&);
|
|
RcAutoLock<LockClass>& operator =(const RcAutoLock<LockClass>&);
|
|
|
|
private:
|
|
LockClass& m_lock;
|
|
};
|
|
|
|
|
|
HANDLE s_rcProcessHandle = 0;
|
|
RcLock s_rcProcessHandleLock;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
static void ShowMessageBoxRcNotFound([[maybe_unused]] const wchar_t* const szCmdLine, [[maybe_unused]] const wchar_t* const szDir)
|
|
{
|
|
SettingsManagerHelpers::CFixedString<wchar_t, MAX_PATH* 4 + 150> tmp;
|
|
|
|
tmp.append(L"The resource compiler (RC.EXE) was not found.");
|
|
MessageBoxW(0, tmp.c_str(), L"Error", MB_ICONERROR | MB_OK);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
class ResourceCompilerLineHandler
|
|
{
|
|
public:
|
|
ResourceCompilerLineHandler(IResourceCompilerListener* listener)
|
|
: m_listener(listener)
|
|
{
|
|
}
|
|
|
|
void HandleLine(const char* line)
|
|
{
|
|
if (!m_listener || !line)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// check the first three characters to see if it's a warning or error.
|
|
bool bHasPrefix;
|
|
IResourceCompilerListener::MessageSeverity severity;
|
|
if ((line[0] == 'E') && (line[1] == ':') && (line[2] == ' '))
|
|
{
|
|
bHasPrefix = true;
|
|
severity = IResourceCompilerListener::MessageSeverity_Error;
|
|
line += 3; // skip the prefix
|
|
}
|
|
else if ((line[0] == 'W') && (line[1] == ':') && (line[2] == ' '))
|
|
{
|
|
bHasPrefix = true;
|
|
severity = IResourceCompilerListener::MessageSeverity_Warning;
|
|
line += 3; // skip the prefix
|
|
}
|
|
else if ((line[0] == ' ') && (line[1] == ' ') && (line[2] == ' '))
|
|
{
|
|
bHasPrefix = true;
|
|
severity = IResourceCompilerListener::MessageSeverity_Info;
|
|
line += 3; // skip the prefix
|
|
}
|
|
else
|
|
{
|
|
bHasPrefix = false;
|
|
severity = IResourceCompilerListener::MessageSeverity_Info;
|
|
}
|
|
|
|
if (bHasPrefix)
|
|
{
|
|
// skip thread info "%d>", if present
|
|
{
|
|
const char* p = line;
|
|
while (*p == ' ')
|
|
{
|
|
++p;
|
|
}
|
|
if (isdigit(*p))
|
|
{
|
|
while (isdigit(*p))
|
|
{
|
|
++p;
|
|
}
|
|
if (*p == '>')
|
|
{
|
|
line = p + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip time info "%d:%d", if present
|
|
{
|
|
const char* p = line;
|
|
while (*p == ' ')
|
|
{
|
|
++p;
|
|
}
|
|
if (isdigit(*p))
|
|
{
|
|
while (isdigit(*p))
|
|
{
|
|
++p;
|
|
}
|
|
if (*p == ':')
|
|
{
|
|
++p;
|
|
if (isdigit(*p))
|
|
{
|
|
while (isdigit(*p))
|
|
{
|
|
++p;
|
|
}
|
|
while (*p == ' ')
|
|
{
|
|
++p;
|
|
}
|
|
line = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_listener->OnRCMessage(severity, line);
|
|
}
|
|
|
|
private:
|
|
IResourceCompilerListener* m_listener;
|
|
};
|
|
|
|
// we now support macros like #ENGINEROOT# in the string:
|
|
void replaceAllInStringInPlace(std::string& inOut, const char* findValue, const char* replaceValue)
|
|
{
|
|
if (!findValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!replaceValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::string::size_type pos = std::string::npos;
|
|
std::string::size_type replaceLen = strlen(findValue);
|
|
|
|
while ((pos = inOut.find(findValue)) != std::string::npos)
|
|
{
|
|
inOut.replace(pos, replaceLen, replaceValue);
|
|
}
|
|
}
|
|
|
|
// given a string that contains macros (like #ENGINEROOT#), eliminate the macros and replace them with the real data.
|
|
// note that in the 'remote' implementation, these macros are sent to the remote RC. It can then expand them for its own environment
|
|
// but in a local RC, these macros are expanded by the local environment.
|
|
void expandMacros(const char* inputString, char* outputString, std::size_t bufferSize)
|
|
{
|
|
if (!inputString)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!outputString)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::string_view rootFolder;
|
|
AZ::ComponentApplicationBus::BroadcastResult(rootFolder, &AZ::ComponentApplicationRequests::GetAppRoot);
|
|
|
|
std::string finalString(inputString);
|
|
const AZStd::string rootFolderStr = rootFolder.data();
|
|
replaceAllInStringInPlace(finalString, "#ENGINEROOT#", rootFolderStr.c_str());
|
|
// put additional replacements here.
|
|
|
|
azstrcpy(outputString, bufferSize, finalString.c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
IResourceCompilerHelper::ERcCallResult CResourceCompilerHelper::CallResourceCompiler(
|
|
const char* szFileName,
|
|
const char* szAdditionalSettings,
|
|
IResourceCompilerListener* listener,
|
|
bool bMayShowWindow,
|
|
bool bSilent,
|
|
bool bNoUserDialog,
|
|
const wchar_t* szWorkingDirectory,
|
|
[[maybe_unused]] const wchar_t* szRootPath)
|
|
{
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
HANDLE hChildStdOutRd = INVALID_HANDLE_VALUE, hChildStdOutWr = INVALID_HANDLE_VALUE;
|
|
HANDLE hChildStdInRd = INVALID_HANDLE_VALUE, hChildStdInWr = INVALID_HANDLE_VALUE;
|
|
PROCESS_INFORMATION pi;
|
|
#else
|
|
FILE* hChildStdOutRd;
|
|
#endif
|
|
|
|
{
|
|
RcAutoLock<RcLock> lock(s_rcProcessHandleLock);
|
|
|
|
// make command for execution
|
|
SettingsManagerHelpers::CFixedString<wchar_t, MAX_PATH* 3> wRemoteCmdLine;
|
|
|
|
|
|
if (!szAdditionalSettings)
|
|
{
|
|
szAdditionalSettings = "";
|
|
}
|
|
|
|
// expand the additioanl settings.
|
|
char szActualFileName[512] = {0};
|
|
char szActualAdditionalSettings[512] = {0};
|
|
|
|
expandMacros(szFileName, szActualFileName, 512);
|
|
expandMacros(szAdditionalSettings, szActualAdditionalSettings, 512);
|
|
|
|
CSettingsManagerTools smTools = CSettingsManagerTools(); // moved this line to after macro expansion to avoid multiple of these existing at once.
|
|
|
|
AZStd::string_view exeFolderName;
|
|
AZ::ComponentApplicationBus::BroadcastResult(exeFolderName, &AZ::ComponentApplicationRequests::GetExecutableFolder);
|
|
|
|
wchar_t szRegSettingsBuffer[1024];
|
|
smTools.GetEngineSettingsManager()->GetValueByRef("RC_Parameters", SettingsManagerHelpers::CWCharBuffer(szRegSettingsBuffer, sizeof(szRegSettingsBuffer)));
|
|
bool enableSourceControl = true;
|
|
smTools.GetEngineSettingsManager()->GetValueByRef("RC_EnableSourceControl", enableSourceControl);
|
|
|
|
wRemoteCmdLine.appendAscii("\"");
|
|
wRemoteCmdLine.appendAscii(exeFolderName.data(), exeFolderName.size());
|
|
wRemoteCmdLine.appendAscii("/");
|
|
wRemoteCmdLine.appendAscii(RC_EXECUTABLE);
|
|
wRemoteCmdLine.appendAscii("\"");
|
|
|
|
if (!enableSourceControl)
|
|
{
|
|
wRemoteCmdLine.appendAscii(" -nosourcecontrol ");
|
|
}
|
|
|
|
if (!szFileName)
|
|
{
|
|
wRemoteCmdLine.appendAscii(" -userdialog=0 ");
|
|
wRemoteCmdLine.appendAscii(szActualAdditionalSettings);
|
|
wRemoteCmdLine.appendAscii(" ");
|
|
wRemoteCmdLine.append(szRegSettingsBuffer);
|
|
}
|
|
else
|
|
{
|
|
wRemoteCmdLine.appendAscii(" \"");
|
|
wRemoteCmdLine.appendAscii(szActualFileName);
|
|
wRemoteCmdLine.appendAscii("\"");
|
|
wRemoteCmdLine.appendAscii(bNoUserDialog ? " -userdialog=0 " : " -userdialog=1 ");
|
|
wRemoteCmdLine.appendAscii(szActualAdditionalSettings);
|
|
wRemoteCmdLine.appendAscii(" ");
|
|
wRemoteCmdLine.append(szRegSettingsBuffer);
|
|
}
|
|
|
|
// Create a pipe to read the stdout of the RC.
|
|
SECURITY_ATTRIBUTES saAttr;
|
|
if (listener)
|
|
{
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
ZeroMemory(&saAttr, sizeof(saAttr));
|
|
saAttr.bInheritHandle = TRUE;
|
|
saAttr.lpSecurityDescriptor = 0;
|
|
CreatePipe(&hChildStdOutRd, &hChildStdOutWr, &saAttr, 0);
|
|
SetHandleInformation(hChildStdOutRd, HANDLE_FLAG_INHERIT, 0); // Need to do this according to MSDN
|
|
CreatePipe(&hChildStdInRd, &hChildStdInWr, &saAttr, 0);
|
|
SetHandleInformation(hChildStdInWr, HANDLE_FLAG_INHERIT, 0); // Need to do this according to MSDN
|
|
#endif
|
|
}
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
STARTUPINFOW si;
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
si.dwX = 100;
|
|
si.dwY = 100;
|
|
if (listener)
|
|
{
|
|
si.hStdError = hChildStdOutWr;
|
|
si.hStdOutput = hChildStdOutWr;
|
|
si.hStdInput = hChildStdInRd;
|
|
si.dwFlags = STARTF_USEPOSITION | STARTF_USESTDHANDLES;
|
|
}
|
|
else
|
|
{
|
|
si.dwFlags = STARTF_USEPOSITION;
|
|
}
|
|
|
|
ZeroMemory(&pi, sizeof(pi));
|
|
#endif
|
|
|
|
bool bShowWindow;
|
|
if (bMayShowWindow)
|
|
{
|
|
wchar_t buffer[20];
|
|
smTools.GetEngineSettingsManager()->GetValueByRef("ShowWindow", SettingsManagerHelpers::CWCharBuffer(buffer, sizeof(buffer)));
|
|
bShowWindow = (wcscmp(buffer, L"true") == 0);
|
|
}
|
|
else
|
|
{
|
|
bShowWindow = false;
|
|
}
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
const wchar_t* szStartingDirectory = szWorkingDirectory;
|
|
if (!szStartingDirectory)
|
|
{
|
|
char currentDirectory[MAX_PATH];
|
|
AZ::Utils::GetExecutableDirectory(currentDirectory, MAX_PATH);
|
|
SettingsManagerHelpers::CFixedString<wchar_t, MAX_PATH> wCurrentDirectory;
|
|
wCurrentDirectory.appendAscii(currentDirectory);
|
|
szStartingDirectory = wCurrentDirectory.c_str();
|
|
}
|
|
|
|
|
|
if (!CreateProcessW(
|
|
NULL, // No module name (use command line).
|
|
const_cast<wchar_t*>(wRemoteCmdLine.c_str()), // Command line.
|
|
NULL, // Process handle not inheritable.
|
|
NULL, // Thread handle not inheritable.
|
|
TRUE, // Set handle inheritance to TRUE.
|
|
bShowWindow ? 0 : CREATE_NO_WINDOW, // creation flags.
|
|
NULL, // Use parent's environment block.
|
|
szStartingDirectory, // Set starting directory.
|
|
&si, // Pointer to STARTUPINFO structure.
|
|
&pi)) // Pointer to PROCESS_INFORMATION structure.
|
|
{
|
|
// The following code block is commented out instead of being deleted
|
|
// because it's good to have at hand for a debugging session.
|
|
#if 0
|
|
const size_t charsInMessageBuffer = 32768; // msdn about FormatMessage(): "The output buffer cannot be larger than 64K bytes."
|
|
wchar_t szMessageBuffer[charsInMessageBuffer] = L"";
|
|
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, szMessageBuffer, charsInMessageBuffer, NULL);
|
|
GetCurrentDirectoryW(charsInMessageBuffer, szMessageBuffer);
|
|
#endif
|
|
|
|
if (!bSilent)
|
|
{
|
|
ShowMessageBoxRcNotFound(wRemoteCmdLine.c_str(), szStartingDirectory);
|
|
}
|
|
|
|
return eRcCallResult_notFound;
|
|
}
|
|
|
|
s_rcProcessHandle = pi.hProcess;
|
|
#else
|
|
int fd = open(".", O_RDONLY);
|
|
char remoteCmdLineUtf8[MAX_PATH * 8];
|
|
char workingDirectory[MAX_PATH * 8];
|
|
ConvertUtf16ToUtf8(wRemoteCmdLine.c_str(), SettingsManagerHelpers::CCharBuffer(remoteCmdLineUtf8, MAX_PATH * 8));
|
|
if (szWorkingDirectory)
|
|
{
|
|
ConvertUtf16ToUtf8(szWorkingDirectory, SettingsManagerHelpers::CCharBuffer(workingDirectory, MAX_PATH * 8));
|
|
chdir(workingDirectory);
|
|
}
|
|
hChildStdOutRd = popen(remoteCmdLineUtf8, "r");
|
|
fchdir(fd);
|
|
if (hChildStdOutRd == nullptr)
|
|
{
|
|
if (!bSilent)
|
|
{
|
|
ShowMessageBoxRcNotFound(wRemoteCmdLine.c_str(), szWorkingDirectory);
|
|
}
|
|
return eRcCallResult_notFound;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool bFailedToReadOutput = false;
|
|
|
|
if (listener)
|
|
{
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
// Close the pipe that writes to the child process, since we don't actually have any input for it.
|
|
CloseHandle(hChildStdInWr);
|
|
|
|
// Read all the output from the child process.
|
|
CloseHandle(hChildStdOutWr);
|
|
#endif
|
|
ResourceCompilerLineHandler lineHandler(listener);
|
|
LineStreamBuffer lineBuffer(&lineHandler, &ResourceCompilerLineHandler::HandleLine);
|
|
for (;; )
|
|
{
|
|
char buffer[2048];
|
|
DWORD bytesRead;
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
if (!ReadFile(hChildStdOutRd, buffer, sizeof(buffer), &bytesRead, NULL) || (bytesRead == 0))
|
|
#else
|
|
if (fgets(buffer, sizeof(buffer), hChildStdOutRd) == nullptr || (bytesRead = strlen(buffer) == 0))
|
|
#endif
|
|
{
|
|
break;
|
|
}
|
|
lineBuffer.HandleText(buffer, bytesRead);
|
|
}
|
|
|
|
bFailedToReadOutput = lineBuffer.IsTruncated();
|
|
}
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
// Wait until child process exits.
|
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
#else
|
|
DWORD exitCode = pclose(hChildStdOutRd);
|
|
#endif
|
|
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
RcAutoLock<RcLock> lock(s_rcProcessHandleLock);
|
|
s_rcProcessHandle = 0;
|
|
|
|
DWORD exitCode = eRcExitCode_Error;
|
|
if (bFailedToReadOutput || GetExitCodeProcess(pi.hProcess, &exitCode) == 0)
|
|
{
|
|
exitCode = eRcExitCode_Error;
|
|
}
|
|
|
|
// Close process and thread handles.
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
#endif
|
|
|
|
return ConvertResourceCompilerExitCodeToResultCode(exitCode);
|
|
}
|
|
|
|
|
|
#endif //(CRY_ENABLE_RC_HELPER)
|