初始适配

This commit is contained in:
chinosk
2025-03-17 21:53:55 +00:00
commit 45338b40cd
334 changed files with 113217 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
#define KEY_W 51
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_F 34
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_V 50
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define KEY_ADD 70
#define KEY_SUB 69
#define WM_KEYDOWN 0
#define WM_KEYUP 1
#define BTN_A 96
#define BTN_B 97
#define BTN_X 99
#define BTN_Y 100
#define BTN_LB 102
#define BTN_RB 103
#define BTN_THUMBL 106
#define BTN_THUMBR 107
#define BTN_SELECT 109
#define BTN_START 108
#define BTN_SHARE 130
#define BTN_XBOX 110

1667
src/GakumasLocalify/Hook.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
#ifndef GAKUMAS_LOCALIFY_HOOK_H
#define GAKUMAS_LOCALIFY_HOOK_H
#include <string>
namespace GakumasLocal::Hook
{
void Install();
}
#endif //GAKUMAS_LOCALIFY_HOOK_H

View File

@@ -0,0 +1,417 @@
#pragma once
#include "../deps/UnityResolve/UnityResolve.hpp"
#include "Log.h"
#include <memory>
namespace Il2cppUtils {
using namespace GakumasLocal;
struct Il2CppClassHead {
// The following fields are always valid for a Il2CppClass structure
const void* image;
void* gc_desc;
const char* name;
const char* namespaze;
};
struct Il2CppObject
{
union
{
void* klass;
void* vtable;
};
void* monitor;
};
enum Il2CppTypeEnum
{
IL2CPP_TYPE_END = 0x00, /* End of List */
IL2CPP_TYPE_VOID = 0x01,
IL2CPP_TYPE_BOOLEAN = 0x02,
IL2CPP_TYPE_CHAR = 0x03,
IL2CPP_TYPE_I1 = 0x04,
IL2CPP_TYPE_U1 = 0x05,
IL2CPP_TYPE_I2 = 0x06,
IL2CPP_TYPE_U2 = 0x07,
IL2CPP_TYPE_I4 = 0x08,
IL2CPP_TYPE_U4 = 0x09,
IL2CPP_TYPE_I8 = 0x0a,
IL2CPP_TYPE_U8 = 0x0b,
IL2CPP_TYPE_R4 = 0x0c,
IL2CPP_TYPE_R8 = 0x0d,
IL2CPP_TYPE_STRING = 0x0e,
IL2CPP_TYPE_PTR = 0x0f,
IL2CPP_TYPE_BYREF = 0x10,
IL2CPP_TYPE_VALUETYPE = 0x11,
IL2CPP_TYPE_CLASS = 0x12,
IL2CPP_TYPE_VAR = 0x13,
IL2CPP_TYPE_ARRAY = 0x14,
IL2CPP_TYPE_GENERICINST = 0x15,
IL2CPP_TYPE_TYPEDBYREF = 0x16,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U = 0x19,
IL2CPP_TYPE_FNPTR = 0x1b,
IL2CPP_TYPE_OBJECT = 0x1c,
IL2CPP_TYPE_SZARRAY = 0x1d,
IL2CPP_TYPE_MVAR = 0x1e,
IL2CPP_TYPE_CMOD_REQD = 0x1f,
IL2CPP_TYPE_CMOD_OPT = 0x20,
IL2CPP_TYPE_INTERNAL = 0x21,
IL2CPP_TYPE_MODIFIER = 0x40,
IL2CPP_TYPE_SENTINEL = 0x41,
IL2CPP_TYPE_PINNED = 0x45,
IL2CPP_TYPE_ENUM = 0x55
};
typedef struct Il2CppType
{
void* dummy;
unsigned int attrs : 16;
Il2CppTypeEnum type : 8;
unsigned int num_mods : 6;
unsigned int byref : 1;
unsigned int pinned : 1;
} Il2CppType;
struct Il2CppReflectionType
{
Il2CppObject object;
const Il2CppType* type;
};
struct Resolution_t {
int width;
int height;
int herz;
};
struct FieldInfo {
const char* name;
const Il2CppType* type;
uintptr_t parent;
int32_t offset;
uint32_t token;
};
struct MethodInfo {
uintptr_t methodPointer;
uintptr_t invoker_method;
const char* name;
uintptr_t klass;
const Il2CppType* return_type;
//const ParameterInfo* parameters;
// const void* return_type;
const void* parameters;
uintptr_t methodDefinition;
uintptr_t genericContainer;
uint32_t token;
uint16_t flags;
uint16_t iflags;
uint16_t slot;
uint8_t parameters_count;
uint8_t is_generic : 1;
uint8_t is_inflated : 1;
uint8_t wrapper_type : 1;
uint8_t is_marshaled_from_native : 1;
};
static UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className) {
const auto assembly = UnityResolve::Get(assemblyName);
if (!assembly) {
Log::ErrorFmt("GetMethodPointer error: assembly %s not found.", assemblyName.c_str());
return nullptr;
}
const auto pClass = assembly->Get(className, nameSpaceName);
if (!pClass) {
Log::ErrorFmt("GetMethodPointer error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
return nullptr;
}
return pClass;
}
/*
UnityResolve::Method* GetMethodIl2cpp(const char* assemblyName, const char* nameSpaceName,
const char* className, const char* methodName, const int argsCount) {
auto domain = UnityResolve::Invoke<void*>("il2cpp_domain_get");
UnityResolve::Invoke<void*>("il2cpp_thread_attach", domain);
auto image = UnityResolve::Invoke<void*>("il2cpp_assembly_get_image", domain);
if (!image) {
Log::ErrorFmt("GetMethodIl2cpp error: assembly %s not found.", assemblyName);
return nullptr;
}
Log::Debug("GetMethodIl2cpp 1");
auto klass = UnityResolve::Invoke<void*>("il2cpp_class_from_name", image, nameSpaceName, className);
if (!klass) {
Log::ErrorFmt("GetMethodIl2cpp error: Class %s::%s not found.", nameSpaceName, className);
return nullptr;
}
Log::Debug("GetMethodIl2cpp 2");
auto ret = UnityResolve::Invoke<UnityResolve::Method*>("il2cpp_class_get_method_from_name", klass, methodName, argsCount);
if (!ret) {
Log::ErrorFmt("GetMethodIl2cpp error: method %s::%s.%s not found.", nameSpaceName, className, methodName);
return nullptr;
}
return ret;
}*/
static UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
const auto assembly = UnityResolve::Get(assemblyName);
if (!assembly) {
Log::ErrorFmt("GetMethod error: assembly %s not found.", assemblyName.c_str());
return nullptr;
}
const auto pClass = assembly->Get(className, nameSpaceName);
if (!pClass) {
Log::ErrorFmt("GetMethod error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
return nullptr;
}
auto method = pClass->Get<UnityResolve::Method>(methodName, args);
if (!method) {
/*
method = GetMethodIl2cpp(assemblyName.c_str(), nameSpaceName.c_str(), className.c_str(),
methodName.c_str(), args.size() == 0 ? -1 : args.size());
if (!method) {
Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
return nullptr;
}*/
Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
return nullptr;
}
return method;
}
static void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
if (method) {
return method->function;
}
return nullptr;
}
static void* il2cpp_resolve_icall(const char* s) {
return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
}
static Il2CppClassHead* get_class_from_instance(const void* instance) {
return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
}
static MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount);
}
static uintptr_t il2cpp_class_get_method_pointer_from_name(void* klass, const char* name, int argsCount) {
auto findKlass = il2cpp_class_get_method_from_name(klass, name, argsCount);
if (findKlass) {
return findKlass->methodPointer;
}
Log::ErrorFmt("method: %s not found", name);
return 0;
}
static void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate) {
void* iter{};
while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
{
if (static_cast<decltype(predicate)>(predicate)(curNestedClass))
{
return curNestedClass;
}
}
return nullptr;
}
static void* find_nested_class_from_name(void* klass, const char* name) {
return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
});
}
template <typename RType>
static auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
}
template <typename RType>
static auto ClassGetFieldValue(void* obj, FieldInfo* field) -> RType {
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
}
template <typename T>
static auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, T value) -> void {
const auto fieldPtr = static_cast<std::byte*>(obj) + field->offset;
std::memcpy(fieldPtr, std::addressof(value), sizeof(T));
}
template <typename RType>
static auto ClassSetFieldValue(void* obj, FieldInfo* field, RType value) -> void {
*reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value;
}
static void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib") {
using Il2CppString = UnityResolve::UnityType::String;
static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
GetMethodPointer("mscorlib.dll", "System.Reflection",
"Assembly", "Load", {"*"})
);
static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
GetMethodPointer("mscorlib.dll", "System.Reflection",
"Assembly", "GetType", {"*"})
);
static auto reflectionAssembly = assemblyLoad(Il2CppString::New(assemblyName));
auto reflectionType = assemblyGetType(reflectionAssembly, Il2CppString::New(typeStr));
return UnityResolve::Invoke<void*>("il2cpp_class_from_system_type", reflectionType);
}
static std::unordered_map<std::string, std::unordered_map<int, std::string>> enumToValueMapCache{};
static std::unordered_map<int, std::string> EnumToValueMap(Il2CppClassHead* enumClass, bool useCache) {
std::unordered_map<int, std::string> ret{};
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", enumClass);
if (isEnum) {
Il2cppUtils::FieldInfo* field = nullptr;
void* iter = nullptr;
std::string cacheName = std::string(enumClass->namespaze) + "::" + enumClass->name;
if (useCache) {
if (auto it = enumToValueMapCache.find(cacheName); it != enumToValueMapCache.end()) {
return it->second;
}
}
while ((field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_fields", enumClass, &iter))) {
// Log::DebugFmt("field: %s, off: %d", field->name, field->offset);
if (field->offset > 0) continue; // 非 static
if (strcmp(field->name, "value__") == 0) {
continue;
}
int value;
UnityResolve::Invoke<void>("il2cpp_field_static_get_value", field, &value);
// Log::DebugFmt("returnClass: %s - %s: 0x%x", enumClass->name, field->name, value);
std::string itemName = std::string(enumClass->name) + "_" + field->name;
ret.emplace(value, std::move(itemName));
}
if (useCache) {
enumToValueMapCache.emplace(std::move(cacheName), ret);
}
}
return ret;
}
template <typename T = void*>
static void iterate_IEnumerable(const void* obj, std::invocable<T> auto&& receiver)
{
const auto klass = get_class_from_instance(obj);
const auto getEnumeratorMethod = reinterpret_cast<void* (*)(const void*)>(il2cpp_class_get_method_from_name(klass, "GetEnumerator", 0)->methodPointer);
const auto enumerator = getEnumeratorMethod(obj);
const auto enumeratorClass = get_class_from_instance(enumerator);
const auto getCurrentMethod = reinterpret_cast<T(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer);
const auto moveNextMethod = reinterpret_cast<bool(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer);
while (moveNextMethod(enumerator))
{
static_cast<decltype(receiver)>(receiver)(getCurrentMethod(enumerator));
}
}
namespace Tools {
template <typename T = void*>
class CSListEditor {
public:
CSListEditor(void* list) {
list_klass = get_class_from_instance(list);
lst = list;
lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2);
lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
}
void Add(T value) {
lst_Add(lst, value, lst_Add_method);
}
T get_Item(int index) {
return lst_get_Item(lst, index, lst_get_Item_method);
}
void set_Item(int index, T value) {
return lst_set_Item(lst, index, value, lst_set_Item_method);
}
int get_Count() {
return lst_get_Count(lst, lst_get_Count_method);
}
T operator[] (int key) {
return get_Item(key);
}
class Iterator {
public:
Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}
T operator*() const {
return editor->get_Item(index);
}
Iterator& operator++() {
++index;
return *this;
}
bool operator!=(const Iterator& other) const {
return index != other.index;
}
private:
CSListEditor<T>* editor;
int index;
};
Iterator begin() {
return Iterator(this, 0);
}
Iterator end() {
return Iterator(this, get_Count());
}
void* lst;
void* list_klass;
private:
typedef T(*lst_get_Item_t)(void*, int, void* mtd);
typedef void(*lst_Add_t)(void*, T, void* mtd);
typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
typedef int(*lst_get_Count_t)(void*, void* mtd);
MethodInfo* lst_get_Item_method;
MethodInfo* lst_Add_method;
MethodInfo* lst_get_Count_method;
MethodInfo* lst_set_Item_method;
lst_get_Item_t lst_get_Item;
lst_set_Item_t lst_set_Item;
lst_Add_t lst_Add;
lst_get_Count_t lst_get_Count;
};
}
}

View File

@@ -0,0 +1,619 @@
#include "Local.h"
#include "Log.h"
#include "Plugin.h"
#include "config/Config.hpp"
#include <filesystem>
#include <iostream>
#include <fstream>
#include <unordered_map>
#include <unordered_set>
#include <nlohmann/json.hpp>
#include <thread>
#include <regex>
#include <ranges>
#include <string>
#include <cctype>
#include <algorithm>
#include "BaseDefine.h"
#include "string_parser/StringParser.hpp"
namespace GakumasLocal::Local {
std::unordered_map<std::string, std::string> i18nData{};
std::unordered_map<std::string, std::string> i18nDumpData{};
std::unordered_map<std::string, std::string> genericText{};
std::unordered_map<std::string, std::string> genericSplitText{};
std::unordered_map<std::string, std::string> genericFmtText{};
std::vector<std::string> genericTextDumpData{};
std::vector<std::string> genericSplittedDumpData{};
std::vector<std::string> genericOrigTextDumpData{};
std::vector<std::string> genericFmtTextDumpData{};
std::unordered_set<std::string> translatedText{};
int genericDumpFileIndex = 0;
const std::string splitTextPrefix = "[__split__]";
std::filesystem::path GetBasePath() {
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
}
std::string trim(const std::string& str) {
auto is_not_space = [](char ch) { return !std::isspace(ch); };
auto start = std::ranges::find_if(str, is_not_space);
auto end = std::ranges::find_if(str | std::views::reverse, is_not_space).base();
if (start < end) {
return {start, end};
}
return "";
}
std::string findInMapIgnoreSpace(const std::string& key, const std::unordered_map<std::string, std::string>& searchMap) {
auto is_space = [](char ch) { return std::isspace(ch); };
auto front = std::ranges::find_if_not(key, is_space);
auto back = std::ranges::find_if_not(key | std::views::reverse, is_space).base();
std::string prefix(key.begin(), front);
std::string suffix(back, key.end());
std::string trimmedKey = trim(key);
if ( auto it = searchMap.find(trimmedKey); it != searchMap.end()) {
return prefix + it->second + suffix;
}
else {
return "";
}
}
enum class DumpStrStat {
DEFAULT = 0,
SPLITTABLE_ORIG = 1,
SPLITTED = 2,
FMT = 3
};
enum class SplitTagsTranslationStat {
NO_TRANS,
PART_TRANS,
FULL_TRANS,
NO_SPLIT,
NO_SPLIT_AND_EMPTY
};
void LoadJsonDataToMap(const std::filesystem::path& filePath, std::unordered_map<std::string, std::string>& dict,
const bool insertToTranslated = false, const bool needClearDict = true,
const bool needCheckSplitPrefix = false) {
if (!exists(filePath)) return;
try {
if (needClearDict) {
dict.clear();
}
std::ifstream file(filePath);
if (!file.is_open()) {
Log::ErrorFmt("Load %s failed.\n", filePath.string().c_str());
return;
}
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
auto fileData = nlohmann::json::parse(fileContent);
for (auto& i : fileData.items()) {
const auto& key = i.key();
const std::string value = i.value();
if (needCheckSplitPrefix && key.starts_with(splitTextPrefix) && value.starts_with(splitTextPrefix)) {
static const auto splitTextPrefixLength = splitTextPrefix.size();
const auto splitValue = value.substr(splitTextPrefixLength);
genericSplitText[key.substr(splitTextPrefixLength)] = splitValue;
if (insertToTranslated) translatedText.emplace(splitValue);
}
else {
dict[key] = value;
if (insertToTranslated) translatedText.emplace(value);
}
}
}
catch (std::exception& e) {
Log::ErrorFmt("Load %s failed: %s\n", filePath.string().c_str(), e.what());
}
}
void DumpMapDataToJson(const std::filesystem::path& dumpBasePath, const std::filesystem::path& fileName,
const std::unordered_map<std::string, std::string>& dict) {
const auto dumpFilePath = dumpBasePath / fileName;
try {
if (!is_directory(dumpBasePath)) {
std::filesystem::create_directories(dumpBasePath);
}
if (!std::filesystem::exists(dumpFilePath)) {
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
dumpWriteLrcFile << "{}";
dumpWriteLrcFile.close();
}
std::ifstream dumpLrcFile(dumpFilePath);
std::string fileContent((std::istreambuf_iterator<char>(dumpLrcFile)), std::istreambuf_iterator<char>());
dumpLrcFile.close();
auto fileData = nlohmann::ordered_json::parse(fileContent);
for (const auto& i : dict) {
fileData[i.first] = i.second;
}
const auto newStr = fileData.dump(4, 32, false);
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
dumpWriteLrcFile << newStr.c_str();
dumpWriteLrcFile.close();
}
catch (std::exception& e) {
Log::ErrorFmt("DumpMapDataToJson %s failed: %s", dumpFilePath.c_str(), e.what());
}
}
void DumpVectorDataToJson(const std::filesystem::path& dumpBasePath, const std::filesystem::path& fileName,
const std::vector<std::string>& vec, const std::string& prefix = "") {
const auto dumpFilePath = dumpBasePath / fileName;
try {
if (!is_directory(dumpBasePath)) {
std::filesystem::create_directories(dumpBasePath);
}
if (!std::filesystem::exists(dumpFilePath)) {
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
dumpWriteLrcFile << "{}";
dumpWriteLrcFile.close();
}
std::ifstream dumpLrcFile(dumpFilePath);
std::string fileContent((std::istreambuf_iterator<char>(dumpLrcFile)), std::istreambuf_iterator<char>());
dumpLrcFile.close();
auto fileData = nlohmann::ordered_json::parse(fileContent);
for (const auto& i : vec) {
if (!prefix.empty()) {
fileData[prefix + i] = prefix + i;
}
else {
fileData[i] = i;
}
}
const auto newStr = fileData.dump(4, 32, false);
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
dumpWriteLrcFile << newStr.c_str();
dumpWriteLrcFile.close();
}
catch (std::exception& e) {
Log::ErrorFmt("DumpVectorDataToJson %s failed: %s", dumpFilePath.c_str(), e.what());
}
}
std::string to_lower(const std::string& str) {
std::string lower_str = str;
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
return lower_str;
}
bool IsPureStringValue(const std::string& str) {
static std::unordered_set<char> notDeeds = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
'/', ' ', '.', '%', ',', '+', '-', 'x', '\n'};
for (const auto& i : str) {
if (!notDeeds.contains(i)) {
return false;
}
}
return true;
}
std::vector<std::string> SplitByTags(const std::string& origText) {
static const std::regex tagsRe("<.*?>(.*?)</.*?>");
std::string text = origText;
std::smatch match;
std::vector<std::string> ret{};
std::string lastSuffix;
while (std::regex_search(text, match, tagsRe)) {
const auto tagValue = match[1].str();
if (IsPureStringValue(tagValue)) {
ret.push_back(match.prefix().str());
lastSuffix = match.suffix().str();
}
text = match.suffix().str();
}
if (!lastSuffix.empty()) {
ret.push_back(lastSuffix);
}
return ret;
}
void ProcessGenericTextLabels() {
std::unordered_map<std::string, std::string> appendsText{};
for (const auto& i : genericText) {
const auto origContents = SplitByTags(i.first);
if (origContents.empty()) {
continue;
}
const auto translatedContents = SplitByTags(i.second);
if (origContents.size() == translatedContents.size()) {
for (const auto& [orig, trans] : std::ranges::views::zip(origContents, translatedContents)) {
appendsText.emplace(orig, trans);
}
}
}
genericText.insert(appendsText.begin(), appendsText.end());
}
bool ReplaceString(std::string* str, const std::string& oldSubstr, const std::string& newSubstr) {
size_t pos = str->find(oldSubstr);
if (pos != std::string::npos) {
str->replace(pos, oldSubstr.length(), newSubstr);
return true;
}
return false;
}
bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
if (!origText.contains(L'<')) return false;
const auto splitResult = SplitByTags(origText);
if (splitResult.empty()) return false;
*newText = origText;
bool ret = true;
for (const auto& i : splitResult) {
if (const auto iter = genericText.find(i); iter != genericText.end()) {
ReplaceString(newText, i, iter->second);
}
else {
unTransResultRet.emplace_back(i);
ret = false;
}
}
return ret;
}
void ReplaceNumberComma(std::string* orig) {
if (!orig->contains("")) return;
std::string newStr = *orig;
ReplaceString(&newStr, "", ",");
if (IsPureStringValue(newStr)) {
*orig = newStr;
}
}
SplitTagsTranslationStat GetSplitTagsTranslationFull(const std::string& origTextIn, std::string* newText, std::vector<std::string>& unTransResultRet) {
// static const std::u16string splitFlags = u"0123456789+-%%【】.";
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
u'6', u'7', u'8', u'9', u'+', u'',
u'-', u'', u'%', u'', u'', u'',
u'.', u':', u'', u'×'};
const auto origText = Misc::ToUTF16(origTextIn);
bool isInTag = false;
std::vector<std::string> waitingReplaceTexts{};
std::u16string currentWaitingReplaceText;
#define checkCurrentWaitingReplaceTextAndClear() \
if (!currentWaitingReplaceText.empty()) { \
waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
currentWaitingReplaceText.clear(); }
for (char16_t currChar : origText) {
if (currChar == u'<') {
isInTag = true;
}
if (currChar == u'>') {
isInTag = false;
checkCurrentWaitingReplaceTextAndClear()
continue;
}
if (isInTag) {
checkCurrentWaitingReplaceTextAndClear()
continue;
}
if (!splitFlags.contains(currChar)) {
currentWaitingReplaceText.push_back(currChar);
}
else {
checkCurrentWaitingReplaceTextAndClear()
}
}
if (waitingReplaceTexts.empty()) {
if (currentWaitingReplaceText.empty()) {
return SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY;
}
else {
if (!(!origText.empty() && splitFlags.contains(origText[0]))) { // 开头为特殊符号或数字
return SplitTagsTranslationStat::NO_SPLIT;
}
}
}
checkCurrentWaitingReplaceTextAndClear()
*newText = origTextIn;
SplitTagsTranslationStat ret;
bool hasTrans = false;
bool hasNotTrans = false;
if (!waitingReplaceTexts.empty()) {
for (const auto& i : waitingReplaceTexts) {
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
if (!searchResult.empty()) {
ReplaceNumberComma(&searchResult);
ReplaceString(newText, i, searchResult);
hasTrans = true;
}
else {
unTransResultRet.emplace_back(trim(i));
hasNotTrans = true;
}
}
if (hasTrans && hasNotTrans) {
ret = SplitTagsTranslationStat::PART_TRANS;
}
else if (hasTrans && !hasNotTrans) {
ret = SplitTagsTranslationStat::FULL_TRANS;
}
else {
ret = SplitTagsTranslationStat::NO_TRANS;
}
}
else {
ret = SplitTagsTranslationStat::NO_TRANS;
}
return ret;
}
void LoadData() {
static auto localizationFile = GetBasePath() / "local-files" / "localization.json";
static auto genericFile = GetBasePath() / "local-files" / "generic.json";
static auto genericSplitFile = GetBasePath() / "local-files" / "generic.split.json";
static auto genericDir = GetBasePath() / "local-files" / "genericTrans";
if (!std::filesystem::is_regular_file(localizationFile)) {
Log::ErrorFmt("localizationFile: %s not found.", localizationFile.c_str());
return;
}
LoadJsonDataToMap(localizationFile, i18nData, true);
Log::InfoFmt("%ld localization items loaded.", i18nData.size());
LoadJsonDataToMap(genericFile, genericText, true, true, true);
genericSplitText.clear();
genericFmtText.clear();
LoadJsonDataToMap(genericSplitFile, genericSplitText, true, true, true);
if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
if (std::filesystem::is_regular_file(entry.path())) {
const auto& currFile = entry.path();
if (to_lower(currFile.extension().string()) == ".json") {
if (currFile.filename().string().ends_with(".split.json")) { // split text file
LoadJsonDataToMap(currFile, genericSplitText, true, false, true);
}
if (currFile.filename().string().ends_with(".fmt.json")) { // fmt text file
LoadJsonDataToMap(currFile, genericFmtText, true, false, false);
}
else {
LoadJsonDataToMap(currFile, genericText, true, false, true);
}
}
}
}
}
ProcessGenericTextLabels();
Log::InfoFmt("%ld generic text items loaded.", genericText.size());
static auto dumpBasePath = GetBasePath() / "dump-files";
static auto dumpFilePath = dumpBasePath / "localization.json";
LoadJsonDataToMap(dumpFilePath, i18nDumpData);
}
bool GetI18n(const std::string& key, std::string* ret) {
if (const auto iter = i18nData.find(key); iter != i18nData.end()) {
*ret = iter->second;
return true;
}
return false;
}
bool inDump = false;
void DumpI18nItem(const std::string& key, const std::string& value) {
if (!Config::dumpText) return;
if (i18nDumpData.contains(key)) return;
i18nDumpData[key] = value;
Log::DebugFmt("DumpI18nItem: %s - %s", key.c_str(), value.c_str());
static auto dumpBasePath = GetBasePath() / "dump-files";
if (inDump) return;
inDump = true;
std::thread([](){
std::this_thread::sleep_for(std::chrono::seconds(5));
DumpMapDataToJson(dumpBasePath, "localization.json", i18nDumpData);
inDump = false;
}).detach();
}
std::string readFileToString(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::exception();
}
std::string content((std::istreambuf_iterator<char>(file)),
(std::istreambuf_iterator<char>()));
file.close();
return content;
}
bool GetResourceText(const std::string& name, std::string* ret) {
static std::filesystem::path basePath = GetBasePath();
try {
const auto targetFilePath = basePath / "local-files" / "resource" / name;
// Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
if (exists(targetFilePath)) {
auto readStr = readFileToString(targetFilePath.string());
*ret = readStr;
return true;
}
}
catch (std::exception& e) {
Log::ErrorFmt("read file: %s failed.", name.c_str());
}
return false;
}
std::string GetDumpGenericFileName(DumpStrStat stat = DumpStrStat::DEFAULT) {
if (stat == DumpStrStat::SPLITTABLE_ORIG) {
if (genericDumpFileIndex == 0) return "generic_orig.json";
return Log::StringFormat("generic_orig_%d.json", genericDumpFileIndex);
}
else if (stat == DumpStrStat::FMT) {
if (genericDumpFileIndex == 0) return "generic.fmt.json";
return Log::StringFormat("generic_%d.fmt.json", genericDumpFileIndex);
}
else {
if (genericDumpFileIndex == 0) return "generic.json";
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
}
}
bool inDumpGeneric = false;
void DumpGenericText(const std::string& origText, DumpStrStat stat = DumpStrStat::DEFAULT) {
if (translatedText.contains(origText)) return;
std::array<std::reference_wrapper<std::vector<std::string>>, 4> targets = {
genericTextDumpData,
genericOrigTextDumpData,
genericSplittedDumpData,
genericFmtTextDumpData
};
auto& appendTarget = targets[static_cast<int>(stat)].get();
if (std::find(appendTarget.begin(), appendTarget.end(), origText) != appendTarget.end()) {
return;
}
if (IsPureStringValue(origText)) return;
appendTarget.push_back(origText);
static auto dumpBasePath = GetBasePath() / "dump-files";
if (inDumpGeneric) return;
inDumpGeneric = true;
std::thread([](){
std::this_thread::sleep_for(std::chrono::seconds(5));
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::DEFAULT), genericTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTABLE_ORIG), genericOrigTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTED), genericSplittedDumpData, splitTextPrefix);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::FMT), genericFmtTextDumpData);
genericTextDumpData.clear();
genericSplittedDumpData.clear();
genericOrigTextDumpData.clear();
genericFmtTextDumpData.clear();
inDumpGeneric = false;
}).detach();
}
bool GetGenericText(const std::string& origText, std::string* newStr) {
// 完全匹配
if (const auto iter = genericText.find(origText); iter != genericText.end()) {
*newStr = iter->second;
return true;
}
// 不翻译翻译过的文本
if (translatedText.contains(origText)) {
return false;
}
// 匹配升级卡名
if (auto plusPos = origText.find_last_not_of('+'); plusPos != std::string::npos) {
const auto noPlusText = origText.substr(0, plusPos + 1);
if (const auto iter = genericText.find(noPlusText); iter != genericText.end()) {
size_t plusCount = origText.length() - (plusPos + 1);
*newStr = iter->second + std::string(plusCount, '+');
return true;
}
}
// fmt 文本
auto fmtText = StringParser::ParseItems::parse(origText, false);
if (fmtText.isValid) {
const auto fmtStr = fmtText.ToFmtString();
if (auto it = genericFmtText.find(fmtStr); it != genericFmtText.end()) {
auto newRet = fmtText.MergeText(it->second);
if (!newRet.empty()) {
*newStr = newRet;
return true;
}
}
if (Config::dumpText) {
DumpGenericText(fmtStr, DumpStrStat::FMT);
}
}
auto ret = false;
// 分割匹配
std::vector<std::string> unTransResultRet;
const auto splitTransStat = GetSplitTagsTranslationFull(origText, newStr, unTransResultRet);
switch (splitTransStat) {
case SplitTagsTranslationStat::FULL_TRANS: {
DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
return true;
} break;
case SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY: {
return false;
} break;
case SplitTagsTranslationStat::NO_SPLIT: {
ret = false;
} break;
case SplitTagsTranslationStat::NO_TRANS: {
ret = false;
} break;
case SplitTagsTranslationStat::PART_TRANS: {
ret = true;
} break;
}
if (!Config::dumpText) {
return ret;
}
if (unTransResultRet.empty() || (splitTransStat == SplitTagsTranslationStat::NO_SPLIT)) {
DumpGenericText(origText);
}
else {
for (const auto& i : unTransResultRet) {
DumpGenericText(i, DumpStrStat::SPLITTED);
}
// 若未翻译部分长度为1且未翻译文本等于原文本则不 dump 到原文本文件
//if (unTransResultRet.size() != 1 || unTransResultRet[0] != origText) {
DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
//}
}
return ret;
}
std::string ChangeDumpTextIndex(int changeValue) {
if (!Config::dumpText) return "";
genericDumpFileIndex += changeValue;
return Log::StringFormat("GenericDumpFile: %s", GetDumpGenericFileName().c_str());
}
std::string OnKeyDown(int message, int key) {
if (message == WM_KEYDOWN) {
switch (key) {
case KEY_ADD: {
return ChangeDumpTextIndex(1);
} break;
case KEY_SUB: {
return ChangeDumpTextIndex(-1);
} break;
}
}
return "";
}
}

View File

@@ -0,0 +1,22 @@
#ifndef GAKUMAS_LOCALIFY_LOCAL_H
#define GAKUMAS_LOCALIFY_LOCAL_H
#include <string>
#include <filesystem>
#include <unordered_set>
namespace GakumasLocal::Local {
extern std::unordered_set<std::string> translatedText;
std::filesystem::path GetBasePath();
void LoadData();
bool GetI18n(const std::string& key, std::string* ret);
void DumpI18nItem(const std::string& key, const std::string& value);
bool GetResourceText(const std::string& name, std::string* ret);
bool GetGenericText(const std::string& origText, std::string* newStr);
std::string OnKeyDown(int message, int key);
}
#endif //GAKUMAS_LOCALIFY_LOCAL_H

156
src/GakumasLocalify/Log.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "Log.h"
#include "Misc.hpp"
#include <sstream>
#include <string>
#include <thread>
#include <queue>
#include <cstdarg>
#ifndef GKMS_WINDOWS
#include <android/log.h>
extern JavaVM* g_javaVM;
extern jclass g_gakumasHookMainClass;
extern jmethodID showToastMethodId;
#endif // GKMS_WINDOWS
#define GetParamStringResult(name)\
va_list args;\
va_start(args, fmt);\
va_list args_copy;\
va_copy(args_copy, args);\
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1;\
va_end(args_copy);\
char* buffer = new char[size];\
vsnprintf(buffer, size, fmt, args);\
va_end(args);\
std::string name(buffer);\
delete[] buffer
namespace GakumasLocal::Log {
namespace {
std::queue<std::string> showingToasts{};
}
std::string StringFormat(const char* fmt, ...) {
GetParamStringResult(result);
return result;
}
void Log(int prio, const char* msg) {
__android_log_write(prio, "GakumasLocal-Native", msg);
}
void LogFmt(int prio, const char* fmt, ...) {
GetParamStringResult(result);
Log(prio, result.c_str());
}
void Info(const char* msg) {
Log(ANDROID_LOG_INFO, msg);
}
void InfoFmt(const char* fmt, ...) {
GetParamStringResult(result);
Info(result.c_str());
}
void Error(const char* msg) {
Log(ANDROID_LOG_ERROR, msg);
}
void ErrorFmt(const char* fmt, ...) {
GetParamStringResult(result);
Error(result.c_str());
}
void Debug(const char* msg) {
Log(ANDROID_LOG_DEBUG, msg);
}
void DebugFmt(const char* fmt, ...) {
GetParamStringResult(result);
Debug(result.c_str());
}
void LogUnityLog(int prio, const char* fmt, ...) {
GetParamStringResult(result);
__android_log_write(prio, "GakumasLog", result.c_str());
}
/*
void ShowToastJNI(const char* text) {
DebugFmt("Toast: %s", text);
std::thread([text](){
auto env = Misc::GetJNIEnv();
if (!env) {
return;
}
jclass& kotlinClass = g_gakumasHookMainClass;
if (!kotlinClass) {
g_javaVM->DetachCurrentThread();
return;
}
jmethodID& methodId = showToastMethodId;
if (!methodId) {
g_javaVM->DetachCurrentThread();
return;
}
jstring param = env->NewStringUTF(text);
env->CallStaticVoidMethod(kotlinClass, methodId, param);
g_javaVM->DetachCurrentThread();
}).detach();
}*/
void ShowToast(const std::string& text) {
#ifndef GKMS_WINDOWS
showingToasts.push(text);
#else
InfoFmt("Toast: %s", text.c_str());
#endif
}
void ShowToast(const char* text) {
// DebugFmt("Toast: %s", text);
return ShowToast(std::string(text));
}
void ShowToastFmt(const char* fmt, ...) {
GetParamStringResult(result);
ShowToast(result);
}
std::string GetQueuedToast() {
if (showingToasts.empty()) {
return "";
}
const auto ret = showingToasts.front();
showingToasts.pop();
return ret;
}
#ifndef GKMS_WINDOWS
void ToastLoop(JNIEnv *env, jclass clazz) {
const auto toastString = GetQueuedToast();
if (toastString.empty()) return;
static auto _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
if (env && clazz && _showToastMethodId) {
jstring param = env->NewStringUTF(toastString.c_str());
env->CallStaticVoidMethod(clazz, _showToastMethodId, param);
env->DeleteLocalRef(param);
}
else {
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
}
}
#endif
}

32
src/GakumasLocalify/Log.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef GAKUMAS_LOCALIFY_LOG_H
#define GAKUMAS_LOCALIFY_LOG_H
#include "../platformDefine.hpp"
#include <string>
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif
namespace GakumasLocal::Log {
std::string StringFormat(const char* fmt, ...);
void LogUnityLog(int prio, const char* fmt, ...);
void LogFmt(int prio, const char* fmt, ...);
void Info(const char* msg);
void InfoFmt(const char* fmt, ...);
void Error(const char* msg);
void ErrorFmt(const char* fmt, ...);
void Debug(const char* msg);
void DebugFmt(const char* fmt, ...);
void ShowToast(const char* text);
void ShowToastFmt(const char* fmt, ...);
#ifndef GKMS_WINDOWS
void ToastLoop(JNIEnv *env, jclass clazz);
#endif
}
#endif //GAKUMAS_LOCALIFY_LOG_H

View File

@@ -0,0 +1,808 @@
#include "MasterLocal.h"
#include "Local.h"
#include "Il2cppUtils.hpp"
#include "config/Config.hpp"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <regex>
#include <nlohmann/json.hpp>
namespace GakumasLocal::MasterLocal {
using Il2cppString = UnityResolve::UnityType::String;
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
enum class JsonValueType {
JVT_String,
JVT_Int,
JVT_Object,
JVT_ArrayObject,
JVT_ArrayString,
JVT_Unsupported,
JVT_NeedMore_EmptyArray
};
struct ItemRule {
std::vector<std::string> mainPrimaryKey;
std::map<std::string, std::vector<std::string>> subPrimaryKey;
std::vector<std::string> mainLocalKey;
std::map<std::string, std::vector<std::string>> subLocalKey;
};
struct TableLocalData {
ItemRule itemRule;
std::unordered_map<std::string, JsonValueType> mainKeyType;
std::unordered_map<std::string, std::unordered_map<std::string, JsonValueType>> subKeyType;
std::unordered_map<std::string, std::string> transData;
std::unordered_map<std::string, std::vector<std::string>> transStrListData;
[[nodiscard]] JsonValueType GetMainKeyType(const std::string& mainKey) const {
if (auto it = mainKeyType.find(mainKey); it != mainKeyType.end()) {
return it->second;
}
return JsonValueType::JVT_Unsupported;
}
[[nodiscard]] JsonValueType GetSubKeyType(const std::string& parentKey, const std::string& subKey) const {
if (auto it = subKeyType.find(parentKey); it != subKeyType.end()) {
if (auto subIt = it->second.find(subKey); subIt != it->second.end()) {
return subIt->second;
}
}
return JsonValueType::JVT_Unsupported;
}
};
static std::unordered_map<std::string, TableLocalData> masterLocalData;
class FieldController {
void* self;
std::string self_klass_name;
static std::string capitalizeFirstLetter(const std::string& input) {
if (input.empty()) return input;
std::string result = input;
result[0] = static_cast<char>(std::toupper(result[0]));
return result;
}
Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount,
std::unordered_map<std::string, Il2cppUtils::MethodInfo*>& fromCache, const std::string& prefix = "set_") {
const std::string methodName = prefix + capitalizeFirstLetter(fieldName);
const std::string searchName = self_klass_name + "." + methodName;
if (auto it = fromCache.find(searchName); it != fromCache.end()) {
return it->second;
}
auto set_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
self_klass,
methodName.c_str(),
argsCount
);
fromCache.emplace(searchName, set_mtd);
return set_mtd;
}
public:
Il2cppUtils::Il2CppClassHead* self_klass;
explicit FieldController(void* from) {
if (!from) {
self = nullptr;
return;
}
self = from;
self_klass = Il2cppUtils::get_class_from_instance(self);
if (self_klass) {
self_klass_name = self_klass->name;
}
}
template<typename T>
T ReadField(const std::string& fieldName) {
if (!self) return T();
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
if (get_mtd) {
return reinterpret_cast<T (*)(void*, void*)>(get_mtd->methodPointer)(self, get_mtd);
}
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
"il2cpp_class_get_field_from_name",
self_klass,
(fieldName + '_').c_str()
);
if (!field) {
return T();
}
return Il2cppUtils::ClassGetFieldValue<T>(self, field);
}
template<typename T>
void SetField(const std::string& fieldName, T value) {
if (!self) return;
auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_");
if (set_mtd) {
reinterpret_cast<void (*)(void*, T, void*)>(
set_mtd->methodPointer
)(self, value, set_mtd);
return;
}
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
"il2cpp_class_get_field_from_name",
self_klass,
(fieldName + '_').c_str()
);
if (!field) return;
Il2cppUtils::ClassSetFieldValue(self, field, value);
}
int ReadIntField(const std::string& fieldName) {
return ReadField<int>(fieldName);
}
Il2cppString* ReadStringField(const std::string& fieldName) {
if (!self) return nullptr;
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
if (!get_mtd) {
return ReadField<Il2cppString*>(fieldName);
}
auto returnClass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>(
"il2cpp_class_from_type",
UnityResolve::Invoke<void*>("il2cpp_method_get_return_type", get_mtd)
);
if (!returnClass) {
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
}
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", returnClass);
if (!isEnum) {
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
}
auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true);
auto enumValue = reinterpret_cast<int (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
if (auto it = enumMap.find(enumValue); it != enumMap.end()) {
return Il2cppString::New(it->second);
}
return nullptr;
}
void SetStringField(const std::string& fieldName, const std::string& value) {
if (!self) return;
auto newString = Il2cppString::New(value);
SetField(fieldName, newString);
}
void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
if (!self) return;
static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
"System.Collections.Generic.List`1[System.String]"
);
static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
List_String_klass, ".ctor", 0
);
static auto List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(
List_String_ctor_mtd->methodPointer
);
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
List_String_ctor(newList, List_String_ctor_mtd);
Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
for (auto& s : data) {
newListEditor.Add(Il2cppString::New(s));
}
SetField(fieldName, newList);
}
void* ReadObjectField(const std::string& fieldName) {
if (!self) return nullptr;
return ReadField<void*>(fieldName);
}
void* ReadObjectListField(const std::string& fieldName) {
if (!self) return nullptr;
return ReadField<void*>(fieldName);
}
static FieldController CreateSubFieldController(void* subObj) {
return FieldController(subObj);
}
FieldController CreateSubFieldController(const std::string& subObjName) {
auto field = ReadObjectField(subObjName);
return FieldController(field);
}
};
JsonValueType checkJsonValueType(const nlohmann::json& j) {
if (j.is_string()) return JsonValueType::JVT_String;
if (j.is_number_integer()) return JsonValueType::JVT_Int;
if (j.is_object()) return JsonValueType::JVT_Object;
if (j.is_array()) {
if (!j.empty()) {
if (j.begin()->is_object()) {
return JsonValueType::JVT_ArrayObject;
}
else if (j.begin()->is_string()) {
return JsonValueType::JVT_ArrayString;
}
}
else {
return JsonValueType::JVT_NeedMore_EmptyArray;
}
}
return JsonValueType::JVT_Unsupported;
}
std::string ReadFileToString(const std::filesystem::path& path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) return {};
std::stringstream buffer;
buffer << ifs.rdbuf();
return buffer.str();
}
namespace Load {
std::vector<std::string> ArrayStrJsonToVec(nlohmann::json& data) {
return data;
}
bool BuildObjectItemLocalRule(nlohmann::json& transData, ItemRule& itemRule) {
// transData: data[]
bool hasSuccess = false;
for (auto& data : transData) {
// data: {"id": "xxx", "produceDescriptions": [{"k", "v"}], "descriptions": {"k2", "v2"}}
if (!data.is_object()) continue;
for (auto& [key, value] : data.items()) {
// key: "id", value: "xxx"
// key: "produceDescriptions", value: [{"k", "v"}]
const auto valueType = checkJsonValueType(value);
switch (valueType) {
case JsonValueType::JVT_String:
// case JsonValueType::JVT_Int:
case JsonValueType::JVT_ArrayString: {
if (std::find(itemRule.mainPrimaryKey.begin(), itemRule.mainPrimaryKey.end(), key) != itemRule.mainPrimaryKey.end()) {
continue;
}
if (auto it = std::find(itemRule.mainLocalKey.begin(), itemRule.mainLocalKey.end(), key); it == itemRule.mainLocalKey.end()) {
itemRule.mainLocalKey.emplace_back(key);
}
hasSuccess = true;
} break;
case JsonValueType::JVT_Object: {
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
auto vJson = nlohmann::json::array();
vJson.push_back(value);
if (BuildObjectItemLocalRule(vJson, currRule)) {
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
hasSuccess = true;
}
} break;
case JsonValueType::JVT_ArrayObject: {
for (auto& obj : value) {
// obj: {"k", "v"}
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
if (BuildObjectItemLocalRule(value, currRule)) {
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
hasSuccess = true;
break;
}
}
} break;
case JsonValueType::JVT_Unsupported:
default:
break;
}
}
if (hasSuccess) break;
}
return hasSuccess;
}
bool GetItemRule(nlohmann::json& fullData, ItemRule& itemRule) {
auto& primaryKeys = fullData["rules"]["primaryKeys"];
auto& transData = fullData["data"];
if (!primaryKeys.is_array()) return false;
if (!transData.is_array()) return false;
// 首先构造 mainPrimaryKey 规则
for (auto& pkItem : primaryKeys) {
if (!pkItem.is_string()) {
return false;
}
std::string pk = pkItem;
auto dotCount = std::ranges::count(pk, '.');
if (dotCount == 0) {
itemRule.mainPrimaryKey.emplace_back(pk);
}
else if (dotCount == 1) {
auto [parentKey, subKey] = Misc::StringFormat::split_once(pk, ".");
if (itemRule.subPrimaryKey.contains(parentKey)) {
itemRule.subPrimaryKey[parentKey].emplace_back(subKey);
}
else {
itemRule.subPrimaryKey.emplace(parentKey, std::vector<std::string>{subKey});
}
}
else {
Log::ErrorFmt("Unsupported depth: %d", dotCount);
continue;
}
}
return BuildObjectItemLocalRule(transData, itemRule);
}
std::string BuildBaseMainUniqueKey(nlohmann::json& data, TableLocalData& tableLocalData) {
try {
std::string mainBaseUniqueKey;
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
if (!data.contains(mainPrimaryKey)) {
return "";
}
auto& value = data[mainPrimaryKey];
if (value.is_number_integer()) {
mainBaseUniqueKey.append(std::to_string(value.get<int>()));
}
else {
mainBaseUniqueKey.append(value);
}
mainBaseUniqueKey.push_back('|');
}
return mainBaseUniqueKey;
}
catch (std::exception& e) {
Log::ErrorFmt("LoadData - BuildBaseMainUniqueKey failed: %s", e.what());
throw e;
}
}
void BuildBaseObjectSubUniqueKey(nlohmann::json& value, JsonValueType valueType, std::string& currLocalKey) {
switch (valueType) {
case JsonValueType::JVT_String:
currLocalKey.append(value.get<std::string>()); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
currLocalKey.push_back('|');
break;
case JsonValueType::JVT_Int:
currLocalKey.append(std::to_string(value.get<int>()));
currLocalKey.push_back('|');
break;
default:
break;
}
}
bool BuildUniqueKeyValue(nlohmann::json& data, TableLocalData& tableLocalData) {
// 首先处理 main 部分
const std::string mainBaseUniqueKey = BuildBaseMainUniqueKey(data, tableLocalData); // p_card-00-acc-0_002|0|
if (mainBaseUniqueKey.empty()) return false;
for (auto& mainLocalKey : tableLocalData.itemRule.mainLocalKey) {
if (!data.contains(mainLocalKey)) continue;
auto& currLocalValue = data[mainLocalKey];
auto currUniqueKey = mainBaseUniqueKey + mainLocalKey; // p_card-00-acc-0_002|0|name
if (tableLocalData.GetMainKeyType(mainLocalKey) == JsonValueType::JVT_ArrayString) {
tableLocalData.transStrListData.emplace(currUniqueKey, ArrayStrJsonToVec(currLocalValue));
}
else {
tableLocalData.transData.emplace(currUniqueKey, currLocalValue);
}
}
// 然后处理 sub 部分
/*
for (const auto& [subPrimaryParentKey, subPrimarySubKeys] : tableLocalData.itemRule.subPrimaryKey) {
if (!data.contains(subPrimaryParentKey)) continue;
const std::string subBaseUniqueKey = mainBaseUniqueKey + subPrimaryParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
auto subValueType = checkJsonValueType(data[subPrimaryParentKey]);
std::string currLocalKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
switch (subValueType) {
case JsonValueType::JVT_Object: {
for (auto& subPrimarySubKey : subPrimarySubKeys) {
if (!data[subPrimaryParentKey].contains(subPrimarySubKey)) continue;
auto& value = data[subPrimaryParentKey][subPrimarySubKey];
auto valueType = tableLocalData.GetSubKeyType(subPrimaryParentKey, subPrimarySubKey);
BuildBaseObjectSubUniqueKey(value, valueType, currLocalKey); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
}
} break;
case JsonValueType::JVT_ArrayObject: {
int currIndex = 0;
for (auto& obj : data[subPrimaryParentKey]) {
for (auto& subPrimarySubKey : subPrimarySubKeys) {
}
currIndex++;
}
} break;
default:
break;
}
}*/
for (const auto& [subLocalParentKey, subLocalSubKeys] : tableLocalData.itemRule.subLocalKey) {
if (!data.contains(subLocalParentKey)) continue;
const std::string subBaseUniqueKey = mainBaseUniqueKey + subLocalParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
auto subValueType = checkJsonValueType(data[subLocalParentKey]);
if (subValueType != JsonValueType::JVT_NeedMore_EmptyArray) {
tableLocalData.mainKeyType.emplace(subLocalParentKey, subValueType); // 在这里插入 subParent 的类型
}
switch (subValueType) {
case JsonValueType::JVT_Object: {
for (auto& localSubKey : subLocalSubKeys) {
const std::string currLocalUniqueKey = subBaseUniqueKey + localSubKey; // p_card-00-acc-0_002|0|produceDescriptions|text
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(data[subLocalParentKey][localSubKey]));
}
else {
tableLocalData.transData.emplace(currLocalUniqueKey, data[subLocalParentKey][localSubKey]);
}
}
} break;
case JsonValueType::JVT_ArrayObject: {
int currIndex = 0;
for (auto& obj : data[subLocalParentKey]) {
for (auto& localSubKey : subLocalSubKeys) {
std::string currLocalUniqueKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
currLocalUniqueKey.push_back('[');
currLocalUniqueKey.append(std::to_string(currIndex));
currLocalUniqueKey.append("]|");
currLocalUniqueKey.append(localSubKey); // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
// if (obj[localSubKey].is_array()) {
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(obj[localSubKey]));
}
else if (obj[localSubKey].is_string()) {
tableLocalData.transData.emplace(currLocalUniqueKey, obj[localSubKey]);
}
}
currIndex++;
}
} break;
default:
break;
}
}
return true;
}
#define MainKeyTypeProcess() if (!data.contains(mainPrimaryKey)) { Log::ErrorFmt("mainPrimaryKey: %s not found", mainPrimaryKey.c_str()); isFailed = true; break; } \
auto currType = checkJsonValueType(data[mainPrimaryKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.mainKeyType[mainPrimaryKey] = currType
#define SubKeyTypeProcess() if (!data.contains(subKeyParent)) { Log::ErrorFmt("subKeyParent: %s not found", subKeyParent.c_str()); isFailed = true; break; } \
for (auto& subKey : subKeys) { \
auto& subKeyValue = data[subKeyParent]; \
if (subKeyValue.is_object()) { \
if (!subKeyValue.contains(subKey)) { \
Log::ErrorFmt("subKey: %s not in subKeyParent: %s", subKey.c_str(), subKeyParent.c_str()); isFailed = true; break; \
} \
auto currType = checkJsonValueType(subKeyValue[subKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
} \
else if (subKeyValue.is_array()) { \
if (subKeyValue.empty()) goto NextLoop; \
for (auto& i : subKeyValue) { \
if (!i.is_object()) continue; \
if (!i.contains(subKey)) continue; \
auto currType = checkJsonValueType(i[subKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
break; \
} \
} \
else { \
goto NextLoop;\
} \
}
bool GetTableLocalData(nlohmann::json& fullData, TableLocalData& tableLocalData) {
bool isFailed = false;
// 首先 Build mainKeyType 和 subKeyType
for (auto& data : fullData["data"]) {
if (!data.is_object()) continue;
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
MainKeyTypeProcess();
}
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainLocalKey) {
MainKeyTypeProcess();
}
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subPrimaryKey) {
SubKeyTypeProcess()
if (isFailed) break;
}
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subLocalKey) {
SubKeyTypeProcess()
if (isFailed) break;
}
if (!isFailed) break;
NextLoop:
;
}
if (isFailed) return false;
bool hasSuccess = false;
// 然后构造 transData
for (auto& data : fullData["data"]) {
if (!data.is_object()) continue;
if (BuildUniqueKeyValue(data, tableLocalData)) {
hasSuccess = true;
}
}
if (!hasSuccess) {
Log::ErrorFmt("BuildUniqueKeyValue failed.");
}
return hasSuccess;
}
void LoadData() {
masterLocalData.clear();
static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans";
if (!std::filesystem::is_directory(masterDir)) {
Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str());
return;
}
bool isFirstIteration = true;
for (auto& p : std::filesystem::directory_iterator(masterDir)) {
if (isFirstIteration) {
auto totalFileCount = std::distance(
std::filesystem::directory_iterator(masterDir),
std::filesystem::directory_iterator{}
);
UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount;
isFirstIteration = false;
}
UnityResolveProgress::classProgress.current++;
if (!p.is_regular_file()) continue;
const auto& path = p.path();
if (path.extension() != ".json") continue;
std::string tableName = path.stem().string();
auto fileContent = ReadFileToString(path);
if (fileContent.empty()) continue;
try {
auto j = nlohmann::json::parse(fileContent);
if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) {
continue;
}
ItemRule currRule;
if (!GetItemRule(j, currRule)) {
Log::ErrorFmt("GetItemRule failed: %s", path.string().c_str());
continue;
}
/*
if (tableName == "ProduceStepEventDetail") {
for (auto& i : currRule.mainLocalKey) {
Log::DebugFmt("currRule.mainLocalKey: %s", i.c_str());
}
for (auto& i : currRule.mainPrimaryKey) {
Log::DebugFmt("currRule.mainPrimaryKey: %s", i.c_str());
}
for (auto& i : currRule.subLocalKey) {
for (auto& m : i.second) {
Log::DebugFmt("currRule.subLocalKey: %s - %s", i.first.c_str(), m.c_str());
}
}
for (auto& i : currRule.subPrimaryKey) {
for (auto& m : i.second) {
Log::DebugFmt("currRule.subPrimaryKey: %s - %s", i.first.c_str(), m.c_str());
}
}
}*/
TableLocalData tableLocalData{ .itemRule = currRule };
if (GetTableLocalData(j, tableLocalData)) {
for (auto& i : tableLocalData.transData) {
// Log::DebugFmt("%s: %s -> %s", tableName.c_str(), i.first.c_str(), i.second.c_str());
Local::translatedText.emplace(i.second);
}
for (auto& i : tableLocalData.transStrListData) {
for (auto& str : i.second) {
// Log::DebugFmt("%s[]: %s -> %s", tableName.c_str(), i.first.c_str(), str.c_str());
Local::translatedText.emplace(str);
}
}
/*
if (tableName == "ProduceStepEventDetail") {
for (auto& i : tableLocalData.mainKeyType) {
Log::DebugFmt("mainKeyType: %s -> %d", i.first.c_str(), i.second);
}
for (auto& i : tableLocalData.subKeyType) {
for (auto& m : i.second) {
Log::DebugFmt("subKeyType: %s - %s -> %d", i.first.c_str(), m.first.c_str(), m.second);
}
}
}*/
// JVT_ArrayString in HelpCategory, ProduceStory, Tutorial
masterLocalData.emplace(tableName, std::move(tableLocalData));
}
else {
Log::ErrorFmt("GetTableLocalData failed: %s", path.string().c_str());
}
} catch (std::exception& e) {
Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
path.string().c_str(), e.what());
}
}
}
}
void LoadData() {
return Load::LoadData();
}
std::string GetTransString(const std::string& key, const TableLocalData& localData) {
if (auto it = localData.transData.find(key); it != localData.transData.end()) {
return it->second;
}
return {};
}
std::vector<std::string> GetTransArrayString(const std::string& key, const TableLocalData& localData) {
if (auto it = localData.transStrListData.find(key); it != localData.transStrListData.end()) {
return it->second;
}
return {};
}
void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
auto it = masterLocalData.find(tableName);
if (it == masterLocalData.end()) return;
const auto& localData = it->second;
// 首先拼 BasePrimaryKey
std::string baseDataKey; // p_card-00-acc-0_002|0|
for (auto& mainPk : localData.itemRule.mainPrimaryKey) {
auto mainPkType = localData.GetMainKeyType(mainPk);
switch (mainPkType) {
case JsonValueType::JVT_Int: {
auto readValue = std::to_string(fc.ReadIntField(mainPk));
baseDataKey.append(readValue);
baseDataKey.push_back('|');
} break;
case JsonValueType::JVT_String: {
auto readValue = fc.ReadStringField(mainPk);
baseDataKey.append(readValue->ToString());
baseDataKey.push_back('|');
} break;
default:
break;
}
}
// 然后本地化 mainLocal
for (auto& mainLocal : localData.itemRule.mainLocalKey) {
std::string currSearchKey = baseDataKey;
currSearchKey.append(mainLocal); // p_card-00-acc-0_002|0|name
auto localVType = localData.GetMainKeyType(mainLocal);
switch (localVType) {
case JsonValueType::JVT_String: {
auto localValue = GetTransString(currSearchKey, localData);
if (!localValue.empty()) {
fc.SetStringField(mainLocal, localValue);
}
} break;
case JsonValueType::JVT_ArrayString: {
auto localValue = GetTransArrayString(currSearchKey, localData);
if (!localValue.empty()) {
fc.SetStringListField(mainLocal, localValue);
}
} break;
default:
break;
}
}
// 处理 sub
for (const auto& [subParentKey, subLocalKeys] : localData.itemRule.subLocalKey) {
const auto subBaseSearchKey = baseDataKey + subParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
const auto subParentType = localData.GetMainKeyType(subParentKey);
switch (subParentType) {
case JsonValueType::JVT_Object: {
auto subParentField = fc.CreateSubFieldController(subParentKey);
for (const auto& subLocalKey : subLocalKeys) {
const auto currSearchKey = subBaseSearchKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|text
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
if (localKeyType == JsonValueType::JVT_String) {
auto setData = GetTransString(currSearchKey, localData);
if (!setData.empty()) {
subParentField.SetStringField(subLocalKey, setData);
}
}
else if (localKeyType == JsonValueType::JVT_ArrayString) {
auto setData = GetTransArrayString(currSearchKey, localData);
if (!setData.empty()) {
subParentField.SetStringListField(subLocalKey, setData);
}
}
}
} break;
case JsonValueType::JVT_ArrayObject: {
auto subArrField = fc.ReadObjectListField(subParentKey);
if (!subArrField) continue;
Il2cppUtils::Tools::CSListEditor<void*> subListEdit(subArrField);
auto count = subListEdit.get_Count();
for (int idx = 0; idx < count; idx++) {
auto currItem = subListEdit.get_Item(idx);
if (!currItem) continue;
auto currFc = FieldController::CreateSubFieldController(currItem);
std::string currSearchBaseKey = subBaseSearchKey; // p_card-00-acc-0_002|0|produceDescriptions|
currSearchBaseKey.push_back('[');
currSearchBaseKey.append(std::to_string(idx));
currSearchBaseKey.append("]|"); // p_card-00-acc-0_002|0|produceDescriptions|[0]|
for (const auto& subLocalKey : subLocalKeys) {
std::string currSearchKey = currSearchBaseKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
/*
if (tableName == "ProduceStepEventDetail") {
Log::DebugFmt("localKeyType: %d currSearchKey: %s", localKeyType, currSearchKey.c_str());
}*/
if (localKeyType == JsonValueType::JVT_String) {
auto setData = GetTransString(currSearchKey, localData);
if (!setData.empty()) {
currFc.SetStringField(subLocalKey, setData);
}
}
else if (localKeyType == JsonValueType::JVT_ArrayString) {
auto setData = GetTransArrayString(currSearchKey, localData);
if (!setData.empty()) {
currFc.SetStringListField(subLocalKey, setData);
}
}
}
}
} break;
default:
break;
}
}
}
void LocalizeMasterItem(void* item, const std::string& tableName) {
if (!Config::useMasterTrans) return;
// Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
FieldController fc(item);
LocalizeMasterItem(fc, tableName);
}
} // namespace GakumasLocal::MasterLocal

View File

@@ -0,0 +1,12 @@
#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H
#define GAKUMAS_LOCALIFY_MASTERLOCAL_H
#include <string>
namespace GakumasLocal::MasterLocal {
void LoadData();
void LocalizeMasterItem(void* item, const std::string& tableName);
}
#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H

View File

@@ -0,0 +1,212 @@
#include "Misc.hpp"
#include <codecvt>
#include <locale>
#include "fmt/core.h"
#ifndef GKMS_WINDOWS
#include <jni.h>
extern JavaVM* g_javaVM;
#else
#include "cpprest/details/http_helpers.h"
#endif
namespace GakumasLocal::Misc {
std::u16string ToUTF16(const std::string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
return utf16conv.from_bytes(str.data(), str.data() + str.size());
}
std::string ToUTF8(const std::u16string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
return utf16conv.to_bytes(str.data(), str.data() + str.size());
}
#ifdef GKMS_WINDOWS
std::string ToUTF8(const std::wstring_view& str) {
return utility::conversions::to_utf8string(str.data());
}
#endif
#ifndef GKMS_WINDOWS
JNIEnv* GetJNIEnv() {
if (!g_javaVM) return nullptr;
JNIEnv* env = nullptr;
if (g_javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
int status = g_javaVM->AttachCurrentThread(&env, nullptr);
if (status < 0) {
return nullptr;
}
}
return env;
}
#endif
CSEnum::CSEnum(const std::string& name, const int value) {
this->Add(name, value);
}
CSEnum::CSEnum(const std::vector<std::string>& names, const std::vector<int>& values) {
if (names.size() != values.size()) return;
this->names = names;
this->values = values;
}
int CSEnum::GetIndex() {
return currIndex;
}
void CSEnum::SetIndex(int index) {
if (index < 0) return;
if (index + 1 >= values.size()) return;
currIndex = index;
}
int CSEnum::GetTotalLength() {
return values.size();
}
void CSEnum::Add(const std::string &name, const int value) {
this->names.push_back(name);
this->values.push_back(value);
}
std::pair<std::string, int> CSEnum::GetCurrent() {
return std::make_pair(names[currIndex], values[currIndex]);
}
std::pair<std::string, int> CSEnum::Last() {
const auto maxIndex = this->GetTotalLength() - 1;
if (currIndex <= 0) {
currIndex = maxIndex;
}
else {
currIndex--;
}
return this->GetCurrent();
}
std::pair<std::string, int> CSEnum::Next() {
const auto maxIndex = this->GetTotalLength() - 1;
if (currIndex >= maxIndex) {
currIndex = 0;
}
else {
currIndex++;
}
return this->GetCurrent();
}
int CSEnum::GetValueByName(const std::string &name) {
for (int i = 0; i < names.size(); i++) {
if (names[i] == name) {
return values[i];
}
}
return values[0];
}
namespace StringFormat {
template<typename... Args>
std::string string_format(const std::string& fmt, Args&&... args) {
// return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
return fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...);
}
template <std::size_t N, std::size_t... Indices, typename T>
auto vectorToTupleImpl(const std::vector<T>& vec, std::index_sequence<Indices...>) {
if (vec.size() != N) {
// printf("vec.size: %zu, N: %zu\n", vec.size(), N);
throw std::out_of_range("Vector size does not match tuple size.");
}
return std::make_tuple(vec[Indices]...);
}
template <std::size_t N, typename T>
auto vectorToTuple(const std::vector<T>& vec) {
return vectorToTupleImpl<N>(vec, std::make_index_sequence<N>{});
}
template <typename T>
std::string stringFormat(const std::string& fmt, const std::vector<T>& vec) {
std::string ret = fmt;
#define CASE_ARG_COUNT(N) \
case N: {\
auto tp = vectorToTuple<N>(vec); \
std::apply([&](auto&&... args) { \
ret = string_format(fmt, args...); \
}, tp); } break;
switch (vec.size()) {
CASE_ARG_COUNT(1)
CASE_ARG_COUNT(2)
CASE_ARG_COUNT(3)
CASE_ARG_COUNT(4)
CASE_ARG_COUNT(5)
CASE_ARG_COUNT(6)
CASE_ARG_COUNT(7)
CASE_ARG_COUNT(8)
CASE_ARG_COUNT(9)
CASE_ARG_COUNT(10)
CASE_ARG_COUNT(11)
CASE_ARG_COUNT(12)
CASE_ARG_COUNT(13)
CASE_ARG_COUNT(14)
CASE_ARG_COUNT(15)
CASE_ARG_COUNT(16)
CASE_ARG_COUNT(17)
CASE_ARG_COUNT(18)
CASE_ARG_COUNT(19)
CASE_ARG_COUNT(20)
CASE_ARG_COUNT(21)
CASE_ARG_COUNT(22)
CASE_ARG_COUNT(23)
CASE_ARG_COUNT(24)
}
return ret;
}
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec) {
try {
return stringFormat(fmt, vec);
}
catch (std::exception& e) {
return fmt;
}
}
std::vector<std::string> split(const std::string& str, char delimiter) {
std::vector<std::string> result;
std::string current;
for (char c : str) {
if (c == delimiter) {
if (!current.empty()) {
result.push_back(current);
}
current.clear();
} else {
current += c;
}
}
if (!current.empty()) {
result.push_back(current);
}
return result;
}
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter) {
size_t pos = str.find(delimiter);
if (pos != std::string::npos) {
return {str.substr(0, pos), str.substr(pos + delimiter.size())};
}
return {str, ""};
}
}
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include <string>
#include <string_view>
#include <deque>
#include <numeric>
#include <vector>
#include "../platformDefine.hpp"
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif
namespace GakumasLocal {
using OpaqueFunctionPointer = void (*)();
namespace Misc {
std::u16string ToUTF16(const std::string_view& str);
std::string ToUTF8(const std::u16string_view& str);
#ifdef GKMS_WINDOWS
std::string ToUTF8(const std::wstring_view& str);
#endif
#ifndef GKMS_WINDOWS
JNIEnv* GetJNIEnv();
#endif
class CSEnum {
public:
CSEnum(const std::string& name, const int value);
CSEnum(const std::vector<std::string>& names, const std::vector<int>& values);
int GetIndex();
void SetIndex(int index);
int GetTotalLength();
void Add(const std::string& name, const int value);
std::pair<std::string, int> GetCurrent();
std::pair<std::string, int> Last();
std::pair<std::string, int> Next();
int GetValueByName(const std::string& name);
private:
int currIndex = 0;
std::vector<std::string> names{};
std::vector<int> values{};
};
template <typename T>
class FixedSizeQueue {
static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type");
public:
FixedSizeQueue(size_t maxSize) : maxSize(maxSize), sum(0) {}
void Push(T value) {
if (deque.size() >= maxSize) {
sum -= deque.front();
deque.pop_front();
}
deque.push_back(value);
sum += value;
}
float Average() {
if (deque.empty()) {
return 0.0;
}
return static_cast<float>(sum) / deque.size();
}
private:
std::deque<T> deque;
size_t maxSize;
T sum;
};
namespace StringFormat {
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
std::vector<std::string> split(const std::string& str, char delimiter);
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter);
}
}
}

View File

@@ -0,0 +1,24 @@
#include "Plugin.h"
#include "Hook.h"
namespace GakumasLocal {
HookInstaller::~HookInstaller() {
}
Plugin &Plugin::GetInstance() {
static Plugin instance;
return instance;
}
void Plugin::InstallHook(std::unique_ptr<HookInstaller>&& hookInstaller)
{
m_HookInstaller = std::move(hookInstaller);
Hook::Install();
}
HookInstaller* Plugin::GetHookInstaller() const
{
return m_HookInstaller.get();
}
}

View File

@@ -0,0 +1,46 @@
#ifndef GAKUMAS_LOCALIFY_PLUGIN_H
#define GAKUMAS_LOCALIFY_PLUGIN_H
#include "Misc.hpp"
#include <string>
#include <memory>
#include "../platformDefine.hpp"
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif // !GKMS_WINDOWS
namespace GakumasLocal {
struct HookInstaller
{
virtual ~HookInstaller();
virtual void* InstallHook(void* addr, void* hook, void** orig) = 0;
virtual OpaqueFunctionPointer LookupSymbol(const char* name) = 0;
std::string m_il2cppLibraryPath;
std::string localizationFilesDir;
};
class Plugin
{
public:
static Plugin& GetInstance();
void InstallHook(std::unique_ptr<HookInstaller>&& hookInstaller);
HookInstaller* GetHookInstaller() const;
Plugin(Plugin const&) = delete;
Plugin& operator=(Plugin const&) = delete;
private:
Plugin() = default;
std::unique_ptr<HookInstaller> m_HookInstaller;
};
}
#endif //GAKUMAS_LOCALIFY_PLUGIN_H

View File

@@ -0,0 +1,131 @@
#include "baseCamera.hpp"
#include <thread>
#include "../../platformDefine.hpp"
#ifdef GKMS_WINDOWS
#include <corecrt_math_defines.h>
#endif // GKMS_WINDOWS
namespace BaseCamera {
using Vector3_t = UnityResolve::UnityType::Vector3;
float moveStep = 0.05;
float look_radius = 5; // 转向半径
float moveAngel = 1; // 转向角度
int smoothLevel = 1;
unsigned long sleepTime = 0;
Camera::Camera() {
Camera(0, 0, 0, 0, 0, 0);
}
Camera::Camera(Vector3_t* vec, Vector3_t* lookAt) {
Camera(vec->x, vec->y, vec->z, lookAt->x, lookAt->y, lookAt->z);
}
Camera::Camera(Vector3_t& vec, Vector3_t& lookAt) {
Camera(vec.x, vec.y, vec.z, lookAt.x, lookAt.y, lookAt.z);
}
Camera::Camera(float x, float y, float z, float lx, float ly, float lz) {
pos.x = x;
pos.y = y;
pos.z = z;
lookAt.x = lx;
lookAt.y = ly;
lookAt.z = lz;
}
void Camera::setPos(float x, float y, float z) {
pos.x = x;
pos.y = y;
pos.z = z;
}
void Camera::setLookAt(float x, float y, float z) {
lookAt.x = x;
lookAt.y = y;
lookAt.z = z;
}
void Camera::reset() {
setPos(0.5, 1.1, 1.3);
setLookAt(0.5, 1.1, -3.7);
fov = 60;
verticalAngle = 0;
horizontalAngle = 0;
}
Vector3_t Camera::GetPos() {
return pos;
}
Vector3_t Camera::GetLookAt() {
return lookAt;
}
void Camera::set_lon_move(float vertanglePlus, LonMoveHState moveState, float multiplier) { // 前后移动
auto radian = (verticalAngle + vertanglePlus) * M_PI / 180;
auto radianH = (double)horizontalAngle * M_PI / 180;
auto f_step = cos(radian) * moveStep * cos(radianH) / smoothLevel * multiplier; // ↑↓
auto l_step = sin(radian) * moveStep * cos(radianH) / smoothLevel * multiplier; // ←→
// auto h_step = tan(radianH) * sqrt(pow(f_step, 2) + pow(l_step, 2));
auto h_step = sin(radianH) * moveStep / smoothLevel * multiplier;
switch (moveState)
{
case LonMoveForward: break;
case LonMoveBack: h_step = -h_step; break;
default: h_step = 0; break;
}
for (int i = 0; i < smoothLevel; i++) {
pos.z -= f_step;
lookAt.z -= f_step;
pos.x += l_step;
lookAt.x += l_step;
pos.y += h_step;
lookAt.y += h_step;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
void Camera::updateVertLook() { // 上+
auto radian = verticalAngle * M_PI / 180;
auto radian2 = ((double)horizontalAngle - 90) * M_PI / 180; // 日
auto stepX1 = look_radius * sin(radian2) * cos(radian) / smoothLevel;
auto stepX2 = look_radius * sin(radian2) * sin(radian) / smoothLevel;
auto stepX3 = look_radius * cos(radian2) / smoothLevel;
for (int i = 0; i < smoothLevel; i++) {
lookAt.z = pos.z + stepX1;
lookAt.y = pos.y + stepX3;
lookAt.x = pos.x - stepX2;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
void Camera::setHoriLook(float vertangle) { // 左+
auto radian = vertangle * M_PI / 180;
auto radian2 = horizontalAngle * M_PI / 180;
auto stepBt = cos(radian) * look_radius * cos(radian2) / smoothLevel;
auto stepHi = sin(radian) * look_radius * cos(radian2) / smoothLevel;
auto stepY = sin(radian2) * look_radius / smoothLevel;
for (int i = 0; i < smoothLevel; i++) {
lookAt.x = pos.x + stepHi;
lookAt.z = pos.z - stepBt;
lookAt.y = pos.y + stepY;
std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime));
}
}
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include "../../deps/UnityResolve/UnityResolve.hpp"
enum LonMoveHState {
LonMoveLeftAndRight,
LonMoveForward,
LonMoveBack
};
namespace BaseCamera {
using Vector3_t = UnityResolve::UnityType::Vector3;
extern float moveStep;
extern float look_radius; // 转向半径
extern float moveAngel; // 转向角度
extern int smoothLevel;
extern unsigned long sleepTime;
class Camera {
public:
Camera();
Camera(Vector3_t& vec, Vector3_t& lookAt);
Camera(Vector3_t* vec, Vector3_t* lookAt);
Camera(float x, float y, float z, float lx, float ly, float lz);
void reset();
void setPos(float x, float y, float z);
void setLookAt(float x, float y, float z);
void set_lon_move(float vertanglePlus, LonMoveHState moveState = LonMoveHState::LonMoveLeftAndRight, float multiplier = 1.0f);
void updateVertLook();
void setHoriLook(float vertangle);
Vector3_t GetPos();
Vector3_t GetLookAt();
Vector3_t pos{0.5, 1.1, 1.3};
Vector3_t lookAt{0.5, 1.1, -3.7};
float fov = 60;
float horizontalAngle = 0; // 水平方向角度
float verticalAngle = 0; // 垂直方向角度
};
}

View File

@@ -0,0 +1,744 @@
#include "baseCamera.hpp"
#include "camera.hpp"
#include <thread>
#include "../Misc.hpp"
#include "../BaseDefine.h"
#include "../../platformDefine.hpp"
#ifdef GKMS_WINDOWS
#include <corecrt_math_defines.h>
#endif // GKMS_WINDOWS
namespace GKCamera {
BaseCamera::Camera baseCamera{};
CameraMode cameraMode = CameraMode::FREE;
FirstPersonRoll firstPersonRoll = FirstPersonRoll::ENABLE_ROLL;
FollowModeY followModeY = FollowModeY::SMOOTH_Y;
UnityResolve::UnityType::Vector3 firstPersonPosOffset{0, 0.064f, 0.000f};
UnityResolve::UnityType::Vector3 followPosOffset{0, 0, 1.5};
UnityResolve::UnityType::Vector2 followLookAtOffset{0, 0};
float offsetMoveStep = 0.008;
int followCharaIndex = 0;
float l_sensitivity = 0.5f;
float r_sensitivity = 0.5f;
bool showToast = true;
GakumasLocal::Misc::CSEnum bodyPartsEnum("Head", 0xa);
// bool rMousePressFlg = false;
void SetCameraMode(CameraMode mode) {
cameraMode = mode;
}
CameraMode GetCameraMode() {
return cameraMode;
}
void SetFirstPersonRoll(FirstPersonRoll mode) {
firstPersonRoll = mode;
}
FirstPersonRoll GetFirstPersonRoll() {
return firstPersonRoll;
}
void reset_camera() {
followCharaIndex = 0;
firstPersonPosOffset = {0, 0.064f, 0.000f}; // f3: 0.008f
followPosOffset = {0, 0, 1.5};
followLookAtOffset = {0, 0};
baseCamera.reset();
}
void camera_forward() { // 向前
switch (cameraMode) {
case CameraMode::FREE: {
baseCamera.set_lon_move(0, LonMoveHState::LonMoveForward);
} break;
case CameraMode::FIRST_PERSON: {
firstPersonPosOffset.z += offsetMoveStep;
} break;
case CameraMode::FOLLOW: {
followPosOffset.z -= offsetMoveStep;
}
}
}
void camera_back(float multiplier = 1.0f) { // 后退
switch (cameraMode) {
case CameraMode::FREE: {
baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack, multiplier);
} break;
case CameraMode::FIRST_PERSON: {
firstPersonPosOffset.z -= offsetMoveStep * multiplier;
} break;
case CameraMode::FOLLOW: {
followPosOffset.z += offsetMoveStep * multiplier;
}
}
}
void camera_left() { // 向左
switch (cameraMode) {
case CameraMode::FREE: {
baseCamera.set_lon_move(90);
} break;
case CameraMode::FOLLOW: {
// followPosOffset.x += 0.8;
followLookAtOffset.x += offsetMoveStep;
}
default:
break;
}
}
void camera_right(float multiplier = 1.0f) { // 向右
switch (cameraMode) {
case CameraMode::FREE: {
baseCamera.set_lon_move(-90, LonMoveLeftAndRight, multiplier);
} break;
case CameraMode::FOLLOW: {
// followPosOffset.x -= 0.8;
followLookAtOffset.x -= offsetMoveStep * multiplier;
}
default:
break;
}
}
void camera_down(float multiplier = 1.0f) { // 向下
switch (cameraMode) {
case CameraMode::FREE: {
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel * multiplier;
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
baseCamera.pos.y -= preStep;
baseCamera.lookAt.y -= preStep;
std::this_thread::sleep_for(std::chrono::milliseconds(BaseCamera::sleepTime));
}
} break;
case CameraMode::FIRST_PERSON: {
firstPersonPosOffset.y -= offsetMoveStep * multiplier;
} break;
case CameraMode::FOLLOW: {
// followPosOffset.y -= offsetMoveStep;
followLookAtOffset.y -= offsetMoveStep * multiplier;
}
}
}
void camera_up(float multiplier = 1.0f) { // 向上
switch (cameraMode) {
case CameraMode::FREE: {
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel * multiplier;
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
baseCamera.pos.y += preStep;
baseCamera.lookAt.y += preStep;
std::this_thread::sleep_for(std::chrono::milliseconds(BaseCamera::sleepTime));
}
} break;
case CameraMode::FIRST_PERSON: {
firstPersonPosOffset.y += offsetMoveStep * multiplier;
} break;
case CameraMode::FOLLOW: {
// followPosOffset.y += offsetMoveStep;
followLookAtOffset.y += offsetMoveStep * multiplier;
}
}
}
void cameraLookat_up(float mAngel, bool mouse = false) {
baseCamera.horizontalAngle += mAngel;
if (baseCamera.horizontalAngle >= 90) baseCamera.horizontalAngle = 89.99;
baseCamera.updateVertLook();
}
void cameraLookat_down(float mAngel, bool mouse = false) {
baseCamera.horizontalAngle -= mAngel;
if (baseCamera.horizontalAngle <= -90) baseCamera.horizontalAngle = -89.99;
baseCamera.updateVertLook();
}
void cameraLookat_left(float mAngel) {
baseCamera.verticalAngle += mAngel;
if (baseCamera.verticalAngle >= 360) baseCamera.verticalAngle = -360;
baseCamera.setHoriLook(baseCamera.verticalAngle);
}
void cameraLookat_right(float mAngel) {
baseCamera.verticalAngle -= mAngel;
if (baseCamera.verticalAngle <= -360) baseCamera.verticalAngle = 360;
baseCamera.setHoriLook(baseCamera.verticalAngle);
}
void changeCameraFOV(float value) {
baseCamera.fov += value;
}
void SwitchCameraMode() {
switch (cameraMode) {
case CameraMode::FREE: {
cameraMode = CameraMode::FOLLOW;
GakumasLocal::Log::Info("CameraMode: FOLLOW");
} break;
case CameraMode::FOLLOW: {
cameraMode = CameraMode::FIRST_PERSON;
GakumasLocal::Log::Info("CameraMode: FIRST_PERSON");
} break;
case CameraMode::FIRST_PERSON: {
cameraMode = CameraMode::FREE;
GakumasLocal::Log::Info("CameraMode: FREE");
} break;
}
}
void SwitchCameraSubMode() {
switch (cameraMode) {
case CameraMode::FIRST_PERSON: {
if (firstPersonRoll == FirstPersonRoll::ENABLE_ROLL) {
firstPersonRoll = FirstPersonRoll::DISABLE_ROLL;
GakumasLocal::Log::Info("FirstPersonRoll: DISABLE_ROLL");
}
else {
firstPersonRoll = FirstPersonRoll::ENABLE_ROLL;
GakumasLocal::Log::Info("FirstPersonRoll: ENABLE_ROLL");
}
} break;
case CameraMode::FOLLOW: {
if (followModeY == FollowModeY::APPLY_Y) {
followModeY = FollowModeY::SMOOTH_Y;
GakumasLocal::Log::Info("FollowModeY: SMOOTH_Y");
}
else {
followModeY = FollowModeY::APPLY_Y;
GakumasLocal::Log::Info("FollowModeY: APPLY_Y");
}
} break;
default: break;
}
}
void OnLeftDown() {
if (cameraMode == CameraMode::FREE) return;
if (followCharaIndex >= 1) {
followCharaIndex--;
}
}
void OnRightDown() {
if (cameraMode == CameraMode::FREE) return;
followCharaIndex++;
}
void OnUpDown() {
if (cameraMode == CameraMode::FOLLOW) {
const auto currPart = bodyPartsEnum.Last();
GakumasLocal::Log::InfoFmt("Look at: %s (0x%x)", currPart.first.c_str(), currPart.second);
}
}
void OnDownDown() {
if (cameraMode == CameraMode::FOLLOW) {
const auto currPart = bodyPartsEnum.Next();
GakumasLocal::Log::InfoFmt("Look at: %s (0x%x)", currPart.first.c_str(), currPart.second);
}
}
void ChangeLiveFollowCameraOffsetY(const float value) {
if (cameraMode == CameraMode::FOLLOW) {
followPosOffset.y += value;
}
}
void ChangeLiveFollowCameraOffsetX(const float value) {
if (cameraMode == CameraMode::FOLLOW) {
followPosOffset.x += value;
}
}
void ShowToast(const char *text) {
if (showToast) {
GakumasLocal::Log::ShowToast(text);
}
}
void JLThumbRight(float value) {
camera_right(value * l_sensitivity * baseCamera.fov / 60);
}
void JLThumbDown(float value) {
camera_back(value * l_sensitivity * baseCamera.fov / 60);
}
void JRThumbRight(float value) {
cameraLookat_right(value * r_sensitivity * baseCamera.fov / 60);
ChangeLiveFollowCameraOffsetX(-1 * value * r_sensitivity * baseCamera.fov / 60);
}
void JRThumbDown(float value) {
cameraLookat_down(value * r_sensitivity * baseCamera.fov / 60);
ChangeLiveFollowCameraOffsetY(-0.1 * value * r_sensitivity * baseCamera.fov / 60);
}
void JDadUp(){
reset_camera();
ShowToast("Reset Camera");
}
void JDadDown(){
ShowToast("Notification off, click again to turn it on.");
showToast = !showToast;
}
void JDadLeft(){
l_sensitivity = 1.0f;
ShowToast("Reset Movement Sensitivity");
}
void JDadRight(){
r_sensitivity = 1.0f;
ShowToast("Reset Camera Sensitivity");
}
void JAKeyDown() {
if (cameraMode == CameraMode::FOLLOW) {
const auto currPart = bodyPartsEnum.Next();
if (showToast) {
GakumasLocal::Log::ShowToastFmt("Look at: %s (0x%x)", currPart.first.c_str(),
currPart.second);
}
} else {
r_sensitivity *= 0.8f;
}
}
void JBKeyDown() {
if (cameraMode == CameraMode::FOLLOW) {
const auto currPart = bodyPartsEnum.Last();
if (showToast) {
GakumasLocal::Log::ShowToastFmt("Look at: %s (0x%x)", currPart.first.c_str(),
currPart.second);
}
} else {
r_sensitivity *= 1.2f;
}
}
void JXKeyDown() {
if (cameraMode == CameraMode::FOLLOW) {
OnLeftDown();
if (showToast) {
GakumasLocal::Log::ShowToastFmt("Look at position: %d", followCharaIndex);
}
} else {
l_sensitivity *= 0.8f;
}
}
void JYKeyDown() {
if (cameraMode == CameraMode::FOLLOW) {
OnRightDown();
if (showToast) {
GakumasLocal::Log::ShowToastFmt("Look at position: %d", followCharaIndex);
}
} else {
l_sensitivity *= 1.2f;
}
}
void JSelectKeyDown() {
switch (cameraMode) {
case CameraMode::FREE: {
cameraMode = CameraMode::FOLLOW;
ShowToast("Follow Mode");
} break;
case CameraMode::FOLLOW: {
cameraMode = CameraMode::FIRST_PERSON;
ShowToast("First-person Mode");
} break;
case CameraMode::FIRST_PERSON: {
cameraMode = CameraMode::FREE;
ShowToast("Free Mode");
} break;
}
}
void JStartKeyDown() {
switch (cameraMode) {
case CameraMode::FIRST_PERSON: {
if (firstPersonRoll == FirstPersonRoll::ENABLE_ROLL) {
firstPersonRoll = FirstPersonRoll::DISABLE_ROLL;
ShowToast("Camera Horizontal Fixed");
}
else {
firstPersonRoll = FirstPersonRoll::ENABLE_ROLL;
ShowToast("Camera Horizontal Rollable");
}
} break;
case CameraMode::FOLLOW: {
if (followModeY == FollowModeY::APPLY_Y) {
followModeY = FollowModeY::SMOOTH_Y;
ShowToast("Smooth Lift");
}
else {
followModeY = FollowModeY::APPLY_Y;
ShowToast("Instant Lift");
}
} break;
default: break;
}
}
UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target,
const UnityResolve::UnityType::Vector3& offset) {
// offset: z 远近, y 高低, x角度
const float angleX = offset.x;
const float distanceZ = offset.z;
const float angleRad = angleX * (M_PI / 180.0f);
const float newX = target.x + distanceZ * std::sin(angleRad);
const float newZ = target.z + distanceZ * std::cos(angleRad);
const float newY = target.y + offset.y;
return UnityResolve::UnityType::Vector3(newX, newY, newZ);
}
float CheckNewY(const UnityResolve::UnityType::Vector3& targetPos, const bool recordY,
GakumasLocal::Misc::FixedSizeQueue<float>& recordsY) {
const auto currentY = targetPos.y;
static auto lastRetY = currentY;
if (followModeY == FollowModeY::APPLY_Y) {
lastRetY = currentY;
return currentY;
}
const auto currentAvg = recordsY.Average();
// GakumasLocal::Log::DebugFmt("currentY: %f, currentAvg: %f, diff: %f", currentY, currentAvg, abs(currentY - currentAvg));
if (recordY) {
recordsY.Push(currentY);
}
if (abs(currentY - currentAvg) < 0.02) {
return lastRetY;
}
const auto retAvg = recordsY.Average();
lastRetY = lastRetY + (retAvg - lastRetY) / 8;
return lastRetY;
}
UnityResolve::UnityType::Vector3 CalcFollowModeLookAt(const UnityResolve::UnityType::Vector3& targetPos,
const UnityResolve::UnityType::Vector3& posOffset,
const bool recordY) {
static GakumasLocal::Misc::FixedSizeQueue<float> recordsY(60);
const float angleX = posOffset.x;
const float angleRad = (angleX + (followPosOffset.z >= 0 ? 90.0f : -90.0f)) * (M_PI / 180.0f);
UnityResolve::UnityType::Vector3 newTargetPos = targetPos;
newTargetPos.y = CheckNewY(targetPos, recordY, recordsY);
const float offsetX = followLookAtOffset.x * sin(angleRad);
const float offsetZ = followLookAtOffset.x * cos(angleRad);
newTargetPos.x += offsetX;
newTargetPos.z += offsetZ;
newTargetPos.y += followLookAtOffset.y;
return newTargetPos;
}
UnityResolve::UnityType::Vector3 CalcFirstPersonPosition(const UnityResolve::UnityType::Vector3& position,
const UnityResolve::UnityType::Vector3& forward,
const UnityResolve::UnityType::Vector3& offset) {
using Vector3 = UnityResolve::UnityType::Vector3;
// 计算角色的右方向
Vector3 up(0, 1, 0); // Y轴方向
Vector3 right = forward.cross(up).Normalize();
Vector3 fwd = forward;
Vector3 pos = position;
// 计算角色的左方向
Vector3 left = right * -1.0f;
// 计算最终位置
Vector3 backwardOffset = fwd * -offset.z;
Vector3 leftOffset = left * offset.x;
Vector3 finalPosition = pos + backwardOffset + leftOffset;
finalPosition.y += offset.y;
return finalPosition;
}
struct CameraMoveState {
bool w = false;
bool s = false;
bool a = false;
bool d = false;
bool ctrl = false;
bool space = false;
bool up = false;
bool down = false;
bool left = false;
bool right = false;
bool q = false;
bool e = false;
bool i = false;
bool k = false;
bool j = false;
bool l = false;
float thumb_l_right = 0.0f;
float thumb_l_down = 0.0f;
bool thumb_l_button = false;
float thumb_r_right = 0.0f;
float thumb_r_down = 0.0f;
bool thumb_r_button = false;
bool dpad_up = false;
bool dpad_down = false;
bool dpad_left = false;
bool dpad_right = false;
bool a_button = false;
bool b_button = false;
bool x_button = false;
bool y_button = false;
bool lb_button = false;
float lt_button = 0.0f;
bool rb_button = false;
float rt_button = 0.0f;
bool select_button = false;
bool start_button = false;
bool share_button = false;
bool xbox_button = false;
bool threadRunning = false;
void resetAll() {
// 获取当前对象的指针并转换为 unsigned char* 类型
unsigned char* p = reinterpret_cast<unsigned char*>(this);
// 遍历对象的每个字节
for (size_t offset = 0; offset < sizeof(*this); ) {
if (offset + sizeof(bool) <= sizeof(*this) && reinterpret_cast<bool*>(p + offset) == reinterpret_cast<bool*>(this) + offset / sizeof(bool)) {
// 如果当前偏移量适用于 bool 类型,则将其设置为 false
*reinterpret_cast<bool*>(p + offset) = false;
offset += sizeof(bool);
} else if (offset + sizeof(float) <= sizeof(*this) && reinterpret_cast<float*>(p + offset) == reinterpret_cast<float*>(this) + offset / sizeof(float)) {
// 如果当前偏移量适用于 float 类型,则将其设置为 0.0
*reinterpret_cast<float*>(p + offset) = 0.0f;
offset += sizeof(float);
} else {
// 处理未定义的情况(例如混合类型数组或其他类型成员)
// 可以根据实际情况调整逻辑或添加更多类型检查
offset += 1; // 跳过一个字节
}
}
}
} cameraMoveState;
void cameraRawInputThread() {
using namespace BaseCamera;
std::thread([]() {
if (cameraMoveState.threadRunning) return;
cameraMoveState.threadRunning = true;
while (true) {
if (cameraMoveState.w) camera_forward();
if (cameraMoveState.s) camera_back();
if (cameraMoveState.a) camera_left();
if (cameraMoveState.d) camera_right();
if (cameraMoveState.ctrl) camera_down();
if (cameraMoveState.space) camera_up();
if (cameraMoveState.up) cameraLookat_up(moveAngel);
if (cameraMoveState.down) cameraLookat_down(moveAngel);
if (cameraMoveState.left) cameraLookat_left(moveAngel);
if (cameraMoveState.right) cameraLookat_right(moveAngel);
if (cameraMoveState.q) changeCameraFOV(0.5f);
if (cameraMoveState.e) changeCameraFOV(-0.5f);
if (cameraMoveState.i) ChangeLiveFollowCameraOffsetY(offsetMoveStep);
if (cameraMoveState.k) ChangeLiveFollowCameraOffsetY(-offsetMoveStep);
if (cameraMoveState.j) ChangeLiveFollowCameraOffsetX(0.8);
if (cameraMoveState.l) ChangeLiveFollowCameraOffsetX(-0.8);
// 手柄操作响应
// 左摇杆
if (std::abs(cameraMoveState.thumb_l_right) > 0.1f)
JLThumbRight(cameraMoveState.thumb_l_right);
if (std::abs(cameraMoveState.thumb_l_down) > 0.1f)
JLThumbDown(cameraMoveState.thumb_l_down);
// 右摇杆
if (std::abs(cameraMoveState.thumb_r_right) > 0.1f)
JRThumbRight(cameraMoveState.thumb_r_right);
if (std::abs(cameraMoveState.thumb_r_down) > 0.1f)
JRThumbDown(cameraMoveState.thumb_r_down);
// 左扳机
if (std::abs(cameraMoveState.lt_button) > 0.1f)
camera_down(cameraMoveState.lt_button * l_sensitivity * baseCamera.fov / 60);
// 右扳机
if (std::abs(cameraMoveState.rt_button) > 0.1f)
camera_up(cameraMoveState.rt_button * l_sensitivity * baseCamera.fov / 60);
// 左肩键
if (cameraMoveState.lb_button) changeCameraFOV(0.5f * r_sensitivity);
// 右肩键
if (cameraMoveState.rb_button) changeCameraFOV(-0.5f * r_sensitivity);
// 十字键
if (cameraMoveState.dpad_up) JDadUp();
// if (cameraMoveState.dpad_down) JDadDown();
if (cameraMoveState.dpad_left) JDadLeft();
if (cameraMoveState.dpad_right) JDadRight();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}).detach();
}
void on_cam_rawinput_keyboard(int message, int key) {
if (message == WM_KEYDOWN || message == WM_KEYUP) {
switch (key) {
case KEY_W:
cameraMoveState.w = message == WM_KEYDOWN; break;
case KEY_S:
cameraMoveState.s = message == WM_KEYDOWN; break;
case KEY_A:
cameraMoveState.a = message == WM_KEYDOWN; break;
case KEY_D:
cameraMoveState.d = message == WM_KEYDOWN; break;
case KEY_CTRL:
cameraMoveState.ctrl = message == WM_KEYDOWN; break;
case KEY_SPACE:
cameraMoveState.space = message == WM_KEYDOWN; break;
case KEY_UP: {
if (message == WM_KEYDOWN) {
OnUpDown();
}
cameraMoveState.up = message == WM_KEYDOWN;
} break;
case KEY_DOWN: {
if (message == WM_KEYDOWN) {
OnDownDown();
}
cameraMoveState.down = message == WM_KEYDOWN;
} break;
case KEY_LEFT: {
if (message == WM_KEYDOWN) {
OnLeftDown();
}
cameraMoveState.left = message == WM_KEYDOWN;
} break;
case KEY_RIGHT: {
if (message == WM_KEYDOWN) {
OnRightDown();
}
cameraMoveState.right = message == WM_KEYDOWN;
} break;
case KEY_Q:
cameraMoveState.q = message == WM_KEYDOWN; break;
case KEY_E:
cameraMoveState.e = message == WM_KEYDOWN; break;
case KEY_I:
cameraMoveState.i = message == WM_KEYDOWN; break;
case KEY_K:
cameraMoveState.k = message == WM_KEYDOWN; break;
case KEY_J:
cameraMoveState.j = message == WM_KEYDOWN; break;
case KEY_L:
cameraMoveState.l = message == WM_KEYDOWN; break;
case KEY_R: {
if (message == WM_KEYDOWN) reset_camera();
} break;
case KEY_F: if (message == WM_KEYDOWN) SwitchCameraMode(); break;
case KEY_V: if (message == WM_KEYDOWN) SwitchCameraSubMode(); break;
// 手柄操作响应
case BTN_A:
cameraMoveState.a_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JAKeyDown();
break;
case BTN_B:
cameraMoveState.b_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JBKeyDown();
break;
case BTN_X:
cameraMoveState.x_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JXKeyDown();
break;
case BTN_Y:
cameraMoveState.y_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JYKeyDown();
break;
case BTN_LB:
cameraMoveState.lb_button = message == WM_KEYDOWN;
break;
case BTN_RB:
cameraMoveState.rb_button = message == WM_KEYDOWN;
break;
case BTN_THUMBL:
cameraMoveState.thumb_l_button = message == WM_KEYDOWN;
break;
case BTN_THUMBR:
cameraMoveState.thumb_r_button = message == WM_KEYDOWN;
break;
case BTN_SELECT:
cameraMoveState.select_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JSelectKeyDown();
break;
case BTN_START:
cameraMoveState.start_button = message == WM_KEYDOWN;
if (message == WM_KEYDOWN) JStartKeyDown();
break;
case BTN_SHARE:
cameraMoveState.share_button = message == WM_KEYDOWN;
break;
case BTN_XBOX:
cameraMoveState.xbox_button = message == WM_KEYDOWN;
break;
default: break;
}
}
}
void
on_cam_rawinput_joystick(JoystickEvent event) {
int message = event.getMessage();
float leftStickX = event.getLeftStickX();
float leftStickY = event.getLeftStickY();
float rightStickX = event.getRightStickX();
float rightStickY = event.getRightStickY();
float leftTrigger = event.getLeftTrigger();
float rightTrigger = event.getRightTrigger();
float hatX = event.getHatX();
float hatY = event.getHatY();
cameraMoveState.thumb_l_right = (std::abs(leftStickX) > 0.1f) ? leftStickX : 0;
cameraMoveState.thumb_l_down = (std::abs(leftStickY) > 0.1f) ? leftStickY : 0;
cameraMoveState.thumb_r_right = (std::abs(rightStickX) > 0.1f) ? rightStickX : 0;
cameraMoveState.thumb_r_down = (std::abs(rightStickY) > 0.1f) ? rightStickY : 0;
cameraMoveState.lt_button = (std::abs(leftTrigger) > 0.1f) ? leftTrigger : 0;
cameraMoveState.rt_button = (std::abs(rightTrigger) > 0.1f) ? rightTrigger : 0;
cameraMoveState.dpad_up = hatY == -1.0f;
cameraMoveState.dpad_down = hatY == 1.0f;
cameraMoveState.dpad_left = hatX == -1.0f;
cameraMoveState.dpad_right = hatX == 1.0f;
if (cameraMoveState.dpad_down) {
JDadDown();
}
// GakumasLocal::Log::InfoFmt(
// "Motion event: action=%d, leftStickX=%.2f, leftStickY=%.2f, rightStickX=%.2f, rightStickY=%.2f, leftTrigger=%.2f, rightTrigger=%.2f, hatX=%.2f, hatY=%.2f",
// message, leftStickX, leftStickY, rightStickX, rightStickY, leftTrigger,
// rightTrigger, hatX, hatY);
}
void initCameraSettings() {
reset_camera();
cameraRawInputThread();
}
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include "baseCamera.hpp"
#include "../../deps/Joystick/JoystickEvent.h"
namespace GKCamera {
enum class CameraMode {
FREE,
FIRST_PERSON,
FOLLOW
};
enum class FirstPersonRoll {
ENABLE_ROLL,
DISABLE_ROLL
};
enum class FollowModeY {
APPLY_Y,
SMOOTH_Y
};
void SetCameraMode(CameraMode mode);
CameraMode GetCameraMode();
void SetFirstPersonRoll(FirstPersonRoll mode);
FirstPersonRoll GetFirstPersonRoll();
extern BaseCamera::Camera baseCamera;
extern UnityResolve::UnityType::Vector3 firstPersonPosOffset;
extern UnityResolve::UnityType::Vector3 followPosOffset;
extern int followCharaIndex;
extern GakumasLocal::Misc::CSEnum bodyPartsEnum;
float CheckNewY(const UnityResolve::UnityType::Vector3& targetPos, const bool recordY,
GakumasLocal::Misc::FixedSizeQueue<float>& recordsY);
UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target,
const UnityResolve::UnityType::Vector3& offset);
UnityResolve::UnityType::Vector3 CalcFirstPersonPosition(const UnityResolve::UnityType::Vector3& position,
const UnityResolve::UnityType::Vector3& forward,
const UnityResolve::UnityType::Vector3& offset);
UnityResolve::UnityType::Vector3 CalcFollowModeLookAt(const UnityResolve::UnityType::Vector3& targetPos,
const UnityResolve::UnityType::Vector3& posOffset,
const bool recordY = false);
void on_cam_rawinput_keyboard(int message, int key);
void on_cam_rawinput_joystick(JoystickEvent event);
void initCameraSettings();
}

View File

@@ -0,0 +1,110 @@
#include <string>
#include "nlohmann/json.hpp"
#include "../Log.h"
#include <thread>
namespace GakumasLocal::Config {
bool isConfigInit = false;
bool dbgMode = false;
bool enabled = true;
bool lazyInit = true;
bool replaceFont = true;
bool forceExportResource = true;
bool textTest = false;
bool useMasterTrans = true;
int gameOrientation = 0;
bool dumpText = false;
bool enableFreeCamera = false;
int targetFrameRate = 0;
bool unlockAllLive = false;
bool unlockAllLiveCostume = false;
bool enableLiveCustomeDress = false;
std::string liveCustomeHeadId = "";
std::string liveCustomeCostumeId = "";
bool loginAsIOS = false;
bool useCustomeGraphicSettings = false;
float renderScale = 0.77f;
int qualitySettingsLevel = 3;
int volumeIndex = 3;
int maxBufferPixel = 3384;
int reflectionQualityLevel = 4;
int lodQualityLevel = 4;
bool enableBreastParam = false;
float bDamping = 0.33f;
float bStiffness = 0.08f;
float bSpring = 1.0f;
float bPendulum = 0.055f;
float bPendulumRange = 0.15f;
float bAverage = 0.20f;
float bRootWeight = 0.5f;
bool bUseArmCorrection = true;
bool bUseScale = false;
float bScale = 1.0f;
bool bUseLimit = true;
float bLimitXx = 1.0f;
float bLimitXy = 1.0f;
float bLimitYx = 1.0f;
float bLimitYy = 1.0f;
float bLimitZx = 1.0f;
float bLimitZy = 1.0f;
void LoadConfig(const std::string& configStr) {
try {
const auto config = nlohmann::json::parse(configStr);
#define GetConfigItem(name) if (config.contains(#name)) name = config[#name]
GetConfigItem(dbgMode);
GetConfigItem(enabled);
GetConfigItem(lazyInit);
GetConfigItem(replaceFont);
GetConfigItem(forceExportResource);
GetConfigItem(gameOrientation);
GetConfigItem(textTest);
GetConfigItem(useMasterTrans);
GetConfigItem(dumpText);
GetConfigItem(targetFrameRate);
GetConfigItem(enableFreeCamera);
GetConfigItem(unlockAllLive);
GetConfigItem(unlockAllLiveCostume);
GetConfigItem(enableLiveCustomeDress);
GetConfigItem(liveCustomeHeadId);
GetConfigItem(liveCustomeCostumeId);
GetConfigItem(loginAsIOS);
GetConfigItem(useCustomeGraphicSettings);
GetConfigItem(renderScale);
GetConfigItem(qualitySettingsLevel);
GetConfigItem(volumeIndex);
GetConfigItem(maxBufferPixel);
GetConfigItem(reflectionQualityLevel);
GetConfigItem(lodQualityLevel);
GetConfigItem(enableBreastParam);
GetConfigItem(bDamping);
GetConfigItem(bStiffness);
GetConfigItem(bSpring);
GetConfigItem(bPendulum);
GetConfigItem(bPendulumRange);
GetConfigItem(bAverage);
GetConfigItem(bRootWeight);
GetConfigItem(bUseArmCorrection);
GetConfigItem(bUseScale);
GetConfigItem(bScale);
GetConfigItem(bUseLimit);
GetConfigItem(bLimitXx);
GetConfigItem(bLimitXy);
GetConfigItem(bLimitYx);
GetConfigItem(bLimitYy);
GetConfigItem(bLimitZx);
GetConfigItem(bLimitZy);
}
catch (std::exception& e) {
Log::ErrorFmt("LoadConfig error: %s", e.what());
}
isConfigInit = true;
}
}

View File

@@ -0,0 +1,55 @@
#pragma once
namespace GakumasLocal::Config {
extern bool isConfigInit;
extern bool dbgMode;
extern bool enabled;
extern bool lazyInit;
extern bool replaceFont;
extern bool forceExportResource;
extern int gameOrientation;
extern bool textTest;
extern bool useMasterTrans;
extern bool dumpText;
extern bool enableFreeCamera;
extern int targetFrameRate;
extern bool unlockAllLive;
extern bool unlockAllLiveCostume;
extern bool enableLiveCustomeDress;
extern std::string liveCustomeHeadId;
extern std::string liveCustomeCostumeId;
extern bool loginAsIOS;
extern bool useCustomeGraphicSettings;
extern float renderScale;
extern int qualitySettingsLevel;
extern int volumeIndex;
extern int maxBufferPixel;
extern int reflectionQualityLevel;
extern int lodQualityLevel;
extern bool enableBreastParam;
extern float bDamping;
extern float bStiffness;
extern float bSpring;
extern float bPendulum;
extern float bPendulumRange;
extern float bAverage;
extern float bRootWeight;
extern bool bUseArmCorrection;
extern bool bUseScale;
extern float bScale;
extern bool bUseLimit;
extern float bLimitXx;
extern float bLimitXy;
extern float bLimitYx;
extern float bLimitYy;
extern float bLimitZx;
extern float bLimitZy;
void LoadConfig(const std::string& configStr);
}

View File

@@ -0,0 +1,123 @@
#include <unordered_set>
#include "StringParser.hpp"
#include "fmt/core.h"
#include "fmt/ranges.h"
#include "../Misc.hpp"
namespace StringParser {
std::string ParseItems::ToFmtString() {
std::vector<std::string> ret{};
int currFlagIndex = 0;
for (const auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret.push_back(fmt::format("{{{}}}", currFlagIndex));
currFlagIndex++;
}
else {
ret.push_back(i.content);
}
}
return fmt::format("{}", fmt::join(ret, ""));
}
std::vector<std::string> ParseItems::GetFlagValues() {
std::vector<std::string> ret{};
for (const auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret.push_back(i.content);
}
}
return ret;
}
ParseItems ParseItems::parse(const std::string &str, bool parseTags) {
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
u'6', u'7', u'8', u'9', u'+', u'',
u'-', u'', u'%', u'',
u'.', u'×', u',', u''};
ParseItems result;
if (str.contains("{")) {
result.isValid = false;
return result;
}
std::u16string origText = GakumasLocal::Misc::ToUTF16(str);
bool isInTag = false;
bool isInFlagSequence = false;
std::u16string currentCacheText;
for (char16_t currChar : origText) {
if (parseTags && currChar == u'<') {
if (!currentCacheText.empty()) {
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
isInFlagSequence = false;
}
isInTag = true;
currentCacheText.push_back(currChar);
} else if (parseTags && currChar == u'>') {
isInTag = false;
currentCacheText.push_back(currChar);
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
} else if (isInTag) {
currentCacheText.push_back(currChar);
} else if (splitFlags.contains(currChar)) {
if (!isInFlagSequence && !currentCacheText.empty()) {
result.items.push_back({ParseItemType::TEXT, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
}
isInFlagSequence = true;
currentCacheText.push_back(currChar);
} else {
if (isInFlagSequence && !currentCacheText.empty()) {
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
isInFlagSequence = false;
}
currentCacheText.push_back(currChar);
}
}
if (!currentCacheText.empty()) {
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
GakumasLocal::Misc::ToUTF8(currentCacheText)});
}
for (auto& i : result.items) {
if (i.type == ParseItemType::FLAG) {
return result;
}
}
result.isValid = false;
return result;
}
std::string ParseItems::MergeText(ParseItems &textTarget, ParseItems &valueTarget) {
if (!textTarget.isValid) return "";
if (!valueTarget.isValid) return "";
const auto fmtText = textTarget.ToFmtString();
const auto values = valueTarget.GetFlagValues();
const std::string ret = GakumasLocal::Misc::StringFormat::stringFormatString(fmtText, values);
return {ret.begin(), ret.end()};
}
std::string ParseItems::MergeText(const std::string &newStr) {
if (!isValid) return "";
const auto values = GetFlagValues();
return GakumasLocal::Misc::StringFormat::stringFormatString(newStr, values);
}
int ParseItems::GetFlagCount() {
int ret = 0;
for (auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret++;
}
}
return ret;
}
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
namespace StringParser {
enum class ParseItemType {
FLAG,
TEXT
};
struct ParseItem {
ParseItemType type;
std::string content;
};
struct ParseItems {
bool isValid = true;
std::vector<ParseItem> items;
std::string ToFmtString();
std::vector<std::string> GetFlagValues();
std::string MergeText(const std::string& newStr);
int GetFlagCount();
static ParseItems parse(const std::string& str, bool parseTags);
static std::string MergeText(ParseItems& textTarget, ParseItems& valueTarget);
};
}

24
src/console.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "stdinclude.hpp"
namespace
{
void console_thread()
{
std::string line;
while (true)
{
std::cin >> line;
std::cout << "\n] " << line << "\n";
}
}
}
void start_console()
{
#ifdef _DEBUG
std::thread(console_thread).detach();
#endif
}

View File

@@ -0,0 +1,67 @@
//
// Created by RanKaeder on 2024/6/18.
//
#ifndef GAKUMAS_LOCALIFY_JOYSTICKEVENT_H
#define GAKUMAS_LOCALIFY_JOYSTICKEVENT_H
class JoystickEvent {
public:
JoystickEvent(int message, float leftStickX, float leftStickY, float rightStickX,
float rightStickY, float leftTrigger, float rightTrigger,
float hatX, float hatY)
: message(message), leftStickX(leftStickX), leftStickY(leftStickY),
rightStickX(rightStickX), rightStickY(rightStickY), leftTrigger(leftTrigger),
rightTrigger(rightTrigger), hatX(hatX), hatY(hatY) {
}
// Getter 方法
int getMessage() const {
return message;
}
float getLeftStickX() const {
return leftStickX;
}
float getLeftStickY() const {
return leftStickY;
}
float getRightStickX() const {
return rightStickX;
}
float getRightStickY() const {
return rightStickY;
}
float getLeftTrigger() const {
return leftTrigger;
}
float getRightTrigger() const {
return rightTrigger;
}
float getHatX() const {
return hatX;
}
float getHatY() const {
return hatY;
}
private:
int message;
float leftStickX;
float leftStickY;
float rightStickX;
float rightStickY;
float leftTrigger;
float rightTrigger;
float hatX;
float hatY;
};
#endif //GAKUMAS_LOCALIFY_JOYSTICKEVENT_H

398
src/deps/UnityResolve/.gitignore vendored Normal file
View File

@@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 遂沫
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,194 @@
> [!NOTE]\
> 有新的功能建议或者Bug可以提交Issues (当然你也可以尝试自己修改代码后提交到该仓库\
> New feature suggestions or bugs can be commit as issues. Of course, you can also try modifying the code yourself and then commit it to the repository.
> > Dome
> > - [Phasmophobia Cheat](https://github.com/issuimo/PhasmophobiaCheat/tree/main)
> 如果是MSVC编译器请打开SEH选项 \
> If using the MSVC compiler, please open the SEH option. \
> 对于高版本安卓程序崩溃的可能问题请参阅 [link](https://github.com/issuimo/UnityResolve.hpp/issues/11) \
> For potential issues related to crashes in higher version Android programs, please refer to the link [link](https://github.com/issuimo/UnityResolve.hpp/issues/11)
<hr>
<h3 align="center">简要概述 (Brief overview)</h3>
<hr>
# UnityResolve.hpp
> ### 支持的平台 (Supported platforms)
> - [X] Windows
> - [X] Android
> - [X] Linux
> ### 类型 (Type)
> - [X] Camera
> - [X] Transform
> - [X] Component
> - [X] Object (Unity)
> - [X] LayerMask
> - [X] Rigidbody
> - [x] MonoBehaviour
> - [x] Renderer
> - [x] Mesh
> - [X] Behaviour
> - [X] Physics
> - [X] GameObject
> - [X] Collider
> - [X] Vector4
> - [X] Vector3
> - [X] Vector2
> - [X] Quaternion
> - [X] Bounds
> - [X] Plane
> - [X] Ray
> - [X] Rect
> - [X] Color
> - [X] Matrix4x4
> - [X] Array
> - [x] String
> - [x] Object (C#)
> - [X] Type (C#)
> - [X] List
> - [X] Dictionary
> - [X] Animator
> - [X] CapsuleCollider
> - [X] BoxCollider
> - [X] Time
> - More...
> ### 功能 (Function)
> - [X] DumpToFile
> - [X] 附加线程 (Thread Attach / Detach)
> - [X] 修改静态变量值 (Modifying the value of a static variable)
> - [X] 获取对象 (Obtaining an instance)
> - [X] 创建C#字符串 (Create C# String)
> - [X] 创建C#数组 (Create C# Array)
> - [X] 创建C#对象 (Create C# instance)
> - [X] 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint)
> - [X] 获取继承子类的名称 (Get the name of the inherited subclass)
> - [X] 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get))
> - [x] 获取Gameobject组件 (Get GameObject component)
> - More...
<hr>
<h3 align="center">功能使用 (How to use)</h3>
<hr>
#### 更改平台 (Change platform)
> ``` c++
> #define WINDOWS_MODE 1 // 如果需要请改为 1 | 1 if you need
> #define ANDROID_MODE 0
> #define LINUX_MODE 0
> ```
#### 初始化 (Initialization)
> ``` c++
> UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll | mono.dll"), UnityResolve::Mode::Mono);
> // Linux or Android
> UnityResolve::Init(dlopen(L"GameAssembly.so | mono.so", RTLD_NOW), UnityResolve::Mode::Mono);
> ```
> 参数1: dll句柄 \
> Parameter 1: DLL handle \
> 参数2: 使用模式 \
> Parameter 2: Usage mode
> - Mode::Il2cpp
> - Mode::Mono
#### 附加线程 (Thread Attach / Detach)
> ``` c++
> // C# GC Attach
> UnityResolve::ThreadAttach();
>
> // C# GC Detach
> UnityResolve::ThreadDetach();
> ```
#### 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get))
> ``` c++
> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
> const auto pClass = assembly->Get("className | 类名称");
> // assembly->Get("className | 类名称", "*");
> // assembly->Get("className | 类名称", "namespace | 空间命名");
>
> const auto field = pClass->Get<UnityResolve::Field>("Field Name | 变量名");
> const auto fieldOffset = pClass->Get<std::int32_t>("Field Name | 变量名");
> const int time = pClass->GetValue<int>(obj Instance | 对象地址, "time");
> // pClass->GetValue(obj Instance*, name);
> = pClass->SetValue<int>(obj Instance | 对象地址, "time", 114514);
> // pClass->SetValue(obj Instance*, name, value);
> const auto method = pClass->Get<UnityResolve::Method>("Method Name | 函数名");
> // pClass->Get<UnityResolve::Method>("Method Name | 函数名", { "System.String" });
> // pClass->Get<UnityResolve::Method>("Method Name | 函数名", { "*", "System.String" });
> // pClass->Get<UnityResolve::Method>("Method Name | 函数名", { "*", "", "System.String" });
> // pClass->Get<UnityResolve::Method>("Method Name | 函数名", { "*", "System.Int32", "System.String" });
> // pClass->Get<UnityResolve::Method>("Method Name | 函数名", { "*", "System.Int32", "System.String", "*" });
> // "*" == ""
>
> const auto functionPtr = method->function;
>
> const auto method1 = pClass->Get<UnityResolve::Method>("method name1 | 函数名称1");
> const auto method2 = pClass->Get<UnityResolve::Method>("method name2 | 函数名称2");
>
> method1->Invoke<int>(114, 514, "114514");
> // Invoke<return type>(args...);
>
> const auto ptr = method2->Cast<void, int, bool>();
> // Cast<return type, args...>(void);
> ptr(114514, true);
> ```
#### 转存储到文件 (DumpToFile)
> ``` C++
> UnityResolve::DumpToFile("./output/");
> ```
#### 创建C#字符串 (Create C# String)
> ``` c++
> const auto str = UnityResolve::UnityType::String::New("string | 字符串");
> std::string cppStr = str.ToString();
> ```
#### 创建C#数组 (Create C# Array)
> ``` c++
> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
> const auto pClass = assembly->Get("className | 类名称");
> const auto array = UnityResolve::UnityType::Array<T>::New(pClass, size);
> std::vector<T> cppVector = array.ToVector();
> ```
#### 创建C#对象 (Create C# instance)
> ``` c++
> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
> const auto pClass = assembly->Get("className | 类名称");
> const auto pGame = pClass->New<Game*>();
> ```
#### 获取对象 (Obtaining an instance)
> ``` c++
> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
> const auto pClass = assembly->Get("className | 类名称");
> std::vector<Player*> playerVector = pClass->FindObjectsByType<Player*>();
> // FindObjectsByType<return type>(void);
> playerVector.size();
> ```
#### 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint)
> ``` c++
> Camera* pCamera = UnityResolve::UnityType::Camera::GetMain();
> Vector3 point = pCamera->WorldToScreenPoint(Vector3, Eye::Left);
> Vector3 world = pCamera->ScreenToWorldPoint(point, Eye::Left);
> ```
#### 获取继承子类的名称 (Get the name of the inherited subclass)
> ``` c++
> const auto assembly = UnityResolve::Get("UnityEngine.CoreModule.dll");
> const auto pClass = assembly->Get("MonoBehaviour");
> Parent* pParent = pClass->FindObjectsByType<Parent*>()[0];
> std::string child = pParent->GetType()->GetFullName();
> ```
#### 获取Gameobject组件 (Get GameObject component)
> ``` c++
> std::vector<T*> objs = gameobj->GetComponents<T*>(UnityResolve::Get("assembly.dll")->Get("class")));
> // gameobj->GetComponents<return type>(Class* component)
> std::vector<T*> objs = gameobj->GetComponentsInChildren<T*>(UnityResolve::Get("assembly.dll")->Get("class")));
> // gameobj->GetComponentsInChildren<return type>(Class* component)
> std::vector<T*> objs = gameobj->GetComponentsInParent<T*>(UnityResolve::Get("assembly.dll")->Get("class")));
> // gameobj->GetComponentsInParent<return type>(Class* component)
> ```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
// Formatting library for C++ - dynamic argument lists
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#ifndef FMT_MODULE
# include <functional> // std::reference_wrapper
# include <memory> // std::unique_ptr
# include <vector>
#endif
#include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
template <typename T>
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
return static_cast<const T&>(v);
}
// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
// 2022 (v17.10.0).
//
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So node is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
class dynamic_arg_list {
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
* A dynamic list of formatting arguments with storage.
*
* It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`.
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_type = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
/**
* Adds an argument into the dynamic store for later passing to a formatting
* function.
*
* Note that custom types and string types (but not string views) are copied
* into the store dynamically allocating memory if necessary.
*
* **Example**:
*
* fmt::dynamic_format_arg_store<fmt::format_context> store;
* store.push_back(42);
* store.push_back("abc");
* store.push_back(1.5f);
* std::string result = fmt::vformat("{} and {} and {}", store);
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
* Adds a reference to the argument into the dynamic store for later passing
* to a formatting function.
*
* **Example**:
*
* fmt::dynamic_format_arg_store<fmt::format_context> store;
* char band[] = "Rolling Stones";
* store.push_back(std::cref(band));
* band[9] = 'c'; // Changing str affects the output.
* std::string result = fmt::vformat("{}", store);
* // result == "Rolling Scones"
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
* Adds named argument into the dynamic store for later passing to a
* formatting function. `std::reference_wrapper` is supported to avoid
* copying of the argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
}
/// Erase all elements from the store.
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
}
/// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};
FMT_END_NAMESPACE
#endif // FMT_ARGS_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,612 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
faint = 1 << 1,
italic = 1 << 2,
underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace detail {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace detail
/// A text style consisting of foreground and background colors and emphasis.
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {}
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
report_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
report_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
-> text_style {
return lhs |= rhs;
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color;
}
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color;
}
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style;
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
/// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style {
return text_style(true, foreground);
}
/// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style {
return text_style(false, background);
}
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
-> text_style {
return text_style(lhs) | rhs;
}
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0;
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size();
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
-> bool {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
};
template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg : detail::view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char>
void vformat_to(
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
format_args args) {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
}
/**
* Formats a string and prints it to the specified file stream using ANSI
* escape sequences to specify text formatting.
*
* **Example**:
*
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
T&&... args) {
vprint(f, ts, fmt, fmt::make_format_args(args...));
}
/**
* Formats a string and prints it to stdout using ANSI escape sequences to
* specify text formatting.
*
* **Example**:
*
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...);
}
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
-> std::string {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return fmt::to_string(buf);
}
/**
* Formats arguments and returns the result as a string using ANSI escape
* sequences to specify text formatting.
*
* **Example**:
*
* ```
* #include <fmt/color.h>
* std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
* "The answer is {}", 42);
* ```
*/
template <typename... T>
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
-> std::string {
return fmt::vformat(ts, fmt, fmt::make_format_args(args...));
}
/// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
format_args args) -> OutputIt {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out);
}
/**
* Formats arguments with the given text style, writes the result to the output
* iterator `out` and returns the iterator past the end of the output range.
*
* **Example**:
*
* std::vector<char> out;
* fmt::format_to(std::back_inserter(out),
* fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, const text_style& ts,
format_string<T...> fmt, T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
}
template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
template <typename FormatContext>
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out);
}
return out;
}
};
/**
* Returns an argument that will be formatted using ANSI escape sequences,
* to be used in a formatting function.
*
* **Example**:
*
* fmt::print("Elapsed time: {0:.2f} seconds",
* fmt::styled(1.23, fmt::fg(fmt::color::green) |
* fmt::bg(fmt::color::blue)));
*/
template <typename T>
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
-> detail::styled_arg<remove_cvref_t<T>> {
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View File

@@ -0,0 +1,529 @@
// Formatting library for C++ - experimental format string compilation
//
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_
#ifndef FMT_MODULE
# include <iterator> // std::back_inserter
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
// A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {};
namespace detail {
template <typename T, typename InputIt>
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
-> counting_iterator {
return it + (end - begin);
}
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/**
* Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17
* `constexpr if` compiler support.
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
else
return detail::get<N - 1>(rest...);
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
using get_type = typename get_type_impl<N, T>::type;
template <typename T> struct is_compiled_format : std::false_type {};
template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
*out++ = value;
return out;
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
const T& arg = get_arg_checked<T, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
}
return write<Char>(out, arg);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMT_THROW(format_error("argument with specified name is not found"));
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
constexpr FMT_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
template <typename L, typename R> struct concat {
L lhs;
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
};
template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
return pos;
}
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S fmt);
template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S fmt) {
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>())
return tail;
else
return make_concat(head, tail);
} else {
return head;
}
}
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S fmt) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(fmt);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
} else if constexpr (c != ':') {
FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMT_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
fmt);
}
}
}
// Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S fmt) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(fmt);
if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(fmt);
} else {
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
fmt);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(fmt);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
fmt);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
}
}
}
template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
return result;
}
}
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmt::to_string(first.value);
} else {
return fmt::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format(compiled, std::forward<Args>(args)...);
}
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
-> size_t {
return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) {
memory_buffer buffer;
fmt::format_to(std::back_inserter(buffer), fmt, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) {
print(stdout, fmt, args...);
}
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
}
} // namespace literals
#endif
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_

View File

@@ -0,0 +1,5 @@
// This file is only provided for compatibility and may be removed in future
// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h
// otherwise.
#include "format.h"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,439 @@
// Formatting library for C++ - optional OS-specific functionality
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_OS_H_
#define FMT_OS_H_
#include "format.h"
#ifndef FMT_MODULE
# include <cerrno>
# include <cstddef>
# include <cstdio>
# include <system_error> // std::system_error
# if FMT_HAS_INCLUDE(<xlocale.h>)
# include <xlocale.h> // LC_NUMERIC_MASK on macOS
# endif
#endif // FMT_MODULE
#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe.
# if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
# endif
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
# else
# define FMT_USE_FCNTL 0
# endif
#endif
#ifndef FMT_POSIX
# if defined(_WIN32) && !defined(__MINGW32__)
// Fix warnings about deprecated symbols.
# define FMT_POSIX(call) _##call
# else
# define FMT_POSIX(call) call
# endif
#endif
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
#ifdef FMT_SYSTEM
# define FMT_HAS_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else
# define FMT_SYSTEM(call) ::call
# ifdef _WIN32
// Fix warnings about deprecated symbols.
# define FMT_POSIX_CALL(call) ::_##call
# else
# define FMT_POSIX_CALL(call) ::call
# endif
#endif
// Retries the expression while it evaluates to error_result and errno
// equals to EINTR.
#ifndef _WIN32
# define FMT_RETRY_VAL(result, expression, error_result) \
do { \
(result) = (expression); \
} while ((result) == (error_result) && errno == EINTR)
#else
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
#endif
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
/**
* A reference to a null-terminated string. It can be constructed from a C
* string or `std::string`.
*
* You can use one of the following type aliases for common character types:
*
* +---------------+-----------------------------+
* | Type | Definition |
* +===============+=============================+
* | cstring_view | basic_cstring_view<char> |
* +---------------+-----------------------------+
* | wcstring_view | basic_cstring_view<wchar_t> |
* +---------------+-----------------------------+
*
* This class is most useful as a parameter type for functions that wrap C APIs.
*/
template <typename Char> class basic_cstring_view {
private:
const Char* data_;
public:
/// Constructs a string reference object from a C string.
basic_cstring_view(const Char* s) : data_(s) {}
/// Constructs a string reference from an `std::string` object.
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
/// Returns the pointer to a C string.
auto c_str() const -> const Char* { return data_; }
};
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
#ifdef _WIN32
FMT_API const std::error_category& system_category() noexcept;
namespace detail {
FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
}
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
/**
* Constructs a `std::system_error` object with the description of the form
*
* <message>: <system-message>
*
* where `<message>` is the formatted message and `<system-message>` is the
* system message corresponding to the error code.
* `error_code` is a Windows error code as given by `GetLastError`.
* If `error_code` is not a valid error code such as -1, the system message
* will look like "error -1".
*
* **Example**:
*
* // This throws a system_error with the description
* // cannot open file 'madeup': The system cannot find the file
* specified.
* // or similar (system message may vary).
* const char *filename = "madeup";
* LPOFSTRUCT of = LPOFSTRUCT();
* HFILE file = OpenFile(filename, &of, OF_READ);
* if (file == HFILE_ERROR) {
* throw fmt::windows_error(GetLastError(),
* "cannot open file '{}'", filename);
* }
*/
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
}
// Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors.
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
#else
inline auto system_category() noexcept -> const std::error_category& {
return std::system_category();
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
FILE* file_;
friend class file;
explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept;
public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
auto operator=(buffered_file&& other) -> buffered_file& {
close();
file_ = other.file_;
other.file_ = nullptr;
return *this;
}
// Opens a file.
FMT_API buffered_file(cstring_view filename, cstring_view mode);
// Closes the file.
FMT_API void close();
// Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int;
template <typename... T>
inline void print(string_view fmt, const T&... args) {
const auto& vargs = fmt::make_format_args(args...);
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
: fmt::vprint(file_, fmt, vargs);
}
};
#if FMT_USE_FCNTL
// A file. Closed file is represented by a file object with descriptor -1.
// Methods that are not declared with noexcept may throw
// fmt::system_error in case of failure. Note that some errors such as
// closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler.
class FMT_API file {
private:
int fd_; // File descriptor.
// Constructs a file object with a given descriptor.
explicit file(int fd) : fd_(fd) {}
friend struct pipe;
public:
// Possible values for the oflag argument to the constructor.
enum {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
};
// Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag);
public:
file(const file&) = delete;
void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& {
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
// Destroys the object closing the file it represents if any.
~file() noexcept;
// Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; }
// Closes the file.
void close();
// Returns the file size. The size has signed type for consistency with
// stat::st_size.
auto size() const -> long long;
// Attempts to read count bytes from the file into the specified buffer.
auto read(void* buffer, size_t count) -> size_t;
// Attempts to write count bytes from the specified buffer to the file.
auto write(const void* buffer, size_t count) -> size_t;
// Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object.
static auto dup(int fd) -> file;
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
void dup2(int fd, std::error_code& ec) noexcept;
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
auto fdopen(const char* mode) -> buffered_file;
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
struct FMT_API pipe {
file read_end;
file write_end;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
pipe();
};
// Returns the memory page size.
auto getpagesize() -> long;
namespace detail {
struct buffer_size {
buffer_size() = default;
size_t value = 0;
auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
oflag = new_oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
};
class file_buffer final : public buffer<char> {
private:
file file_;
FMT_API static void grow(buffer<char>& buf, size_t);
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other) noexcept;
FMT_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
} // namespace detail
constexpr auto buffer_size = detail::buffer_size();
/// A fast output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); }
/// Formats `args` according to specifications in `fmt` and writes the
/// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
}
};
/**
* Opens a file for writing. Supported parameters passed in `params`:
*
* - `<integer>`: Flags passed to [open](
* https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)
* (`file::WRONLY | file::CREATE | file::TRUNC` by default)
* - `buffer_size=<integer>`: Output buffer size
*
* **Example**:
*
* auto out = fmt::output_file("guide.txt");
* out.print("Don't {}", "Panic");
*/
template <typename... T>
inline auto output_file(cstring_view path, T... params) -> ostream {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_OS_H_

View File

@@ -0,0 +1,211 @@
// Formatting library for C++ - std::ostream support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#ifndef FMT_MODULE
# include <fstream> // std::filebuf
#endif
#ifdef _WIN32
# ifdef __GLIBCXX__
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# include <io.h>
#endif
#include "chrono.h" // formatbuf
FMT_BEGIN_NAMESPACE
namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
#if FMT_MSC_VERSION
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#endif
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
unsigned_streamsize n = size <= max_size ? size : max_size;
os.write(buf_data, static_cast<std::streamsize>(n));
buf_data += n;
size -= n;
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view {
const T& value;
};
} // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
};
using ostream_formatter = basic_ostream_formatter<char>;
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename Context>
auto format(detail::streamed_view<T> view, Context& ctx) const
-> decltype(ctx.out()) {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};
/**
* Returns a view that formats `value` via an ostream `operator<<`.
*
* **Example**:
*
* fmt::print("Current thread id: {}\n",
* fmt::streamed(std::this_thread::get_id()));
*/
template <typename T>
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
namespace detail {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
typename detail::vformat_args<Char>::type args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer);
}
/**
* Prints formatted data to the stream `os`.
*
* **Example**:
*
* fmt::print(cerr, "Don't {}!", "panic");
*/
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...);
if (detail::use_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_

View File

@@ -0,0 +1,656 @@
// Formatting library for C++ - legacy printf implementation
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_PRINTF_H_
#define FMT_PRINTF_H_
#ifndef FMT_MODULE
# include <algorithm> // std::max
# include <limits> // std::numeric_limits
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context {
private:
basic_appender<Char> out_;
basic_format_args<basic_printf_context> args_;
static_assert(std::is_same<Char, char>::value ||
std::is_same<Char, wchar_t>::value,
"Unsupported code unit type.");
public:
using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes.
basic_printf_context(basic_appender<Char> out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
};
namespace detail {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = to_unsigned(max_value<int>());
return value <= max;
}
static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
static auto fits_in_int(int) -> bool { return true; }
};
struct printf_precision_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> int {
report_error("precision is not integer");
return 0;
}
};
// An argument visitor that returns true iff arg is a zero integer.
struct is_zero_int {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> bool {
return value == 0;
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> bool {
return false;
}
};
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> {
using type = bool;
};
template <typename T, typename Context> class arg_converter {
private:
using char_type = typename Context::char_type;
basic_format_arg<Context>& arg_;
char_type type_;
public:
arg_converter(basic_format_arg<Context>& arg, char_type type)
: arg_(arg), type_(type) {}
void operator()(bool value) {
if (type_ != 's') operator()<bool>(value);
}
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
void operator()(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
}
}
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
void operator()(U) {} // No conversion needed for non-integral types.
};
// Converts an integer argument to T for printf, if T is an integral type.
// If T is void, the argument is converted to corresponding signed or unsigned
// type depending on the type specifier: 'd' and 'i' - signed, other -
// unsigned).
template <typename T, typename Context, typename Char>
void convert_arg(basic_format_arg<Context>& arg, Char type) {
arg.visit(arg_converter<T, Context>(arg, type));
}
// Converts an integer argument to char for printf.
template <typename Context> class char_converter {
private:
basic_format_arg<Context>& arg_;
public:
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
void operator()(T) {} // No conversion needed for non-integral types.
};
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
auto operator()(const Char* s) -> const Char* { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative.
class printf_width_handler {
private:
format_specs& specs_;
public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.align = align::left;
width = 0 - width;
}
unsigned int_max = to_unsigned(max_value<int>());
if (width > int_max) report_error("number is too big");
return static_cast<unsigned>(width);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
auto operator()(T) -> unsigned {
report_error("width is not integer");
return 0;
}
};
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(basic_appender<Char> iter, format_specs& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The `printf` argument formatter.
template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<Char>;
context_type& context_;
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = presentation_type::none;
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
}
public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
base::operator()(value);
return;
}
format_specs s = this->specs;
if (s.type != presentation_type::none && s.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
s.sign = sign::none;
s.alt = false;
s.fill = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (s.align == align::none || s.align == align::numeric)
s.align = align::right;
write<Char>(this->out, static_cast<Char>(value), s);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
base::operator()(value);
}
void operator()(const char* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
void operator()(const void* value) {
if (value)
base::operator()(value);
else
write_null_pointer();
}
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({});
handle.format(parse_ctx, context_);
}
};
template <typename Char>
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
specs.align = align::left;
break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill = '0';
break;
case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space;
break;
case '#':
specs.alt = true;
break;
default:
return;
}
}
}
template <typename Char, typename GetArg>
auto parse_header(const Char*& it, const Char* end, format_specs& specs,
GetArg get_arg) -> int {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
int value = parse_nonnegative_int(it, end, -1);
if (it != end && *it == '$') { // value is an argument index
++it;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.fill = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) report_error("number is too big");
specs.width = value;
return arg_index;
}
}
}
parse_flags(specs, it, end);
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) report_error("number is too big");
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(
get_arg(-1).visit(detail::printf_width_handler(specs)));
}
}
return arg_index;
}
inline auto parse_printf_presentation_type(char c, type t, bool& upper)
-> presentation_type {
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'X':
upper = true;
FMT_FALLTHROUGH;
case 'x':
return in(t, integral_set) ? pt::hex : pt::none;
case 'E':
upper = true;
FMT_FALLTHROUGH;
case 'e':
return in(t, float_set) ? pt::exp : pt::none;
case 'F':
upper = true;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G':
upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using iterator = basic_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
if (!find<false, Char>(it, end, '%', it)) {
it = end; // find leaves it == nullptr if it doesn't find '%'.
break;
}
Char c = *it++;
if (it != end && *it == c) {
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs();
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) report_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
specs.precision = parse_nonnegative_int(it, end, 0);
} else if (c == '*') {
++it;
specs.precision =
static_cast<int>(get_arg(-1).visit(printf_precision_handler()));
} else {
specs.precision = 0;
}
}
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) {
// Ignore '0' for non-numeric types or if '-' present.
specs.fill = ' ';
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>());
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv);
}
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
if (specs.fill.template get<Char>() == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
}
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0;
switch (c) {
case 'h':
if (t == 'h') {
++it;
t = it != end ? *it : 0;
convert_arg<signed char>(arg, t);
} else {
convert_arg<short>(arg, t);
}
break;
case 'l':
if (t == 'l') {
++it;
t = it != end ? *it : 0;
convert_arg<long long>(arg, t);
} else {
convert_arg<long>(arg, t);
}
break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default:
--it;
convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) report_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
switch (type) {
case 'i':
case 'u':
type = 'd';
break;
case 'c':
arg.visit(char_converter<basic_printf_context<Char>>(arg));
break;
}
}
bool upper = false;
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
if (specs.type == presentation_type::none)
report_error("invalid format specifier");
specs.upper = upper;
start = it;
// Format argument.
arg.visit(printf_arg_formatter<Char>(out, specs, context));
}
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
} // namespace detail
using printf_context = basic_printf_context<char>;
using wprintf_context = basic_printf_context<wchar_t>;
using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>;
/// Constructs an `format_arg_store` object that contains references to
/// arguments and can be implicitly converted to `printf_args`.
template <typename Char = char, typename... T>
inline auto make_printf_args(T&... args)
-> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
return fmt::make_format_args<basic_printf_context<Char>>(args...);
}
template <typename Char> struct vprintf_args {
using type = basic_format_args<basic_printf_context<Char>>;
};
template <typename Char>
inline auto vsprintf(basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
return to_string(buf);
}
/**
* Formats `args` according to specifications in `fmt` and returns the result
* as as string.
*
* **Example**:
*
* std::string message = fmt::sprintf("The answer is %d", 42);
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
/**
* Formats `args` according to specifications in `fmt` and writes the output
* to `f`.
*
* **Example**:
*
* fmt::fprintf(stderr, "Don't %s!", "panic");
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
make_printf_args<Char>(args...));
}
template <typename Char>
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args)
-> int {
return vfprintf(stdout, fmt, args);
}
/**
* Formats `args` according to specifications in `fmt` and writes the output
* to `stdout`.
*
* **Example**:
*
* fmt::printf("Elapsed time: %.2f seconds", 1.23);
*/
template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_

View File

@@ -0,0 +1,882 @@
// Formatting library for C++ - range and tuple support
//
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_RANGES_H_
#define FMT_RANGES_H_
#ifndef FMT_MODULE
# include <initializer_list>
# include <iterator>
# include <string>
# include <tuple>
# include <type_traits>
# include <utility>
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_EXPORT
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T> class is_map {
template <typename U> static auto check(U*) -> typename U::mapped_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename T> class is_set {
template <typename U> static auto check(U*) -> typename U::key_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};
template <typename T>
struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
// Member function overloads.
template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overloads. Only participate in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}
template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};
template <typename T>
struct has_const_begin_end<
T, void_t<decltype(*detail::range_begin(
std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_end(
std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(*detail::range_begin(std::declval<T&>())),
decltype(detail::range_end(std::declval<T&>())),
// the extra int here is because older versions of MSVC don't
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
// Check for integer_sequence
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900
template <typename T, T... N>
using integer_sequence = std::integer_sequence<T, N...>;
template <size_t... N> using index_sequence = std::index_sequence<N...>;
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
#else
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
};
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
template <typename T, size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
template <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
#endif
template <typename T>
using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ {
public:
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is>
static auto all_true(index_sequence<Is...>,
integer_sequence<bool, (Is >= 0)...>) -> std::true_type;
static auto all_true(...) -> std::false_type;
template <size_t... Is>
static auto check(index_sequence<Is...>) -> decltype(all_true(
index_sequence<Is...>{},
integer_sequence<bool,
(is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{}));
public:
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
template <typename Tuple, typename F, size_t... Is>
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get;
// Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused);
}
template <typename Tuple, typename F>
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
std::forward<Tuple>(t), std::forward<F>(f));
}
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
}
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
template <typename T>
using range_reference_type = typename range_reference_type_impl<T>::type;
#else
template <typename Range>
using range_reference_type =
decltype(*detail::range_begin(std::declval<Range&>()));
#endif
// We don't use the Range's value_type for anything, but we do need the Range's
// reference type, with cv-ref stripped.
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename T>
void operator()(const formatter<T, char_type>& f, const T& v) {
if (i > 0) ctx.advance_to(detail::copy<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
}
int i;
FormatContext& ctx;
basic_string_view<char_type> separator;
};
} // namespace detail
template <typename T> struct is_tuple_like {
static constexpr const bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<Tuple, Char>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
public:
FMT_CONSTEXPR formatter() {}
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy<Char>(closing_bracket_, ctx.out());
}
};
template <typename T, typename Char> struct is_range {
static constexpr const bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
};
namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element>
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
.map(std::declval<Element>()))>,
Char>;
template <typename R>
using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename...> struct conjunction : std::true_type {};
template <typename P> struct conjunction<P> : P {};
template <typename P1, typename... Pn>
struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
template <typename T, typename Char>
struct range_formatter<
T, Char,
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
bool is_debug = false;
template <typename Output, typename It, typename Sentinel, typename U = T,
FMT_ENABLE_IF(std::is_same<U, Char>::value)>
auto write_debug_string(Output& out, It it, Sentinel end) const -> Output {
auto buf = basic_memory_buffer<Char>();
for (; it != end; ++it) buf.push_back(*it);
auto specs = format_specs();
specs.type = presentation_type::debug;
return detail::write<Char>(
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
}
template <typename Output, typename It, typename Sentinel, typename U = T,
FMT_ENABLE_IF(!std::is_same<U, Char>::value)>
auto write_debug_string(Output& out, It, Sentinel) const -> Output {
return out;
}
public:
FMT_CONSTEXPR range_formatter() {}
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
return underlying_;
}
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
detail::maybe_set_debug_format(underlying_, true);
if (it == end) return underlying_.parse(ctx);
switch (detail::to_ascii(*it)) {
case 'n':
set_brackets({}, {});
++it;
break;
case '?':
is_debug = true;
set_brackets({}, {});
++it;
if (it == end || *it != 's') report_error("invalid format specifier");
FMT_FALLTHROUGH;
case 's':
if (!std::is_same<T, Char>::value)
report_error("invalid format specifier");
if (!is_debug) {
set_brackets(detail::string_literal<Char, '"'>{},
detail::string_literal<Char, '"'>{});
set_separator({});
detail::maybe_set_debug_format(underlying_, false);
}
++it;
return it;
}
if (it != end && *it != '}') {
if (*it != ':') report_error("invalid format specifier");
detail::maybe_set_debug_format(underlying_, false);
++it;
}
ctx.advance_to(it);
return underlying_.parse(ctx);
}
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
auto mapper = detail::range_mapper<buffered_context<Char>>();
auto out = ctx.out();
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
if (is_debug) return write_debug_string(out, std::move(it), end);
out = detail::copy<Char>(opening_bracket_, out);
int i = 0;
for (; it != end; ++it) {
if (i > 0) out = detail::copy<Char>(separator_, out);
ctx.advance_to(out);
auto&& item = *it; // Need an lvalue
out = underlying_.format(mapper.map(item), ctx);
++i;
}
out = detail::copy<Char>(closing_bracket_, out);
return out;
}
};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void>
struct range_format_kind
: conditional_t<
is_range<T, Char>::value, detail::range_format_kind_<T>,
std::integral_constant<range_format, range_format::disabled>> {};
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<conjunction<
bool_constant<
range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string>
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
private:
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
public:
using nonlocking = void;
FMT_CONSTEXPR formatter() {
if (detail::const_check(range_format_kind<R, Char>::value !=
range_format::set))
return;
range_formatter_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return range_formatter_.parse(ctx);
}
template <typename FormatContext>
auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
return range_formatter_.format(range, ctx);
}
};
// A map formatter.
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
private:
using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>;
decltype(detail::tuple::get_formatters<element_type, Char>(
detail::tuple_index_sequence<element_type>())) formatters_;
bool no_delimiters_ = false;
public:
FMT_CONSTEXPR formatter() {}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end) {
if (detail::to_ascii(*it) == 'n') {
no_delimiters_ = true;
++it;
}
if (it != end && *it != '}') {
if (*it != ':') report_error("invalid format specifier");
++it;
}
ctx.advance_to(it);
}
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext>
auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) {
auto out = ctx.out();
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
if (!no_delimiters_) out = detail::copy<Char>(open, out);
int i = 0;
auto mapper = detail::range_mapper<buffered_context<Char>>();
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
for (auto&& value : map) {
if (i > 0) out = detail::copy<Char>(sep, out);
ctx.advance_to(out);
detail::for_each2(formatters_, mapper.map(value),
detail::format_tuple_element<FormatContext>{
0, ctx, detail::string_literal<Char, ':', ' '>{}});
++i;
}
basic_string_view<Char> close = detail::string_literal<Char, '}'>{};
if (!no_delimiters_) out = detail::copy<Char>(close, out);
return out;
}
};
// A (debug_)string formatter.
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::string ||
range_format_kind<R, Char>::value ==
range_format::debug_string>> {
private:
using range_type = detail::maybe_const_range<R>;
using string_type =
conditional_t<std::is_constructible<
detail::std_string_view<Char>,
decltype(detail::range_begin(std::declval<R>())),
decltype(detail::range_end(std::declval<R>()))>::value,
detail::std_string_view<Char>, std::basic_string<Char>>;
formatter<string_type, Char> underlying_;
public:
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
if (detail::const_check(range_format_kind<R, Char>::value ==
range_format::debug_string))
*out++ = '"';
out = underlying_.format(
string_type{detail::range_begin(range), detail::range_end(range)}, ctx);
if (detail::const_check(range_format_kind<R, Char>::value ==
range_format::debug_string))
*out++ = '"';
return out;
}
};
template <typename It, typename Sentinel, typename Char = char>
struct join_view : detail::view {
It begin;
Sentinel end;
basic_string_view<Char> sep;
join_view(It b, Sentinel e, basic_string_view<Char> s)
: begin(std::move(b)), end(e), sep(s) {}
};
template <typename It, typename Sentinel, typename Char>
struct formatter<join_view<It, Sentinel, Char>, Char> {
private:
using value_type =
#ifdef __cpp_lib_ranges
std::iter_value_t<It>;
#else
typename std::iterator_traits<It>::value_type;
#endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view_ref = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&,
join_view<It, Sentinel, Char>&&>;
public:
using nonlocking = void;
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
return value_formatter_.parse(ctx);
}
template <typename FormatContext>
auto format(view_ref& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto it = std::forward<view_ref>(value).begin;
auto out = ctx.out();
if (it == value.end) return out;
out = value_formatter_.format(*it, ctx);
++it;
while (it != value.end) {
out = detail::copy<Char>(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
out = value_formatter_.format(*it, ctx);
++it;
}
return out;
}
};
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
template <typename ParseContext>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
report_error("incompatible format specs for tuple elements");
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/**
* Returns an object that formats `std::tuple` with elements separated by `sep`.
*
* **Example**:
*
* auto t = std::tuple<int, char>{1, 'a'};
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}
/**
* Returns an object that formats `std::initializer_list` with elements
* separated by `sep`.
*
* **Example**:
*
* fmt::print("{}", fmt::join({1, 2, 3}, ", "));
* // Output: "1, 2, 3"
*/
template <typename T>
auto join(std::initializer_list<T> list, string_view sep)
-> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_

View File

@@ -0,0 +1,699 @@
// Formatting library for C++ - formatters for standard library types
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include "format.h"
#include "ostream.h"
#ifndef FMT_MODULE
# include <atomic>
# include <bitset>
# include <complex>
# include <cstdlib>
# include <exception>
# include <memory>
# include <thread>
# include <type_traits>
# include <typeinfo>
# include <utility>
# include <vector>
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>)
# include <filesystem>
# endif
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
# endif
// Use > instead of >= in the version check because <source_location> may be
// available after C++17 but before C++20 is marked as implemented.
# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
# include <source_location>
# endif
# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
# include <expected>
# endif
#endif // FMT_MODULE
#if FMT_HAS_INCLUDE(<version>)
# include <version>
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
#ifndef FMT_CPP_LIB_FILESYSTEM
# ifdef __cpp_lib_filesystem
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else
# define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif
#ifndef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
# else
# define FMT_CPP_LIB_VARIANT 0
# endif
#endif
#if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
return p.string<Char>();
}
template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMT_ASSERT(valid, "invalid utf16");
} else if constexpr (std::is_same_v<Char, PathChar>) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), native);
} else {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
}
} // namespace detail
FMT_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
char path_type_ = 0;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);
return it;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
auto specs = specs_;
auto path_string =
!path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p, path_string);
return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
}
};
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
private:
// Functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) {
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out;
}
};
public:
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return write_padded(ctx, writer{bs});
}
};
FMT_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(const std::optional<T>& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char,
std::enable_if_t<is_formattable<T, Char>::value &&
is_formattable<E, Char>::value>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::expected<T, E>& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
out = detail::write_escaped_alternative<Char>(out, *value);
} else {
out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error());
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write(out, loc.file_name());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.line());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.column());
out = detail::write(out, ": ");
out = detail::write(out, loc.function_name());
return out;
}
};
FMT_END_NAMESPACE
#endif
#if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
return detail::write<Char>(ctx.out(), "monostate");
}
};
FMT_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const Variant& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v);
},
value);
}
FMT_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#if FMT_USE_RTTI
namespace detail {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
FMT_EXPORT
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) {
return detail::write_demangled_name<Char>(ctx.out(), ti);
}
};
#endif
FMT_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char, // DEPRECATED! Mixing code unit types.
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = FMT_USE_RTTI != 0;
}
return it;
}
template <typename Context>
auto format(const std::exception& ex, Context& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
#if FMT_USE_RTTI
if (with_typename_) {
out = detail::write_demangled_name<Char>(out, typeid(ex));
*out++ = ':';
*out++ = ' ';
}
#endif
return detail::write_bytes<Char>(out, string_view(ex.what()));
}
};
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char>
struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
: formatter<bool, Char> {
template <typename FormatContext>
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v, ctx);
}
};
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
: formatter<T, Char> {
template <typename FormatContext>
auto format(const std::atomic<T>& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<T, Char>::format(v.load(), ctx);
}
};
#ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext>
auto format(const std::atomic_flag& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v.test(), ctx);
}
};
#endif // __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private:
detail::dynamic_format_specs<Char> specs_;
template <typename FormatContext, typename OutputIt>
FMT_CONSTEXPR auto do_format(const std::complex<T>& c,
detail::dynamic_format_specs<Char>& specs,
FormatContext& ctx, OutputIt out) const
-> OutputIt {
if (c.real() != 0) {
*out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.sign = sign::plus;
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
*out++ = Char(')');
return out;
}
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
return out;
}
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value);
}
template <typename FormatContext>
auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = specs_;
if (specs.width_ref.kind != detail::arg_id_kind::none ||
specs.precision_ref.kind != detail::arg_id_kind::none) {
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(
specs.precision, specs.precision_ref, ctx);
}
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
auto buf = basic_memory_buffer<Char>();
auto outer_specs = format_specs();
outer_specs.width = specs.width;
outer_specs.fill = specs.fill;
outer_specs.align = specs.align;
specs.width = 0;
specs.fill = {};
specs.align = align::none;
do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(),
basic_string_view<Char>(buf.data(), buf.size()),
outer_specs);
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View File

@@ -0,0 +1,322 @@
// Formatting library for C++ - optional wchar_t and exotic character support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_XCHAR_H_
#define FMT_XCHAR_H_
#include "color.h"
#include "format.h"
#include "ranges.h"
#ifndef FMT_MODULE
# include <cwchar>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# include <locale>
# endif
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
template <typename S, typename = void> struct format_string_char {};
template <typename S>
struct format_string_char<
S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {
using type = char_t<S>;
};
template <typename S>
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
using type = typename S::char_type;
};
template <typename S>
using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <>
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
#endif
template <typename... T>
constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) {
return fmt::make_format_args<wformat_context>(args...);
}
inline namespace literals {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
return {s};
}
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
-> join_view<It, Sentinel, wchar_t> {
return {begin, end, sep};
}
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
template <typename T>
auto join(std::initializer_list<T> list, wstring_view sep)
-> join_view<const T*, const T*, wchar_t> {
return join(std::begin(list), std::end(list), sep);
}
template <typename... T>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args);
return to_string(buf);
}
template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename OutputIt, typename... T>
auto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)
-> OutputIt {
return vformat_to(out, fmt::wstring_view(fmt),
fmt::make_wformat_args(args...));
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename Locale, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& format_str,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args);
}
template <typename Locale, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, T&&... args)
-> std::basic_string<Char> {
return detail::vformat(
loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
typename detail::vformat_args<Char>::type args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&
!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
return vformat_to(out, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
typename detail::vformat_args<Char>::type args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename Locale, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n,
basic_string_view<Char> format_str,
typename detail::vformat_args<Char>::type args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
return buf.count();
}
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
auto buf = wmemory_buffer();
detail::vformat_to(buf, fmt, args);
buf.push_back(L'\0');
if (std::fputws(buf.data(), f) == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
inline void vprint(wstring_view fmt, wformat_args args) {
vprint(stdout, fmt, args);
}
template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
-> std::wstring {
auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return fmt::to_string(buf);
}
template <typename... T>
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
wformat_string<T...> fmt, const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
/// Converts `value` to `std::wstring` using the default format for type `T`.
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_

View File

@@ -0,0 +1,135 @@
module;
// Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module.
#ifndef FMT_IMPORT_STD
# include <algorithm>
# include <bitset>
# include <chrono>
# include <cmath>
# include <complex>
# include <cstddef>
# include <cstdint>
# include <cstdio>
# include <cstdlib>
# include <cstring>
# include <ctime>
# include <exception>
# include <expected>
# include <filesystem>
# include <fstream>
# include <functional>
# include <iterator>
# include <limits>
# include <locale>
# include <memory>
# include <optional>
# include <ostream>
# include <source_location>
# include <stdexcept>
# include <string>
# include <string_view>
# include <system_error>
# include <thread>
# include <type_traits>
# include <typeinfo>
# include <utility>
# include <variant>
# include <vector>
#else
# include <limits.h>
# include <stdint.h>
# include <stdio.h>
# include <time.h>
#endif
#include <cerrno>
#include <climits>
#include <version>
#if __has_include(<cxxabi.h>)
# include <cxxabi.h>
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <intrin.h>
#endif
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h>
#endif
#if __has_include(<winapifamily.h>)
# include <winapifamily.h>
#endif
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h>
# include <sys/stat.h>
# include <sys/types.h>
# ifndef _WIN32
# include <unistd.h>
# else
# include <io.h>
# endif
#endif
#ifdef _WIN32
# if defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
export module fmt;
#ifdef FMT_IMPORT_STD
import std;
#endif
#define FMT_EXPORT export
#define FMT_BEGIN_EXPORT export {
#define FMT_END_EXPORT }
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
// - all declarations are detached from module 'fmt'
// - the module behaves like a traditional static library, too
// - all library symbols are mangled traditionally
// - you can mix TUs with either importing or #including the {fmt} API
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
#ifndef FMT_OS
# define FMT_OS 1
#endif
// All library-provided declarations and definitions must be in the module
// purview to be exported.
#include "fmt/args.h"
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/compile.h"
#include "fmt/format.h"
#if FMT_OS
# include "fmt/os.h"
#endif
#include "fmt/ostream.h"
#include "fmt/printf.h"
#include "fmt/ranges.h"
#include "fmt/std.h"
#include "fmt/xchar.h"
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
// gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION
module :private;
#endif
#if FMT_HAS_INCLUDE("format.cc")
# include "format.cc"
#endif
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
# include "os.cc"
#endif

View File

@@ -0,0 +1,43 @@
// Formatting library for C++
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept
-> dragonbox::decimal_fp<float>;
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char;
template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail
FMT_END_NAMESPACE

View File

@@ -0,0 +1,403 @@
// Formatting library for C++ - optional OS-specific functionality
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
// Disable bogus MSVC warnings.
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS
#endif
#include "fmt/os.h"
#ifndef FMT_MODULE
# include <climits>
# if FMT_USE_FCNTL
# include <sys/stat.h>
# include <sys/types.h>
# ifdef _WRS_KERNEL // VxWorks7 kernel
# include <ioLib.h> // getpagesize
# endif
# ifndef _WIN32
# include <unistd.h>
# else
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <io.h>
# endif // _WIN32
# endif // FMT_USE_FCNTL
# ifdef _WIN32
# include <windows.h>
# endif
#endif
#ifdef _WIN32
# ifndef S_IRUSR
# define S_IRUSR _S_IREAD
# endif
# ifndef S_IWUSR
# define S_IWUSR _S_IWRITE
# endif
# ifndef S_IRGRP
# define S_IRGRP 0
# endif
# ifndef S_IWGRP
# define S_IWGRP 0
# endif
# ifndef S_IROTH
# define S_IROTH 0
# endif
# ifndef S_IWOTH
# define S_IWOTH 0
# endif
#endif
namespace {
#ifdef _WIN32
// Return type of read and write functions.
using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#elif FMT_USE_FCNTL
// Return type of read and write functions.
using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; }
#endif
} // namespace
FMT_BEGIN_NAMESPACE
#ifdef _WIN32
namespace detail {
class system_message {
system_message(const system_message&) = delete;
void operator=(const system_message&) = delete;
unsigned long result_;
wchar_t* message_;
static bool is_whitespace(wchar_t c) noexcept {
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
}
public:
explicit system_message(unsigned long error_code)
: result_(0), message_(nullptr) {
result_ = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
if (result_ != 0) {
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
--result_;
}
}
}
~system_message() { LocalFree(message_); }
explicit operator bool() const noexcept { return result_ != 0; }
operator basic_string_view<wchar_t>() const noexcept {
return basic_string_view<wchar_t>(message_, result_);
}
};
class utf8_system_category final : public std::error_category {
public:
const char* name() const noexcept override { return "system"; }
std::string message(int error_code) const override {
auto&& msg = system_message(error_code);
if (msg) {
auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg)) {
return utf8_message.str();
}
}
return "unknown error";
}
};
} // namespace detail
FMT_API const std::error_category& system_category() noexcept {
static const detail::utf8_system_category category;
return category;
}
std::system_error vwindows_error(int err_code, string_view format_str,
format_args args) {
auto ec = std::error_code(err_code, system_category());
return std::system_error(ec, vformat(format_str, args));
}
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
const char* message) noexcept {
FMT_TRY {
auto&& msg = system_message(error_code);
if (msg) {
auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg)) {
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
string_view(utf8_message));
return;
}
}
}
FMT_CATCH(...) {}
format_error_code(out, error_code, message);
}
void report_windows_error(int error_code, const char* message) noexcept {
report_error(detail::format_windows_error, error_code, message);
}
#endif // _WIN32
buffered_file::~buffered_file() noexcept {
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
report_system_error(errno, "cannot close file");
}
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
nullptr);
if (!file_)
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
filename.c_str()));
}
void buffered_file::close() {
if (!file_) return;
int result = FMT_SYSTEM(fclose(file_));
file_ = nullptr;
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
int buffered_file::descriptor() const {
#ifdef FMT_HAS_SYSTEM
// fileno is a macro on OpenBSD.
# ifdef fileno
# undef fileno
# endif
int fd = FMT_POSIX_CALL(fileno(file_));
#elif defined(_WIN32)
int fd = _fileno(file_);
#else
int fd = fileno(file_);
#endif
if (fd == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
return fd;
}
#if FMT_USE_FCNTL
# ifdef _WIN32
using mode_t = int;
# endif
constexpr mode_t default_open_mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
file::file(cstring_view path, int oflag) {
# if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1;
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
*this = file::open_windows_file(converted.c_str(), oflag);
# else
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
if (fd_ == -1)
FMT_THROW(
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
# endif
}
file::~file() noexcept {
// Don't retry close in case of EINTR!
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
report_system_error(errno, "cannot close file");
}
void file::close() {
if (fd_ == -1) return;
// Don't retry close in case of EINTR!
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_));
fd_ = -1;
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
long long file::size() const {
# ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds.
// Both functions support large file sizes.
DWORD size_upper = 0;
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError();
if (error != NO_ERROR)
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
}
unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
# else
using Stat = struct stat;
Stat file_stat = Stat();
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
"return type of file::size is not large enough");
return file_stat.st_size;
# endif
}
std::size_t file::read(void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
return detail::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
return detail::to_unsigned(result);
}
file file::dup(int fd) {
// Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
if (new_fd == -1)
FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
return file(new_fd);
}
void file::dup2(int fd) {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) {
FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
fd));
}
}
void file::dup2(int fd, std::error_code& ec) noexcept {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) ec = std::error_code(errno, std::generic_category());
}
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
# else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f) {
FMT_THROW(system_error(
errno, FMT_STRING("cannot associate stream with file descriptor")));
}
buffered_file bf(f);
fd_ = -1;
return bf;
}
# if defined(_WIN32) && !defined(__MINGW32__)
file file::open_windows_file(wcstring_view path, int oflag) {
int fd = -1;
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
if (fd == -1) {
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
}
return file(fd);
}
# endif
pipe::pipe() {
int fds[2] = {};
# ifdef _WIN32
// Make the default pipe capacity same as on Linux 2.6.11+.
enum { DEFAULT_CAPACITY = 65536 };
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
# else
// Don't retry as the pipe function doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
int result = FMT_POSIX_CALL(pipe(fds));
# endif
if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
// The following assignments don't throw.
read_end = file(fds[0]);
write_end = file(fds[1]);
}
# if !defined(__MSDOS__)
long getpagesize() {
# ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwPageSize;
# else
# ifdef _WRS_KERNEL
long size = FMT_POSIX_CALL(getpagesize());
# else
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
# endif
if (size < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
return size;
# endif
}
# endif
namespace detail {
void file_buffer::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
}
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
: buffer<char>(grow), file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
file_buffer::file_buffer(file_buffer&& other) noexcept
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
file_buffer::~file_buffer() {
flush();
delete[] data();
}
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

24765
src/deps/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

123
src/dllproxy/proxy.cpp Normal file
View File

@@ -0,0 +1,123 @@
#include <stdinclude.hpp>
#include <cpprest/details/web_utilities.h>
extern "C"
{
void* GetFileVersionInfoA_Original = NULL;
void* GetFileVersionInfoByHandle_Original = NULL;
void* GetFileVersionInfoExA_Original = NULL;
void* GetFileVersionInfoExW_Original = NULL;
void* GetFileVersionInfoSizeA_Original = NULL;
void* GetFileVersionInfoSizeExA_Original = NULL;
void* GetFileVersionInfoSizeExW_Original = NULL;
void* GetFileVersionInfoSizeW_Original = NULL;
void* GetFileVersionInfoW_Original = NULL;
void* VerFindFileA_Original = NULL;
void* VerFindFileW_Original = NULL;
void* VerInstallFileA_Original = NULL;
void* VerInstallFileW_Original = NULL;
void* VerLanguageNameA_Original = NULL;
void* VerLanguageNameW_Original = NULL;
void* VerQueryValueA_Original = NULL;
void* VerQueryValueW_Original = NULL;
#define RESOLVE_DEF(name)\
void* name##_Original = NULL;
RESOLVE_DEF(ApplyCompatResolutionQuirking);
RESOLVE_DEF(CompatString);
RESOLVE_DEF(CompatValue);
RESOLVE_DEF(DXGIDumpJournal);
RESOLVE_DEF(PIXBeginCapture);
RESOLVE_DEF(PIXEndCapture);
RESOLVE_DEF(PIXGetCaptureState);
RESOLVE_DEF(SetAppCompatStringPointer);
RESOLVE_DEF(UpdateHMDEmulationStatus);
RESOLVE_DEF(CreateDXGIFactory);
RESOLVE_DEF(CreateDXGIFactory1);
RESOLVE_DEF(CreateDXGIFactory2);
RESOLVE_DEF(DXGID3D10CreateDevice);
RESOLVE_DEF(DXGID3D10CreateLayeredDevice);
RESOLVE_DEF(DXGID3D10GetLayeredDeviceSize);
RESOLVE_DEF(DXGID3D10RegisterLayers);
RESOLVE_DEF(DXGIDeclareAdapterRemovalSupport);
RESOLVE_DEF(DXGIGetDebugInterface1);
RESOLVE_DEF(DXGIReportAdapterConfiguration);
}
using namespace std;
namespace
{
class version_init
{
public:
version_init()
{
std::string dll_path;
dll_path.resize(MAX_PATH);
dll_path.resize(GetSystemDirectoryA(dll_path.data(), MAX_PATH));
dll_path += "\\" + "version.dll"s;
auto original_dll = LoadLibraryA(dll_path.data());
GetFileVersionInfoA_Original = GetProcAddress(original_dll, "GetFileVersionInfoA");
GetFileVersionInfoByHandle_Original = GetProcAddress(original_dll, "GetFileVersionInfoByHandle");
GetFileVersionInfoExA_Original = GetProcAddress(original_dll, "GetFileVersionInfoExA");
GetFileVersionInfoExW_Original = GetProcAddress(original_dll, "GetFileVersionInfoExW");
GetFileVersionInfoSizeA_Original = GetProcAddress(original_dll, "GetFileVersionInfoSizeA");
GetFileVersionInfoSizeExA_Original = GetProcAddress(original_dll, "GetFileVersionInfoSizeExA");
GetFileVersionInfoSizeExW_Original = GetProcAddress(original_dll, "GetFileVersionInfoSizeExW");
GetFileVersionInfoSizeW_Original = GetProcAddress(original_dll, "GetFileVersionInfoSizeW");
GetFileVersionInfoW_Original = GetProcAddress(original_dll, "GetFileVersionInfoW");
VerFindFileA_Original = GetProcAddress(original_dll, "VerFindFileA");
VerFindFileW_Original = GetProcAddress(original_dll, "VerFindFileW");
VerInstallFileA_Original = GetProcAddress(original_dll, "VerInstallFileA");
VerInstallFileW_Original = GetProcAddress(original_dll, "VerInstallFileW");
VerLanguageNameA_Original = GetProcAddress(original_dll, "VerLanguageNameA");
VerLanguageNameW_Original = GetProcAddress(original_dll, "VerLanguageNameW");
VerQueryValueA_Original = GetProcAddress(original_dll, "VerQueryValueA");
VerQueryValueW_Original = GetProcAddress(original_dll, "VerQueryValueW");
};
};
#define RESOLVE_ADDR(name)\
name##_Original = GetProcAddress(original_dll, #name)
class dxgi_init {
public :
dxgi_init() {
std::string dll_path;
dll_path.resize(MAX_PATH);
dll_path.resize(GetSystemDirectoryA(dll_path.data(), MAX_PATH));
dll_path += "\\" + "dxgi.dll"s;
auto original_dll = LoadLibraryA(dll_path.data());
RESOLVE_ADDR(ApplyCompatResolutionQuirking);
RESOLVE_ADDR(CompatString);
RESOLVE_ADDR(CompatValue);
RESOLVE_ADDR(DXGIDumpJournal);
RESOLVE_ADDR(PIXBeginCapture);
RESOLVE_ADDR(PIXEndCapture);
RESOLVE_ADDR(PIXGetCaptureState);
RESOLVE_ADDR(SetAppCompatStringPointer);
RESOLVE_ADDR(UpdateHMDEmulationStatus);
RESOLVE_ADDR(CreateDXGIFactory);
RESOLVE_ADDR(CreateDXGIFactory1);
RESOLVE_ADDR(CreateDXGIFactory2);
RESOLVE_ADDR(DXGID3D10CreateDevice);
RESOLVE_ADDR(DXGID3D10CreateLayeredDevice);
RESOLVE_ADDR(DXGID3D10GetLayeredDeviceSize);
RESOLVE_ADDR(DXGID3D10RegisterLayers);
RESOLVE_ADDR(DXGIDeclareAdapterRemovalSupport);
RESOLVE_ADDR(DXGIGetDebugInterface1);
RESOLVE_ADDR(DXGIReportAdapterConfiguration);
}
};
version_init init {};
dxgi_init dx_init{};
}

185
src/dllproxy/version.asm Normal file
View File

@@ -0,0 +1,185 @@
.code
extern GetFileVersionInfoA_Original:QWORD
extern GetFileVersionInfoByHandle_Original:QWORD
extern GetFileVersionInfoExA_Original:QWORD
extern GetFileVersionInfoExW_Original:QWORD
extern GetFileVersionInfoSizeA_Original:QWORD
extern GetFileVersionInfoSizeExA_Original:QWORD
extern GetFileVersionInfoSizeExW_Original:QWORD
extern GetFileVersionInfoSizeW_Original:QWORD
extern GetFileVersionInfoW_Original:QWORD
extern VerFindFileA_Original:QWORD
extern VerFindFileW_Original:QWORD
extern VerInstallFileA_Original:QWORD
extern VerInstallFileW_Original:QWORD
extern VerLanguageNameA_Original:QWORD
extern VerLanguageNameW_Original:QWORD
extern VerQueryValueA_Original:QWORD
extern VerQueryValueW_Original:QWORD
extern ApplyCompatResolutionQuirking_Original:QWORD
extern CompatString_Original:QWORD
extern CompatValue_Original:QWORD
extern DXGIDumpJournal_Original:QWORD
extern PIXBeginCapture_Original:QWORD
extern PIXEndCapture_Original:QWORD
extern PIXGetCaptureState_Original:QWORD
extern SetAppCompatStringPointer_Original:QWORD
extern UpdateHMDEmulationStatus_Original:QWORD
extern CreateDXGIFactory_Original:QWORD
extern CreateDXGIFactory1_Original:QWORD
extern CreateDXGIFactory2_Original:QWORD
extern DXGID3D10CreateDevice_Original:QWORD
extern DXGID3D10CreateLayeredDevice_Original:QWORD
extern DXGID3D10GetLayeredDeviceSize_Original:QWORD
extern DXGID3D10RegisterLayers_Original:QWORD
extern DXGIDeclareAdapterRemovalSupport_Original:QWORD
extern DXGIGetDebugInterface1_Original:QWORD
extern DXGIReportAdapterConfiguration_Original:QWORD
GetFileVersionInfoA_EXPORT proc
jmp QWORD ptr GetFileVersionInfoA_Original
GetFileVersionInfoA_EXPORT endp
GetFileVersionInfoByHandle_EXPORT proc
jmp QWORD ptr GetFileVersionInfoByHandle_Original
GetFileVersionInfoByHandle_EXPORT endp
GetFileVersionInfoExA_EXPORT proc
jmp QWORD ptr GetFileVersionInfoExA_Original
GetFileVersionInfoExA_EXPORT endp
GetFileVersionInfoExW_EXPORT proc
jmp QWORD ptr GetFileVersionInfoExW_Original
GetFileVersionInfoExW_EXPORT endp
GetFileVersionInfoSizeA_EXPORT proc
jmp QWORD ptr GetFileVersionInfoSizeA_Original
GetFileVersionInfoSizeA_EXPORT endp
GetFileVersionInfoSizeExA_EXPORT proc
jmp QWORD ptr GetFileVersionInfoSizeExA_Original
GetFileVersionInfoSizeExA_EXPORT endp
GetFileVersionInfoSizeExW_EXPORT proc
jmp QWORD ptr GetFileVersionInfoSizeExW_Original
GetFileVersionInfoSizeExW_EXPORT endp
GetFileVersionInfoSizeW_EXPORT proc
jmp QWORD ptr GetFileVersionInfoSizeW_Original
GetFileVersionInfoSizeW_EXPORT endp
GetFileVersionInfoW_EXPORT proc
jmp QWORD ptr GetFileVersionInfoW_Original
GetFileVersionInfoW_EXPORT endp
VerFindFileA_EXPORT proc
jmp QWORD ptr VerFindFileA_Original
VerFindFileA_EXPORT endp
VerFindFileW_EXPORT proc
jmp QWORD ptr VerFindFileW_Original
VerFindFileW_EXPORT endp
VerInstallFileA_EXPORT proc
jmp QWORD ptr VerInstallFileA_Original
VerInstallFileA_EXPORT endp
VerInstallFileW_EXPORT proc
jmp QWORD ptr VerInstallFileW_Original
VerInstallFileW_EXPORT endp
VerLanguageNameA_EXPORT proc
jmp QWORD ptr VerLanguageNameA_Original
VerLanguageNameA_EXPORT endp
VerLanguageNameW_EXPORT proc
jmp QWORD ptr VerLanguageNameW_Original
VerLanguageNameW_EXPORT endp
VerQueryValueA_EXPORT proc
jmp QWORD ptr VerQueryValueA_Original
VerQueryValueA_EXPORT endp
VerQueryValueW_EXPORT proc
jmp QWORD ptr VerQueryValueW_Original
VerQueryValueW_EXPORT endp
ApplyCompatResolutionQuirking_EXPORT proc
jmp QWORD ptr ApplyCompatResolutionQuirking_Original
ApplyCompatResolutionQuirking_EXPORT endp
CompatString_EXPORT proc
jmp QWORD ptr CompatString_Original
CompatString_EXPORT endp
CompatValue_EXPORT proc
jmp QWORD ptr CompatValue_Original
CompatValue_EXPORT endp
DXGIDumpJournal_EXPORT proc
jmp QWORD ptr DXGIDumpJournal_Original
DXGIDumpJournal_EXPORT endp
PIXBeginCapture_EXPORT proc
jmp QWORD ptr PIXBeginCapture_Original
PIXBeginCapture_EXPORT endp
PIXEndCapture_EXPORT proc
jmp QWORD ptr PIXEndCapture_Original
PIXEndCapture_EXPORT endp
PIXGetCaptureState_EXPORT proc
jmp QWORD ptr PIXGetCaptureState_Original
PIXGetCaptureState_EXPORT endp
SetAppCompatStringPointer_EXPORT proc
jmp QWORD ptr SetAppCompatStringPointer_Original
SetAppCompatStringPointer_EXPORT endp
UpdateHMDEmulationStatus_EXPORT proc
jmp QWORD ptr UpdateHMDEmulationStatus_Original
UpdateHMDEmulationStatus_EXPORT endp
CreateDXGIFactory_EXPORT proc
jmp QWORD ptr CreateDXGIFactory_Original
CreateDXGIFactory_EXPORT endp
CreateDXGIFactory1_EXPORT proc
jmp QWORD ptr CreateDXGIFactory1_Original
CreateDXGIFactory1_EXPORT endp
CreateDXGIFactory2_EXPORT proc
jmp QWORD ptr CreateDXGIFactory2_Original
CreateDXGIFactory2_EXPORT endp
DXGID3D10CreateDevice_EXPORT proc
jmp QWORD ptr DXGID3D10CreateDevice_Original
DXGID3D10CreateDevice_EXPORT endp
DXGID3D10CreateLayeredDevice_EXPORT proc
jmp QWORD ptr DXGID3D10CreateLayeredDevice_Original
DXGID3D10CreateLayeredDevice_EXPORT endp
DXGID3D10GetLayeredDeviceSize_EXPORT proc
jmp QWORD ptr DXGID3D10GetLayeredDeviceSize_Original
DXGID3D10GetLayeredDeviceSize_EXPORT endp
DXGID3D10RegisterLayers_EXPORT proc
jmp QWORD ptr DXGID3D10RegisterLayers_Original
DXGID3D10RegisterLayers_EXPORT endp
DXGIDeclareAdapterRemovalSupport_EXPORT proc
jmp QWORD ptr DXGIDeclareAdapterRemovalSupport_Original
DXGIDeclareAdapterRemovalSupport_EXPORT endp
DXGIGetDebugInterface1_EXPORT proc
jmp QWORD ptr DXGIGetDebugInterface1_Original
DXGIGetDebugInterface1_EXPORT endp
DXGIReportAdapterConfiguration_EXPORT proc
jmp QWORD ptr DXGIReportAdapterConfiguration_Original
DXGIReportAdapterConfiguration_EXPORT endp
end

40
src/dllproxy/version.def Normal file
View File

@@ -0,0 +1,40 @@
EXPORTS
; version.dll
GetFileVersionInfoA=GetFileVersionInfoA_EXPORT
GetFileVersionInfoByHandle=GetFileVersionInfoByHandle_EXPORT
GetFileVersionInfoExA=GetFileVersionInfoExA_EXPORT
GetFileVersionInfoExW=GetFileVersionInfoExW_EXPORT
GetFileVersionInfoSizeA=GetFileVersionInfoSizeA_EXPORT
GetFileVersionInfoSizeExA=GetFileVersionInfoSizeExA_EXPORT
GetFileVersionInfoSizeExW=GetFileVersionInfoSizeExW_EXPORT
GetFileVersionInfoSizeW=GetFileVersionInfoSizeW_EXPORT
GetFileVersionInfoW=GetFileVersionInfoW_EXPORT
VerFindFileA=VerFindFileA_EXPORT
VerFindFileW=VerFindFileW_EXPORT
VerInstallFileA=VerInstallFileA_EXPORT
VerInstallFileW=VerInstallFileW_EXPORT
VerLanguageNameA=VerLanguageNameA_EXPORT
VerLanguageNameW=VerLanguageNameW_EXPORT
VerQueryValueA=VerQueryValueA_EXPORT
VerQueryValueW=VerQueryValueW_EXPORT
;dxgi.dll
ApplyCompatResolutionQuirking=ApplyCompatResolutionQuirking_EXPORT
CompatString=CompatString_EXPORT
CompatValue=CompatValue_EXPORT
DXGIDumpJournal=DXGIDumpJournal_EXPORT
PIXBeginCapture=PIXBeginCapture_EXPORT
PIXEndCapture=PIXEndCapture_EXPORT
PIXGetCaptureState=PIXGetCaptureState_EXPORT
SetAppCompatStringPointer=SetAppCompatStringPointer_EXPORT
UpdateHMDEmulationStatus=UpdateHMDEmulationStatus_EXPORT
CreateDXGIFactory=CreateDXGIFactory_EXPORT
CreateDXGIFactory1=CreateDXGIFactory1_EXPORT
CreateDXGIFactory2=CreateDXGIFactory2_EXPORT
DXGID3D10CreateDevice=DXGID3D10CreateDevice_EXPORT
DXGID3D10CreateLayeredDevice=DXGID3D10CreateLayeredDevice_EXPORT
DXGID3D10GetLayeredDeviceSize=DXGID3D10GetLayeredDeviceSize_EXPORT
DXGID3D10RegisterLayers=DXGID3D10RegisterLayers_EXPORT
DXGIDeclareAdapterRemovalSupport=DXGIDeclareAdapterRemovalSupport_EXPORT
DXGIGetDebugInterface1=DXGIGetDebugInterface1_EXPORT
DXGIReportAdapterConfiguration=DXGIReportAdapterConfiguration_EXPORT

View File

@@ -0,0 +1,179 @@
#include <stdinclude.hpp>
il2cpp_string_new_utf16_t il2cpp_string_new_utf16;
il2cpp_string_new_t il2cpp_string_new;
il2cpp_domain_get_t il2cpp_domain_get;
il2cpp_domain_assembly_open_t il2cpp_domain_assembly_open;
il2cpp_assembly_get_image_t il2cpp_assembly_get_image;
il2cpp_class_from_name_t il2cpp_class_from_name;
il2cpp_class_get_methods_t il2cpp_class_get_methods;
il2cpp_class_get_method_from_name_t il2cpp_class_get_method_from_name;
il2cpp_method_get_param_t il2cpp_method_get_param;
il2cpp_method_get_name_t il2cpp_method_get_name;
il2cpp_object_new_t il2cpp_object_new;
il2cpp_resolve_icall_t il2cpp_resolve_icall;
il2cpp_array_new_t il2cpp_array_new;
il2cpp_thread_attach_t il2cpp_thread_attach;
il2cpp_thread_detach_t il2cpp_thread_detach;
il2cpp_class_get_field_from_name_t il2cpp_class_get_field_from_name;
il2cpp_class_is_assignable_from_t il2cpp_class_is_assignable_from;
il2cpp_class_for_each_t il2cpp_class_for_each;
il2cpp_class_get_nested_types_t il2cpp_class_get_nested_types;
il2cpp_class_get_type_t il2cpp_class_get_type;
il2cpp_type_get_object_t il2cpp_type_get_object;
il2cpp_gchandle_new_t il2cpp_gchandle_new;
il2cpp_gchandle_free_t il2cpp_gchandle_free;
il2cpp_gchandle_get_target_t il2cpp_gchandle_get_target;
il2cpp_class_from_type_t il2cpp_class_from_type;
il2cpp_runtime_class_init_t il2cpp_runtime_class_init;
il2cpp_runtime_invoke_t il2cpp_runtime_invoke;
il2cpp_class_from_system_type_t il2cpp_class_from_system_type;
char* il2cpp_array_addr_with_size(void* array, int32_t size, uintptr_t idx)
{
return ((char*)array) + kIl2CppSizeOfArray + size * idx;
}
namespace il2cpp_symbols
{
#define RESOLVE_IMPORT(name) name = reinterpret_cast<name##_t>(GetProcAddress(game_module, #name))
void* il2cpp_domain = nullptr;
void init(HMODULE game_module)
{
RESOLVE_IMPORT(il2cpp_string_new_utf16);
RESOLVE_IMPORT(il2cpp_string_new);
RESOLVE_IMPORT(il2cpp_domain_get);
RESOLVE_IMPORT(il2cpp_domain_assembly_open);
RESOLVE_IMPORT(il2cpp_assembly_get_image);
RESOLVE_IMPORT(il2cpp_class_from_name);
RESOLVE_IMPORT(il2cpp_class_get_methods);
RESOLVE_IMPORT(il2cpp_class_get_method_from_name);
RESOLVE_IMPORT(il2cpp_method_get_param);
RESOLVE_IMPORT(il2cpp_object_new);
RESOLVE_IMPORT(il2cpp_resolve_icall);
RESOLVE_IMPORT(il2cpp_array_new);
RESOLVE_IMPORT(il2cpp_thread_attach);
RESOLVE_IMPORT(il2cpp_thread_detach);
RESOLVE_IMPORT(il2cpp_class_get_field_from_name);
RESOLVE_IMPORT(il2cpp_class_is_assignable_from);
RESOLVE_IMPORT(il2cpp_class_for_each);
RESOLVE_IMPORT(il2cpp_class_get_nested_types);
RESOLVE_IMPORT(il2cpp_class_get_type);
RESOLVE_IMPORT(il2cpp_type_get_object);
RESOLVE_IMPORT(il2cpp_gchandle_new);
RESOLVE_IMPORT(il2cpp_gchandle_free);
RESOLVE_IMPORT(il2cpp_gchandle_get_target);
RESOLVE_IMPORT(il2cpp_class_from_type);
RESOLVE_IMPORT(il2cpp_runtime_class_init);
RESOLVE_IMPORT(il2cpp_runtime_invoke);
RESOLVE_IMPORT(il2cpp_class_from_system_type);
RESOLVE_IMPORT(il2cpp_method_get_name);
printf("il2cpp_domain_get() at %p\n", il2cpp_domain_get);
il2cpp_domain = il2cpp_domain_get();
}
void* get_class(const char* assemblyName, const char* namespaze, const char* klassName)
{
auto assembly = il2cpp_domain_assembly_open(il2cpp_domain, assemblyName);
auto image = il2cpp_assembly_get_image(assembly);
return il2cpp_class_from_name(image, namespaze, klassName);
}
uintptr_t get_method_pointer(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name, int argsCount)
{
auto assembly = il2cpp_domain_assembly_open(il2cpp_domain, assemblyName);
if (!assembly) {
printf("assembly: %s not found!\n", assemblyName);
return NULL;
}
auto image = il2cpp_assembly_get_image(assembly);
auto klass = il2cpp_class_from_name(image, namespaze, klassName);
if (!klass) {
printf("class %s::%s not found!\n", namespaze, klassName);
return NULL;
}
auto ret = il2cpp_class_get_method_from_name(klass, name, argsCount);
if (ret) {
return ret->methodPointer;
}
else {
printf("\nError: method not found: %s - %s::%s.%s (%d)\n\n", assemblyName, namespaze, klassName, name, argsCount);
return NULL;
}
}
void* find_nested_class_from_name(void* klass, const char* name)
{
return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
});
}
MethodInfo* get_method(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name, int argsCount)
{
auto assembly = il2cpp_domain_assembly_open(il2cpp_domain, assemblyName);
auto image = il2cpp_assembly_get_image(assembly);
auto klass = il2cpp_class_from_name(image, namespaze, klassName);
return il2cpp_class_get_method_from_name(klass, name, argsCount);
}
uintptr_t find_method(const char* assemblyName, const char* namespaze,
const char* klassName, std::function<bool(const MethodInfo*)> predict)
{
auto assembly = il2cpp_domain_assembly_open(il2cpp_domain, assemblyName);
auto image = il2cpp_assembly_get_image(assembly);
auto klass = il2cpp_class_from_name(image, namespaze, klassName);
void* iter = nullptr;
while (const MethodInfo* method = il2cpp_class_get_methods(klass, &iter))
{
if (predict(method))
return method->methodPointer;
}
return 0;
}
FieldInfo* get_field(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name)
{
const auto assembly = il2cpp_domain_assembly_open(il2cpp_domain, assemblyName);
const auto image = il2cpp_assembly_get_image(assembly);
const auto klass = il2cpp_class_from_name(image, namespaze, klassName);
return il2cpp_class_get_field_from_name(klass, name);
}
void* get_class_from_instance(const void* instance)
{
return *static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance));
}
Il2CppString* NewWStr(std::wstring_view str)
{
return il2cpp_string_new_utf16(str.data(), str.size());
}
void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName) {
static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
il2cpp_symbols::get_method_pointer("mscorlib.dll", "System.Reflection",
"Assembly", "Load", 1)
);
static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
il2cpp_symbols::get_method_pointer("mscorlib.dll", "System.Reflection",
"Assembly", "GetType", 1)
);
static auto reflectionAssembly = assemblyLoad(il2cpp_string_new(assemblyName));
auto reflectionType = assemblyGetType(reflectionAssembly, il2cpp_string_new(typeStr));
return il2cpp_class_from_system_type(reflectionType);
}
}

View File

@@ -0,0 +1,549 @@
#pragma once
#include <concepts>
// UnityEngine.Color
struct Color_t
{
public:
// System.Single UnityEngine.Color::r
float r;
// System.Single UnityEngine.Color::g
float g;
// System.Single UnityEngine.Color::b
float b;
// System.Single UnityEngine.Color::a
float a;
};
// UnityEngine.Vector2
struct Vector2_t
{
public:
// System.Single UnityEngine.Vector2::x
float x;
// System.Single UnityEngine.Vector2::y
float y;
};
// UnityEngine.Vector3
struct Vector3_t
{
public:
// System.Single UnityEngine.Vector3::x
float x;
// System.Single UnityEngine.Vector3::y
float y;
// System.Single UnityEngine.Vector3::z
float z;
};
struct Vector4_t {
float x;
float y;
float z;
float w;
};
// UnityEngine.Quaternion
struct Quaternion_t
{
public:
float w;
float x;
float y;
float z;
};
struct Resolution_t
{
public:
int width;
int height;
int herz;
};
struct Vector2_Int_t {
int m_X;
int m_Y;
};
struct CsEnum_t {
public:
int value__;
};
// UnityEngine.TextGenerationSettings
struct TextGenerationSettings_t
{
public:
// UnityEngine.Font UnityEngine.TextGenerationSettings::font
void* font;
// UnityEngine.Color UnityEngine.TextGenerationSettings::color
Color_t color;
// System.Int32 UnityEngine.TextGenerationSettings::fontSize
int32_t fontSize;
// System.Single UnityEngine.TextGenerationSettings::lineSpacing
float lineSpacing;
// System.Boolean UnityEngine.TextGenerationSettings::richText
bool richText;
// System.Single UnityEngine.TextGenerationSettings::scaleFactor
float scaleFactor;
// UnityEngine.FontStyle UnityEngine.TextGenerationSettings::fontStyle
int32_t fontStyle;
// UnityEngine.TextAnchor UnityEngine.TextGenerationSettings::textAnchor
int32_t textAnchor;
// System.Boolean UnityEngine.TextGenerationSettings::alignByGeometry
bool alignByGeometry;
// System.Boolean UnityEngine.TextGenerationSettings::resizeTextForBestFit
bool resizeTextForBestFit;
// System.Int32 UnityEngine.TextGenerationSettings::resizeTextMinSize
int32_t resizeTextMinSize;
// System.Int32 UnityEngine.TextGenerationSettings::resizeTextMaxSize
int32_t resizeTextMaxSize;
// System.Boolean UnityEngine.TextGenerationSettings::updateBounds
bool updateBounds;
// UnityEngine.VerticalWrapMode UnityEngine.TextGenerationSettings::verticalOverflow
int32_t verticalOverflow;
// UnityEngine.HorizontalWrapMode UnityEngine.TextGenerationSettings::horizontalOverflow
int32_t horizontalOverflow;
// UnityEngine.Vector2 UnityEngine.TextGenerationSettings::generationExtents
Vector2_t generationExtents;
// UnityEngine.Vector2 UnityEngine.TextGenerationSettings::pivot
Vector2_t pivot;
// System.Boolean UnityEngine.TextGenerationSettings::generateOutOfBounds
bool generateOutOfBounds;
};
// not real Il2CppString class
struct Il2CppString
{
void* Empty;
void* WhiteChars;
int32_t length;
wchar_t start_char[1];
};
enum Il2CppTypeEnum
{
IL2CPP_TYPE_END = 0x00, /* End of List */
IL2CPP_TYPE_VOID = 0x01,
IL2CPP_TYPE_BOOLEAN = 0x02,
IL2CPP_TYPE_CHAR = 0x03,
IL2CPP_TYPE_I1 = 0x04,
IL2CPP_TYPE_U1 = 0x05,
IL2CPP_TYPE_I2 = 0x06,
IL2CPP_TYPE_U2 = 0x07,
IL2CPP_TYPE_I4 = 0x08,
IL2CPP_TYPE_U4 = 0x09,
IL2CPP_TYPE_I8 = 0x0a,
IL2CPP_TYPE_U8 = 0x0b,
IL2CPP_TYPE_R4 = 0x0c,
IL2CPP_TYPE_R8 = 0x0d,
IL2CPP_TYPE_STRING = 0x0e,
IL2CPP_TYPE_PTR = 0x0f,
IL2CPP_TYPE_BYREF = 0x10,
IL2CPP_TYPE_VALUETYPE = 0x11,
IL2CPP_TYPE_CLASS = 0x12,
IL2CPP_TYPE_VAR = 0x13,
IL2CPP_TYPE_ARRAY = 0x14,
IL2CPP_TYPE_GENERICINST = 0x15,
IL2CPP_TYPE_TYPEDBYREF = 0x16,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U = 0x19,
IL2CPP_TYPE_FNPTR = 0x1b,
IL2CPP_TYPE_OBJECT = 0x1c,
IL2CPP_TYPE_SZARRAY = 0x1d,
IL2CPP_TYPE_MVAR = 0x1e,
IL2CPP_TYPE_CMOD_REQD = 0x1f,
IL2CPP_TYPE_CMOD_OPT = 0x20,
IL2CPP_TYPE_INTERNAL = 0x21,
IL2CPP_TYPE_MODIFIER = 0x40,
IL2CPP_TYPE_SENTINEL = 0x41,
IL2CPP_TYPE_PINNED = 0x45,
IL2CPP_TYPE_ENUM = 0x55
};
typedef struct Il2CppType
{
void* dummy;
unsigned int attrs : 16;
Il2CppTypeEnum type : 8;
unsigned int num_mods : 6;
unsigned int byref : 1;
unsigned int pinned : 1;
} Il2CppType;
struct ParameterInfo
{
const char* name;
int32_t position;
uint32_t token;
const Il2CppType* parameter_type;
};
struct MethodInfo
{
uintptr_t methodPointer;
uintptr_t invoker_method;
const char* name;
uintptr_t klass;
const Il2CppType* return_type;
const ParameterInfo* parameters;
uintptr_t methodDefinition;
uintptr_t genericContainer;
uint32_t token;
uint16_t flags;
uint16_t iflags;
uint16_t slot;
uint8_t parameters_count;
uint8_t is_generic : 1;
uint8_t is_inflated : 1;
uint8_t wrapper_type : 1;
uint8_t is_marshaled_from_native : 1;
};
struct FieldInfo
{
const char* name;
const Il2CppType* type;
uintptr_t parent;
int32_t offset;
uint32_t token;
};
template <typename T>
struct TypedField
{
FieldInfo* Field;
constexpr FieldInfo* operator->() const noexcept
{
return Field;
}
};
struct Il2CppObject
{
union
{
void* klass;
void* vtable;
};
void* monitor;
};
typedef struct Il2CppArraySize
{
Il2CppObject obj;
void* bounds;
uintptr_t max_length;
alignas(8)
void* vector[0];
} Il2CppArraySize;
typedef struct Il2CppException
{
Il2CppObject object;
Il2CppString* className;
Il2CppString* message;
Il2CppObject* _data;
Il2CppException* inner_ex;
Il2CppString* _helpURL;
Il2CppArraySize* trace_ips;
Il2CppString* stack_trace;
Il2CppString* remote_stack_trace;
int remote_stack_index;
Il2CppObject* _dynamicMethods;
int32_t hresult;
Il2CppString* source;
Il2CppObject* safeSerializationManager;
Il2CppArraySize* captured_traces;
Il2CppArraySize* native_trace_ips;
} Il2CppException;
template<typename T>
struct Il2CppArraySize_t
{
Il2CppObject obj;
void* bounds;
uintptr_t max_length;
alignas(8)
T vector[0];
};
struct Il2CppClassHead
{
const void* image;
void* gc_desc;
const char* name;
const char* namespaze;
};
struct Il2CppReflectionType
{
Il2CppObject object;
const Il2CppType* type;
};
struct Rect_t
{
public:
float x;
float y;
float width;
float height;
};
struct Object {
union {
void* klass{ nullptr };
void* vtable;
} Il2CppClass;
struct MonitorData* monitor{ nullptr };
};
template <typename T>
struct Array : Object {
struct {
std::uintptr_t length;
std::int32_t lower_bound;
}*bounds{ nullptr };
std::uintptr_t max_length{ 0 };
__declspec(align(8)) T** vector {};
auto GetData() -> uintptr_t { return reinterpret_cast<uintptr_t>(&vector); }
auto operator[](const unsigned int m_uIndex) -> T& { return *reinterpret_cast<T*>(GetData() + sizeof(T) * m_uIndex); }
auto At(const unsigned int m_uIndex) -> T& { return operator[](m_uIndex); }
auto ToVector() -> std::vector<T> {
if (IsBadReadPtr(this, sizeof(Array))) return {};
if (!this) return {};
try {
std::vector<T> rs{};
rs.reserve(this->max_length);
for (auto i = 0; i < this->max_length; i++) rs.push_back(this->At(i));
return rs;
}
catch (...) {
std::cout << "Array Invoke Error\n";
return {};
}
}
auto RemoveAt(const unsigned int m_uIndex) -> void {
if (m_uIndex >= max_length) return;
if (max_length > (m_uIndex + 1)) for (auto u = m_uIndex; (max_length - m_uIndex) > u; ++u) operator[](u) = operator[](u + 1);
--max_length;
}
auto Insert(T* m_pArray, uintptr_t m_uSize, const uintptr_t m_uIndex = 0) -> void {
if ((m_uSize + m_uIndex) >= max_length) {
if (m_uIndex >= max_length) return;
m_uSize = max_length - m_uIndex;
}
for (uintptr_t u = 0; m_uSize > u; ++u) operator[](u + m_uIndex) = m_pArray[u];
}
auto RemoveRange(const unsigned int m_uIndex, unsigned int m_uCount) -> void {
if (m_uCount == 0) m_uCount = 1;
const auto m_uTotal = m_uIndex + m_uCount;
if (m_uTotal >= max_length) return;
if (max_length > (m_uTotal + 1)) for (auto u = m_uIndex; (max_length - m_uTotal) >= u; ++u) operator[](u) = operator[](u + m_uCount);
max_length -= m_uCount;
}
};
static const size_t kIl2CppSizeOfArray = (offsetof(Il2CppArraySize, vector));
// function types
typedef Il2CppString* (*il2cpp_string_new_utf16_t)(const wchar_t* str, unsigned int len);
typedef Il2CppString* (*il2cpp_string_new_t)(const char* str);
typedef void* (*il2cpp_domain_get_t)();
typedef void* (*il2cpp_domain_assembly_open_t)(void* domain, const char* name);
typedef void* (*il2cpp_assembly_get_image_t)(void* assembly);
typedef void* (*il2cpp_class_from_name_t)(void* image, const char* namespaze, const char* name);
typedef MethodInfo* (*il2cpp_class_get_methods_t)(void* klass, void** iter);
typedef MethodInfo* (*il2cpp_class_get_method_from_name_t)(void* klass, const char* name, int argsCount);
typedef void* (*il2cpp_method_get_param_t)(const MethodInfo* method, uint32_t index);
typedef void* (*il2cpp_object_new_t)(void* klass);
typedef void* (*il2cpp_resolve_icall_t)(const char* name);
typedef void* (*il2cpp_array_new_t)(void* klass, uintptr_t count);
typedef void* (*il2cpp_thread_attach_t)(void* domain);
typedef void (*il2cpp_thread_detach_t)(void* thread);
typedef FieldInfo* (*il2cpp_class_get_field_from_name_t)(void* klass, const char* name);
typedef bool (*il2cpp_class_is_assignable_from_t)(void* klass, void* oklass);
typedef void (*il2cpp_class_for_each_t)(void(*klassReportFunc)(void* klass, void* userData), void* userData);
typedef void* (*il2cpp_class_get_nested_types_t)(void* klass, void** iter);
typedef void* (*il2cpp_class_get_type_t)(void* klass);
typedef Il2CppReflectionType* (*il2cpp_type_get_object_t)(const void* type);
typedef uint32_t (*il2cpp_gchandle_new_t)(void* obj, bool pinned);
typedef void (*il2cpp_gchandle_free_t)(uint32_t gchandle);
typedef void* (*il2cpp_gchandle_get_target_t)(uint32_t gchandle);
typedef void* (*il2cpp_class_from_type_t)(const Il2CppType* type);
typedef void (*il2cpp_runtime_class_init_t)(void* klass);
typedef void* (*il2cpp_runtime_invoke_t)(MethodInfo* method, void* obj, void** params, Il2CppObject** exc);
typedef void* (*il2cpp_class_from_system_type_t)(Il2CppReflectionType* type);
typedef char* (*il2cpp_method_get_name_t)(void* mtd);
// function defines
extern il2cpp_string_new_utf16_t il2cpp_string_new_utf16;
extern il2cpp_string_new_t il2cpp_string_new;
extern il2cpp_domain_get_t il2cpp_domain_get;
extern il2cpp_domain_assembly_open_t il2cpp_domain_assembly_open;
extern il2cpp_assembly_get_image_t il2cpp_assembly_get_image;
extern il2cpp_class_from_name_t il2cpp_class_from_name;
extern il2cpp_class_get_methods_t il2cpp_class_get_methods;
extern il2cpp_class_get_method_from_name_t il2cpp_class_get_method_from_name;
extern il2cpp_method_get_param_t il2cpp_method_get_param;
extern il2cpp_object_new_t il2cpp_object_new;
extern il2cpp_resolve_icall_t il2cpp_resolve_icall;
extern il2cpp_array_new_t il2cpp_array_new;
extern il2cpp_thread_attach_t il2cpp_thread_attach;
extern il2cpp_thread_detach_t il2cpp_thread_detach;
extern il2cpp_class_get_field_from_name_t il2cpp_class_get_field_from_name;
extern il2cpp_class_is_assignable_from_t il2cpp_class_is_assignable_from;
extern il2cpp_class_for_each_t il2cpp_class_for_each;
extern il2cpp_class_get_nested_types_t il2cpp_class_get_nested_types;
extern il2cpp_class_get_type_t il2cpp_class_get_type;
extern il2cpp_type_get_object_t il2cpp_type_get_object;
extern il2cpp_gchandle_new_t il2cpp_gchandle_new;
extern il2cpp_gchandle_free_t il2cpp_gchandle_free;
extern il2cpp_gchandle_get_target_t il2cpp_gchandle_get_target;
extern il2cpp_class_from_type_t il2cpp_class_from_type;
extern il2cpp_runtime_class_init_t il2cpp_runtime_class_init;
extern il2cpp_runtime_invoke_t il2cpp_runtime_invoke;
extern il2cpp_class_from_system_type_t il2cpp_class_from_system_type;
extern il2cpp_method_get_name_t il2cpp_method_get_name;
char* il2cpp_array_addr_with_size(void* arr, int32_t size, uintptr_t idx);
// array macro
#define il2cpp_array_addr(array, type, index) ((type*)(void*) il2cpp_array_addr_with_size (array, sizeof (type), index))
#define il2cpp_array_setref(array, index, value) \
do { \
void* *__p = (void* *) il2cpp_array_addr ((array), void*, (index)); \
*__p = (value); \
} while (0)
namespace il2cpp_symbols
{
void init(HMODULE game_module);
uintptr_t get_method_pointer(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name, int argsCount);
void* get_class(const char* assemblyName, const char* namespaze, const char* klassName);
void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate)
{
void* iter{};
while (const auto curNestedClass = il2cpp_class_get_nested_types(klass, &iter))
{
if (static_cast<decltype(predicate)>(predicate)(curNestedClass))
{
return curNestedClass;
}
}
return nullptr;
}
void* find_nested_class_from_name(void* klass, const char* name);
MethodInfo* get_method(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name, int argsCount);
uintptr_t find_method(const char* assemblyName, const char* namespaze,
const char* klassName, std::function<bool(const MethodInfo*)> predict);
FieldInfo* get_field(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name);
template <typename T>
TypedField<T> get_field(const char* assemblyName, const char* namespaze,
const char* klassName, const char* name)
{
return { get_field(assemblyName, namespaze, klassName, name) };
}
void* get_class_from_instance(const void* instance);
template <typename T = void*> requires std::is_trivial_v<T>
T read_field(const void* ptr, const FieldInfo* field)
{
T result;
const auto fieldPtr = static_cast<const std::byte*>(ptr) + field->offset;
std::memcpy(std::addressof(result), fieldPtr, sizeof(T));
return result;
}
template <typename T>
T read_field(const void* ptr, TypedField<T> field)
{
return read_field<T>(ptr, field.Field);
}
template <typename T> requires std::is_trivial_v<T>
void write_field(void* ptr, const FieldInfo* field, const T& value)
{
const auto fieldPtr = static_cast<std::byte*>(ptr) + field->offset;
std::memcpy(fieldPtr, std::addressof(value), sizeof(T));
}
template <typename T, typename U>
void write_field(void* ptr, TypedField<T> field, U&& value)
{
write_field<T>(ptr, field.Field, static_cast<T>(std::forward<U>(value)));
}
template <typename T = void*>
void iterate_list(const void* list, std::invocable<int32_t, T> auto&& receiver)
{
const auto listClass = get_class_from_instance(list);
const auto getItemMethod = reinterpret_cast<T(*)(const void*, int32_t)>(il2cpp_class_get_method_from_name(listClass, "get_Item", 1)->methodPointer);
const auto getCountMethod = reinterpret_cast<int32_t(*)(const void*)>(il2cpp_class_get_method_from_name(listClass, "get_Count", 0)->methodPointer);
const auto count = getCountMethod(list);
for (int32_t i = 0; i < count; ++i)
{
static_cast<decltype(receiver)>(receiver)(i, getItemMethod(list, i));
}
}
template <typename T = void*>
void iterate_IEnumerable(const void* obj, std::invocable<T> auto&& receiver)
{
const auto klass = get_class_from_instance(obj);
const auto getEnumeratorMethod = reinterpret_cast<void* (*)(const void*)>(il2cpp_class_get_method_from_name(klass, "GetEnumerator", 0)->methodPointer);
const auto enumerator = getEnumeratorMethod(obj);
const auto enumeratorClass = get_class_from_instance(enumerator);
const auto getCurrentMethod = reinterpret_cast<T (*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer);
const auto moveNextMethod = reinterpret_cast<bool(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer);
while (moveNextMethod(enumerator))
{
static_cast<decltype(receiver)>(receiver)(getCurrentMethod(enumerator));
}
}
Il2CppString* NewWStr(std::wstring_view str);
void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib");
}

151
src/main.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include <stdinclude.hpp>
#include <minizip/unzip.h>
#include <TlHelp32.h>
#include <unordered_set>
#include <charconv>
#include <cassert>
#include <format>
#include <cpprest/uri.h>
#include <cpprest/http_listener.h>
#include <ranges>
#include <windows.h>
#include "windowsPlatform.hpp"
extern void start_console();
const auto CONSOLE_TITLE = L"Gakumas Localify";
std::filesystem::path gakumasLocalPath = "./gakumas-local";
std::filesystem::path ProgramConfigJson = gakumasLocalPath / "config.json";
std::filesystem::path ConfigJson = gakumasLocalPath / "localizationConfig.json";
bool g_has_config_file = false;
bool g_enable_console = true;
bool g_confirm_every_times = false;
std::vector<std::string> g_pluginPath{};
namespace
{
void create_debug_console()
{
AllocConsole();
// open stdout stream
auto _ = freopen("CONOUT$", "w+t", stdout);
_ = freopen("CONOUT$", "w", stderr);
_ = freopen("CONIN$", "r", stdin);
SetConsoleTitleW(CONSOLE_TITLE);
// set this to avoid turn japanese texts into question mark
SetConsoleOutputCP(65001);
std::locale::global(std::locale(""));
wprintf(L"%ls Loaded! - By chinosk\n", CONSOLE_TITLE);
}
}
namespace
{
void readProgramConfig()
{
std::vector<std::string> dicts{};
std::ifstream config_stream{ ProgramConfigJson };
if (!config_stream.is_open())
return;
rapidjson::IStreamWrapper wrapper{ config_stream };
rapidjson::Document document;
document.ParseStream(wrapper);
if (!document.HasParseError())
{
g_has_config_file = true;
if (document.HasMember("enableConsole")) {
g_enable_console = document["enableConsole"].GetBool();
}
}
config_stream.close();
}
}
LONG WINAPI AddressExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) {
// Check if the exception is an access violation
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// Check if the violation address is above 0x7FFFFFFFFFFF
if (ExceptionInfo->ExceptionRecord->ExceptionAddress > (PVOID)0x7FFFFFFFFFFF) {
// Return EXCEPTION_CONTINUE_EXECUTION to ignore the exception and continue
return EXCEPTION_CONTINUE_EXECUTION;
}
}
// For other exceptions or addresses, use the default exception handler
return EXCEPTION_CONTINUE_SEARCH;
}
int __stdcall DllMain(HINSTANCE dllModule, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
AddVectoredExceptionHandler(1, AddressExceptionHandler);
// the DMM Launcher set start path to system32 wtf????
std::string module_name;
module_name.resize(MAX_PATH);
module_name.resize(GetModuleFileName(nullptr, module_name.data(), MAX_PATH));
std::filesystem::path module_path(module_name);
// check name
if (module_path.filename() != "gakumas.exe")
return 1;
std::filesystem::current_path(
module_path.parent_path()
);
readProgramConfig();
if (g_enable_console)
create_debug_console();
std::thread init_thread([] {
if (g_enable_console)
{
start_console();
printf("Command: %s\n", GetCommandLineA());
}
loadConfig(ConfigJson);
initHook();
std::mutex mutex;
std::condition_variable cond;
std::atomic<bool> hookIsReady(false);
// 依赖检查游戏版本的指针加载,因此在 hook 完成后再加载翻译数据
std::unique_lock lock(mutex);
cond.wait(lock, [&] {
return hookIsReady.load(std::memory_order_acquire);
});
if (g_enable_console)
{
auto _ = freopen("CONOUT$", "w+t", stdout);
_ = freopen("CONOUT$", "w", stderr);
_ = freopen("CONIN$", "r", stdin);
}
});
init_thread.detach();
}
else if (reason == DLL_PROCESS_DETACH)
{
RemoveVectoredExceptionHandler(AddressExceptionHandler);
unInitHook();
}
return 1;
}

43
src/platformDefine.hpp Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <minhook.h>
#include <vector>
#define ANDROID_LOG_VERBOSE 0
#define ANDROID_LOG_DEBUG 1
#define ANDROID_LOG_INFO 2
#define ANDROID_LOG_ERROR 3
#define GKMS_WINDOWS
#define LogMinVersion ANDROID_LOG_DEBUG
#define ADD_HOOK(name, addr) \
name##_Addr = reinterpret_cast<name##_Type>(addr); \
if (addr) { \
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
reinterpret_cast<void*>(name##_Hook), \
reinterpret_cast<void**>(&name##_Orig)); \
if (stub) { \
Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, \
MH_StatusToString(static_cast<MH_STATUS>(reinterpret_cast<int>(stub)))); \
} \
else { \
hookedStubs.emplace(stub); \
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
} \
} \
else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr); \
if (Config::lazyInit) UnityResolveProgress::classProgress.current++
static void __android_log_write(int prio, const char* tag, const char* msg) {
if (prio < LogMinVersion) return;
static const char* logLevels[4] = { "VERBOSE", "DEBUG", "INFO", "ERROR" };
if (prio < 0 || prio > 3) {
prio = 1;
}
printf("[%s] %s: %s\n", logLevels[prio], tag, msg);
}

43
src/stdinclude.hpp Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#define NOMINMAX
#include <Windows.h>
#include <shlobj.h>
#include <cinttypes>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <locale>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <map>
#include <thread>
#include <variant>
#include <exception>
#include <vector>
#include <regex>
#include <MinHook.h>
#include <rapidjson/document.h>
#include <rapidjson/encodings.h>
#include <rapidjson/istreamwrapper.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/ostreamwrapper.h>
#include <rapidjson/writer.h>
#include "il2cpp/il2cpp_symbols.hpp"
#include <nlohmann/json.hpp>
extern bool g_has_config_file;
extern bool g_enable_console;
extern bool g_confirm_every_times;
extern std::vector<std::string> g_pluginPath;

48
src/steam/steam.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include <stdinclude.hpp>
namespace
{
const char* SteamAPI_GetSteamInstallPath()
{
static std::string install_path {};
if (!install_path.empty())
{
return install_path.data();
}
HKEY reg_key;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\WOW6432Node\\Valve\\Steam", 0, KEY_QUERY_VALUE,
&reg_key) ==
ERROR_SUCCESS)
{
char path[MAX_PATH] = {0};
DWORD length = sizeof(path);
RegQueryValueExA(reg_key, "InstallPath", nullptr, nullptr, reinterpret_cast<BYTE*>(path),
&length);
RegCloseKey(reg_key);
install_path = path;
}
return install_path.data();
}
HMODULE steam_overlay_module, steam_client_module;
void load_client()
{
const std::filesystem::path steam_path = SteamAPI_GetSteamInstallPath();
if (steam_path.empty())
return;
LoadLibrary((steam_path / "tier0_s64.dll").string().data());
LoadLibrary((steam_path / "vstdlib_s64.dll").string().data());
steam_overlay_module = LoadLibrary((steam_path / "gameoverlayrenderer64.dll").string().data());
steam_client_module = LoadLibrary((steam_path / "steamclient64.dll").string().data());
if (!steam_client_module)
return;
}
}

91
src/utils.hpp Normal file
View File

@@ -0,0 +1,91 @@
#pragma once
#include <stdinclude.hpp>
template <typename T = void*>
class CSListEditor {
public:
CSListEditor(void* list, void* klass) {
// list_klass = il2cpp_symbols::get_class_from_instance(list);
list_klass = klass;
lst = list;
lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
lst_ToArray_method = il2cpp_class_get_method_from_name(list_klass, "ToArray", 0);
lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
lst_ToArray = reinterpret_cast<lst_ToArray_t>(lst_ToArray_method->methodPointer);
}
void Add(T value) {
lst_Add(lst, value, lst_Add_method);
}
T get_Item(int index) {
return lst_get_Item(lst, index, lst_get_Item_method);
}
int get_Count() {
return lst_get_Count(lst, lst_get_Count_method);
}
T operator[] (int key) {
return get_Item(key);
}
Array<T>* ToArray() {
return static_cast<Array<T>*>(lst_ToArray(lst, lst_ToArray_method));
}
class Iterator {
public:
Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}
T operator*() const {
return editor->get_Item(index);
}
Iterator& operator++() {
++index;
return *this;
}
bool operator!=(const Iterator& other) const {
return index != other.index;
}
private:
CSListEditor<T>* editor;
int index;
};
Iterator begin() {
return Iterator(this, 0);
}
Iterator end() {
return Iterator(this, get_Count());
}
void* lst;
void* list_klass;
private:
typedef T(*lst_get_Item_t)(void*, int, void* mtd);
typedef void(*lst_Add_t)(void*, T, void* mtd);
typedef int(*lst_get_Count_t)(void*, void* mtd);
typedef void* (*lst_ToArray_t)(void*, void* mtd);
MethodInfo* lst_get_Item_method;
MethodInfo* lst_Add_method;
MethodInfo* lst_ToArray_method;
MethodInfo* lst_get_Count_method;
lst_get_Item_t lst_get_Item;
lst_Add_t lst_Add;
lst_get_Count_t lst_get_Count;
lst_ToArray_t lst_ToArray;
};

139
src/windowsPlatform.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "GakumasLocalify/Plugin.h"
#include "GakumasLocalify/Log.h"
#include "GakumasLocalify/Local.h"
#include "GakumasLocalify/camera/camera.hpp"
#include "GakumasLocalify/config/Config.hpp"
#include "GakumasLocalify/Il2cppUtils.hpp"
#include <stdinclude.hpp>
#include "deps/UnityResolve/UnityResolve.hpp"
#include <cpprest/details/basic_types.h>
#include <cpprest/details/http_helpers.h>
bool mh_inited;
extern std::filesystem::path gakumasLocalPath;
void patchGameAssenbly();
namespace
{
class AndroidHookInstaller : public GakumasLocal::HookInstaller
{
public:
explicit AndroidHookInstaller(const std::string& il2cppLibraryPath, const std::string& localizationFilesDir)
{
this->m_Il2CppLibrary = GetModuleHandle("GameAssembly.dll");
this->m_il2cppLibraryPath = il2cppLibraryPath;
this->localizationFilesDir = localizationFilesDir;
}
~AndroidHookInstaller() override {
if (!mh_inited) return;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}
void* InstallHook(void* addr, void* hook, void** orig) override
{
auto createStat = MH_CreateHook(addr, hook, orig);
MH_EnableHook(addr);
return reinterpret_cast<void*>(createStat);
}
GakumasLocal::OpaqueFunctionPointer LookupSymbol(const char* name) override
{
return reinterpret_cast<GakumasLocal::OpaqueFunctionPointer>(GetProcAddress(reinterpret_cast<HMODULE>(m_Il2CppLibrary), name));
}
private:
void* m_Il2CppLibrary;
};
}
void* load_library_w_orig;
HMODULE __stdcall load_library_w_hook(const wchar_t* path)
{
// GakumasLocal::Log::DebugFmt("LoadLibrary %s", utility::conversions::to_utf8string(path).c_str());
using namespace std;
if (path == L"cri_ware_unity.dll"sv) {
// if (path == L"NVUnityPlugin"sv) {
patchGameAssenbly();
}
return reinterpret_cast<decltype(LoadLibraryW)*>(load_library_w_orig)(path);
}
void patchGameAssenbly() {
static bool patched = false;
if (patched) return;
patched = true;
auto& plugin = GakumasLocal::Plugin::GetInstance();
plugin.InstallHook(std::make_unique<AndroidHookInstaller>("GameAssembly.dll", gakumasLocalPath.string()));
}
void initHook() {
mh_inited = true;
if (MH_Initialize() != MH_OK)
return;
auto stat1 = MH_CreateHook(LoadLibraryW, load_library_w_hook, &load_library_w_orig);
auto stat2 = MH_EnableHook(LoadLibraryW);
GakumasLocal::Log::InfoFmt("initHook stat: %s - %s\n", MH_StatusToString(stat1), MH_StatusToString(stat2));
}
void unInitHook() {
if (!mh_inited) return;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
}
void loadConfig(const std::string& configJson) {
GakumasLocal::Config::LoadConfig(configJson);
}
void loadConfig(const std::filesystem::path& filePath) {
std::ifstream file(filePath);
if (!file.is_open()) {
GakumasLocal::Log::ErrorFmt("Load config %s failed.\n", filePath.string().c_str());
loadConfig(std::string("{}"));
return;
}
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
loadConfig(fileContent);
}
bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal, int* currCurrent) {
*stepTotal = UnityResolveProgress::assembliesProgress.total;
*stepCurrent = UnityResolveProgress::assembliesProgress.current;
*currTotal = UnityResolveProgress::classProgress.total;
*currCurrent = UnityResolveProgress::classProgress.current;
return UnityResolveProgress::startInit;
}
namespace GakumasLocal::WinHooks {
using Il2cppString = UnityResolve::UnityType::String;
void* LoadAssetBundle(const std::string& path) {
Il2cppString* bundlePath = Il2cppString::New(path);
static auto LoadFromFileAsync = Il2cppUtils::GetMethod("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundle", "LoadFromFileAsync");
static auto get_assetBundle = Il2cppUtils::GetMethod("UnityEngine.AssetBundleModule.dll", "UnityEngine", "AssetBundleCreateRequest", "get_assetBundle");
auto bundleTask = LoadFromFileAsync->Invoke<void*>(bundlePath);
if (!bundleTask) {
return nullptr;
}
return get_assetBundle->Invoke<void*>(bundleTask);
}
}
// TODO keyboard events

18
src/windowsPlatform.hpp Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <string>
void initHook();
void unInitHook();
void loadConfig(const std::string& configJson);
void loadConfig(const std::filesystem::path& filePath);
bool getCurrentLodingProgress(int* stepTotal, int* stepCurrent, int* currTotal, int* currCurrent);
// TODO keyboard events
namespace GakumasLocal::WinHooks {
void* LoadAssetBundle(const std::string& path);
}