mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
Merge branch 'net5'
This commit is contained in:
commit
a78754ce22
100 changed files with 67306 additions and 923 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
|
@ -4,3 +4,9 @@
|
|||
[submodule "lib/FFXIVClientStructs"]
|
||||
path = lib/FFXIVClientStructs
|
||||
url = https://github.com/goatcorp/FFXIVClientStructs.git
|
||||
[submodule "lib/Nomade040-nmd"]
|
||||
path = lib/Nomade040-nmd
|
||||
url = https://github.com/Nomade040/nmd.git
|
||||
[submodule "lib/TsudaKageyu-minhook"]
|
||||
path = lib/TsudaKageyu-minhook
|
||||
url = https://github.com/TsudaKageyu/minhook.git
|
||||
|
|
|
|||
|
|
@ -25,15 +25,19 @@
|
|||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
||||
<IntDir>obj\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
|
|
@ -49,7 +53,7 @@
|
|||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<AdditionalDependencies>dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>dbghelp.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
@ -58,6 +62,8 @@
|
|||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">26812</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>false</EnableCOMDATFolding>
|
||||
|
|
@ -69,6 +75,8 @@
|
|||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">26812</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
|
|
@ -91,22 +99,54 @@
|
|||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\buffer.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\HDE\hde32.c">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\HDE\hde64.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\hook.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\trampoline.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DalamudStartInfo.cpp" />
|
||||
<ClCompile Include="hooks.cpp" />
|
||||
<ClCompile Include="logging.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="unicode.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="xivfixes.cpp" />
|
||||
<ClCompile Include="utils.cpp" />
|
||||
<ClCompile Include="pch_nmd_assembly_impl.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="rewrite_entrypoint.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="veh.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="veh.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\boot.h" />
|
||||
|
|
@ -114,8 +154,22 @@
|
|||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\include\MinHook.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\buffer.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\hde32.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\hde64.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\pstdint.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\table32.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\table64.h" />
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\trampoline.h" />
|
||||
<ClInclude Include="DalamudStartInfo.h" />
|
||||
<ClInclude Include="hooks.h" />
|
||||
<ClInclude Include="logging.h" />
|
||||
<ClInclude Include="unicode.h" />
|
||||
<ClInclude Include="utils.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="veh.h" />
|
||||
<ClInclude Include="xivfixes.h" />
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
|
|
|
|||
|
|
@ -8,14 +8,20 @@
|
|||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Project Files">
|
||||
<UniqueIdentifier>{0c915688-91ea-431f-8b68-845cad422a50}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Common Boot">
|
||||
<UniqueIdentifier>{e31f7ca0-db29-4198-8b91-bb11b339705f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="MinHook">
|
||||
<UniqueIdentifier>{6ec5597d-e293-4d2a-a307-7444c8fac04b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="veh.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -28,6 +34,45 @@
|
|||
<ClCompile Include="..\lib\CoreCLR\boot.cpp">
|
||||
<Filter>CoreCLR</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch_nmd_assembly_impl.cpp">
|
||||
<Filter>Project Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Project Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="utils.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="xivfixes.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="logging.cpp">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="unicode.cpp">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\buffer.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\HDE\hde32.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\HDE\hde64.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\hook.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\lib\TsudaKageyu-minhook\src\trampoline.c">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hooks.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DalamudStartInfo.cpp">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
|
|
@ -49,7 +94,49 @@
|
|||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Project Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="utils.h">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hooks.h">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DalamudStartInfo.h">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="xivfixes.h">
|
||||
<Filter>Dalamud.Boot DLL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="logging.h">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="unicode.h">
|
||||
<Filter>Common Boot</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\trampoline.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\buffer.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\hde32.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\hde64.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\include\MinHook.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\pstdint.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\table32.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\TsudaKageyu-minhook\src\HDE\table64.h">
|
||||
<Filter>MinHook</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
117
Dalamud.Boot/DalamudStartInfo.cpp
Normal file
117
Dalamud.Boot/DalamudStartInfo.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#include "pch.h"
|
||||
#include "DalamudStartInfo.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
DalamudStartInfo g_startInfo;
|
||||
|
||||
void from_json(const nlohmann::json& json, DalamudStartInfo::WaitMessageboxFlags& value) {
|
||||
if (json.is_number_integer()) {
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(json.get<int>());
|
||||
|
||||
} else if (json.is_array()) {
|
||||
value = DalamudStartInfo::WaitMessageboxFlags::None;
|
||||
for (const auto& item : json) {
|
||||
if (item.is_number_integer()) {
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | item.get<int>());
|
||||
|
||||
} else if (item.is_string()) {
|
||||
const auto iteml = unicode::convert<std::string>(item.get<std::string>(), &unicode::lower);
|
||||
if (item == "beforeinitialize")
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize));
|
||||
else if (item == "beforedalamudentrypoint")
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (json.is_string()) {
|
||||
value = DalamudStartInfo::WaitMessageboxFlags::None;
|
||||
for (const auto& item : utils::split(json.get<std::string>(), ",")) {
|
||||
const auto iteml = unicode::convert<std::string>(item, &unicode::lower);
|
||||
if (iteml == "beforeinitialize")
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize));
|
||||
else if (iteml == "beforedalamudentrypoint")
|
||||
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, DalamudStartInfo::DotNetOpenProcessHookMode& value) {
|
||||
if (json.is_number_integer()) {
|
||||
value = static_cast<DalamudStartInfo::DotNetOpenProcessHookMode>(json.get<int>());
|
||||
|
||||
} else if (json.is_string()) {
|
||||
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
|
||||
if (langstr == "importhooks")
|
||||
value = DalamudStartInfo::DotNetOpenProcessHookMode::ImportHooks;
|
||||
else if (langstr == "directhook")
|
||||
value = DalamudStartInfo::DotNetOpenProcessHookMode::DirectHook;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, DalamudStartInfo::ClientLanguage& value) {
|
||||
if (json.is_number_integer()) {
|
||||
value = static_cast<DalamudStartInfo::ClientLanguage>(json.get<int>());
|
||||
|
||||
} else if (json.is_string()) {
|
||||
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
|
||||
if (langstr == "japanese")
|
||||
value = DalamudStartInfo::ClientLanguage::Japanese;
|
||||
else if (langstr == "english")
|
||||
value = DalamudStartInfo::ClientLanguage::English;
|
||||
else if (langstr == "german")
|
||||
value = DalamudStartInfo::ClientLanguage::German;
|
||||
else if (langstr == "french")
|
||||
value = DalamudStartInfo::ClientLanguage::French;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
|
||||
if (!json.is_object())
|
||||
return;
|
||||
|
||||
config.WorkingDirectory = json.value("WorkingDirectory", config.WorkingDirectory);
|
||||
config.ConfigurationPath = json.value("ConfigurationPath", config.ConfigurationPath);
|
||||
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);
|
||||
config.DefaultPluginDirectory = json.value("DefaultPluginDirectory", config.DefaultPluginDirectory);
|
||||
config.AssetDirectory = json.value("AssetDirectory", config.AssetDirectory);
|
||||
config.Language = json.value("Language", config.Language);
|
||||
config.GameVersion = json.value("GameVersion", config.GameVersion);
|
||||
config.DelayInitializeMs = json.value("DelayInitializeMs", config.DelayInitializeMs);
|
||||
|
||||
config.BootLogPath = json.value("BootLogPath", config.BootLogPath);
|
||||
config.BootShowConsole = json.value("BootShowConsole", config.BootShowConsole);
|
||||
config.BootDisableFallbackConsole = json.value("BootDisableFallbackConsole", config.BootDisableFallbackConsole);
|
||||
config.BootWaitMessageBox = json.value("BootWaitMessageBox", config.BootWaitMessageBox);
|
||||
config.BootWaitDebugger = json.value("BootWaitDebugger", config.BootWaitDebugger);
|
||||
config.BootVehEnabled = json.value("BootVehEnabled", config.BootVehEnabled);
|
||||
config.BootVehFull = json.value("BootVehFull", config.BootVehFull);
|
||||
config.BootEnableEtw = json.value("BootEnableEtw", config.BootEnableEtw);
|
||||
config.BootDotnetOpenProcessHookMode = json.value("BootDotnetOpenProcessHookMode", config.BootDotnetOpenProcessHookMode);
|
||||
if (const auto it = json.find("BootEnabledGameFixes"); it != json.end() && it->is_array()) {
|
||||
config.BootEnabledGameFixes.clear();
|
||||
for (const auto& val : *it)
|
||||
config.BootEnabledGameFixes.insert(unicode::convert<std::string>(val.get<std::string>(), &unicode::lower));
|
||||
}
|
||||
if (const auto it = json.find("BootUnhookDlls"); it != json.end() && it->is_array()) {
|
||||
config.BootUnhookDlls.clear();
|
||||
for (const auto& val : *it)
|
||||
config.BootUnhookDlls.insert(unicode::convert<std::string>(val.get<std::string>(), &unicode::lower));
|
||||
}
|
||||
}
|
||||
|
||||
void DalamudStartInfo::from_envvars() {
|
||||
BootLogPath = utils::get_env<std::string>(L"DALAMUD_BOOT_LOGFILE");
|
||||
BootShowConsole = utils::get_env<bool>(L"DALAMUD_SHOW_CONSOLE");
|
||||
BootDisableFallbackConsole = utils::get_env<bool>(L"DALAMUD_DISABLE_FALLBACK_CONSOLE");
|
||||
BootWaitMessageBox = static_cast<WaitMessageboxFlags>(utils::get_env<int>(L"DALAMUD_WAIT_MESSAGEBOX"));
|
||||
BootWaitDebugger = utils::get_env<bool>(L"DALAMUD_WAIT_DEBUGGER");
|
||||
BootVehEnabled = utils::get_env<bool>(L"DALAMUD_IS_VEH");
|
||||
BootVehFull = utils::get_env<bool>(L"DALAMUD_IS_VEH_FULL");
|
||||
BootEnableEtw = utils::get_env<bool>(L"DALAMUD_ENABLE_ETW");
|
||||
BootDotnetOpenProcessHookMode = static_cast<DotNetOpenProcessHookMode>(utils::get_env<int>(L"DALAMUD_DOTNET_OPENPROCESS_HOOKMODE"));
|
||||
for (const auto& item : utils::get_env_list<std::string>(L"DALAMUD_GAMEFIX_LIST"))
|
||||
BootEnabledGameFixes.insert(unicode::convert<std::string>(item, &unicode::lower));
|
||||
for (const auto& item : utils::get_env_list<std::string>(L"DALAMUD_UNHOOK_DLLS"))
|
||||
BootUnhookDlls.insert(unicode::convert<std::string>(item, &unicode::lower));
|
||||
}
|
||||
50
Dalamud.Boot/DalamudStartInfo.h
Normal file
50
Dalamud.Boot/DalamudStartInfo.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
struct DalamudStartInfo {
|
||||
enum class WaitMessageboxFlags : int {
|
||||
None = 0,
|
||||
BeforeInitialize = 1 << 0,
|
||||
BeforeDalamudEntrypoint = 1 << 1,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);
|
||||
|
||||
enum class DotNetOpenProcessHookMode : int {
|
||||
ImportHooks = 0,
|
||||
DirectHook = 1,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, DotNetOpenProcessHookMode&);
|
||||
|
||||
enum class ClientLanguage : int {
|
||||
Japanese,
|
||||
English,
|
||||
German,
|
||||
French,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, ClientLanguage&);
|
||||
|
||||
std::string WorkingDirectory;
|
||||
std::string ConfigurationPath;
|
||||
std::string PluginDirectory;
|
||||
std::string DefaultPluginDirectory;
|
||||
std::string AssetDirectory;
|
||||
ClientLanguage Language = ClientLanguage::English;
|
||||
std::string GameVersion;
|
||||
int DelayInitializeMs = 0;
|
||||
|
||||
std::string BootLogPath;
|
||||
bool BootShowConsole = false;
|
||||
bool BootDisableFallbackConsole = false;
|
||||
WaitMessageboxFlags BootWaitMessageBox = WaitMessageboxFlags::None;
|
||||
bool BootWaitDebugger = false;
|
||||
bool BootVehEnabled = false;
|
||||
bool BootVehFull = false;
|
||||
bool BootEnableEtw = false;
|
||||
DotNetOpenProcessHookMode BootDotnetOpenProcessHookMode = DotNetOpenProcessHookMode::ImportHooks;
|
||||
std::set<std::string> BootEnabledGameFixes{};
|
||||
std::set<std::string> BootUnhookDlls{};
|
||||
|
||||
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
|
||||
void from_envvars();
|
||||
};
|
||||
|
||||
extern DalamudStartInfo g_startInfo;
|
||||
|
|
@ -1,74 +1,122 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "DalamudStartInfo.h"
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
#include "veh.h"
|
||||
#include "xivfixes.h"
|
||||
|
||||
HMODULE g_hModule;
|
||||
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
|
||||
|
||||
bool is_running_on_linux()
|
||||
{
|
||||
size_t required_size;
|
||||
getenv_s(&required_size, nullptr, 0, "XL_WINEONLINUX");
|
||||
if (required_size > 0)
|
||||
{
|
||||
if (char* is_wine_on_linux = static_cast<char*>(malloc(required_size * sizeof(char))))
|
||||
{
|
||||
getenv_s(&required_size, is_wine_on_linux, required_size, "XL_WINEONLINUX");
|
||||
auto result = _stricmp(is_wine_on_linux, "true");
|
||||
free(is_wine_on_linux);
|
||||
if (result == 0)
|
||||
return true;
|
||||
}
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||
g_startInfo.from_envvars();
|
||||
|
||||
std::string jsonParseError;
|
||||
try {
|
||||
from_json(nlohmann::json::parse(std::string_view(static_cast<char*>(lpParam))), g_startInfo);
|
||||
} catch (const std::exception& e) {
|
||||
jsonParseError = e.what();
|
||||
}
|
||||
|
||||
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hntdll) // not running on NT
|
||||
return true;
|
||||
|
||||
FARPROC pwine_get_version = GetProcAddress(hntdll, "wine_get_version");
|
||||
FARPROC pwine_get_host_version = GetProcAddress(hntdll, "wine_get_host_version");
|
||||
|
||||
return pwine_get_version != nullptr || pwine_get_host_version != nullptr;
|
||||
}
|
||||
|
||||
bool is_veh_enabled()
|
||||
{
|
||||
size_t required_size;
|
||||
getenv_s(&required_size, nullptr, 0, "DALAMUD_IS_STAGING");
|
||||
if (required_size > 0)
|
||||
{
|
||||
if (char* is_no_veh = static_cast<char*>(malloc(required_size * sizeof(char))))
|
||||
{
|
||||
getenv_s(&required_size, is_no_veh, required_size, "DALAMUD_IS_STAGING");
|
||||
auto result = _stricmp(is_no_veh, "true");
|
||||
free(is_no_veh);
|
||||
if (result == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
if (g_startInfo.BootShowConsole)
|
||||
ConsoleSetup(L"Dalamud Boot");
|
||||
#endif
|
||||
|
||||
printf("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors\nBuilt at: %s@%s\n\n", __DATE__, __TIME__);
|
||||
logging::update_dll_load_status(true);
|
||||
|
||||
wchar_t _module_path[MAX_PATH];
|
||||
GetModuleFileNameW(g_hModule, _module_path, sizeof _module_path / 2);
|
||||
std::filesystem::path fs_module_path(_module_path);
|
||||
const auto logFilePath = unicode::convert<std::wstring>(g_startInfo.BootLogPath);
|
||||
|
||||
std::wstring runtimeconfig_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.runtimeconfig.json").c_str());
|
||||
std::wstring module_path = _wcsdup(fs_module_path.replace_filename(L"Dalamud.dll").c_str());
|
||||
auto attemptFallbackLog = false;
|
||||
if (logFilePath.empty()) {
|
||||
attemptFallbackLog = true;
|
||||
|
||||
logging::I("No log file path given; not logging to file.");
|
||||
} else {
|
||||
try {
|
||||
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
|
||||
logging::I("Logging to file: {}", logFilePath);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
attemptFallbackLog = true;
|
||||
|
||||
logging::E("Couldn't open log file: {}", logFilePath);
|
||||
logging::E("Error: {} / {}", errno, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsonParseError.empty())
|
||||
logging::E("Couldn't parse input JSON: {}", jsonParseError);
|
||||
|
||||
if (attemptFallbackLog) {
|
||||
std::wstring logFilePath(PATHCCH_MAX_CCH + 1, L'\0');
|
||||
logFilePath.resize(GetTempPathW(static_cast<DWORD>(logFilePath.size()), &logFilePath[0]));
|
||||
if (logFilePath.empty()) {
|
||||
logFilePath.resize(PATHCCH_MAX_CCH + 1);
|
||||
logFilePath.resize(GetCurrentDirectoryW(static_cast<DWORD>(logFilePath.size()), &logFilePath[0]));
|
||||
}
|
||||
if (!logFilePath.empty() && logFilePath.back() != '/' && logFilePath.back() != '\\')
|
||||
logFilePath += L"\\";
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
logFilePath += std::format(L"Dalamud.Boot.{:04}{:02}{:02}.{:02}{:02}{:02}.{:03}.{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId());
|
||||
|
||||
try {
|
||||
logging::start_file_logging(logFilePath, !g_startInfo.BootShowConsole);
|
||||
logging::I("Logging to fallback log file: {}", logFilePath);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
if (!g_startInfo.BootShowConsole && !g_startInfo.BootDisableFallbackConsole)
|
||||
ConsoleSetup(L"Dalamud Boot - Fallback Console");
|
||||
|
||||
logging::E("Couldn't open fallback log file: {}", logFilePath);
|
||||
logging::E("Error: {} / {}", errno, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto minHookLoaded = false;
|
||||
if (const auto mhStatus = MH_Initialize(); mhStatus == MH_OK) {
|
||||
logging::I("MinHook initialized.");
|
||||
minHookLoaded = true;
|
||||
} else if (mhStatus == MH_ERROR_ALREADY_INITIALIZED) {
|
||||
logging::I("MinHook already initialized.");
|
||||
minHookLoaded = true;
|
||||
} else {
|
||||
logging::E("Failed to initialize MinHook (status={}({}))", MH_StatusToString(mhStatus), static_cast<int>(mhStatus));
|
||||
}
|
||||
|
||||
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at: " __DATE__ "@" __TIME__);
|
||||
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize))
|
||||
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
|
||||
|
||||
if (minHookLoaded) {
|
||||
logging::I("Applying fixes...");
|
||||
xivfixes::apply_all(true);
|
||||
logging::I("Fixes OK");
|
||||
} else {
|
||||
logging::W("Skipping fixes, as MinHook has failed to load.");
|
||||
}
|
||||
|
||||
if (g_startInfo.BootWaitDebugger) {
|
||||
logging::I("Waiting for debugger to attach...");
|
||||
while (!IsDebuggerPresent())
|
||||
Sleep(100);
|
||||
logging::I("Debugger attached.");
|
||||
}
|
||||
|
||||
const auto fs_module_path = utils::get_module_path(g_hModule);
|
||||
const auto runtimeconfig_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.runtimeconfig.json").wstring();
|
||||
const auto module_path = std::filesystem::path(fs_module_path).replace_filename(L"Dalamud.dll").wstring();
|
||||
|
||||
// ============================== CLR ========================================= //
|
||||
|
||||
logging::I("Calling InitializeClrAndGetEntryPoint");
|
||||
|
||||
void* entrypoint_vfn;
|
||||
int result = InitializeClrAndGetEntryPoint(
|
||||
g_hModule,
|
||||
g_startInfo.BootEnableEtw,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.EntryPoint, Dalamud",
|
||||
|
|
@ -79,39 +127,39 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam)
|
|||
if (result != 0)
|
||||
return result;
|
||||
|
||||
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(LPVOID);
|
||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||
using custom_component_entry_point_fn = void (CORECLR_DELEGATE_CALLTYPE*)(LPVOID, HANDLE);
|
||||
const auto entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||
|
||||
// ============================== VEH ======================================== //
|
||||
|
||||
printf("Initializing VEH... ");
|
||||
if(is_running_on_linux())
|
||||
{
|
||||
printf("VEH was disabled, running on linux\n");
|
||||
}
|
||||
else if (is_veh_enabled())
|
||||
{
|
||||
if (veh::add_handler())
|
||||
printf("Done!\n");
|
||||
else printf("Failed!\n");
|
||||
}
|
||||
logging::I("Initializing VEH...");
|
||||
if (utils::is_running_on_linux()) {
|
||||
logging::I("=> VEH was disabled, running on linux");
|
||||
} else if (g_startInfo.BootVehEnabled) {
|
||||
if (veh::add_handler(g_startInfo.BootVehFull))
|
||||
logging::I("=> Done!");
|
||||
else
|
||||
{
|
||||
printf("VEH was disabled manually\n");
|
||||
logging::I("=> Failed!");
|
||||
} else {
|
||||
logging::I("VEH was disabled manually");
|
||||
}
|
||||
|
||||
// ============================== Dalamud ==================================== //
|
||||
|
||||
printf("Initializing Dalamud... ");
|
||||
entrypoint_fn(lpParam);
|
||||
printf("Done!\n");
|
||||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||
MessageBoxW(nullptr, L"Press OK to continue (BeforeDalamudEntrypoint)", L"Dalamud Boot", MB_OK);
|
||||
|
||||
#ifndef NDEBUG
|
||||
fclose(stdin);
|
||||
fclose(stdout);
|
||||
fclose(stderr);
|
||||
FreeConsole();
|
||||
#endif
|
||||
if (hMainThreadContinue) {
|
||||
// Let the game initialize.
|
||||
SetEvent(hMainThreadContinue);
|
||||
}
|
||||
|
||||
// We don't need to do this anymore, Dalamud now loads without needing the window to be there. Speed!
|
||||
// utils::wait_for_game_window();
|
||||
|
||||
logging::I("Initializing Dalamud...");
|
||||
entrypoint_fn(lpParam, hMainThreadContinue);
|
||||
logging::I("Done!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -119,13 +167,28 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam)
|
|||
BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
|
||||
switch (dwReason)
|
||||
{
|
||||
switch (dwReason) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
g_hModule = hModule;
|
||||
break;
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
// process is terminating; don't bother cleaning up
|
||||
if (lpReserved)
|
||||
return TRUE;
|
||||
|
||||
logging::update_dll_load_status(false);
|
||||
|
||||
xivfixes::apply_all(false);
|
||||
|
||||
MH_DisableHook(MH_ALL_HOOKS);
|
||||
if (const auto mhStatus = MH_Uninitialize(); MH_OK != mhStatus && MH_ERROR_NOT_INITIALIZED != mhStatus) {
|
||||
logging::E("Failed to uninitialize MinHook (status={})", static_cast<int>(mhStatus));
|
||||
__fastfail(logging::MinHookUnload);
|
||||
}
|
||||
|
||||
veh::remove_handler();
|
||||
//logging::log_file.close();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
|
|
|
|||
149
Dalamud.Boot/hooks.cpp
Normal file
149
Dalamud.Boot/hooks.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "hooks.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
enum {
|
||||
LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
|
||||
LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
|
||||
};
|
||||
|
||||
struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
|
||||
ULONG Flags; //Reserved.
|
||||
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
||||
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
||||
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
||||
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
||||
};
|
||||
|
||||
struct LDR_DLL_LOADED_NOTIFICATION_DATA {
|
||||
ULONG Flags; //Reserved.
|
||||
const UNICODE_STRING* FullDllName; //The full path name of the DLL module.
|
||||
const UNICODE_STRING* BaseDllName; //The base file name of the DLL module.
|
||||
PVOID DllBase; //A pointer to the base address for the DLL in memory.
|
||||
ULONG SizeOfImage; //The size of the DLL image, in bytes.
|
||||
};
|
||||
|
||||
union LDR_DLL_NOTIFICATION_DATA {
|
||||
LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
|
||||
LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
|
||||
};
|
||||
|
||||
using PLDR_DLL_NOTIFICATION_FUNCTION = VOID CALLBACK(_In_ ULONG NotificationReason, _In_ const LDR_DLL_NOTIFICATION_DATA* NotificationData, _In_opt_ PVOID Context);
|
||||
|
||||
static const auto LdrRegisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie)>("LdrRegisterDllNotification");
|
||||
static const auto LdrUnregisterDllNotification = utils::loaded_module(GetModuleHandleW(L"ntdll.dll")).get_exported_function<NTSTATUS(NTAPI)(PVOID Cookie)>("LdrUnregisterDllNotification");
|
||||
|
||||
hooks::getprocaddress_singleton_import_hook::getprocaddress_singleton_import_hook()
|
||||
: m_pfnGetProcAddress(GetProcAddress)
|
||||
, m_thunk("kernel32!GetProcAddress(Singleton Import Hook)",
|
||||
[this](HMODULE hModule, LPCSTR lpProcName) { return get_proc_address_handler(hModule, lpProcName); }) {
|
||||
}
|
||||
|
||||
hooks::getprocaddress_singleton_import_hook::~getprocaddress_singleton_import_hook() {
|
||||
LdrUnregisterDllNotification(m_ldrDllNotificationCookie);
|
||||
}
|
||||
|
||||
std::shared_ptr<void> hooks::getprocaddress_singleton_import_hook::set_handler(std::wstring dllName, std::string functionName, void* pfnDetour, std::function<void(void*)> fnOnOriginalAddressAvailable) {
|
||||
const auto hModule = GetModuleHandleW(dllName.c_str());
|
||||
if (!hModule)
|
||||
throw std::out_of_range("Specified DLL is not found.");
|
||||
|
||||
const auto pfn = m_pfnGetProcAddress(hModule, functionName.c_str());
|
||||
if (!pfn)
|
||||
throw std::out_of_range("Could not find the specified function.");
|
||||
|
||||
fnOnOriginalAddressAvailable(pfn);
|
||||
|
||||
auto& target = m_targetFns[hModule][functionName];
|
||||
if (target)
|
||||
throw std::runtime_error("Specified function has already been hooked.");
|
||||
|
||||
target = pfnDetour;
|
||||
m_dllNameMap[hModule] = unicode::convert<std::string>(dllName);
|
||||
for (const auto& mod : utils::loaded_module::all_modules())
|
||||
hook_module(mod);
|
||||
|
||||
return { pfn,[pThis = this->shared_from_this(), hModule, functionName](void*) {
|
||||
auto& modFns = pThis->m_targetFns[hModule];
|
||||
auto& hooks = pThis->m_hooks[hModule];
|
||||
modFns.erase(functionName);
|
||||
hooks.erase(functionName);
|
||||
if (modFns.empty()) {
|
||||
pThis->m_targetFns.erase(hModule);
|
||||
pThis->m_hooks.erase(hModule);
|
||||
pThis->m_dllNameMap.erase(hModule);
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
std::shared_ptr<hooks::getprocaddress_singleton_import_hook> hooks::getprocaddress_singleton_import_hook::get_instance() {
|
||||
static std::weak_ptr<hooks::getprocaddress_singleton_import_hook> s_instance;
|
||||
std::shared_ptr<hooks::getprocaddress_singleton_import_hook> res;
|
||||
|
||||
res = s_instance.lock();
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
static std::mutex m_mtx;
|
||||
const auto lock = std::lock_guard(m_mtx);
|
||||
res = s_instance.lock();
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
s_instance = res = std::make_shared<getprocaddress_singleton_import_hook>();
|
||||
res->initialize();
|
||||
return res;
|
||||
}
|
||||
|
||||
void hooks::getprocaddress_singleton_import_hook::initialize() {
|
||||
m_getProcAddressHandler = set_handler(L"kernel32.dll", "GetProcAddress", m_thunk.get_thunk(), [this](void*) {});
|
||||
|
||||
LdrRegisterDllNotification(0, [](ULONG notiReason, const LDR_DLL_NOTIFICATION_DATA* pData, void* context) {
|
||||
if (notiReason == LDR_DLL_NOTIFICATION_REASON_LOADED) {
|
||||
const auto dllName = unicode::convert<std::string>(pData->Loaded.FullDllName->Buffer);
|
||||
logging::I(R"({} "{}" has been loaded at 0x{:X} ~ 0x{:X} (0x{:X}); finding import table items to hook.)",
|
||||
LogTag, dllName,
|
||||
reinterpret_cast<size_t>(pData->Loaded.DllBase),
|
||||
reinterpret_cast<size_t>(pData->Loaded.DllBase) + pData->Loaded.SizeOfImage,
|
||||
pData->Loaded.SizeOfImage);
|
||||
reinterpret_cast<getprocaddress_singleton_import_hook*>(context)->hook_module(utils::loaded_module(pData->Loaded.DllBase));
|
||||
} else if (notiReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) {
|
||||
const auto dllName = unicode::convert<std::string>(pData->Unloaded.FullDllName->Buffer);
|
||||
logging::I(R"({} "{}" has been unloaded.)", LogTag, dllName);
|
||||
}
|
||||
}, this, &m_ldrDllNotificationCookie);
|
||||
}
|
||||
|
||||
FARPROC hooks::getprocaddress_singleton_import_hook::get_proc_address_handler(HMODULE hModule, LPCSTR lpProcName) {
|
||||
if (const auto it1 = m_targetFns.find(hModule); it1 != m_targetFns.end()) {
|
||||
if (const auto it2 = it1->second.find(lpProcName); it2 != it1->second.end()) {
|
||||
logging::I(R"({} Redirecting GetProcAddress("{}", "{}"))", LogTag, m_dllNameMap[hModule], lpProcName);
|
||||
|
||||
return reinterpret_cast<FARPROC>(it2->second);
|
||||
}
|
||||
}
|
||||
return this->m_pfnGetProcAddress(hModule, lpProcName);
|
||||
}
|
||||
|
||||
void hooks::getprocaddress_singleton_import_hook::hook_module(const utils::loaded_module& mod) {
|
||||
if (mod.is_current_process())
|
||||
return;
|
||||
|
||||
const auto path = unicode::convert<std::string>(mod.path().wstring());
|
||||
|
||||
for (const auto& [hModule, targetFns] : m_targetFns) {
|
||||
for (const auto& [targetFn, pfnThunk] : targetFns) {
|
||||
const auto& dllName = m_dllNameMap[hModule];
|
||||
if (void* pGetProcAddressImport; mod.find_imported_function_pointer(dllName.c_str(), targetFn.c_str(), 0, pGetProcAddressImport)) {
|
||||
auto& hook = m_hooks[hModule][targetFn][mod];
|
||||
if (!hook) {
|
||||
logging::I("{} Hooking {}!{} imported by {}", LogTag, dllName, targetFn, unicode::convert<std::string>(mod.path().wstring()));
|
||||
|
||||
hook.emplace(std::format("getprocaddress_singleton_import_hook::hook_module({}!{})", dllName, targetFn), static_cast<void**>(pGetProcAddressImport), pfnThunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
252
Dalamud.Boot/hooks.h
Normal file
252
Dalamud.Boot/hooks.h
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace hooks {
|
||||
class base_untyped_hook {
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
base_untyped_hook(std::string name) : m_name(name) {}
|
||||
|
||||
virtual ~base_untyped_hook() = default;
|
||||
|
||||
virtual bool check_consistencies() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void assert_dominance() const {
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return m_name;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename>
|
||||
class base_hook;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class base_hook<TReturn(TArgs...)> : public base_untyped_hook {
|
||||
using TFn = TReturn(TArgs...);
|
||||
|
||||
private:
|
||||
TFn* const m_pfnOriginal;
|
||||
utils::thunk<TReturn(TArgs...)> m_thunk;
|
||||
|
||||
public:
|
||||
base_hook(std::string name, TFn* pfnOriginal)
|
||||
: base_untyped_hook(name)
|
||||
, m_pfnOriginal(pfnOriginal)
|
||||
, m_thunk(std::move(name), m_pfnOriginal) {
|
||||
}
|
||||
|
||||
virtual void set_detour(std::function<TFn> fn) {
|
||||
if (!fn)
|
||||
m_thunk.set_target(m_pfnOriginal);
|
||||
else
|
||||
m_thunk.set_target(std::move(fn));
|
||||
}
|
||||
|
||||
virtual TReturn call_original(TArgs... args) {
|
||||
return m_pfnOriginal(std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
protected:
|
||||
TFn* get_original() const {
|
||||
return m_pfnOriginal;
|
||||
}
|
||||
|
||||
TFn* get_thunk() const {
|
||||
return m_thunk.get_thunk();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TFn>
|
||||
class import_hook : public base_hook<TFn> {
|
||||
using Base = base_hook<TFn>;
|
||||
|
||||
TFn** const m_ppfnImportTableItem;
|
||||
|
||||
public:
|
||||
import_hook(std::string name, TFn** ppfnImportTableItem)
|
||||
: Base(std::move(name), *ppfnImportTableItem)
|
||||
, m_ppfnImportTableItem(ppfnImportTableItem) {
|
||||
|
||||
const utils::memory_tenderizer tenderizer(ppfnImportTableItem, sizeof * ppfnImportTableItem, PAGE_READWRITE);
|
||||
*ppfnImportTableItem = Base::get_thunk();
|
||||
}
|
||||
|
||||
import_hook(std::string name, const char* pcszDllName, const char* pcszFunctionName, int hintOrOrdinal)
|
||||
: import_hook(std::move(name), utils::loaded_module::current_process().get_imported_function_pointer<TFn>(pcszDllName, pcszFunctionName, hintOrOrdinal)) {
|
||||
}
|
||||
|
||||
~import_hook() override {
|
||||
const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE);
|
||||
*m_ppfnImportTableItem = Base::get_original();
|
||||
}
|
||||
|
||||
bool check_consistencies() const override {
|
||||
return *m_ppfnImportTableItem == Base::get_thunk();
|
||||
}
|
||||
|
||||
void assert_dominance() const override {
|
||||
if (check_consistencies())
|
||||
return;
|
||||
|
||||
const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE);
|
||||
*m_ppfnImportTableItem = Base::get_thunk();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename>
|
||||
class direct_hook;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class direct_hook<TReturn(TArgs...)> : public base_hook<TReturn(TArgs...)> {
|
||||
using TFn = TReturn(TArgs...);
|
||||
using Base = base_hook<TFn>;
|
||||
|
||||
TFn* m_pfnMinHookBridge;
|
||||
|
||||
public:
|
||||
direct_hook(std::string name, TFn* pfnFunction)
|
||||
: Base(std::move(name), pfnFunction) {
|
||||
if (const auto mhStatus = MH_CreateHook(pfnFunction, Base::get_thunk(), reinterpret_cast<void**>(&m_pfnMinHookBridge)); mhStatus != MH_OK)
|
||||
throw std::runtime_error(std::format("MH_CreateHook(0x{:X}, ...) failure: {}", reinterpret_cast<size_t>(pfnFunction), static_cast<int>(mhStatus)));
|
||||
|
||||
MH_EnableHook(Base::get_original());
|
||||
}
|
||||
|
||||
~direct_hook() override {
|
||||
MH_DisableHook(Base::get_original());
|
||||
}
|
||||
|
||||
TReturn call_original(TArgs... args) override {
|
||||
return m_pfnMinHookBridge(std::forward<TArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
class wndproc_hook : public base_hook<std::remove_pointer_t<WNDPROC>> {
|
||||
using Base = base_hook<std::remove_pointer_t<WNDPROC>>;
|
||||
|
||||
const HWND m_hwnd;
|
||||
|
||||
public:
|
||||
wndproc_hook(std::string name, HWND hwnd)
|
||||
: Base(std::move(name), reinterpret_cast<WNDPROC>(GetWindowLongPtrW(hwnd, GWLP_WNDPROC)))
|
||||
, m_hwnd(hwnd) {
|
||||
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Base::get_thunk()));
|
||||
}
|
||||
|
||||
~wndproc_hook() override {
|
||||
SetWindowLongPtrW(m_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Base::get_original()));
|
||||
}
|
||||
|
||||
LRESULT call_original(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) override {
|
||||
return CallWindowProcW(Base::get_original(), hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
bool check_consistencies() const override {
|
||||
return GetWindowLongPtrW(m_hwnd, GWLP_WNDPROC) == reinterpret_cast<LONG_PTR>(Base::get_thunk());
|
||||
}
|
||||
|
||||
void assert_dominance() const override {
|
||||
if (check_consistencies())
|
||||
return;
|
||||
|
||||
SetWindowLongPtrW(m_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Base::get_thunk()));
|
||||
}
|
||||
};
|
||||
|
||||
class untyped_import_hook : public base_untyped_hook {
|
||||
void** const m_ppfnImportTableItem;
|
||||
void* const m_pfnOriginalImport;
|
||||
|
||||
public:
|
||||
untyped_import_hook(std::string name, void** ppfnImportTableItem, void* pThunk)
|
||||
: base_untyped_hook(std::move(name))
|
||||
, m_pfnOriginalImport(*ppfnImportTableItem)
|
||||
, m_ppfnImportTableItem(ppfnImportTableItem) {
|
||||
|
||||
const utils::memory_tenderizer tenderizer(ppfnImportTableItem, sizeof * ppfnImportTableItem, PAGE_READWRITE);
|
||||
*ppfnImportTableItem = pThunk;
|
||||
}
|
||||
|
||||
~untyped_import_hook() override {
|
||||
MEMORY_BASIC_INFORMATION mbi{};
|
||||
VirtualQuery(m_ppfnImportTableItem, &mbi, sizeof mbi);
|
||||
if (mbi.State != MEM_COMMIT)
|
||||
return;
|
||||
|
||||
const utils::memory_tenderizer tenderizer(m_ppfnImportTableItem, sizeof * m_ppfnImportTableItem, PAGE_READWRITE);
|
||||
*m_ppfnImportTableItem = m_pfnOriginalImport;
|
||||
}
|
||||
};
|
||||
|
||||
class getprocaddress_singleton_import_hook : public std::enable_shared_from_this<getprocaddress_singleton_import_hook> {
|
||||
static inline const char* LogTag = "[global_import_hook]";
|
||||
|
||||
decltype(GetProcAddress)* const m_pfnGetProcAddress;
|
||||
|
||||
utils::thunk<decltype(GetProcAddress)> m_thunk;
|
||||
std::shared_ptr<void> m_getProcAddressHandler;
|
||||
|
||||
void* m_ldrDllNotificationCookie{};
|
||||
std::map<HMODULE, std::string> m_dllNameMap;
|
||||
std::map<HMODULE, std::map<std::string, void*>> m_targetFns;
|
||||
std::map<HMODULE, std::map<std::string, std::map<HMODULE, std::optional<untyped_import_hook>>>> m_hooks;
|
||||
|
||||
public:
|
||||
getprocaddress_singleton_import_hook();
|
||||
~getprocaddress_singleton_import_hook();
|
||||
|
||||
std::shared_ptr<void> set_handler(std::wstring dllName, std::string functionName, void* pfnDetour, std::function<void(void*)> fnOnOriginalAddressAvailable);
|
||||
|
||||
static std::shared_ptr<getprocaddress_singleton_import_hook> get_instance();
|
||||
|
||||
private:
|
||||
void initialize();
|
||||
|
||||
FARPROC get_proc_address_handler(HMODULE hModule, LPCSTR lpProcName);
|
||||
|
||||
void hook_module(const utils::loaded_module& mod);
|
||||
};
|
||||
|
||||
template<typename>
|
||||
class global_import_hook;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class global_import_hook<TReturn(TArgs...)> : public base_untyped_hook {
|
||||
using TFn = TReturn(TArgs...);
|
||||
utils::thunk<TFn> m_thunk;
|
||||
std::shared_ptr<void> m_singleImportHook;
|
||||
|
||||
public:
|
||||
global_import_hook(std::string name, std::wstring dllName, std::string functionName)
|
||||
: base_untyped_hook(name)
|
||||
, m_thunk(std::move(name), nullptr) {
|
||||
|
||||
m_singleImportHook = getprocaddress_singleton_import_hook::get_instance()->set_handler(
|
||||
dllName,
|
||||
functionName,
|
||||
m_thunk.get_thunk(),
|
||||
[this](void* p) { m_thunk.set_target(reinterpret_cast<TFn*>(p)); }
|
||||
);
|
||||
}
|
||||
|
||||
virtual void set_detour(std::function<TFn> fn) {
|
||||
if (!fn)
|
||||
m_thunk.set_target(reinterpret_cast<TFn*>(m_singleImportHook.get()));
|
||||
else
|
||||
m_thunk.set_target(std::move(fn));
|
||||
}
|
||||
|
||||
virtual TReturn call_original(TArgs... args) {
|
||||
return reinterpret_cast<TFn*>(m_singleImportHook.get())(std::forward<TArgs>(args)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
91
Dalamud.Boot/logging.cpp
Normal file
91
Dalamud.Boot/logging.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
static bool s_bLoaded = false;
|
||||
static bool s_bSkipLogFileWrite = false;
|
||||
static std::shared_ptr<void> s_hLogFile;
|
||||
|
||||
void logging::start_file_logging(const std::filesystem::path& path, bool redirect_stderrout) {
|
||||
if (s_hLogFile)
|
||||
return;
|
||||
|
||||
try {
|
||||
if (exists(path) && file_size(path) > 1048576) {
|
||||
auto oldPath = std::filesystem::path(path);
|
||||
oldPath.replace_extension(".log.old");
|
||||
if (exists(oldPath))
|
||||
remove(oldPath);
|
||||
rename(path, oldPath);
|
||||
}
|
||||
} catch (...) {
|
||||
// whatever
|
||||
}
|
||||
|
||||
const auto h = CreateFile(path.wstring().c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_ALWAYS, 0, nullptr);
|
||||
if (h == INVALID_HANDLE_VALUE)
|
||||
throw std::runtime_error(std::format("Win32 error {}(0x{:x})", GetLastError(), GetLastError()));
|
||||
|
||||
SetFilePointer(h, 0, 0, FILE_END);
|
||||
s_hLogFile = {h, &CloseHandle};
|
||||
|
||||
if (redirect_stderrout) {
|
||||
SetStdHandle(STD_ERROR_HANDLE, h);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, h);
|
||||
s_bSkipLogFileWrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
void logging::update_dll_load_status(bool loaded) {
|
||||
s_bLoaded = loaded;
|
||||
}
|
||||
|
||||
template<>
|
||||
void logging::print<char>(Level level, const char* s) {
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
|
||||
std::string estr;
|
||||
switch (level) {
|
||||
case Level::Verbose:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/VRB] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
case Level::Debug:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/DBG] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
case Level::Info:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/INF] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
case Level::Warning:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/WRN] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
case Level::Error:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/ERR] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
case Level::Fatal:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/FTL] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
default:
|
||||
estr = std::format("[{:02}:{:02}:{:02} CPP/???] {}\n", st.wHour, st.wMinute, st.wSecond, s);
|
||||
break;
|
||||
}
|
||||
|
||||
OutputDebugStringW(unicode::convert<std::wstring>(estr).c_str());
|
||||
|
||||
// Handle accesses should not be done during DllMain process attach/detach calls
|
||||
if (s_bLoaded) {
|
||||
DWORD wr{};
|
||||
WriteFile(GetStdHandle(STD_ERROR_HANDLE), &estr[0], static_cast<DWORD>(estr.size()), &wr, nullptr);
|
||||
|
||||
if (s_hLogFile && !s_bSkipLogFileWrite) {
|
||||
WriteFile(s_hLogFile.get(), &estr[0], static_cast<DWORD>(estr.size()), &wr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Dalamud.Boot/logging.h
Normal file
125
Dalamud.Boot/logging.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
#include "unicode.h"
|
||||
|
||||
namespace logging {
|
||||
enum class Level : int {
|
||||
Verbose = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warning = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
};
|
||||
|
||||
enum FastFailErrorCode : int {
|
||||
Unspecified = 12345,
|
||||
MinHookUnload,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Starts writing log to specified file.
|
||||
*/
|
||||
void start_file_logging(const std::filesystem::path& path, bool redirect_stderrout = false);
|
||||
|
||||
/**
|
||||
* @brief Marks this DLL either as loaded or unloaded, top prevent accessing handles when the DLL is not loaded.
|
||||
*/
|
||||
void update_dll_load_status(bool loaded);
|
||||
|
||||
/**
|
||||
* @brief Prints log, unformatted.
|
||||
* @param level Log level.
|
||||
* @param s Log to print, as a C-string.
|
||||
*/
|
||||
template<typename TElem>
|
||||
void print(Level level, const TElem* s) { print(level, unicode::convert<std::string>(s).c_str()); }
|
||||
|
||||
/**
|
||||
* @brief Prints log, unformatted.
|
||||
* @param level Log level.
|
||||
* @param s Log to print, as a basic_string.
|
||||
*/
|
||||
template<typename TElem, typename TTraits = std::char_traits<TElem>, typename TAlloc = std::allocator<TElem>>
|
||||
void print(Level level, const std::basic_string<TElem, TTraits, TAlloc>& s) { print(level, s.c_str()); }
|
||||
|
||||
/**
|
||||
* @brief Prints log, unformatted.
|
||||
* @param level Log level.
|
||||
* @param s Log to print, as a basic_string_view.
|
||||
*/
|
||||
template<typename TElem, typename TTraits = std::char_traits<TElem>>
|
||||
void print(Level level, const std::basic_string_view<TElem, TTraits>& s) { print(level, unicode::convert<std::string>(s).c_str()); }
|
||||
|
||||
template<>
|
||||
void print<char>(Level level, const char* s);
|
||||
|
||||
template<typename>
|
||||
struct is_basic_string : std::false_type {};
|
||||
|
||||
template<typename TElem, typename TTraits, typename TAlloc>
|
||||
struct is_basic_string<std::basic_string<TElem, TTraits, TAlloc>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto is_basic_string_v = is_basic_string<T>::value;
|
||||
|
||||
template<typename>
|
||||
struct is_basic_string_view : std::false_type {};
|
||||
|
||||
template<typename TElem, typename TTraits, typename TAlloc>
|
||||
struct is_basic_string_view<std::basic_string<TElem, TTraits, TAlloc>> : std::true_type {};
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto is_basic_string_view_v = is_basic_string_view<T>::value;
|
||||
|
||||
template<typename T>
|
||||
auto to_format_arg(T&& x) {
|
||||
using Td = std::remove_cvref_t<T>;
|
||||
if constexpr (std::is_pointer_v<Td>) {
|
||||
using Tdd = std::remove_cvref_t<std::remove_pointer_t<Td>>;
|
||||
if constexpr (std::is_same_v<Tdd, wchar_t> || std::is_same_v<Tdd, char8_t> || std::is_same_v<Tdd, char16_t> || std::is_same_v<Tdd, char32_t>)
|
||||
return unicode::convert<std::string>(x);
|
||||
else
|
||||
return std::forward<T>(x);
|
||||
|
||||
} else {
|
||||
if constexpr (is_basic_string_v<Td> || is_basic_string_view_v<Td>) {
|
||||
using Tdd = Td::value_type;
|
||||
if constexpr (std::is_same_v<Tdd, wchar_t> || std::is_same_v<Tdd, char8_t> || std::is_same_v<Tdd, char16_t> || std::is_same_v<Tdd, char32_t>)
|
||||
return unicode::convert<std::string>(x);
|
||||
else
|
||||
return std::forward<T>(x);
|
||||
|
||||
} else if constexpr (std::is_same_v<Td, std::filesystem::path>) {
|
||||
auto u8s = x.u8string();
|
||||
return std::move(*reinterpret_cast<std::string*>(&u8s));
|
||||
|
||||
} else {
|
||||
return std::forward<T>(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prints log, formatted.
|
||||
* @param level Log level.
|
||||
* @param fmt C-string.
|
||||
* @param arg1 First format parameter.
|
||||
* @param args Second and further format parameters, if any.
|
||||
*/
|
||||
template<typename Arg, typename...Args>
|
||||
void print(Level level, const char* fmt, Arg&& arg1, Args&&...args) {
|
||||
print(level, std::vformat(fmt, std::make_format_args(to_format_arg(std::forward<Arg>(arg1)), to_format_arg(std::forward<Args>(args))...)));
|
||||
}
|
||||
|
||||
template<typename...Args> void V(Args&&...args) { print(Level::Verbose, std::forward<Args>(args)...); }
|
||||
template<typename...Args> void D(Args&&...args) { print(Level::Debug, std::forward<Args>(args)...); }
|
||||
template<typename...Args> void I(Args&&...args) { print(Level::Info, std::forward<Args>(args)...); }
|
||||
template<typename...Args> void W(Args&&...args) { print(Level::Warning, std::forward<Args>(args)...); }
|
||||
template<typename...Args> void E(Args&&...args) { print(Level::Error, std::forward<Args>(args)...); }
|
||||
template<typename...Args> void F(Args&&...args) { print(Level::Fatal, std::forward<Args>(args)...); }
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
// Exclude rarely-used stuff from Windows headers
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
|
||||
// Windows Header Files
|
||||
#include <windows.h>
|
||||
|
|
@ -17,25 +18,52 @@
|
|||
#include <Psapi.h>
|
||||
#include <Shlobj.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <Dbt.h>
|
||||
#include <SubAuth.h>
|
||||
|
||||
// MSVC Compiler Intrinsic
|
||||
#include <intrin.h>
|
||||
|
||||
// C++ Standard Libraries
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
|
||||
// https://www.akenotsuki.com/misc/srell/en/
|
||||
#include "../lib/srell3_009/single-header/srell.hpp"
|
||||
|
||||
// https://github.com/TsudaKageyu/minhook
|
||||
#include "../lib/TsudaKageyu-minhook/include/MinHook.h"
|
||||
|
||||
// https://github.com/Nomade040/nmd
|
||||
#include "../lib/Nomade040-nmd/nmd_assembly.h"
|
||||
|
||||
// https://github.com/dotnet/coreclr
|
||||
#include "..\lib\CoreCLR\CoreCLR.h"
|
||||
#include "..\lib\CoreCLR\boot.h"
|
||||
#include "../lib/CoreCLR/CoreCLR.h"
|
||||
#include "../lib/CoreCLR/boot.h"
|
||||
|
||||
// https://github.com/nlohmann/json
|
||||
#include "../lib/nlohmann-json/json.hpp"
|
||||
|
||||
#include "unicode.h"
|
||||
|
||||
// Commonly used macros
|
||||
#define DllExport extern "C" __declspec(dllexport)
|
||||
|
||||
// Global variables
|
||||
extern HMODULE g_hModule;
|
||||
extern HINSTANCE g_hGameInstance;
|
||||
extern std::optional<CoreCLR> g_clr;
|
||||
|
||||
#endif //PCH_H
|
||||
|
|
|
|||
2
Dalamud.Boot/pch_nmd_assembly_impl.cpp
Normal file
2
Dalamud.Boot/pch_nmd_assembly_impl.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#define NMD_ASSEMBLY_IMPLEMENTATION
|
||||
#include "../lib/Nomade040-nmd/nmd_assembly.h"
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#include "pch.h"
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam);
|
||||
#include "logging.h"
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||
|
||||
struct RewrittenEntryPointParameters {
|
||||
void* pAllocation;
|
||||
|
|
@ -231,28 +233,14 @@ void* get_mapped_image_base_address(HANDLE hProcess, const std::filesystem::path
|
|||
|
||||
return mbi.AllocationBase;
|
||||
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
printf("%s", e.what());
|
||||
} catch (const std::exception& e) {
|
||||
logging::W("Failed to check memory block 0x{:X}(len=0x{:X}): {}", mbi.BaseAddress, mbi.RegionSize, e.what());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("corresponding base address not found");
|
||||
}
|
||||
|
||||
/// @brief Find the game main window.
|
||||
/// @return Handle to the game main window, or nullptr if it doesn't exist (yet).
|
||||
HWND try_find_game_window() {
|
||||
HWND hwnd = nullptr;
|
||||
while ((hwnd = FindWindowExW(nullptr, hwnd, L"FFXIVGAME", nullptr))) {
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
|
||||
if (pid == GetCurrentProcessId() && IsWindowVisible(hwnd))
|
||||
break;
|
||||
}
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
std::string from_utf16(const std::wstring& wstr, UINT codePage = CP_UTF8) {
|
||||
std::string str(WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), nullptr, 0, nullptr, nullptr), 0);
|
||||
WideCharToMultiByte(codePage, 0, &wstr[0], static_cast<int>(wstr.size()), &str[0], static_cast<int>(str.size()), nullptr, nullptr);
|
||||
|
|
@ -358,15 +346,6 @@ DllExport DWORD WINAPI RewriteRemoteEntryPoint(HANDLE hProcess, const wchar_t* p
|
|||
return RewriteRemoteEntryPointW(hProcess, pcwzPath, to_utf16(pcszLoadInfo).c_str());
|
||||
}
|
||||
|
||||
void wait_for_game_window() {
|
||||
HWND game_window;
|
||||
while (!(game_window = try_find_game_window())) {
|
||||
WaitForInputIdle(GetCurrentProcess(), INFINITE);
|
||||
Sleep(100);
|
||||
};
|
||||
SendMessageW(game_window, WM_NULL, 0, 0);
|
||||
}
|
||||
|
||||
/// @brief Entry point function "called" instead of game's original main entry point.
|
||||
/// @param params Parameters set up from RewriteRemoteEntryPoint.
|
||||
DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) {
|
||||
|
|
@ -379,23 +358,17 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params)
|
|||
params.hMainThread = CreateThread(nullptr, 0, [](void* p) -> DWORD {
|
||||
try {
|
||||
std::string loadInfo;
|
||||
{
|
||||
auto& params = *reinterpret_cast<RewrittenEntryPointParameters*>(p);
|
||||
|
||||
{
|
||||
// Restore original entry point.
|
||||
// Use WriteProcessMemory instead of memcpy to avoid having to fiddle with VirtualProtect.
|
||||
write_process_memory_or_throw(GetCurrentProcess(), params.pEntrypoint, params.pEntrypointBytes, params.entrypointLength);
|
||||
|
||||
// Make a copy of load info, as the whole params will be freed after this code block.
|
||||
loadInfo = params.pLoadInfo;
|
||||
|
||||
// Let the game initialize.
|
||||
SetEvent(params.hMainThreadContinue);
|
||||
}
|
||||
|
||||
wait_for_game_window();
|
||||
|
||||
Initialize(&loadInfo[0]);
|
||||
Initialize(&loadInfo[0], params.hMainThreadContinue);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR);
|
||||
|
|
|
|||
245
Dalamud.Boot/unicode.cpp
Normal file
245
Dalamud.Boot/unicode.cpp
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#include "unicode.h"
|
||||
|
||||
size_t unicode::decode(EncodingTag<char8_t>, char32_t& out, const char8_t* in, size_t nRemainingBytes, bool strict) {
|
||||
if (nRemainingBytes == 0) {
|
||||
out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (0 == (*in & 0x80)) {
|
||||
out = *in;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (0xC0 == (*in & 0xE0)) {
|
||||
if (nRemainingBytes < 2) goto invalid;
|
||||
if (0x80 != (in[1] & 0xC0)) goto invalid;
|
||||
out = (
|
||||
((static_cast<char32_t>(in[0]) & 0x1F) << 6) |
|
||||
((static_cast<char32_t>(in[1]) & 0x3F) << 0));
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (0xE0 == (*in & 0xF0)) {
|
||||
if (nRemainingBytes < 3) goto invalid;
|
||||
if (0x80 != (in[1] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[2] & 0xC0)) goto invalid;
|
||||
out = static_cast<char32_t>(
|
||||
((static_cast<char32_t>(in[0]) & 0x0F) << 12) |
|
||||
((static_cast<char32_t>(in[1]) & 0x3F) << 6) |
|
||||
((static_cast<char32_t>(in[2]) & 0x3F) << 0));
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (0xF0 == (*in & 0xF8)) {
|
||||
if (nRemainingBytes < 4) goto invalid;
|
||||
if (0x80 != (in[1] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[2] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[3] & 0xC0)) goto invalid;
|
||||
out = (
|
||||
((static_cast<char32_t>(in[0]) & 0x07) << 18) |
|
||||
((static_cast<char32_t>(in[1]) & 0x3F) << 12) |
|
||||
((static_cast<char32_t>(in[2]) & 0x3F) << 6) |
|
||||
((static_cast<char32_t>(in[3]) & 0x3F) << 0));
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (!strict) {
|
||||
if (0xF8 == (*in & 0xFC)) {
|
||||
if (nRemainingBytes < 5) goto invalid;
|
||||
if (0x80 != (in[1] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[2] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[3] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[4] & 0xC0)) goto invalid;
|
||||
out = (
|
||||
((static_cast<char32_t>(in[0]) & 0x07) << 24) |
|
||||
((static_cast<char32_t>(in[1]) & 0x3F) << 18) |
|
||||
((static_cast<char32_t>(in[2]) & 0x3F) << 12) |
|
||||
((static_cast<char32_t>(in[3]) & 0x3F) << 6) |
|
||||
((static_cast<char32_t>(in[4]) & 0x3F) << 0));
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (0xFC == (*in & 0xFE)) {
|
||||
if (nRemainingBytes < 6) goto invalid;
|
||||
if (0x80 != (in[1] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[2] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[3] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[4] & 0xC0)) goto invalid;
|
||||
if (0x80 != (in[5] & 0xC0)) goto invalid;
|
||||
out = (
|
||||
((static_cast<char32_t>(in[0]) & 0x07) << 30) |
|
||||
((static_cast<char32_t>(in[1]) & 0x3F) << 24) |
|
||||
((static_cast<char32_t>(in[2]) & 0x3F) << 18) |
|
||||
((static_cast<char32_t>(in[3]) & 0x3F) << 12) |
|
||||
((static_cast<char32_t>(in[4]) & 0x3F) << 6) |
|
||||
((static_cast<char32_t>(in[5]) & 0x3F) << 0));
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
invalid:
|
||||
out = UReplacement;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unicode::decode(EncodingTag<char16_t>, char32_t& out, const char16_t* in, size_t nRemainingBytes, bool strict) {
|
||||
if (nRemainingBytes == 0) {
|
||||
out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((*in & 0xFC00) == 0xD800) {
|
||||
if (nRemainingBytes < 2 || (in[1] & 0xFC00) != 0xDC00)
|
||||
goto invalid;
|
||||
out = 0x10000 + (
|
||||
((static_cast<char32_t>(in[0]) & 0x03FF) << 10) |
|
||||
((static_cast<char32_t>(in[1]) & 0x03FF) << 0)
|
||||
);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (0xD800 <= *in && *in <= 0xDFFF && strict)
|
||||
out = UReplacement;
|
||||
else
|
||||
out = *in;
|
||||
return 1;
|
||||
|
||||
invalid:
|
||||
out = UReplacement;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unicode::decode(EncodingTag<char32_t>, char32_t& out, const char32_t* in, size_t nRemainingBytes, bool strict) {
|
||||
if (nRemainingBytes == 0) {
|
||||
out = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
out = *in;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unicode::decode(EncodingTag<char>, char32_t& out, const char* in, size_t nRemainingBytes, bool strict) {
|
||||
return decode(EncodingTag<char8_t>(), out, reinterpret_cast<const char8_t*>(in), nRemainingBytes, strict);
|
||||
}
|
||||
|
||||
size_t unicode::decode(EncodingTag<wchar_t>, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict) {
|
||||
return decode(EncodingTag<char16_t>(), out, reinterpret_cast<const char16_t*>(in), nRemainingBytes, strict);
|
||||
}
|
||||
|
||||
size_t unicode::encode(EncodingTag<char8_t>, char8_t* ptr, char32_t c, bool strict) {
|
||||
if (c < (1 << 7)) {
|
||||
if (ptr)
|
||||
*(ptr++) = static_cast<char8_t>(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (c < (1 << (5 + 6))) {
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xC0 | static_cast<char8_t>(c >> 6);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
if (c < (1 << (4 + 6 + 6))) {
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xE0 | static_cast<char8_t>(c >> 12);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (c < (1 << (3 + 6 + 6 + 6))) {
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xF0 | static_cast<char8_t>(c >> 18);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
if (ptr) { // Replacement character U+FFFD
|
||||
*(ptr++) = 0xEF;
|
||||
*(ptr++) = 0xBF;
|
||||
*(ptr++) = 0xBD;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (c < (1 << (3 + 6 + 6 + 6 + 6))) {
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xF8 | static_cast<char8_t>(c >> 24);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 18) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xFC | static_cast<char8_t>(c >> 30);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 24) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 18) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 12) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 6) & 0x3F);
|
||||
*(ptr++) = 0x80 | static_cast<char8_t>((c >> 0) & 0x3F);
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
|
||||
size_t unicode::encode(EncodingTag<char16_t>, char16_t* ptr, char32_t c, bool strict) {
|
||||
if (c < 0x10000) {
|
||||
if (ptr) {
|
||||
if (0xD800 <= c && c <= 0xDFFF && strict)
|
||||
*(ptr++) = 0xFFFD;
|
||||
else
|
||||
*(ptr++) = static_cast<char16_t>(c);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
c -= 0x10000;
|
||||
|
||||
if (c < (1 << 20)) {
|
||||
if (ptr) {
|
||||
*(ptr++) = 0xD800 | static_cast<char16_t>((c >> 10) & 0x3FF);
|
||||
*(ptr++) = 0xDC00 | static_cast<char16_t>((c >> 0) & 0x3FF);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (ptr)
|
||||
*(ptr++) = 0xFFFD;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unicode::encode(EncodingTag<char32_t>, char32_t* ptr, char32_t c, bool strict) {
|
||||
if (ptr)
|
||||
*ptr = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unicode::encode(EncodingTag<char>, char* ptr, char32_t c, bool strict) {
|
||||
return encode(EncodingTag<char8_t>(), reinterpret_cast<char8_t*>(ptr), c, strict);
|
||||
}
|
||||
|
||||
size_t unicode::encode(EncodingTag<wchar_t>, wchar_t* ptr, char32_t c, bool strict) {
|
||||
return encode(EncodingTag<char16_t>(), reinterpret_cast<char16_t*>(ptr), c, strict);
|
||||
}
|
||||
|
||||
char32_t unicode::lower(char32_t in) {
|
||||
if ('A' <= in && in <= 'Z')
|
||||
return in - 'A' + 'a';
|
||||
return in;
|
||||
}
|
||||
|
||||
char32_t unicode::upper(char32_t in) {
|
||||
if ('a' <= in && in <= 'z')
|
||||
return in - 'a' + 'A';
|
||||
return in;
|
||||
}
|
||||
108
Dalamud.Boot/unicode.h
Normal file
108
Dalamud.Boot/unicode.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace unicode {
|
||||
constexpr char32_t UReplacement = U'\uFFFD';
|
||||
constexpr char32_t UInvalid = U'\uFFFF';
|
||||
|
||||
template<typename T> struct EncodingTag {};
|
||||
|
||||
size_t decode(EncodingTag<char8_t>, char32_t& out, const char8_t* in, size_t nRemainingBytes, bool strict);
|
||||
|
||||
size_t decode(EncodingTag<char16_t>, char32_t& out, const char16_t* in, size_t nRemainingBytes, bool strict);
|
||||
|
||||
size_t decode(EncodingTag<char32_t>, char32_t& out, const char32_t* in, size_t nRemainingBytes, bool strict);
|
||||
|
||||
size_t decode(EncodingTag<char>, char32_t& out, const char* in, size_t nRemainingBytes, bool strict);
|
||||
|
||||
size_t decode(EncodingTag<wchar_t>, char32_t& out, const wchar_t* in, size_t nRemainingBytes, bool strict);
|
||||
|
||||
template<typename T>
|
||||
inline size_t decode(char32_t& out, const T* in, size_t nRemainingBytes, bool strict = true) {
|
||||
return decode(EncodingTag<T>(), out, in, nRemainingBytes, strict);
|
||||
}
|
||||
|
||||
size_t encode(EncodingTag<char8_t>, char8_t* ptr, char32_t c, bool strict);
|
||||
|
||||
size_t encode(EncodingTag<char16_t>, char16_t* ptr, char32_t c, bool strict);
|
||||
|
||||
size_t encode(EncodingTag<char32_t>, char32_t* ptr, char32_t c, bool strict);
|
||||
|
||||
size_t encode(EncodingTag<char>, char* ptr, char32_t c, bool strict);
|
||||
|
||||
size_t encode(EncodingTag<wchar_t>, wchar_t* ptr, char32_t c, bool strict);
|
||||
|
||||
template<typename T>
|
||||
inline size_t encode(T* ptr, char32_t c, bool strict = true) {
|
||||
return encode(EncodingTag<T>(), ptr, c, strict);
|
||||
}
|
||||
|
||||
char32_t lower(char32_t in);
|
||||
char32_t upper(char32_t in);
|
||||
|
||||
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>>
|
||||
TTo& convert(TTo& out, const std::basic_string_view<TFromElem, TFromTraits>& in, char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
out.reserve(out.size() + in.size() * 4 / sizeof(in[0]) / sizeof(out[0]));
|
||||
|
||||
char32_t c{};
|
||||
for (size_t decLen = 0, decIdx = 0; decIdx < in.size() && ((decLen = decode(c, &in[decIdx], in.size() - decIdx, strict))); decIdx += decLen) {
|
||||
if (pfnCharMap)
|
||||
c = pfnCharMap(c);
|
||||
|
||||
const auto encIdx = out.size();
|
||||
const auto encLen = encode<typename TTo::value_type>(nullptr, c, strict);
|
||||
out.resize(encIdx + encLen);
|
||||
encode(&out[encIdx], c, strict);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>, class TFromAlloc = std::allocator<TFromElem>>
|
||||
TTo& convert(TTo& out, const std::basic_string<TFromElem, TFromTraits, TFromAlloc>& in, char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
return convert(out, std::basic_string_view<TFromElem, TFromTraits>(in), pfnCharMap, strict);
|
||||
}
|
||||
|
||||
template<class TTo, class TFromElem, typename = std::enable_if_t<std::is_integral_v<TFromElem>>>
|
||||
TTo& convert(TTo& out, const TFromElem* in, size_t length = (std::numeric_limits<size_t>::max)(), char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
if (length == (std::numeric_limits<size_t>::max)())
|
||||
length = std::char_traits<TFromElem>::length(in);
|
||||
|
||||
return convert(out, std::basic_string_view<TFromElem>(in, length), pfnCharMap, strict);
|
||||
}
|
||||
|
||||
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>>
|
||||
TTo convert(const std::basic_string_view<TFromElem, TFromTraits>& in, char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
TTo out{};
|
||||
return convert(out, in, pfnCharMap, strict);
|
||||
}
|
||||
|
||||
template<class TTo, class TFromElem, class TFromTraits = std::char_traits<TFromElem>, class TFromAlloc = std::allocator<TFromElem>>
|
||||
TTo convert(const std::basic_string<TFromElem, TFromTraits, TFromAlloc>& in, char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
TTo out{};
|
||||
return convert(out, std::basic_string_view<TFromElem, TFromTraits>(in), pfnCharMap, strict);
|
||||
}
|
||||
|
||||
template<class TTo, class TFromElem, typename = std::enable_if_t<std::is_integral_v<TFromElem>>>
|
||||
TTo convert(const TFromElem* in, size_t length = (std::numeric_limits<size_t>::max)(), char32_t(*pfnCharMap)(char32_t) = nullptr, bool strict = false) {
|
||||
if (length == (std::numeric_limits<size_t>::max)())
|
||||
length = std::char_traits<TFromElem>::length(in);
|
||||
|
||||
TTo out{};
|
||||
return convert(out, std::basic_string_view<TFromElem>(in, length), pfnCharMap, strict);
|
||||
}
|
||||
|
||||
inline const std::u8string& convert(const std::u8string& in) { return in; }
|
||||
|
||||
inline const std::u16string& convert(const std::u16string& in) { return in; }
|
||||
|
||||
inline const std::u32string& convert(const std::u32string& in) { return in; }
|
||||
|
||||
inline const std::string& convert(const std::string& in) { return in; }
|
||||
|
||||
inline const std::wstring& convert(const std::wstring& in) { return in; }
|
||||
}
|
||||
522
Dalamud.Boot/utils.cpp
Normal file
522
Dalamud.Boot/utils.cpp
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
std::filesystem::path utils::loaded_module::path() const {
|
||||
std::wstring buf(MAX_PATH, L'\0');
|
||||
for (;;) {
|
||||
if (const auto len = GetModuleFileNameExW(GetCurrentProcess(), m_hModule, &buf[0], static_cast<DWORD>(buf.size())); len != buf.size()) {
|
||||
if (buf.empty())
|
||||
throw std::runtime_error(std::format("Failed to resolve module path: Win32 error {}", GetLastError()));
|
||||
buf.resize(len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (buf.size() * 2 < PATHCCH_MAX_CCH)
|
||||
buf.resize(buf.size() * 2);
|
||||
else if (auto p = std::filesystem::path(buf); exists(p))
|
||||
return p;
|
||||
else
|
||||
throw std::runtime_error("Failed to resolve module path: no amount of buffer size would fit the data");
|
||||
}
|
||||
}
|
||||
|
||||
bool utils::loaded_module::owns_address(const void* pAddress) const {
|
||||
const auto pcAddress = reinterpret_cast<const char*>(pAddress);
|
||||
const auto pcModule = reinterpret_cast<const char*>(m_hModule);
|
||||
return pcModule <= pcAddress && pcAddress <= pcModule + (is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage);
|
||||
}
|
||||
|
||||
std::span<IMAGE_SECTION_HEADER> utils::loaded_module::section_headers() const {
|
||||
const auto& dosHeader = ref_as<IMAGE_DOS_HEADER>(0);
|
||||
const auto& ntHeader32 = ref_as<IMAGE_NT_HEADERS32>(dosHeader.e_lfanew);
|
||||
// Since this does not refer to OptionalHeader32/64 else than its offset, we can use either.
|
||||
return { IMAGE_FIRST_SECTION(&ntHeader32), ntHeader32.FileHeader.NumberOfSections };
|
||||
}
|
||||
|
||||
IMAGE_SECTION_HEADER& utils::loaded_module::section_header(const char* pcszSectionName) const {
|
||||
for (auto& section : section_headers()) {
|
||||
if (strncmp(reinterpret_cast<const char*>(section.Name), pcszSectionName, IMAGE_SIZEOF_SHORT_NAME) == 0)
|
||||
return section;
|
||||
}
|
||||
|
||||
throw std::out_of_range(std::format("Section [{}] not found", pcszSectionName));
|
||||
}
|
||||
|
||||
std::span<char> utils::loaded_module::section(size_t index) const {
|
||||
auto& sectionHeader = section_headers()[index];
|
||||
return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize };
|
||||
}
|
||||
|
||||
std::span<char> utils::loaded_module::section(const char* pcszSectionName) const {
|
||||
auto& sectionHeader = section_header(pcszSectionName);
|
||||
return { address(sectionHeader.VirtualAddress), sectionHeader.Misc.VirtualSize };
|
||||
}
|
||||
|
||||
template<typename TEntryType>
|
||||
static bool find_imported_function_pointer_helper(const char* pcBaseAddress, const IMAGE_IMPORT_DESCRIPTOR& desc, const IMAGE_DATA_DIRECTORY& dir, std::string_view reqFunc, uint32_t hintOrOrdinal, void*& ppFunctionAddress) {
|
||||
const auto importLookupsOversizedSpan = std::span(reinterpret_cast<const TEntryType*>(&pcBaseAddress[desc.OriginalFirstThunk]), (dir.Size - desc.OriginalFirstThunk) / sizeof(TEntryType));
|
||||
const auto importAddressesOversizedSpan = std::span(reinterpret_cast<const TEntryType*>(&pcBaseAddress[desc.FirstThunk]), (dir.Size - desc.FirstThunk) / sizeof(TEntryType));
|
||||
|
||||
for (size_t i = 0, i_ = (std::min)(importLookupsOversizedSpan.size(), importAddressesOversizedSpan.size()); i < i_ && importLookupsOversizedSpan[i] && importAddressesOversizedSpan[i]; i++) {
|
||||
const auto& importLookup = importLookupsOversizedSpan[i];
|
||||
const auto& importAddress = importAddressesOversizedSpan[i];
|
||||
const auto& importByName = *reinterpret_cast<const IMAGE_IMPORT_BY_NAME*>(&pcBaseAddress[importLookup]);
|
||||
|
||||
// Is this entry importing by ordinals? A lot of socket functions are the case.
|
||||
if (IMAGE_SNAP_BY_ORDINAL32(importLookup)) {
|
||||
|
||||
// Is this the entry?
|
||||
if (!hintOrOrdinal || IMAGE_ORDINAL32(importLookup) != hintOrOrdinal)
|
||||
continue;
|
||||
|
||||
// Is this entry not importing by ordinals, and are we using hint exclusively to find the entry?
|
||||
} else if (reqFunc.empty()) {
|
||||
|
||||
// Is this the entry?
|
||||
if (importByName.Hint != hintOrOrdinal)
|
||||
continue;
|
||||
|
||||
} else {
|
||||
|
||||
// Name must be contained in this directory.
|
||||
auto currFunc = std::string_view(importByName.Name, (std::min<size_t>)(&pcBaseAddress[dir.Size] - importByName.Name, reqFunc.size()));
|
||||
currFunc = currFunc.substr(0, strnlen(currFunc.data(), currFunc.size()));
|
||||
|
||||
// Is this the entry? (Case sensitive)
|
||||
if (reqFunc != currFunc)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found the entry; return the address of the pointer to the target function.
|
||||
ppFunctionAddress = const_cast<void*>(reinterpret_cast<const void*>(&importAddress));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool utils::loaded_module::find_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress) const {
|
||||
const auto requestedDllName = std::string_view(pcszDllName, strlen(pcszDllName));
|
||||
const auto requestedFunctionName = pcszFunctionName ? std::string_view(pcszFunctionName, strlen(pcszFunctionName)) : std::string_view();
|
||||
const auto& directory = data_directory(IMAGE_DIRECTORY_ENTRY_IMPORT);
|
||||
ppFunctionAddress = nullptr;
|
||||
|
||||
// This span might be too long in terms of meaningful data; it only serves to prevent accessing memory outsides boundaries.
|
||||
for (const auto& importDescriptor : span_as<IMAGE_IMPORT_DESCRIPTOR>(directory.VirtualAddress, directory.Size / sizeof IMAGE_IMPORT_DESCRIPTOR)) {
|
||||
|
||||
// Having all zero values signals the end of the table. We didn't find anything.
|
||||
if (!importDescriptor.OriginalFirstThunk && !importDescriptor.TimeDateStamp && !importDescriptor.ForwarderChain && !importDescriptor.FirstThunk)
|
||||
return false;
|
||||
|
||||
// Skip invalid entries, just in case.
|
||||
if (!importDescriptor.Name || !importDescriptor.OriginalFirstThunk)
|
||||
continue;
|
||||
|
||||
// Name must be contained in this directory.
|
||||
if (importDescriptor.Name < directory.VirtualAddress)
|
||||
continue;
|
||||
auto currentDllName = std::string_view(address_as<char>(importDescriptor.Name), (std::min<size_t>)(directory.Size - importDescriptor.Name, requestedDllName.size()));
|
||||
currentDllName = currentDllName.substr(0, strnlen(currentDllName.data(), currentDllName.size()));
|
||||
|
||||
// Is this entry about the DLL that we're looking for? (Case insensitive)
|
||||
if (requestedDllName.size() != currentDllName.size() || _strcmpi(requestedDllName.data(), currentDllName.data()))
|
||||
continue;
|
||||
|
||||
if (is_pe64()) {
|
||||
if (find_imported_function_pointer_helper<uint64_t>(address(), importDescriptor, directory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress))
|
||||
return true;
|
||||
} else {
|
||||
if (find_imported_function_pointer_helper<uint32_t>(address(), importDescriptor, directory, requestedFunctionName, hintOrOrdinal, ppFunctionAddress))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Found nothing.
|
||||
return false;
|
||||
}
|
||||
|
||||
void* utils::loaded_module::get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const {
|
||||
if (void* ppImportTableItem{}; find_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal, ppImportTableItem))
|
||||
return ppImportTableItem;
|
||||
|
||||
throw std::runtime_error("Failed to find import for kernel32!OpenProcess.");
|
||||
}
|
||||
|
||||
utils::loaded_module utils::loaded_module::current_process() {
|
||||
return { GetModuleHandleW(nullptr) };
|
||||
}
|
||||
|
||||
std::vector<utils::loaded_module> utils::loaded_module::all_modules() {
|
||||
std::vector<HMODULE> hModules(128);
|
||||
for (DWORD dwNeeded{}; EnumProcessModules(GetCurrentProcess(), &hModules[0], static_cast<DWORD>(std::span(hModules).size_bytes()), &dwNeeded) && hModules.size() < dwNeeded;)
|
||||
hModules.resize(hModules.size() + 128);
|
||||
|
||||
std::vector<loaded_module> modules;
|
||||
modules.reserve(hModules.size());
|
||||
for (const auto hModule : hModules) {
|
||||
if (!hModule)
|
||||
break;
|
||||
modules.emplace_back(hModule);
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_in(const void* pFirst, size_t length) {
|
||||
if (length)
|
||||
m_ranges.emplace_back(std::span(reinterpret_cast<const char*>(pFirst), length));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_in(const loaded_module& m, const char* sectionName) {
|
||||
return look_in(m.section(sectionName));
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern, std::string_view mask, char cExactMatch, char cWildcard) {
|
||||
if (pattern.size() != mask.size())
|
||||
throw std::runtime_error("Length of pattern does not match the length of mask.");
|
||||
|
||||
std::string buf;
|
||||
buf.reserve(pattern.size() * 4);
|
||||
for (size_t i = 0; i < pattern.size(); i++) {
|
||||
const auto c = pattern[i];
|
||||
if (mask[i] == cWildcard) {
|
||||
buf.push_back('.');
|
||||
} else if (mask[i] == cExactMatch) {
|
||||
buf.push_back('\\');
|
||||
buf.push_back('x');
|
||||
buf.push_back((c >> 4) < 10 ? (c >> 4) - 10 : 'A' + (c >> 4) - 10);
|
||||
buf.push_back((c & 15) < 10 ? (c & 15) - 10 : 'A' + (c & 15) - 10);
|
||||
}
|
||||
}
|
||||
m_patterns.emplace_back(buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern, char wildcardMask) {
|
||||
std::string buf;
|
||||
buf.reserve(pattern.size() * 4);
|
||||
for (const auto& c : pattern) {
|
||||
if (c == wildcardMask) {
|
||||
buf.push_back('.');
|
||||
} else {
|
||||
buf.push_back('\\');
|
||||
buf.push_back('x');
|
||||
buf.push_back((c >> 4) < 10 ? '0' + (c >> 4) : 'A' + (c >> 4) - 10);
|
||||
buf.push_back((c & 15) < 10 ? '0' + (c & 15) : 'A' + (c & 15) - 10);
|
||||
}
|
||||
}
|
||||
m_patterns.emplace_back(buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_for(std::string_view pattern) {
|
||||
std::string buf;
|
||||
buf.reserve(pattern.size() * 4);
|
||||
for (const auto& c : pattern) {
|
||||
buf.push_back('\\');
|
||||
buf.push_back('x');
|
||||
buf.push_back((c >> 4) < 10 ? '0' + (c >> 4) : 'A' + (c >> 4) - 10);
|
||||
buf.push_back((c & 15) < 10 ? '0' + (c & 15) : 'A' + (c & 15) - 10);
|
||||
}
|
||||
m_patterns.emplace_back(buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
utils::signature_finder& utils::signature_finder::look_for_hex(std::string_view pattern) {
|
||||
std::string buf;
|
||||
buf.reserve(pattern.size());
|
||||
bool bHighByte = true;
|
||||
for (size_t i = 0; i < pattern.size(); i++) {
|
||||
int n = -1;
|
||||
if ('0' <= pattern[i] && pattern[i] <= '9')
|
||||
n = pattern[i] - '0';
|
||||
else if ('a' <= pattern[i] && pattern[i] <= 'f')
|
||||
n = 10 + pattern[i] - 'A';
|
||||
else if ('A' <= pattern[i] && pattern[i] <= 'F')
|
||||
n = 10 + pattern[i] - 'A';
|
||||
else if (pattern[i] == '?' && i + 1 < pattern.size() && pattern[i + 1] == '?') {
|
||||
i++;
|
||||
n = -2;
|
||||
} else if (pattern[i] == '?')
|
||||
n = -2;
|
||||
|
||||
if (n == -1)
|
||||
continue;
|
||||
else if (n == -2) {
|
||||
if (!bHighByte) {
|
||||
buf.insert(buf.begin() + buf.size() - 1, '0');
|
||||
bHighByte = true;
|
||||
}
|
||||
buf.push_back('.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bHighByte) {
|
||||
buf.push_back('\\');
|
||||
buf.push_back('x');
|
||||
}
|
||||
buf.push_back(pattern[i]);
|
||||
bHighByte = !bHighByte;
|
||||
}
|
||||
m_patterns.emplace_back(buf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<utils::signature_finder::result> utils::signature_finder::find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const {
|
||||
std::vector<result> res;
|
||||
|
||||
for (const auto& rangeSpan : m_ranges) {
|
||||
for (size_t patternIndex = 0; patternIndex < m_patterns.size(); patternIndex++) {
|
||||
srell::match_results<std::span<const char>::iterator> matches;
|
||||
auto ptr = rangeSpan.begin();
|
||||
for (size_t matchIndex = 0;; ptr = matches[0].first + 1, matchIndex++) {
|
||||
if (!m_patterns[patternIndex].search(ptr, rangeSpan.end(), rangeSpan.begin(), matches, srell::regex_constants::match_flag_type::match_default))
|
||||
break;
|
||||
|
||||
for (size_t captureIndex = 0; captureIndex < matches.size(); captureIndex++) {
|
||||
const auto& capture = matches[captureIndex];
|
||||
res.emplace_back(
|
||||
std::span(capture.first, capture.second),
|
||||
patternIndex,
|
||||
matchIndex,
|
||||
captureIndex);
|
||||
|
||||
if (bErrorOnMoreThanMaximum) {
|
||||
if (res.size() > maxCount)
|
||||
throw std::runtime_error(std::format("Found {} result(s), wanted at most {} results", res.size(), maxCount));
|
||||
} else if (res.size() == maxCount)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.size() < minCount)
|
||||
throw std::runtime_error(std::format("Found {} result(s), wanted at least {} results", res.size(), minCount));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::span<const char> utils::signature_finder::find_one() const {
|
||||
return find(1, 1, false).front().Match;
|
||||
}
|
||||
|
||||
utils::memory_tenderizer::memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect) : m_data(reinterpret_cast<char*>(const_cast<void*>(pAddress)), length) {
|
||||
try {
|
||||
for (auto pCoveredAddress = &m_data[0];
|
||||
pCoveredAddress < &m_data[0] + m_data.size();
|
||||
pCoveredAddress = reinterpret_cast<char*>(m_regions.back().BaseAddress) + m_regions.back().RegionSize) {
|
||||
|
||||
MEMORY_BASIC_INFORMATION region{};
|
||||
if (!VirtualQuery(pCoveredAddress, ®ion, sizeof region)) {
|
||||
throw std::runtime_error(std::format(
|
||||
"VirtualQuery(addr=0x{:X}, ..., cb={}) failed with Win32 code 0x{:X}",
|
||||
reinterpret_cast<size_t>(pCoveredAddress),
|
||||
sizeof region,
|
||||
GetLastError()));
|
||||
}
|
||||
|
||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, dwNewProtect, ®ion.Protect)) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(Change)VirtualProtect(addr=0x{:X}, size=0x{:X}, ..., ...) failed with Win32 code 0x{:X}",
|
||||
reinterpret_cast<size_t>(region.BaseAddress),
|
||||
region.RegionSize,
|
||||
GetLastError()));
|
||||
}
|
||||
|
||||
m_regions.emplace_back(region);
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
||||
// Could not restore; fast fail
|
||||
__fastfail(GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
utils::memory_tenderizer::~memory_tenderizer() {
|
||||
for (auto& region : std::ranges::reverse_view(m_regions)) {
|
||||
if (!VirtualProtect(region.BaseAddress, region.RegionSize, region.Protect, ®ion.Protect)) {
|
||||
// Could not restore; fast fail
|
||||
__fastfail(GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<void> utils::allocate_executable_heap(size_t len) {
|
||||
static std::weak_ptr<void> s_hHeap;
|
||||
|
||||
std::shared_ptr<void> hHeap;
|
||||
if (hHeap = s_hHeap.lock(); !hHeap) {
|
||||
static std::mutex m_mtx;
|
||||
const auto lock = std::lock_guard(m_mtx);
|
||||
|
||||
if (hHeap = s_hHeap.lock(); !hHeap) {
|
||||
if (const auto hHeapRaw = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0); hHeapRaw)
|
||||
s_hHeap = hHeap = std::shared_ptr<void>(hHeapRaw, HeapDestroy);
|
||||
else
|
||||
throw std::runtime_error("Failed to create heap.");
|
||||
}
|
||||
}
|
||||
|
||||
const auto pAllocRaw = HeapAlloc(hHeap.get(), 0, len);
|
||||
if (!pAllocRaw)
|
||||
throw std::runtime_error("Failed to allocate memory.");
|
||||
|
||||
return {
|
||||
pAllocRaw,
|
||||
[hHeap = std::move(hHeap)](void* pAddress) { HeapFree(hHeap.get(), 0, pAddress); },
|
||||
};
|
||||
}
|
||||
|
||||
std::shared_ptr<void> utils::create_thunk(void* pfnFunction, void* pThis, uint64_t placeholderValue) {
|
||||
const auto pcBaseFn = reinterpret_cast<const uint8_t*>(pfnFunction);
|
||||
auto sourceCode = std::vector<uint8_t>(pcBaseFn, pcBaseFn + 256);
|
||||
|
||||
size_t i = 0;
|
||||
auto placeholderFound = false;
|
||||
for (nmd_x86_instruction instruction{}; ; i += instruction.length) {
|
||||
if (i == sourceCode.size() || !nmd_x86_decode(&sourceCode[i], sourceCode.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
|
||||
sourceCode.insert(sourceCode.end(), &pcBaseFn[sourceCode.size()], &pcBaseFn[sourceCode.size() + 512]);
|
||||
if (!nmd_x86_decode(&sourceCode[i], sourceCode.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL))
|
||||
throw std::runtime_error("Failed to find detour function");
|
||||
}
|
||||
|
||||
if (instruction.opcode == 0xCC)
|
||||
throw std::runtime_error("Failed to find detour function");
|
||||
|
||||
// msvc debugger related
|
||||
if ((instruction.group & NMD_GROUP_CALL) && (instruction.imm_mask & NMD_X86_IMM_ANY))
|
||||
std::fill_n(&sourceCode[i], instruction.length, 0x90);
|
||||
|
||||
if ((instruction.group & NMD_GROUP_JUMP) || (instruction.group & NMD_GROUP_RET)) {
|
||||
sourceCode.resize(i + instruction.length);
|
||||
break;
|
||||
}
|
||||
|
||||
if (instruction.opcode == 0xB8 // mov <register>, <thunk placeholder 64bit value>
|
||||
&& (instruction.imm_mask & NMD_X86_IMM64)
|
||||
&& instruction.immediate == placeholderValue) {
|
||||
*reinterpret_cast<void**>(&sourceCode[i + instruction.length - 8]) = pThis;
|
||||
placeholderFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!placeholderFound)
|
||||
throw std::runtime_error("Failed to find detour function");
|
||||
|
||||
return allocate_executable_heap(std::span(sourceCode));
|
||||
}
|
||||
|
||||
template<>
|
||||
std::wstring utils::get_env(const wchar_t* pcwzName) {
|
||||
std::wstring buf(GetEnvironmentVariableW(pcwzName, nullptr, 0) + 1, L'\0');
|
||||
buf.resize(GetEnvironmentVariableW(pcwzName, &buf[0], static_cast<DWORD>(buf.size())));
|
||||
return buf;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string utils::get_env(const wchar_t* pcwzName) {
|
||||
return unicode::convert<std::string>(get_env<std::wstring>(pcwzName));
|
||||
}
|
||||
|
||||
template<>
|
||||
int utils::get_env(const wchar_t* pcwzName) {
|
||||
auto env = get_env<std::wstring>(pcwzName);
|
||||
const auto trimmed = trim(std::wstring_view(env));
|
||||
if (trimmed.empty())
|
||||
return 0;
|
||||
return std::wcstol(&trimmed[0], nullptr, 0);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool utils::get_env(const wchar_t* pcwzName) {
|
||||
auto env = get_env<std::wstring>(pcwzName);
|
||||
const auto trimmed = trim(std::wstring_view(env));
|
||||
for (auto& c : env) {
|
||||
if (c < 255)
|
||||
c = std::tolower(c);
|
||||
}
|
||||
return trimmed == L"1"
|
||||
|| trimmed == L"true"
|
||||
|| trimmed == L"t"
|
||||
|| trimmed == L"yes"
|
||||
|| trimmed == L"y";
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector<std::wstring> utils::get_env_list(const wchar_t* pcszName) {
|
||||
const auto src = utils::get_env<std::wstring>(pcszName);
|
||||
auto res = utils::split(src, L",");
|
||||
for (auto& s : res)
|
||||
s = utils::trim(s);
|
||||
if (res.size() == 1 && res[0].empty())
|
||||
return {};
|
||||
return res;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
|
||||
const auto src = utils::get_env<std::string>(pcszName);
|
||||
auto res = utils::split(src, ",");
|
||||
for (auto& s : res)
|
||||
s = utils::trim(s);
|
||||
if (res.size() == 1 && res[0].empty())
|
||||
return {};
|
||||
return res;
|
||||
}
|
||||
|
||||
bool utils::is_running_on_linux() {
|
||||
if (get_env<bool>(L"XL_WINEONLINUX"))
|
||||
return true;
|
||||
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hntdll)
|
||||
return true;
|
||||
if (GetProcAddress(hntdll, "wine_get_version"))
|
||||
return true;
|
||||
if (GetProcAddress(hntdll, "wine_get_host_version"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path utils::get_module_path(HMODULE hModule) {
|
||||
std::wstring buf(MAX_PATH, L'\0');
|
||||
while (true) {
|
||||
if (const auto res = GetModuleFileNameW(hModule, &buf[0], static_cast<int>(buf.size())); !res)
|
||||
throw std::runtime_error(std::format("GetModuleFileName failure: 0x{:X}", GetLastError()));
|
||||
else if (res < buf.size()) {
|
||||
buf.resize(res);
|
||||
return buf;
|
||||
} else
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
HWND utils::try_find_game_window() {
|
||||
HWND hwnd = nullptr;
|
||||
while ((hwnd = FindWindowExW(nullptr, hwnd, L"FFXIVGAME", nullptr))) {
|
||||
DWORD pid;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
|
||||
if (pid == GetCurrentProcessId() && IsWindowVisible(hwnd))
|
||||
break;
|
||||
}
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
void utils::wait_for_game_window() {
|
||||
HWND game_window;
|
||||
while (!(game_window = try_find_game_window())) {
|
||||
WaitForInputIdle(GetCurrentProcess(), INFINITE);
|
||||
Sleep(100);
|
||||
};
|
||||
SendMessageW(game_window, WM_NULL, 0, 0);
|
||||
}
|
||||
263
Dalamud.Boot/utils.h
Normal file
263
Dalamud.Boot/utils.h
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "unicode.h"
|
||||
|
||||
namespace utils {
|
||||
class loaded_module {
|
||||
HMODULE m_hModule;
|
||||
public:
|
||||
loaded_module() : m_hModule(nullptr) {}
|
||||
loaded_module(const void* hModule) : m_hModule(reinterpret_cast<HMODULE>(const_cast<void*>(hModule))) {}
|
||||
loaded_module(void* hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
|
||||
loaded_module(size_t hModule) : m_hModule(reinterpret_cast<HMODULE>(hModule)) {}
|
||||
|
||||
std::filesystem::path path() const;
|
||||
|
||||
bool is_current_process() const { return m_hModule == GetModuleHandleW(nullptr); }
|
||||
bool owns_address(const void* pAddress) const;
|
||||
|
||||
operator HMODULE() const {
|
||||
return m_hModule;
|
||||
}
|
||||
|
||||
size_t address_int() const { return reinterpret_cast<size_t>(m_hModule); }
|
||||
size_t image_size() const { return is_pe64() ? nt_header64().OptionalHeader.SizeOfImage : nt_header32().OptionalHeader.SizeOfImage; }
|
||||
char* address(size_t offset = 0) const { return reinterpret_cast<char*>(m_hModule) + offset; }
|
||||
template<typename T> T* address_as(size_t offset) const { return reinterpret_cast<T*>(address(offset)); }
|
||||
template<typename T> std::span<T> span_as(size_t offset, size_t count) const { return std::span<T>(reinterpret_cast<T*>(address(offset)), count); }
|
||||
template<typename T> T& ref_as(size_t offset) const { return *reinterpret_cast<T*>(address(offset)); }
|
||||
|
||||
IMAGE_DOS_HEADER& dos_header() const { return ref_as<IMAGE_DOS_HEADER>(0); }
|
||||
IMAGE_NT_HEADERS32& nt_header32() const { return ref_as<IMAGE_NT_HEADERS32>(dos_header().e_lfanew); }
|
||||
IMAGE_NT_HEADERS64& nt_header64() const { return ref_as<IMAGE_NT_HEADERS64>(dos_header().e_lfanew); }
|
||||
bool is_pe64() const { return nt_header32().OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC; }
|
||||
|
||||
std::span<IMAGE_DATA_DIRECTORY> data_directories() const { return is_pe64() ? nt_header64().OptionalHeader.DataDirectory : nt_header32().OptionalHeader.DataDirectory; }
|
||||
IMAGE_DATA_DIRECTORY& data_directory(size_t index) const { return data_directories()[index]; }
|
||||
|
||||
std::span<IMAGE_SECTION_HEADER> section_headers() const;
|
||||
IMAGE_SECTION_HEADER& section_header(const char* pcszSectionName) const;
|
||||
std::span<char> section(size_t index) const;
|
||||
std::span<char> section(const char* pcszSectionName) const;
|
||||
|
||||
template<typename TFn> TFn* get_exported_function(const char* pcszFunctionName) {
|
||||
const auto pAddress = GetProcAddress(m_hModule, pcszFunctionName);
|
||||
if (!pAddress)
|
||||
throw std::out_of_range(std::format("Exported function \"{}\" not found.", pcszFunctionName));
|
||||
return reinterpret_cast<TFn*>(pAddress);
|
||||
}
|
||||
|
||||
bool find_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal, void*& ppFunctionAddress) const;
|
||||
void* get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) const;
|
||||
template<typename TFn> TFn** get_imported_function_pointer(const char* pcszDllName, const char* pcszFunctionName, uint32_t hintOrOrdinal) { return reinterpret_cast<TFn**>(get_imported_function_pointer(pcszDllName, pcszFunctionName, hintOrOrdinal)); }
|
||||
|
||||
static loaded_module current_process();
|
||||
static std::vector<loaded_module> all_modules();
|
||||
};
|
||||
|
||||
class signature_finder {
|
||||
std::vector<std::span<const char>> m_ranges;
|
||||
std::vector<srell::regex> m_patterns;
|
||||
|
||||
public:
|
||||
signature_finder& look_in(const void* pFirst, size_t length);
|
||||
signature_finder& look_in(const loaded_module& m, const char* sectionName);
|
||||
|
||||
template<typename T>
|
||||
signature_finder& look_in(std::span<T> s) {
|
||||
return look_in(s.data(), s.size());
|
||||
}
|
||||
|
||||
signature_finder& look_for(std::string_view pattern, std::string_view mask, char cExactMatch = 'x', char cWildcard = '.');
|
||||
signature_finder& look_for(std::string_view pattern, char wildcardMask);
|
||||
signature_finder& look_for(std::string_view pattern);
|
||||
signature_finder& look_for_hex(std::string_view pattern);
|
||||
|
||||
template<size_t len>
|
||||
signature_finder& look_for(char pattern[len]) {
|
||||
static_assert(len == 5);
|
||||
}
|
||||
|
||||
struct result {
|
||||
std::span<const char> Match;
|
||||
size_t PatternIndex;
|
||||
size_t MatchIndex;
|
||||
size_t CaptureIndex;
|
||||
};
|
||||
|
||||
std::vector<result> find(size_t minCount, size_t maxCount, bool bErrorOnMoreThanMaximum) const;
|
||||
|
||||
std::span<const char> find_one() const;
|
||||
};
|
||||
|
||||
class memory_tenderizer {
|
||||
std::span<char> m_data;
|
||||
std::vector<MEMORY_BASIC_INFORMATION> m_regions;
|
||||
|
||||
public:
|
||||
memory_tenderizer(const void* pAddress, size_t length, DWORD dwNewProtect);
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_trivial_v<T>&& std::is_standard_layout_v<T>>>
|
||||
memory_tenderizer(const T& object, DWORD dwNewProtect) : memory_tenderizer(&object, sizeof T, dwNewProtect) {}
|
||||
|
||||
template<typename T>
|
||||
memory_tenderizer(std::span<const T> s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {}
|
||||
|
||||
template<typename T>
|
||||
memory_tenderizer(std::span<T> s, DWORD dwNewProtect) : memory_tenderizer(&s[0], s.size(), dwNewProtect) {}
|
||||
|
||||
~memory_tenderizer();
|
||||
};
|
||||
|
||||
std::shared_ptr<void> allocate_executable_heap(size_t len);
|
||||
|
||||
template<typename T>
|
||||
std::shared_ptr<void> allocate_executable_heap(std::span<T> data) {
|
||||
auto res = allocate_executable_heap(data.size_bytes());
|
||||
memcpy(res.get(), data.data(), data.size_bytes());
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<void> create_thunk(void* pfnFunction, void* pThis, uint64_t placeholderValue);
|
||||
|
||||
template<typename>
|
||||
class thunk;
|
||||
|
||||
template<typename TReturn, typename ... TArgs>
|
||||
class thunk<TReturn(TArgs...)> {
|
||||
using TFn = TReturn(TArgs...);
|
||||
|
||||
static constexpr uint64_t Placeholder = 0xCC90CC90CC90CC90ULL;
|
||||
|
||||
const std::shared_ptr<void> m_pThunk;
|
||||
std::string m_name;
|
||||
std::function<TFn> m_fnTarget;
|
||||
|
||||
public:
|
||||
thunk(std::string name, std::function<TFn> target)
|
||||
: m_pThunk(utils::create_thunk(&detour_static, this, Placeholder))
|
||||
, m_fnTarget(std::move(target))
|
||||
, m_name(name) {
|
||||
}
|
||||
|
||||
void set_target(std::function<TFn> detour) {
|
||||
m_fnTarget = std::move(detour);
|
||||
}
|
||||
|
||||
TFn* get_thunk() const {
|
||||
return reinterpret_cast<TFn*>(m_pThunk.get());
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
private:
|
||||
// mark it as virtual to prevent compiler from inlining
|
||||
virtual TReturn detour(TArgs... args) {
|
||||
return m_fnTarget(std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
static TReturn detour_static(TArgs... args) {
|
||||
const volatile auto pThis = reinterpret_cast<thunk<TFn>*>(Placeholder);
|
||||
return pThis->detour(args...);
|
||||
}
|
||||
};
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>>
|
||||
std::basic_string_view<TElem, TTraits> trim(std::basic_string_view<TElem, TTraits> view, bool left = true, bool right = true) {
|
||||
if (left) {
|
||||
while (!view.empty() && (view.front() < 255 && std::isspace(view.front())))
|
||||
view = view.substr(1);
|
||||
}
|
||||
if (right) {
|
||||
while (!view.empty() && (view.back() < 255 && std::isspace(view.back())))
|
||||
view = view.substr(0, view.size() - 1);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
std::basic_string<TElem, TTraits> trim(std::basic_string<TElem, TTraits> view, bool left = true, bool right = true) {
|
||||
return std::basic_string<TElem, TTraits, TAlloc>(trim(std::basic_string_view<TElem, TTraits>(view), left, right));
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const std::basic_string_view<TElem, TTraits>& delimiter, size_t maxSplit = SIZE_MAX) {
|
||||
std::vector<std::basic_string<TElem, TTraits, TAlloc>> result;
|
||||
if (delimiter.empty()) {
|
||||
for (size_t i = 0; i < str.size(); ++i)
|
||||
result.push_back(str.substr(i, 1));
|
||||
} else {
|
||||
size_t previousOffset = 0, offset;
|
||||
while (maxSplit && (offset = str.find(delimiter, previousOffset)) != std::string::npos) {
|
||||
result.push_back(str.substr(previousOffset, offset - previousOffset));
|
||||
previousOffset = offset + delimiter.length();
|
||||
--maxSplit;
|
||||
}
|
||||
result.push_back(str.substr(previousOffset));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const std::basic_string<TElem, TTraits, TAlloc>& delimiter, size_t maxSplit = SIZE_MAX) {
|
||||
return split(str, std::basic_string_view<TElem, TTraits>(delimiter), maxSplit);
|
||||
}
|
||||
|
||||
template<class TElem, class TTraits = std::char_traits<TElem>, class TAlloc = std::allocator<TElem>>
|
||||
[[nodiscard]] std::vector<std::basic_string<TElem, TTraits, TAlloc>> split(const std::basic_string<TElem, TTraits, TAlloc>& str, const TElem* pcszDelimiter, size_t maxSplit = SIZE_MAX) {
|
||||
return split(str, std::basic_string_view<TElem, TTraits>(pcszDelimiter), maxSplit);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get_env(const wchar_t* pcwzName) = delete;
|
||||
|
||||
template<>
|
||||
std::wstring get_env(const wchar_t* pcwzName);
|
||||
|
||||
template<>
|
||||
std::string get_env(const wchar_t* pcwzName);
|
||||
|
||||
template<>
|
||||
int get_env(const wchar_t* pcwzName);
|
||||
|
||||
template<>
|
||||
bool get_env(const wchar_t* pcwzName);
|
||||
|
||||
template<typename T>
|
||||
T get_env(const char* pcszName) {
|
||||
return get_env<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> get_env_list(const wchar_t* pcwzName) = delete;
|
||||
|
||||
template<>
|
||||
std::vector<std::wstring> get_env_list(const wchar_t* pcwzName);
|
||||
|
||||
template<>
|
||||
std::vector<std::string> get_env_list(const wchar_t* pcwzName);
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> get_env_list(const char* pcszName) {
|
||||
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
|
||||
}
|
||||
|
||||
bool is_running_on_linux();
|
||||
|
||||
std::filesystem::path get_module_path(HMODULE hModule);
|
||||
|
||||
/// @brief Find the game main window.
|
||||
/// @return Handle to the game main window, or nullptr if it doesn't exist (yet).
|
||||
HWND try_find_game_window();
|
||||
|
||||
void wait_for_game_window();
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include "veh.h"
|
||||
|
||||
PVOID g_veh_handle = nullptr;
|
||||
bool g_veh_do_full_dump = false;
|
||||
|
||||
bool is_whitelist_exception(const DWORD code)
|
||||
{
|
||||
switch (code)
|
||||
|
|
@ -251,8 +254,12 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
|||
ex_info.ExceptionPointers = ex;
|
||||
ex_info.ThreadId = GetCurrentThreadId();
|
||||
|
||||
auto miniDumpType = MiniDumpWithDataSegs;
|
||||
if (g_veh_do_full_dump)
|
||||
miniDumpType = MiniDumpWithFullMemory;
|
||||
|
||||
HANDLE file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpWithDataSegs, &ex_info, nullptr, nullptr);
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr);
|
||||
CloseHandle(file);
|
||||
|
||||
void* fn;
|
||||
|
|
@ -262,16 +269,18 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
|||
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
|
||||
nullptr, nullptr, &fn)))
|
||||
{
|
||||
const auto msg = L"An error within the game has occurred.\n\n"
|
||||
const auto formatted = std::format(
|
||||
L"An error within the game has occurred.\n\n"
|
||||
L"This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n"
|
||||
L"Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n"
|
||||
L"The log file is located at:\n"
|
||||
L"{1}\n\n"
|
||||
L"Press OK to exit the application.\n\nFailed to read stack trace: {2:08x}";
|
||||
L"Press OK to exit the application.\n\nFailed to read stack trace: {2:08x}",
|
||||
dmp_path, log_path, err);
|
||||
|
||||
// show in another thread to prevent messagebox from pumping messages of current thread
|
||||
std::thread([&]() {
|
||||
MessageBoxW(nullptr, std::format(msg, dmp_path, log_path, err).c_str(), L"Dalamud Error", MB_OK | MB_ICONERROR | MB_TOPMOST);
|
||||
MessageBoxW(nullptr, formatted.c_str(), L"Dalamud Error", MB_OK | MB_ICONERROR | MB_TOPMOST);
|
||||
}).join();
|
||||
}
|
||||
else
|
||||
|
|
@ -282,15 +291,15 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
|
|||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
|
||||
PVOID g_veh_handle = nullptr;
|
||||
|
||||
bool veh::add_handler()
|
||||
bool veh::add_handler(bool doFullDump)
|
||||
{
|
||||
if (g_veh_handle)
|
||||
return false;
|
||||
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
|
||||
SetUnhandledExceptionFilter(nullptr);
|
||||
|
||||
g_veh_do_full_dump = doFullDump;
|
||||
|
||||
return g_veh_handle != nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
namespace veh
|
||||
{
|
||||
bool add_handler();
|
||||
bool add_handler(bool doFullDump);
|
||||
bool remove_handler();
|
||||
}
|
||||
|
|
|
|||
426
Dalamud.Boot/xivfixes.cpp
Normal file
426
Dalamud.Boot/xivfixes.cpp
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
#include "pch.h"
|
||||
|
||||
#include "xivfixes.h"
|
||||
|
||||
#include "DalamudStartInfo.h"
|
||||
#include "hooks.h"
|
||||
#include "logging.h"
|
||||
#include "utils.h"
|
||||
|
||||
template<typename T>
|
||||
static std::span<T> assume_nonempty_span(std::span<T> t, const char* descr) {
|
||||
if (t.empty())
|
||||
throw std::runtime_error(std::format("Unexpected empty span found: {}", descr));
|
||||
return t;
|
||||
}
|
||||
void xivfixes::unhook_dll(bool bApply) {
|
||||
static const auto LogTag = "[xivfixes:unhook_dll]";
|
||||
static const auto LogTagW = L"[xivfixes:unhook_dll]";
|
||||
|
||||
if (!bApply)
|
||||
return;
|
||||
|
||||
const auto mods = utils::loaded_module::all_modules();
|
||||
|
||||
const auto test_module = [&](size_t i, const utils::loaded_module & mod) {
|
||||
std::filesystem::path path;
|
||||
try {
|
||||
path = mod.path();
|
||||
logging::I("{} [{}/{}] Module 0x{:X} ~ 0x{:X} (0x{:X}): \"{}\"", LogTagW, i + 1, mods.size(), mod.address_int(), mod.address_int() + mod.image_size(), mod.image_size(), path.wstring());
|
||||
} catch (const std::exception& e) {
|
||||
logging::W("{} [{}/{}] Module 0x{:X}: Failed to resolve path: {}", LogTag, i + 1, mods.size(), mod.address_int(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto moduleName = unicode::convert<std::string>(path.filename().wstring());
|
||||
|
||||
std::vector<char> buf;
|
||||
std::string formatBuf;
|
||||
try {
|
||||
const auto& sectionHeader = mod.section_header(".text");
|
||||
const auto section = assume_nonempty_span(mod.span_as<char>(sectionHeader.VirtualAddress, sectionHeader.Misc.VirtualSize), ".text[VA:VA+VS]");
|
||||
auto hFsDllRaw = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (hFsDllRaw == INVALID_HANDLE_VALUE) {
|
||||
logging::W("{} Module loaded in current process but could not open file: Win32 error {}", LogTag, GetLastError());
|
||||
return;
|
||||
}
|
||||
auto hFsDll = std::unique_ptr<void, decltype(CloseHandle)*>(hFsDllRaw, &CloseHandle);
|
||||
|
||||
buf.resize(section.size());
|
||||
SetFilePointer(hFsDll.get(), sectionHeader.PointerToRawData, nullptr, FILE_CURRENT);
|
||||
if (DWORD read{}; ReadFile(hFsDll.get(), &buf[0], static_cast<DWORD>(buf.size()), &read, nullptr)) {
|
||||
if (read < section.size_bytes()) {
|
||||
logging::W("{} ReadFile: read {} bytes < requested {} bytes", LogTagW, read, section.size_bytes());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
logging::I("{} ReadFile: Win32 error {}", LogTagW, GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto doRestore = g_startInfo.BootUnhookDlls.contains(unicode::convert<std::string>(path.filename().u8string()));
|
||||
|
||||
std::optional<utils::memory_tenderizer> tenderizer;
|
||||
for (size_t i = 0, instructionLength = 1, printed = 0; i < buf.size(); i += instructionLength) {
|
||||
if (section[i] == buf[i]) {
|
||||
instructionLength = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto rva = sectionHeader.VirtualAddress + i;
|
||||
nmd_x86_instruction instruction{};
|
||||
if (!nmd_x86_decode(§ion[i], section.size() - i, &instruction, NMD_X86_MODE_64, NMD_X86_DECODER_FLAGS_ALL)) {
|
||||
instructionLength = 1;
|
||||
if (printed < 64) {
|
||||
logging::W("{} {}+0x{:0X}: dd {:02X}", LogTag, moduleName, rva, static_cast<uint8_t>(section[i]));
|
||||
printed++;
|
||||
}
|
||||
} else {
|
||||
instructionLength = instruction.length;
|
||||
if (printed < 64) {
|
||||
formatBuf.resize(128);
|
||||
nmd_x86_format(&instruction, &formatBuf[0], reinterpret_cast<size_t>(§ion[i]), NMD_X86_FORMAT_FLAGS_DEFAULT | NMD_X86_FORMAT_FLAGS_BYTES);
|
||||
formatBuf.resize(strnlen(&formatBuf[0], formatBuf.size()));
|
||||
|
||||
const auto& directory = mod.data_directory(IMAGE_DIRECTORY_ENTRY_EXPORT);
|
||||
const auto& exportDirectory = mod.ref_as<IMAGE_EXPORT_DIRECTORY>(directory.VirtualAddress);
|
||||
const auto names = mod.span_as<DWORD>(exportDirectory.AddressOfNames, exportDirectory.NumberOfNames);
|
||||
const auto ordinals = mod.span_as<WORD>(exportDirectory.AddressOfNameOrdinals, exportDirectory.NumberOfNames);
|
||||
const auto functions = mod.span_as<DWORD>(exportDirectory.AddressOfFunctions, exportDirectory.NumberOfFunctions);
|
||||
|
||||
std::string resolvedExportName;
|
||||
for (size_t j = 0; j < names.size(); ++j) {
|
||||
std::string_view name;
|
||||
if (const char* pcszName = mod.address_as<char>(names[j]); pcszName < mod.address() || pcszName >= mod.address() + mod.image_size()) {
|
||||
if (IsBadReadPtr(pcszName, 256)) {
|
||||
logging::W("{} Name #{} points to an invalid address outside the executable. Skipping.", LogTag, j);
|
||||
continue;
|
||||
}
|
||||
|
||||
name = std::string_view(pcszName, strnlen(pcszName, 256));
|
||||
logging::W("{} Name #{} points to a seemingly valid address outside the executable: {}", LogTag, j, name);
|
||||
}
|
||||
|
||||
if (ordinals[j] >= functions.size()) {
|
||||
logging::W("{} Ordinal #{} points to function index #{} >= #{}. Skipping.", LogTag, j, ordinals[j], functions.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto rva = functions[ordinals[j]];
|
||||
if (rva == §ion[i] - mod.address()) {
|
||||
resolvedExportName = std::format("[export:{}]", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logging::W("{} {}+0x{:0X}{}: {}", LogTag, moduleName, rva, resolvedExportName, formatBuf);
|
||||
printed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (doRestore) {
|
||||
if (!tenderizer)
|
||||
tenderizer.emplace(section, PAGE_EXECUTE_READWRITE);
|
||||
memcpy(§ion[i], &buf[i], instructionLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (tenderizer)
|
||||
logging::I("{} Verification and overwriting complete.", LogTag);
|
||||
else if (doRestore)
|
||||
logging::I("{} Verification complete. Overwriting was not required.", LogTag);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
logging::W("{} Error: {}", LogTag, e.what());
|
||||
}
|
||||
};
|
||||
|
||||
// This is needed since try and __try cannot be used in the same function. Lambdas circumvent the limitation.
|
||||
const auto windows_exception_handler = [&]() {
|
||||
for (size_t i = 0; i < mods.size(); i++) {
|
||||
const auto& mod = mods[i];
|
||||
__try {
|
||||
test_module(i, mod);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
logging::W("{} Error: Access Violation", LogTag);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
windows_exception_handler();
|
||||
}
|
||||
|
||||
using TFnGetInputDeviceManager = void* ();
|
||||
static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) {
|
||||
static TFnGetInputDeviceManager* pCached = nullptr;
|
||||
if (pCached)
|
||||
return pCached;
|
||||
|
||||
char szClassName[256];
|
||||
GetClassNameA(hwnd, szClassName, static_cast<int>(sizeof szClassName));
|
||||
|
||||
WNDCLASSEXA wcx{};
|
||||
GetClassInfoExA(g_hGameInstance, szClassName, &wcx);
|
||||
const auto match = utils::signature_finder()
|
||||
.look_in(utils::loaded_module(g_hGameInstance), ".text")
|
||||
.look_for_hex("41 81 fe 19 02 00 00 0f 87 ?? ?? 00 00 0f 84 ?? ?? 00 00")
|
||||
.find_one();
|
||||
|
||||
auto ptr = match.data() + match.size() + *reinterpret_cast<const int*>(match.data() + match.size() - 4);
|
||||
ptr += 4; // CMP RBX, 0x7
|
||||
ptr += 2; // JNZ <giveup>
|
||||
ptr += 7; // MOV RCX, <Framework::Instance>
|
||||
ptr += 3; // TEST RCX, RCX
|
||||
ptr += 2; // JZ <giveup>
|
||||
ptr += 5; // CALL <GetInputDeviceManagerInstance()>
|
||||
ptr += *reinterpret_cast<const int*>(ptr - 4);
|
||||
|
||||
return pCached = reinterpret_cast<TFnGetInputDeviceManager*>(ptr);
|
||||
}
|
||||
|
||||
void xivfixes::prevent_devicechange_crashes(bool bApply) {
|
||||
static const char* LogTag = "[xivfixes:prevent_devicechange_crashes]";
|
||||
static std::optional<hooks::import_hook<decltype(CreateWindowExA)>> s_hookCreateWindowExA;
|
||||
static std::optional<hooks::wndproc_hook> s_hookWndProc;
|
||||
|
||||
if (bApply) {
|
||||
if (!g_startInfo.BootEnabledGameFixes.contains("prevent_devicechange_crashes")) {
|
||||
logging::I("{} Turned off via environment variable.", LogTag);
|
||||
return;
|
||||
}
|
||||
|
||||
s_hookCreateWindowExA.emplace("user32.dll!CreateWindowExA (prevent_devicechange_crashes)", "user32.dll", "CreateWindowExA", 0);
|
||||
s_hookCreateWindowExA->set_detour([](DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)->HWND {
|
||||
const auto hWnd = s_hookCreateWindowExA->call_original(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
|
||||
|
||||
if (!hWnd
|
||||
|| hInstance != g_hGameInstance
|
||||
|| 0 != strcmp(lpClassName, "FFXIVGAME"))
|
||||
return hWnd;
|
||||
|
||||
logging::I(R"({} CreateWindow(0x{:08X}, "{}", "{}", 0x{:08X}, {}, {}, {}, {}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}) called; unhooking CreateWindowExA and hooking WndProc.)",
|
||||
LogTag, dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, reinterpret_cast<size_t>(hWndParent), reinterpret_cast<size_t>(hMenu), reinterpret_cast<size_t>(hInstance), reinterpret_cast<size_t>(lpParam));
|
||||
|
||||
s_hookWndProc.emplace("FFXIVGAME:WndProc (prevent_devicechange_crashes)", hWnd);
|
||||
s_hookWndProc->set_detour([](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT {
|
||||
|
||||
if (uMsg == WM_DEVICECHANGE && wParam == DBT_DEVNODES_CHANGED) {
|
||||
if (!GetGetInputDeviceManager(hWnd)()) {
|
||||
logging::I("{} WndProc(0x{:X}, WM_DEVICECHANGE, DBT_DEVNODES_CHANGED, {}) called but the game does not have InputDeviceManager initialized; doing nothing.", LogTag, reinterpret_cast<size_t>(hWnd), lParam);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return s_hookWndProc->call_original(hWnd, uMsg, wParam, lParam);
|
||||
});
|
||||
|
||||
return hWnd;
|
||||
});
|
||||
|
||||
logging::I("{} Enable", LogTag);
|
||||
|
||||
} else {
|
||||
if (s_hookCreateWindowExA) {
|
||||
logging::I("{} Disable CreateWindowExA", LogTag);
|
||||
s_hookCreateWindowExA.reset();
|
||||
}
|
||||
|
||||
// This will effectively revert any other WndProc alterations, including Dalamud.
|
||||
if (s_hookWndProc) {
|
||||
logging::I("{} Disable WndProc", LogTag);
|
||||
s_hookWndProc.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_xivalex(const std::filesystem::path& dllPath) {
|
||||
DWORD verHandle = 0;
|
||||
std::vector<uint8_t> block;
|
||||
block.resize(GetFileVersionInfoSizeW(dllPath.c_str(), &verHandle));
|
||||
if (block.empty())
|
||||
return false;
|
||||
if (!GetFileVersionInfoW(dllPath.c_str(), 0, static_cast<DWORD>(block.size()), &block[0]))
|
||||
return false;
|
||||
struct LANGANDCODEPAGE {
|
||||
WORD wLanguage;
|
||||
WORD wCodePage;
|
||||
} * lpTranslate;
|
||||
UINT cbTranslate;
|
||||
if (!VerQueryValueW(&block[0],
|
||||
TEXT("\\VarFileInfo\\Translation"),
|
||||
reinterpret_cast<LPVOID*>(&lpTranslate),
|
||||
&cbTranslate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); i++) {
|
||||
wchar_t* buf = nullptr;
|
||||
UINT size = 0;
|
||||
if (!VerQueryValueW(&block[0],
|
||||
std::format(L"\\StringFileInfo\\{:04x}{:04x}\\FileDescription",
|
||||
lpTranslate[i].wLanguage,
|
||||
lpTranslate[i].wCodePage).c_str(),
|
||||
reinterpret_cast<LPVOID*>(&buf),
|
||||
&size)) {
|
||||
continue;
|
||||
}
|
||||
auto currName = std::wstring_view(buf, size);
|
||||
while (!currName.empty() && currName.back() == L'\0')
|
||||
currName = currName.substr(0, currName.size() - 1);
|
||||
if (currName.empty())
|
||||
continue;
|
||||
if (currName == L"XivAlexander Main DLL")
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_openprocess_already_dealt_with() {
|
||||
static const auto s_value = [] {
|
||||
for (const auto& mod : utils::loaded_module::all_modules()) {
|
||||
try {
|
||||
if (is_xivalex(mod.path()))
|
||||
return true;
|
||||
|
||||
} catch (...) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
return s_value;
|
||||
}
|
||||
|
||||
void xivfixes::disable_game_openprocess_access_check(bool bApply) {
|
||||
static const char* LogTag = "[xivfixes:disable_game_openprocess_access_check]";
|
||||
static std::optional<hooks::import_hook<decltype(OpenProcess)>> s_hook;
|
||||
|
||||
if (bApply) {
|
||||
if (!g_startInfo.BootEnabledGameFixes.contains("disable_game_openprocess_access_check")) {
|
||||
logging::I("{} Turned off via environment variable.", LogTag);
|
||||
return;
|
||||
}
|
||||
if (is_openprocess_already_dealt_with()) {
|
||||
logging::I("{} Someone else already did it.", LogTag);
|
||||
return;
|
||||
}
|
||||
|
||||
s_hook.emplace("kernel32.dll!OpenProcess (import, disable_game_openprocess_access_check)", "kernel32.dll", "OpenProcess", 0);
|
||||
s_hook->set_detour([](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
logging::I("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId());
|
||||
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
// Prevent game from feeling unsafe that it restarts
|
||||
if (dwDesiredAccess & PROCESS_VM_WRITE) {
|
||||
logging::I("{} Returning failure with last error code set to ERROR_ACCESS_DENIED(5).", LogTag);
|
||||
SetLastError(ERROR_ACCESS_DENIED);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return s_hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||
});
|
||||
|
||||
logging::I("{} Enable", LogTag);
|
||||
} else {
|
||||
if (s_hook) {
|
||||
logging::I("{} Disable OpenProcess", LogTag);
|
||||
s_hook.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xivfixes::redirect_openprocess(bool bApply) {
|
||||
static const char* LogTag = "[xivfixes:redirect_openprocess]";
|
||||
static std::shared_ptr<hooks::base_untyped_hook> s_hook;
|
||||
static std::mutex s_silenceSetMtx;
|
||||
static std::set<DWORD> s_silenceSet;
|
||||
|
||||
if (bApply) {
|
||||
if (!g_startInfo.BootEnabledGameFixes.contains("redirect_openprocess")) {
|
||||
logging::I("{} Turned off via environment variable.", LogTag);
|
||||
return;
|
||||
}
|
||||
if (is_openprocess_already_dealt_with()) {
|
||||
logging::I("{} Someone else already did it.", LogTag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_startInfo.BootDotnetOpenProcessHookMode == DalamudStartInfo::DotNetOpenProcessHookMode::ImportHooks) {
|
||||
auto hook = std::make_shared<hooks::global_import_hook<decltype(OpenProcess)>>("kernel32.dll!OpenProcess (global import, redirect_openprocess)", L"kernel32.dll", "OpenProcess");
|
||||
hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
if (s_silenceSet.emplace(GetCurrentThreadId()).second)
|
||||
logging::I("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId());
|
||||
|
||||
if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0))
|
||||
return res;
|
||||
|
||||
return {};
|
||||
}
|
||||
return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||
});
|
||||
s_hook = std::dynamic_pointer_cast<hooks::base_untyped_hook>(std::move(hook));
|
||||
|
||||
logging::I("{} Enable via import_hook", LogTag);
|
||||
|
||||
} else {
|
||||
auto hook = std::make_shared<hooks::direct_hook<decltype(OpenProcess)>>("kernel32.dll!OpenProcess (direct, redirect_openprocess)", OpenProcess);
|
||||
hook->set_detour([hook = hook.get()](DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId)->HANDLE {
|
||||
if (dwProcessId == GetCurrentProcessId()) {
|
||||
if (s_silenceSet.emplace(GetCurrentThreadId()).second)
|
||||
logging::I("{} OpenProcess(0x{:08X}, {}, {}) was invoked by thread {}. Redirecting to DuplicateHandle.", LogTag, dwDesiredAccess, bInheritHandle, dwProcessId, GetCurrentThreadId());
|
||||
|
||||
if (HANDLE res; DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &res, dwDesiredAccess, bInheritHandle, 0))
|
||||
return res;
|
||||
|
||||
return {};
|
||||
}
|
||||
return hook->call_original(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||
});
|
||||
s_hook = std::dynamic_pointer_cast<hooks::base_untyped_hook>(std::move(hook));
|
||||
|
||||
logging::I("{} Enable via direct_hook", LogTag);
|
||||
}
|
||||
|
||||
//std::thread([]() {
|
||||
// SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
|
||||
// for (const auto to = GetTickCount64() + 3000; GetTickCount64() < to;)
|
||||
// s_hook->assert_dominance();
|
||||
//}).detach();
|
||||
|
||||
} else {
|
||||
if (s_hook) {
|
||||
logging::I("{} Disable OpenProcess", LogTag);
|
||||
s_hook.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xivfixes::apply_all(bool bApply) {
|
||||
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
||||
{
|
||||
{ "unhook_dll", &unhook_dll },
|
||||
{ "prevent_devicechange_crashes", &prevent_devicechange_crashes },
|
||||
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
||||
{ "redirect_openprocess", &redirect_openprocess },
|
||||
}
|
||||
) {
|
||||
try {
|
||||
taskFunction(bApply);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
if (bApply)
|
||||
logging::W("Error trying to activate fixup [{}]: {}", taskName, e.what());
|
||||
else
|
||||
logging::W("Error trying to deactivate fixup [{}]: {}", taskName, e.what());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bApply)
|
||||
logging::I("Fixup [{}] activated.", taskName);
|
||||
else
|
||||
logging::I("Fixup [{}] deactivated.", taskName);
|
||||
}
|
||||
}
|
||||
10
Dalamud.Boot/xivfixes.h
Normal file
10
Dalamud.Boot/xivfixes.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
namespace xivfixes {
|
||||
void unhook_dll(bool bApply);
|
||||
void prevent_devicechange_crashes(bool bApply);
|
||||
void disable_game_openprocess_access_check(bool bApply);
|
||||
void redirect_openprocess(bool bApply);
|
||||
|
||||
void apply_all(bool bApply);
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lumina" Version="3.5.2" />
|
||||
<PackageReference Include="Lumina" Version="3.5.4" />
|
||||
<PackageReference Include="Lumina.Excel" Version="6.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<OutDir>..\bin\$(Configuration)\</OutDir>
|
||||
|
|
@ -88,16 +88,20 @@
|
|||
<ResourceCompile Include="resources.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp" />
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\boot.cpp" />
|
||||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h" />
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\core\hostfxr.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
|
||||
<ClInclude Include="..\lib\CoreCLR\pch.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<Target Name="RemoveExtraFiles" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)$(TargetName).lib" />
|
||||
|
|
|
|||
|
|
@ -34,11 +34,14 @@
|
|||
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\logging.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\Dalamud.Boot\unicode.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\lib\CoreCLR\pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\lib\CoreCLR\CoreCLR.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -51,5 +54,14 @@
|
|||
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\logging.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Dalamud.Boot\unicode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -3,12 +3,14 @@
|
|||
#include <filesystem>
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include "..\Dalamud.Boot\logging.h"
|
||||
#include "..\lib\CoreCLR\CoreCLR.h"
|
||||
#include "..\lib\CoreCLR\boot.h"
|
||||
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
printf("Dalamud.Injector, (c) 2021 XIVLauncher Contributors\nBuilt at: %s@%s\n\n", __DATE__, __TIME__);
|
||||
logging::I("Dalamud Injector, (c) 2021 XIVLauncher Contributors");
|
||||
logging::I("Built at : " __DATE__ "@" __TIME__);
|
||||
|
||||
wchar_t _module_path[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, _module_path, sizeof _module_path / 2);
|
||||
|
|
@ -22,6 +24,7 @@ int wmain(int argc, wchar_t** argv)
|
|||
void* entrypoint_vfn;
|
||||
int result = InitializeClrAndGetEntryPoint(
|
||||
GetModuleHandleW(nullptr),
|
||||
false,
|
||||
runtimeconfig_path,
|
||||
module_path,
|
||||
L"Dalamud.Injector.EntryPoint, Dalamud.Injector",
|
||||
|
|
@ -35,9 +38,9 @@ int wmain(int argc, wchar_t** argv)
|
|||
typedef void (CORECLR_DELEGATE_CALLTYPE* custom_component_entry_point_fn)(int, wchar_t**);
|
||||
custom_component_entry_point_fn entrypoint_fn = reinterpret_cast<custom_component_entry_point_fn>(entrypoint_vfn);
|
||||
|
||||
printf("Running Dalamud Injector...\n");
|
||||
logging::I("Running Dalamud Injector...");
|
||||
entrypoint_fn(argc, argv);
|
||||
printf("Done!\n");
|
||||
logging::I("Done!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
1
Dalamud.Injector.Boot/pch.h
Normal file
1
Dalamud.Injector.Boot/pch.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#pragma once
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="PeNet" Version="2.6.4" />
|
||||
<PackageReference Include="Reloaded.Memory" Version="4.1.1" />
|
||||
<PackageReference Include="Reloaded.Memory.Buffers" Version="1.3.5" />
|
||||
<PackageReference Include="Reloaded.Memory.Buffers" Version="1.4.4" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -38,7 +39,6 @@ namespace Dalamud.Injector
|
|||
public static void Main(int argc, IntPtr argvPtr)
|
||||
{
|
||||
List<string> args = new(argc);
|
||||
Init(args);
|
||||
|
||||
unsafe
|
||||
{
|
||||
|
|
@ -47,6 +47,9 @@ namespace Dalamud.Injector
|
|||
args.Add(Marshal.PtrToStringUni(argv[i]));
|
||||
}
|
||||
|
||||
Init(args);
|
||||
args.Remove("-v"); // Remove "verbose" flag
|
||||
|
||||
if (args.Count >= 2 && args[1].ToLowerInvariant() == "launch-test")
|
||||
{
|
||||
Environment.Exit(ProcessLaunchTestCommand(args));
|
||||
|
|
@ -80,6 +83,8 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||
args.Remove("--console"); // Remove "console" flag, already handled
|
||||
args.Remove("--etw"); // Remove "etw" flag, already handled
|
||||
|
||||
var mainCommand = args[1].ToLowerInvariant();
|
||||
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
||||
|
|
@ -100,10 +105,23 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static string GetLogPath(string filename)
|
||||
{
|
||||
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, $"{filename}.log");
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", $"{filename}.log");
|
||||
#endif
|
||||
|
||||
return logPath;
|
||||
}
|
||||
|
||||
private static void Init(List<string> args)
|
||||
{
|
||||
InitLogging(args.Any(x => x == "-v"));
|
||||
InitUnhandledException(args);
|
||||
InitLogging();
|
||||
|
||||
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
if (cwd.FullName != Directory.GetCurrentDirectory())
|
||||
|
|
@ -158,23 +176,18 @@ namespace Dalamud.Injector
|
|||
};
|
||||
}
|
||||
|
||||
private static void InitLogging()
|
||||
private static void InitLogging(bool verbose)
|
||||
{
|
||||
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.injector.log");
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.injector.log");
|
||||
verbose = true;
|
||||
#endif
|
||||
|
||||
var levelSwitch = new LoggingLevelSwitch();
|
||||
var levelSwitch = new LoggingLevelSwitch
|
||||
{
|
||||
MinimumLevel = verbose ? LogEventLevel.Verbose : LogEventLevel.Information,
|
||||
};
|
||||
|
||||
#if DEBUG
|
||||
levelSwitch.MinimumLevel = LogEventLevel.Verbose;
|
||||
#else
|
||||
levelSwitch.MinimumLevel = LogEventLevel.Information;
|
||||
#endif
|
||||
var logPath = GetLogPath("dalamud.injector");
|
||||
|
||||
CullLogFile(logPath, 1 * 1024 * 1024);
|
||||
|
||||
|
|
@ -236,8 +249,7 @@ namespace Dalamud.Injector
|
|||
int len;
|
||||
string key;
|
||||
|
||||
if (startInfo == null)
|
||||
startInfo = new();
|
||||
startInfo ??= new DalamudStartInfo();
|
||||
|
||||
var workingDirectory = startInfo.WorkingDirectory;
|
||||
var configurationPath = startInfo.ConfigurationPath;
|
||||
|
|
@ -288,7 +300,7 @@ namespace Dalamud.Injector
|
|||
clientLanguage = ClientLanguage.Japanese;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "german").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.German;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "deutsche").Length))] == key[0..len])
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "deutsch").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.German;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "french").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.French;
|
||||
|
|
@ -299,17 +311,25 @@ namespace Dalamud.Injector
|
|||
else
|
||||
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
|
||||
|
||||
return new()
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
ConfigurationPath = configurationPath,
|
||||
PluginDirectory = pluginDirectory,
|
||||
DefaultPluginDirectory = defaultPluginDirectory,
|
||||
AssetDirectory = assetDirectory,
|
||||
Language = clientLanguage,
|
||||
GameVersion = null,
|
||||
DelayInitializeMs = delayInitializeMs,
|
||||
};
|
||||
startInfo.WorkingDirectory = workingDirectory;
|
||||
startInfo.ConfigurationPath = configurationPath;
|
||||
startInfo.PluginDirectory = pluginDirectory;
|
||||
startInfo.DefaultPluginDirectory = defaultPluginDirectory;
|
||||
startInfo.AssetDirectory = assetDirectory;
|
||||
startInfo.Language = clientLanguage;
|
||||
startInfo.DelayInitializeMs = delayInitializeMs;
|
||||
startInfo.GameVersion = null;
|
||||
|
||||
// Set boot defaults
|
||||
startInfo.BootShowConsole = args.Contains("--console");
|
||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||
startInfo.BootLogPath = GetLogPath("dalamud.boot");
|
||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" };
|
||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||
// startInfo.BootWaitMessageBox = 2;
|
||||
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private static int ProcessHelpCommand(List<string> args, string? particularCommand = default)
|
||||
|
|
@ -324,7 +344,7 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("{0} help [command]", exeName);
|
||||
|
||||
if (particularCommand is null or "inject")
|
||||
Console.WriteLine("{0} inject [-h/--help] [-a/--all] [--warn] [pid1] [pid2] [pid3] ...", exeName);
|
||||
Console.WriteLine("{0} inject [-h/--help] [-a/--all] [--warn] [--fix-acl] [--se-debug-privilege] [pid1] [pid2] [pid3] ...", exeName);
|
||||
|
||||
if (particularCommand is null or "launch")
|
||||
{
|
||||
|
|
@ -332,7 +352,7 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("{0} [-g path/to/ffxiv_dx11.exe] [--game=path/to/ffxiv_dx11.exe]", exeSpaces);
|
||||
Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
|
||||
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
||||
Console.WriteLine("{0} [--without-dalamud]", exeSpaces);
|
||||
Console.WriteLine("{0} [--without-dalamud] [--no-fix-acl]", exeSpaces);
|
||||
Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", exeSpaces);
|
||||
}
|
||||
|
||||
|
|
@ -341,6 +361,10 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
|
||||
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
|
||||
|
||||
Console.WriteLine("Verbose logging:\t[-v]");
|
||||
Console.WriteLine("Show Console:\t[--console]");
|
||||
Console.WriteLine("Enable ETW:\t[--etw]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +375,8 @@ namespace Dalamud.Injector
|
|||
var targetProcessSpecified = false;
|
||||
var warnManualInjection = false;
|
||||
var showHelp = args.Count <= 2;
|
||||
var tryFixAcl = false;
|
||||
var tryClaimSeDebugPrivilege = false;
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
|
|
@ -378,6 +404,14 @@ namespace Dalamud.Injector
|
|||
targetProcessSpecified = true;
|
||||
processes.AddRange(Process.GetProcessesByName("ffxiv_dx11"));
|
||||
}
|
||||
else if (args[i] == "--fix-acl" || args[i] == "--acl-fix")
|
||||
{
|
||||
tryFixAcl = true;
|
||||
}
|
||||
else if (args[i] == "--se-debug-privilege")
|
||||
{
|
||||
tryClaimSeDebugPrivilege = true;
|
||||
}
|
||||
else if (args[i] == "--warn")
|
||||
{
|
||||
warnManualInjection = true;
|
||||
|
|
@ -416,8 +450,21 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
if (tryClaimSeDebugPrivilege)
|
||||
{
|
||||
try
|
||||
{
|
||||
GameStart.ClaimSeDebug();
|
||||
Log.Information("SeDebugPrivilege claimed.");
|
||||
}
|
||||
catch (Win32Exception e2)
|
||||
{
|
||||
Log.Warning(e2, "Failed to claim SeDebugPrivilege");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var process in processes)
|
||||
Inject(process, AdjustStartInfo(dalamudStartInfo, process.MainModule.FileName));
|
||||
Inject(process, AdjustStartInfo(dalamudStartInfo, process.MainModule.FileName), tryFixAcl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -431,6 +478,7 @@ namespace Dalamud.Injector
|
|||
var showHelp = args.Count <= 2;
|
||||
var handleOwner = IntPtr.Zero;
|
||||
var withoutDalamud = false;
|
||||
var noFixAcl = false;
|
||||
|
||||
var parsingGameArgument = false;
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
|
|
@ -447,6 +495,8 @@ namespace Dalamud.Injector
|
|||
useFakeArguments = true;
|
||||
else if (args[i] == "--without-dalamud")
|
||||
withoutDalamud = true;
|
||||
else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix")
|
||||
noFixAcl = true;
|
||||
else if (args[i] == "-g")
|
||||
gamePath = args[++i];
|
||||
else if (args[i].StartsWith("--game="))
|
||||
|
|
@ -547,7 +597,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
|
||||
var process = NativeAclFix.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, (Process p) =>
|
||||
var process = GameStart.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, noFixAcl, (Process p) =>
|
||||
{
|
||||
if (!withoutDalamud && mode == "entrypoint")
|
||||
{
|
||||
|
|
@ -558,14 +608,18 @@ namespace Dalamud.Injector
|
|||
Log.Error("[HOOKS] RewriteRemoteEntryPointW failed");
|
||||
throw new Exception("RewriteRemoteEntryPointW failed");
|
||||
}
|
||||
|
||||
Log.Verbose("RewriteRemoteEntryPointW called!");
|
||||
}
|
||||
});
|
||||
|
||||
Log.Verbose("Game process started with PID {0}", process.Id);
|
||||
|
||||
if (!withoutDalamud && mode == "inject")
|
||||
{
|
||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||
Inject(process, startInfo);
|
||||
Inject(process, startInfo, false);
|
||||
}
|
||||
|
||||
var processHandleForOwner = IntPtr.Zero;
|
||||
|
|
@ -634,21 +688,26 @@ namespace Dalamud.Injector
|
|||
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
|
||||
var gameVer = GameVersion.Parse(gameVerStr);
|
||||
|
||||
return new()
|
||||
return new DalamudStartInfo(startInfo)
|
||||
{
|
||||
WorkingDirectory = startInfo.WorkingDirectory,
|
||||
ConfigurationPath = startInfo.ConfigurationPath,
|
||||
PluginDirectory = startInfo.PluginDirectory,
|
||||
DefaultPluginDirectory = startInfo.DefaultPluginDirectory,
|
||||
AssetDirectory = startInfo.AssetDirectory,
|
||||
Language = startInfo.Language,
|
||||
GameVersion = gameVer,
|
||||
DelayInitializeMs = startInfo.DelayInitializeMs,
|
||||
};
|
||||
}
|
||||
|
||||
private static void Inject(Process process, DalamudStartInfo startInfo)
|
||||
private static void Inject(Process process, DalamudStartInfo startInfo, bool tryFixAcl = false)
|
||||
{
|
||||
if (tryFixAcl)
|
||||
{
|
||||
try
|
||||
{
|
||||
GameStart.CopyAclFromSelfToTargetProcess(process.SafeHandle.DangerousGetHandle());
|
||||
}
|
||||
catch (Win32Exception e1)
|
||||
{
|
||||
Log.Warning(e1, "Failed to copy ACL");
|
||||
}
|
||||
}
|
||||
|
||||
var bootName = "Dalamud.Boot.dll";
|
||||
var bootPath = Path.GetFullPath(bootName);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ using Serilog;
|
|||
namespace Dalamud.Injector
|
||||
{
|
||||
/// <summary>
|
||||
/// Class responsible for stripping ACL protections from processes.
|
||||
/// Class responsible for starting the game and stripping ACL protections from processes.
|
||||
/// </summary>
|
||||
public static class NativeAclFix
|
||||
public static class GameStart
|
||||
{
|
||||
/// <summary>
|
||||
/// Start a process without ACL protections.
|
||||
|
|
@ -22,14 +22,19 @@ namespace Dalamud.Injector
|
|||
/// <param name="workingDir">The working directory.</param>
|
||||
/// <param name="exePath">The path to the executable file.</param>
|
||||
/// <param name="arguments">Arguments to pass to the executable file.</param>
|
||||
/// <param name="dontFixAcl">Don't actually fix the ACL.</param>
|
||||
/// <param name="beforeResume">Action to execute before the process is started.</param>
|
||||
/// <param name="waitForGameWindow">Wait for the game window to be ready before proceeding.</param>
|
||||
/// <returns>The started process.</returns>
|
||||
/// <exception cref="Win32Exception">Thrown when a win32 error occurs.</exception>
|
||||
/// <exception cref="GameExitedException">Thrown when the process did not start correctly.</exception>
|
||||
public static Process LaunchGame(string workingDir, string exePath, string arguments, Action<Process> beforeResume)
|
||||
/// <exception cref="GameStartException">Thrown when the process did not start correctly.</exception>
|
||||
public static Process LaunchGame(string workingDir, string exePath, string arguments, bool dontFixAcl, Action<Process> beforeResume, bool waitForGameWindow = true)
|
||||
{
|
||||
Process process = null;
|
||||
|
||||
var psecDesc = IntPtr.Zero;
|
||||
if (!dontFixAcl)
|
||||
{
|
||||
var userName = Environment.UserName;
|
||||
|
||||
var pExplicitAccess = default(PInvoke.EXPLICIT_ACCESS);
|
||||
|
|
@ -55,8 +60,9 @@ namespace Dalamud.Injector
|
|||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var psecDesc = Marshal.AllocHGlobal(Marshal.SizeOf<PInvoke.SECURITY_DESCRIPTOR>());
|
||||
psecDesc = Marshal.AllocHGlobal(Marshal.SizeOf<PInvoke.SECURITY_DESCRIPTOR>());
|
||||
Marshal.StructureToPtr(secDesc, psecDesc, true);
|
||||
}
|
||||
|
||||
var lpProcessInformation = default(PInvoke.PROCESS_INFORMATION);
|
||||
try
|
||||
|
|
@ -75,7 +81,15 @@ namespace Dalamud.Injector
|
|||
|
||||
var compatLayerPrev = Environment.GetEnvironmentVariable("__COMPAT_LAYER");
|
||||
|
||||
if (!string.IsNullOrEmpty(compatLayerPrev) && !compatLayerPrev.Contains("RunAsInvoker"))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("__COMPAT_LAYER", $"RunAsInvoker {compatLayerPrev}");
|
||||
}
|
||||
else if (string.IsNullOrEmpty(compatLayerPrev))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("__COMPAT_LAYER", "RunAsInvoker");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!PInvoke.CreateProcess(
|
||||
|
|
@ -98,6 +112,7 @@ namespace Dalamud.Injector
|
|||
Environment.SetEnvironmentVariable("__COMPAT_LAYER", compatLayerPrev);
|
||||
}
|
||||
|
||||
if (!dontFixAcl)
|
||||
DisableSeDebug(lpProcessInformation.hProcess);
|
||||
|
||||
process = new ExistingProcess(lpProcessInformation.hProcess);
|
||||
|
|
@ -107,21 +122,69 @@ namespace Dalamud.Injector
|
|||
PInvoke.ResumeThread(lpProcessInformation.hThread);
|
||||
|
||||
// Ensure that the game main window is prepared
|
||||
if (waitForGameWindow)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tries = 0;
|
||||
const int maxTries = 420;
|
||||
const int timeout = 50;
|
||||
|
||||
do
|
||||
{
|
||||
process.WaitForInputIdle();
|
||||
Thread.Sleep(timeout);
|
||||
|
||||
Thread.Sleep(100);
|
||||
if (process.HasExited)
|
||||
throw new GameStartException();
|
||||
|
||||
if (tries > maxTries)
|
||||
throw new GameStartException($"Couldn't find game window after {maxTries * timeout}ms");
|
||||
|
||||
tries++;
|
||||
}
|
||||
while (TryFindGameWindow(process) == IntPtr.Zero);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new GameExitedException();
|
||||
throw new GameStartException("Could not read process information.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!dontFixAcl)
|
||||
CopyAclFromSelfToTargetProcess(lpProcessInformation.hProcess);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "[GameStart] Uncaught error during initialization, trying to kill process");
|
||||
|
||||
try
|
||||
{
|
||||
process?.Kill();
|
||||
}
|
||||
catch (Exception killEx)
|
||||
{
|
||||
Log.Error(killEx, "[GameStart] Could not kill process");
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (psecDesc != IntPtr.Zero)
|
||||
Marshal.FreeHGlobal(psecDesc);
|
||||
PInvoke.CloseHandle(lpProcessInformation.hThread);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies ACL of current process to the target process.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">Native handle to the target process.</param>
|
||||
/// <exception cref="Win32Exception">Thrown when a win32 error occurs.</exception>
|
||||
public static void CopyAclFromSelfToTargetProcess(IntPtr hProcess)
|
||||
{
|
||||
if (PInvoke.GetSecurityInfo(
|
||||
PInvoke.GetCurrentProcess(),
|
||||
PInvoke.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
|
||||
|
|
@ -136,7 +199,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
if (PInvoke.SetSecurityInfo(
|
||||
lpProcessInformation.hProcess,
|
||||
hProcess,
|
||||
PInvoke.SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
|
||||
PInvoke.SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | PInvoke.SECURITY_INFORMATION.UNPROTECTED_DACL_SECURITY_INFORMATION,
|
||||
IntPtr.Zero,
|
||||
|
|
@ -147,28 +210,49 @@ namespace Dalamud.Injector
|
|||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "[NativeAclFix] Uncaught error during initialization, trying to kill process");
|
||||
|
||||
public static void ClaimSeDebug()
|
||||
{
|
||||
var hToken = PInvoke.INVALID_HANDLE_VALUE;
|
||||
try
|
||||
{
|
||||
process?.Kill();
|
||||
}
|
||||
catch (Exception killEx)
|
||||
if (!PInvoke.OpenThreadToken(PInvoke.GetCurrentThread(), PInvoke.TOKEN_QUERY | PInvoke.TOKEN_ADJUST_PRIVILEGES, false, out hToken))
|
||||
{
|
||||
Log.Error(killEx, "[NativeAclFix] Could not kill process");
|
||||
if (Marshal.GetLastWin32Error() != PInvoke.ERROR_NO_TOKEN)
|
||||
throw new Exception("ClaimSeDebug.OpenProcessToken#1", new Win32Exception(Marshal.GetLastWin32Error()));
|
||||
|
||||
if (!PInvoke.ImpersonateSelf(PInvoke.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation))
|
||||
throw new Exception("ClaimSeDebug.ImpersonateSelf", new Win32Exception(Marshal.GetLastWin32Error()));
|
||||
|
||||
if (!PInvoke.OpenThreadToken(PInvoke.GetCurrentThread(), PInvoke.TOKEN_QUERY | PInvoke.TOKEN_ADJUST_PRIVILEGES, false, out hToken))
|
||||
throw new Exception("ClaimSeDebug.OpenProcessToken#2", new Win32Exception(Marshal.GetLastWin32Error()));
|
||||
}
|
||||
|
||||
throw;
|
||||
var luidDebugPrivilege = default(PInvoke.LUID);
|
||||
if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_DEBUG_NAME, ref luidDebugPrivilege))
|
||||
throw new Exception("ClaimSeDebug.LookupPrivilegeValue", new Win32Exception(Marshal.GetLastWin32Error()));
|
||||
|
||||
var tpLookup = new PInvoke.TOKEN_PRIVILEGES()
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Privileges = new PInvoke.LUID_AND_ATTRIBUTES[1]
|
||||
{
|
||||
new PInvoke.LUID_AND_ATTRIBUTES()
|
||||
{
|
||||
Luid = luidDebugPrivilege,
|
||||
Attributes = PInvoke.SE_PRIVILEGE_ENABLED,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (!PInvoke.AdjustTokenPrivileges(hToken, false, ref tpLookup, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
throw new Exception("ClaimSeDebug.AdjustTokenPrivileges", new Win32Exception(Marshal.GetLastWin32Error()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(psecDesc);
|
||||
PInvoke.CloseHandle(lpProcessInformation.hThread);
|
||||
if (hToken != PInvoke.INVALID_HANDLE_VALUE && hToken != IntPtr.Zero)
|
||||
PInvoke.CloseHandle(hToken);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private static void DisableSeDebug(IntPtr processHandle)
|
||||
|
|
@ -179,7 +263,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
var luidDebugPrivilege = default(PInvoke.LUID);
|
||||
if (!PInvoke.LookupPrivilegeValue(null, "SeDebugPrivilege", ref luidDebugPrivilege))
|
||||
if (!PInvoke.LookupPrivilegeValue(null, PInvoke.SE_DEBUG_NAME, ref luidDebugPrivilege))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
|
@ -211,7 +295,7 @@ namespace Dalamud.Injector
|
|||
tokenPrivileges.Privileges[0].Luid = luidDebugPrivilege;
|
||||
tokenPrivileges.Privileges[0].Attributes = PInvoke.SE_PRIVILEGE_REMOVED;
|
||||
|
||||
if (!PInvoke.AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, 0))
|
||||
if (!PInvoke.AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
|
@ -239,13 +323,14 @@ namespace Dalamud.Injector
|
|||
/// <summary>
|
||||
/// Exception thrown when the process has exited before a window could be found.
|
||||
/// </summary>
|
||||
public class GameExitedException : Exception
|
||||
public class GameStartException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameExitedException"/> class.
|
||||
/// Initializes a new instance of the <see cref="GameStartException"/> class.
|
||||
/// </summary>
|
||||
public GameExitedException()
|
||||
: base("Game exited prematurely.")
|
||||
/// <param name="message">The message to pass on.</param>
|
||||
public GameStartException(string? message = null)
|
||||
: base(message ?? "Game exited prematurely.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +345,10 @@ namespace Dalamud.Injector
|
|||
private static class PInvoke
|
||||
{
|
||||
#region Constants
|
||||
public static readonly IntPtr INVALID_HANDLE_VALUE = new(-1);
|
||||
|
||||
public const string SE_DEBUG_NAME = "SeDebugPrivilege";
|
||||
|
||||
public const UInt32 STANDARD_RIGHTS_ALL = 0x001F0000;
|
||||
public const UInt32 SPECIFIC_RIGHTS_ALL = 0x0000FFFF;
|
||||
public const UInt32 PROCESS_VM_WRITE = 0x0020;
|
||||
|
|
@ -278,6 +367,8 @@ namespace Dalamud.Injector
|
|||
public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
|
||||
|
||||
public const UInt32 ERROR_NO_TOKEN = 0x000003F0;
|
||||
|
||||
public enum MULTIPLE_TRUSTEE_OPERATION
|
||||
{
|
||||
NO_MULTIPLE_TRUSTEE,
|
||||
|
|
@ -334,6 +425,14 @@ namespace Dalamud.Injector
|
|||
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
|
||||
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000,
|
||||
}
|
||||
|
||||
public enum SECURITY_IMPERSONATION_LEVEL
|
||||
{
|
||||
SecurityAnonymous,
|
||||
SecurityIdentification,
|
||||
SecurityImpersonation,
|
||||
SecurityDelegation
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
|
@ -384,12 +483,24 @@ namespace Dalamud.Injector
|
|||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint ResumeThread(IntPtr hThread);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool ImpersonateSelf(
|
||||
SECURITY_IMPERSONATION_LEVEL impersonationLevel
|
||||
);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool OpenProcessToken(
|
||||
IntPtr processHandle,
|
||||
UInt32 desiredAccess,
|
||||
out IntPtr tokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool OpenThreadToken(
|
||||
IntPtr ThreadHandle,
|
||||
uint DesiredAccess,
|
||||
bool OpenAsSelf,
|
||||
out IntPtr TokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
|
||||
|
||||
|
|
@ -404,9 +515,9 @@ namespace Dalamud.Injector
|
|||
IntPtr tokenHandle,
|
||||
bool disableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES newState,
|
||||
UInt32 bufferLengthInBytes,
|
||||
int cbPreviousState,
|
||||
IntPtr previousState,
|
||||
UInt32 returnLengthInBytes);
|
||||
IntPtr cbOutPreviousState);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
public static extern uint GetSecurityInfo(
|
||||
|
|
@ -432,6 +543,9 @@ namespace Dalamud.Injector
|
|||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetCurrentThread();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string className, IntPtr windowTitle);
|
||||
|
||||
52
Dalamud.sln
52
Dalamud.sln
|
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31424.327
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32319.34
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CEF7D22B-CB85-400E-BD64-349A30E3C097}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
|
|
@ -38,106 +38,154 @@ Global
|
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{94E5B016-02B1-459B-97D9-E783F28764B2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x64.Build.0 = Debug|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|Any CPU.Build.0 = Release|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.ActiveCfg = Release|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x64.Build.0 = Release|x64
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B92DAB43-2279-4A2C-96E3-D9D5910EDBEA}.Release|x86.Build.0 = Release|Any CPU
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x64.Build.0 = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Debug|x86.Build.0 = Debug|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|Any CPU.Build.0 = Release|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.ActiveCfg = Release|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x64.Build.0 = Release|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x86.ActiveCfg = Release|x64
|
||||
{55198DC3-A03D-408E-A8EB-2077780C8576}.Release|x86.Build.0 = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x64.Build.0 = Debug|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|Any CPU.Build.0 = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.ActiveCfg = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.Build.0 = Release|x64
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x64.Build.0 = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Debug|x86.Build.0 = Debug|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.ActiveCfg = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x64.Build.0 = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x86.ActiveCfg = Release|x64
|
||||
{8874326B-E755-4D13-90B4-59AB263A3E6B}.Release|x86.Build.0 = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x64.Build.0 = Debug|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|Any CPU.Build.0 = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.ActiveCfg = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x64.Build.0 = Release|x64
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C8004563-1806-4329-844F-0EF6274291FC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.Build.0 = Debug|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.ActiveCfg = Release|x64
|
||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.Build.0 = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.Build.0 = Debug|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.ActiveCfg = Release|x64
|
||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.Build.0 = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x64.Build.0 = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Debug|x86.Build.0 = Debug|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|Any CPU.Build.0 = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.ActiveCfg = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x64.Build.0 = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x86.ActiveCfg = Release|x64
|
||||
{2F7FF0A8-B619-4572-86C7-71E46FE22FB8}.Release|x86.Build.0 = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x64.Build.0 = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Debug|x86.Build.0 = Debug|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|Any CPU.Build.0 = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.ActiveCfg = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x64.Build.0 = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x86.ActiveCfg = Release|x64
|
||||
{4AFDB34A-7467-4D41-B067-53BC4101D9D0}.Release|x86.Build.0 = Release|x64
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C9B87BD7-AF49-41C3-91F1-D550ADEB7833}.Release|x86.Build.0 = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ namespace Dalamud.Configuration.Internal
|
|||
/// </summary>
|
||||
public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing.
|
||||
/// </summary>
|
||||
public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not Dalamud context menus should be disabled.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ using Dalamud.Plugin.Internal;
|
|||
using Dalamud.Plugin.Ipc.Internal;
|
||||
using Dalamud.Support;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
|
@ -48,6 +49,7 @@ namespace Dalamud
|
|||
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
private readonly ManualResetEvent finishUnloadSignal;
|
||||
private readonly IntPtr mainThreadContinueEvent;
|
||||
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
||||
private bool hasDisposedPlugins = false;
|
||||
|
||||
|
|
@ -60,10 +62,9 @@ namespace Dalamud
|
|||
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
|
||||
/// <param name="finishSignal">Signal signalling shutdown.</param>
|
||||
/// <param name="configuration">The Dalamud configuration.</param>
|
||||
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration)
|
||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
this.ApplyProcessPatch();
|
||||
|
||||
Service<Dalamud>.Set(this);
|
||||
Service<DalamudStartInfo>.Set(info);
|
||||
Service<DalamudConfiguration>.Set(configuration);
|
||||
|
|
@ -75,6 +76,8 @@ namespace Dalamud
|
|||
|
||||
this.finishUnloadSignal = finishSignal;
|
||||
this.finishUnloadSignal.Reset();
|
||||
|
||||
this.mainThreadContinueEvent = mainThreadContinueEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -92,6 +95,8 @@ namespace Dalamud
|
|||
/// </summary>
|
||||
public void LoadTier1()
|
||||
{
|
||||
using var tier1Timing = Timings.Start("Tier 1 Init");
|
||||
|
||||
try
|
||||
{
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
|
@ -99,12 +104,21 @@ namespace Dalamud
|
|||
Service<ServiceContainer>.Set();
|
||||
|
||||
// Initialize the process information.
|
||||
Service<SigScanner>.Set(new SigScanner(true));
|
||||
var info = Service<DalamudStartInfo>.Get();
|
||||
var cacheDir = new DirectoryInfo(Path.Combine(info.WorkingDirectory!, "cachedSigs"));
|
||||
if (!cacheDir.Exists)
|
||||
cacheDir.Create();
|
||||
|
||||
Service<SigScanner>.Set(
|
||||
new SigScanner(true, new FileInfo(Path.Combine(cacheDir.FullName, $"{info.GameVersion}.json"))));
|
||||
Service<HookManager>.Set();
|
||||
|
||||
// Initialize FFXIVClientStructs function resolver
|
||||
FFXIVClientStructs.Resolver.Initialize();
|
||||
using (Timings.Start("CS Resolver Init"))
|
||||
{
|
||||
FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{info.GameVersion}_cs.json")));
|
||||
Log.Information("[T1] FFXIVClientStructs initialized!");
|
||||
}
|
||||
|
||||
// Initialize game subsystem
|
||||
var framework = Service<Framework>.Set();
|
||||
|
|
@ -123,9 +137,18 @@ namespace Dalamud
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 1 load failed.");
|
||||
Log.Error(ex, "Tier 1 load failed");
|
||||
this.Unload();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Signal the main game thread to continue
|
||||
// TODO: This is done in rewrite_entrypoint.cpp again to avoid a race condition. Should be fixed!
|
||||
// NativeFunctions.SetEvent(this.mainThreadContinueEvent);
|
||||
|
||||
// Timings.Event("Game kickoff");
|
||||
// Log.Information("[T1] Game thread continued!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,6 +157,11 @@ namespace Dalamud
|
|||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier2()
|
||||
{
|
||||
// This marks the first time we are actually on the game's main thread
|
||||
ThreadSafety.MarkMainThread();
|
||||
|
||||
using var tier2Timing = Timings.Start("Tier 2 Init");
|
||||
|
||||
try
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -157,21 +185,27 @@ namespace Dalamud
|
|||
Service<NetworkHandlers>.Set();
|
||||
Log.Information("[T2] NH OK!");
|
||||
|
||||
using (Timings.Start("DM Init"))
|
||||
{
|
||||
try
|
||||
{
|
||||
Service<DataManager>.Set().Initialize(this.AssetDirectory.FullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not initialize DataManager.");
|
||||
Log.Error(e, "Could not initialize DataManager");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Information("[T2] Data OK!");
|
||||
|
||||
var clientState = Service<ClientState>.Set();
|
||||
using (Timings.Start("CS Init"))
|
||||
{
|
||||
Service<ClientState>.Set();
|
||||
Log.Information("[T2] CS OK!");
|
||||
}
|
||||
|
||||
var localization = Service<Localization>.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_"));
|
||||
if (!string.IsNullOrEmpty(configuration.LanguageOverride))
|
||||
|
|
@ -186,14 +220,23 @@ namespace Dalamud
|
|||
Log.Information("[T2] LOC OK!");
|
||||
|
||||
// This is enabled in ImGuiScene setup
|
||||
using (Timings.Start("IME Init"))
|
||||
{
|
||||
Service<DalamudIME>.Set();
|
||||
Log.Information("[T2] IME OK!");
|
||||
}
|
||||
|
||||
using (Timings.Start("IM Enable"))
|
||||
{
|
||||
Service<InterfaceManager>.Set().Enable();
|
||||
Log.Information("[T2] IM OK!");
|
||||
}
|
||||
|
||||
using (Timings.Start("GFM Init"))
|
||||
{
|
||||
Service<GameFontManager>.Set();
|
||||
Log.Information("[T2] GFM OK!");
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Service<SeStringManager>.Set();
|
||||
|
|
@ -201,27 +244,30 @@ namespace Dalamud
|
|||
|
||||
Log.Information("[T2] SeString OK!");
|
||||
|
||||
// Initialize managers. Basically handlers for the logic
|
||||
using (Timings.Start("CM Init"))
|
||||
{
|
||||
Service<CommandManager>.Set();
|
||||
|
||||
Service<DalamudCommands>.Set().SetupCommands();
|
||||
|
||||
Log.Information("[T2] CM OK!");
|
||||
}
|
||||
|
||||
Service<ChatHandlers>.Set();
|
||||
|
||||
Log.Information("[T2] CH OK!");
|
||||
|
||||
clientState.Enable();
|
||||
using (Timings.Start("CS Enable"))
|
||||
{
|
||||
Service<ClientState>.Get().Enable();
|
||||
Log.Information("[T2] CS ENABLE!");
|
||||
}
|
||||
|
||||
Service<DalamudAtkTweaks>.Set().Enable();
|
||||
Log.Information("[T2] ATKTWEAKS ENABLE!");
|
||||
|
||||
Log.Information("[T2] Load complete!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 2 load failed.");
|
||||
Log.Error(ex, "Tier 2 load failed");
|
||||
this.Unload();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -235,46 +281,62 @@ namespace Dalamud
|
|||
/// <returns>Whether or not the load succeeded.</returns>
|
||||
public bool LoadTier3()
|
||||
{
|
||||
using var tier3Timing = Timings.Start("Tier 3 Init");
|
||||
|
||||
ThreadSafety.AssertMainThread();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("[T3] START!");
|
||||
|
||||
Service<TitleScreenMenu>.Set();
|
||||
|
||||
var pluginManager = Service<PluginManager>.Set();
|
||||
PluginManager pluginManager;
|
||||
using (Timings.Start("PM Init"))
|
||||
{
|
||||
pluginManager = Service<PluginManager>.Set();
|
||||
Service<CallGate>.Set();
|
||||
|
||||
Log.Information("[T3] PM OK!");
|
||||
}
|
||||
|
||||
Service<DalamudInterface>.Set();
|
||||
Log.Information("[T3] DUI OK!");
|
||||
|
||||
try
|
||||
{
|
||||
using (Timings.Start("PM Load Plugin Repos"))
|
||||
{
|
||||
_ = pluginManager.SetPluginReposFromConfigAsync(false);
|
||||
|
||||
pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting;
|
||||
|
||||
Log.Information("[T3] Sync plugins OK!");
|
||||
Log.Information("[T3] PM repos OK!");
|
||||
}
|
||||
|
||||
using (Timings.Start("PM Cleanup Plugins"))
|
||||
{
|
||||
pluginManager.CleanupPlugins();
|
||||
Log.Information("[T3] PMC OK!");
|
||||
}
|
||||
|
||||
using (Timings.Start("PM Load Sync Plugins"))
|
||||
{
|
||||
pluginManager.LoadAllPlugins();
|
||||
Log.Information("[T3] PML OK!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Plugin load failed.");
|
||||
Log.Error(ex, "Plugin load failed");
|
||||
}
|
||||
|
||||
Troubleshooting.LogTroubleshooting();
|
||||
|
||||
Log.Information("Dalamud is ready.");
|
||||
Log.Information("Dalamud is ready");
|
||||
Timings.Event("Dalamud ready");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Tier 3 load failed.");
|
||||
Log.Error(ex, "Tier 3 load failed");
|
||||
this.Unload();
|
||||
|
||||
return false;
|
||||
|
|
@ -354,7 +416,10 @@ namespace Dalamud
|
|||
Service<AntiDebug>.GetNullable()?.Dispose();
|
||||
Service<DalamudAtkTweaks>.GetNullable()?.Dispose();
|
||||
Service<HookManager>.GetNullable()?.Dispose();
|
||||
Service<SigScanner>.GetNullable()?.Dispose();
|
||||
|
||||
var sigScanner = Service<SigScanner>.Get();
|
||||
sigScanner.Save();
|
||||
sigScanner.Dispose();
|
||||
|
||||
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
||||
|
||||
|
|
@ -388,35 +453,5 @@ namespace Dalamud
|
|||
|
||||
Troubleshooting.LogException(e.Exception, e.Line);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it
|
||||
/// uses pseudo-handles to access memory, to prevent permission errors.
|
||||
/// It should never be called manually.
|
||||
/// </summary>
|
||||
/// <param name="orig">A delegate that acts as the original method.</param>
|
||||
/// <param name="self">The equivalent of `this`.</param>
|
||||
/// <returns>A pseudo-handle for the current process, or the result from the original method.</returns>
|
||||
private static IntPtr ProcessHandlePatch(Func<Process, IntPtr> orig, Process self)
|
||||
{
|
||||
var result = orig(self);
|
||||
|
||||
if (self.Id == Environment.ProcessId)
|
||||
{
|
||||
result = (IntPtr)0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ApplyProcessPatch()
|
||||
{
|
||||
var targetType = typeof(Process);
|
||||
|
||||
var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod();
|
||||
var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>6.4.0.9</DalamudVersion>
|
||||
<DalamudVersion>6.4.0.31</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
@ -65,13 +65,14 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="CheapLoc" Version="1.1.6" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
||||
<PackageReference Include="Lumina" Version="3.5.2" />
|
||||
<PackageReference Include="Lumina" Version="3.5.4" />
|
||||
<PackageReference Include="Lumina.Excel" Version="6.1.1" />
|
||||
<PackageReference Include="MinSharp" Version="1.0.4" />
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="21.10.10.01" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Reloaded.Hooks" Version="3.5.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
|
|
@ -94,6 +95,13 @@
|
|||
<AdditionalFiles Include="..\stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="licenses.txt" />
|
||||
<Content Include="licenses.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="AddRuntimeDependenciesToContent" BeforeTargets="GetCopyToOutputDirectoryItems" DependsOnTargets="GenerateBuildDependencyFile;GenerateBuildRuntimeConfigurationFiles">
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(ProjectDepsFilePath)" CopyToOutputDirectory="PreserveNewest" TargetPath="$(ProjectDepsFileName)" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -11,45 +12,135 @@ namespace Dalamud
|
|||
[Serializable]
|
||||
public record DalamudStartInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
||||
/// </summary>
|
||||
public DalamudStartInfo()
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="other">Object to copy values from.</param>
|
||||
public DalamudStartInfo(DalamudStartInfo other)
|
||||
{
|
||||
this.WorkingDirectory = other.WorkingDirectory;
|
||||
this.ConfigurationPath = other.ConfigurationPath;
|
||||
this.PluginDirectory = other.PluginDirectory;
|
||||
this.DefaultPluginDirectory = other.DefaultPluginDirectory;
|
||||
this.AssetDirectory = other.AssetDirectory;
|
||||
this.Language = other.Language;
|
||||
this.GameVersion = other.GameVersion;
|
||||
this.DelayInitializeMs = other.DelayInitializeMs;
|
||||
this.BootLogPath = other.BootLogPath;
|
||||
this.BootShowConsole = other.BootShowConsole;
|
||||
this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
|
||||
this.BootWaitMessageBox = other.BootWaitMessageBox;
|
||||
this.BootWaitDebugger = other.BootWaitDebugger;
|
||||
this.BootVehEnabled = other.BootVehEnabled;
|
||||
this.BootVehFull = other.BootVehFull;
|
||||
this.BootEnableEtw = other.BootEnableEtw;
|
||||
this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
|
||||
this.BootEnabledGameFixes = other.BootEnabledGameFixes;
|
||||
this.BootUnhookDlls = other.BootUnhookDlls;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the working directory of the XIVLauncher installations.
|
||||
/// </summary>
|
||||
public string WorkingDirectory { get; set; }
|
||||
public string? WorkingDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the configuration file.
|
||||
/// Gets or sets the path to the configuration file.
|
||||
/// </summary>
|
||||
public string ConfigurationPath { get; init; }
|
||||
public string? ConfigurationPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for installed plugins.
|
||||
/// Gets or sets the path to the directory for installed plugins.
|
||||
/// </summary>
|
||||
public string PluginDirectory { get; init; }
|
||||
public string? PluginDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the directory for developer plugins.
|
||||
/// Gets or sets the path to the directory for developer plugins.
|
||||
/// </summary>
|
||||
public string DefaultPluginDirectory { get; init; }
|
||||
public string? DefaultPluginDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to core Dalamud assets.
|
||||
/// Gets or sets the path to core Dalamud assets.
|
||||
/// </summary>
|
||||
public string AssetDirectory { get; init; }
|
||||
public string? AssetDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language of the game client.
|
||||
/// Gets or sets the language of the game client.
|
||||
/// </summary>
|
||||
public ClientLanguage Language { get; init; }
|
||||
public ClientLanguage Language { get; set; } = ClientLanguage.English;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current game version code.
|
||||
/// Gets or sets the current game version code.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion GameVersion { get; init; }
|
||||
public GameVersion? GameVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value that specifies how much to wait before a new Dalamud session.
|
||||
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
||||
/// </summary>
|
||||
public int DelayInitializeMs { get; init; } = 0;
|
||||
public int DelayInitializeMs { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path the boot log file is supposed to be written to.
|
||||
/// </summary>
|
||||
public string BootLogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a Boot console should be shown.
|
||||
/// </summary>
|
||||
public bool BootShowConsole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the fallback console should be shown, if needed.
|
||||
/// </summary>
|
||||
public bool BootDisableFallbackConsole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating where Dalamud should wait with a message box.
|
||||
/// </summary>
|
||||
public int BootWaitMessageBox { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether Dalamud should wait for a debugger to be attached before initializing.
|
||||
/// </summary>
|
||||
public bool BootWaitDebugger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the VEH should be enabled.
|
||||
/// </summary>
|
||||
public bool BootVehEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the VEH should be doing full crash dumps.
|
||||
/// </summary>
|
||||
public bool BootVehFull { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not ETW should be enabled.
|
||||
/// </summary>
|
||||
public bool BootEnableEtw { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value choosing the OpenProcess hookmode.
|
||||
/// </summary>
|
||||
public int BootDotnetOpenProcessHookMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of enabled game fixes.
|
||||
/// </summary>
|
||||
public List<string>? BootEnabledGameFixes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of DLLs that should be unhooked.
|
||||
/// </summary>
|
||||
public List<string>? BootUnhookDlls { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Dalamud.Interface.Internal;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
using ImGuiScene;
|
||||
using JetBrains.Annotations;
|
||||
using Lumina;
|
||||
|
|
@ -298,6 +299,8 @@ namespace Dalamud.Data
|
|||
|
||||
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
|
||||
|
||||
using (Timings.Start("Lumina Init"))
|
||||
{
|
||||
var luminaOptions = new LuminaOptions
|
||||
{
|
||||
CacheFileResources = true,
|
||||
|
|
@ -314,8 +317,13 @@ namespace Dalamud.Data
|
|||
{
|
||||
this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not main module.");
|
||||
}
|
||||
|
||||
Log.Information("Lumina is ready: {0}", this.GameData.DataPath);
|
||||
}
|
||||
|
||||
this.IsDataReady = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ namespace Dalamud
|
|||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
||||
/// </summary>
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public delegate void InitDelegate(IntPtr infoPtr);
|
||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
public delegate void InitDelegate(IntPtr infoPtr, IntPtr mainThreadContinueEvent);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate used from VEH handler on exception which CoreCLR will fast fail by default.
|
||||
|
|
@ -43,12 +44,13 @@ namespace Dalamud
|
|||
/// Initialize Dalamud.
|
||||
/// </summary>
|
||||
/// <param name="infoPtr">Pointer to a serialized <see cref="DalamudStartInfo"/> data.</param>
|
||||
public static void Initialize(IntPtr infoPtr)
|
||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
||||
|
||||
new Thread(() => RunThread(info)).Start();
|
||||
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,18 +108,11 @@ namespace Dalamud
|
|||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="DalamudStartInfo"/> containing information needed to initialize Dalamud.</param>
|
||||
private static void RunThread(DalamudStartInfo info)
|
||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
if (EnvironmentConfiguration.DalamudWaitForDebugger)
|
||||
{
|
||||
while (!Debugger.IsAttached)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup logger
|
||||
var levelSwitch = InitLogging(info.WorkingDirectory);
|
||||
var levelSwitch = InitLogging(info.WorkingDirectory, info.BootShowConsole);
|
||||
|
||||
// Load configuration first to get some early persistent state, like log level
|
||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
||||
|
|
@ -150,11 +145,12 @@ namespace Dalamud
|
|||
if (!Util.IsLinux())
|
||||
InitSymbolHandler(info);
|
||||
|
||||
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration);
|
||||
Log.Information("Starting a session..");
|
||||
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent);
|
||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
||||
|
||||
// Run session
|
||||
dalamud.LoadTier1();
|
||||
|
||||
dalamud.WaitForUnload();
|
||||
|
||||
dalamud.Dispose();
|
||||
|
|
@ -197,7 +193,7 @@ namespace Dalamud
|
|||
}
|
||||
}
|
||||
|
||||
private static LoggingLevelSwitch InitLogging(string baseDirectory)
|
||||
private static LoggingLevelSwitch InitLogging(string baseDirectory, bool logConsole)
|
||||
{
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||
|
|
@ -211,11 +207,15 @@ namespace Dalamud
|
|||
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
||||
|
||||
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
var config = new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath, fileSizeLimitBytes: null, buffered: false, flushToDiskInterval: TimeSpan.FromSeconds(1)))
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
.MinimumLevel.ControlledBy(levelSwitch);
|
||||
|
||||
if (logConsole)
|
||||
config = config.WriteTo.Console();
|
||||
|
||||
Log.Logger = config.CreateLogger();
|
||||
|
||||
return levelSwitch;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ namespace Dalamud.Game
|
|||
// };
|
||||
|
||||
private readonly Regex rmtRegex = new(
|
||||
@"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia",
|
||||
RegexOptions.Compiled);
|
||||
@"4KGOLD|We have sufficient stock|VPK\.OM|[Gg]il for free|[Gg]il [Cc]heap|5GOLD|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|[Oo][Ff][Ff] [Cc]ode( *)[:;]|offers Fantasia",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
|
|
@ -100,12 +100,12 @@ namespace Dalamud.Game.ClientState
|
|||
/// <summary>
|
||||
/// Event that fires when a character is entering PvP.
|
||||
/// </summary>
|
||||
public event System.Action EnterPvP;
|
||||
public event Action EnterPvP;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a character is leaving PvP.
|
||||
/// </summary>
|
||||
public event System.Action LeavePvP;
|
||||
public event Action LeavePvP;
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when a duty is ready.
|
||||
|
|
@ -142,6 +142,11 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
public bool IsPvP { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
|
||||
/// </summary>
|
||||
public bool IsPvPExcludingDen { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
|
|
@ -203,11 +208,8 @@ namespace Dalamud.Game.ClientState
|
|||
gameGui.ResetUiHideState();
|
||||
}
|
||||
|
||||
if (this.TerritoryType != 0)
|
||||
{
|
||||
var terriRow = data.GetExcelSheet<TerritoryType>()!.GetRow(this.TerritoryType);
|
||||
this.IsPvP = terriRow?.Bg.RawString.StartsWith("ffxiv/pvp") ?? false;
|
||||
}
|
||||
this.IsPvP = GameMain.IsInPvPArea();
|
||||
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
||||
|
||||
if (this.IsPvP != this.lastFramePvP)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ namespace Dalamud.Game
|
|||
private bool tierInitError = false;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
private Hook<OnDestroyDetour> destroyHook;
|
||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||
private Hook<OnDestroyDetour> freeHook;
|
||||
private Hook<OnRealDestroyDelegate> destroyHook;
|
||||
|
||||
private Thread? frameworkUpdateThread;
|
||||
|
||||
|
|
@ -49,14 +49,9 @@ namespace Dalamud.Game
|
|||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup();
|
||||
|
||||
Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}");
|
||||
if (this.Address.BaseAddress == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||
}
|
||||
|
||||
// Hook virtual functions
|
||||
this.HookVTable();
|
||||
this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
|
||||
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -138,8 +133,8 @@ namespace Dalamud.Game
|
|||
Service<GameNetwork>.Get().Enable();
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.freeHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
this.realDestroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -229,38 +224,18 @@ namespace Dalamud.Game
|
|||
Service<GameNetwork>.GetNullable()?.ExplicitDispose();
|
||||
|
||||
this.updateHook?.Disable();
|
||||
this.freeHook?.Disable();
|
||||
this.destroyHook?.Disable();
|
||||
this.realDestroyHook?.Disable();
|
||||
Thread.Sleep(500);
|
||||
|
||||
this.updateHook?.Dispose();
|
||||
this.freeHook?.Dispose();
|
||||
this.destroyHook?.Dispose();
|
||||
this.realDestroyHook?.Dispose();
|
||||
|
||||
this.updateStopwatch.Reset();
|
||||
statsStopwatch.Reset();
|
||||
}
|
||||
|
||||
private void HookVTable()
|
||||
{
|
||||
var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress);
|
||||
// Virtual function layout:
|
||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||
// .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy
|
||||
// .rdata:00000001411F1FF8 dq offset Xiv__Framework__free
|
||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||
|
||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, this.HandleFrameworkUpdate);
|
||||
|
||||
var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3);
|
||||
this.destroyHook = new Hook<OnDestroyDetour>(pDestroy, this.HandleFrameworkDestroy);
|
||||
|
||||
var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2);
|
||||
this.realDestroyHook = new Hook<OnRealDestroyDelegate>(pRealDestroy, this.HandleRealDestroy);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
{
|
||||
// If any of the tier loads failed, just go to the original code.
|
||||
|
|
@ -371,7 +346,7 @@ namespace Dalamud.Game
|
|||
return this.updateHook.Original(framework);
|
||||
}
|
||||
|
||||
private bool HandleRealDestroy(IntPtr framework)
|
||||
private bool HandleFrameworkDestroy(IntPtr framework)
|
||||
{
|
||||
if (this.DispatchUpdateEvents)
|
||||
{
|
||||
|
|
@ -385,15 +360,15 @@ namespace Dalamud.Game
|
|||
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
||||
return this.realDestroyHook.Original(framework);
|
||||
return this.destroyHook.Original(framework);
|
||||
}
|
||||
|
||||
private IntPtr HandleFrameworkDestroy()
|
||||
private IntPtr HandleFrameworkFree()
|
||||
{
|
||||
Log.Information("Framework::Free!");
|
||||
|
||||
// Store the pointer to the original trampoline location
|
||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);
|
||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.freeHook.Original);
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
dalamud.Unload();
|
||||
|
|
|
|||
|
|
@ -1,55 +1,49 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game
|
||||
{
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
public sealed unsafe class FrameworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base address native Framework class.
|
||||
/// Gets the base address of the Framework object.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
[Obsolete("Please use FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance() instead.")]
|
||||
public IntPtr BaseAddress => new(FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native GuiManager class.
|
||||
/// Gets the address for the function that is called once the Framework is destroyed.
|
||||
/// </summary>
|
||||
public IntPtr GuiManager { get; private set; }
|
||||
public IntPtr DestroyAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the native ScriptManager class.
|
||||
/// Gets the address for the function that is called once the Framework is free'd.
|
||||
/// </summary>
|
||||
public IntPtr ScriptManager { get; private set; }
|
||||
public IntPtr FreeAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function that is called every tick.
|
||||
/// </summary>
|
||||
public IntPtr TickAddress { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(SigScanner sig)
|
||||
{
|
||||
this.SetupFramework(sig);
|
||||
|
||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||
this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08);
|
||||
|
||||
// Called from Framework::Init
|
||||
this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here
|
||||
}
|
||||
|
||||
private void SetupFramework(SigScanner scanner)
|
||||
{
|
||||
// Dissasembly of part of the .dtor
|
||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||
// 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38]
|
||||
// 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0
|
||||
// 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80
|
||||
var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D");
|
||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||
this.DestroyAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF");
|
||||
|
||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||
this.BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
this.FreeAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ??");
|
||||
|
||||
this.TickAddress =
|
||||
scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,11 @@ namespace Dalamud.Game.Gui
|
|||
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||
internal ChatGui(IntPtr baseAddress)
|
||||
internal ChatGui()
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver(baseAddress);
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup();
|
||||
|
||||
Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}");
|
||||
|
||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
|
|
|
|||
|
|
@ -7,20 +7,6 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address of the native ChatManager class.</param>
|
||||
public ChatGuiAddressResolver(IntPtr baseAddress)
|
||||
{
|
||||
this.BaseAddress = baseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native PrintMessage method.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ namespace Dalamud.Game.Gui
|
|||
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
|
||||
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
|
||||
|
||||
Service<ChatGui>.Set(new ChatGui(this.address.ChatManager));
|
||||
Service<ChatGui>.Set();
|
||||
Service<PartyFinderGui>.Set();
|
||||
Service<ToastGui>.Set();
|
||||
Service<FlyTextGui>.Set();
|
||||
|
|
|
|||
|
|
@ -7,24 +7,11 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameGuiAddressResolver"/> class.
|
||||
/// </summary>
|
||||
public GameGuiAddressResolver()
|
||||
{
|
||||
this.BaseAddress = Service<Framework>.Get().Address.BaseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the base address of the native GuiManager class.
|
||||
/// </summary>
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ChatManager class.
|
||||
/// </summary>
|
||||
public IntPtr ChatManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native SetGlobalBgm method.
|
||||
/// </summary>
|
||||
|
|
@ -89,13 +76,5 @@ namespace Dalamud.Game.Gui
|
|||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void SetupInternal(SigScanner scanner)
|
||||
{
|
||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||
this.ChatManager = this.BaseAddress + 0x13E0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
|
|
@ -14,7 +18,7 @@ namespace Dalamud.Game.Gui.Internal
|
|||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
internal class DalamudIME : IDisposable
|
||||
internal unsafe class DalamudIME : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
|
||||
|
|
@ -22,6 +26,8 @@ namespace Dalamud.Game.Gui.Internal
|
|||
private IntPtr wndProcPtr;
|
||||
private IntPtr oldWndProcPtr;
|
||||
private WndProcDelegate wndProcDelegate;
|
||||
private AsmHook imguiTextInputCursorHook;
|
||||
private Vector2* cursorPos;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||
|
|
@ -60,6 +66,18 @@ namespace Dalamud.Game.Gui.Internal
|
|||
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
||||
this.oldWndProcPtr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
this.imguiTextInputCursorHook?.Dispose();
|
||||
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the position of the cursor.
|
||||
/// </summary>
|
||||
/// <returns>The position of the cursor.</returns>
|
||||
internal Vector2 GetCursorPos()
|
||||
{
|
||||
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -73,6 +91,31 @@ namespace Dalamud.Game.Gui.Internal
|
|||
this.interfaceHandle = Service<InterfaceManager>.Get().WindowHandlePtr;
|
||||
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
|
||||
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
|
||||
|
||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
||||
var scanner = new SigScanner(module);
|
||||
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
||||
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
||||
|
||||
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
||||
this.cursorPos->X = 0f;
|
||||
this.cursorPos->Y = 0f;
|
||||
|
||||
var asm = new[]
|
||||
{
|
||||
"use64",
|
||||
$"push rax",
|
||||
$"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}",
|
||||
$"movss [rax],xmm7",
|
||||
$"mov rax, {(IntPtr)this.cursorPos}",
|
||||
$"movss [rax],xmm6",
|
||||
$"pop rax",
|
||||
};
|
||||
|
||||
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
||||
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
||||
this.imguiTextInputCursorHook?.Enable();
|
||||
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Dalamud.Game.Text;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
|
@ -90,8 +91,9 @@ namespace Dalamud.Game.Internal
|
|||
{
|
||||
var systemText = Service<DataManager>.Get().GetExcelSheet<Addon>()!.GetRow(1059)!.Text.RawString; // "System"
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
if (args.Title == systemText && configuration.DoButtonsSystemMenu)
|
||||
if (args.Title == systemText && configuration.DoButtonsSystemMenu && interfaceManager.IsDispatchingEvents)
|
||||
{
|
||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||
|
||||
|
|
@ -131,8 +133,9 @@ namespace Dalamud.Game.Internal
|
|||
}
|
||||
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
if (!configuration.DoButtonsSystemMenu)
|
||||
if (!configuration.DoButtonsSystemMenu || !interfaceManager.IsDispatchingEvents)
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game
|
||||
|
|
@ -16,17 +18,22 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
public sealed class SigScanner : IDisposable
|
||||
public class SigScanner : IDisposable
|
||||
{
|
||||
private readonly FileInfo? cacheFile;
|
||||
|
||||
private IntPtr moduleCopyPtr;
|
||||
private long moduleCopyOffset;
|
||||
|
||||
private Dictionary<string, long>? textCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process.
|
||||
/// </summary>
|
||||
/// <param name="doCopy">Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks.</param>
|
||||
public SigScanner(bool doCopy = false)
|
||||
: this(Process.GetCurrentProcess().MainModule!, doCopy)
|
||||
/// <param name="cacheFile">File used to cached signatures.</param>
|
||||
public SigScanner(bool doCopy = false, FileInfo? cacheFile = null)
|
||||
: this(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -35,8 +42,10 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
/// <param name="module">The ProcessModule to be used for scanning.</param>
|
||||
/// <param name="doCopy">Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks.</param>
|
||||
public SigScanner(ProcessModule module, bool doCopy = false)
|
||||
/// <param name="cacheFile">File used to cached signatures.</param>
|
||||
public SigScanner(ProcessModule module, bool doCopy = false, FileInfo? cacheFile = null)
|
||||
{
|
||||
this.cacheFile = cacheFile;
|
||||
this.Module = module;
|
||||
this.Is32BitProcess = !Environment.Is64BitProcess;
|
||||
this.IsCopy = doCopy;
|
||||
|
|
@ -49,6 +58,9 @@ namespace Dalamud.Game
|
|||
|
||||
Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}");
|
||||
Log.Verbose($"Module size: 0x{this.TextSectionSize:X}");
|
||||
|
||||
if (cacheFile != null)
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -294,8 +306,12 @@ namespace Dalamud.Game
|
|||
/// <returns>The real offset of the found signature.</returns>
|
||||
public IntPtr ScanText(string signature)
|
||||
{
|
||||
var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase;
|
||||
if (this.textCache != null && this.textCache.TryGetValue(signature, out var address))
|
||||
{
|
||||
return new IntPtr(address + this.Module.BaseAddress.ToInt64());
|
||||
}
|
||||
|
||||
var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase;
|
||||
var scanRet = Scan(mBase, this.TextSectionSize, signature);
|
||||
|
||||
if (this.IsCopy)
|
||||
|
|
@ -304,7 +320,9 @@ namespace Dalamud.Game
|
|||
var insnByte = Marshal.ReadByte(scanRet);
|
||||
|
||||
if (insnByte == 0xE8 || insnByte == 0xE9)
|
||||
return ReadJmpCallSig(scanRet);
|
||||
scanRet = ReadJmpCallSig(scanRet);
|
||||
|
||||
this.textCache?.Add(signature, scanRet.ToInt64() - this.Module.BaseAddress.ToInt64());
|
||||
|
||||
return scanRet;
|
||||
}
|
||||
|
|
@ -337,6 +355,17 @@ namespace Dalamud.Game
|
|||
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the current state of the cache.
|
||||
/// </summary>
|
||||
internal void Save()
|
||||
{
|
||||
if (this.cacheFile == null)
|
||||
return;
|
||||
|
||||
File.WriteAllText(this.cacheFile.FullName, JsonConvert.SerializeObject(this.textCache));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location.
|
||||
/// </summary>
|
||||
|
|
@ -479,5 +508,16 @@ namespace Dalamud.Game
|
|||
|
||||
this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
if (this.cacheFile is not { Exists: true })
|
||||
{
|
||||
this.textCache = new();
|
||||
return;
|
||||
}
|
||||
|
||||
this.textCache = JsonConvert.DeserializeObject<Dictionary<string, long>>(File.ReadAllText(this.cacheFile.FullName)) ?? new Dictionary<string, long>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,29 +9,31 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// </summary>
|
||||
public class FdtReader
|
||||
{
|
||||
private static unsafe T StructureFromByteArray<T> (byte[] data, int offset)
|
||||
{
|
||||
var len = Marshal.SizeOf<T>();
|
||||
if (offset + len > data.Length)
|
||||
throw new Exception("Data too short");
|
||||
|
||||
fixed (byte* ptr = data)
|
||||
return Marshal.PtrToStructure<T>(new(ptr + offset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FdtReader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">Content of a FDT file.</param>
|
||||
public FdtReader(byte[] data)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
this.FileHeader = *(FdtHeader*)ptr;
|
||||
this.FontHeader = *(FontTableHeader*)(ptr + this.FileHeader.FontTableHeaderOffset);
|
||||
this.KerningHeader = *(KerningTableHeader*)(ptr + this.FileHeader.KerningTableHeaderOffset);
|
||||
this.FileHeader = StructureFromByteArray<FdtHeader>(data, 0);
|
||||
this.FontHeader = StructureFromByteArray<FontTableHeader>(data, this.FileHeader.FontTableHeaderOffset);
|
||||
this.KerningHeader = StructureFromByteArray<KerningTableHeader>(data, this.FileHeader.KerningTableHeaderOffset);
|
||||
|
||||
var glyphs = (FontTableEntry*)(ptr + this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf(this.FontHeader));
|
||||
for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++)
|
||||
this.Glyphs.Add(glyphs[i]);
|
||||
this.Glyphs.Add(StructureFromByteArray<FontTableEntry>(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf<FontTableHeader>() + (Marshal.SizeOf<FontTableEntry>() * i)));
|
||||
|
||||
var kerns = (KerningTableEntry*)(ptr + this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf(this.KerningHeader));
|
||||
for (var i = 0; i < this.FontHeader.KerningTableEntryCount; i++)
|
||||
this.Distances.Add(kerns[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++)
|
||||
this.Distances.Add(StructureFromByteArray<KerningTableEntry>(data, this.FileHeader.KerningTableHeaderOffset+ Marshal.SizeOf<KerningTableHeader>() + (Marshal.SizeOf<KerningTableEntry>() * i)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
|||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Files;
|
||||
using Serilog;
|
||||
|
|
@ -39,7 +39,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
private readonly Dictionary<GameFontStyle, int> fontUseCounter = new();
|
||||
private readonly Dictionary<GameFontStyle, Dictionary<char, Tuple<int, FdtReader.FontTableEntry>>> glyphRectIds = new();
|
||||
|
||||
private bool isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||
private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
|
||||
private bool isBuildingAsFallbackFontMode = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -49,12 +49,31 @@ namespace Dalamud.Interface.GameFonts
|
|||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
using (Timings.Start("Load FDTs"))
|
||||
{
|
||||
this.fdts = FontNames.Select(fontName =>
|
||||
{
|
||||
var file = fontName == null ? null : dataManager.GetFile($"common/font/{fontName}.fdt");
|
||||
var fileName = $"common/font/{fontName}.fdt";
|
||||
using (Timings.Start($"Loading FDT: {fileName}"))
|
||||
{
|
||||
var file = fontName == null ? null : dataManager.GetFile(fileName);
|
||||
return file == null ? null : new FdtReader(file!.Data);
|
||||
}
|
||||
}).ToArray();
|
||||
this.texturePixels = Enumerable.Range(1, 1 + this.fdts.Where(x => x != null).Select(x => x.Glyphs.Select(x => x.TextureFileIndex).Max()).Max()).Select(x => dataManager.GameData.GetFile<TexFile>($"common/font/font{x}.tex").ImageData).ToList();
|
||||
}
|
||||
|
||||
using (Timings.Start("Getting texture data"))
|
||||
{
|
||||
this.texturePixels = Enumerable.Range(1, 1 + this.fdts.Where(x => x != null).Select(x => x.Glyphs.Select(x => x.TextureFileIndex).Max()).Max()).Select(
|
||||
x =>
|
||||
{
|
||||
var fileName = $"common/font/font{x}.tex";
|
||||
using (Timings.Start($"Get tex: {fileName}"))
|
||||
{
|
||||
return dataManager.GameData.GetFile<TexFile>(fileName)!.ImageData;
|
||||
}
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
this.interfaceManager = Service<InterfaceManager>.Get();
|
||||
}
|
||||
|
|
@ -174,7 +193,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
needRebuild = !this.fonts.ContainsKey(style);
|
||||
if (needRebuild)
|
||||
{
|
||||
if (Service<InterfaceManager>.Get().IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndAfterBuildFonts)
|
||||
if (Service<InterfaceManager>.Get().IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild)
|
||||
{
|
||||
Log.Information("[GameFontManager] NewFontRef: Building {0} right now, as it is called while BuildFonts is already in progress yet atlas build has not been called yet.", style.ToString());
|
||||
this.EnsureFont(style);
|
||||
|
|
@ -246,7 +265,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
public void BuildFonts(bool forceMinSize)
|
||||
{
|
||||
this.isBuildingAsFallbackFontMode = forceMinSize;
|
||||
this.isBetweenBuildFontsAndAfterBuildFonts = true;
|
||||
this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = true;
|
||||
|
||||
this.glyphRectIds.Clear();
|
||||
this.fonts.Clear();
|
||||
|
|
@ -255,6 +274,21 @@ namespace Dalamud.Interface.GameFonts
|
|||
this.EnsureFont(style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record that ImGui.GetIO().Fonts.Build() has been called.
|
||||
/// </summary>
|
||||
public void AfterIoFontsBuild()
|
||||
{
|
||||
this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether GameFontMamager owns an ImFont.
|
||||
/// </summary>
|
||||
/// <param name="fontPtr">ImFontPtr to check.</param>
|
||||
/// <returns>Whether it owns.</returns>
|
||||
public bool OwnsFont(ImFontPtr fontPtr) => this.fonts.ContainsValue(fontPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Post-build fonts before plugins do something more. To be called from InterfaceManager.
|
||||
/// </summary>
|
||||
|
|
@ -270,6 +304,9 @@ namespace Dalamud.Interface.GameFonts
|
|||
var fdt = this.fdts[(int)(this.isBuildingAsFallbackFontMode ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||
var scale = style.SizePt / fdt.FontHeader.Size;
|
||||
var fontPtr = font.NativePtr;
|
||||
|
||||
Log.Verbose("[GameFontManager] AfterBuildFonts: Scaling {0} from {1}pt to {2}pt (scale: {3})", style.ToString(), fdt.FontHeader.Size, style.SizePt, scale);
|
||||
|
||||
fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3;
|
||||
if (fontPtr->ConfigData != null)
|
||||
fontPtr->ConfigData->SizePixels = fontPtr->FontSize;
|
||||
|
|
@ -364,12 +401,8 @@ namespace Dalamud.Interface.GameFonts
|
|||
}
|
||||
}
|
||||
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, font, true, false);
|
||||
UnscaleFont(font, 1 / scale, false);
|
||||
font.BuildLookupTable();
|
||||
}
|
||||
|
||||
this.isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
66
Dalamud/Interface/ImGuiExtensions.cs
Normal file
66
Dalamud/Interface/ImGuiExtensions.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface;
|
||||
|
||||
public static class ImGuiExtensions
|
||||
{
|
||||
public static void AddTextClippedEx(
|
||||
this ImDrawListPtr drawListPtr, Vector2 posMin, Vector2 posMax, string text, Vector2? textSizeIfKnown,
|
||||
Vector2 align, Vector4? clipRect)
|
||||
{
|
||||
var pos = posMin;
|
||||
var textSize = textSizeIfKnown ?? ImGui.CalcTextSize(text, false, 0);
|
||||
|
||||
var clipMin = clipRect.HasValue ? new Vector2(clipRect.Value.X, clipRect.Value.Y) : posMin;
|
||||
var clipMax = clipRect.HasValue ? new Vector2(clipRect.Value.Z, clipRect.Value.W) : posMax;
|
||||
|
||||
var needClipping = (pos.X + textSize.X >= clipMax.X) || (pos.Y + textSize.Y >= clipMax.Y);
|
||||
if (clipRect.HasValue)
|
||||
needClipping |= (pos.X < clipMin.X) || (pos.Y < clipMin.Y);
|
||||
|
||||
if (align.X > 0)
|
||||
{
|
||||
pos.X = Math.Max(pos.X, pos.X + ((posMax.X - pos.X - textSize.X) * align.X));
|
||||
}
|
||||
|
||||
if (align.Y > 0)
|
||||
{
|
||||
pos.Y = Math.Max(pos.Y, pos.Y + ((posMax.Y - pos.Y - textSize.Y) * align.Y));
|
||||
}
|
||||
|
||||
if (needClipping)
|
||||
{
|
||||
var fineClipRect = new Vector4(clipMin.X, clipMin.Y, clipMax.X, clipMax.Y);
|
||||
drawListPtr.AddText(ImGui.GetFont(), ImGui.GetFontSize(), pos, ImGui.GetColorU32(ImGuiCol.Text), text, ref fineClipRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawListPtr.AddText(ImGui.GetFont(), ImGui.GetFontSize(), pos, ImGui.GetColorU32(ImGuiCol.Text), text);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should go into ImDrawList.Manual.cs in ImGui.NET...
|
||||
public static unsafe void AddText(this ImDrawListPtr drawListPtr, ImFontPtr font, float fontSize, Vector2 pos, uint col, string textBegin, ref Vector4 cpuFineClipRect)
|
||||
{
|
||||
var nativeFont = font.NativePtr;
|
||||
var textBeginByteCount = Encoding.UTF8.GetByteCount(textBegin);
|
||||
var nativeTextBegin = stackalloc byte[textBeginByteCount + 1];
|
||||
|
||||
fixed (char* textBeginPtr = textBegin)
|
||||
{
|
||||
var nativeTextBeginOffset = Encoding.UTF8.GetBytes(textBeginPtr, textBegin.Length, nativeTextBegin, textBeginByteCount);
|
||||
nativeTextBegin[nativeTextBeginOffset] = 0;
|
||||
}
|
||||
|
||||
byte* nativeTextEnd = null;
|
||||
var wrapWidth = 0.0f;
|
||||
|
||||
fixed (Vector4* nativeCpuFineClipRect = &cpuFineClipRect)
|
||||
{
|
||||
ImGuiNative.ImDrawList_AddText_FontPtr(drawListPtr.NativePtr, nativeFont, fontSize, pos, col, nativeTextBegin, nativeTextEnd, wrapWidth, nativeCpuFineClipRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -298,70 +298,25 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
private void SetupSideBar()
|
||||
{
|
||||
var drives = DriveInfo.GetDrives();
|
||||
foreach (var drive in drives)
|
||||
foreach (var drive in DriveInfo.GetDrives())
|
||||
{
|
||||
this.drives.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Server,
|
||||
Location = drive.Name,
|
||||
Text = drive.Name,
|
||||
});
|
||||
this.drives.Add(new SideBarItem(drive.Name, drive.Name, FontAwesomeIcon.Server));
|
||||
}
|
||||
|
||||
var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Desktop,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
Text = "Desktop",
|
||||
});
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.File,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
Text = "Documents",
|
||||
});
|
||||
this.quickAccess.Add(new SideBarItem("Desktop", Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FontAwesomeIcon.Desktop));
|
||||
this.quickAccess.Add(new SideBarItem("Documents", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), FontAwesomeIcon.File));
|
||||
|
||||
if (!string.IsNullOrEmpty(personal))
|
||||
{
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Download,
|
||||
Location = Path.Combine(personal, "Downloads"),
|
||||
Text = "Downloads",
|
||||
});
|
||||
this.quickAccess.Add(new SideBarItem("Downloads", Path.Combine(personal, "Downloads"), FontAwesomeIcon.Download));
|
||||
}
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Star,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.Favorites),
|
||||
Text = "Favorites",
|
||||
});
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Music,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic),
|
||||
Text = "Music",
|
||||
});
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Image,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
|
||||
Text = "Pictures",
|
||||
});
|
||||
|
||||
this.quickAccess.Add(new SideBarItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.Video,
|
||||
Location = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos),
|
||||
Text = "Videos",
|
||||
});
|
||||
this.quickAccess.Add(new SideBarItem("Favorites", Environment.GetFolderPath(Environment.SpecialFolder.Favorites), FontAwesomeIcon.Star));
|
||||
this.quickAccess.Add(new SideBarItem("Music", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), FontAwesomeIcon.Music));
|
||||
this.quickAccess.Add(new SideBarItem("Pictures", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), FontAwesomeIcon.Image));
|
||||
this.quickAccess.Add(new SideBarItem("Videos", Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), FontAwesomeIcon.Video));
|
||||
}
|
||||
|
||||
private void SortFields(SortingField sortingField, bool canChangeOrder = false)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiFileDialog
|
||||
{
|
||||
|
|
@ -19,11 +21,26 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
public string FileModifiedDate;
|
||||
}
|
||||
|
||||
private struct SideBarItem
|
||||
private readonly struct SideBarItem
|
||||
{
|
||||
public char Icon;
|
||||
public string Text;
|
||||
public string Location;
|
||||
public SideBarItem(string text, string location, FontAwesomeIcon icon)
|
||||
{
|
||||
this.Text = text;
|
||||
this.Location = location;
|
||||
this.Icon = icon;
|
||||
this.Exists = !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location);
|
||||
}
|
||||
|
||||
public string Text { get; init; }
|
||||
|
||||
public string Location { get; init; }
|
||||
|
||||
public FontAwesomeIcon Icon { get; init; }
|
||||
|
||||
public bool Exists { get; init; }
|
||||
|
||||
public bool CheckExistence()
|
||||
=> !this.Location.IsNullOrEmpty() && Directory.Exists(this.Location);
|
||||
}
|
||||
|
||||
private struct FilterStruct
|
||||
|
|
@ -50,7 +67,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
private struct IconColorItem
|
||||
{
|
||||
public char Icon;
|
||||
public FontAwesomeIcon Icon;
|
||||
public Vector4 Color;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using ImGuiNET;
|
||||
|
|
@ -43,11 +44,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
if (this.isModal && !this.okResultToConfirm)
|
||||
{
|
||||
ImGui.OpenPopup(name);
|
||||
windowVisible = ImGui.BeginPopupModal(name, ref this.visible, ImGuiWindowFlags.NoScrollbar);
|
||||
windowVisible = ImGui.BeginPopupModal(name, ref this.visible, this.WindowFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
windowVisible = ImGui.Begin(name, ref this.visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoNav);
|
||||
windowVisible = ImGui.Begin(name, ref this.visible, this.WindowFlags);
|
||||
}
|
||||
|
||||
bool wasClosed = false;
|
||||
|
|
@ -99,7 +100,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res);
|
||||
}
|
||||
|
||||
private static void AddToIconMap(string[] extensions, char icon, Vector4 color)
|
||||
private static void AddToIconMap(string[] extensions, FontAwesomeIcon icon, Vector4 color)
|
||||
{
|
||||
foreach (var ext in extensions)
|
||||
{
|
||||
|
|
@ -116,19 +117,19 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
if (iconMap == null)
|
||||
{
|
||||
iconMap = new();
|
||||
AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, (char)FontAwesomeIcon.FileVideo, miscTextColor);
|
||||
AddToIconMap(new[] { "pdf" }, (char)FontAwesomeIcon.FilePdf, miscTextColor);
|
||||
AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, (char)FontAwesomeIcon.FileImage, imageTextColor);
|
||||
AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, (char)FontAwesomeIcon.FileCode, codeTextColor);
|
||||
AddToIconMap(new[] { "txt", "md" }, (char)FontAwesomeIcon.FileAlt, standardTextColor);
|
||||
AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, (char)FontAwesomeIcon.FileArchive, miscTextColor);
|
||||
AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, (char)FontAwesomeIcon.FileAudio, miscTextColor);
|
||||
AddToIconMap(new[] { "csv" }, (char)FontAwesomeIcon.FileCsv, miscTextColor);
|
||||
AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, FontAwesomeIcon.FileVideo, miscTextColor);
|
||||
AddToIconMap(new[] { "pdf" }, FontAwesomeIcon.FilePdf, miscTextColor);
|
||||
AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, FontAwesomeIcon.FileImage, imageTextColor);
|
||||
AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, FontAwesomeIcon.FileCode, codeTextColor);
|
||||
AddToIconMap(new[] { "txt", "md" }, FontAwesomeIcon.FileAlt, standardTextColor);
|
||||
AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, FontAwesomeIcon.FileArchive, miscTextColor);
|
||||
AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, FontAwesomeIcon.FileAudio, miscTextColor);
|
||||
AddToIconMap(new[] { "csv" }, FontAwesomeIcon.FileCsv, miscTextColor);
|
||||
}
|
||||
|
||||
return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem
|
||||
{
|
||||
Icon = (char)FontAwesomeIcon.File,
|
||||
Icon = FontAwesomeIcon.File,
|
||||
Color = standardTextColor,
|
||||
};
|
||||
}
|
||||
|
|
@ -147,7 +148,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
private void DrawPathComposer()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button($"{(this.pathInputActivated ? (char)FontAwesomeIcon.Times : (char)FontAwesomeIcon.Edit)}"))
|
||||
if (ImGui.Button(this.pathInputActivated ? FontAwesomeIcon.Times.ToIconString() : FontAwesomeIcon.Edit.ToIconString()))
|
||||
{
|
||||
this.pathInputActivated = !this.pathInputActivated;
|
||||
}
|
||||
|
|
@ -158,13 +159,10 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
if (this.pathDecomposition.Count > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
|
||||
if (this.pathInputActivated)
|
||||
{
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255);
|
||||
ImGui.PopItemWidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -203,7 +201,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
private void DrawSearchBar()
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button($"{(char)FontAwesomeIcon.Home}"))
|
||||
if (ImGui.Button(FontAwesomeIcon.Home.ToIconString()))
|
||||
{
|
||||
this.SetPath(".");
|
||||
}
|
||||
|
|
@ -222,12 +220,10 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
if (!this.createDirectoryMode)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("Search :");
|
||||
ImGui.TextUnformatted("Search :");
|
||||
ImGui.SameLine();
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
var edited = ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255);
|
||||
ImGui.PopItemWidth();
|
||||
if (edited)
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255))
|
||||
{
|
||||
this.ApplyFilteringOnFileList();
|
||||
}
|
||||
|
|
@ -239,14 +235,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button($"{(char)FontAwesomeIcon.FolderPlus}"))
|
||||
{
|
||||
if (!this.createDirectoryMode)
|
||||
if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString()) && !this.createDirectoryMode)
|
||||
{
|
||||
this.createDirectoryMode = true;
|
||||
this.createDirectoryBuffer = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
|
|
@ -258,12 +251,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
if (this.createDirectoryMode)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Text("New Directory Name");
|
||||
ImGui.TextUnformatted("New Directory Name");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - 100f);
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 100f);
|
||||
ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255);
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
|
|
@ -292,10 +284,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
if (!this.flags.HasFlag(ImGuiFileDialogFlags.HideSideBar))
|
||||
{
|
||||
ImGui.BeginChild("##FileDialog_ColumnChild", size);
|
||||
if (ImGui.BeginChild("##FileDialog_ColumnChild", size))
|
||||
{
|
||||
ImGui.Columns(2, "##FileDialog_Columns");
|
||||
|
||||
this.DrawSideBar(new Vector2(150, size.Y));
|
||||
this.DrawSideBar(size with { X = 150 });
|
||||
|
||||
ImGui.SetColumnWidth(0, 150);
|
||||
ImGui.NextColumn();
|
||||
|
|
@ -303,6 +296,8 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
this.DrawFileListView(size - new Vector2(160, 0));
|
||||
|
||||
ImGui.Columns(1);
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
else
|
||||
|
|
@ -313,45 +308,30 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
private void DrawSideBar(Vector2 size)
|
||||
{
|
||||
ImGui.BeginChild("##FileDialog_SideBar", size);
|
||||
if (ImGui.BeginChild("##FileDialog_SideBar", size))
|
||||
{
|
||||
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5);
|
||||
|
||||
foreach (var drive in this.drives)
|
||||
var idx = 0;
|
||||
foreach (var qa in this.drives.Concat(this.quickAccess).Where(qa => qa.Exists))
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Selectable($"{drive.Icon}##{drive.Text}", drive.Text == this.selectedSideBar))
|
||||
ImGui.PushID(idx++);
|
||||
ImGui.SetCursorPosX(25);
|
||||
if (ImGui.Selectable(qa.Text, qa.Text == this.selectedSideBar) && qa.CheckExistence())
|
||||
{
|
||||
this.SetPath(drive.Location);
|
||||
this.selectedSideBar = drive.Text;
|
||||
}
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
ImGui.SameLine(25);
|
||||
|
||||
ImGui.Text(drive.Text);
|
||||
}
|
||||
|
||||
foreach (var quick in this.quickAccess)
|
||||
{
|
||||
if (string.IsNullOrEmpty(quick.Location))
|
||||
{
|
||||
continue;
|
||||
this.SetPath(qa.Location);
|
||||
this.selectedSideBar = qa.Text;
|
||||
}
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Selectable($"{quick.Icon}##{quick.Text}", quick.Text == this.selectedSideBar))
|
||||
{
|
||||
this.SetPath(quick.Location);
|
||||
this.selectedSideBar = quick.Text;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosX(0);
|
||||
ImGui.TextUnformatted(qa.Icon.ToIconString());
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
ImGui.SameLine(25);
|
||||
|
||||
ImGui.Text(quick.Text);
|
||||
ImGui.PopID();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
|
@ -359,9 +339,13 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
private unsafe void DrawFileListView(Vector2 size)
|
||||
{
|
||||
ImGui.BeginChild("##FileDialog_FileList", size);
|
||||
if (!ImGui.BeginChild("##FileDialog_FileList", size))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
var tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX;
|
||||
const ImGuiTableFlags tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX;
|
||||
if (ImGui.BeginTable("##FileTable", 4, tableFlags, size))
|
||||
{
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
|
|
@ -429,11 +413,10 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
var item = !dir ? GetIcon(file.Ext) : new IconColorItem
|
||||
{
|
||||
Color = dirTextColor,
|
||||
Icon = (char)FontAwesomeIcon.Folder,
|
||||
Icon = FontAwesomeIcon.Folder,
|
||||
};
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, item.Color);
|
||||
if (selected) ImGui.PushStyleColor(ImGuiCol.Text, selectedTextColor);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, selected ? selectedTextColor : item.Color);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
|
||||
|
|
@ -444,30 +427,28 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
ImGui.Text(file.Ext);
|
||||
ImGui.TextUnformatted(file.Ext);
|
||||
}
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
if (file.Type == FileStructType.File)
|
||||
{
|
||||
ImGui.Text(file.FormattedFileSize + " ");
|
||||
ImGui.TextUnformatted(file.FormattedFileSize + " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text(" ");
|
||||
ImGui.TextUnformatted(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.TableNextColumn())
|
||||
{
|
||||
var sz = ImGui.CalcTextSize(file.FileModifiedDate);
|
||||
ImGui.PushItemWidth(sz.X + 5);
|
||||
ImGui.Text(file.FileModifiedDate + " ");
|
||||
ImGui.PopItemWidth();
|
||||
ImGui.SetNextItemWidth(sz.X + 5);
|
||||
ImGui.TextUnformatted(file.FileModifiedDate + " ");
|
||||
}
|
||||
|
||||
if (selected) ImGui.PopStyleColor();
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (needToBreak) break;
|
||||
|
|
@ -475,6 +456,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
}
|
||||
|
||||
clipper.End();
|
||||
clipper.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -503,13 +485,13 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private bool SelectableItem(FileStruct file, bool selected, char icon)
|
||||
private bool SelectableItem(FileStruct file, bool selected, FontAwesomeIcon icon)
|
||||
{
|
||||
var flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns;
|
||||
const ImGuiSelectableFlags flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
|
||||
ImGui.Text($"{icon}");
|
||||
ImGui.TextUnformatted(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
ImGui.SameLine(25f);
|
||||
|
|
@ -523,7 +505,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
this.pathClicked = this.SelectDirectory(file);
|
||||
return true;
|
||||
}
|
||||
else if (this.IsDirectoryMode())
|
||||
if (this.IsDirectoryMode())
|
||||
{
|
||||
this.SelectFileName(file);
|
||||
}
|
||||
|
|
@ -712,11 +694,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
if (this.IsDirectoryMode())
|
||||
{
|
||||
ImGui.Text("Directory Path :");
|
||||
ImGui.TextUnformatted("Directory Path :");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("File Name :");
|
||||
ImGui.TextUnformatted("File Name :");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -729,18 +711,17 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
|
||||
var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly);
|
||||
|
||||
ImGui.PushItemWidth(width);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (selectOnly) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.InputText("##FileName", ref this.fileNameBuffer, 255, selectOnly ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None);
|
||||
if (selectOnly) ImGui.PopStyleVar();
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (this.filters.Count > 0)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
var needToApplyNewFilter = false;
|
||||
|
||||
ImGui.PushItemWidth(150f);
|
||||
ImGui.SetNextItemWidth(150f);
|
||||
if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None))
|
||||
{
|
||||
var idx = 0;
|
||||
|
|
@ -760,8 +741,6 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.PopItemWidth();
|
||||
|
||||
if (needToApplyNewFilter)
|
||||
{
|
||||
this.SetPath(this.currentPath);
|
||||
|
|
@ -825,12 +804,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
{ // quit dialog, it doesn't exist anyway
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{ // already exists, open dialog to confirm overwrite
|
||||
|
||||
// already exists, open dialog to confirm overwrite
|
||||
this.isOk = false;
|
||||
this.okResultToConfirm = true;
|
||||
}
|
||||
}
|
||||
|
||||
var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog";
|
||||
var res = false;
|
||||
|
|
@ -839,7 +817,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
ImGui.OpenPopup(name);
|
||||
if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove))
|
||||
{
|
||||
ImGui.Text("Would you like to Overwrite it ?");
|
||||
ImGui.TextUnformatted("Would you like to Overwrite it ?");
|
||||
if (ImGui.Button("Confirm"))
|
||||
{
|
||||
this.okResultToConfirm = false;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiFileDialog
|
||||
{
|
||||
|
|
@ -10,6 +11,11 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// </summary>
|
||||
public partial class FileDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// The flags used to draw the file picker window.
|
||||
/// </summary>
|
||||
public ImGuiWindowFlags WindowFlags;
|
||||
|
||||
private readonly string title;
|
||||
private readonly int selectionCountMax;
|
||||
private readonly ImGuiFileDialogFlags flags;
|
||||
|
|
@ -74,6 +80,9 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
this.flags = flags;
|
||||
this.selectionCountMax = selectionCountMax;
|
||||
this.isModal = isModal;
|
||||
this.WindowFlags = ImGuiWindowFlags.NoNav;
|
||||
if (!isModal)
|
||||
this.WindowFlags |= ImGuiWindowFlags.NoScrollbar;
|
||||
|
||||
this.currentPath = path;
|
||||
this.defaultExtension = defaultExtension;
|
||||
|
|
@ -161,6 +170,42 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
return this.currentPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set or remove a quick access folder for the navigation panel.
|
||||
/// </summary>
|
||||
/// <param name="name">The displayed name of the folder. If this name already exists, it will be overwritten.</param>
|
||||
/// <param name="path">The new linked path. If this is empty, no link will be added and existing links will be removed.</param>
|
||||
/// <param name="icon">The FontAwesomeIcon-ID of the icon displayed before the name.</param>
|
||||
/// <param name="position">An optional position at which to insert the new link. If the link is updated, having this less than zero will keep its position.
|
||||
/// Otherwise, invalid indices will insert it at the end.</param>
|
||||
public void SetQuickAccess(string name, string path, FontAwesomeIcon icon, int position = -1)
|
||||
{
|
||||
var idx = this.quickAccess.FindIndex(q => q.Text.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (idx >= 0)
|
||||
{
|
||||
if (position >= 0 || path.Length == 0)
|
||||
{
|
||||
this.quickAccess.RemoveAt(idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.quickAccess[idx] = new SideBarItem(name, path, icon);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.Length == 0) return;
|
||||
|
||||
if (position < 0 || position >= this.quickAccess.Count)
|
||||
{
|
||||
this.quickAccess.Add(new SideBarItem(name, path, icon));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.quickAccess.Insert(position, new SideBarItem(name, path, icon));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFilePathName()
|
||||
{
|
||||
var path = this.GetCurrentPath();
|
||||
|
|
@ -184,7 +229,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
var result = this.fileNameBuffer;
|
||||
|
||||
// a collection like {.cpp, .h}, so can't decide on an extension
|
||||
if (this.selectedFilter.CollectionFilters != null && this.selectedFilter.CollectionFilters.Count > 0)
|
||||
if (this.selectedFilter.CollectionFilters is { Count: > 0 })
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
|
@ -195,7 +240,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
var lastPoint = result.LastIndexOf('.');
|
||||
if (lastPoint != -1)
|
||||
{
|
||||
result = result.Substring(0, lastPoint);
|
||||
result = result[..lastPoint];
|
||||
}
|
||||
|
||||
result += this.selectedFilter.Filter;
|
||||
|
|
@ -230,7 +275,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
this.currentPath = dir.FullName;
|
||||
if (this.currentPath[^1] == Path.DirectorySeparatorChar)
|
||||
{ // handle selecting a drive, like C: -> C:\
|
||||
this.currentPath = this.currentPath[0..^1];
|
||||
this.currentPath = this.currentPath[..^1];
|
||||
}
|
||||
|
||||
this.pathInputBuffer = this.currentPath;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.ImGuiFileDialog
|
||||
{
|
||||
|
|
@ -8,6 +9,12 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// </summary>
|
||||
public class FileDialogManager
|
||||
{
|
||||
/// <summary> Additional quick access items for the side bar.</summary>
|
||||
public readonly List<(string Name, string Path, FontAwesomeIcon Icon, int Position)> CustomSideBarItems = new();
|
||||
|
||||
/// <summary> Additional flags with which to draw the window. </summary>
|
||||
public ImGuiWindowFlags AddedWindowFlags = ImGuiWindowFlags.None;
|
||||
|
||||
private FileDialog? dialog;
|
||||
private Action<bool, string>? callback;
|
||||
private Action<bool, List<string>>? multiCallback;
|
||||
|
|
@ -181,6 +188,9 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
}
|
||||
|
||||
this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags);
|
||||
this.dialog.WindowFlags |= this.AddedWindowFlags;
|
||||
foreach (var (name, location, icon, position) in this.CustomSideBarItems)
|
||||
this.dialog.SetQuickAccess(name, location, icon, position);
|
||||
this.dialog.Show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,41 +16,41 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <summary>
|
||||
/// Confirm the selection when choosing a file which already exists.
|
||||
/// </summary>
|
||||
ConfirmOverwrite = 1,
|
||||
ConfirmOverwrite = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Only allow selection of files or folders which currently exist.
|
||||
/// </summary>
|
||||
SelectOnly = 2,
|
||||
SelectOnly = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Hide files or folders which start with a period.
|
||||
/// </summary>
|
||||
DontShowHiddenFiles = 3,
|
||||
DontShowHiddenFiles = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Disable the creation of new folders within the dialog.
|
||||
/// </summary>
|
||||
DisableCreateDirectoryButton = 4,
|
||||
DisableCreateDirectoryButton = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Hide the type column.
|
||||
/// </summary>
|
||||
HideColumnType = 5,
|
||||
HideColumnType = 0x10,
|
||||
|
||||
/// <summary>
|
||||
/// Hide the file size column.
|
||||
/// </summary>
|
||||
HideColumnSize = 6,
|
||||
HideColumnSize = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// Hide the last modified date column.
|
||||
/// </summary>
|
||||
HideColumnDate = 7,
|
||||
HideColumnDate = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// Hide the quick access sidebar.
|
||||
/// </summary>
|
||||
HideSideBar = 8,
|
||||
HideSideBar = 0x80,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,13 @@ namespace Dalamud.Interface.Internal
|
|||
HelpMessage = "Dalamud version info",
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/xlui", new CommandInfo(this.OnUiCommand)
|
||||
{
|
||||
HelpMessage = Loc.Localize(
|
||||
"DalamudUiModeHelp",
|
||||
"Toggle Dalamud UI display modes. Native UI modifications may also be affected by this, but that depends on the plugin."),
|
||||
});
|
||||
|
||||
commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand)
|
||||
{
|
||||
HelpMessage = "ImGui DEBUG",
|
||||
|
|
@ -366,5 +373,31 @@ namespace Dalamud.Interface.Internal
|
|||
{
|
||||
Service<DalamudInterface>.Get().ToggleSettingsWindow();
|
||||
}
|
||||
|
||||
private void OnUiCommand(string command, string arguments)
|
||||
{
|
||||
var im = Service<InterfaceManager>.Get();
|
||||
|
||||
im.IsDispatchingEvents = arguments switch
|
||||
{
|
||||
"show" => true,
|
||||
"hide" => false,
|
||||
_ => !im.IsDispatchingEvents,
|
||||
};
|
||||
|
||||
var pm = Service<PluginManager>.Get();
|
||||
|
||||
foreach (var plugin in pm.InstalledPlugins)
|
||||
{
|
||||
if (im.IsDispatchingEvents)
|
||||
{
|
||||
plugin.DalamudInterface?.UiBuilder.NotifyShowUi();
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.DalamudInterface?.UiBuilder.NotifyHideUi();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ namespace Dalamud.Interface.Internal
|
|||
private readonly StyleEditorWindow styleEditorWindow;
|
||||
private readonly TitleScreenMenuWindow titleScreenMenuWindow;
|
||||
private readonly FallbackFontNoticeWindow fallbackFontNoticeWindow;
|
||||
private readonly ProfilerWindow profilerWindow;
|
||||
|
||||
private readonly TextureWrap logoTexture;
|
||||
private readonly TextureWrap tsmLogoTexture;
|
||||
|
|
@ -66,7 +67,12 @@ namespace Dalamud.Interface.Internal
|
|||
private bool isImGuiDrawDevMenu = false;
|
||||
#endif
|
||||
|
||||
#if BOOT_AGING
|
||||
private bool signaledBoot = false;
|
||||
#endif
|
||||
|
||||
private bool isImGuiDrawDemoWindow = false;
|
||||
private bool isImGuiTestWindowsInMonospace = false;
|
||||
private bool isImGuiDrawMetricsWindow = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -94,6 +100,7 @@ namespace Dalamud.Interface.Internal
|
|||
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
||||
this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false };
|
||||
this.fallbackFontNoticeWindow = new FallbackFontNoticeWindow() { IsOpen = interfaceManager.IsFallbackFontMode && !configuration.DisableFontFallbackNotice };
|
||||
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
||||
|
||||
this.WindowSystem.AddWindow(this.changelogWindow);
|
||||
this.WindowSystem.AddWindow(this.colorDemoWindow);
|
||||
|
|
@ -110,6 +117,7 @@ namespace Dalamud.Interface.Internal
|
|||
this.WindowSystem.AddWindow(this.styleEditorWindow);
|
||||
this.WindowSystem.AddWindow(this.titleScreenMenuWindow);
|
||||
this.WindowSystem.AddWindow(this.fallbackFontNoticeWindow);
|
||||
this.WindowSystem.AddWindow(this.profilerWindow);
|
||||
|
||||
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
|
||||
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
|
||||
|
|
@ -256,6 +264,11 @@ namespace Dalamud.Interface.Internal
|
|||
/// </summary>
|
||||
public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the <see cref="ProfilerWindow"/>.
|
||||
/// </summary>
|
||||
public void OpenProfiler() => this.profilerWindow.IsOpen = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Close
|
||||
|
|
@ -347,12 +360,30 @@ namespace Dalamud.Interface.Internal
|
|||
/// </summary>
|
||||
public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle();
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the <see cref="ProfilerWindow"/>.
|
||||
/// </summary>
|
||||
public void ToggleProfilerWindow() => this.profilerWindow.Toggle();
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnDraw()
|
||||
{
|
||||
this.frameCount++;
|
||||
|
||||
#if BOOT_AGING
|
||||
if (this.frameCount > 500 && !this.signaledBoot)
|
||||
{
|
||||
this.signaledBoot = true;
|
||||
|
||||
System.Threading.Tasks.Task.Run(async () =>
|
||||
{
|
||||
using var client = new System.Net.Http.HttpClient();
|
||||
await client.PostAsync("http://localhost:1415/aging/success", new System.Net.Http.StringContent(string.Empty));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
this.DrawHiddenDevMenuOpener();
|
||||
|
|
@ -363,12 +394,18 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
this.WindowSystem.Draw();
|
||||
|
||||
if (this.isImGuiTestWindowsInMonospace)
|
||||
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||
|
||||
if (this.isImGuiDrawDemoWindow)
|
||||
ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow);
|
||||
|
||||
if (this.isImGuiDrawMetricsWindow)
|
||||
ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow);
|
||||
|
||||
if (this.isImGuiTestWindowsInMonospace)
|
||||
ImGui.PopFont();
|
||||
|
||||
// Release focus of any ImGui window if we click into the game.
|
||||
var io = ImGui.GetIO();
|
||||
if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0)
|
||||
|
|
@ -519,6 +556,11 @@ namespace Dalamud.Interface.Internal
|
|||
this.OpenStyleEditor();
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Open Profiler"))
|
||||
{
|
||||
this.OpenProfiler();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.MenuItem("Unload Dalamud"))
|
||||
|
|
@ -566,8 +608,8 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
if (ImGui.BeginMenu("GUI"))
|
||||
{
|
||||
ImGui.MenuItem("Use Monospace font for following windows", string.Empty, ref this.isImGuiTestWindowsInMonospace);
|
||||
ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow);
|
||||
|
||||
ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow);
|
||||
|
||||
ImGui.Separator();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
using PInvoke;
|
||||
|
|
@ -151,7 +152,7 @@ namespace Dalamud.Interface.Internal
|
|||
/// <summary>
|
||||
/// Gets or sets an action that is executed right after font fallback mode has been changed.
|
||||
/// </summary>
|
||||
public event Action<bool> OnFallbackFontModeChange;
|
||||
public event Action<bool> FallbackFontModeChange;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default ImGui font.
|
||||
|
|
@ -202,6 +203,11 @@ namespace Dalamud.Interface.Internal
|
|||
/// </summary>
|
||||
public bool IsReady => this.scene != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not Draw events should be dispatched.
|
||||
/// </summary>
|
||||
public bool IsDispatchingEvents { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the font has been loaded in fallback mode.
|
||||
/// </summary>
|
||||
|
|
@ -214,7 +220,7 @@ namespace Dalamud.Interface.Internal
|
|||
return;
|
||||
|
||||
this.isFallbackFontMode = value;
|
||||
this.OnFallbackFontModeChange?.Invoke(value);
|
||||
this.FallbackFontModeChange?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -468,6 +474,8 @@ namespace Dalamud.Interface.Internal
|
|||
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
||||
|
||||
if (this.scene == null)
|
||||
{
|
||||
using (Timings.Start("IM Scene Init"))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -519,8 +527,6 @@ namespace Dalamud.Interface.Internal
|
|||
this.scene.OnBuildUI += this.Display;
|
||||
this.scene.OnNewInputFrame += this.OnNewInputFrame;
|
||||
|
||||
this.SetupFonts();
|
||||
|
||||
StyleModel.TransferOldModels();
|
||||
|
||||
if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name))
|
||||
|
|
@ -552,6 +558,8 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||
|
||||
this.SetupFonts();
|
||||
|
||||
if (!configuration.IsDocking)
|
||||
{
|
||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
|
||||
|
|
@ -582,6 +590,7 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
Service<DalamudIME>.Get().Enable();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.address.IsReshade)
|
||||
{
|
||||
|
|
@ -628,6 +637,8 @@ namespace Dalamud.Interface.Internal
|
|||
/// <param name="disableBigFonts">If set, then glyphs will be loaded in smaller resolution to make all glyphs fit into given constraints.</param>
|
||||
private unsafe void SetupFonts(bool disableBigFonts = false)
|
||||
{
|
||||
using var setupFontsTimings = Timings.Start("IM SetupFonts");
|
||||
|
||||
var gameFontManager = Service<GameFontManager>.Get();
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
var io = ImGui.GetIO();
|
||||
|
|
@ -828,10 +839,15 @@ namespace Dalamud.Interface.Internal
|
|||
for (int i = customFontFirstConfigIndex, i_ = ioFonts.ConfigData.Size; i < i_; i++)
|
||||
{
|
||||
var config = ioFonts.ConfigData[i];
|
||||
if (gameFontManager.OwnsFont(config.DstFont))
|
||||
continue;
|
||||
|
||||
config.OversampleH = 1;
|
||||
config.OversampleV = 1;
|
||||
|
||||
var name = Encoding.UTF8.GetString((byte*)config.Name.Data, config.Name.Count).TrimEnd('\0');
|
||||
if (name.IsNullOrEmpty())
|
||||
name = $"{config.SizePixels}px";
|
||||
|
||||
// ImFont information is reflected only if corresponding ImFontConfig has MergeMode not set.
|
||||
if (config.MergeMode)
|
||||
|
|
@ -869,6 +885,7 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
Log.Verbose("[FONT] ImGui.IO.Build will be called.");
|
||||
ioFonts.Build();
|
||||
gameFontManager.AfterIoFontsBuild();
|
||||
Log.Verbose("[FONT] ImGui.IO.Build OK!");
|
||||
|
||||
if (ioFonts.TexHeight > maxTexDimension)
|
||||
|
|
@ -886,6 +903,7 @@ namespace Dalamud.Interface.Internal
|
|||
if (possibilityForScaling && !disableBigFonts)
|
||||
{
|
||||
Log.Information("[FONT] Atlas size is {0}x{1} which is bigger than allowed {2}x{3}. Retrying with minimized font sizes.", ioFonts.TexWidth, ioFonts.TexHeight, maxTexDimension, maxTexDimension);
|
||||
this.IsFallbackFontMode = true;
|
||||
this.SetupFonts(true);
|
||||
return;
|
||||
}
|
||||
|
|
@ -895,7 +913,8 @@ namespace Dalamud.Interface.Internal
|
|||
}
|
||||
}
|
||||
|
||||
this.IsFallbackFontMode = disableBigFonts;
|
||||
if (!disableBigFonts)
|
||||
this.IsFallbackFontMode = false;
|
||||
|
||||
if (Math.Abs(fontGamma - 1.0f) >= 0.001)
|
||||
{
|
||||
|
|
@ -929,9 +948,10 @@ namespace Dalamud.Interface.Internal
|
|||
if (mod.Axis == TargetFontModification.AxisMode.Overwrite)
|
||||
{
|
||||
Log.Verbose("[FONT] {0}: Overwrite from AXIS of size {1}px (was {2}px)", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize);
|
||||
font.FontSize = mod.SourceAxis.ImFont.FontSize;
|
||||
font.Ascent = mod.SourceAxis.ImFont.Ascent;
|
||||
font.Descent = mod.SourceAxis.ImFont.Descent;
|
||||
GameFontManager.UnscaleFont(font, font.FontSize / mod.SourceAxis.ImFont.FontSize, false);
|
||||
var ascentDiff = mod.SourceAxis.ImFont.Ascent - font.Ascent;
|
||||
font.Ascent += ascentDiff;
|
||||
font.Descent = ascentDiff;
|
||||
font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar;
|
||||
font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar;
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false);
|
||||
|
|
@ -964,6 +984,9 @@ namespace Dalamud.Interface.Internal
|
|||
this.AfterBuildFonts?.Invoke();
|
||||
Log.Verbose("[FONT] OnAfterBuildFonts OK!");
|
||||
|
||||
if (ioFonts.Fonts[0].NativePtr != DefaultFont.NativePtr)
|
||||
Log.Warning("[FONT] First font is not DefaultFont");
|
||||
|
||||
Log.Verbose("[FONT] Fonts built!");
|
||||
|
||||
this.fontBuildSignal.Set();
|
||||
|
|
@ -995,7 +1018,36 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
Log.Verbose("[FONT] RebuildFontsInternal() detaching");
|
||||
this.scene.OnNewRenderFrame -= this.RebuildFontsInternal;
|
||||
|
||||
Log.Verbose("[FONT] Calling InvalidateFonts");
|
||||
try
|
||||
{
|
||||
this.scene.InvalidateFonts();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (this.FontResolutionLevel > 2)
|
||||
{
|
||||
Log.Error(ex, "[FONT] Failed to create font textures; setting font resolution level to 2 and retrying");
|
||||
this.FontResolutionLevelOverride = 2;
|
||||
this.SetupFonts();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error(ex, "[FONT] Failed to create font textures; forcing fallback font mode");
|
||||
this.SetupFonts(true);
|
||||
}
|
||||
|
||||
Log.Verbose("[FONT] Calling InvalidateFonts again");
|
||||
try
|
||||
{
|
||||
this.scene.InvalidateFonts();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error(ex2, "[FONT] Giving up");
|
||||
}
|
||||
}
|
||||
|
||||
Log.Verbose("[FONT] Font Rebuild OK!");
|
||||
|
||||
|
|
@ -1109,7 +1161,10 @@ namespace Dalamud.Interface.Internal
|
|||
WindowSystem.FocusedWindowSystemNamespace = string.Empty;
|
||||
|
||||
var snap = ImGuiManagedAsserts.GetSnapshot();
|
||||
|
||||
if (this.IsDispatchingEvents)
|
||||
this.Draw?.Invoke();
|
||||
|
||||
ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap);
|
||||
|
||||
Service<NotificationManager>.Get().Draw();
|
||||
|
|
|
|||
|
|
@ -28,24 +28,30 @@ Version D{0}
|
|||
created by:
|
||||
|
||||
goat
|
||||
Mino
|
||||
Meli
|
||||
attick
|
||||
Aida-Enna
|
||||
perchbird
|
||||
Wintermute
|
||||
fmauNeko
|
||||
Caraxi
|
||||
Adam
|
||||
nibs/Poliwrath
|
||||
karashiiro
|
||||
Pohky
|
||||
daemitus
|
||||
Aireil
|
||||
kalilistic
|
||||
MgAl2O4
|
||||
Soreepeong
|
||||
ff-meli
|
||||
attickdoor
|
||||
Caraxi
|
||||
ascclemens
|
||||
r00telement
|
||||
kalilistic
|
||||
0ceal0t
|
||||
lmcintyre
|
||||
pohky
|
||||
Aireil
|
||||
fitzchivalrik
|
||||
MgAl2O4
|
||||
NotAdam
|
||||
marimelon
|
||||
karashiiro
|
||||
pmgr
|
||||
Ottermandias
|
||||
aers
|
||||
Poliwrath
|
||||
Minizbot2021
|
||||
MalRD
|
||||
SheepGoMeh
|
||||
philpax
|
||||
|
||||
|
||||
|
||||
|
|
@ -99,7 +105,7 @@ Franz
|
|||
aers
|
||||
|
||||
|
||||
We use these awesome C# libraries:
|
||||
We use these awesome libraries:
|
||||
|
||||
Lumina by Adam
|
||||
FFXIVClientStructs by aers ({2})
|
||||
|
|
@ -107,7 +113,23 @@ FFXIVClientStructs by aers ({2})
|
|||
DotNetCorePlugins
|
||||
Copyright (c) Nate McMaster
|
||||
Licensed under the Apache License, Version 2.0
|
||||
See License.txt for license information.
|
||||
|
||||
json
|
||||
Copyright (c) 2013-2022 Niels Lohmann
|
||||
Licensed under the MIT License
|
||||
|
||||
nmd by Nomade040
|
||||
Licensed under the Unlicense
|
||||
|
||||
MinHook
|
||||
Copyright (C) 2009-2017 Tsuda Kageyu
|
||||
Licensed under the BSD 2-Clause License
|
||||
|
||||
SRELL
|
||||
Copyright (c) 2012-2022, Nozomu Katoo
|
||||
|
||||
Please see licenses.txt for more information.
|
||||
|
||||
|
||||
Thanks to everyone in the XIVLauncher Discord server
|
||||
|
||||
|
|
|
|||
|
|
@ -452,7 +452,6 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
else
|
||||
{
|
||||
stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n";
|
||||
stateString += $"ObjectTableLen: {objectTable.Length}\n";
|
||||
stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n";
|
||||
|
|
@ -530,7 +529,6 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
else
|
||||
{
|
||||
stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n";
|
||||
stateString += $"FateTableLen: {fateTable.Length}\n";
|
||||
|
||||
ImGui.TextUnformatted(stateString);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
Service<InterfaceManager>.Get().OnFallbackFontModeChange += this.OnFallbackFontModeChange;
|
||||
Service<InterfaceManager>.Get().FallbackFontModeChange += this.OnFallbackFontModeChange;
|
||||
}
|
||||
|
||||
private static string Title => Loc.Localize("FallbackFontNoticeWindowTitle", "Fallback Font Mode Active");
|
||||
|
|
@ -80,7 +80,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Service<InterfaceManager>.Get().OnFallbackFontModeChange -= this.OnFallbackFontModeChange;
|
||||
Service<InterfaceManager>.Get().FallbackFontModeChange -= this.OnFallbackFontModeChange;
|
||||
}
|
||||
|
||||
private void OnFallbackFontModeChange(bool mode)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Gui.Internal;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
/// <summary>
|
||||
/// A window for displaying IME details.
|
||||
/// </summary>
|
||||
internal class IMEWindow : Window
|
||||
internal unsafe class IMEWindow : Window
|
||||
{
|
||||
private const int ImePageSize = 9;
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
/// Initializes a new instance of the <see cref="IMEWindow"/> class.
|
||||
/// </summary>
|
||||
public IMEWindow()
|
||||
: base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize)
|
||||
: base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground)
|
||||
{
|
||||
this.Size = new Vector2(100, 200);
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
|
@ -29,6 +29,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
if (this.IsOpen && Service<KeyState>.Get()[VirtualKey.SHIFT]) Service<DalamudInterface>.Get().CloseIMEWindow();
|
||||
var ime = Service<DalamudIME>.GetNullable();
|
||||
|
||||
if (ime == null || !ime.IsEnabled)
|
||||
|
|
@ -36,34 +37,70 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGui.Text("IME is unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.Text(ime.ImmComp);
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var native = ime.ImmCandNative;
|
||||
for (var i = 0; i < ime.ImmCand.Count; i++)
|
||||
{
|
||||
var selected = i == (native.Selection % ImePageSize);
|
||||
|
||||
if (selected)
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen);
|
||||
|
||||
ImGui.Text($"{i + 1}. {ime.ImmCand[i]}");
|
||||
|
||||
if (selected)
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void PostDraw()
|
||||
{
|
||||
if (this.IsOpen && Service<KeyState>.Get()[VirtualKey.SHIFT]) Service<DalamudInterface>.Get().CloseIMEWindow();
|
||||
var ime = Service<DalamudIME>.GetNullable();
|
||||
|
||||
if (ime == null || !ime.IsEnabled)
|
||||
return;
|
||||
|
||||
var cursorPos = ime.GetCursorPos();
|
||||
|
||||
var nextDrawPosY = cursorPos.Y;
|
||||
var maxTextWidth = 0f;
|
||||
var textHeight = ImGui.CalcTextSize(ime.ImmComp).Y;
|
||||
var drawAreaPosX = cursorPos.X + ImGui.GetStyle().WindowPadding.X;
|
||||
|
||||
var native = ime.ImmCandNative;
|
||||
var totalIndex = native.Selection + 1;
|
||||
var totalSize = native.Count;
|
||||
|
||||
var pageStart = native.PageStart;
|
||||
var pageIndex = (pageStart / ImePageSize) + 1;
|
||||
var pageCount = (totalSize / ImePageSize) + 1;
|
||||
var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})";
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})");
|
||||
// Calc the window size
|
||||
for (var i = 0; i < ime.ImmCand.Count; i++)
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}");
|
||||
maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X;
|
||||
}
|
||||
|
||||
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X;
|
||||
maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X ? maxTextWidth : ImGui.CalcTextSize(ime.ImmComp).X;
|
||||
|
||||
var imeWindowMinPos = new Vector2(cursorPos.X, cursorPos.Y);
|
||||
var imeWindowMaxPos = new Vector2(cursorPos.X + maxTextWidth + (2 * ImGui.GetStyle().WindowPadding.X), cursorPos.Y + (textHeight * (ime.ImmCand.Count + 2)) + (5 * (ime.ImmCand.Count - 1)) + (2 * ImGui.GetStyle().WindowPadding.Y));
|
||||
|
||||
var drawList = ImGui.GetForegroundDrawList();
|
||||
// Draw the background rect
|
||||
drawList.AddRectFilled(imeWindowMinPos, imeWindowMaxPos, ImGui.GetColorU32(ImGuiCol.WindowBg), ImGui.GetStyle().WindowRounding);
|
||||
// Add component text
|
||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp);
|
||||
nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y;
|
||||
// Add separator
|
||||
drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator));
|
||||
// Add candidate words
|
||||
for (var i = 0; i < ime.ImmCand.Count; i++)
|
||||
{
|
||||
var selected = i == (native.Selection % ImePageSize);
|
||||
var color = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
if (selected)
|
||||
color = ImGui.GetColorU32(ImGuiCol.NavHighlight);
|
||||
|
||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), color, $"{i + 1}. {ime.ImmCand[i]}");
|
||||
nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y;
|
||||
}
|
||||
|
||||
// Add separator
|
||||
drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator));
|
||||
// Add pages infomation
|
||||
drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), pageInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -386,6 +386,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
|||
|
||||
if (this.errorModalOnNextFrame)
|
||||
{
|
||||
// NOTE(goat): ImGui cannot open a modal if no window is focused, at the moment.
|
||||
// If people click out of the installer into the game while a plugin is installing, we won't be able to show a modal if we don't grab focus.
|
||||
ImGui.SetWindowFocus(this.WindowName);
|
||||
|
||||
ImGui.OpenPopup(modalTitle);
|
||||
this.errorModalOnNextFrame = false;
|
||||
this.errorModalDrawing = true;
|
||||
|
|
|
|||
176
Dalamud/Interface/Internal/Windows/ProfilerWindow.cs
Normal file
176
Dalamud/Interface/Internal/Windows/ProfilerWindow.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility.Numerics;
|
||||
using Dalamud.Utility.Timing;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
public class ProfilerWindow : Window
|
||||
{
|
||||
private double min;
|
||||
private double max;
|
||||
|
||||
public ProfilerWindow() : base("Profiler", forceMainWindow: true) { }
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
this.min = Timings.AllTimings.Min(x => x.StartTime);
|
||||
this.max = Timings.AllTimings.Max(x => x.EndTime);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
var width = ImGui.GetWindowWidth();
|
||||
var actualMin = Timings.AllTimings.Min(x => x.StartTime);
|
||||
var actualMax = Timings.AllTimings.Max(x => x.EndTime);
|
||||
|
||||
ImGui.Text("Timings");
|
||||
|
||||
const int childHeight = 300;
|
||||
|
||||
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
|
||||
{
|
||||
var pos = ImGui.GetCursorScreenPos();
|
||||
|
||||
for (var i = 0; i < width; i += 80)
|
||||
{
|
||||
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||
|
||||
var lineEnd = childHeight - 20;
|
||||
|
||||
ImGui.GetWindowDrawList().AddLine(
|
||||
pos + new Vector2(i, 0),
|
||||
pos + new Vector2(i, lineEnd - 10),
|
||||
ImGui.GetColorU32(ImGuiColors.ParsedGrey.WithW(0x40)));
|
||||
|
||||
// Draw ms label for line
|
||||
var ms = ((i / width) * (this.max - this.min)) + this.min;
|
||||
var msStr = (ms / 1000).ToString("F2") + "s";
|
||||
var msSize = ImGui.CalcTextSize(msStr);
|
||||
var labelPos = pos + new Vector2(i - (msSize.X / 2), (-msSize.Y / 2) + lineEnd);
|
||||
|
||||
// nudge label to the side if it's the first, so we're not cut off
|
||||
if (i == 0)
|
||||
labelPos.X += msSize.X / 2;
|
||||
|
||||
ImGui.GetWindowDrawList().AddText(
|
||||
labelPos,
|
||||
ImGui.GetColorU32(ImGuiColors.ParsedGrey.WithW(0x40)),
|
||||
msStr);
|
||||
|
||||
ImGui.PopFont();
|
||||
}
|
||||
|
||||
uint maxRectDept = 0;
|
||||
|
||||
foreach (var timingHandle in Timings.AllTimings)
|
||||
{
|
||||
var startX = (timingHandle.StartTime - this.min) / (this.max - this.min) * width;
|
||||
var endX = (timingHandle.EndTime - this.min) / (this.max - this.min) * width;
|
||||
|
||||
startX = Math.Max(startX, 0);
|
||||
endX = Math.Max(endX, 0);
|
||||
|
||||
var rectColor = timingHandle.IsMainThread ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedPurple;
|
||||
rectColor.X -= timingHandle.Depth * 0.12f;
|
||||
rectColor.Y -= timingHandle.Depth * 0.12f;
|
||||
rectColor.Z -= timingHandle.Depth * 0.12f;
|
||||
|
||||
if (maxRectDept < timingHandle.Depth)
|
||||
maxRectDept = timingHandle.Depth;
|
||||
|
||||
if (startX == endX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var minPos = pos + new Vector2((uint)startX, 20 * timingHandle.Depth);
|
||||
var maxPos = pos + new Vector2((uint)endX, 20 * (timingHandle.Depth + 1));
|
||||
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
minPos,
|
||||
maxPos,
|
||||
ImGui.GetColorU32(rectColor));
|
||||
|
||||
ImGui.GetWindowDrawList().AddTextClippedEx(minPos, maxPos, timingHandle.Name, null, Vector2.Zero, null);
|
||||
|
||||
// Show tooltip when hovered
|
||||
var mousePos = ImGui.GetMousePos();
|
||||
if (mousePos.X > pos.X + startX && mousePos.X < pos.X + endX &&
|
||||
mousePos.Y > pos.Y + (20 * timingHandle.Depth) &&
|
||||
mousePos.Y < pos.Y + (20 * (timingHandle.Depth + 1)))
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.Text(timingHandle.Name);
|
||||
ImGui.Text(timingHandle.MemberName);
|
||||
ImGui.Text($"{timingHandle.FileName}:{timingHandle.LineNumber}");
|
||||
ImGui.Text($"Duration: {timingHandle.Duration}ms");
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
uint eventTextDepth = maxRectDept + 2;
|
||||
|
||||
foreach (var timingEvent in Timings.Events)
|
||||
{
|
||||
var startX = (timingEvent.StartTime - this.min) / (this.max - this.min) * width;
|
||||
|
||||
if (startX < 0 || startX > width)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui.GetWindowDrawList().AddLine(
|
||||
pos + new Vector2((uint)startX, 0),
|
||||
pos + new Vector2((uint)startX, childHeight),
|
||||
ImGui.GetColorU32(ImGuiColors.ParsedOrange),
|
||||
1.5f);
|
||||
|
||||
const uint padding = 5;
|
||||
|
||||
var textSize = ImGui.CalcTextSize(timingEvent.Name);
|
||||
var textPos = pos + new Vector2((uint)startX + padding, eventTextDepth * 20);
|
||||
|
||||
if (textPos.X + textSize.X > pos.X + width - 20)
|
||||
{
|
||||
textPos.X = pos.X + (uint)startX - textSize.X - padding;
|
||||
}
|
||||
|
||||
ImGui.GetWindowDrawList().AddText(
|
||||
textPos,
|
||||
ImGui.GetColorU32(ImGuiColors.DalamudWhite),
|
||||
timingEvent.Name);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
|
||||
var sliderMin = (float)this.min / 1000f;
|
||||
if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.1fs"))
|
||||
{
|
||||
this.min = sliderMin * 1000f;
|
||||
}
|
||||
|
||||
var sliderMax = (float)this.max / 1000f;
|
||||
if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.1fs"))
|
||||
{
|
||||
this.max = sliderMax * 1000f;
|
||||
}
|
||||
|
||||
var sizeShown = (float)(this.max - this.min);
|
||||
var sizeActual = (float)(actualMax - actualMin);
|
||||
if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.1fs"))
|
||||
{
|
||||
this.max = this.min + sizeShown;
|
||||
}
|
||||
|
||||
ImGui.Text("Min: " + actualMin.ToString("0.000"));
|
||||
ImGui.Text("Max: " + actualMax.ToString("0.000"));
|
||||
ImGui.Text("Timings: " + Timings.AllTimings.Count);
|
||||
}
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu."));
|
||||
|
||||
ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering);
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering."));
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable Dalamud's built-in RMT ad filtering."));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
|
|
@ -367,6 +367,8 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGuiHelpers.ScaledDummy(3);
|
||||
|
||||
ImGui.Text(Loc.Localize("DalamudSettingsFontResolutionLevel", "Font resolution level"));
|
||||
if (interfaceManager.FontResolutionLevelOverride != null)
|
||||
this.fontResolutionLevel = interfaceManager.FontResolutionLevelOverride.Value;
|
||||
if (ImGui.Combo("##DalamudSettingsFontResolutionLevelCombo", ref this.fontResolutionLevel, this.fontResolutionLevelStrings, this.fontResolutionLevelStrings.Length))
|
||||
{
|
||||
interfaceManager.FontResolutionLevelOverride = this.fontResolutionLevel;
|
||||
|
|
@ -375,7 +377,9 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||
ImGui.TextWrapped(string.Format(
|
||||
Loc.Localize("DalamudSettingsFontResolutionLevelHint", "This option allows Dalamud fonts to look better. If your game crashes when changing this option, your PC does not support high font resolutions in Dalamud - you will have to use a lower one.\nCurrent font atlas size is {0}px * {1}px."),
|
||||
Loc.Localize(
|
||||
"DalamudSettingsFontResolutionLevelHint",
|
||||
"This option allows Dalamud fonts to look better.\n* If your game crashes right away, or the option reverts, when changing this option, your PC does not support high font resolutions in Dalamud - you will have to use a lower one.\n* If it doesn't crash or revert immediately, then you can keep the new choice indefinitely as it's not going to crash your game once it worked.\n* Either choose the 3rd or 5th option. Use other options only when neither works well.\n* Current font atlas size is {0}px * {1}px."),
|
||||
ImGui.GetIO().Fonts.TexWidth,
|
||||
ImGui.GetIO().Fonts.TexHeight));
|
||||
ImGui.PopStyleColor();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ namespace Dalamud.Interface
|
|||
private readonly Stopwatch stopwatch;
|
||||
private readonly string namespaceName;
|
||||
|
||||
private bool hasErrorWindow;
|
||||
private bool hasErrorWindow = false;
|
||||
private bool lastFrameUiHideState = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UiBuilder"/> class and registers it.
|
||||
|
|
@ -78,6 +79,18 @@ namespace Dalamud.Interface
|
|||
/// </summary>
|
||||
public event Action AfterBuildFonts;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be hidden.
|
||||
/// These may be fired consecutively.
|
||||
/// </summary>
|
||||
public event Action ShowUi;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action that is called when plugin UI or interface modifications are supposed to be shown.
|
||||
/// These may be fired consecutively.
|
||||
/// </summary>
|
||||
public event Action HideUi;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons.
|
||||
/// </summary>
|
||||
|
|
@ -137,6 +150,36 @@ namespace Dalamud.Interface
|
|||
/// </summary>
|
||||
public ulong FrameCount { get; private set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not a cutscene is playing.
|
||||
/// </summary>
|
||||
public bool CutsceneActive
|
||||
{
|
||||
get
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
return condition[ConditionFlag.OccupiedInCutSceneEvent]
|
||||
|| condition[ConditionFlag.WatchingCutscene78];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not gpose is active.
|
||||
/// </summary>
|
||||
public bool GposeActive
|
||||
{
|
||||
get
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
return condition[ConditionFlag.WatchingCutscene];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin should modify the game's interface at this time.
|
||||
/// </summary>
|
||||
public bool ShouldModifyUi => Service<InterfaceManager>.GetNullable()?.IsDispatchingEvents ?? true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||
/// </summary>
|
||||
|
|
@ -166,25 +209,6 @@ namespace Dalamud.Interface
|
|||
/// </summary>
|
||||
internal List<long> DrawTimeHistory { get; set; } = new List<long>();
|
||||
|
||||
private bool CutsceneActive
|
||||
{
|
||||
get
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
return condition[ConditionFlag.OccupiedInCutSceneEvent]
|
||||
|| condition[ConditionFlag.WatchingCutscene78];
|
||||
}
|
||||
}
|
||||
|
||||
private bool GposeActive
|
||||
{
|
||||
get
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
return condition[ConditionFlag.WatchingCutscene];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an image from the specified file.
|
||||
/// </summary>
|
||||
|
|
@ -261,16 +285,49 @@ namespace Dalamud.Interface
|
|||
this.OpenConfigUi?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify this UiBuilder about plugin UI being hidden.
|
||||
/// </summary>
|
||||
internal void NotifyHideUi()
|
||||
{
|
||||
this.HideUi?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify this UiBuilder about plugin UI being shown.
|
||||
/// </summary>
|
||||
internal void NotifyShowUi()
|
||||
{
|
||||
this.ShowUi?.Invoke();
|
||||
}
|
||||
|
||||
private void OnDraw()
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
if ((gameGui.GameUiHidden && configuration.ToggleUiHide && !(this.DisableUserUiHide || this.DisableAutomaticUiHide)) ||
|
||||
(this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes && !(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) ||
|
||||
(this.GposeActive && configuration.ToggleUiHideDuringGpose && !(this.DisableGposeUiHide || this.DisableAutomaticUiHide)))
|
||||
if ((gameGui.GameUiHidden && configuration.ToggleUiHide &&
|
||||
!(this.DisableUserUiHide || this.DisableAutomaticUiHide)) ||
|
||||
(this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes &&
|
||||
!(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) ||
|
||||
(this.GposeActive && configuration.ToggleUiHideDuringGpose &&
|
||||
!(this.DisableGposeUiHide || this.DisableAutomaticUiHide)))
|
||||
{
|
||||
if (!this.lastFrameUiHideState)
|
||||
{
|
||||
this.lastFrameUiHideState = true;
|
||||
this.HideUi?.Invoke();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastFrameUiHideState)
|
||||
{
|
||||
this.lastFrameUiHideState = false;
|
||||
this.ShowUi?.Invoke();
|
||||
}
|
||||
|
||||
if (!interfaceManager.FontsReady)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ namespace Dalamud.Interface.Windowing
|
|||
public ImGuiCond PositionCondition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the window.
|
||||
/// Gets or sets the size of the window. The size provided will be scaled by the global scale.
|
||||
/// </summary>
|
||||
public Vector2? Size { get; set; }
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ namespace Dalamud.Interface.Windowing
|
|||
public ImGuiCond SizeCondition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size constraints of the window.
|
||||
/// Gets or sets the size constraints of the window. The size constraints provided will be scaled by the global scale.
|
||||
/// </summary>
|
||||
public WindowSizeConstraints? SizeConstraints { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1393,6 +1393,18 @@ namespace Dalamud
|
|||
WriteCombine = 0x400,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setevent
|
||||
/// Sets the specified event object to the signaled state.
|
||||
/// </summary>
|
||||
/// <param name="hEvent">A handle to the event object. The CreateEvent or OpenEvent function returns this handle.</param>
|
||||
/// <returns>
|
||||
/// If the function succeeds, the return value is nonzero.
|
||||
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
|
||||
/// </returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool SetEvent(IntPtr hEvent);
|
||||
|
||||
/// <summary>
|
||||
/// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary.
|
||||
/// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Dalamud.Game.Text.Sanitizer;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Plugin.Ipc.Exceptions;
|
||||
|
|
@ -139,6 +140,11 @@ namespace Dalamud.Plugin
|
|||
/// <summary>
|
||||
/// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds.
|
||||
/// </summary>
|
||||
public bool IsDevMenuOpen => Service<DalamudInterface>.GetNullable() is { IsDevMenuOpen: true }; // Can be null during boot
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a debugger is attached.
|
||||
/// </summary>
|
||||
public bool IsDebugging => Debugger.IsAttached;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
|||
using CheapLoc;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.Game.Text;
|
||||
|
|
@ -340,6 +341,9 @@ internal partial class PluginManager : IDisposable
|
|||
{
|
||||
this.PluginsReady = true;
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
|
||||
// Save signatures, makes sense to do it here since all plugins will be loaded
|
||||
Service<SigScanner>.Get().Save();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
131
Dalamud/Utility/MapUtil.cs
Normal file
131
Dalamud/Utility/MapUtil.cs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Utility helper class for game maps and coordinate translations that don't require state.
|
||||
///
|
||||
/// The conversion methods were found in 89 54 24 10 56 41 55 41 56 48 81 EC, which itself was found by looking for
|
||||
/// uses of AddonText 1631.
|
||||
/// </summary>
|
||||
public static class MapUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper method to convert one of the game's Vector3 X/Z provided by the game to a map coordinate suitable for
|
||||
/// display to the player.
|
||||
/// </summary>
|
||||
/// <param name="value">The raw float of a game Vector3 X or Z coordinate to convert.</param>
|
||||
/// <param name="scale">The scale factor of the map, generally retrieved from Lumina.</param>
|
||||
/// <param name="offset">The dimension offset for either X or Z, generally retrieved from Lumina.</param>
|
||||
/// <returns>Returns a converted float for display to the player.</returns>
|
||||
public static float ConvertWorldCoordXZToMapCoord(float value, uint scale, int offset)
|
||||
{
|
||||
// Derived from E8 ?? ?? ?? ?? 0F B7 4B 1C and simplified.
|
||||
|
||||
return (0.02f * offset) + (2048f / scale) + (0.02f * value) + 1.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to convert a game Vector3 Y coordinate to a map coordinate suitable for display to the player.
|
||||
/// </summary>
|
||||
/// <param name="value">The raw float of a game Vector3 Y coordinate to convert.</param>
|
||||
/// <param name="zOffset">The zOffset for this map. Retrieved from TerritoryTypeTransient.</param>
|
||||
/// <param name="correctZOffset">Optionally enable Z offset correction. When a Z offset of -10,000 is set, replace
|
||||
/// it with 0 for calculation purposes to show a more sane Z coordinate.</param>
|
||||
/// <returns>Returns a converted float for display to the player.</returns>
|
||||
public static float ConvertWorldCoordYToMapCoord(float value, int zOffset, bool correctZOffset = false)
|
||||
{
|
||||
// Derived from 48 83 EC 38 80 3D ?? ?? ?? ?? ?? 0F 29 74 24
|
||||
|
||||
// zOffset of -10000 indicates that the map should not display a Z coordinate.
|
||||
if (zOffset == -10000 && correctZOffset) zOffset = 0;
|
||||
|
||||
return (value - zOffset) / 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All-in-one helper method to convert a World Coordinate (internal to the game) to a Map Coordinate (visible to
|
||||
/// players in the minimap/elsewhere).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this method will swap Y and Z in the resulting Vector3 to appropriately reflect the game's display.
|
||||
/// </remarks>
|
||||
/// <param name="worldCoordinates">A Vector3 of raw World coordinates from the game.</param>
|
||||
/// <param name="xOffset">The offset to apply to the incoming X parameter, generally Lumina's Map.OffsetX.</param>
|
||||
/// <param name="yOffset">The offset to apply to the incoming Y parameter, generally Lumina's Map.OffsetY.</param>
|
||||
/// <param name="zOffset">The offset to apply to the incoming Z parameter, generally Lumina's TerritoryTypeTransient.OffsetZ.</param>
|
||||
/// <param name="scale">The global scale to apply to the incoming X and Y parameters, generally Lumina's Map.SizeFactor.</param>
|
||||
/// <param name="correctZOffset">An optional mode to "correct" a Z offset of -10000 to be a more human-friendly value.</param>
|
||||
/// <returns>Returns a Vector3 representing visible map coordinates.</returns>
|
||||
public static Vector3 WorldToMap(
|
||||
Vector3 worldCoordinates,
|
||||
int xOffset = 0,
|
||||
int yOffset = 0,
|
||||
int zOffset = 0,
|
||||
uint scale = 100,
|
||||
bool correctZOffset = false)
|
||||
{
|
||||
return new Vector3(
|
||||
ConvertWorldCoordXZToMapCoord(worldCoordinates.X, scale, xOffset),
|
||||
ConvertWorldCoordXZToMapCoord(worldCoordinates.Z, scale, yOffset),
|
||||
ConvertWorldCoordYToMapCoord(worldCoordinates.Y, zOffset, correctZOffset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All-in-one helper method to convert a World Coordinate (internal to the game) to a Map Coordinate (visible to
|
||||
/// players in the minimap/elsewhere).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this method will swap Y and Z to appropriately reflect the game's display.
|
||||
/// </remarks>
|
||||
/// <param name="worldCoordinates">A Vector3 of raw World coordinates from the game.</param>
|
||||
/// <param name="map">A Lumina map to use for offset/scale information.</param>
|
||||
/// <param name="territoryTransient">A TerritoryTypeTransient to use for Z offset information.</param>
|
||||
/// <param name="correctZOffset">An optional mode to "correct" a Z offset of -10000 to be a more human-friendly value.</param>
|
||||
/// <returns>Returns a Vector3 representing visible map coordinates.</returns>
|
||||
public static Vector3 WorldToMap(
|
||||
Vector3 worldCoordinates, Map map, TerritoryTypeTransient territoryTransient, bool correctZOffset = false)
|
||||
{
|
||||
return WorldToMap(
|
||||
worldCoordinates,
|
||||
map.OffsetX,
|
||||
map.OffsetY,
|
||||
territoryTransient.OffsetZ,
|
||||
map.SizeFactor,
|
||||
correctZOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All-in-one helper method to convert a World Coordinate (internal to the game) to a Map Coordinate (visible to
|
||||
/// players in the minimap/elsewhere).
|
||||
/// </summary>
|
||||
/// <param name="worldCoordinates">A Vector2 of raw World coordinates from the game.</param>
|
||||
/// <param name="xOffset">The offset to apply to the incoming X parameter, generally Lumina's Map.OffsetX.</param>
|
||||
/// <param name="yOffset">The offset to apply to the incoming Y parameter, generally Lumina's Map.OffsetY.</param>
|
||||
/// <param name="scale">The global scale to apply to the incoming X and Y parameters, generally Lumina's Map.SizeFactor.</param>
|
||||
/// <returns>Returns a Vector2 representing visible map coordinates.</returns>
|
||||
public static Vector2 WorldToMap(
|
||||
Vector2 worldCoordinates,
|
||||
int xOffset = 0,
|
||||
int yOffset = 0,
|
||||
uint scale = 100)
|
||||
{
|
||||
return new Vector2(
|
||||
ConvertWorldCoordXZToMapCoord(worldCoordinates.X, scale, xOffset),
|
||||
ConvertWorldCoordXZToMapCoord(worldCoordinates.Y, scale, yOffset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All-in-one helper method to convert a World Coordinate (internal to the game) to a Map Coordinate (visible to
|
||||
/// players in the minimap/elsewhere).
|
||||
/// </summary>
|
||||
/// <param name="worldCoordinates">A Vector2 of raw World coordinates from the game.</param>
|
||||
/// <param name="map">A Lumina map to use for offset/scale information.</param>
|
||||
/// <returns>Returns a Vector2 representing visible map coordinates.</returns>
|
||||
public static Vector2 WorldToMap(Vector2 worldCoordinates, Map map)
|
||||
{
|
||||
return WorldToMap(worldCoordinates, map.OffsetX, map.OffsetY, map.SizeFactor);
|
||||
}
|
||||
}
|
||||
56
Dalamud/Utility/Numerics/VectorExtensions.cs
Normal file
56
Dalamud/Utility/Numerics/VectorExtensions.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Dalamud.Utility.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for vectors.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Redundant.")]
|
||||
public static class VectorExtensions
|
||||
{
|
||||
public static Vector4 WithX(this Vector4 v, float x)
|
||||
{
|
||||
return new Vector4(x, v.Y, v.Z, v.W);
|
||||
}
|
||||
|
||||
public static Vector4 WithY(this Vector4 v, float y)
|
||||
{
|
||||
return new Vector4(v.X, y, v.Z, v.W);
|
||||
}
|
||||
|
||||
public static Vector4 WithZ(this Vector4 v, float z)
|
||||
{
|
||||
return new Vector4(v.X, v.Y, z, v.W);
|
||||
}
|
||||
|
||||
public static Vector4 WithW(this Vector4 v, float w)
|
||||
{
|
||||
return new Vector4(v.X, v.Y, v.Z, w);
|
||||
}
|
||||
|
||||
public static Vector3 WithX(this Vector3 v, float x)
|
||||
{
|
||||
return new Vector3(x, v.Y, v.Z);
|
||||
}
|
||||
|
||||
public static Vector3 WithY(this Vector3 v, float y)
|
||||
{
|
||||
return new Vector3(v.X, y, v.Z);
|
||||
}
|
||||
|
||||
public static Vector3 WithZ(this Vector3 v, float z)
|
||||
{
|
||||
return new Vector3(v.X, v.Y, z);
|
||||
}
|
||||
|
||||
public static Vector2 WithX(this Vector2 v, float x)
|
||||
{
|
||||
return new Vector2(x, v.Y);
|
||||
}
|
||||
|
||||
public static Vector2 WithY(this Vector2 v, float y)
|
||||
{
|
||||
return new Vector2(v.X, y);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Dalamud.Utility
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -18,13 +20,13 @@ namespace Dalamud.Utility
|
|||
/// </summary>
|
||||
/// <param name="value">The string to test.</param>
|
||||
/// <returns>true if the value parameter is null or an empty string (""); otherwise, false.</returns>
|
||||
public static bool IsNullOrEmpty(this string? value) => string.IsNullOrEmpty(value);
|
||||
public static bool IsNullOrEmpty([NotNullWhen(false)] this string? value) => string.IsNullOrEmpty(value);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to test.</param>
|
||||
/// <returns>true if the value parameter is null or an empty string (""), or if value consists exclusively of white-space characters.</returns>
|
||||
public static bool IsNullOrWhitespace(this string? value) => string.IsNullOrWhiteSpace(value);
|
||||
public static bool IsNullOrWhitespace([NotNullWhen(false)] this string? value) => string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
Dalamud/Utility/ThreadSafety.cs
Normal file
49
Dalamud/Utility/ThreadSafety.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Helpers for working with thread safety.
|
||||
/// </summary>
|
||||
public static class ThreadSafety
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool isMainThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current thread is the main thread.
|
||||
/// </summary>
|
||||
public static bool IsMainThread => isMainThread;
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception when the current thread is not the main thread.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the current thread is not the main thread.</exception>
|
||||
public static void AssertMainThread()
|
||||
{
|
||||
if (!isMainThread)
|
||||
{
|
||||
throw new InvalidOperationException("Not on main thread!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception when the current thread is the main thread.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the current thread is the main thread.</exception>
|
||||
public static void AssertNotMainThread()
|
||||
{
|
||||
if (isMainThread)
|
||||
{
|
||||
throw new InvalidOperationException("On main thread!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a thread as the main thread.
|
||||
/// </summary>
|
||||
internal static void MarkMainThread()
|
||||
{
|
||||
isMainThread = true;
|
||||
}
|
||||
}
|
||||
35
Dalamud/Utility/Timing/TimingEvent.cs
Normal file
35
Dalamud/Utility/Timing/TimingEvent.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
namespace Dalamud.Utility.Timing;
|
||||
|
||||
public class TimingEvent
|
||||
{
|
||||
internal TimingEvent(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
this.StartTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this timing started.
|
||||
/// </summary>
|
||||
public double StartTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the timing.
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member that created this timing.
|
||||
/// </summary>
|
||||
public string? MemberName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name that created this timing.
|
||||
/// </summary>
|
||||
public string? FileName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line number that created this timing.
|
||||
/// </summary>
|
||||
public int LineNumber { get; init; }
|
||||
}
|
||||
93
Dalamud/Utility/Timing/TimingHandle.cs
Normal file
93
Dalamud/Utility/Timing/TimingHandle.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Utility.Timing;
|
||||
|
||||
/// <summary>
|
||||
/// Class used for tracking a time interval taken.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{Name} - {Duration}")]
|
||||
public sealed class TimingHandle : TimingEvent, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimingHandle"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this timing.</param>
|
||||
internal TimingHandle(string name) : base(name)
|
||||
{
|
||||
this.Parent = Timings.Current.Value;
|
||||
Timings.Current.Value = this;
|
||||
|
||||
lock (Timings.AllTimings)
|
||||
{
|
||||
if (this.Parent != null)
|
||||
{
|
||||
this.ChildCount++;
|
||||
}
|
||||
|
||||
this.EndTime = this.StartTime;
|
||||
this.IsMainThread = ThreadSafety.IsMainThread;
|
||||
|
||||
if (Timings.ActiveTimings.Count > 0)
|
||||
{
|
||||
this.Depth = Timings.ActiveTimings.Max(x => x.Depth) + 1;
|
||||
}
|
||||
|
||||
Timings.ActiveTimings.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this timing ended.
|
||||
/// </summary>
|
||||
public double EndTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration of this timing.
|
||||
/// </summary>
|
||||
public double Duration => Math.Floor(this.EndTime - this.StartTime);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent timing.
|
||||
/// </summary>
|
||||
public TimingHandle? Parent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this timing has already returned to its parent.
|
||||
/// </summary>
|
||||
public bool Returned { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this timing was started on the main thread.
|
||||
/// </summary>
|
||||
public bool IsMainThread { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of child timings.
|
||||
/// </summary>
|
||||
public uint ChildCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the depth of this timing.
|
||||
/// </summary>
|
||||
public uint Depth { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.EndTime = Timings.Stopwatch.Elapsed.TotalMilliseconds;
|
||||
Timings.Current.Value = this.Parent;
|
||||
|
||||
lock (Timings.AllTimings)
|
||||
{
|
||||
if (this.Duration > 1 || this.ChildCount > 0)
|
||||
{
|
||||
Timings.AllTimings.Add(this);
|
||||
this.Returned = this.Parent != null && Timings.ActiveTimings.Contains(this.Parent);
|
||||
}
|
||||
|
||||
Timings.ActiveTimings.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Dalamud/Utility/Timing/Timings.cs
Normal file
75
Dalamud/Utility/Timing/Timings.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Dalamud.Utility.Timing;
|
||||
|
||||
/// <summary>
|
||||
/// Class for measuring time taken in various subsystems.
|
||||
/// </summary>
|
||||
public static class Timings
|
||||
{
|
||||
/// <summary>
|
||||
/// Stopwatch used for timing.
|
||||
/// </summary>
|
||||
internal static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
|
||||
|
||||
/// <summary>
|
||||
/// All concluded timings.
|
||||
/// </summary>
|
||||
internal static readonly List<TimingHandle> AllTimings = new();
|
||||
|
||||
/// <summary>
|
||||
/// All active timings.
|
||||
/// </summary>
|
||||
internal static readonly List<TimingHandle> ActiveTimings = new();
|
||||
|
||||
internal static readonly List<TimingEvent> Events = new();
|
||||
|
||||
/// <summary>
|
||||
/// Current active timing entry.
|
||||
/// </summary>
|
||||
internal static readonly AsyncLocal<TimingHandle> Current = new();
|
||||
|
||||
/// <summary>
|
||||
/// Start a new timing.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the timing.</param>
|
||||
/// <param name="memberName">Name of the calling member.</param>
|
||||
/// <param name="sourceFilePath">Name of the calling file.</param>
|
||||
/// <param name="sourceLineNumber">Name of the calling line number.</param>
|
||||
/// <returns>Disposable that stops the timing once disposed.</returns>
|
||||
public static IDisposable Start(string name, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
return new TimingHandle(name)
|
||||
{
|
||||
MemberName = memberName,
|
||||
FileName = sourceFilePath,
|
||||
LineNumber = sourceLineNumber,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record a one-time event.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the timing.</param>
|
||||
/// <param name="memberName">Name of the calling member.</param>
|
||||
/// <param name="sourceFilePath">Name of the calling file.</param>
|
||||
/// <param name="sourceLineNumber">Name of the calling line number.</param>
|
||||
/// <returns>Disposable that stops the timing once disposed.</returns>
|
||||
public static void Event(string name, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "",
|
||||
[CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
lock (Events)
|
||||
{
|
||||
Events.Add(new TimingEvent(name)
|
||||
{
|
||||
MemberName = memberName,
|
||||
FileName = sourceFilePath,
|
||||
LineNumber = sourceLineNumber,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
407
Dalamud/licenses.txt
Normal file
407
Dalamud/licenses.txt
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
============== SRELL ==============
|
||||
|
||||
/*****************************************************************************
|
||||
**
|
||||
** SRELL (std::regex-like library) version 3.009
|
||||
**
|
||||
** Copyright (c) 2012-2022, Nozomu Katoo. All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright notice,
|
||||
** this list of conditions and the following disclaimer.
|
||||
**
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||
** IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**
|
||||
******************************************************************************
|
||||
**/
|
||||
|
||||
|
||||
============== MINHOOK ==============
|
||||
|
||||
MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
================================================================================
|
||||
Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
================================================================================
|
||||
Hacker Disassembler Engine 32 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Hacker Disassembler Engine 64 C
|
||||
Copyright (c) 2008-2009, Vyacheslav Patkov.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
============== NLOHMANN/JSON ==============
|
||||
__ _____ _____ _____
|
||||
__| | __| | | | JSON for Modern C++
|
||||
| | |__ | | | | | | version 3.10.5
|
||||
|_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
|
||||
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||||
SPDX-License-Identifier: MIT
|
||||
Copyright (c) 2013-2022 Niels Lohmann <http://nlohmann.me>.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
============== NMD ==============
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
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 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.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
|
||||
|
||||
============== FFXIVCLIENTSTRUCTS ==============
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 aers
|
||||
|
||||
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.
|
||||
|
||||
|
||||
============== DOTNETCOREPLUGINS ==============
|
||||
|
||||
https://github.com/natemcmaster/DotNetCorePlugins
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include "nethost/nethost.h"
|
||||
#include "..\..\Dalamud.Boot\logging.h"
|
||||
|
||||
CoreCLR::CoreCLR(void* calling_module)
|
||||
: m_calling_module(calling_module)
|
||||
|
|
@ -82,7 +83,7 @@ int CoreCLR::load_runtime(const std::wstring& runtime_config_path, const struct
|
|||
// Success_HostAlreadyInitialized
|
||||
if (result == 1)
|
||||
{
|
||||
printf("Success_HostAlreadyInitialized (0x1) ");
|
||||
logging::I("Success_HostAlreadyInitialized (0x1)");
|
||||
result = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <Windows.h>
|
||||
#include <Shlobj.h>
|
||||
#include "CoreCLR.h"
|
||||
#include "..\..\Dalamud.Boot\logging.h"
|
||||
|
||||
FILE* g_CmdStream;
|
||||
void ConsoleSetup(const std::wstring console_name)
|
||||
|
|
@ -16,6 +17,7 @@ void ConsoleSetup(const std::wstring console_name)
|
|||
freopen_s(&g_CmdStream, "CONOUT$", "w", stdout);
|
||||
freopen_s(&g_CmdStream, "CONOUT$", "w", stderr);
|
||||
freopen_s(&g_CmdStream, "CONIN$", "r", stdin);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
}
|
||||
|
||||
void ConsoleTeardown()
|
||||
|
|
@ -27,6 +29,7 @@ std::optional<CoreCLR> g_clr;
|
|||
|
||||
int InitializeClrAndGetEntryPoint(
|
||||
void* calling_module,
|
||||
bool enable_etw,
|
||||
std::wstring runtimeconfig_path,
|
||||
std::wstring module_path,
|
||||
std::wstring entrypoint_assembly_name,
|
||||
|
|
@ -42,6 +45,13 @@ int InitializeClrAndGetEntryPoint(
|
|||
SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1");
|
||||
SetEnvironmentVariable(L"COMPLUS_ForceENC", L"1");
|
||||
|
||||
// Enable Dynamic PGO
|
||||
SetEnvironmentVariable(L"DOTNET_TieredPGO", L"1");
|
||||
SetEnvironmentVariable(L"DOTNET_TC_QuickJitForLoops", L"1");
|
||||
SetEnvironmentVariable(L"DOTNET_ReadyToRun", L"1");
|
||||
|
||||
SetEnvironmentVariable(L"COMPlus_ETWEnabled", enable_etw ? L"1" : L"0");
|
||||
|
||||
wchar_t* dotnet_path;
|
||||
wchar_t* _appdata;
|
||||
|
||||
|
|
@ -61,7 +71,7 @@ int InitializeClrAndGetEntryPoint(
|
|||
|
||||
if (result != 0)
|
||||
{
|
||||
printf("Error: Unable to get RoamingAppData path (err=%d)\n", result);
|
||||
logging::E("Unable to get RoamingAppData path (err={})", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -71,13 +81,13 @@ int InitializeClrAndGetEntryPoint(
|
|||
|
||||
// =========================================================================== //
|
||||
|
||||
wprintf(L"with dotnet_path: %s\n", dotnet_path);
|
||||
wprintf(L"with config_path: %s\n", runtimeconfig_path.c_str());
|
||||
wprintf(L"with module_path: %s\n", module_path.c_str());
|
||||
logging::I("with dotnet_path: {}", dotnet_path);
|
||||
logging::I("with config_path: {}", runtimeconfig_path);
|
||||
logging::I("with module_path: {}", module_path);
|
||||
|
||||
if (!std::filesystem::exists(dotnet_path))
|
||||
{
|
||||
printf("Error: Unable to find .NET runtime path\n");
|
||||
logging::E("Error: Unable to find .NET runtime path");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -88,13 +98,13 @@ int InitializeClrAndGetEntryPoint(
|
|||
dotnet_path,
|
||||
};
|
||||
|
||||
printf("Loading hostfxr... ");
|
||||
logging::I("Loading hostfxr...");
|
||||
if ((result = g_clr->load_hostfxr(&init_parameters)) != 0)
|
||||
{
|
||||
printf("\nError: Failed to load the `hostfxr` library (err=0x%08x)\n", result);
|
||||
logging::E("Failed to load the `hostfxr` library (err=0x{:08x})", result);
|
||||
return result;
|
||||
}
|
||||
printf("Done!\n");
|
||||
logging::I("Done!");
|
||||
|
||||
// =========================================================================== //
|
||||
|
||||
|
|
@ -105,17 +115,17 @@ int InitializeClrAndGetEntryPoint(
|
|||
dotnet_path,
|
||||
};
|
||||
|
||||
printf("Loading coreclr... ");;
|
||||
logging::I("Loading coreclr... ");
|
||||
if ((result = g_clr->load_runtime(runtimeconfig_path, &runtime_parameters)) != 0)
|
||||
{
|
||||
printf("\nError: Failed to load coreclr (err=%d)\n", result);
|
||||
logging::E("Failed to load coreclr (err=0x{:08X})", static_cast<uint32_t>(result));
|
||||
return result;
|
||||
}
|
||||
printf("Done!\n");
|
||||
logging::I("Done!");
|
||||
|
||||
// =========================================================================== //
|
||||
|
||||
printf("Loading module... ");
|
||||
logging::I("Loading module...");
|
||||
if ((result = g_clr->load_assembly_and_get_function_pointer(
|
||||
module_path.c_str(),
|
||||
entrypoint_assembly_name.c_str(),
|
||||
|
|
@ -123,10 +133,10 @@ int InitializeClrAndGetEntryPoint(
|
|||
entrypoint_delegate_type_name.c_str(),
|
||||
nullptr, entrypoint_fn)) != 0)
|
||||
{
|
||||
printf("\nError: Failed to load module (err=%d)\n", result);
|
||||
logging::E("Failed to load module (err={})", result);
|
||||
return result;
|
||||
}
|
||||
printf("Done!\n");
|
||||
logging::I("Done!");
|
||||
|
||||
// =========================================================================== //
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ void ConsoleTeardown();
|
|||
|
||||
int InitializeClrAndGetEntryPoint(
|
||||
void* calling_module,
|
||||
bool enable_etw,
|
||||
std::wstring runtimeconfig_path,
|
||||
std::wstring module_path,
|
||||
std::wstring entrypoint_assembly_name,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit a2972adbd333d0ad9c127fff1cfc288d1cecf6b4
|
||||
Subproject commit a0244fc290eb64fccfac2fd36704f7371f5c5c7f
|
||||
1
lib/Nomade040-nmd
Submodule
1
lib/Nomade040-nmd
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 33ac3b62c7d1eb28ae6b71d4dd78aa133ef96488
|
||||
1
lib/TsudaKageyu-minhook
Submodule
1
lib/TsudaKageyu-minhook
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 4a455528f61b5a375b1f9d44e7d296d47f18bb18
|
||||
22091
lib/nlohmann-json/json.hpp
Normal file
22091
lib/nlohmann-json/json.hpp
Normal file
File diff suppressed because it is too large
Load diff
484
lib/srell3_009/history_en.txt
Normal file
484
lib/srell3_009/history_en.txt
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
20220511; version 3.009:
|
||||
* Fixed an optimisation bug that caused /abcd|ab/ not to match "abc".
|
||||
|
||||
20220504; version 3.008:
|
||||
* Fixed the behaviour of [^\P{...}] when the icase flag is set, as it
|
||||
behaved similarly to the one in v-mode that has been proposed in
|
||||
TC39.
|
||||
|
||||
20220429; version 3.007:
|
||||
* Further modification to the counter mechanism.
|
||||
|
||||
20220428; version 3.006:
|
||||
* Modified the mechanism of the counter used for repetition.
|
||||
* Re-removed the implementation of linear search for small character
|
||||
classes.
|
||||
|
||||
20220424; version 3.005:
|
||||
* Fixed a bug that caused /(?<=$.*)/ not to match the end of "a" when
|
||||
the multiline flag is set
|
||||
* Preparations for \A, \z, (?m:) that have been proposed in TC39.
|
||||
|
||||
20220420; version 3.004:
|
||||
* Added a new optimisation for /A*B/ and /A+B/ where a character class
|
||||
A overlaps a character or character class B, such as /[A-Za-z]+ing/,
|
||||
/".*"/.
|
||||
|
||||
20220416; version 3.003:
|
||||
* Combined two optimisation functions into one.
|
||||
* Reduced the amount of code for lookaround (lookahead and lookbehind)
|
||||
assertions.
|
||||
|
||||
20220416; version 3.002:
|
||||
* Fixed a bug that caused regex_match or regex_search with the
|
||||
match_continuous flag being set to fail when the entry point
|
||||
selector introduced in version 3.000 was used internally.
|
||||
|
||||
20211025; version 3.001:
|
||||
* Removed the code for splitting counter as it seemed to be no effect
|
||||
or to make performance a bit worse.
|
||||
* Fixed potential bugs.
|
||||
* Minor improvements.
|
||||
|
||||
20211023; version 3.000:
|
||||
* Updated srell_ucfdata2.hpp and srell_updata.hpp to support Unicode
|
||||
14.0.0.
|
||||
* Updated unicode/updataout.cpp to support Unicode 14. (Support in
|
||||
advance new script names that are expected to be available in RegExp
|
||||
of ECMAScript 2022).
|
||||
* Changed the type used to store a Unicode value when char32_t is not
|
||||
available, from an "unsigned integer type with width of at least 21
|
||||
bits" to a "one of at least 32 bits".
|
||||
* Changed the type used to store a repetition count or character class
|
||||
number when char32_t is not available, from "unsigned int" to
|
||||
"unsigned integer type of at least 32-bit width".
|
||||
* Added overflow check in the function that translates digits into a
|
||||
numeric value. For example, while up to the previous version
|
||||
/a{0,4294967297}/ was treated as /a{0,1}/ because of overflow when
|
||||
the unsigned int type is 32-bit width, SRELL now throws error_brace
|
||||
in cases like this.
|
||||
* Fixed a bug that caused /[^;]*^;?/ not to match the beginning of an
|
||||
input string when the multiline flag is not set.
|
||||
* Implemented a very simple and limited entry point selector.
|
||||
|
||||
20211004; version 2.930:
|
||||
* Added new typedefs whose prefix is u1632w- and support UTF-16 or
|
||||
UTF-32 depending on the value of WCHAR_MAX. (When 0xFFFF <=
|
||||
WCHAR_MAX < 0x10FFFF, u1632w- types are aliases of u16w- types.
|
||||
When 0x10FFFF <= WCHAR_MAX, u1632w- types are aliases of u32w-
|
||||
types).
|
||||
* Reduced the amount of memory used for Eytzinger layout search.
|
||||
* Various improvements. (Some of them are based on suggestions to NIRE
|
||||
by Marko Njezic).
|
||||
|
||||
20210624; version 2.920:
|
||||
* Added a new optimisation for the quantifier '?' (I.e., {0,1}).
|
||||
* Changed the version number of the ECMAScript specification
|
||||
referenced in misc/sample01.cpp to 2021.
|
||||
|
||||
20210429; version 2.912:
|
||||
* Fixed another bug in the optimisation introduced in version 2.900,
|
||||
which caused /aa|a|aa/ not to match "a" (Thanks to Jan Schrötter for
|
||||
the report).
|
||||
Incidentally, this optimisation can be disabled by defining
|
||||
SRELLDBG_NO_BRANCH_OPT2 prior to including srell.hpp.
|
||||
|
||||
20210424; version 2.911:
|
||||
* Fixed a bug in the optimisation introduced in version 2.900, which
|
||||
caused /abc|ab|ac/ not to match "ac". (Thanks for the bug report [As
|
||||
my email to the reporter was rejected by the email server and
|
||||
returned, it is unclear whether mentioning the name here is okay
|
||||
with the reporter. So, I refrain]).
|
||||
|
||||
20210407; version 2.910:
|
||||
* Fixed a potential memory leak in move assignment operators used by
|
||||
the pattern compiler since 2.900. (Thanks to Michal Švec for the
|
||||
report).
|
||||
|
||||
20210214; version 2.901:
|
||||
* Removed redundant template specialisations.
|
||||
|
||||
20210214; version 2.900:
|
||||
* Added a new optimisation for the alternative expression that consist
|
||||
of string literals, such as /abc|abd|acde/.
|
||||
* Fixed the problem that brought u(8|16)[cs]regex_(token_)?iterator
|
||||
(i.e., regex (token) iterators specialised for char8_t or char16_t)
|
||||
to a compile error.
|
||||
* Minor improvements.
|
||||
|
||||
20210131; version 2.810:
|
||||
* Improved internal UTF-8 iterators.
|
||||
|
||||
20200724; version 2.800:
|
||||
* Introduced the Eytzinger layout for binary search in the character
|
||||
class.
|
||||
* Reimplemented linear search for small character classes.
|
||||
* Modified handling of the property data used for parsing the name for
|
||||
a named capturing group. Now they are loaded only when needed
|
||||
instead of being loaded into an instance of basic_regex always.
|
||||
|
||||
20200714; version 2.730:
|
||||
* Added code to prevent redundant save and restore operations when
|
||||
nested capturing round brackets are processed.
|
||||
* Improved regex_iterator.
|
||||
|
||||
20200703; version 2.720:
|
||||
* Improved case-insensitive (icase) search using the
|
||||
Boyer-Moore-Horspool algorithm for UTF-8 string that includes
|
||||
non-ASCII characters or UTF-16 string that includes non-BMP
|
||||
characters.
|
||||
* Fixed a bug that caused regex_iterator->prefix().first to point to
|
||||
the beginning of the subject string instead of the end of the
|
||||
previous match (regression introduced in version 2.650, when
|
||||
three-iterators overloads were added to regex_search()).
|
||||
* In accordance with the fix above, when a three-iterators version of
|
||||
regex_search() is called, now match_results.position() returns a
|
||||
distance from the position passed to as the lookbehind limit (3rd
|
||||
param of regex_search) and match_results.prefix().first points to
|
||||
the position passed to as the beginning of the subject string (1st
|
||||
param of regex_search).
|
||||
* Fixed a bug that could cause a valid UTF-8 sequence being adjacent
|
||||
to an invalid UTF-8 sequence to be skipped when the BMH algorithm
|
||||
was used (regression introduced in version 2.630, when UTF-8
|
||||
handling was modified).
|
||||
|
||||
20200701; version 2.710:
|
||||
* Minor modifications to Boyer-Moore-Horspool search.
|
||||
|
||||
20200630; version 2.700:
|
||||
* Optimisation adjustments.
|
||||
|
||||
20200620: version 2.651:
|
||||
* Move the group name validity check to after parsing the \u escape.
|
||||
* Updated misc/sample01.cpp to version 1.103. Changed the version
|
||||
number of the ECMAScript specification referenced by to 2020 (ES11).
|
||||
|
||||
20200618: version 2.650:
|
||||
* To element access functions in match_results, added overload
|
||||
functions for specifying the group name by a pointer.
|
||||
* When a three-iterators version of regex_search() is used, SRELL now
|
||||
sets match_results::prefix::first to the position passed to as the
|
||||
lookbehind limit (third param) instead of the position passed to as
|
||||
the beginning of the subject (first param).
|
||||
* Removed some operations that seem to be redundant.
|
||||
|
||||
20200601: version 2.643:
|
||||
* Added "inline" to operators in syntax_option_type and
|
||||
match_flag_type types, based on a report that it is needed not to
|
||||
cause the multiple definition error.
|
||||
* Minor improvements.
|
||||
|
||||
20200530: version 2.642:
|
||||
* Reduced the size of memory allocated by the basic_regex instance.
|
||||
|
||||
20200528: version 2.641:
|
||||
* The fix in 2.640 was incomplete. Fixed the optimisation bug 1 again.
|
||||
* Optimisation adjustments.
|
||||
|
||||
20200516: version 2.640:
|
||||
* Fixed an optimisation bug 1: It was possible for regex_match to pass
|
||||
the end of a subject string under certain conditions.
|
||||
* Fixed an optimisation bug 2: ^ and $ were not given a chance to
|
||||
match an appropriate position in some cases when the multiline flag
|
||||
is set to true.
|
||||
* Updated srell_ucfdata2.hpp and srell_updata.hpp.
|
||||
|
||||
20200509: version 2.630:
|
||||
* SRELL's pattern compiler no longer permits invalid UTF-8 sequences
|
||||
in regular expressions. It throws regex_utf8. (Invalid UTF-8
|
||||
sequences in the subject string are not treated as an error.)
|
||||
* Fixed BMH search functions not to include extra (invalid) UTF-8
|
||||
trailing bytes following the real matched substring, in a returned
|
||||
result.
|
||||
* Fixed minor issues: 1) basic_regex.flags() did not return the
|
||||
correct value in some cases, 2) match_results.format() did not
|
||||
replace $<NAME> with an empty string when any capturing group whose
|
||||
name is NAME did not exist.
|
||||
|
||||
20200502: version 2.620:
|
||||
* Removed methods used for match_continuous and regex_match in the
|
||||
class for the Boyer-Moore-Horspool algorithm. Now SRELL always uses
|
||||
the automaton like earlier versions when they are processed.
|
||||
* Some clean-ups.
|
||||
|
||||
20200428: version 2.611:
|
||||
* Fixed a bug that caused /\d*/ not to match the head of "abc" but to
|
||||
match the end of it. (regression introduced in version 2.210.)
|
||||
|
||||
20200426: version 2.610:
|
||||
* Fixed a bug that caused case-insensitive (icase) BMH search to skip
|
||||
a matched sequence at the beginning of the entire text, when 1)
|
||||
search is done against UTF-8 or UTF-16 text, and 2) the searched
|
||||
pattern ends with a character that consists of multiple code units
|
||||
in that encoding.
|
||||
* Now SRELL parses a capturing group name according to the ECMA
|
||||
specification and strictly checks its validity. Group names like
|
||||
/(?<,>...)/ cause regex_error.
|
||||
|
||||
20200418: version 2.600:
|
||||
* To pass to regex_search() directly the limit of a sequence until
|
||||
where the automaton can lookbehind, added three-iterators versions
|
||||
of regex_search().
|
||||
* [Breaking Change] Removed the match_lblim_avail flag from
|
||||
match_flag_type and the lookbehind_limit member from match_results
|
||||
which were added in version 2.300.
|
||||
* Updated srell_ucfdata2.hpp and srell_updata.hpp to support Unicode
|
||||
13.0.0.
|
||||
* Updated unicode/updataout.cpp to support Unicode 13. (Support in
|
||||
advance new script names that will be available in RegExp of
|
||||
ECMAScript 2020).
|
||||
|
||||
20191118: version 2.500:
|
||||
* Modified basic_regex to hold precomputed tables for icase matching,
|
||||
instead of creating them from case folding data when its instance is
|
||||
first created.
|
||||
* In accordance with the change above, srell_ucfdata.hpp and
|
||||
ucfdataout.cpp that outputs the former were replaced with
|
||||
srell_ucfdata2.hpp that holds precomputed tables and ucfdataout2.cpp
|
||||
that outputs the former.
|
||||
* Changed the method of character class matching from linear search to
|
||||
binary search.
|
||||
* Changed the timing of optimisation of a character class from "when a
|
||||
closing bracket ']' is found" to "every time a character or
|
||||
character range is pushed to its character class array".
|
||||
* Removed all asserts.
|
||||
* Modified the pattern compiler to interpret sequential \uHHHH escapes
|
||||
as a Unicode code point value if they represent a valid surrogate
|
||||
pair. (By this change, incompatibilities with the ECMAScript
|
||||
specification disappeared.)
|
||||
* Fixed the position of an endif directive that caused a compiler
|
||||
error when -DSRELL_NO_NAMEDCAPTURE is specified.
|
||||
* Updated updataout.cpp to version 1.101.
|
||||
* Added a standalone version of SRELL in the single-header directory.
|
||||
|
||||
20190914: version 2.401:
|
||||
* Reduced the size of basic_regex. (It was bloated by my carelessness
|
||||
when support for Unicode property escapes was added).
|
||||
* Improved basic_regex::swap().
|
||||
|
||||
20190907: version 2.400:
|
||||
* Improved the performance of character class matching.
|
||||
* Modified the pattern compiler to interpret the \u escape sequence in
|
||||
the group name in accordance with the ECMAScript specification.
|
||||
* Updated ucfdataout.cpp to version 1.200. A new member has been added
|
||||
to the unicode_casefolding class in srell_ucfdata.hpp that
|
||||
ucfdataout.cpp generates.
|
||||
Because SRELL 2.400 and later need this added member, they cannot be
|
||||
used with srell_ucfdata.hpp output by ucfdataout.cpp version 1.101
|
||||
or earlier. (No problem in using an older version of SRELL with a
|
||||
newer version of srell_ucfdata.hpp).
|
||||
* Some clean-ups and improvements.
|
||||
|
||||
20190902: version 2.304:
|
||||
* Fixed regex_iterator that had been broken by the code clean-up in
|
||||
version 2.303.
|
||||
|
||||
20190810: version 2.303:
|
||||
* Refixed the problem that was fixed in version 2.302 as the fix was
|
||||
incomplete.
|
||||
* Cleaned up code.
|
||||
|
||||
20190809: version 2.302:
|
||||
* Bug fix: When (?...) has a quantifier, strings captured by round
|
||||
brackets inside it were not cleared in each repetition but carried
|
||||
over to the next loop. For example,
|
||||
/(?:(ab)|(cd))+/.exec("abcd") returned ["abcd", "ab", "cd"], instead
|
||||
of ["abcd", undefined, "cd"]. (The latter is correct).
|
||||
* Updated misc/sample01.cpp to version 1.102. Rewrote the chapter
|
||||
numbers in accordance with ECMAScript 2019 (ES10).
|
||||
|
||||
20190724: version 2.301:
|
||||
* In accordance with the ECMAScript spec, restricted the characters
|
||||
which can be escaped by '\', to the following fifteen characters:
|
||||
^$\.*+?()[]{}|/
|
||||
Only in the character class, i.e., inside [], '-' also becomes a
|
||||
member of the group.
|
||||
|
||||
20190717: version 2.300:
|
||||
* Added a feature for specifying the limit until where the automaton
|
||||
can lookbehind, separated from the beginning of a target sequence.
|
||||
(Addition of the match_lblim_avail flag to match_flag_type and the
|
||||
lookbehind_limit member to match_results).
|
||||
And, lookbehind_limit of match_results being private and used
|
||||
internally in regex_iterator is also set in its constructor.
|
||||
* Removed order restriction of capturing parentheses and
|
||||
backreferences, in accordance with the ECMAScript spec. Now /\1(.)/,
|
||||
/(?<=(.)\1)/, and /\k<a>(?<a>.)/ are all okay.
|
||||
* Updated misc/sample01.cpp to version 1.101. Added one compliance
|
||||
test from misc.js.
|
||||
|
||||
20190714: version 2.230:
|
||||
* Improved the performance of searching when regular expressions begin
|
||||
with a character or character class followed by a '*' or '+'. (E.g.,
|
||||
/[A-Za-z]+ing/).
|
||||
|
||||
20190707: version 2.221:
|
||||
* Changed the feature test macro used for checking availability of
|
||||
std::u8string, from __cpp_char8_t to __cpp_lib_char8_t.
|
||||
* When icase specified, if all characters in a character class become
|
||||
the same character as a result of case-folding, the pattern compiler
|
||||
has been changed to convert the character class to the character
|
||||
literal (e.g., /r[Ss\u017F]t/i -> /rst/i).
|
||||
* Fixed a minor issue.
|
||||
|
||||
20190617: version 2.220:
|
||||
* Changed the internal representation of repetition in the case that
|
||||
it becomes more compact by not using the counter.
|
||||
* Fixed an optimisation bug that caused searching for /a{1,2}?b/
|
||||
against "aab" to return "ab" instead of "aab". (Condition: a
|
||||
character or character class with a non-greedy quantifier is
|
||||
followed by its exclusive character or character class).
|
||||
|
||||
20190613: version 2.210:
|
||||
* Improved a method of matching for expressions like /ab|cd|ef/ (where
|
||||
string literals separaterd by '|' begin with a character exclusive
|
||||
to each other).
|
||||
|
||||
20190603: version 2.202:
|
||||
* Fixed a bug that caused regex_match to behave like regex_search in
|
||||
the situation where the BMH algorithm is used.
|
||||
|
||||
20190531: version 2.200:
|
||||
* For searching with a ordinary (non-regex) string, added an
|
||||
implementation based on the Boyer-Moore-Horspool algorithm.
|
||||
* Improved UTF-8 iterators.
|
||||
* Fixed behaviours of \b and \B when icase specified, to match /.\B./i
|
||||
against "s\u017F".
|
||||
* Fixed minor issues.
|
||||
|
||||
20190508: version 2.100:
|
||||
* Fixed a bug that caused failure of capturing when 1) a pair of
|
||||
capturing brackets exists in a lookbehind assertion, and 2) variable
|
||||
length expressions exist in both the left side of and the inside of
|
||||
the pair of brackets. E.g., given "1053" =~ /(?<=(\d+)(\d+))$/, no
|
||||
appropriate string was set for $2.
|
||||
* Updated srell_ucfdata.hpp and srell_updata.hpp to support Unicode
|
||||
12.1.0.
|
||||
* Updated unicode/updataout.cpp to support Unicode 12. (Support in
|
||||
advance a new binary property and new script names that will be
|
||||
available in RegExp of ECMAScript 2019 and new script names that are
|
||||
anticipated to be available in RegExp of ECMAScript 2020).
|
||||
* Changed the newline character in srell.hpp from CR+LF to LF.
|
||||
* Modified unicode/*.cpp to output LF as a newline instead of CR+LF.
|
||||
* Updated misc/sample01.cpp to version 1.100:
|
||||
1. Rewrote the chapter numbers in subtitles of compliance tests, in
|
||||
accordance with ECMAScript 2018 Language Specification (ES9).
|
||||
(The old chapter numbers were based on ECMAScript specifications
|
||||
up to version 5.1).
|
||||
2. Added one compliance test from ECMAScript 2018 Language
|
||||
Specification 21.2.2.3, NOTE.
|
||||
* Modified the macros for detecting C++11 features.
|
||||
* Changed the method of the character class.
|
||||
* For all the constructors and assign functions of basic_regex to have
|
||||
a default argument for flag_type, reimplemented syntax_option_type
|
||||
and match_flag_type (missed changes between TR1 -> C++11).
|
||||
* Experimental support for the char8_t type. If a compiler supports
|
||||
char8_t (detected by the __cpp_char8_t macro), classes whose names
|
||||
have the "u8-" prefix accept a sequence of char8_t and handle it as
|
||||
a UTF-8 string. If char8_t is not supported, the classes handle a
|
||||
sequence of char as a UTF-8 string, as before.
|
||||
* As classes that always handle a sequence of char as a UTF-8 string,
|
||||
new classes whose names have the "u8c-" prefix were added. They
|
||||
correspond to the classes having the "u8-" prefix in their names up
|
||||
to version 2.002:
|
||||
* u8cregex; u8ccmatch, u8csmatch; u8ccsub_match, u8cssub_match;
|
||||
u8ccregex_iterator, u8csregex_iterator; u8ccregex_token_iterator,
|
||||
u8csregex_token_iterator.
|
||||
|
||||
20180717: version 2.002:
|
||||
* Changed the maximum number of hexdigits in \u{h...} from six to
|
||||
'unlimited' in accordance with the ECMAScript specification. ("one
|
||||
to six hexadecimal digits" of the old implementation was based on
|
||||
the proposal document).
|
||||
* Updated updataout.cpp to version 1.001. Encounting unknown
|
||||
(newly-encoded) script names is no longer treated as an error.
|
||||
* Updated srell_ucfdata.hpp and srell_updata.hpp to support Unicode
|
||||
11.0.0.
|
||||
|
||||
20180204: version 2.001:
|
||||
* When icase is specified, [\W] (a character class containing \W) no
|
||||
longer matches any of [KkSs\u017F\u212A] (ecma262 issue #512).
|
||||
|
||||
20180127: version 2.000:
|
||||
* Added the following features that are to be included into RegExp of
|
||||
ECMAScript 2018:
|
||||
* New syntax option flag for '.' to match every code point, dotall,
|
||||
was added to srell::regex_constants as a value of
|
||||
syntax_option_type and to srell::basic_regex as a value of
|
||||
flag_type.
|
||||
* New expressions to support the Unicode property, \p{...} and
|
||||
\P{...}.
|
||||
* Named capture groups (?<NAME>...) and the new expression for
|
||||
backreference to a named capture group, \k<NAME>.
|
||||
* The behaviors of lookbehind assertions changed. Now both (?<=...)
|
||||
and (?<!...) support variable-length lookbehind.
|
||||
|
||||
20180125; version 1.401:
|
||||
* Limited the maximum of numbers that are recognised as backreference
|
||||
in match_results.format() up to 99, in accordance with the
|
||||
ECMAScript specification. (I.e., restricted to $1..$9 and $01..$99).
|
||||
* Removed an unused macro and its related code.
|
||||
|
||||
20180101; version 1.400:
|
||||
* Changed the behaviour of the pattern compiler so that an empty
|
||||
non-capturing group can have a quantifier, for example, /(?:)*/. It
|
||||
is a meaningless expression, but changed just for compatibility with
|
||||
RegExp of ECMAScript.
|
||||
* Fixed a hang bug: This occured when 1) a non-capturing group has a
|
||||
quantifier, 2) and the length of the group itself can be zero-width,
|
||||
3) and a backreference that can be zero-width is included in the
|
||||
group somewhere other than the last, such as /(.*)(?:\1.*)*/.
|
||||
|
||||
20171216; version 1.300:
|
||||
* Fixed an important bug: /^(;[^;]*)*$/ did not match ";;;;" because
|
||||
of a bug in optimisation. This problem occured when a sequence of
|
||||
regular expressions ended like /(A...B*)*$/ where a character or
|
||||
character set that A represents and the one that B represents are
|
||||
exclusive to each other.
|
||||
|
||||
20170621; version 1.200:
|
||||
* Updated srell_ucfdata.hpp to support Unicode 10.0.0.
|
||||
* Improved u8regex_traits to handle corrupt UTF-8 sequences more
|
||||
safely.
|
||||
|
||||
20150618; version 1.141:
|
||||
Updated srell_ucfdata.hpp to support Unicode 8.0.0.
|
||||
|
||||
20150517; version 1.140:
|
||||
* Modified the method for regex_match() to determine whether a
|
||||
sequence of regular expressions is matched against a sequence of
|
||||
characters. (Issue raised at #2273 in C++ Standard Library Issues
|
||||
List).
|
||||
* Restricted the accepted range of X in the expression "\cX" to
|
||||
[A-Za-z] in accordance with the ECMAScript specification.
|
||||
* Fixed the problem that caused parens in a lookaround assertion not
|
||||
to capture a sequence correctly in some circumstances because the
|
||||
bug fix done in version 1.111 was imperfect.
|
||||
|
||||
20150503; version 1.130:
|
||||
* Improved case-folding functions.
|
||||
* Updated unicode/ucfdataout.cpp to version 1.100.
|
||||
* Fixed a typo in #if directives for u(16|32)[cs]match.
|
||||
|
||||
20150425; version 1.120:
|
||||
* Fixed the bug that caused characters in U+010000-U+10FFFF in UTF-8
|
||||
(i.e., four octet length characters) not to have been recognised.
|
||||
* Updated misc/sample01.cpp to version 1.010.
|
||||
|
||||
20150402; version 1.111:
|
||||
* Fixed the problem that caused $2 of "aaa" =~ /((.*)*)/ to be empty
|
||||
instead of "aaa" because of a bug in optimisation.
|
||||
|
||||
20141101; version 1.110:
|
||||
* Several fixes based on a bug report:
|
||||
1. Added "this->" to compile() in basic_regex::assign().
|
||||
2. Implemented operator=() functions explicitly instead of using
|
||||
default ones generated automatically.
|
||||
* unicode/ucfdataout.cpp revised and updated to version 1.001.
|
||||
|
||||
20140622; version 1.101:
|
||||
Updated srell_ucfdata.hpp to support Unicode 7.0.0.
|
||||
|
||||
20121118; version 1.100:
|
||||
The first released version.
|
||||
|
||||
421
lib/srell3_009/history_ja.txt
Normal file
421
lib/srell3_009/history_ja.txt
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
20220511; version 3.009:
|
||||
・最適化バグにより /abcd|ab/ が "abc" にマッチしなかった問題を修正。
|
||||
|
||||
20220504; version 3.008:
|
||||
・icase指定時の[^\P{...}]の振る舞いが、TC39で提案中のv-modeのそれに近
|
||||
いものになっていた問題を修正。
|
||||
|
||||
20220429; version 3.007:
|
||||
・カウンタの仕組みをさらに変更。
|
||||
|
||||
20220428; version 3.006:
|
||||
・繰り返し処理用のカウンタを調整。
|
||||
・小さな文字クラス用の線形探索を再削除。
|
||||
|
||||
20220424; version 3.005:
|
||||
・multiline指定時に /(?<=$.*)/ が "a" の終わりにマッチしなかった問題を
|
||||
修正。
|
||||
・TC39で提案中の\A, \z, (?m:)の準備。
|
||||
|
||||
20220420; version 3.004:
|
||||
・'*' または '+' 付きの文字クラスが後続する文字または文字クラスと排他
|
||||
的になっていない表現用の最適化処理を追加。例:/[A-Za-z]+ing/,
|
||||
/".*"/ など。
|
||||
|
||||
20220416; version 3.003:
|
||||
・2つの最適化函数を1つに統合。
|
||||
・先読み (lookahead)・戻り読み (lookbehind) 用のコード量を削減。
|
||||
|
||||
20220416; version 3.002:
|
||||
・3.000で導入した簡易エントリーポイント選択の使用時に、regex_matchや
|
||||
match_continuousフラグが指定されたregex_searchが機能しない場合があっ
|
||||
た問題を修正。
|
||||
|
||||
20211025; version 3.001:
|
||||
・カウンタ分割を廃止。効果がないかむしろ若干速度が低下しているように見
|
||||
えるため。
|
||||
・潜在的なバグを修正。
|
||||
・その他細かな改良など。
|
||||
|
||||
20211023; version 3.000:
|
||||
・srell_ucfdata2.hppとsrell_updata.hppとをUnicode 14.0.0対応に更新。
|
||||
・unicode/updataout.cppをUnicode 14対応に更新(ECMAScript 2022で対応さ
|
||||
れる見込みのスクリプト名の先行対応)。
|
||||
・char32_t未対応のコンパイラでUnicode値を保持するため内部で使用する型
|
||||
を「21ビット以上あるunsigned整数型」から「32ビット以上あるunsigned整
|
||||
数型」に変更。
|
||||
・char32_t未対応のコンパイラで繰り返し回数や文字クラス番号を保持するの
|
||||
に使う型を「unsigned int」から「32ビット以上あるunsigned整数型」に変
|
||||
更。
|
||||
・数値用パーザにoverflowチェックを追加。例:unsigned int型が32ビットの
|
||||
幅の時、前の版まで /a{0,4294967297}/ は /a{0,1}/ 相当になってしまっ
|
||||
ていましたが、前記のチェックを入れたことによりこのような場合には
|
||||
error_braceがthrowされるようになっています。
|
||||
・非multilineモード時に /[^;]*^;?/ が入力文字列の先頭にマッチしなかっ
|
||||
たバグを修正。
|
||||
・ごく簡易なエントリーポイント選択を実装。
|
||||
|
||||
20211004; version 2.930:
|
||||
・WCHAR_MAXの値に基づいてUTF-16/UTF-32対応が切り替わるu1632w-型を新規
|
||||
に追加(WCHAR_MAXが0xFFFF以上・0x10FFFF未満ならu1632w-型はu16w-型の
|
||||
別名となり、WCHAR_MAXが0x10FFFF以上ならu1632w-型はu32w-型の別名とな
|
||||
ります)。
|
||||
・Eytzinger layout検索時に使われるメモリ使用量を削減。
|
||||
・その他細かな改良など(いくつかはNIREに対するMarko Njezic氏の改善案に
|
||||
基づきます)。
|
||||
|
||||
20210624; version 2.920:
|
||||
・?({0,1}相当)用の最適化処理を追加。
|
||||
・misc/sample01.cpp内で参照しているECMAScript仕様書の版を2021に変更。
|
||||
|
||||
20210429; version 2.912:
|
||||
・2.900で導入した最適化処理のバグにより /aa|a|aa/ が "a" にマッチしな
|
||||
くなっていた問題を修正(報告してくださったJan Schrötter氏に感謝しま
|
||||
す)。
|
||||
ちなみにこの最適化処理は、srell.hppをincludeする前に
|
||||
SRELLDBG_NO_BRANCH_OPT2マクロを定義しておくと無効化できます。
|
||||
|
||||
20210424; version 2.911:
|
||||
・2.900で導入した最適化処理内の不用意な行削除が原因で、/abc|ab|ac/ が
|
||||
"ac" に対してマッチしなくなっていた問題を修正(バグ報告に感謝します)。
|
||||
|
||||
20210407; version 2.910:
|
||||
・2.900以降、パターンコンパイラ内部でmove代入演算子が使われる時にメモ
|
||||
リリークしていた問題を修正(報告してくださったMichal Švec氏に感謝し
|
||||
ます)。
|
||||
|
||||
20210214; version 2.901:
|
||||
・不要なテンプレートの特殊化を削除。
|
||||
|
||||
20210214; version 2.900:
|
||||
・文字列のみからなる選択(例:/abc|abd|acde/)用の最適化処理を新規に追
|
||||
加。
|
||||
・u(8|16)[cs]regex_(token_)?iteratorがコンパイルエラーとなり使用できな
|
||||
かった問題を修正。
|
||||
・その他細かな改良など。
|
||||
|
||||
20210131; version 2.810:
|
||||
・UTF-8用内部iteratorの改良。
|
||||
|
||||
20200724; version 2.800:
|
||||
・文字クラスの二分探索にEytzinger layoutを導入。
|
||||
・小さな文字クラス用に線形探索を再実装。
|
||||
・名前付き括弧の名前部分をパーズするためのプロパティーデータの扱いを変
|
||||
更。basic_regex型インスタンス内に読み込むのを止めて、必要な時のみ読
|
||||
み込むように。
|
||||
|
||||
20200714; version 2.730:
|
||||
・入れ子になった捕獲括弧で冗長な退避・復元処理をせぬように変更。
|
||||
・regex_iteratorの改良。
|
||||
|
||||
20200703; version 2.720:
|
||||
・非ASCII文字を含むUTF-8文字列または非BMPの文字を含むUTF-16文字列を、
|
||||
Boyer-Moore-Horspoolアルゴリズムを用いて、大文字小文字の区別無しで
|
||||
(icase/case-insensitiveで) 検索する場合の処理の改良。
|
||||
・Version 2.650での変更により、regex_iterator->prefix().firstが前回マ
|
||||
ッチした位置の終端ではなく文字列全体の最初を指すようにになってしまっ
|
||||
ていたのを修正。
|
||||
・上記修正に合わせて3イテレータ版のregex_search()が呼ばれる場合、
|
||||
match_results.position()は戻り読みの逆行限界として渡された位置
|
||||
(regex_searchの第3引数)を起点とした位置を返し、
|
||||
match_results.prefix().firstは検索開始位置(同第1引数)を指すように
|
||||
変更。
|
||||
・BMH検索時に、不正なUTF-8シークウェンスの前後にある有効なシークウェン
|
||||
スが読み飛ばされてしまう問題を修正(2.630でUTF-8の処理方法を変えた時
|
||||
に混入したバグ)。
|
||||
|
||||
20200701; version 2.710:
|
||||
・Boyer-Moore-Horspool検索の調整。
|
||||
|
||||
20200630; version 2.700:
|
||||
・最適化処理の調整。
|
||||
|
||||
20200620: version 2.651:
|
||||
・グループ名のチェックを行う位置を\uエスケープの解釈後に移動。
|
||||
・misc/sample01.cppをversion 1.103に更新。参照しているECMAScript仕様書
|
||||
の版を2020(ES11)に変更。
|
||||
|
||||
20200618: version 2.650:
|
||||
・名前付き括弧に捕獲された文字列へのアクセス用函数に、グループ名をポイ
|
||||
ンタで指定するoverloadをmatch_resultsに追加。
|
||||
・3イテレータ版のregex_search()使用時には、検索の開始位置ではなく戻り
|
||||
読み (lookbehind) の逆行限界として渡された位置のほうを
|
||||
match_results::prefix::firstにセットするよう変更。
|
||||
・不要と思われる処理をいくつか削除。
|
||||
|
||||
20200601: version 2.643:
|
||||
・syntax_option_typeおよびmatch_flag_typeのoperator函数にinline指定を
|
||||
追加(これがないとリンク時に多重定義エラーが出ることがあるとのご指摘
|
||||
がありました)。
|
||||
・その他細かな改良など。
|
||||
|
||||
20200530: version 2.642:
|
||||
・basic_regex型インスタンスが確保するメモリのサイズを削減。
|
||||
|
||||
20200528: version 2.641:
|
||||
・2.640での修正1が不完全であったため再修正。
|
||||
・最適化処理の調整。
|
||||
|
||||
20200516: version 2.640:
|
||||
・最適化バグの修正1: regex_matchが入力文字列の終端を通り過ぎてしまうこ
|
||||
とがあった問題を修正。
|
||||
・最適化バグの修正2: multilineフラグ指定時に ^ や $ が適切な位置でのマ
|
||||
ッチングをさせてもらえなくなってしまっていた問題を修正。
|
||||
・srell_ucfdata2.hppとsrell_updata.hppとを更新。
|
||||
|
||||
20200509: version 2.630:
|
||||
・正規表現中に不正なUTF-8のシークウェンスがあった場合、パターンコンパ
|
||||
イラがregex_utf8をthrowするように仕様変更(検索対象文字列中に不正な
|
||||
UTF-8の並びがあってもエラー扱いされません)。
|
||||
・UTF-8でBMH検索が行われる際、マッチした箇所の直後に余分な後続
|
||||
(trailing) バイトが続いていた場合にその部分もマッチング結果に含めて
|
||||
しまう問題を修正。
|
||||
・basic_regex.flags() が正しい値を返さないことがあったのを修正。
|
||||
・正規表現中で実際には使われていないグループ名 (NAME) を
|
||||
match_results.format()に渡す書式文字列の中で$<NAME>のようにして指定
|
||||
すると、その部分が空文字に置換されずそのまま残ってしまう問題を修正。
|
||||
|
||||
20200502: version 2.620:
|
||||
・Boyer-Moore-Horspoolアルゴリズム用クラスからmatch_continuous指定時用
|
||||
およびregex_match用の函数を削除。これらの処理時は以前のようにオート
|
||||
マトンを使うように変更。
|
||||
・その他クリーンナップ。
|
||||
|
||||
20200428: version 2.611:
|
||||
・/\d*/ が "abc" の冒頭にマッチせず末尾にマッチする問題を修正(Version
|
||||
2.210で混入したバグ)。
|
||||
|
||||
20200426: version 2.610:
|
||||
・Case-insensitive (icase) なBMH検索が行われる際、探している文字列が検
|
||||
索対象テキスト全体の先頭にあった場合に読み飛ばされてしまうことがある
|
||||
バグを修正(UTF-8またはUTF-16で、検索文字列の末尾が複数のコードユニ
|
||||
ットからなる文字である場合に発生)。
|
||||
・キャプチャグループ名のパーズをECMAScriptの仕様書通りきっちり行うよう
|
||||
に変更。これにより、前の版までは受理されていた /(?<,>...)/ のような
|
||||
グループ名はregex_errorがthrowされるように。
|
||||
|
||||
20200418: version 2.600:
|
||||
・戻り読み (lookbehind) の逆行限界を直接regex_search()に渡せるように
|
||||
3イテレータ版のregex_search()を追加。
|
||||
・[非互換変更] 2.300で導入したmatch_flag_typeのmatch_lblim_availフラグ
|
||||
と、match_resultsのlookbehind_limitメンバとを廃止。
|
||||
・srell_ucfdata2.hppとsrell_updata.hppとをUnicode 13.0.0対応に更新。
|
||||
・unicode/updataout.cppをUnicode 13対応に更新(ECMAScript 2020で対応さ
|
||||
れる見込みのスクリプト名の先行対応)。
|
||||
|
||||
20191118: version 2.500:
|
||||
・初めてbasic_regex型インスタンスが作られた時にcase foldingデータから
|
||||
icaseマッチング用テーブルを展開するのに代えて、最初から計算済みテー
|
||||
ブルを保持しているように仕様変更。
|
||||
・上記変更に併せてsrell_ucfdata.hppおよびそれを出力するucfdataout.cpp
|
||||
はお役御免とし、代わりに展開済みicase用テーブルを保持する
|
||||
srell_ucfdata2.hppとそれを出力するucfdataout2.cppとを追加。
|
||||
・文字クラスの照合方法を線形探索から二分探索に変更。
|
||||
・文字クラスの最適化処理のタイミングを「']' が見つかった時にまとめて一
|
||||
括」から「文字または文字コードの範囲をpushするたびごと逐次」に変更。
|
||||
・assertをすべて削除。
|
||||
・連続する\uHHHHがサロゲートペアをなしている場合はUnicode値として解釈
|
||||
するように変更(これによりECMAScript仕様との相違はなくなりました)。
|
||||
・SRELL_NO_NAMEDCAPTUREマクロ使用時にコンパイルエラーが出ていたのを修
|
||||
正。
|
||||
・updataout.cppを1.101にヴァージョンアップ。
|
||||
・単体版のsrellを追加(single-headerディレクトリ内)。
|
||||
|
||||
20190914: version 2.401:
|
||||
・basic_regex型インスタンスのサイズを削減(Unicode property escapes対
|
||||
応時にうっかり膨張させてしまっていました)。
|
||||
・basic_regex::swap()の改良。
|
||||
|
||||
20190907: version 2.400:
|
||||
・文字クラスの照合速度を改善。
|
||||
・パターンコンパイル時にグループ名中の\uエスケープを解釈するように変更
|
||||
(ECMAScriptの仕様に準拠)。
|
||||
・ucfdataout.cppを1.200にヴァージョンアップ。このプログラムが出力する
|
||||
srell_ucfdata.hpp中のunicode_casefoldingクラスに、新たにメンバ変数が
|
||||
追加されました。
|
||||
SRELL 2.400以降はこの追加されたメンバ変数をコンパイル時に必要とする
|
||||
ため、ucfdataout.cpp 1.101以前によって出力されたsrell_ucfdata.hppを
|
||||
SRELL 2.400以降で使うことはできません(古いSRELLで新しい
|
||||
srell_ucfdata.hppを使うことは可)。
|
||||
・その他コードの整理や改良など。
|
||||
|
||||
20190902: version 2.304:
|
||||
・Version 2.303のコード整理で壊れてしまっていたregex_iteratorを修復。
|
||||
|
||||
20190810: version 2.303:
|
||||
・2.302の修正が不完全であったため再修正。
|
||||
・その他コードの整理。
|
||||
|
||||
20190809: version 2.302:
|
||||
・(?...) に繰り返し指定がついている時、内側の括弧によって捕獲された文
|
||||
字列がループごとにクリアされず持ち越されていたバグを修正。
|
||||
例:/(?:(ab)|(cd))+/.exec("abcd") → 1番括弧はundefinedになるはずが
|
||||
"ab"になってしまっていた。
|
||||
・misc/sample01.cppをversion 1.102に更新。テスト名中の章番号を
|
||||
ECMAScript 2019 (ES10) 準拠に変更
|
||||
|
||||
20190724: version 2.301:
|
||||
・ECMAScriptの仕様に準じて、\でエスケープ可能な文字の種類を次の15字に
|
||||
限定。^$\.*+?()[]{}|/
|
||||
文字クラス内([]内)ではこの15字に加えて '-' も対象に。
|
||||
|
||||
20190717: version 2.300:
|
||||
・検索対象範囲とは別に、戻り読み (lookbehind) の逆行限界を指定できる機
|
||||
能を追加(match_flag_typeへのmatch_lblim_availフラグの追加と
|
||||
match_resultsへのlookbehind_limitメンバの追加)。
|
||||
これに併せてregex_iteratorのコンストラクタ内でも、内部で使うprivate
|
||||
なmatch_results型インスタンスのlookbehind_limitメンバに値を設定する
|
||||
ように変更。
|
||||
・ECMAScriptの仕様に合わせて、後方参照が対応する捕獲括弧より先に出現し
|
||||
てもエラー扱いせぬように変更。/\1(.)/, /(?<=(.)\1)/, /\k<a>(?<a>.)/
|
||||
などすべてOKに。
|
||||
・misc/sample01.cppをversion 1.101に更新。misc.jsより準拠テストを1つ追
|
||||
加。
|
||||
|
||||
20190714: version 2.230:
|
||||
・正規表現が '*' か '+' かを伴う文字または文字クラスで始まる場合の検索
|
||||
速度を改善(例:/[A-Za-z]+ing/)。
|
||||
|
||||
20190707: version 2.221:
|
||||
・std::u8stringの利用可否は__cpp_char8_tではなく__cpp_lib_char8_tを用
|
||||
いて判断するように変更。
|
||||
・icase指定時にcase-folding処理をした結果、文字クラス内の文字がすべて
|
||||
同じ文字になった場合には、文字クラスを解消して文字リテラルとして処理
|
||||
するように変更。例:/r[Ss\u017F]t/i → /rst/i。
|
||||
・その他問題を修正。
|
||||
|
||||
20190617: version 2.220:
|
||||
・カウンタを使わぬほうが内部表現がコンパクトになる繰り返しはカウンタを
|
||||
使わぬように変更。
|
||||
・最適化バグにより、/a{1,2}?b/.exec("aab") が "aab" ではなく "ab" を返
|
||||
していたのを修正(発生条件:最短一致優先の回数指定が付いている文字ま
|
||||
たは文字クラスの後ろに、その文字集合と排他的な文字または文字クラスが
|
||||
続いている場合)。
|
||||
|
||||
20190613: version 2.210:
|
||||
・/ab|cd|ef/ のような表現('|' で区切られている文字列の先頭文字が互い
|
||||
に排他的な場合)の照合方法を改良。
|
||||
|
||||
20190603: version 2.202:
|
||||
・BMHアルゴリズムが使われる状況で、regex_matchがregex_search相当の処理
|
||||
をしてしまうバグを修正。
|
||||
|
||||
20190531: version 2.200:
|
||||
・通常の(正規表現ではない)テキスト検索用に、Boyer-Moore-Horspoolアル
|
||||
ゴリズムに基づく実装を追加。
|
||||
・UTF-8用iteratorの改良。
|
||||
・icase指定時の\b/\Bの挙動を修正。/.\B./i が "s\u017F" にマッチするよ
|
||||
うに。
|
||||
・その他問題を修正。
|
||||
|
||||
20190508: version 2.100:
|
||||
・Lookbehind中に文字列のキャプチャがあり、かつその中および左方に可変長
|
||||
の正規表現があった場合、文字列の捕獲に失敗することがあったのを修正。
|
||||
例:"1053" =~ /(?<=(\d+)(\d+))$/ で$2に適切な文字列がセットされず。
|
||||
・srell_ucfdata.hppとsrell_updata.hppとをUnicode 12.1.0対応に更新。
|
||||
・unicode/updataout.cppをUnicode 12対応に更新(ECMAScript 2020で対応さ
|
||||
れる見込みのスクリプト名の先行対応)。
|
||||
・srell.hpp中の改行コードをCR+LFからLFに変更。
|
||||
・unicode/*.cppが出力するファイルの改行コードをCR+LFからLFに変更。
|
||||
・misc/sample01.cppをversion 1.010に更新。
|
||||
1. テスト名中の章番号をECMAScript 2018 (ES9) 準拠に変更(前版までは
|
||||
ECMAScript 5.1までの章番号準拠でした)。
|
||||
2. ECMAScript 2018規格の2.2.2.3 NOTEから準拠テストを1つ追加。
|
||||
・C++11の機能の使用可否を判定するマクロを変更。
|
||||
・文字クラスの処理方法を変更。
|
||||
・basic_regexの全コンストラクタと全assign函数とでflag_typeのdefault引
|
||||
数を指定できるように、syntax_option_typeとmatch_flag_typeとを再実装
|
||||
(TR1→C++11間の変更の見落とし)。
|
||||
・char8_t型に試験対応。コンパイラがchar8_tに対応している場合
|
||||
(__cpp_char8_tマクロ定義の有無で判断)、"u8-"というprefixの付いた
|
||||
クラスは「char8_t型文字列を受け取り、それをUTF-8として扱う」ように。
|
||||
char8_tに未対応の場合は従来通り、char型文字列をUTF-8として処理。
|
||||
・常に「char型文字列をUTF-8として扱う」クラスとして新規に"u8c-"という
|
||||
prefixに付いたクラスを追加。2.002までの"u8-"付きクラス相当。
|
||||
・u8cregex; u8ccmatch, u8csmatch; u8ccsub_match, u8cssub_match;
|
||||
u8ccregex_iterator, u8csregex_iterator; u8ccregex_token_iterator,
|
||||
u8csregex_token_iterator.
|
||||
|
||||
20180717: version 2.002:
|
||||
・ECMAScriptの仕様に合わせて \u{h...} の h... 部分の最大桁数を6から無
|
||||
制限に変更(変更前の1~6桁というのは提案書に基づく実装でした)。
|
||||
・updataout.cppを1.001に更新。新規に追加されたスクリプト名をエラー扱い
|
||||
せぬように修整。
|
||||
・srell_ucfdata.hppとsrell_updata.hppとをUnicode 11.0.0対応に更新。
|
||||
|
||||
20180204: version 2.001:
|
||||
・icase指定時に、[\W](\Wを含む文字class)が [KkSs\u017F\u212A] のいず
|
||||
れにもマッチせぬよう変更(関連:ecma262 issue #512)。
|
||||
|
||||
20180127; version 2.000:
|
||||
・ECMAScript 2018のRegExpに追加されることになった次の機能を実装:
|
||||
・'.' があらゆるコードポイントにマッチするようにするための指定
|
||||
"dotall" フラグを、srell::regex_constants内の syntax_option_type
|
||||
および srell::basic_regex内の flag_type に追加。
|
||||
・Unicode property用の表現、\p{...} と \P{...} とを追加。
|
||||
・名前付きキャプチャ (?<NAME>...) と、名前付きキャプチャによって捕獲
|
||||
された文字列を後方参照するための正規表現、\k<NAME> とを追加。
|
||||
・戻り読み (lookbehind) の振る舞いを変更。(?<=...), (?<!...) とも可変
|
||||
幅の戻り読みに対応。
|
||||
|
||||
20180125; version 1.401:
|
||||
・ECMAScriptの仕様に合わせて、match_results.format()内で後方参照として
|
||||
認識される数値を99までに制限(即ち$1~$9および$01~$99のみ有効)。
|
||||
・長い間メンテナンスしていないマクロを削除。
|
||||
|
||||
20180101; version 1.400:
|
||||
・/(?:)*/ のように、空のnon-capturingグループにも量指定子を付けられる
|
||||
ように変更(ECMAScriptのRegExpとの互換性確保のための変更で、使い道は
|
||||
おそらくありません)。
|
||||
・次の3条件が揃った時に固まってしまったのを修正: 1) non-capturingグル
|
||||
ープに量指定子が付いていて、2) そのグループ自身が0幅になり得て、3)
|
||||
そのグループ内の最後以外の場所に、0幅になり得る後方参照が現れる時。
|
||||
たとえば /(.*)(?:\1.*)*/ のような表現。
|
||||
|
||||
20171216; version 1.300:
|
||||
・最適化処理のバグにより、/^(;[^;]*)*$/ が ";;;;" にマッチしなかった問
|
||||
題を修正。この問題の発生条件は次の通り:
|
||||
・/(A...B*)*$/ のような終わり方をしていて、かつAとBとが互いに排他的
|
||||
な文字または文字集合である場合。
|
||||
|
||||
20170621; version 1.200:
|
||||
・srell_ucfdata.hppをUnicode 10.0.0対応に。
|
||||
・不正なUTF-8 sequenceに対するu8regex_traitsの振る舞いを改善。
|
||||
|
||||
20150618; version 1.141:
|
||||
srell_ucfdata.hppをUnicode 8.0.0対応に。
|
||||
|
||||
20150517; version 1.140:
|
||||
・regex_match()がマッチの成否を判定する方法の変更。
|
||||
(C++ Standard Library Issues List #2273 への対応)
|
||||
・ECMAScriptの仕様に合わせて \cX の X の範囲を [A-Za-z] に制限。
|
||||
・look-around assertions中の丸括弧が、ある条件下で正しく文字列をキャプ
|
||||
チャせぬ場合があった問題を修正。Version 1.111での修正が不完全であっ
|
||||
たことによるもの。
|
||||
|
||||
20150503; version 1.130:
|
||||
・case-folding用函数の改善。
|
||||
・unicode/ucfdataout.cppをversion 1.100に。
|
||||
・u(16|32)[cs]match用の#if directives中にあったtypoを修正。
|
||||
|
||||
20150425; version 1.120:
|
||||
・UTF-8文字列においてU+010000-U+10FFFFの範囲の文字(4オクテット長の文
|
||||
字)が認識されぬバグを修正。
|
||||
・misc/sample01.cppをversion 1.010に。
|
||||
|
||||
20150402; version 1.111:
|
||||
・最適化処理のバグにより、"aaa" =~ /((.*)*)/ の $2 が "aaa" ではなく空
|
||||
になってしまう問題を修正。
|
||||
|
||||
20141101; version 1.110:
|
||||
・バグ報告による修正:
|
||||
1. basic_regex::assign() 内の compile() に "this->" を追加。
|
||||
2. operator=() 函数を明示的に実装。
|
||||
・unicode/ucfdataout.cppをversion 1.001 に。
|
||||
|
||||
20140622; version 1.101:
|
||||
srell_ucfdata.hppをUnicode 7.0.0対応に。
|
||||
|
||||
20121118; version 1.100:
|
||||
最初のリリース版。
|
||||
|
||||
32
lib/srell3_009/license.txt
Normal file
32
lib/srell3_009/license.txt
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*****************************************************************************
|
||||
**
|
||||
** SRELL (std::regex-like library) version 3.009
|
||||
**
|
||||
** Copyright (c) 2012-2022, Nozomu Katoo. All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
**
|
||||
** 1. Redistributions of source code must retain the above copyright notice,
|
||||
** this list of conditions and the following disclaimer.
|
||||
**
|
||||
** 2. Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in the
|
||||
** documentation and/or other materials provided with the distribution.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||
** IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**
|
||||
******************************************************************************
|
||||
**/
|
||||
|
||||
379
lib/srell3_009/misc/sample01.cpp
Normal file
379
lib/srell3_009/misc/sample01.cpp
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
//
|
||||
// A sample program for SRELL (tests and benchmarks).
|
||||
// 2021/06/24; version 1.104
|
||||
//
|
||||
// Macro Options:
|
||||
// -DSTD_REGEX: std::regex used.
|
||||
// -DBOOST_REGEX: boost::regex used.
|
||||
// -DBOOST_XPRESSIVE: boost::xpressive used.
|
||||
// unspecified or others: SRELL used.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(STD_REGEX)
|
||||
#include <regex>
|
||||
#define RE_PREFIX std
|
||||
#pragma message("std::regex selected.")
|
||||
#elif defined(BOOST_REGEX)
|
||||
#include <boost/regex.hpp>
|
||||
#define RE_PREFIX boost
|
||||
#pragma message("boost::regex selected.")
|
||||
#elif defined(BOOST_XPRESSIVE)
|
||||
#include <boost/xpressive/xpressive.hpp>
|
||||
#define RE_PREFIX boost::xpressive
|
||||
#pragma message("boost::xpressive selected.")
|
||||
#else
|
||||
#include "../srell.hpp"
|
||||
#define RE_PREFIX srell
|
||||
#pragma message("srell selected.")
|
||||
#endif
|
||||
|
||||
bool test(const std::string &str, const std::string &exp, const unsigned int max, const std::vector<std::string> *const expected = NULL)
|
||||
{
|
||||
#if !defined(BOOST_XPRESSIVE)
|
||||
RE_PREFIX::regex re;
|
||||
#else
|
||||
boost::xpressive::cregex re;
|
||||
#endif
|
||||
RE_PREFIX::cmatch mr;
|
||||
bool b = false;
|
||||
unsigned int num_of_failures = 0;
|
||||
|
||||
try
|
||||
{
|
||||
std::string matched;
|
||||
std::string msg;
|
||||
|
||||
#if !defined(BOOST_XPRESSIVE)
|
||||
re.assign(exp, RE_PREFIX::regex::ECMAScript);
|
||||
#else
|
||||
re = boost::xpressive::cregex::compile(exp, boost::xpressive::cregex::ECMAScript | boost::xpressive::cregex::not_dot_newline);
|
||||
#endif
|
||||
|
||||
const clock_t st = std::clock();
|
||||
|
||||
for (unsigned int i = 0; i < max; i++)
|
||||
#if !defined(BOOST_REGEX)
|
||||
b = RE_PREFIX::regex_search(str.c_str(), str.c_str() + str.size(), mr, re);
|
||||
#else
|
||||
b = RE_PREFIX::regex_search(str.c_str(), str.c_str() + str.size(), mr, re, boost::regex_constants::match_not_dot_newline);
|
||||
#endif
|
||||
|
||||
const clock_t ed = std::clock();
|
||||
|
||||
// std::fprintf(stdout, "\t\"%s\" =~ /%s/\n", str.c_str(), exp.c_str()); // Perl 5 style.
|
||||
std::fprintf(stdout, "\t/%s/.exec(\"%s\");\n", exp.c_str(), str.c_str()); // ECMAScript style.
|
||||
if (max > 1)
|
||||
std::fprintf(stdout, "\t%u times\n", max);
|
||||
std::fprintf(stdout, "\t%s (%ld msec)\n", b ? "Found" : "Not Found", static_cast<long>(static_cast<double>(ed - st) * 1000 / CLOCKS_PER_SEC));
|
||||
|
||||
for (RE_PREFIX::cmatch::size_type i = 0; i < mr.size(); ++i)
|
||||
{
|
||||
if (i)
|
||||
std::fprintf(stdout, "\t$%u = ", i);
|
||||
else
|
||||
std::fputs("\t$& = ", stdout);
|
||||
if (mr[i].matched)
|
||||
{
|
||||
matched = mr[i].str();
|
||||
msg = '"' + matched + '"' + " (%u+%u)";
|
||||
}
|
||||
else
|
||||
msg = matched = "(undefined)";
|
||||
|
||||
if (expected)
|
||||
{
|
||||
if (i < expected->size())
|
||||
{
|
||||
if (matched == expected->operator[](i))
|
||||
msg += "; passed!";
|
||||
else
|
||||
{
|
||||
msg += "; failed... (expected: \"" + expected->operator[](i) + "\")";
|
||||
++num_of_failures;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += "; failed..."; // should not exist.
|
||||
++num_of_failures;
|
||||
}
|
||||
}
|
||||
msg += '\n';
|
||||
std::fprintf(stdout, msg.c_str(), mr.position(i), mr.length(i));
|
||||
}
|
||||
|
||||
if (!num_of_failures && expected->size() != mr.size())
|
||||
++num_of_failures;
|
||||
|
||||
std::fprintf(stdout, "Result: %s.\n\n", num_of_failures ? "failed" : "passed");
|
||||
return num_of_failures == 0;
|
||||
}
|
||||
catch (const RE_PREFIX::regex_error &e)
|
||||
{
|
||||
std::fprintf(stdout, "Error (regex_error): %d \"%s\"\n\n", e.code(), e.what());
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::fprintf(stdout, "Error (std::exception): \"%s\"\n\n", e.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
const unsigned int count = 100000;
|
||||
std::string exp;
|
||||
std::string str;
|
||||
std::vector<std::string> expected;
|
||||
unsigned int num_of_tests = 0;
|
||||
unsigned int num_of_tests_passed = 0;
|
||||
unsigned int num_of_benches = 0;
|
||||
unsigned int num_of_benches_passed = 0;
|
||||
|
||||
std::fputs("Test 1 (ECMAScript 2021 Language Specification 22.2.2.3, NOTE)\n", stdout);
|
||||
str = "abc";
|
||||
exp = "((a)|(ab))((c)|(bc))";
|
||||
expected.resize(7);
|
||||
expected[0] = "abc";
|
||||
expected[1] = "a";
|
||||
expected[2] = "a";
|
||||
expected[3] = "(undefined)";
|
||||
expected[4] = "bc";
|
||||
expected[5] = "(undefined)";
|
||||
expected[6] = "bc";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 2a (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 2)\n", stdout);
|
||||
str = "abcdefghi";
|
||||
exp = "a[a-z]{2,4}";
|
||||
expected.resize(1);
|
||||
expected[0] = "abcde";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 2b (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 2)\n", stdout);
|
||||
str = "abcdefghi";
|
||||
exp = "a[a-z]{2,4}?";
|
||||
expected[0] = "abc";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 3 (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 2)\n", stdout);
|
||||
str = "aabaac";
|
||||
exp = "(aa|aabaac|ba|b|c)*";
|
||||
expected.resize(2);
|
||||
expected[0] = "aaba";
|
||||
expected[1] = "ba";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 4 (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 3)\n", stdout);
|
||||
str = "zaacbbbcac";
|
||||
exp = "(z)((a+)?(b+)?(c))*";
|
||||
expected.resize(6);
|
||||
expected[0] = "zaacbbbcac";
|
||||
expected[1] = "z";
|
||||
expected[2] = "ac";
|
||||
expected[3] = "a";
|
||||
expected[4] = "(undefined)";
|
||||
expected[5] = "c";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 5a (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 4)\n", stdout);
|
||||
str = "b";
|
||||
exp = "(a*)*";
|
||||
expected.resize(2);
|
||||
expected[0] = "";
|
||||
expected[1] = "";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 5b (ECMAScript 2021 Language Specification 22.2.2.5.1, NOTE 4)\n", stdout);
|
||||
str = "baaaac";
|
||||
exp = "(a*)b\\1+";
|
||||
expected[0] = "b";
|
||||
expected[1] = "";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 6a (ECMAScript 2021 Language Specification 22.2.2.8.2, NOTE 2)\n", stdout);
|
||||
str = "baaabac";
|
||||
exp = "(?=(a+))";
|
||||
expected[0] = "";
|
||||
expected[1] = "aaa";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 6b (ECMAScript 2021 Language Specification 22.2.2.8.2, NOTE 2)\n", stdout);
|
||||
str = "baaabac";
|
||||
exp = "(?=(a+))a*b\\1";
|
||||
expected[0] = "aba";
|
||||
expected[1] = "a";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 7 (ECMAScript 2021 Language Specification 22.2.2.8.2, NOTE 3)\n", stdout);
|
||||
str = "baaabaac";
|
||||
exp = "(.*?)a(?!(a+)b\\2c)\\2(.*)";
|
||||
expected.resize(4);
|
||||
expected[0] = "baaabaac";
|
||||
expected[1] = "ba";
|
||||
expected[2] = "(undefined)";
|
||||
expected[3] = "abaac";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
std::fputs("Test 8 (from https://github.com/tc39/test262/tree/master/test/built-ins/RegExp/lookBehind/misc.js)\n", stdout);
|
||||
str = "abc";
|
||||
exp = "(abc\\1)";
|
||||
expected.resize(2);
|
||||
expected[0] = "abc";
|
||||
expected[1] = "abc";
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_tests_passed;
|
||||
++num_of_tests;
|
||||
|
||||
#ifndef SKIP_BENCHMARK
|
||||
|
||||
std::fputs("Benchmark 01\n", stdout);
|
||||
//0123456
|
||||
str = "aaaabaa";
|
||||
exp = "^(.*)*b\\1$";
|
||||
expected.resize(2);
|
||||
expected[0] = "aaaabaa";
|
||||
expected[1] = "aa";
|
||||
if (test(str, exp, count, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 02\n", stdout);
|
||||
//012345678
|
||||
str = "aaaabaaaa";
|
||||
exp = "^(.*)*b\\1\\1$";
|
||||
expected[0] = "aaaabaaaa";
|
||||
expected[1] = "aa";
|
||||
if (test(str, exp, count, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 03\n", stdout);
|
||||
//01
|
||||
str = "ab";
|
||||
exp = "(.*?)*b\\1";
|
||||
expected[0] = "b";
|
||||
expected[1] = "";
|
||||
if (test(str, exp, count * 10, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 04\n", stdout);
|
||||
//01234567
|
||||
str = "acaaabbb";
|
||||
exp = "(a(.)a|\\2(.)b){2}";
|
||||
expected.resize(4);
|
||||
expected[0] = "aaabb";
|
||||
expected[1] = "bb";
|
||||
expected[2] = "(undefined)";
|
||||
expected[3] = "b";
|
||||
if (test(str, exp, count * 10, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 05\n", stdout);
|
||||
str = "aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaa";
|
||||
exp = "(a*)(b)*\\1\\1\\1";
|
||||
expected.resize(3);
|
||||
expected[0] = "aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaa";
|
||||
expected[1] = "aa";
|
||||
expected[2] = "b";
|
||||
if (test(str, exp, count, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 06a\n", stdout);
|
||||
str = "aaaaaaaaaab";
|
||||
exp = "(.*)*b";
|
||||
expected.resize(2);
|
||||
expected[0] = "aaaaaaaaaab";
|
||||
expected[1] = "aaaaaaaaaa";
|
||||
if (test(str, exp, count * 10, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 06b\n", stdout);
|
||||
str = "aaaaaaaaaab";
|
||||
exp = "(.*)+b";
|
||||
if (test(str, exp, count * 10, &expected)) // the same results expected.
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 06c\n", stdout);
|
||||
str = "aaaaaaaaaab";
|
||||
exp = "(.*){2,}b";
|
||||
expected[1] = "";
|
||||
if (test(str, exp, count * 10, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 07\n", stdout);
|
||||
str = "aaaaaaaaaabc";
|
||||
exp = "(?=(a+))(abc)";
|
||||
expected.resize(3);
|
||||
expected[0] = "abc";
|
||||
expected[1] = "a";
|
||||
expected[2] = "abc";
|
||||
if (test(str, exp, count, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 08\n", stdout);
|
||||
str = "1234-5678-1234-456";
|
||||
exp = "(\\d{4}[-]){3}\\d{3,4}";
|
||||
expected.resize(2);
|
||||
expected[0] = "1234-5678-1234-456";
|
||||
expected[1] = "1234-";
|
||||
if (test(str, exp, count * 5, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
std::fputs("Benchmark 09\n", stdout);
|
||||
str = "aaaaaaaaaaaaaaaaaaaaa";
|
||||
exp = "(.*)*b";
|
||||
expected.resize(0);
|
||||
if (test(str, exp, 1, &expected))
|
||||
++num_of_benches_passed;
|
||||
++num_of_benches;
|
||||
|
||||
#endif // !defined(SKIP_BENCHMARK)
|
||||
|
||||
std::fprintf(stdout, "Results of tests: %u/%u passed.\n", num_of_tests_passed, num_of_tests);
|
||||
std::fprintf(stdout, "Results of benchmarks: %u/%u passed.\n", num_of_benches_passed, num_of_benches);
|
||||
|
||||
return 0;
|
||||
|
||||
std::fputs("Benchmark 10\n", stdout);
|
||||
str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxz";
|
||||
exp = "(x+y*)+a";
|
||||
test(str, exp, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
21
lib/srell3_009/readme_en.txt
Normal file
21
lib/srell3_009/readme_en.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
How to Use
|
||||
|
||||
Put the following three files in one directory, and include srell.hpp.
|
||||
1. srell.hpp
|
||||
2. srell_ucfdata2.hpp (data for case folding)
|
||||
3. srell_updata.hpp (data for Unicode properties)
|
||||
|
||||
The files in the following directories are supplements. As SRELL does not use
|
||||
them, it is safe to remove them.
|
||||
|
||||
* misc
|
||||
Contains a source code file for a simple test and benchmark program.
|
||||
|
||||
* single-header
|
||||
Contains a standalone version of srell.hpp into which srell_ucfdata2.hpp
|
||||
and srell_updata.hpp have been merged.
|
||||
|
||||
* unicode
|
||||
Contains source code files for programs that generate srell_ucfdata.hpp and
|
||||
srell_update.hpp from latest Unicode data text files.
|
||||
|
||||
23
lib/srell3_009/readme_ja.txt
Normal file
23
lib/srell3_009/readme_ja.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
■使用法
|
||||
|
||||
次のファイルを同じディレクトリに置き、srell.hppをincludeするだけです。
|
||||
・srell.hpp
|
||||
・srell_ucfdata2.hpp(case folding用データ)
|
||||
・srell_updata.hpp(Unicode property用データ)
|
||||
|
||||
■付属物
|
||||
以下のディレクトリ内にあるものはおまけのようなものです。
|
||||
SRELL側からは参照していませんので、削除してしまってもライブラリの動作に
|
||||
影響はありません。
|
||||
|
||||
・misc
|
||||
簡単なテスト及びベンチマークプログラムのソースが入っています。
|
||||
|
||||
・single-header
|
||||
srell.hppの中にsrell_ucfdata2.hppとsrell_updata.hppとを統合してしまい、
|
||||
これ単体で使用できるようにしたstandalone版が入っています。
|
||||
|
||||
・unicode
|
||||
最新のUnicodeデータからsrell_ucfdata.hpp及びsrell_updata.hppを作るため
|
||||
のプログラムのソースが入っています。
|
||||
|
||||
18361
lib/srell3_009/single-header/srell.hpp
Normal file
18361
lib/srell3_009/single-header/srell.hpp
Normal file
File diff suppressed because it is too large
Load diff
9868
lib/srell3_009/srell.hpp
Normal file
9868
lib/srell3_009/srell.hpp
Normal file
File diff suppressed because it is too large
Load diff
2491
lib/srell3_009/srell_ucfdata2.hpp
Normal file
2491
lib/srell3_009/srell_ucfdata2.hpp
Normal file
File diff suppressed because it is too large
Load diff
6000
lib/srell3_009/srell_updata.hpp
Normal file
6000
lib/srell3_009/srell_updata.hpp
Normal file
File diff suppressed because it is too large
Load diff
76
lib/srell3_009/unicode/readme_en.txt
Normal file
76
lib/srell3_009/unicode/readme_en.txt
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
Contents of this directory:
|
||||
|
||||
1. ucfdataout2.cpp
|
||||
2. updataout.cpp
|
||||
|
||||
----
|
||||
1. ucfdataout2.cpp
|
||||
|
||||
This is a C++ source file for a program that generates a newer version
|
||||
of srell_ucfdata2.hpp, which is a C++ source file that SRELL 2.500- includes
|
||||
for case-folding. It is generated by ucfdataout with CaseFolding.txt provided
|
||||
by the Unicode Consortium.
|
||||
|
||||
+---------------------------------------------------------------------------
|
||||
| What is CaseFolding.txt?
|
||||
|
|
||||
| It is a data file needed for case-insensitive matching based on the
|
||||
| Unicode Standard. Whenever a new version of the Unicode Standard is
|
||||
| released, CaseFolding.txt may also be updated accordingly.
|
||||
|
|
||||
+---------------------------------------------------------------------------
|
||||
|
||||
1-1. Usage
|
||||
|
||||
1) compile ucfdataout2.cpp,
|
||||
2) get the latest version of CaseFolding.txt, which is available at
|
||||
http://www.unicode.org/Public/UNIDATA/CaseFolding.txt ,
|
||||
3) put CaseFolding.txt and a binary file generated at 1) in the same
|
||||
directory and run the binary file,
|
||||
4) move the newly generated "srell_ucfdata2.hpp" to the directory in where
|
||||
SRELL is put.
|
||||
|
||||
1-2. Compatibility
|
||||
|
||||
srell_ucfdata2.hpp is not compatible with srell_ucfdata.hpp that SRELL up
|
||||
to 2.401 was using.
|
||||
|
||||
----
|
||||
2. updataout.cpp
|
||||
|
||||
This is a C++ source file for a program that generates a newer version
|
||||
of srell_updata.hpp, which is a C++ source file that SRELL includes for
|
||||
the Unicode property escapes (\p{...} and \P{...}). It is generated by
|
||||
updataout with the following text files provided by the Unicode Consortium:
|
||||
|
||||
* DerivedCoreProperties.txt
|
||||
* DerivedNormalizationProps.txt
|
||||
* emoji-data.txt
|
||||
* PropList.txt
|
||||
* ScriptExtensions.txt
|
||||
* Scripts.txt
|
||||
* UnicodeData.txt
|
||||
|
||||
As well as CaseFolding.txt mentioned above, these files may be updated
|
||||
accordingly whenever a new version of the Unicode Standard is released.
|
||||
|
||||
2-1. Usage
|
||||
|
||||
1) compile updataout.cpp,
|
||||
2) get the latest versions of the text files mentioned above, which are
|
||||
available at:
|
||||
a. emoji-data.txt: http://www.unicode.org/Public/UNIDATA/emoji/
|
||||
b. others: http://www.unicode.org/Public/UNIDATA/ ,
|
||||
3) put the text files and a binary file generated at 1) in the same
|
||||
directory and run the binary file,
|
||||
4) move the newly generated "srell_updata.hpp" to the directory in where
|
||||
SRELL is put.
|
||||
|
||||
Note: emoji-data.txt has been moved from /Public/UNIDATA/ to
|
||||
/Public/emoji/(version number)/ since Unicode 11.0.0.
|
||||
Since Unicode 13.0.0, moved to /Public/UNIDATA/emoji/ .
|
||||
|
||||
2-2. Compatibility
|
||||
|
||||
srell_updata.hpp does not have compatibility issues as of this release.
|
||||
|
||||
84
lib/srell3_009/unicode/readme_ja.txt
Normal file
84
lib/srell3_009/unicode/readme_ja.txt
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
■同梱物について
|
||||
|
||||
1. ucfdataout2.cpp
|
||||
2. updataout.cpp
|
||||
|
||||
----
|
||||
1. ucfdataout2.cpp
|
||||
|
||||
srell_ucfdata2.hppの最新版を作成するプログラムのソースファイルです。SRELLの
|
||||
2.5以降はcase-insensitiveな(大文字小文字の違いを無視した)照合を行うために、
|
||||
このsrell_ucfdata2.hppを必要とします。
|
||||
|
||||
ucfdataout2は、Unicode Consortiumより提供されているCaseFolding.txtというテキ
|
||||
ストデータからsrell_ucfdata2.hppを自動生成します。
|
||||
|
||||
+---------------------------------------------------------------------------
|
||||
| CaseFolding.txtとは
|
||||
|
|
||||
| Case-insensitiveな照合を行う際には、大文字小文字の違いを吸収するために
|
||||
| "case-folding" と呼ばれる処理が行われます。Unicode規格に基づいた
|
||||
| case-foldingを行うために、Unicode Consortiumから提供されているのが
|
||||
| CaseFolding.txtです。
|
||||
|
|
||||
| このデータファイルはUnicode規格がアップデートされるとそれに合わせて
|
||||
| アップデートされる可能性があります。
|
||||
|
|
||||
+---------------------------------------------------------------------------
|
||||
|
||||
1-1. 使用方法
|
||||
|
||||
1) ucfdataout2.cppをコンパイルします。
|
||||
2) 最新版のCaseFolding.txtを次のURLより取得します。
|
||||
http://www.unicode.org/Public/UNIDATA/CaseFolding.txt ,
|
||||
3) CaseFolding.txtと、1)で作成したバイナリとを同じフォルダに置いて
|
||||
バイナリを実行します。
|
||||
4) srell_ucfdata2.hppが生成されますので、それをSRELLの置かれているディレク
|
||||
トリへと移動させます。
|
||||
|
||||
1-2. 互換性
|
||||
|
||||
srell_ucfdata2.hppは、SRELL 2.401までが利用していたsrell_updata.hppと互換
|
||||
性がありません。
|
||||
|
||||
----
|
||||
2. updataout.cpp
|
||||
|
||||
srell_updata.hppの最新版を作成するプログラムのソースファイルです。SRELLは
|
||||
Unicode property escapes(\p{...} と \P{...})を含む正規表現と文字列との照合
|
||||
を行うために、このsrell_updata.hppを必要とします。
|
||||
|
||||
updataoutは、Unicode Consortiumより提供されている次のテキストデータから
|
||||
srell_updata.hppを自動生成します。
|
||||
|
||||
・DerivedCoreProperties.txt
|
||||
・DerivedNormalizationProps.txt
|
||||
・emoji-data.txt
|
||||
・PropList.txt
|
||||
・ScriptExtensions.txt
|
||||
・Scripts.txt
|
||||
・UnicodeData.txt
|
||||
|
||||
先述のCaseFolding.txt同様、これらのテキストデータファイルもUnicode規格が
|
||||
アップデートされるとそれに合わせてアップデートされる可能性があります。
|
||||
|
||||
2-1. 使用方法
|
||||
|
||||
1) updataout.cppをコンパイルします。
|
||||
2) 前記テキストファイルの最新版を次のURLより取得します。
|
||||
a. emoji-data.txt: http://www.unicode.org/Public/UNIDATA/emoji/
|
||||
b. それ以外: http://www.unicode.org/Public/UNIDATA/
|
||||
3) これらのテキストファイルと、1)で作成したバイナリとを同じフォルダに
|
||||
置いてバイナリを実行します。
|
||||
4) srell_updata.hppが生成されますので、それをSRELLの置かれているディレク
|
||||
トリへと移動させます。
|
||||
|
||||
補註: Unicode 11.0.0以降、emoji-data.txt は /Public/UNIDATA/ から
|
||||
/Public/emoji/(ヴァージョン番号)/ へ移されました。
|
||||
さらに Unicode 13.0.0以降、/Public/UNIDATA/emoji/ へ移されました。
|
||||
|
||||
2-2. 互換性
|
||||
|
||||
srell_updata.hpp には非互換となるような変更はこれまでのところ加えられてい
|
||||
ません。
|
||||
|
||||
590
lib/srell3_009/unicode/ucfdataout2.cpp
Normal file
590
lib/srell3_009/unicode/ucfdataout2.cpp
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
//
|
||||
// ucfdataout.cpp: version 2.100 (2020/05/13).
|
||||
//
|
||||
// This is a program that generates srell_ucfdata.hpp from CaseFolding.txt
|
||||
// provided by the Unicode Consortium. The latese version is available at:
|
||||
// http://www.unicode.org/Public/UNIDATA/CaseFolding.txt
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "../srell.hpp"
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
namespace unishared
|
||||
{
|
||||
template <const std::size_t BufSize, typename Type>
|
||||
std::string stringify(const Type value, const char *const fmt)
|
||||
{
|
||||
char buffer[BufSize];
|
||||
std::sprintf(buffer, fmt, value);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
bool read_file(std::string &str, const char *const filename, const char *const dir)
|
||||
{
|
||||
const std::string path(std::string(dir ? dir : "") + filename);
|
||||
FILE *const fp = std::fopen(path.c_str(), "r");
|
||||
|
||||
std::fprintf(stdout, "Reading '%s'... ", path.c_str());
|
||||
|
||||
if (fp)
|
||||
{
|
||||
static const std::size_t bufsize = 4096;
|
||||
char *const buffer = static_cast<char *>(std::malloc(bufsize));
|
||||
|
||||
if (buffer)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
const std::size_t size = std::fread(buffer, 1, bufsize, fp);
|
||||
|
||||
if (!size)
|
||||
break;
|
||||
|
||||
str.append(buffer, size);
|
||||
}
|
||||
std::fclose(fp);
|
||||
std::fputs("done.\n", stdout);
|
||||
std::free(buffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::fputs("failed...\n", stdout);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write_file(const char *const filename, const std::string &str)
|
||||
{
|
||||
FILE *const fp = std::fopen(filename, "wb");
|
||||
|
||||
std::fprintf(stdout, "Writing '%s'... ", filename);
|
||||
|
||||
if (fp)
|
||||
{
|
||||
const bool success = std::fwrite(str.c_str(), 1, str.size(), fp) == str.size();
|
||||
std::fclose(fp);
|
||||
if (success)
|
||||
{
|
||||
std::fputs("done.\n", stdout);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::fputs("failed...\n", stdout);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// namespace unishared
|
||||
|
||||
struct ucf_options
|
||||
{
|
||||
const char *infilename;
|
||||
const char *outfilename;
|
||||
const char *indir;
|
||||
int version;
|
||||
int errorno;
|
||||
|
||||
ucf_options(const int argc, const char *const *const argv)
|
||||
: infilename("CaseFolding.txt")
|
||||
, outfilename("srell_ucfdata2.hpp")
|
||||
, indir("")
|
||||
, version(2)
|
||||
, errorno(0)
|
||||
{
|
||||
bool outfile_specified = false;
|
||||
|
||||
for (int index = 1; index < argc; ++index)
|
||||
{
|
||||
const char firstchar = argv[index][0];
|
||||
|
||||
if (firstchar == '-' || firstchar == '/')
|
||||
{
|
||||
const char *const option = argv[index] + 1;
|
||||
|
||||
++index;
|
||||
if (std::strcmp(option, "i") == 0)
|
||||
{
|
||||
if (index >= argc)
|
||||
goto NO_ARGUMENT;
|
||||
infilename = argv[index];
|
||||
}
|
||||
else if (std::strcmp(option, "o") == 0)
|
||||
{
|
||||
if (index >= argc)
|
||||
goto NO_ARGUMENT;
|
||||
outfilename = argv[index];
|
||||
outfile_specified = true;
|
||||
}
|
||||
else if (std::strcmp(option, "v") == 0)
|
||||
{
|
||||
if (index >= argc)
|
||||
goto NO_ARGUMENT;
|
||||
version = static_cast<int>(std::strtol(argv[index], NULL, 10));
|
||||
if (!outfile_specified && version < 2)
|
||||
{
|
||||
static const char *const v1name = "srell_ucfdata.hpp";
|
||||
outfilename = v1name;
|
||||
}
|
||||
}
|
||||
else if (std::strcmp(option, "id") == 0)
|
||||
{
|
||||
if (index >= argc)
|
||||
goto NO_ARGUMENT;
|
||||
indir = argv[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
--index;
|
||||
goto UNKNOWN_OPTION;
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
NO_ARGUMENT:
|
||||
std::fprintf(stdout, "[Error] no argument for \"%s\" specified.\n", argv[--index]);
|
||||
errorno = -2;
|
||||
}
|
||||
else
|
||||
{
|
||||
UNKNOWN_OPTION:
|
||||
std::fprintf(stdout, "[Error] unknown option \"%s\" found.\n", argv[index]);
|
||||
errorno = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// struct ucf_options
|
||||
|
||||
class unicode_casefolding
|
||||
{
|
||||
public:
|
||||
|
||||
unicode_casefolding()
|
||||
: maxdelta_(0L), maxdelta_cp_(0L), ucf_maxcodepoint_(0L), rev_maxcodepoint_(0L)
|
||||
, ucf_numofsegs_(1U), rev_numofsegs_(1U), numofcps_from_(0U), numofcps_to_(0U)
|
||||
, max_appearance_(0U), nextoffset_(0x100L), rev_charsets_(1, -1L)
|
||||
{
|
||||
}
|
||||
|
||||
int create_ucfdata(std::string &outdata, const ucf_options &opts)
|
||||
{
|
||||
const std::string indent("\t\t\t");
|
||||
int errorno = opts.errorno;
|
||||
std::string buf;
|
||||
|
||||
if (errorno)
|
||||
return errorno;
|
||||
|
||||
if (unishared::read_file(buf, opts.infilename, opts.indir))
|
||||
{
|
||||
static const srell::regex re_line("^.*$", srell::regex::multiline);
|
||||
const srell::cregex_iterator eos;
|
||||
srell::cregex_iterator iter(buf.c_str(), buf.c_str() + buf.size(), re_line);
|
||||
srell::cmatch match;
|
||||
int colcount = 0;
|
||||
|
||||
for (; iter != eos; ++iter)
|
||||
{
|
||||
if (iter->length(0))
|
||||
{
|
||||
static const srell::regex re_datainfo("^# (.*)$");
|
||||
|
||||
if (!srell::regex_match((*iter)[0].first, (*iter)[0].second, match, re_datainfo))
|
||||
{
|
||||
outdata.append(1, '\n');
|
||||
break;
|
||||
}
|
||||
outdata += "// " + match.str(1) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.version <= 1)
|
||||
outdata += "template <typename T1, typename T2, typename T3>\nstruct unicode_casefolding\n{\n\tstatic const T1 *table()\n\t{\n\t\tstatic const T1 ucftable[] =\n\t\t{\n";
|
||||
else
|
||||
outdata += "template <typename T2, typename T3>\nstruct unicode_casefolding\n{\n";
|
||||
|
||||
for (; iter != eos; ++iter)
|
||||
{
|
||||
static const srell::regex re_cfdata("^\\s*([0-9A-Fa-f]+); ([CS]); ([0-9A-Fa-f]+);\\s*#\\s*(.*)$");
|
||||
const srell::cmatch &line = *iter;
|
||||
|
||||
if (srell::regex_match(line[0].first, line[0].second, match, re_cfdata))
|
||||
{
|
||||
const std::string from(match[1]);
|
||||
const std::string to(match[3]);
|
||||
const std::string type(match[2]);
|
||||
const std::string name(match[4]);
|
||||
|
||||
update(from, to);
|
||||
|
||||
if (opts.version == 1)
|
||||
outdata += indent + "{ 0x" + from + ", 0x" + to + " },\t// " + type + "; " + name + "\n";
|
||||
else if (opts.version <= 0)
|
||||
{
|
||||
if (colcount == 0)
|
||||
outdata += indent;
|
||||
outdata += "{ 0x" + from + ", 0x" + to + " },";
|
||||
if (++colcount == 4)
|
||||
{
|
||||
outdata.append(1, '\n');
|
||||
colcount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (opts.version == 1)
|
||||
{
|
||||
static const srell::regex re_comment_or_emptyline("^#.*|^$");
|
||||
|
||||
if (!srell::regex_match(line[0].first, line[0].second, re_comment_or_emptyline))
|
||||
outdata += indent + "// " + line.str(0) + "\n";
|
||||
}
|
||||
}
|
||||
if (colcount > 0)
|
||||
outdata.append(1, '\n');
|
||||
if (opts.version <= 1)
|
||||
outdata += indent + "{ 0, 0 }\n\t\t};\n\t\treturn ucftable;\n\t}\n";
|
||||
|
||||
outdata += "\tstatic const T2 ucf_maxcodepoint = 0x" + unishared::stringify<16>(ucf_maxcodepoint_, "%.4lX") + ";\n";
|
||||
outdata += "\tstatic const T3 ucf_deltatablesize = 0x" + unishared::stringify<16>(ucf_numofsegs_ << 8, "%X") + ";\n";
|
||||
|
||||
outdata += "\tstatic const T2 rev_maxcodepoint = 0x" + unishared::stringify<16>(rev_maxcodepoint_, "%.4lX") + ";\n";
|
||||
outdata += "\tstatic const T3 rev_indextablesize = 0x" + unishared::stringify<16>(rev_numofsegs_ << 8, "%X") + ";\n";
|
||||
outdata += "\tstatic const T3 rev_charsettablesize = " + unishared::stringify<16>(numofcps_to_ * 2 + numofcps_from_ + 1, "%u") + ";\t// 1 + " + unishared::stringify<16>(numofcps_to_, "%u") + " * 2 + " + unishared::stringify<16>(numofcps_from_, "%u") + "\n";
|
||||
outdata += "\tstatic const T3 rev_maxset = " + unishared::stringify<16>(maxset(), "%u") + ";\n";
|
||||
outdata += "\tstatic const T2 eos = 0;\n";
|
||||
|
||||
if (opts.version >= 2)
|
||||
{
|
||||
outdata += "\n\tstatic const T2 ucf_deltatable[];\n\tstatic const T3 ucf_segmenttable[];\n\tstatic const T3 rev_indextable[];\n\tstatic const T3 rev_segmenttable[];\n\tstatic const T2 rev_charsettable[];\n\n\tstatic const T2 *ucf_deltatable_ptr()\n\t{\n\t\treturn ucf_deltatable;\n\t}\n\tstatic const T3 *ucf_segmenttable_ptr()\n\t{\n\t\treturn ucf_segmenttable;\n\t}\n\tstatic const T3 *rev_indextable_ptr()\n\t{\n\t\treturn rev_indextable;\n\t}\n\tstatic const T3 *rev_segmenttable_ptr()\n\t{\n\t\treturn rev_segmenttable;\n\t}\n\tstatic const T2 *rev_charsettable_ptr()\n\t{\n\t\treturn rev_charsettable;\n\t}\n};\n\n";
|
||||
out_v2tables(outdata);
|
||||
outdata += "#define SRELL_UCFDATA_VERSION 200\n";
|
||||
}
|
||||
else
|
||||
outdata += "};\n#define SRELL_UCFDATA_VER 201909L\n";
|
||||
|
||||
std::fprintf(stdout, "MaxDelta: %+ld (U+%.4lX->U+%.4lX)\n", maxdelta_, maxdelta_cp_, maxdelta_cp_ + maxdelta_);
|
||||
}
|
||||
else
|
||||
errorno = 1;
|
||||
|
||||
return errorno;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void update(const std::string &from, const std::string &to)
|
||||
{
|
||||
const long cp_from = std::strtol(from.c_str(), NULL, 16);
|
||||
const long cp_to = std::strtol(to.c_str(), NULL, 16);
|
||||
const long delta = cp_to - cp_from;
|
||||
const long segno_from = cp_from >> 8;
|
||||
const long segno_to = cp_to >> 8;
|
||||
|
||||
update_tables(cp_from, cp_to, segno_from);
|
||||
|
||||
++numofcps_from_;
|
||||
if (std::abs(maxdelta_) < std::abs(delta))
|
||||
{
|
||||
maxdelta_cp_ = cp_from;
|
||||
maxdelta_ = delta;
|
||||
}
|
||||
|
||||
if (ucf_maxcodepoint_ < cp_from)
|
||||
ucf_maxcodepoint_ = cp_from;
|
||||
|
||||
if (rev_maxcodepoint_ < cp_to)
|
||||
rev_maxcodepoint_ = cp_to;
|
||||
|
||||
if (rev_maxcodepoint_ < cp_from)
|
||||
rev_maxcodepoint_ = cp_from;
|
||||
|
||||
if (!ucf_countedsegnos.count(segno_from))
|
||||
{
|
||||
ucf_countedsegnos[segno_from] = 1;
|
||||
++ucf_numofsegs_;
|
||||
}
|
||||
|
||||
if (!rev_countedsegnos.count(segno_to))
|
||||
{
|
||||
rev_countedsegnos[segno_to] = 1;
|
||||
++rev_numofsegs_;
|
||||
}
|
||||
if (!rev_countedsegnos.count(segno_from))
|
||||
{
|
||||
rev_countedsegnos[segno_from] = 1;
|
||||
++rev_numofsegs_;
|
||||
}
|
||||
|
||||
if (!cps_counted_as_foldedto.count(cp_to))
|
||||
{
|
||||
cps_counted_as_foldedto[cp_to] = 1;
|
||||
++numofcps_to_;
|
||||
}
|
||||
|
||||
if (appearance_counts_.count(to))
|
||||
++appearance_counts_[to];
|
||||
else
|
||||
appearance_counts_[to] = 1;
|
||||
|
||||
if (max_appearance_ < appearance_counts_[to])
|
||||
max_appearance_ = appearance_counts_[to];
|
||||
}
|
||||
|
||||
unsigned int maxset() const
|
||||
{
|
||||
return max_appearance_ + 1;
|
||||
}
|
||||
|
||||
void out_v2tables(std::string &outdata)
|
||||
{
|
||||
const char *const headers[] = {
|
||||
"template <typename T2, typename T3>\nconst ",
|
||||
" unicode_casefolding<T2, T3>::",
|
||||
"[] =\n{\n"
|
||||
};
|
||||
|
||||
create_revtables();
|
||||
out_lowertable(outdata, headers, "T2", "ucf_deltatable", ucf_deltas_, ucf_segments_);
|
||||
outdata.append(1, '\n');
|
||||
out_uppertable(outdata, headers, "T3", "ucf_segmenttable", ucf_segments_);
|
||||
outdata.append(1, '\n');
|
||||
out_lowertable(outdata, headers, "T3", "rev_indextable", rev_indices_, rev_segments_);
|
||||
outdata.append(1, '\n');
|
||||
out_uppertable(outdata, headers, "T3", "rev_segmenttable", rev_segments_);
|
||||
outdata.append(1, '\n');
|
||||
out_cstable(outdata, headers, "T2", "rev_charsettable", rev_charsets_);
|
||||
}
|
||||
|
||||
// Updates ucf_segments_, ucf_deltas_, and rev_charsets_.
|
||||
void update_tables(const long cp_from, const long cp_to, const long segno_from)
|
||||
{
|
||||
if (segno_from >= static_cast<long>(ucf_segments_.size()))
|
||||
ucf_segments_.resize(segno_from + 1, 0L);
|
||||
|
||||
long &offset_of_segment = ucf_segments_[segno_from];
|
||||
|
||||
if (offset_of_segment == 0L)
|
||||
{
|
||||
offset_of_segment = nextoffset_;
|
||||
nextoffset_ += 0x100L;
|
||||
ucf_deltas_.resize(nextoffset_, 0L);
|
||||
}
|
||||
|
||||
ucf_deltas_[offset_of_segment + (cp_from & 0xffL)] = cp_to - cp_from;
|
||||
|
||||
for (long index = 0L;; ++index)
|
||||
{
|
||||
if (index == static_cast<long>(rev_charsets_.size()))
|
||||
{
|
||||
rev_charsets_.push_back(cp_to);
|
||||
rev_charsets_.push_back(cp_from);
|
||||
rev_charsets_.push_back(-1L);
|
||||
break;
|
||||
}
|
||||
if (rev_charsets_[index] == cp_to)
|
||||
{
|
||||
for (++index; rev_charsets_[index] != -1L; ++index);
|
||||
|
||||
rev_charsets_.insert(index, 1, cp_from);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates rev_segments_ and rev_indices_ from rev_charsets_.
|
||||
void create_revtables()
|
||||
{
|
||||
long nextoffset = 0x100L;
|
||||
for (long index = 0L; index < static_cast<long>(rev_charsets_.size()); ++index)
|
||||
{
|
||||
const long bocs = index; // Beginning of charset.
|
||||
|
||||
for (; rev_charsets_[index] != -1L; ++index)
|
||||
{
|
||||
const long &u21ch = rev_charsets_[index];
|
||||
const long segno = u21ch >> 8L;
|
||||
|
||||
if (segno >= static_cast<long>(rev_segments_.size()))
|
||||
rev_segments_.resize(segno + 1, 0L);
|
||||
|
||||
long &offset_of_segment = rev_segments_[segno];
|
||||
|
||||
if (offset_of_segment == 0L)
|
||||
{
|
||||
offset_of_segment = nextoffset;
|
||||
nextoffset += 0x100L;
|
||||
rev_indices_.resize(nextoffset, 0L);
|
||||
}
|
||||
rev_indices_[offset_of_segment + (u21ch & 0xffL)] = bocs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_lowertable(std::string &outdata, const char *const headers[], const char *const type, const char *const funcname, const std::basic_string<long> &table, const std::basic_string<long> &segtable) const
|
||||
{
|
||||
int end = static_cast<int>(table.size());
|
||||
|
||||
outdata += headers[0];
|
||||
outdata += type;
|
||||
outdata += headers[1];
|
||||
outdata += funcname;
|
||||
outdata += headers[2];
|
||||
|
||||
for (int i = 0; i < end;)
|
||||
{
|
||||
const int col = i & 15;
|
||||
|
||||
if ((i & 255) == 0)
|
||||
{
|
||||
if (i)
|
||||
{
|
||||
for (int j = 0; j < static_cast<int>(segtable.size()); ++j)
|
||||
{
|
||||
if (segtable[j] == i)
|
||||
{
|
||||
outdata += "\n\t// For u+" + unishared::stringify<16>(j, "%.2X") + "xx (" + unishared::stringify<16>(i, "%d") + ")\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
outdata += "\t// For common (0)\n";
|
||||
}
|
||||
|
||||
outdata += col == 0 ? "\t" : (col & 3) == 0 ? " " : " ";
|
||||
if (table[i] >= 0L)
|
||||
outdata += unishared::stringify<16>(table[i], "%ld");
|
||||
else
|
||||
outdata += "static_cast<", outdata += type, outdata += ">(", outdata += unishared::stringify<16>(table[i], "%ld") + ")";
|
||||
|
||||
if (++i == end)
|
||||
outdata.append(1, '\n');
|
||||
else if (col == 15)
|
||||
outdata += ",\n";
|
||||
else
|
||||
outdata.append(1, ',');
|
||||
}
|
||||
outdata += "};\n";
|
||||
}
|
||||
|
||||
void out_uppertable(std::string &outdata, const char *const headers[], const char *const type, const char *const funcname, const std::basic_string<long> &table) const
|
||||
{
|
||||
int end = static_cast<int>(table.size());
|
||||
|
||||
outdata += headers[0];
|
||||
outdata += type;
|
||||
outdata += headers[1];
|
||||
outdata += funcname;
|
||||
outdata += headers[2];
|
||||
|
||||
for (int i = 0; i < end;)
|
||||
{
|
||||
const int col = i & 15;
|
||||
|
||||
outdata += col == 0 ? "\t" : (col & 3) == 0 ? " " : " ";
|
||||
if (table[i] >= 0)
|
||||
outdata += unishared::stringify<16>(table[i], "%ld");
|
||||
else
|
||||
outdata += "static_cast<", outdata += type, outdata += ">(", outdata += unishared::stringify<16>(table[i], "%ld") + ")";
|
||||
|
||||
if (++i == end)
|
||||
outdata.append(1, '\n');
|
||||
else if (col == 15)
|
||||
outdata += ",\n";
|
||||
else
|
||||
outdata.append(1, ',');
|
||||
}
|
||||
outdata += "};\n";
|
||||
}
|
||||
|
||||
void out_cstable(std::string &outdata, const char *const headers[], const char *const type, const char *const funcname, const std::basic_string<long> &table) const
|
||||
{
|
||||
int end = static_cast<int>(table.size());
|
||||
bool newline = true;
|
||||
int bos = 0;
|
||||
int prevprintedbos = -1;
|
||||
|
||||
outdata += headers[0];
|
||||
outdata += type;
|
||||
outdata += headers[1];
|
||||
outdata += funcname;
|
||||
outdata += headers[2];
|
||||
|
||||
for (int i = 0; i < end;)
|
||||
{
|
||||
const long val = table[i];
|
||||
|
||||
outdata += newline ? "\t" : " ";
|
||||
newline = false;
|
||||
|
||||
if (val == -1L)
|
||||
outdata += "eos";
|
||||
else
|
||||
outdata += "0x", outdata += unishared::stringify<16>(val, "%.4lX");
|
||||
|
||||
if (++i != end)
|
||||
outdata.append(1, ',');
|
||||
|
||||
if (val == -1L)
|
||||
{
|
||||
if (prevprintedbos != bos / 10 || i == end)
|
||||
{
|
||||
outdata += "\t// ";
|
||||
outdata += unishared::stringify<16>(bos, "%d");
|
||||
prevprintedbos = bos / 10;
|
||||
}
|
||||
outdata.append(1, '\n');
|
||||
newline = true;
|
||||
bos = i;
|
||||
}
|
||||
}
|
||||
outdata += "};\n";
|
||||
}
|
||||
|
||||
typedef std::map<long, char> flagset_type;
|
||||
|
||||
long maxdelta_; // = 0L;
|
||||
long maxdelta_cp_; // = 0L;
|
||||
long ucf_maxcodepoint_; // = 0L; // The max code point for case-folding.
|
||||
long rev_maxcodepoint_; // = 0L; // The max code point for reverse lookup.
|
||||
unsigned int ucf_numofsegs_; // = 1U; // The number of segments in the delta table.
|
||||
unsigned int rev_numofsegs_; // = 1U; // The number of segments in the table for reverse lookup.
|
||||
unsigned int numofcps_from_; // = 0U; // The number of code points in "folded from"s.
|
||||
unsigned int numofcps_to_; // = 0U; // The number of code points in "folded to"s.
|
||||
|
||||
flagset_type ucf_countedsegnos; // The set of segment nos marked as "counted" for case-folding.
|
||||
flagset_type rev_countedsegnos; // The set of segment nos marked as "counted" for reverse lookup.
|
||||
flagset_type cps_counted_as_foldedto; // The set of code points marked as "folded to".
|
||||
|
||||
unsigned int max_appearance_;
|
||||
std::map<std::string, unsigned int> appearance_counts_;
|
||||
|
||||
long nextoffset_;
|
||||
std::basic_string<long> ucf_deltas_;
|
||||
std::basic_string<long> ucf_segments_;
|
||||
std::basic_string<long> rev_indices_;
|
||||
std::basic_string<long> rev_segments_;
|
||||
std::basic_string<long> rev_deltas_;
|
||||
std::basic_string<long> rev_charsets_;
|
||||
};
|
||||
// class unicode_casefolding
|
||||
|
||||
int main(const int argc, const char *const *const argv)
|
||||
{
|
||||
ucf_options ucfopts(argc, argv);
|
||||
std::string outdata;
|
||||
unicode_casefolding ucf;
|
||||
int errorno = ucf.create_ucfdata(outdata, ucfopts);
|
||||
|
||||
if (errorno == 0)
|
||||
{
|
||||
if (!unishared::write_file(ucfopts.outfilename, outdata))
|
||||
errorno = 2;
|
||||
}
|
||||
return errorno;
|
||||
}
|
||||
1066
lib/srell3_009/unicode/updataout.cpp
Normal file
1066
lib/srell3_009/unicode/updataout.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue