From da3030cfe6452947848e50452a4d35905354292e Mon Sep 17 00:00:00 2001 From: janwas Date: Tue, 3 Nov 2009 22:27:25 +0000 Subject: [PATCH] more unicode/string cleanup: - swprintf -> swprintf_s - use secure_crt's _wfopen_s instead of conversion+fopen - centralize all MBS <-> WCS conversion in new wchar.cpp (requires workspace rebuild) - remove no longer needed os_path - remove unnecessary fs::wpath / VfsPath constructor casts - fixed buffer size parameters -> ARRAY_SIZE This was SVN commit r7162. --- source/i18n/BufferVariable.cpp | 9 +++--- source/lib/debug.cpp | 8 ++--- source/lib/file/archive/archive_zip.cpp | 14 ++++----- source/lib/file/common/trace.cpp | 12 ++++---- source/lib/file/file_system.cpp | 8 ++--- source/lib/file/file_system_util.cpp | 2 +- source/lib/path_util.cpp | 12 +++----- source/lib/res/graphics/ogl_tex.cpp | 2 +- source/lib/res/sound/snd_mgr.cpp | 2 +- source/lib/secure_crt.cpp | 17 +++++------ source/lib/sysdep/os/win/wseh.cpp | 2 +- source/lib/sysdep/tests/test_sysdep.h | 6 ++-- source/lib/wchar.cpp | 37 +++++++++++++++++++++++ source/lib/{os_path.h => wchar.h} | 40 +++++++++++++++---------- source/ps/CLogger.cpp | 4 +-- source/ps/GameSetup/GameSetup.cpp | 2 +- source/ps/ProfileViewer.cpp | 2 +- source/ps/Pyrogenesis.cpp | 10 +++---- source/ps/Util.cpp | 8 ++--- source/ps/scripting/JSCollection.h | 2 +- source/scripting/ScriptGlue.cpp | 2 +- source/scripting/SynchedJSObject.cpp | 3 +- 22 files changed, 119 insertions(+), 85 deletions(-) create mode 100644 source/lib/wchar.cpp rename source/lib/{os_path.h => wchar.h} (75%) diff --git a/source/i18n/BufferVariable.cpp b/source/i18n/BufferVariable.cpp index 2e340df409..446413004a 100644 --- a/source/i18n/BufferVariable.cpp +++ b/source/i18n/BufferVariable.cpp @@ -46,8 +46,8 @@ namespace I18n { StrImW BufferVariable_int::ToString(CLocale*) { - wchar_t buffer[16]; - swprintf(buffer, 16, L"%d", value); + wchar_t buffer[50]; + swprintf_s(buffer, ARRAY_SIZE(buffer), L"%d", value); return buffer; } u32 BufferVariable_int::Hash() @@ -58,9 +58,8 @@ u32 BufferVariable_int::Hash() StrImW BufferVariable_double::ToString(CLocale*) { - // TODO: Work out how big the buffer should be - wchar_t buffer[256]; - swprintf(buffer, 256, L"%f", value); + wchar_t buffer[50]; + swprintf_s(buffer, ARRAY_SIZE(buffer), L"%f", value); return buffer; } u32 BufferVariable_double::Hash() diff --git a/source/lib/debug.cpp b/source/lib/debug.cpp index 2f23c47490..d2e2864416 100644 --- a/source/lib/debug.cpp +++ b/source/lib/debug.cpp @@ -206,10 +206,10 @@ LibError debug_WriteCrashlog(const wchar_t* text) if(!cpu_CAS(&state, IDLE, BUSY)) return ERR::REENTERED; // NOWARN - fs::wpath path = ah_get_log_dir()/L"crashlog.txt"; - fs::path path_c = path_from_wpath(path); - FILE* f = fopen(path_c.string().c_str(), "w"); - if(!f) + FILE* f; + fs::wpath pathname = ah_get_log_dir()/L"crashlog.txt"; + errno_t err = _wfopen_s(&f, pathname.string().c_str(), L"w"); + if(err != 0) { state = FAILED; // must come before DEBUG_DISPLAY_ERROR DEBUG_DISPLAY_ERROR(L"Unable to open crashlog.txt for writing (please ensure the log directory is writable)"); diff --git a/source/lib/file/archive/archive_zip.cpp b/source/lib/file/archive/archive_zip.cpp index 5d71957fe3..02ad387861 100644 --- a/source/lib/file/archive/archive_zip.cpp +++ b/source/lib/file/archive/archive_zip.cpp @@ -27,6 +27,7 @@ #include "lib/bits.h" #include "lib/byte_order.h" +#include "lib/wchar.h" // wstring_from_string #include "lib/fat_time.h" #include "lib/path_util.h" #include "lib/allocators/pool.h" @@ -133,13 +134,8 @@ public: fs::wpath Pathname() const { const size_t length = (size_t)read_le16(&m_fn_len); - const char* pathname_c = (const char*)this + sizeof(CDFH); // not 0-terminated! - wchar_t pathname[PATH_MAX]; - size_t charsConverted; - const errno_t ret = mbstowcs_s(&charsConverted, pathname, pathname_c, length); - debug_assert(ret == 0); - debug_assert(charsConverted == length); - return pathname; + const char* pathname = (const char*)this + sizeof(CDFH); // not 0-terminated! + return wstring_from_string(std::string(pathname, length)); } off_t HeaderOffset() const @@ -404,13 +400,13 @@ public: if(!cdfh) WARN_RETURN(ERR::CORRUPTED); - const fs::wpath relativePathname(cdfh->Pathname()); + const VfsPath relativePathname(cdfh->Pathname().string()); // convert from fs::wpath const std::wstring name = relativePathname.leaf(); if(*name.rbegin() != '/') // ignore paths ending in slash (i.e. representing a directory) { FileInfo fileInfo(name, cdfh->USize(), cdfh->MTime()); shared_ptr archiveFile(new ArchiveFile_Zip(m_file, cdfh->HeaderOffset(), cdfh->CSize(), cdfh->Checksum(), cdfh->Method())); - cb(VfsPath(relativePathname.string()), fileInfo, archiveFile, cbData); + cb(relativePathname, fileInfo, archiveFile, cbData); } pos += cdfh->Size(); diff --git a/source/lib/file/common/trace.cpp b/source/lib/file/common/trace.cpp index e4da1dc137..ee61bd25d3 100644 --- a/source/lib/file/common/trace.cpp +++ b/source/lib/file/common/trace.cpp @@ -149,10 +149,10 @@ public: { pool_free_all(&m_pool); - const fs::path pathname_c = path_from_wpath(pathname); errno = 0; - FILE* file = fopen(pathname_c.string().c_str(), "rt"); - if(!file) + FILE* file; + errno_t err = _wfopen_s(&file, pathname.string().c_str(), L"rt"); + if(err != 0) return LibError_from_errno(); for(;;) @@ -169,10 +169,10 @@ public: virtual LibError Store(const fs::wpath& pathname) const { - const fs::path pathname_c = path_from_wpath(pathname); errno = 0; - FILE* file = fopen(pathname_c.string().c_str(), "at"); - if(!file) + FILE* file; + errno_t err = _wfopen_s(&file, pathname.string().c_str(), L"at"); + if(err != 0) return LibError_from_errno(); for(size_t i = 0; i < NumEntries(); i++) { diff --git a/source/lib/file/file_system.cpp b/source/lib/file/file_system.cpp index 653b822b33..1605e3a157 100644 --- a/source/lib/file/file_system.cpp +++ b/source/lib/file/file_system.cpp @@ -23,6 +23,7 @@ #include #include "lib/path_util.h" +#include "lib/wchar.h" // wstring_from_string #include "lib/posix/posix_filesystem.h" @@ -36,7 +37,7 @@ struct DirDeleter }; // is name "." or ".."? -static bool IsDummyDirectory(const wchar_t* name) +static bool IsDummyDirectory(const std::wstring& name) { if(name[0] != '.') return false; @@ -66,9 +67,8 @@ LibError GetDirectoryEntries(const fs::wpath& path, FileInfos* files, DirectoryN return LibError_from_errno(); } - wchar_t name[PATH_MAX]; - mbstowcs(name, osEnt->d_name, ARRAY_SIZE(name)); - RETURN_ERR(path_component_validate(name)); + const std::wstring name = wstring_from_string(osEnt->d_name); + RETURN_ERR(path_component_validate(name.c_str())); // get file information (mode, size, mtime) struct stat s; diff --git a/source/lib/file/file_system_util.cpp b/source/lib/file/file_system_util.cpp index d600023630..502dc99141 100644 --- a/source/lib/file/file_system_util.cpp +++ b/source/lib/file/file_system_util.cpp @@ -151,7 +151,7 @@ void NextNumberedFilename(const PIVFS& fs, const VfsPath& pathnameFormat, size_t { wchar_t pathnameBuf[PATH_MAX]; swprintf_s(pathnameBuf, ARRAY_SIZE(pathnameBuf), pathnameFormat.string().c_str(), nextNumber++); - nextPathname = VfsPath(pathnameBuf); + nextPathname = pathnameBuf; } while(fs->GetFileInfo(nextPathname, 0) == INFO::OK); } diff --git a/source/lib/path_util.cpp b/source/lib/path_util.cpp index 1e77f56028..c34065273d 100644 --- a/source/lib/path_util.cpp +++ b/source/lib/path_util.cpp @@ -25,6 +25,8 @@ #include #include +#include "lib/wchar.h" + ERROR_ASSOCIATE(ERR::PATH_LENGTH, L"path exceeds PATH_MAX characters", ENAMETOOLONG); ERROR_ASSOCIATE(ERR::PATH_EMPTY, L"path is an empty string", -1); @@ -302,16 +304,10 @@ const wchar_t* path_extension(const wchar_t* fn) fs::wpath wpath_from_path(const fs::path& pathname) { - wchar_t pathname_w[PATH_MAX]; - const size_t numConverted = mbstowcs(pathname_w, pathname.file_string().c_str(), ARRAY_SIZE(pathname_w)); - debug_assert(numConverted < PATH_MAX); // if == PATH_MAX, result isn't zero-terminated - return fs::wpath(pathname_w); + return wstring_from_string(pathname.string()); } fs::path path_from_wpath(const fs::wpath& pathname) { - char pathname_c[PATH_MAX]; - const size_t numConverted = wcstombs(pathname_c, pathname.file_string().c_str(), ARRAY_SIZE(pathname_c)); - debug_assert(numConverted < PATH_MAX); // if == PATH_MAX, result isn't zero-terminated - return fs::path(pathname_c); + return string_from_wstring(pathname.string()); } diff --git a/source/lib/res/graphics/ogl_tex.cpp b/source/lib/res/graphics/ogl_tex.cpp index 4d45b86741..c126e20de7 100644 --- a/source/lib/res/graphics/ogl_tex.cpp +++ b/source/lib/res/graphics/ogl_tex.cpp @@ -235,7 +235,7 @@ static GLint choose_int_fmt(GLenum fmt, int q_flags) default: { wchar_t buf[100]; - swprintf(buf, ARRAY_SIZE(buf), L"choose_int_fmt: fmt 0x%x isn't covered! please add it", fmt); + swprintf_s(buf, ARRAY_SIZE(buf), L"choose_int_fmt: fmt 0x%x isn't covered! please add it", fmt); DEBUG_DISPLAY_ERROR(buf); debug_assert(0); // given fmt isn't covered! please add it. // fall back to a reasonable default diff --git a/source/lib/res/sound/snd_mgr.cpp b/source/lib/res/sound/snd_mgr.cpp index d4294527e5..b7ab5c4e5d 100644 --- a/source/lib/res/sound/snd_mgr.cpp +++ b/source/lib/res/sound/snd_mgr.cpp @@ -275,7 +275,7 @@ static LibError alc_init() // (e.g. DS3D, native, MMSYSTEM) - needed when reporting OpenAL bugs. const char* dev_name = (const char*)alcGetString(alc_dev, ALC_DEVICE_SPECIFIER); wchar_t buf[200]; - swprintf(buf, ARRAY_SIZE(buf), L"SND| alc_init: success, using %hs\n", dev_name); + swprintf_s(buf, ARRAY_SIZE(buf), L"SND| alc_init: success, using %hs\n", dev_name); ah_log(buf); #if WIN_LOADLIBRARY_HACK diff --git a/source/lib/secure_crt.cpp b/source/lib/secure_crt.cpp index 3aba14cee6..9ab811fac0 100644 --- a/source/lib/secure_crt.cpp +++ b/source/lib/secure_crt.cpp @@ -27,6 +27,10 @@ #include "secure_crt.h" +#ifdef WSECURE_CRT +#include "lib/wchar.h" +#endif + // we were included from wsecure_crt.cpp; skip all stuff that // must only be done once. @@ -253,16 +257,9 @@ int tsprintf_s(tchar* buf, size_t max_chars, const tchar* fmt, ...) errno_t _wfopen_s(FILE** pfile, const wchar_t* filename, const wchar_t* mode) { *pfile = NULL; - - size_t numConverted; - char filename_c[PATH_MAX]; - numConverted = wcstombs(filename_c, filename, PATH_MAX); - debug_assert(numConverted < PATH_MAX); - char mode_c[PATH_MAX]; - numConverted = wcstombs(mode_c, mode, PATH_MAX); - debug_assert(numConverted < PATH_MAX); - - return fopen_s(pfile, filename_c, mode_c); + const std::string filename_c = string_from_wstring(filename); + const std::string mode_c = string_from_wstring(mode); + return fopen_s(pfile, filename_c.c_str(), mode_c.c_str()); } #else diff --git a/source/lib/sysdep/os/win/wseh.cpp b/source/lib/sysdep/os/win/wseh.cpp index 0cdf537feb..a665a3c4d7 100644 --- a/source/lib/sysdep/os/win/wseh.cpp +++ b/source/lib/sysdep/os/win/wseh.cpp @@ -185,7 +185,7 @@ static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er, // anything else => unknown; display its exception code. // we don't punt to GetExceptionDescription because anything // we get called for will actually be a SEH exception. - swprintf(description, maxChars, L"Unknown (0x%08X)", code); + swprintf_s(description, maxChars, L"Unknown (0x%08X)", code); return description; } diff --git a/source/lib/sysdep/tests/test_sysdep.h b/source/lib/sysdep/tests/test_sysdep.h index 8dc0bc3776..c223ae51b4 100644 --- a/source/lib/sysdep/tests/test_sysdep.h +++ b/source/lib/sysdep/tests/test_sysdep.h @@ -19,6 +19,7 @@ #include "lib/lib.h" #include "lib/path_util.h" +#include "lib/secure_crt.h" #include "lib/sysdep/sysdep.h" #include "lib/posix/posix.h" // fminf etc. @@ -128,8 +129,9 @@ public: for (size_t i = 0; i < ARRAY_SIZE(files); ++i) { std::string name = rootstr + files[i]; - FILE* f = fopen(name.c_str(), "w"); - TS_ASSERT(f); + FILE* f; + errno_t err = fopen_s(&f, name.c_str(), "w"); + TS_ASSERT_EQUALS(err, 0); fclose(f); } diff --git a/source/lib/wchar.cpp b/source/lib/wchar.cpp new file mode 100644 index 0000000000..69b763dc67 --- /dev/null +++ b/source/lib/wchar.cpp @@ -0,0 +1,37 @@ +/* Copyright (C) 2009 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" +#include "wchar.h" + + +std::wstring wstring_from_string(const std::string& s) +{ + wchar_t buf[1000]; + const size_t numConverted = mbstowcs(buf, s.c_str(), ARRAY_SIZE(buf)-1); + debug_assert(numConverted < ARRAY_SIZE(buf)); + return buf; +} + + +std::string string_from_wstring(const std::wstring& s) +{ + char buf[1000]; + const size_t numConverted = wcstombs(buf, s.c_str(), ARRAY_SIZE(buf)-1); + debug_assert(numConverted < ARRAY_SIZE(buf)); + return buf; +} diff --git a/source/lib/os_path.h b/source/lib/wchar.h similarity index 75% rename from source/lib/os_path.h rename to source/lib/wchar.h index 9b46cfcad6..e3dd5ff8b4 100644 --- a/source/lib/os_path.h +++ b/source/lib/wchar.h @@ -1,16 +1,24 @@ -/* Copyright (C) 2009 Wildfire Games. - * This file is part of 0 A.D. - * - * 0 A.D. is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * 0 A.D. is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with 0 A.D. If not, see . - */ +/* Copyright (C) 2009 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_WCHAR +#define INCLUDED_WCHAR + +LIB_API std::wstring wstring_from_string(const std::string& s); +LIB_API std::string string_from_wstring(const std::wstring& s); + +#endif // #ifndef INCLUDED_WCHAR diff --git a/source/ps/CLogger.cpp b/source/ps/CLogger.cpp index a2adf67546..dba5719685 100644 --- a/source/ps/CLogger.cpp +++ b/source/ps/CLogger.cpp @@ -54,10 +54,10 @@ const wchar_t* html_footer = L""; CLogger::CLogger() { - fs::wpath mainlogPath(fs::wpath(psLogDir())/L"mainlog.html"); + fs::wpath mainlogPath(psLogDir()/L"mainlog.html"); m_MainLog = new std::wofstream(mainlogPath.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc); - fs::wpath interestinglogPath(fs::wpath(psLogDir())/L"interestinglog.html"); + fs::wpath interestinglogPath(psLogDir()/L"interestinglog.html"); m_InterestingLog = new std::wofstream(interestinglogPath.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc); m_OwnsStreams = true; diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index be6b834adc..ff03a7700b 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -964,7 +964,7 @@ void Init(const CmdLineArgs& args, int flags) L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card."; - swprintf(buf, ARRAY_SIZE(buf), fmt, missing); + swprintf_s(buf, ARRAY_SIZE(buf), fmt, missing); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } diff --git a/source/ps/ProfileViewer.cpp b/source/ps/ProfileViewer.cpp index 93d2bb775e..a95222b3d9 100644 --- a/source/ps/ProfileViewer.cpp +++ b/source/ps/ProfileViewer.cpp @@ -429,7 +429,7 @@ void CProfileViewer::SaveToFile() { // Open the file. (It will be closed when the CProfileViewer // destructor is called.) - fs::wpath path(fs::wpath(psLogDir())/L"profile.txt"); + fs::wpath path(psLogDir()/L"profile.txt"); m->outputStream.open(path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc); if (m->outputStream.fail()) diff --git a/source/ps/Pyrogenesis.cpp b/source/ps/Pyrogenesis.cpp index 730b31d8f1..fc65bf07d8 100644 --- a/source/ps/Pyrogenesis.cpp +++ b/source/ps/Pyrogenesis.cpp @@ -67,9 +67,9 @@ void psTranslateFree(const wchar_t* text) // append to file. static void AppendAsciiFile(FILE* out, const fs::wpath& pathname) { - const fs::path pathname_c = path_from_wpath(pathname); - FILE* in = fopen(pathname_c.external_file_string().c_str(), "rb"); - if(!in) + FILE* in; + errno_t err = _wfopen_s(&in, pathname.string().c_str(), L"rb"); + if(err != 0) { fwprintf(out, L"(unavailable)"); return; @@ -96,12 +96,12 @@ void psBundleLogs(FILE* f) fwprintf(f, L"SVN Revision: %ls\n\n", svn_revision); fwprintf(f, L"System info:\n\n"); - fs::wpath path1(fs::wpath(psLogDir())/L"system_info.txt"); + fs::wpath path1(psLogDir()/L"system_info.txt"); AppendAsciiFile(f, path1); fwprintf(f, L"\n\n====================================\n\n"); fwprintf(f, L"Main log:\n\n"); - fs::wpath path2(fs::wpath(psLogDir())/L"mainlog.html"); + fs::wpath path2(psLogDir()/L"mainlog.html"); AppendAsciiFile(f, path2); fwprintf(f, L"\n\n====================================\n\n"); } diff --git a/source/ps/Util.cpp b/source/ps/Util.cpp index e95f1bf21d..cdc8293a16 100644 --- a/source/ps/Util.cpp +++ b/source/ps/Util.cpp @@ -71,10 +71,10 @@ void WriteSystemInfo() struct utsname un; uname(&un); - fs::wpath pathname(fs::wpath(psLogDir())/L"system_info.txt"); - const fs::path pathname_c = path_from_wpath(pathname); - FILE* f = fopen(pathname_c.string().c_str(), "w"); - if(!f) + fs::wpath pathname(psLogDir()/L"system_info.txt"); + FILE* f; + errno_t err = _wfopen_s(&f, pathname.string().c_str(), L"w"); + if(err != 0) return; // current timestamp (redundant WRT OS timestamp, but that is not diff --git a/source/ps/scripting/JSCollection.h b/source/ps/scripting/JSCollection.h index 2e765e9ca8..b91ac24c99 100644 --- a/source/ps/scripting/JSCollection.h +++ b/source/ps/scripting/JSCollection.h @@ -263,7 +263,7 @@ template JSBool CJSCollection::T return( JS_FALSE ); // That's odd; we've lost the pointer. wchar_t buffer[256]; - int len=swprintf( buffer, 256, L"[object Collection: %hs: %d members]", ScriptType->name, set->size() ); + int len = swprintf_s( buffer, ARRAY_SIZE(buffer), L"[object Collection: %hs: %d members]", ScriptType->name, set->size() ); buffer[255] = 0; if (len < 0 || len > 255) len=255; utf16string u16str(buffer, buffer+len); diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index f2cf42306c..3eac85abc8 100644 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -642,7 +642,7 @@ static void InitJsTimers() for(size_t i = 0; i < MAX_JS_TIMERS; i++) { const wchar_t* description = pos; - pos += swprintf(pos, 12, L"js_timer %d", (int)i)+1; + pos += swprintf_s(pos, 12, L"js_timer %d", (int)i)+1; timer_AddClient(&js_timer_clients[i], description); } diff --git a/source/scripting/SynchedJSObject.cpp b/source/scripting/SynchedJSObject.cpp index aa771ec2c4..ded1d8195c 100644 --- a/source/scripting/SynchedJSObject.cpp +++ b/source/scripting/SynchedJSObject.cpp @@ -72,8 +72,7 @@ template <> CStrW ToNetString(const SColour &data) { wchar_t buf[256]; - swprintf(buf, 256, L"%f %f %f %f", data.r, data.g, data.b, data.a); - buf[255]=0; + swprintf_s(buf, ARRAY_SIZE(buf), L"%f %f %f %f", data.r, data.g, data.b, data.a); return buf; }