diff --git a/hecl/blender/BlenderConnection.cpp b/hecl/blender/BlenderConnection.cpp index eddcfe50d..fd0be6665 100644 --- a/hecl/blender/BlenderConnection.cpp +++ b/hecl/blender/BlenderConnection.cpp @@ -9,6 +9,11 @@ #include #include "BlenderConnection.hpp" +#if _WIN32 +#include +#include +#endif + namespace HECL { @@ -17,8 +22,6 @@ BlenderConnection* SharedBlenderConnection = nullptr; #ifdef __APPLE__ #define DEFAULT_BLENDER_BIN "/Applications/Blender.app/Contents/MacOS/blender" -#elif _WIN32 -#define DEFAULT_BLENDER_BIN _S("%ProgramFiles%\\Blender Foundation\\Blender\\blender.exe") #else #define DEFAULT_BLENDER_BIN "blender" #endif @@ -26,6 +29,19 @@ BlenderConnection* SharedBlenderConnection = nullptr; extern "C" uint8_t HECL_BLENDERSHELL[]; extern "C" size_t HECL_BLENDERSHELL_SZ; +extern "C" uint8_t HECL_ADDON[]; +extern "C" size_t HECL_ADDON_SZ; + +static bool InstallAddon(const SystemChar* path) +{ + FILE* fp = HECL::Fopen(path, _S("wb")); + if (!fp) + BlenderLog.report(LogVisor::FatalError, _S("Unable to install blender addon at '%s'"), path); + fwrite(HECL_ADDON, 1, HECL_ADDON_SZ, fp); + fclose(fp); + return true; +} + size_t BlenderConnection::_readLine(char* buf, size_t bufSz) { size_t readBytes = 0; @@ -37,15 +53,9 @@ size_t BlenderConnection::_readLine(char* buf, size_t bufSz) *(buf-1) = '\0'; return bufSz - 1; } -#if _WIN32 - DWORD ret = 0; - if (!ReadFile(m_readpipe[0], buf, 1, &ret, NULL)) - goto err; -#else - ssize_t ret = read(m_readpipe[0], buf, 1); + int ret = read(m_readpipe[0], buf, 1); if (ret < 0) goto err; -#endif else if (ret == 1) { if (*buf == '\n') @@ -69,21 +79,13 @@ err: size_t BlenderConnection::_writeLine(const char* buf) { -#if _WIN32 - DWORD ret = 0; - if (!WriteFile(m_writepipe[1], buf, strlen(buf), &ret, NULL)) - goto err; - if (!WriteFile(m_writepipe[1], "\n", 1, NULL, NULL)) - goto err; -#else - ssize_t ret, nlerr; + int ret, nlerr; ret = write(m_writepipe[1], buf, strlen(buf)); if (ret < 0) goto err; nlerr = write(m_writepipe[1], "\n", 1); if (nlerr < 0) goto err; -#endif return (size_t)ret; err: BlenderLog.report(LogVisor::FatalError, strerror(errno)); @@ -92,15 +94,9 @@ err: size_t BlenderConnection::_readBuf(char* buf, size_t len) { -#if _WIN32 - DWORD ret = 0; - if (!ReadFile(m_readpipe[0], buf, len, &ret, NULL)) - goto err; -#else - ssize_t ret = read(m_readpipe[0], buf, len); + int ret = read(m_readpipe[0], buf, len); if (ret < 0) goto err; -#endif return ret; err: BlenderLog.report(LogVisor::FatalError, strerror(errno)); @@ -109,15 +105,9 @@ err: size_t BlenderConnection::_writeBuf(const char* buf, size_t len) { -#if _WIN32 - DWORD ret = 0; - if (!WriteFile(m_writepipe[1], buf, len, &ret, NULL)) - goto err; -#else - ssize_t ret = write(m_writepipe[1], buf, len); + int ret = write(m_writepipe[1], buf, len); if (ret < 0) goto err; -#endif return ret; err: BlenderLog.report(LogVisor::FatalError, strerror(errno)); @@ -126,13 +116,8 @@ err: void BlenderConnection::_closePipe() { -#if _WIN32 - CloseHandle(m_readpipe[0]); - CloseHandle(m_writepipe[1]); -#else close(m_readpipe[0]); close(m_writepipe[1]); -#endif } BlenderConnection::BlenderConnection(bool silenceBlender) @@ -155,156 +140,181 @@ BlenderConnection::BlenderConnection(bool silenceBlender) fwrite(HECL_BLENDERSHELL, 1, HECL_BLENDERSHELL_SZ, fp); fclose(fp); - /* Construct communication pipes */ -#if _WIN32 - SECURITY_ATTRIBUTES sattrs = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; - CreatePipe(&m_readpipe[0], &m_readpipe[1], &sattrs, 0); - CreatePipe(&m_writepipe[0], &m_writepipe[1], &sattrs, 0); -#else - pipe(m_readpipe); - pipe(m_writepipe); -#endif + HECL::SystemString blenderAddonPath(TMPDIR); + blenderAddonPath += _S("/hecl_blenderaddon.zip"); - /* User-specified blender path */ -#if _WIN32 - wchar_t BLENDER_BIN_BUF[2048]; - wchar_t* blenderBin = _wgetenv(L"BLENDER_BIN"); -#else - char* blenderBin = getenv("BLENDER_BIN"); -#endif - - /* Child process of blender */ -#if _WIN32 - if (!blenderBin) + int installAttempt = 0; + while (true) { - /* Environment not set; use registry */ - HKEY blenderKey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\BlenderFoundation", 0, KEY_READ, &blenderKey) == ERROR_SUCCESS) + + /* Construct communication pipes */ +#if _WIN32 + _pipe(m_readpipe, 2048, _O_BINARY); + _pipe(m_writepipe, 2048, _O_BINARY); + HANDLE writehandle = HANDLE(_get_osfhandle(m_writepipe[0])); + SetHandleInformation(writehandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + HANDLE readhandle = HANDLE(_get_osfhandle(m_readpipe[1])); + SetHandleInformation(readhandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); +#else + pipe(m_readpipe); + pipe(m_writepipe); +#endif + + /* User-specified blender path */ +#if _WIN32 + wchar_t BLENDER_BIN_BUF[2048]; + wchar_t* blenderBin = _wgetenv(L"BLENDER_BIN"); +#else + char* blenderBin = getenv("BLENDER_BIN"); +#endif + + /* Child process of blender */ +#if _WIN32 + if (!blenderBin) { - DWORD bufSz = sizeof(BLENDER_BIN_BUF); - if (RegGetValueW(blenderKey, NULL, L"Install_Dir", REG_SZ, NULL, BLENDER_BIN_BUF, &bufSz) == ERROR_SUCCESS) - { - wcscat_s(BLENDER_BIN_BUF, 2048, L"\\blender.exe"); - blenderBin = BLENDER_BIN_BUF; - } - RegCloseKey(blenderKey); + /* Environment not set; use default */ + wchar_t progFiles[256]; + if (!GetEnvironmentVariableW(L"ProgramFiles", progFiles, 256)) + BlenderLog.report(LogVisor::FatalError, L"unable to determine 'Program Files' path"); + _snwprintf(BLENDER_BIN_BUF, 2048, L"%s\\Blender Foundation\\Blender\\blender.exe", progFiles); + blenderBin = BLENDER_BIN_BUF; } - } - if (!blenderBin) - { - Log.report(LogVisor::FatalError, "unable to find blender"); - return; - } - wchar_t cmdLine[2048]; - _snwprintf(cmdLine, 2048, L" --background -P shellscript.py -- %08X %08X", - (uint32_t)m_writepipe[0], (uint32_t)m_readpipe[1]); - - STARTUPINFO sinfo = {sizeof(STARTUPINFO)}; - HANDLE nulHandle = NULL; - if (silenceBlender) - { - nulHandle = CreateFileW(L"nul", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sattrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - sinfo.hStdError = nulHandle; - sinfo.hStdOutput = nulHandle; - sinfo.dwFlags = STARTF_USESTDHANDLES; - } - - PROCESS_INFORMATION pinfo; - if (!CreateProcessW(blenderBin, cmdLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &sinfo, &pinfo)) - Log.report(LogVisor::FatalError, "unable to launch blender"); - - CloseHandle(m_writepipe[1]); - CloseHandle(m_readpipe[0]); - - if (nulHandle) - CloseHandle(nulHandle); - -#else - pid_t pid = fork(); - if (!pid) - { - close(m_writepipe[1]); - close(m_readpipe[0]); + wchar_t cmdLine[2048]; + if (installAttempt == 1) + _snwprintf(cmdLine, 2048, L" --background -P \"%s\" -- %d %d \"%s\"", + blenderShellPath.c_str(), int(writehandle), int(readhandle), blenderAddonPath.c_str()); + else + _snwprintf(cmdLine, 2048, L" --background -P \"%s\" -- %d %d", + blenderShellPath.c_str(), int(writehandle), int(readhandle)); + STARTUPINFO sinfo = {sizeof(STARTUPINFO)}; + HANDLE nulHandle = NULL; if (silenceBlender) { - int devNull = open("/dev/null", O_WRONLY); - dup2(devNull, STDOUT_FILENO); - dup2(devNull, STDERR_FILENO); + SECURITY_ATTRIBUTES sattrs = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; + nulHandle = CreateFileW(L"nul", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sattrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + sinfo.hStdError = nulHandle; + sinfo.hStdOutput = nulHandle; + sinfo.dwFlags = STARTF_USESTDHANDLES; } - char errbuf[256]; - char readfds[32]; - snprintf(readfds, 32, "%d", m_writepipe[0]); - char writefds[32]; - snprintf(writefds, 32, "%d", m_readpipe[1]); - - /* Try user-specified blender first */ - if (blenderBin) + PROCESS_INFORMATION pinfo; + if (!CreateProcessW(blenderBin, cmdLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &sinfo, &pinfo)) { - execlp(blenderBin, blenderBin, - "--background", "-P", blenderShellPath.c_str(), - "--", readfds, writefds, NULL); + LPWSTR messageBuffer = nullptr; + size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); + BlenderLog.report(LogVisor::FatalError, L"unable to launch blender from %s: %s", blenderBin, messageBuffer); + } + + close(m_writepipe[0]); + close(m_readpipe[1]); + + if (nulHandle) + CloseHandle(nulHandle); + +#else + pid_t pid = fork(); + if (!pid) + { + close(m_writepipe[1]); + close(m_readpipe[0]); + + if (silenceBlender) + { + int devNull = open("/dev/null", O_WRONLY); + dup2(devNull, STDOUT_FILENO); + dup2(devNull, STDERR_FILENO); + } + + char errbuf[256]; + char readfds[32]; + snprintf(readfds, 32, "%d", m_writepipe[0]); + char writefds[32]; + snprintf(writefds, 32, "%d", m_readpipe[1]); + + /* Try user-specified blender first */ + if (blenderBin) + { + if (installAttempt == 1) + execlp(blenderBin, blenderBin, + "--background", "-P", blenderShellPath.c_str(), + "--", readfds, writefds, blenderAddonPath.c_str(), NULL); + else + execlp(blenderBin, blenderBin, + "--background", "-P", blenderShellPath.c_str(), + "--", readfds, writefds, NULL); + if (errno != ENOENT) + { + snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno)); + write(m_writepipe[1], errbuf, strlen(errbuf)); + exit(1); + } + } + + /* Otherwise default blender */ + if (installAttempt == 1) + execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, + "--background", "-P", blenderShellPath.c_str(), + "--", readfds, writefds, blenderAddonPath.c_str(), NULL); + else + execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, + "--background", "-P", blenderShellPath.c_str(), + "--", readfds, writefds, NULL); if (errno != ENOENT) { snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno)); write(m_writepipe[1], errbuf, strlen(errbuf)); exit(1); } - } - /* Otherwise default blender */ - execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, - "--background", "-P", blenderShellPath.c_str(), - "--", readfds, writefds, NULL); - if (errno != ENOENT) - { - snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno)); - write(m_writepipe[1], errbuf, strlen(errbuf)); + /* Unable to find blender */ + write(m_writepipe[1], "NOBLENDER\n", 10); exit(1); + } - - /* Unable to find blender */ - write(m_writepipe[1], "NOBLENDER\n", 10); - exit(1); - - } - close(m_writepipe[0]); - close(m_readpipe[1]); - m_blenderProc = pid; + close(m_writepipe[0]); + close(m_readpipe[1]); + m_blenderProc = pid; #endif - /* Handle first response */ - char lineBuf[256]; - _readLine(lineBuf, sizeof(lineBuf)); - if (!strcmp(lineBuf, "NOLAUNCH")) - { - _closePipe(); - BlenderLog.report(LogVisor::FatalError, "Unable to launch blender"); - } - else if (!strcmp(lineBuf, "NOBLENDER")) - { - _closePipe(); - if (blenderBin) - BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s' or '%s'"), - blenderBin, DEFAULT_BLENDER_BIN); - else - BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s'"), - DEFAULT_BLENDER_BIN); - } - else if (!strcmp(lineBuf, "NOADDON")) - { - _closePipe(); - BlenderLog.report(LogVisor::FatalError, "HECL addon not installed within blender"); - } - else if (strcmp(lineBuf, "READY")) - { - _closePipe(); - BlenderLog.report(LogVisor::FatalError, "read '%s' from blender; expected 'READY'", lineBuf); - } - _writeLine("ACK"); + /* Handle first response */ + char lineBuf[256]; + _readLine(lineBuf, sizeof(lineBuf)); + if (!strcmp(lineBuf, "NOLAUNCH")) + { + _closePipe(); + BlenderLog.report(LogVisor::FatalError, "Unable to launch blender"); + } + else if (!strcmp(lineBuf, "NOBLENDER")) + { + _closePipe(); + if (blenderBin) + BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s' or '%s'"), + blenderBin, DEFAULT_BLENDER_BIN); + else + BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s'"), + DEFAULT_BLENDER_BIN); + } + else if (!strcmp(lineBuf, "NOADDON")) + { + _closePipe(); + InstallAddon(blenderAddonPath.c_str()); + ++installAttempt; + if (installAttempt >= 2) + BlenderLog.report(LogVisor::FatalError, "unable to install blender addon using '%s'", blenderAddonPath.c_str()); + continue; + } + else if (strcmp(lineBuf, "READY")) + { + _closePipe(); + BlenderLog.report(LogVisor::FatalError, "read '%s' from blender; expected 'READY'", lineBuf); + } + _writeLine("ACK"); + break; + } } BlenderConnection::~BlenderConnection() @@ -326,7 +336,7 @@ bool BlenderConnection::createBlend(const SystemString& path) _readLine(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) { - m_loadedBlend = pathView.str(); + m_loadedBlend = path; return true; } return false; @@ -346,7 +356,7 @@ bool BlenderConnection::openBlend(const SystemString& path) _readLine(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) { - m_loadedBlend = pathView.str(); + m_loadedBlend = path; return true; } return false; @@ -373,12 +383,12 @@ void BlenderConnection::deleteBlend() if (m_loadedBlend.size()) { HECL::Unlink(m_loadedBlend.c_str()); - BlenderLog.report(LogVisor::Info, "Deleted '%s'", m_loadedBlend.c_str()); + BlenderLog.report(LogVisor::Info, _S("Deleted '%s'"), m_loadedBlend.c_str()); m_loadedBlend.clear(); } } -void BlenderConnection::PyOutStream::linkBlend(const SystemString& target, const std::string& objName, +void BlenderConnection::PyOutStream::linkBlend(const std::string& target, const std::string& objName, bool link) { format("if '%s' not in bpy.data.scenes:\n" diff --git a/hecl/blender/BlenderConnection.hpp b/hecl/blender/BlenderConnection.hpp index a50d0af40..c9a0364f1 100644 --- a/hecl/blender/BlenderConnection.hpp +++ b/hecl/blender/BlenderConnection.hpp @@ -2,7 +2,9 @@ #define BLENDERCONNECTION_HPP #if _WIN32 -#define _WIN32_LEAN_AND_MEAN 1 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif #include #else #include @@ -23,17 +25,15 @@ extern class BlenderConnection* SharedBlenderConnection; class BlenderConnection { - bool m_lock; + bool m_lock = false; #if _WIN32 HANDLE m_blenderProc; - HANDLE m_readpipe[2]; - HANDLE m_writepipe[2]; #else pid_t m_blenderProc; +#endif int m_readpipe[2]; int m_writepipe[2]; -#endif - std::string m_loadedBlend; + SystemString m_loadedBlend; size_t _readLine(char* buf, size_t bufSz); size_t _writeLine(const char* buf); size_t _readBuf(char* buf, size_t len); @@ -75,11 +75,12 @@ public: { if (!m_parent.m_parent || !m_parent.m_parent->m_lock) BlenderLog.report(LogVisor::FatalError, "lock not held for PyOutStream writing"); - if (ch != traits_type::eof() && ch != '\n') + if (ch != traits_type::eof() && ch != '\n' && ch != '\0') { m_lineBuf += char_type(ch); return ch; } + //printf("FLUSHING %s\n", m_lineBuf.c_str()); m_parent.m_parent->_writeLine(m_lineBuf.c_str()); char readBuf[16]; m_parent.m_parent->_readLine(readBuf, 16); @@ -109,7 +110,8 @@ public: public: PyOutStream(const PyOutStream& other) = delete; PyOutStream(PyOutStream&& other) - : m_parent(other.m_parent), m_sbuf(std::move(other.m_sbuf)) + : m_parent(other.m_parent), m_sbuf(std::move(other.m_sbuf)), + std::ostream(&m_sbuf) {other.m_parent = nullptr;} ~PyOutStream() {close();} void close() @@ -131,13 +133,19 @@ public: va_list ap; va_start(ap, fmt); char* result = nullptr; +#ifdef _WIN32 + int length = _vscprintf(fmt, ap); + result = (char*)malloc(length); + vsnprintf(result, length, fmt, ap); +#else int length = vasprintf(&result, fmt, ap); +#endif + va_end(ap); if (length > 0) this->write(result, length); free(result); - va_end(ap); } - void linkBlend(const SystemString& target, const std::string& objName, bool link=true); + void linkBlend(const std::string& target, const std::string& objName, bool link=true); }; inline PyOutStream beginPythonOut(bool deleteOnError=false) { diff --git a/hecl/blender/CMakeLists.txt b/hecl/blender/CMakeLists.txt index 3b1412d02..ba4292405 100644 --- a/hecl/blender/CMakeLists.txt +++ b/hecl/blender/CMakeLists.txt @@ -1,21 +1,32 @@ list(APPEND PY_SOURCES - addon/__init__.py - addon/Nodegrid.py - addon/hmdl/__init__.py - addon/hmdl/HMDLMesh.py - addon/hmdl/HMDLShader.py - addon/hmdl/HMDLSkin.py - addon/hmdl/HMDLTxtr.py - addon/sact/__init__.py - addon/sact/SACTAction.py - addon/sact/SACTEvent.py - addon/sact/SACTSubtype.py) + hecl/__init__.py + hecl/Nodegrid.py + hecl/hmdl/__init__.py + hecl/hmdl/HMDLMesh.py + hecl/hmdl/HMDLShader.py + hecl/hmdl/HMDLSkin.py + hecl/hmdl/HMDLTxtr.py + hecl/sact/__init__.py + hecl/sact/SACTAction.py + hecl/sact/SACTEvent.py + hecl/sact/SACTSubtype.py) bintoc(hecl_blendershell.c hecl_blendershell.py HECL_BLENDERSHELL) +if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/hecl.zip) +message("-- Generating addon package") +execute_process(COMMAND python zip_package.py + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/hecl.zip) +message(FATAL_ERROR "Unable to generate ${CMAKE_CURRENT_SOURCE_DIR}/hecl.zip; is python installed?") +endif() +endif() +bintoc(hecl_addon.c hecl.zip HECL_ADDON) + add_library(HECLBlender BlenderConnection.cpp BlenderConnection.hpp hecl_blendershell.py hecl_blendershell.c + hecl_addon.c ${PY_SOURCES}) diff --git a/hecl/blender/addon/Nodegrid.py b/hecl/blender/hecl/Nodegrid.py similarity index 100% rename from hecl/blender/addon/Nodegrid.py rename to hecl/blender/hecl/Nodegrid.py diff --git a/hecl/blender/addon/__init__.py b/hecl/blender/hecl/__init__.py similarity index 100% rename from hecl/blender/addon/__init__.py rename to hecl/blender/hecl/__init__.py diff --git a/hecl/blender/addon/hmdl/HMDLMesh.py b/hecl/blender/hecl/hmdl/HMDLMesh.py similarity index 100% rename from hecl/blender/addon/hmdl/HMDLMesh.py rename to hecl/blender/hecl/hmdl/HMDLMesh.py diff --git a/hecl/blender/addon/hmdl/HMDLShader.py b/hecl/blender/hecl/hmdl/HMDLShader.py similarity index 100% rename from hecl/blender/addon/hmdl/HMDLShader.py rename to hecl/blender/hecl/hmdl/HMDLShader.py diff --git a/hecl/blender/addon/hmdl/HMDLSkin.py b/hecl/blender/hecl/hmdl/HMDLSkin.py similarity index 100% rename from hecl/blender/addon/hmdl/HMDLSkin.py rename to hecl/blender/hecl/hmdl/HMDLSkin.py diff --git a/hecl/blender/addon/hmdl/HMDLTxtr.py b/hecl/blender/hecl/hmdl/HMDLTxtr.py similarity index 100% rename from hecl/blender/addon/hmdl/HMDLTxtr.py rename to hecl/blender/hecl/hmdl/HMDLTxtr.py diff --git a/hecl/blender/addon/hmdl/__init__.py b/hecl/blender/hecl/hmdl/__init__.py similarity index 100% rename from hecl/blender/addon/hmdl/__init__.py rename to hecl/blender/hecl/hmdl/__init__.py diff --git a/hecl/blender/addon/sact/ANIM.py b/hecl/blender/hecl/sact/ANIM.py similarity index 100% rename from hecl/blender/addon/sact/ANIM.py rename to hecl/blender/hecl/sact/ANIM.py diff --git a/hecl/blender/addon/sact/SACTAction.py b/hecl/blender/hecl/sact/SACTAction.py similarity index 100% rename from hecl/blender/addon/sact/SACTAction.py rename to hecl/blender/hecl/sact/SACTAction.py diff --git a/hecl/blender/addon/sact/SACTEvent.py b/hecl/blender/hecl/sact/SACTEvent.py similarity index 100% rename from hecl/blender/addon/sact/SACTEvent.py rename to hecl/blender/hecl/sact/SACTEvent.py diff --git a/hecl/blender/addon/sact/SACTSubtype.py b/hecl/blender/hecl/sact/SACTSubtype.py similarity index 100% rename from hecl/blender/addon/sact/SACTSubtype.py rename to hecl/blender/hecl/sact/SACTSubtype.py diff --git a/hecl/blender/addon/sact/__init__.py b/hecl/blender/hecl/sact/__init__.py similarity index 100% rename from hecl/blender/addon/sact/__init__.py rename to hecl/blender/hecl/sact/__init__.py diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index 2d4a56ee4..19505f49b 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -9,6 +9,10 @@ if '--' not in sys.argv: args = sys.argv[sys.argv.index('--')+1:] readfd = int(args[0]) writefd = int(args[1]) +if sys.platform == "win32": + import msvcrt + readfd = msvcrt.open_osfhandle(readfd, os.O_RDONLY | os.O_BINARY) + writefd = msvcrt.open_osfhandle(writefd, os.O_WRONLY | os.O_BINARY) def readpipeline(): retval = bytearray() @@ -29,14 +33,23 @@ def quitblender(): writepipeline(b'QUITTING') bpy.ops.wm.quit_blender() -# Check that HECL addon is installed/enabled -if 'hecl' not in bpy.context.user_preferences.addons: - if 'FINISHED' not in bpy.ops.wm.addon_enable(module='hecl'): - writepipeline(b'NOADDON') - bpy.ops.wm.quit_blender() +# If there's a third argument, use it as the .zip path containing the addon +if len(args) >= 3: + bpy.ops.wm.addon_install(overwrite=True, target='DEFAULT', filepath=args[2]) + bpy.ops.wm.addon_refresh() # Make addon available to commands -import hecl +if bpy.context.user_preferences.addons.find('hecl') == -1: + try: + bpy.ops.wm.addon_enable(module='hecl') + bpy.ops.wm.save_userpref() + except: + pass +try: + import hecl +except: + writepipeline(b'NOADDON') + bpy.ops.wm.quit_blender() # Intro handshake writepipeline(b'READY') diff --git a/hecl/blender/zip_package.py b/hecl/blender/zip_package.py new file mode 100644 index 000000000..c81f193b7 --- /dev/null +++ b/hecl/blender/zip_package.py @@ -0,0 +1,18 @@ +import sys, os +import zipfile + +def zipdir(path, ziph): + # ziph is zipfile handle + for root, dirs, files in os.walk(path): + for file in files: + ziph.write(os.path.join(root, file)) + +package_path = 'hecl' +target_zip = 'hecl.zip' + +zf = zipfile.ZipFile(target_zip, mode='w', compression=zipfile.ZIP_DEFLATED) +print('GENERATING', target_zip) +if os.path.isdir(package_path): + zipdir(package_path, zf) + +zf.close() diff --git a/hecl/driver/CMakeLists.txt b/hecl/driver/CMakeLists.txt index fcdb69ef6..033160b6b 100644 --- a/hecl/driver/CMakeLists.txt +++ b/hecl/driver/CMakeLists.txt @@ -1,7 +1,3 @@ -if(WIN32) -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /INCLUDE:HECLDataSpecs") -endif() - add_executable(hecl main.cpp ToolBase.hpp ToolPackage.hpp @@ -24,8 +20,12 @@ list(APPEND DATA_SPEC_LIBS DNAMP1 DNACommon) +if(NOT WIN32) + list(APPEND PLAT_LIBS pthread) +endif() + target_link_libraries(hecl ${DATA_SPEC_LIBS} HECLDatabase HECLBlender HECLCommon AthenaCore NOD - LogVisor AthenaLibYaml png squish blowfish z lzo2 pthread + LogVisor AthenaLibYaml png squish blowfish z lzo2 ${PLAT_LIBS} ) diff --git a/hecl/driver/ToolExtract.hpp b/hecl/driver/ToolExtract.hpp index 7a8255d65..b92b83c1a 100644 --- a/hecl/driver/ToolExtract.hpp +++ b/hecl/driver/ToolExtract.hpp @@ -44,7 +44,7 @@ public: LogModule.report(LogVisor::FatalError, "hecl extract must be ran within a project directory"); size_t ErrorRef = LogVisor::ErrorCount; - HECL::SystemString rootDir = info.cwd + '/' + baseFile; + HECL::SystemString rootDir = info.cwd + _S('/') + baseFile; HECL::ProjectRootPath newProjRoot(rootDir); newProjRoot.makeDir(); m_fallbackProj.reset(new HECL::Database::Project(newProjRoot)); diff --git a/hecl/driver/main.cpp b/hecl/driver/main.cpp index 5ed3e1a3c..ebc23c3b6 100644 --- a/hecl/driver/main.cpp +++ b/hecl/driver/main.cpp @@ -1,7 +1,9 @@ #if _WIN32 #define WIN_PAUSE 1 +#include #endif +#include #include #include #include @@ -89,6 +91,12 @@ int wmain(int argc, const wchar_t** argv) int main(int argc, const char** argv) #endif { +#if _WIN32 + CoInitializeEx(nullptr, COINIT_MULTITHREADED); +#else + std::setlocale(LC_ALL, "en-US.UTF-8"); +#endif + /* Xterm check */ const char* term = getenv("TERM"); if (term && !strncmp(term, "xterm", 5)) diff --git a/hecl/extern/Athena b/hecl/extern/Athena index 66cb6c982..488a0100e 160000 --- a/hecl/extern/Athena +++ b/hecl/extern/Athena @@ -1 +1 @@ -Subproject commit 66cb6c982e8824fa887807bf8ba42376b301478b +Subproject commit 488a0100e2e6044361a494a280c7f1a28911762e diff --git a/hecl/extern/CMakeLists.txt b/hecl/extern/CMakeLists.txt index 40b9d24b2..00c81b155 100644 --- a/hecl/extern/CMakeLists.txt +++ b/hecl/extern/CMakeLists.txt @@ -1,5 +1,5 @@ -add_subdirectory(libpng) add_subdirectory(libSquish) add_subdirectory(blowfish) add_subdirectory(LogVisor) add_subdirectory(Athena EXCLUDE_FROM_ALL) +add_subdirectory(libpng) diff --git a/hecl/extern/LogVisor b/hecl/extern/LogVisor index 154b84413..828db515b 160000 --- a/hecl/extern/LogVisor +++ b/hecl/extern/LogVisor @@ -1 +1 @@ -Subproject commit 154b84413050cdb03f281c42861b9af6eaef7d14 +Subproject commit 828db515ba316903c367b9dc7acf9d0bec0969e7 diff --git a/hecl/include/HECL/HECL.hpp b/hecl/include/HECL/HECL.hpp index e769aa4dc..af0aa2742 100644 --- a/hecl/include/HECL/HECL.hpp +++ b/hecl/include/HECL/HECL.hpp @@ -10,11 +10,12 @@ #include #include #else -#define _WIN32_LEAN_AND_MEAN 1 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif #include #include -#include "winsupport.h" -#define snprintf _snprintf +#include "winsupport.hpp" #endif #include @@ -29,8 +30,10 @@ #include "../extern/blowfish/blowfish.h" /* Handy MIN/MAX macros */ +#ifndef MAX #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif namespace HECL { @@ -135,11 +138,11 @@ static inline void Unlink(const SystemChar* file) #endif } -static inline void MakeDir(const SystemChar* dir) +static inline void MakeDir(const char* dir) { #if _WIN32 HRESULT err; - if (!CreateDirectory(dir, NULL)) + if (!CreateDirectoryA(dir, NULL)) if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) LogModule.report(LogVisor::FatalError, _S("MakeDir(%s)"), dir); #else @@ -149,11 +152,29 @@ static inline void MakeDir(const SystemChar* dir) #endif } +#if _WIN32 +static inline void MakeDir(const wchar_t* dir) +{ + HRESULT err; + if (!CreateDirectoryW(dir, NULL)) + if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) + LogModule.report(LogVisor::FatalError, _S("MakeDir(%s)"), dir); +} +#endif + static inline void MakeLink(const SystemChar* target, const SystemChar* linkPath) { #if _WIN32 + HRESULT res = CreateShellLink(target, linkPath, _S("HECL Link")); /* :(( */ + if (!SUCCEEDED(res)) + { + LPWSTR messageBuffer = nullptr; + size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, res, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); /* :((( */ + LogModule.report(LogVisor::FatalError, _S("MakeLink(%s, %s): %s"), target, linkPath, messageBuffer); + } #else - if (symlink(target, linkPath)) + if (symlink(target, linkPath)) /* :) */ if (errno != EEXIST) LogModule.report(LogVisor::FatalError, "MakeLink(%s, %s): %s", target, linkPath, strerror(errno)); #endif diff --git a/hecl/include/HECL/winsupport.h b/hecl/include/HECL/winsupport.h deleted file mode 100644 index 043ac6043..000000000 --- a/hecl/include/HECL/winsupport.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _HECL_WINSUPPORT_H_ -#define _HECL_WINSUPPORT_H_ - -#if __cplusplus -extern "C" { -#endif - -void* memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen); - -#if __cplusplus -} -#endif - -#endif // _HECL_WINSUPPORT_H_ diff --git a/hecl/include/HECL/winsupport.hpp b/hecl/include/HECL/winsupport.hpp new file mode 100644 index 000000000..2db5d7f00 --- /dev/null +++ b/hecl/include/HECL/winsupport.hpp @@ -0,0 +1,13 @@ +#ifndef _HECL_WINSUPPORT_H_ +#define _HECL_WINSUPPORT_H_ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include "windows.h" + +void* memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen); +HRESULT CreateShellLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink, LPCWSTR lpszDesc); +HRESULT ResolveShellLink(LPCWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferSize); + +#endif // _HECL_WINSUPPORT_H_ diff --git a/hecl/lib/CMakeLists.txt b/hecl/lib/CMakeLists.txt index 412be8f52..2277730d9 100644 --- a/hecl/lib/CMakeLists.txt +++ b/hecl/lib/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(Frontend) add_subdirectory(Runtime) if(WIN32) -list(APPEND PLAT_SRCS winsupport.c ../include/HECL/winsupport.h) +list(APPEND PLAT_SRCS winsupport.cpp ../include/HECL/winsupport.hpp) endif() add_library(HECLCommon diff --git a/hecl/lib/WideStringConvert.cpp b/hecl/lib/WideStringConvert.cpp index e674832fe..d21c92568 100644 --- a/hecl/lib/WideStringConvert.cpp +++ b/hecl/lib/WideStringConvert.cpp @@ -5,29 +5,45 @@ namespace HECL std::string WideToUTF8(const std::wstring& src) { +#if _WIN32 + int len = WideCharToMultiByte(CP_UTF8, 0, src.c_str(), src.size(), nullptr, 0, nullptr, nullptr); + std::string retval(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, src.c_str(), src.size(), &retval[0], len, nullptr, nullptr); + return retval; +#else std::string retval; retval.reserve(src.length()); + std::mbstate_t state = {}; for (wchar_t ch : src) { - char mb[4]; - int c = std::wctomb(mb, ch); + char mb[MB_LEN_MAX]; + int c = std::wcrtomb(mb, ch, &state); retval.append(mb, c); } return retval; +#endif } std::wstring UTF8ToWide(const std::string& src) { +#if _WIN32 + int len = MultiByteToWideChar(CP_UTF8, 0, src.c_str(), src.size(), nullptr, 0); + std::wstring retval(len, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, src.c_str(), src.size(), &retval[0], len); + return retval; +#else std::wstring retval; retval.reserve(src.length()); const char* buf = src.c_str(); + std::mbstate_t state = {}; while (*buf) { wchar_t wc; - buf += std::mbtowc(&wc, buf, MB_CUR_MAX); + buf += std::mbrtowc(&wc, buf, MB_LEN_MAX, &state); retval += wc; } return retval; +#endif } } diff --git a/hecl/lib/winsupport.c b/hecl/lib/winsupport.c deleted file mode 100644 index ddb933bbc..000000000 --- a/hecl/lib/winsupport.c +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -/* -* The memmem() function finds the start of the first occurrence of the -* substring 'needle' of length 'nlen' in the memory area 'haystack' of -* length 'hlen'. -* -* The return value is a pointer to the beginning of the sub-string, or -* NULL if the substring is not found. -*/ -void *memmem(const uint8_t *haystack, size_t hlen, const void *needle, size_t nlen) -{ - int needle_first; - const uint8_t *p = haystack; - size_t plen = hlen; - - if (!nlen) - return NULL; - - needle_first = *(unsigned char *)needle; - - while (plen >= nlen && (p = memchr(p, needle_first, plen - nlen + 1))) - { - if (!memcmp(p, needle, nlen)) - return (void *)p; - - p++; - plen = hlen - (p - haystack); - } - - return NULL; -} diff --git a/hecl/lib/winsupport.cpp b/hecl/lib/winsupport.cpp new file mode 100644 index 000000000..54fe2bae6 --- /dev/null +++ b/hecl/lib/winsupport.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include "HECL/winsupport.hpp" + +/* +* The memmem() function finds the start of the first occurrence of the +* substring 'needle' of length 'nlen' in the memory area 'haystack' of +* length 'hlen'. +* +* The return value is a pointer to the beginning of the sub-string, or +* NULL if the substring is not found. +*/ +void *memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen) +{ + int needle_first; + const uint8_t *p = static_cast(haystack); + size_t plen = hlen; + + if (!nlen) + return NULL; + + needle_first = *(unsigned char *)needle; + + while (plen >= nlen && (p = static_cast(memchr(p, needle_first, plen - nlen + 1)))) + { + if (!memcmp(p, needle, nlen)) + return (void *)p; + + p++; + plen = hlen - (p - static_cast(haystack)); + } + + return NULL; +} + +/* Clearly, MS doesn't require enough headers for this */ +#include "winnls.h" +#include "shobjidl.h" +#include "objbase.h" +#include "objidl.h" +#include "shlguid.h" +#include "strsafe.h" + +HRESULT CreateShellLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink, LPCWSTR lpszDesc) +{ + std::wstring targetStr(lpszPathObj); + for (wchar_t& ch : targetStr) + if (ch == L'/') + ch = L'\\'; + std::wstring linkStr(lpszPathLink); + linkStr += L".lnk"; + for (wchar_t& ch : linkStr) + if (ch == L'/') + ch = L'\\'; + + HRESULT hres; + IShellLink* psl; + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) + { + IPersistFile* ppf; + + // Set the path to the shortcut target and add the description. + WCHAR targetBuf[MAX_PATH]; + WCHAR linkBuf[MAX_PATH]; + WCHAR* linkFinalPart = nullptr; + GetFullPathNameW(linkStr.c_str(), MAX_PATH, linkBuf, &linkFinalPart); + if (linkFinalPart != linkBuf) + *(linkFinalPart-1) = L'\0'; + StringCbPrintfW(targetBuf, MAX_PATH, L"%s\\%s", linkBuf, targetStr.c_str()); + if (linkFinalPart != linkBuf) + *(linkFinalPart - 1) = L'\\'; + psl->SetPath(targetBuf); + psl->SetRelativePath(linkBuf, 0); + psl->SetDescription(lpszDesc); + + // Query IShellLink for the IPersistFile interface, used for saving the + // shortcut in persistent storage. + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + + if (SUCCEEDED(hres)) + { + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(linkBuf, TRUE); + ppf->Release(); + } + psl->Release(); + } + return hres; +} + +HRESULT ResolveShellLink(LPCWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferSize) +{ + HRESULT hres; + IShellLink* psl; + WCHAR szGotPath[MAX_PATH]; + WCHAR szDescription[MAX_PATH]; + WIN32_FIND_DATA wfd; + + *lpszPath = 0; // Assume failure + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) + { + IPersistFile* ppf; + + // Get a pointer to the IPersistFile interface. + hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf); + + if (SUCCEEDED(hres)) + { + // Load the shortcut. + hres = ppf->Load(lpszLinkFile, STGM_READ); + + if (SUCCEEDED(hres)) + { + // Resolve the link. + HWND hwnd = GetConsoleWindow(); + if (!hwnd) + hwnd = GetTopWindow(nullptr); + hres = psl->Resolve(hwnd, 0); + + if (SUCCEEDED(hres)) + { + // Get the path to the link target. + hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_SHORTPATH); + + if (SUCCEEDED(hres)) + { + // Get the description of the target. + hres = psl->GetDescription(szDescription, MAX_PATH); + + if (SUCCEEDED(hres)) + { + hres = StringCbCopy(lpszPath, iPathBufferSize, szGotPath); + } + } + } + } + + // Release the pointer to the IPersistFile interface. + ppf->Release(); + } + + // Release the pointer to the IShellLink interface. + psl->Release(); + } + return hres; +} + +