diff --git a/.gitignore b/.gitignore index 896bcf5..f8bc89b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build builds .vscode +out +/.vs diff --git a/CMakeLists.txt b/CMakeLists.txt index cc35c4a..17ea5f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ src/Streams/BufferedStream.cpp src/Streams/ByteReader.cpp src/Streams/ByteWriter.cpp src/Streams/PtyStream.cpp +src/Text/StringConverter.cpp src/TextStreams/StreamReader.cpp src/TextStreams/StreamWriter.cpp src/TextStreams/TextReader.cpp @@ -93,12 +94,17 @@ option(TESSESFRAMEWORK_ENABLE_SETDATE "Enable setting date to file" ON) option(TESSESFRAMEWORK_LOGTOFILE "TessesFramework Log to file" OFF) option(TESSESFRAMEWORK_ENABLE_SDL2 "Enable SDL2" OFF) option(TESSESFRAMEWORK_FETCHCONTENT "TessesFramework fetchcontent" OFF) +option(TESSESFRAMEWORK_VENDERCERTCHAIN "Use the ca-certificates.crt in project rather than system" ON) if(TESSESFRAMEWORK_FETCHCONTENT) set(TESSESFRAMEWORK_CERT_BUNDLE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ca-certificates.crt" CACHE FILEPATH "Path to ca-chain") include(FetchContent) else() +if(TESSESFRAMEWORK_VENDERCERTCHAIN) +set(TESSESFRAMEWORK_CERT_BUNDLE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/ca-certificates.crt" CACHE FILEPATH "Path to ca-chain") +else() set(TESSESFRAMEWORK_CERT_BUNDLE_FILE "/etc/ssl/certs/ca-certificates.crt" CACHE FILEPATH "Path to ca-chain") endif() +endif() file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include) @@ -148,8 +154,8 @@ set(USE_SHARED_MBEDTLS_LIBRARY ON) endif() FetchContent_Declare( mbedtls - GIT_REPOSITORY https://github.com/Mbed-TLS/mbedtls.git - GIT_PROGRESS TRUE + URL https://downloads.tesses.net/cache/libraries/source/mbedtls-3.6.3.1.tar.bz2 + ) FetchContent_MakeAvailable(mbedtls) else() @@ -281,14 +287,15 @@ endif() add_library(tessesframework_shared SHARED ${TESSESFRAMEWORK_SOURCE}) TESSESFRAMEWORK_LINKDEPS(tessesframework_shared) -if(TESSESFRAMEWORK_FETCHCONTENT AND TESSESFRAMEWORK_ENABLE_MBED) +if(TESSESFRAMEWORK_ENABLE_MBED) +if(TESSESFRAMEWORK_FETCHCONTENT) target_link_libraries(tessesframework_shared PUBLIC mbedtls mbedx509 mbedcrypto everest p256m) else() target_link_libraries(tessesframework_shared PUBLIC mbedtls mbedx509 mbedcrypto) endif() - +endif() list(APPEND TessesFrameworkLibs tessesframework_shared) endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..3b7a47c --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,89 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "linux-debug", + "displayName": "Linux Debug", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { "microsoft.com/VisualStudioRemoteSettings/CMake/2.0": { "remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}" } } + }, + { + "name": "macos-debug", + "displayName": "macOS Debug", + "description": "Target a remote macOS system.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } } + }, + { + "name": "windows-base", + "description": "Target Windows with the Visual Studio development environment.", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe", + "TESSESFRAMEWORK_FETCHCONTENT": true + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)", + "inherits": "x64-debug", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "description": "Target Windows (32-bit) with the Visual Studio development environment. (Debug)", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "description": "Target Windows (32-bit) with the Visual Studio development environment. (RelWithDebInfo)", + "inherits": "x86-debug", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + } + ] +} \ No newline at end of file diff --git a/include/TessesFramework/Platform/Process.hpp b/include/TessesFramework/Platform/Process.hpp index 6c2fa58..3915656 100644 --- a/include/TessesFramework/Platform/Process.hpp +++ b/include/TessesFramework/Platform/Process.hpp @@ -10,17 +10,22 @@ class Process { public: std::string name; std::vector args; + std::string workingDirectory; std::vector> env; bool includeThisEnv; bool redirectStdIn=false; bool redirectStdOut=false; bool redirectStdErr=false; - //YOU ARE RESPONSABLE FOR FREEING THIS STREAM - Tesses::Framework::Streams::Stream* GetStdinStream(bool closeUnderlying=true); - //YOU ARE RESPONSABLE FOR FREEING THIS STREAM - Tesses::Framework::Streams::Stream* GetStdoutStream(bool closeUnderlying=true); - //YOU ARE RESPONSABLE FOR FREEING THIS STREAM - Tesses::Framework::Streams::Stream* GetStderrStream(bool closeUnderlying=true); + + void CloseStdInNow(); + //YOU ARE RESPONSABLE FOR FREEING THIS STREAM OBJECT + + Tesses::Framework::Streams::Stream* GetStdinStream(); + + //YOU ARE RESPONSABLE FOR FREEING THIS STREAM OBJECT + Tesses::Framework::Streams::Stream* GetStdoutStream(); + //YOU ARE RESPONSABLE FOR FREEING THIS STREAM OBJECT + Tesses::Framework::Streams::Stream* GetStderrStream(); Process(); Process(std::string name, std::vector args,bool includeThisEnv=true); diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp index 51fe944..c626c5d 100644 --- a/include/TessesFramework/TessesFramework.hpp +++ b/include/TessesFramework/TessesFramework.hpp @@ -18,6 +18,7 @@ #include "TextStreams/StdIOWriter.hpp" #include "TextStreams/StringReader.hpp" #include "TextStreams/StringWriter.hpp" +#include "Text/StringConverter.hpp" #include "Threading/Thread.hpp" #include "Threading/Mutex.hpp" #include "Threading/ThreadPool.hpp" diff --git a/include/TessesFramework/Text/StringConverter.hpp b/include/TessesFramework/Text/StringConverter.hpp new file mode 100644 index 0000000..2e8a4b4 --- /dev/null +++ b/include/TessesFramework/Text/StringConverter.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +namespace Tesses::Framework::Text::StringConverter { + class UTF8 { + public: + static void FromUTF16(std::basic_string& utf8, const std::basic_string& utf16); + static void FromUTF32(std::basic_string& utf8, const std::basic_string& utf32); + }; + class UTF16 { + public: + static void FromUTF8(std::basic_string& utf16, const std::basic_string& utf8); + static void FromUTF32(std::basic_string& utf16, const std::basic_string& utf32); + }; + class UTF32 { + public: + static void FromUTF8(std::basic_string& utf32, const std::basic_string& utf8); + static void FromUTF16(std::basic_string& utf32, const std::basic_string& utf16); + }; +} \ No newline at end of file diff --git a/src/Platform/Environment.cpp b/src/Platform/Environment.cpp index 5744883..ebe4108 100644 --- a/src/Platform/Environment.cpp +++ b/src/Platform/Environment.cpp @@ -5,6 +5,9 @@ #endif #if defined(_WIN32) #include +#include "TessesFramework/Filesystem/VFSFix.hpp" +#include "TessesFramework/Text/StringConverter.hpp" +using namespace Tesses::Framework::Text::StringConverter; #endif #if !defined(_WIN32) extern char** environ; @@ -128,12 +131,13 @@ namespace Tesses::Framework::Platform::Environment auto pathParts = HttpUtils::SplitString(path,";"); for(auto item : pathParts) { + + auto newPath = LocalFS.SystemToVFSPath(item) / realPath; for(auto item2 : pext) { auto newPathExt = newPath + item2; if(LocalFS.FileExists(newPathExt)) return newPathExt; } - auto newPath = LocalFS.SystemToVFSPath(item) / realPath; if(LocalFS.FileExists(newPath)) return newPath; } return realPath; @@ -158,20 +162,32 @@ namespace Tesses::Framework::Platform::Environment } void SetVariable(std::string name, std::optional var) { - if(var) - #if defined(_WIN32) - SetEnvironmentVariable(name.c_str(),var->c_str()); - #else + if (var) + #if defined(_WIN32) + { + std::u16string nameu16 = {}; + + std::u16string varu16 = {}; + + UTF16::FromUTF8(nameu16, name); + UTF16::FromUTF8(varu16, var.value()); + SetEnvironmentVariableW((LPCWSTR)nameu16.c_str(),(LPCWSTR)varu16.c_str()); + } + #else setenv(name.c_str(), var->c_str(),1); - #endif - else - #if defined(_WIN32) - { - SetEnvironmentVariable(name.c_str(),NULL); - } - #else - unsetenv(name.c_str()); - #endif + #endif + else + #if defined(_WIN32) + { + std::u16string nameu16 = {}; + + UTF16::FromUTF8(nameu16, name); + + SetEnvironmentVariableW((LPCWSTR)nameu16.c_str(),NULL); + } + #else + unsetenv(name.c_str()); + #endif } @@ -179,11 +195,14 @@ namespace Tesses::Framework::Platform::Environment void GetEnvironmentVariables(std::vector>& env) { #if defined(_WIN32) - char *environ0 = GetEnvironmentStrings(); - char* envthing = environ0; + auto environ0 = GetEnvironmentStringsW(); + auto envthing = environ0; while(*envthing) { - auto items = Http::HttpUtils::SplitString(envthing,"=",2); + std::u16string str = (const char16_t*)envthing; + std::string stru8; + UTF8::FromUTF16(stru8, str); + auto items = Http::HttpUtils::SplitString(stru8, "=", 2); if(items.size() == 2) { @@ -193,9 +212,9 @@ namespace Tesses::Framework::Platform::Environment { env.push_back(std::pair(items[0],"")); } - envthing += strlen(envthing)+1; + envthing += str.size() + 1; } - FreeEnvironmentStrings(environ0); + FreeEnvironmentStringsW(environ0); #else for(char** envthing = environ; envthing != NULL; envthing++) { diff --git a/src/Platform/Process.cpp b/src/Platform/Process.cpp index 6125ff4..9021dc9 100644 --- a/src/Platform/Process.cpp +++ b/src/Platform/Process.cpp @@ -3,16 +3,63 @@ #include "TessesFramework/Platform/Environment.hpp" +#if defined(_WIN32) +extern "C" { +#include +} +#include "TessesFramework/Filesystem/VFSFix.hpp" +#include 'TessesFramework/Text/StringConverter.hpp' +using namespace Tesses::Framework::Text::StringConverter; +static void escape_windows_args(std::string& str, std::vector args) +{ + bool first = true; + for (auto item : args) + { + if (first) + { + str.push_back('\"'); + first = false; + } + else { + str.append(" \""); + } + + for (auto c : item) + { + if (c == '"') str.append("\\\""); + else str.push_back(c); + } + + str.push_back('\"'); + } +} + + + +#else #include #include #include + +#endif + + + namespace Tesses::Framework::Platform { class ProcessData { public: //TODO: Implement for WIN32 #if defined(_WIN32) + STARTUPINFO si; + PROCESS_INFORMATION pi; + + HANDLE stdin_strm; + HANDLE stdout_strm; + HANDLE stderr_strm; + + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else int stdin_strm; @@ -23,7 +70,12 @@ namespace Tesses::Framework::Platform { ProcessData() { //TODO: Implement for WIN32 #if defined(_WIN32) - + stdin_strm = NULL; + stdout_strm = NULL; + stderr_strm = NULL; + + + #elif defined(GEKKO) || defined(__PS2__) || defined(__SWITCH__) #else @@ -35,23 +87,30 @@ namespace Tesses::Framework::Platform { }; class ProcessStream : public Tesses::Framework::Streams::Stream { #if defined(_WIN32) + HANDLE strm; + bool writing; + bool eos; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) #else int strm; bool writing; - bool shouldClose; bool eos; #endif public: #if defined(_WIN32) - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) - #else - ProcessStream(int strm, bool writing, bool shouldClose) + ProcessStream(HANDLE strm, bool writing) + { + this->strm = strm; + this->writing = writing; + this->eos = false; + } + #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) + #else + ProcessStream(int strm, bool writing) { this->strm = strm; this->writing = writing; - this->shouldClose=shouldClose; this->eos=false; } #endif @@ -59,7 +118,7 @@ namespace Tesses::Framework::Platform { { //TODO: Implement for WIN32 #if defined(_WIN32) - return true; + return this->strm == NULL || eos; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return true; #else @@ -70,7 +129,7 @@ namespace Tesses::Framework::Platform { { //TODO: Implement for WIN32 #if defined(_WIN32) - return false; + return !writing && this->strm != NULL; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return false; #else @@ -81,7 +140,8 @@ namespace Tesses::Framework::Platform { { //TODO: Implement for WIN32 #if defined(_WIN32) - return false; + + return writing && this->strm != NULL; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return false; #else @@ -93,7 +153,15 @@ namespace Tesses::Framework::Platform { { //TODO: Implement for WIN32 #if defined(_WIN32) - return 0; + if (this->strm == NULL || this->eos && writing) return 0; + + DWORD dataR = (DWORD)sz; + if (!ReadFile(this->strm, buff, dataR, &dataR, NULL)) + return 0; + if (dataR == 0) { + this->eos = true; + } + return (size_t)dataW; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return 0; #else @@ -110,30 +178,22 @@ namespace Tesses::Framework::Platform { { //TODO: Implement for WIN32 #if defined(_WIN32) - return 0; + if (this->strm == NULL || !writing) return 0; + DWORD dataW=(DWORD)sz; + if (!WriteFile(this->strm, buff, dataW, &dataW, NULL)) + return 0; + + return (size_t)dataW; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return 0; #else - if(this->strm < 0 && !writing) return 0; + if(this->strm < 0 || !writing) return 0; auto r = write(this->strm,buff,sz); if(r == -1) return 0; return (size_t)r; #endif } - - - ~ProcessStream() - { - //TODO: Implement for WIN32 - #if defined(_WIN32) - #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) - //do nothing - #else - if(this->strm > -1 && this->shouldClose) - close(this->strm); - #endif - } }; @@ -174,14 +234,28 @@ namespace Tesses::Framework::Platform { } Process::~Process() { + ProcessData* p = this->hidden.GetField(); + + #if defined(_WIN32) + if (p->stdin_strm != NULL) + CloseHandle(p->stdin_strm); + if (p->stdout_strm != NULL) + CloseHandle(p->stdout_strm); + + if (p->stderr_strm != NULL) + CloseHandle(p->stderr_strm); + + CloseHandle(p->pi.hProcess); + CloseHandle(p->pi.hThread); + #endif } bool Process::Start() { - auto p = + ProcessData* p = this->hidden.GetField(); std::vector> envs; @@ -204,6 +278,114 @@ namespace Tesses::Framework::Platform { } #if defined(_WIN32) + std::u16string u16_name; + std::u16string u16_args; + std::string args; + escape_windows_args(args,this->args); + UTF16::FromUTF8(u16_name,this->name); + UTF16::FromUTF8(u16_args, args); + std::u16string env = {}; + + for (auto envItem : this->env) + { + auto partOld = envItem.first + "=" + envItem.second; + std::u16string part = {}; + + UTF16::FromUTF8(part,partOld); + env.append(part); + env.push_back(0); + } + env.push_back(0); + + std::u16string workDir = {}; + + if (!this->workingDirectory.empty()) + UTF16::FromUTF8(workDir, this->workingDirectory); + + SECURITY_ATTRIBUTES attr; + attr.nLength = sizeof(attr); + attr.lpSecurityDescriptor = NULL; + attr.bInheritHandle = true; + p->si->hStdInput = NULL; + p->si->hStdOutput = NULL; + + p->si->hStdError = NULL; + + p->stdin_strm = NULL; + p->stdout_strm = NULL; + p->stderr_strm = NULL; + + if (this->redirectStdIn) + { + if (!CreatePipe(&p->si->hStdInput, &p->stdin_strm, &attr,0)) return false; + } + + if (this->redirectStdOut) + { + if (!CreatePipe(&p->stdout_strm, &p->si->hStdOutput, &attr, 0)) + { + if (this->redirectStdIn) + { + CloseHandle(p->stdin_strm); + CloseHandle(p->si->hStdInput); + } + return false; + } + } + if (this->redirectStdErr) + { + if (!CreatePipe(&p->stderr_strm, &p->si->hStdError, &attr, 0)) + { + if (this->redirectStdIn) + { + CloseHandle(p->stdin_strm); + CloseHandle(p->si->hStdInput); + } + if (this->redirectStdOut) + { + CloseHandle(p->stdout_strm); + CloseHandle(p->si->hStdOutput); + } + return false; + } + } + + if (!CreateProcessW(u16_name.c_str(), u16_args.data(), NULL, NULL, (this->redirectStdIn || this->redirectStdOut || this->redirectStdErr), CREATE_UNICODE_ENVIRONMENT, (LPCWSTR)env.c_str(), workDir.empty() ? (LPCWSTR)NULL : (LPCWSTR)workDir.c_str(), &(p->si), &(p->pi))) + { + if (this->redirectStdIn) + { + CloseHandle(p->stdin_strm); + CloseHandle(p->si->hStdInput); + } + if (this->redirectStdOut) + { + CloseHandle(p->stdout_strm); + CloseHandle(p->si->hStdOutput); + } + if (this->redirectStdErr) + { + CloseHandle(p->stderr_strm); + CloseHandle(p->si->hStdError); + } + return false; + } + + /* + BOOL +WINAPI +CreateProcessW( + _In_opt_ LPCWSTR lpApplicationName, + _Inout_opt_ LPWSTR lpCommandLine, + _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, + _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, + _In_ BOOL bInheritHandles, + _In_ DWORD dwCreationFlags, + _In_opt_ LPVOID lpEnvironment, + _In_opt_ LPCWSTR lpCurrentDirectory, + _In_ LPSTARTUPINFOW lpStartupInfo, + _Out_ LPPROCESS_INFORMATION lpProcessInformation + ); + */ return false; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return false; @@ -352,34 +534,34 @@ namespace Tesses::Framework::Platform { #endif } - Tesses::Framework::Streams::Stream* Process::GetStdinStream(bool closeUnderlying) + Tesses::Framework::Streams::Stream* Process::GetStdinStream() { #if defined(_WIN32) return nullptr; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return nullptr; #else - return new ProcessStream(this->hidden.GetField()->stdin_strm,true,closeUnderlying); + return new ProcessStream(this->hidden.GetField()->stdin_strm,true); #endif } - Tesses::Framework::Streams::Stream* Process::GetStdoutStream(bool closeUnderlying) + Tesses::Framework::Streams::Stream* Process::GetStdoutStream() { #if defined(_WIN32) return nullptr; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return nullptr; #else - return new ProcessStream(this->hidden.GetField()->stdout_strm,false,closeUnderlying); + return new ProcessStream(this->hidden.GetField()->stdout_strm,false); #endif } - Tesses::Framework::Streams::Stream* Process::GetStderrStream(bool closeUnderlying) + Tesses::Framework::Streams::Stream* Process::GetStderrStream() { #if defined(_WIN32) return nullptr; #elif defined(GEKKO) || defined(__PS2__) defined(__SWITCH__) return nullptr; #else - return new ProcessStream(this->hidden.GetField()->stderr_strm,false,closeUnderlying); + return new ProcessStream(this->hidden.GetField()->stderr_strm,false); #endif } diff --git a/src/Streams/NetworkStream.cpp b/src/Streams/NetworkStream.cpp index ebdad10..311dfa1 100644 --- a/src/Streams/NetworkStream.cpp +++ b/src/Streams/NetworkStream.cpp @@ -6,6 +6,7 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils; #if defined(TESSESFRAMEWORK_ENABLE_NETWORKING) + #if defined(GEKKO) #define ss_family sin_family @@ -24,9 +25,11 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils; #include #include #include +#include #undef min #pragma comment(lib, "ws2_32.lib") #else + extern "C" { #include #include diff --git a/src/Streams/Stream.cpp b/src/Streams/Stream.cpp index 03aade7..f0f523a 100644 --- a/src/Streams/Stream.cpp +++ b/src/Streams/Stream.cpp @@ -114,7 +114,7 @@ namespace Tesses::Framework::Streams { strm.Flush(); #if defined(_WIN32) - delete buffer; + delete[] buffer; #endif } Stream::~Stream() diff --git a/src/Text/StringConverter.cpp b/src/Text/StringConverter.cpp new file mode 100644 index 0000000..a0ff9f3 --- /dev/null +++ b/src/Text/StringConverter.cpp @@ -0,0 +1,270 @@ +#include "TessesFramework/Text/StringConverter.hpp" + +namespace Tesses::Framework::Text::StringConverter { + void UTF8::FromUTF16(std::basic_string& utf8, const std::basic_string& utf16) + { + for (size_t i=0; i < utf16.size();i++) + { + char32_t c = utf16[i]; + if ((c & 0xFC00) == 0xD800) + { + c = (c & 0x03FF) << 10; + i++; + if (i >= utf16.size()) return; + + + char32_t c2 = utf16[i]; + if ((c2 & 0xFC00) != 0xDC00) continue; + + + c |= (c2 & 0x03FF); + + c += 0x10000; + } + + if (c <= 0x7F) + { + utf8.push_back((char)c); + } + else if (c >= 0x80 && c <= 0x7FF) + { + uint8_t high = 0b11000000 | ((c >> 6) & 0b00011111); + uint8_t low = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)high); + utf8.push_back((char)low); + } + else if (c >= 0x800 && c <= 0xFFFF) + { + uint8_t highest = 0b11100000 | ((c >> 12) & 0b00001111); + uint8_t high = 0b10000000 | ((c >> 6) & 0b00111111); + uint8_t low = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)highest); + utf8.push_back((char)high); + utf8.push_back((char)low); + } + else if (c >= 0x010000 && c <= 0x10FFFF) + { + uint8_t highest = 0b11110000 | ((c >> 18) & 0b00000111); + uint8_t high = 0b10000000 | ((c >> 12) & 0b00111111); + uint8_t low = 0b10000000 | ((c >> 6) & 0b00111111); + uint8_t lowest = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)highest); + utf8.push_back((char)high); + utf8.push_back((char)low); + utf8.push_back((char)lowest); + } + + } + } + void UTF8::FromUTF32(std::basic_string& utf8, const std::basic_string& utf32) + { + for (auto c : utf32) + { + if (c <= 0x7F) + { + utf8.push_back((char)c); + } + else if (c >= 0x80 && c <= 0x7FF) + { + uint8_t high = 0b11000000 | ((c >> 6) & 0b00011111); + uint8_t low = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)high); + utf8.push_back((char)low); + } + else if (c >= 0x800 && c <= 0xFFFF) + { + uint8_t highest = 0b11100000 | ((c >> 12) & 0b00001111); + uint8_t high = 0b10000000 | ((c >> 6) & 0b00111111); + uint8_t low = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)highest); + utf8.push_back((char)high); + utf8.push_back((char)low); + } + else if (c >= 0x010000 && c <= 0x10FFFF) + { + uint8_t highest = 0b11110000 | ((c >> 18) & 0b00000111); + uint8_t high = 0b10000000 | ((c >> 12) & 0b00111111); + uint8_t low = 0b10000000 | ((c >> 6) & 0b00111111); + uint8_t lowest = 0b10000000 | (c & 0b00111111); + utf8.push_back((char)highest); + utf8.push_back((char)high); + utf8.push_back((char)low); + utf8.push_back((char)lowest); + } + + } + } + + void UTF16::FromUTF8(std::basic_string& utf16, const std::basic_string& utf8) + { + for (size_t i = 0; i < utf8.size();i++) + { + uint8_t c = (uint8_t)utf8[i]; + char32_t cres = 0; + if (c <= 127) + { + cres = (char32_t)c; + } + else if ((c & 0b11100000) == 0b11000000) + { + if (i + 1 < utf8.size()) + { + + uint8_t c2 = (uint8_t)utf8[++i]; + cres |= c2 & 0b00111111; + cres |= (c & 0b00011111) << 6; + + } + else { + i++; + continue; + }; + } + else if ((c & 0b11110000) == 0b11100000) + { + if (i + 2 < utf8.size()) + { + uint8_t c2 = (uint8_t)utf8[++i]; + uint8_t c3 = (uint8_t)utf8[++i]; + cres |= c3 & 0b00111111; + cres |= (c2 & 0b00111111) << 6; + cres |= (c & 0b00001111) << 12; + + } + else { i += 2; continue; } + } + else if ((c & 0b11111000) == 0b11110000) + { + if (i + 3 < utf8.size()) + { + uint8_t c2 = (uint8_t)utf8[++i]; + uint8_t c3 = (uint8_t)utf8[++i]; + uint8_t c4 = (uint8_t)utf8[++i]; + + cres |= c4 & 0b00111111; + cres |= (c3 & 0b00111111) << 6; + cres |= (c2 & 0b00111111) << 12; + cres |= (c & 0b00000111) << 18; + + } + else { i += 3; continue; } + } + if (cres >= 0x10000 && cres <= 0x10FFFF) + { + auto subtracted = cres - 0x10000; + + auto high = (0x3FF & (subtracted >> 10)) | 0xD800; + auto low = (0x3FF & subtracted) | 0xDC00; + + utf16.push_back(high); + utf16.push_back(low); + } + else { + utf16.push_back((char16_t)cres); + } + + } + } + void UTF16::FromUTF32(std::basic_string& utf16, const std::basic_string& utf32) + { + for (auto cres : utf32) + { + if (cres >= 0x10000 && cres <= 0x10FFFF) + { + auto subtracted = cres - 0x10000; + + auto high = (0x3FF & (subtracted >> 10)) | 0xD800; + auto low = (0x3FF & subtracted) | 0xDC00; + + utf16.push_back(high); + utf16.push_back(low); + } + else { + utf16.push_back((char16_t)cres); + } + } + } + + void UTF32::FromUTF8(std::basic_string& utf32, const std::basic_string& utf8) + { + for (size_t i = 0; i < utf8.size();i++) + { + uint8_t c = (uint8_t)utf8[i]; + char32_t cres = 0; + if (c <= 127) + { + cres=(char32_t)c; + } + else if ((c & 0b11100000) == 0b11000000) + { + if (i + 1 < utf8.size()) + { + + uint8_t c2 = (uint8_t)utf8[++i]; + cres |= c2 & 0b00111111; + cres |= (c & 0b00011111) << 6; + + } + else { + i++; + continue; + }; + } + else if ((c & 0b11110000) == 0b11100000) + { + if (i + 2 < utf8.size()) + { + uint8_t c2 = (uint8_t)utf8[++i]; + uint8_t c3 = (uint8_t)utf8[++i]; + cres |= c3 & 0b00111111; + cres |= (c2 & 0b00111111) << 6; + cres |= (c & 0b00001111) << 12; + + } + else { i += 2; continue; } + } + else if ((c & 0b11111000) == 0b11110000) + { + if (i + 3 < utf8.size()) + { + uint8_t c2 = (uint8_t)utf8[++i]; + uint8_t c3 = (uint8_t)utf8[++i]; + uint8_t c4 = (uint8_t)utf8[++i]; + + cres |= c4 & 0b00111111; + cres |= (c3 & 0b00111111) << 6; + cres |= (c2 & 0b00111111) << 12; + cres |= (c & 0b00000111) << 18; + + } + else { i += 3; continue; } + } + utf32.push_back(cres); + } + + } + + void UTF32::FromUTF16(std::basic_string& utf32, const std::basic_string& utf16) + { + for (size_t i = 0; i < utf16.size();i++) + { + char32_t c = utf16[i]; + if ((c & 0xFC00) == 0xD800) + { + c = (c & 0x03FF) << 10; + i++; + if (i >= utf16.size()) return; + + + char32_t c2 = utf16[i]; + if ((c2 & 0xFC00) != 0xDC00) continue; + + + c |= (c2 & 0x03FF); + + c += 0x10000; + } + utf32.push_back(c); + } + } +} \ No newline at end of file