diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index fcb56a6c32..7797e8a611 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -30,6 +30,7 @@ target_sources(common PRIVATE
RwMutex.cpp
Semaphore.cpp
StringHelpers.cpp
+ StringUtil.cpp
ThreadTools.cpp
wxGuiTools.cpp
wxHelpers.cpp
@@ -80,6 +81,7 @@ target_sources(common PRIVATE
ScopedAlloc.h
ScopedPtrMT.h
StringHelpers.h
+ StringUtil.h
Threading.h
TraceLog.h
wxBaseTools.h
diff --git a/common/StringUtil.cpp b/common/StringUtil.cpp
new file mode 100644
index 0000000000..fab6cdf9a5
--- /dev/null
+++ b/common/StringUtil.cpp
@@ -0,0 +1,260 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2021 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 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 PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+#include "StringUtil.h"
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include "RedtapeWindows.h"
+#endif
+
+namespace StringUtil
+{
+ std::string StdStringFromFormat(const char* format, ...)
+ {
+ std::va_list ap;
+ va_start(ap, format);
+ std::string ret = StdStringFromFormatV(format, ap);
+ va_end(ap);
+ return ret;
+ }
+
+ std::string StdStringFromFormatV(const char* format, std::va_list ap)
+ {
+ std::va_list ap_copy;
+ va_copy(ap_copy, ap);
+
+#ifdef _WIN32
+ int len = _vscprintf(format, ap_copy);
+#else
+ int len = std::vsnprintf(nullptr, 0, format, ap_copy);
+#endif
+ va_end(ap_copy);
+
+ std::string ret;
+ ret.resize(len);
+ std::vsnprintf(ret.data(), ret.size() + 1, format, ap);
+ return ret;
+ }
+
+ bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive /*= true*/)
+ {
+ if (case_sensitive)
+ {
+ const char* cp = nullptr;
+ const char* mp = nullptr;
+
+ while ((*subject) && (*mask != '*'))
+ {
+ if ((*mask != '?') && (std::tolower(*mask) != std::tolower(*subject)))
+ return false;
+
+ mask++;
+ subject++;
+ }
+
+ while (*subject)
+ {
+ if (*mask == '*')
+ {
+ if (*++mask == 0)
+ return true;
+
+ mp = mask;
+ cp = subject + 1;
+ }
+ else
+ {
+ if ((*mask == '?') || (std::tolower(*mask) == std::tolower(*subject)))
+ {
+ mask++;
+ subject++;
+ }
+ else
+ {
+ mask = mp;
+ subject = cp++;
+ }
+ }
+ }
+
+ while (*mask == '*')
+ {
+ mask++;
+ }
+
+ return *mask == 0;
+ }
+ else
+ {
+ const char* cp = nullptr;
+ const char* mp = nullptr;
+
+ while ((*subject) && (*mask != '*'))
+ {
+ if ((*mask != *subject) && (*mask != '?'))
+ return false;
+
+ mask++;
+ subject++;
+ }
+
+ while (*subject)
+ {
+ if (*mask == '*')
+ {
+ if (*++mask == 0)
+ return true;
+
+ mp = mask;
+ cp = subject + 1;
+ }
+ else
+ {
+ if ((*mask == *subject) || (*mask == '?'))
+ {
+ mask++;
+ subject++;
+ }
+ else
+ {
+ mask = mp;
+ subject = cp++;
+ }
+ }
+ }
+
+ while (*mask == '*')
+ {
+ mask++;
+ }
+
+ return *mask == 0;
+ }
+ }
+
+ std::size_t Strlcpy(char* dst, const char* src, std::size_t size)
+ {
+ std::size_t len = std::strlen(src);
+ if (len < size)
+ {
+ std::memcpy(dst, src, len + 1);
+ }
+ else
+ {
+ std::memcpy(dst, src, size - 1);
+ dst[size - 1] = '\0';
+ }
+ return len;
+ }
+
+ std::size_t Strlcpy(char* dst, const std::string_view& src, std::size_t size)
+ {
+ std::size_t len = src.length();
+ if (len < size)
+ {
+ std::memcpy(dst, src.data(), len);
+ dst[len] = '\0';
+ }
+ else
+ {
+ std::memcpy(dst, src.data(), size - 1);
+ dst[size - 1] = '\0';
+ }
+ return len;
+ }
+
+ std::optional> DecodeHex(const std::string_view& in)
+ {
+ std::vector data;
+ data.reserve(in.size() / 2);
+
+ for (size_t i = 0; i < in.size() / 2; i++)
+ {
+ std::optional byte = StringUtil::FromChars(in.substr(i * 2, 2), 16);
+ if (byte.has_value())
+ data.push_back(*byte);
+ else
+ return std::nullopt;
+ }
+
+ return {data};
+ }
+
+ std::string EncodeHex(const u8* data, int length)
+ {
+ std::stringstream ss;
+ for (int i = 0; i < length; i++)
+ ss << std::hex << std::setfill('0') << std::setw(2) << static_cast(data[i]);
+
+ return ss.str();
+ }
+
+#ifdef _WIN32
+
+ std::wstring UTF8StringToWideString(const std::string_view& str)
+ {
+ std::wstring ret;
+ if (!UTF8StringToWideString(ret, str))
+ return {};
+
+ return ret;
+ }
+
+ bool UTF8StringToWideString(std::wstring& dest, const std::string_view& str)
+ {
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), nullptr, 0);
+ if (wlen < 0)
+ return false;
+
+ dest.resize(wlen);
+ if (wlen > 0 && MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), dest.data(), wlen) < 0)
+ return false;
+
+ return true;
+ }
+
+ std::string WideStringToUTF8String(const std::wstring_view& str)
+ {
+ std::string ret;
+ if (!WideStringToUTF8String(ret, str))
+ return {};
+
+ return ret;
+ }
+
+ bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str)
+ {
+ int mblen = WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast(str.length()), nullptr, 0, nullptr, nullptr);
+ if (mblen < 0)
+ return false;
+
+ dest.resize(mblen);
+ if (mblen > 0 && WideCharToMultiByte(CP_UTF8, 0, str.data(), static_cast(str.length()), dest.data(), mblen,
+ nullptr, nullptr) < 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+#endif
+
+} // namespace StringUtil
diff --git a/common/StringUtil.h b/common/StringUtil.h
new file mode 100644
index 0000000000..46a91f0a4d
--- /dev/null
+++ b/common/StringUtil.h
@@ -0,0 +1,218 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2021 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 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 PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "Pcsx2Types.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(__has_include) && __has_include()
+#include
+#ifndef _MSC_VER
+#include
+#endif
+#else
+#include
+#endif
+
+// TODO: Remove me once wx is gone.
+#include
+
+namespace StringUtil
+{
+ /// Constructs a std::string from a format string.
+#ifdef __GNUC__
+ std::string StdStringFromFormat(const char* format, ...) __attribute__((format(printf, 1, 2)));
+#else
+ std::string StdStringFromFormat(const char* format, ...);
+#endif
+ std::string StdStringFromFormatV(const char* format, std::va_list ap);
+
+ /// Checks if a wildcard matches a search string.
+ bool WildcardMatch(const char* subject, const char* mask, bool case_sensitive = true);
+
+ /// Safe version of strlcpy.
+ std::size_t Strlcpy(char* dst, const char* src, std::size_t size);
+
+ /// Strlcpy from string_view.
+ std::size_t Strlcpy(char* dst, const std::string_view& src, std::size_t size);
+
+ /// Platform-independent strcasecmp
+ static inline int Strcasecmp(const char* s1, const char* s2)
+ {
+#ifdef _MSC_VER
+ return _stricmp(s1, s2);
+#else
+ return strcasecmp(s1, s2);
+#endif
+ }
+
+ /// Platform-independent strcasecmp
+ static inline int Strncasecmp(const char* s1, const char* s2, std::size_t n)
+ {
+#ifdef _MSC_VER
+ return _strnicmp(s1, s2, n);
+#else
+ return strncasecmp(s1, s2, n);
+#endif
+ }
+
+ /// Wrapper arond std::from_chars
+ template ::value, bool> = true>
+ inline std::optional FromChars(const std::string_view& str, int base = 10)
+ {
+ T value;
+
+#if defined(__has_include) && __has_include()
+ const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value, base);
+ if (result.ec != std::errc())
+ return std::nullopt;
+#else
+ std::string temp(str);
+ std::istringstream ss(temp);
+ ss >> std::setbase(base) >> value;
+ if (ss.fail())
+ return std::nullopt;
+#endif
+
+ return value;
+ }
+
+ template ::value, bool> = true>
+ inline std::optional FromChars(const std::string_view& str)
+ {
+ T value;
+
+#if defined(__has_include) && __has_include() && defined(_MSC_VER)
+ const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.length(), value);
+ if (result.ec != std::errc())
+ return std::nullopt;
+#else
+ /// libstdc++ does not support from_chars with floats yet
+ std::string temp(str);
+ std::istringstream ss(temp);
+ ss >> value;
+ if (ss.fail())
+ return std::nullopt;
+#endif
+
+ return value;
+ }
+
+ /// Explicit override for booleans
+ template <>
+ inline std::optional FromChars(const std::string_view& str, int base)
+ {
+ if (Strncasecmp("true", str.data(), str.length()) == 0 || Strncasecmp("yes", str.data(), str.length()) == 0 ||
+ Strncasecmp("on", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0 ||
+ Strncasecmp("enabled", str.data(), str.length()) == 0 || Strncasecmp("1", str.data(), str.length()) == 0)
+ {
+ return true;
+ }
+
+ if (Strncasecmp("false", str.data(), str.length()) == 0 || Strncasecmp("no", str.data(), str.length()) == 0 ||
+ Strncasecmp("off", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0 ||
+ Strncasecmp("disabled", str.data(), str.length()) == 0 || Strncasecmp("0", str.data(), str.length()) == 0)
+ {
+ return false;
+ }
+
+ return std::nullopt;
+ }
+
+ /// Encode/decode hexadecimal byte buffers
+ std::optional> DecodeHex(const std::string_view& str);
+ std::string EncodeHex(const u8* data, int length);
+
+ /// starts_with from C++20
+ static inline bool StartsWith(const std::string_view& str, const char* prefix)
+ {
+ return (str.compare(0, std::strlen(prefix), prefix) == 0);
+ }
+ static inline bool EndsWith(const std::string_view& str, const char* suffix)
+ {
+ const std::size_t suffix_length = std::strlen(suffix);
+ return (str.length() >= suffix_length && str.compare(str.length() - suffix_length, suffix_length, suffix) == 0);
+ }
+
+ /// Strided memcpy/memcmp.
+ static inline void StrideMemCpy(void* dst, std::size_t dst_stride, const void* src, std::size_t src_stride,
+ std::size_t copy_size, std::size_t count)
+ {
+ const u8* src_ptr = static_cast(src);
+ u8* dst_ptr = static_cast(dst);
+ for (std::size_t i = 0; i < count; i++)
+ {
+ std::memcpy(dst_ptr, src_ptr, copy_size);
+ src_ptr += src_stride;
+ dst_ptr += dst_stride;
+ }
+ }
+
+ static inline int StrideMemCmp(const void* p1, std::size_t p1_stride, const void* p2, std::size_t p2_stride,
+ std::size_t copy_size, std::size_t count)
+ {
+ const u8* p1_ptr = static_cast(p1);
+ const u8* p2_ptr = static_cast(p2);
+ for (std::size_t i = 0; i < count; i++)
+ {
+ int result = std::memcmp(p1_ptr, p2_ptr, copy_size);
+ if (result != 0)
+ return result;
+ p2_ptr += p2_stride;
+ p1_ptr += p1_stride;
+ }
+
+ return 0;
+ }
+
+ /// Converts a wxString to a UTF-8 std::string.
+ static std::string wxStringToUTF8String(const wxString& str)
+ {
+ const wxScopedCharBuffer buf(str.ToUTF8());
+ return std::string(buf.data(), buf.length());
+ }
+
+ /// Converts a UTF-8 std::string to a wxString.
+ static wxString UTF8StringToWxString(const std::string_view& str)
+ {
+ return wxString::FromUTF8(str.data(), str.length());
+ }
+
+ /// Converts a UTF-8 std::string to a wxString.
+ static wxString UTF8StringToWxString(const std::string& str)
+ {
+ return wxString::FromUTF8(str.data(), str.length());
+ }
+
+#ifdef _WIN32
+
+ /// Converts the specified UTF-8 string to a wide string.
+ std::wstring UTF8StringToWideString(const std::string_view& str);
+ bool UTF8StringToWideString(std::wstring& dest, const std::string_view& str);
+
+ /// Converts the specified wide string to a UTF-8 string.
+ std::string WideStringToUTF8String(const std::wstring_view& str);
+ bool WideStringToUTF8String(std::string& dest, const std::wstring_view& str);
+
+#endif
+
+} // namespace StringUtil
diff --git a/common/common.vcxproj b/common/common.vcxproj
index 7c943449ae..0b05771ffe 100644
--- a/common/common.vcxproj
+++ b/common/common.vcxproj
@@ -52,6 +52,7 @@
+
@@ -99,6 +100,7 @@
+
diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters
index cce1969f44..9396415780 100644
--- a/common/common.vcxproj.filters
+++ b/common/common.vcxproj.filters
@@ -121,6 +121,9 @@
Source Files
+
+ Source Files
+
@@ -270,6 +273,9 @@
Header Files
+
+ Header Files
+