mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-15 20:37:42 +01:00
Add Nuke Build (and temporary nuke Dalamud, heh)
This commit is contained in:
parent
c6a9879498
commit
b262fd7e9d
122 changed files with 287 additions and 7894 deletions
1
.nuke
Normal file
1
.nuke
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Dalamud.sln
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Dalamud.Bootstrap.Crypto
|
namespace Dalamud.Bootstrap.Crypto
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,13 @@
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<nullable>enable</nullable>
|
<nullable>enable</nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources> <!-- Required by CoreHook to build -->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Resources.Extensions" Version="4.7.0" /> <!-- Required by CoreHook to build -->
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\lib\CoreHook\src\CoreHook.BinaryInjection\CoreHook.BinaryInjection.csproj" />
|
<ProjectReference Include="..\lib\CoreHook\src\CoreHook.BinaryInjection\CoreHook.BinaryInjection.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,14 @@ namespace Dalamud.Bootstrap.SqexArg
|
||||||
}
|
}
|
||||||
|
|
||||||
var checksum = argument[^5];
|
var checksum = argument[^5];
|
||||||
var payload = argument[11..^5]
|
var payload = argument[11..^5];
|
||||||
.Replace(; // encoded in url-safe variant of base64
|
|
||||||
|
|
||||||
// decode
|
// decode
|
||||||
|
|
||||||
Convert.FromBase64String();
|
//Convert.FromBase64String();
|
||||||
container = new ArgumentContainer(payload, checksum);
|
//container = new ArgumentContainer(payload, checksum);
|
||||||
|
|
||||||
|
container = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
112
Dalamud.sln
112
Dalamud.sln
|
|
@ -7,22 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud", "Dalamud\Dalamud.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Injector", "Dalamud.Injector\Dalamud.Injector.csproj", "{5B832F73-5F54-4ADC-870F-D0095EF72C9A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGuiScene", "lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj", "{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL2-CS", "lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj", "{85480198-8711-4355-830E-72FD794AD3F6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImGui.NET-472", "lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj", "{0483026E-C6CE-4B1A-AA68-46544C08140B}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interface", "Interface", "{E15BDA6D-E881-4482-94BA-BE5527E917FF}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DiscordNet", "DiscordNet", "{E1F3D3F5-7820-4A62-A16A-53260375A781}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Rest", "lib\Discord.Net\src\Discord.Net.Rest\Discord.Net.Rest.csproj", "{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "lib\Discord.Net\src\Discord.Net.Core\Discord.Net.Core.csproj", "{B46F33B5-9702-434E-A92C-8DE5CF7164E3}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.WebSocket", "lib\Discord.Net\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj", "{AACDC15A-56F8-458F-9C73-DE89F60466AE}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{E9868930-4223-4D57-8F31-84E580E4B24B}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{E9868930-4223-4D57-8F31-84E580E4B24B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CoreHook", "CoreHook", "{CE78D902-02B5-4C7B-A46A-D44BD2F4C622}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CoreHook", "CoreHook", "{CE78D902-02B5-4C7B-A46A-D44BD2F4C622}"
|
||||||
|
|
@ -39,9 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreHook.IPC", "lib\CoreHoo
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreHook.Memory", "lib\CoreHook\src\CoreHook.Memory\CoreHook.Memory.csproj", "{ED4FFE13-5F83-42B9-8847-E7C4D7A988E8}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreHook.Memory", "lib\CoreHook\src\CoreHook.Memory\CoreHook.Memory.csproj", "{ED4FFE13-5F83-42B9-8847-E7C4D7A988E8}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Bootstrap", "Dalamud.Bootstrap\Dalamud.Bootstrap.csproj", "{19032128-E336-460F-B6E4-EAF6055589E5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Bootstrap", "Dalamud.Bootstrap\Dalamud.Bootstrap.csproj", "{19032128-E336-460F-B6E4-EAF6055589E5}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dalamud.Testing", "Dalamud.Testing\Dalamud.Testing.csproj", "{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dalamud.Testing", "Dalamud.Testing\Dalamud.Testing.csproj", "{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Build", "build\Build.csproj", "{A6BB70E8-7F14-410F-A296-1B4495F6F069}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
@ -77,78 +63,6 @@ Global
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.Build.0 = Release|Any CPU
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU
|
{5B832F73-5F54-4ADC-870F-D0095EF72C9A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.ActiveCfg = Debug|x86
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Debug|x86.Build.0 = Debug|x86
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x64.Build.0 = Release|x64
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.ActiveCfg = Release|x86
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6}.Release|x86.Build.0 = Release|x86
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{441EE8F0-AD8E-479B-9F68-12D157F080AF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
|
@ -245,19 +159,21 @@ Global
|
||||||
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x64.Build.0 = Release|Any CPU
|
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x86.ActiveCfg = Release|Any CPU
|
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x86.Build.0 = Release|Any CPU
|
{0A99A6B3-12E2-4197-A6F3-9211B6D4D824}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A6BB70E8-7F14-410F-A296-1B4495F6F069}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{C0E7E797-4FBF-4F46-BC57-463F3719BA7A} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
|
||||||
{85480198-8711-4355-830E-72FD794AD3F6} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
|
||||||
{0483026E-C6CE-4B1A-AA68-46544C08140B} = {E15BDA6D-E881-4482-94BA-BE5527E917FF}
|
|
||||||
{E15BDA6D-E881-4482-94BA-BE5527E917FF} = {E9868930-4223-4D57-8F31-84E580E4B24B}
|
|
||||||
{E1F3D3F5-7820-4A62-A16A-53260375A781} = {E9868930-4223-4D57-8F31-84E580E4B24B}
|
|
||||||
{10E4E5CB-F51E-42EC-B98C-FBCE839D4624} = {E1F3D3F5-7820-4A62-A16A-53260375A781}
|
|
||||||
{B46F33B5-9702-434E-A92C-8DE5CF7164E3} = {E1F3D3F5-7820-4A62-A16A-53260375A781}
|
|
||||||
{AACDC15A-56F8-458F-9C73-DE89F60466AE} = {E1F3D3F5-7820-4A62-A16A-53260375A781}
|
|
||||||
{CE78D902-02B5-4C7B-A46A-D44BD2F4C622} = {E9868930-4223-4D57-8F31-84E580E4B24B}
|
{CE78D902-02B5-4C7B-A46A-D44BD2F4C622} = {E9868930-4223-4D57-8F31-84E580E4B24B}
|
||||||
{441EE8F0-AD8E-479B-9F68-12D157F080AF} = {CE78D902-02B5-4C7B-A46A-D44BD2F4C622}
|
{441EE8F0-AD8E-479B-9F68-12D157F080AF} = {CE78D902-02B5-4C7B-A46A-D44BD2F4C622}
|
||||||
{91CB76FC-8E4B-4B4C-B5AD-D681866A2188} = {CE78D902-02B5-4C7B-A46A-D44BD2F4C622}
|
{91CB76FC-8E4B-4B4C-B5AD-D681866A2188} = {CE78D902-02B5-4C7B-A46A-D44BD2F4C622}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Dalamud.Configuration;
|
|
||||||
using Dalamud.DiscordBot;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class DalamudConfiguration
|
|
||||||
{
|
|
||||||
public DiscordFeatureConfiguration DiscordFeatureConfig { get; set; }
|
|
||||||
|
|
||||||
public bool OptOutMbCollection { get; set; } = false;
|
|
||||||
|
|
||||||
public List<string> BadWords { get; set; }
|
|
||||||
|
|
||||||
public enum PreferredRole
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
All,
|
|
||||||
Tank,
|
|
||||||
Dps,
|
|
||||||
Healer
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<int, PreferredRole> PreferredRoleReminders { get; set; }
|
|
||||||
|
|
||||||
public string LastVersion { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, object> PluginConfigurations { get; set; }
|
|
||||||
|
|
||||||
public static DalamudConfiguration Load(string path) {
|
|
||||||
return JsonConvert.DeserializeObject<DalamudConfiguration>(File.ReadAllText(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(string path) {
|
|
||||||
File.WriteAllText(path, JsonConvert.SerializeObject(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Configuration
|
|
||||||
{
|
|
||||||
public interface IPluginConfiguration
|
|
||||||
{
|
|
||||||
int Version { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,575 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.DiscordBot;
|
|
||||||
using Dalamud.Game;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Game.ClientState;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Game.Internal;
|
|
||||||
using Dalamud.Game.Internal.Gui;
|
|
||||||
using Dalamud.Game.Network;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud {
|
|
||||||
public sealed class Dalamud : IDisposable {
|
|
||||||
private readonly string baseDirectory;
|
|
||||||
|
|
||||||
private readonly ManualResetEvent unloadSignal;
|
|
||||||
|
|
||||||
private readonly ProcessModule targetModule;
|
|
||||||
|
|
||||||
public readonly SigScanner SigScanner;
|
|
||||||
|
|
||||||
public readonly Framework Framework;
|
|
||||||
|
|
||||||
public readonly CommandManager CommandManager;
|
|
||||||
|
|
||||||
public readonly ChatHandlers ChatHandlers;
|
|
||||||
|
|
||||||
public readonly NetworkHandlers NetworkHandlers;
|
|
||||||
|
|
||||||
public readonly DiscordBotManager BotManager;
|
|
||||||
|
|
||||||
public readonly PluginManager PluginManager;
|
|
||||||
|
|
||||||
public readonly ClientState ClientState;
|
|
||||||
|
|
||||||
public readonly DalamudStartInfo StartInfo;
|
|
||||||
|
|
||||||
public readonly DalamudConfiguration Configuration;
|
|
||||||
|
|
||||||
private readonly WinSockHandlers WinSock2;
|
|
||||||
|
|
||||||
public readonly InterfaceManager InterfaceManager;
|
|
||||||
|
|
||||||
public readonly DataManager Data;
|
|
||||||
|
|
||||||
private readonly string assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
|
||||||
|
|
||||||
public Dalamud(DalamudStartInfo info) {
|
|
||||||
this.StartInfo = info;
|
|
||||||
this.Configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
|
||||||
|
|
||||||
this.baseDirectory = info.WorkingDirectory;
|
|
||||||
|
|
||||||
this.unloadSignal = new ManualResetEvent(false);
|
|
||||||
|
|
||||||
// Initialize the process information.
|
|
||||||
this.targetModule = Process.GetCurrentProcess().MainModule;
|
|
||||||
this.SigScanner = new SigScanner(this.targetModule, true);
|
|
||||||
|
|
||||||
// Initialize game subsystem
|
|
||||||
this.Framework = new Framework(this.SigScanner, this);
|
|
||||||
|
|
||||||
// Initialize managers. Basically handlers for the logic
|
|
||||||
this.CommandManager = new CommandManager(this, info.Language);
|
|
||||||
SetupCommands();
|
|
||||||
|
|
||||||
this.ChatHandlers = new ChatHandlers(this);
|
|
||||||
this.NetworkHandlers = new NetworkHandlers(this, this.Configuration.OptOutMbCollection);
|
|
||||||
|
|
||||||
this.Data = new DataManager(this.StartInfo.Language);
|
|
||||||
this.Data.Initialize();
|
|
||||||
|
|
||||||
this.ClientState = new ClientState(this, info, this.SigScanner, this.targetModule);
|
|
||||||
|
|
||||||
this.BotManager = new DiscordBotManager(this, this.Configuration.DiscordFeatureConfig);
|
|
||||||
|
|
||||||
this.PluginManager = new PluginManager(this, info.PluginDirectory);
|
|
||||||
|
|
||||||
this.WinSock2 = new WinSockHandlers();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.InterfaceManager = new InterfaceManager(this, this.SigScanner);
|
|
||||||
this.InterfaceManager.OnDraw += BuildDalamudUi;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.Information(e, "Could not init interface.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start() {
|
|
||||||
try {
|
|
||||||
this.InterfaceManager?.Enable();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.Information("Could not enable interface.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Framework.Enable();
|
|
||||||
|
|
||||||
this.BotManager.Start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.PluginManager.LoadPlugins();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
this.Framework.Gui.Chat.PrintError(
|
|
||||||
"[XIVLAUNCHER] There was an error loading additional plugins. Please check the log for more details.");
|
|
||||||
Log.Error(ex, "Plugin load failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Unload() {
|
|
||||||
this.unloadSignal.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WaitForUnload() {
|
|
||||||
this.unloadSignal.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.PluginManager.UnloadPlugins();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Framework.Gui.Chat.PrintError(
|
|
||||||
"[XIVLAUNCHER] There was an error unloading additional plugins. Please check the log for more details.");
|
|
||||||
Log.Error(ex, "Plugin unload failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.InterfaceManager.Dispose();
|
|
||||||
|
|
||||||
Framework.Dispose();
|
|
||||||
|
|
||||||
this.BotManager.Dispose();
|
|
||||||
|
|
||||||
this.unloadSignal.Dispose();
|
|
||||||
|
|
||||||
this.WinSock2.Dispose();
|
|
||||||
|
|
||||||
this.SigScanner.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Interface
|
|
||||||
|
|
||||||
private bool isImguiDrawDemoWindow = false;
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private bool isImguiDrawDevMenu = true;
|
|
||||||
#else
|
|
||||||
private bool isImguiDrawDevMenu = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private bool isImguiDrawLogWindow = false;
|
|
||||||
private bool isImguiDrawDataWindow = false;
|
|
||||||
private bool isImguiDrawPluginWindow = false;
|
|
||||||
|
|
||||||
private DalamudLogWindow logWindow;
|
|
||||||
private DalamudDataWindow dataWindow;
|
|
||||||
private PluginInstallerWindow pluginWindow;
|
|
||||||
|
|
||||||
private void BuildDalamudUi()
|
|
||||||
{
|
|
||||||
if (this.isImguiDrawDevMenu)
|
|
||||||
{
|
|
||||||
if (ImGui.BeginMainMenuBar())
|
|
||||||
{
|
|
||||||
if (ImGui.BeginMenu("Dalamud"))
|
|
||||||
{
|
|
||||||
ImGui.MenuItem("Draw Dalamud dev menu", "", ref this.isImguiDrawDevMenu);
|
|
||||||
ImGui.Separator();
|
|
||||||
if (ImGui.MenuItem("Open Log window"))
|
|
||||||
{
|
|
||||||
this.logWindow = new DalamudLogWindow();
|
|
||||||
this.isImguiDrawLogWindow = true;
|
|
||||||
}
|
|
||||||
if (ImGui.MenuItem("Open Data window"))
|
|
||||||
{
|
|
||||||
this.dataWindow = new DalamudDataWindow(this.Data);
|
|
||||||
this.isImguiDrawDataWindow = true;
|
|
||||||
}
|
|
||||||
ImGui.MenuItem("Draw ImGui demo", "", ref this.isImguiDrawDemoWindow);
|
|
||||||
ImGui.Separator();
|
|
||||||
if (ImGui.MenuItem("Unload Dalamud"))
|
|
||||||
{
|
|
||||||
Unload();
|
|
||||||
}
|
|
||||||
if (ImGui.MenuItem("Kill game"))
|
|
||||||
{
|
|
||||||
Process.GetCurrentProcess().Kill();
|
|
||||||
}
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.MenuItem(this.assemblyVersion, false);
|
|
||||||
|
|
||||||
ImGui.EndMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginMenu("Plugins"))
|
|
||||||
{
|
|
||||||
if (ImGui.MenuItem("Open Plugin installer"))
|
|
||||||
{
|
|
||||||
this.pluginWindow = new PluginInstallerWindow(this.PluginManager, this.StartInfo.PluginDirectory);
|
|
||||||
this.isImguiDrawPluginWindow = true;
|
|
||||||
}
|
|
||||||
if (ImGui.MenuItem("Print plugin info")) {
|
|
||||||
foreach (var plugin in this.PluginManager.Plugins) {
|
|
||||||
// TODO: some more here, state maybe?
|
|
||||||
Log.Information($"{plugin.Plugin.Name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ImGui.MenuItem("Reload plugins"))
|
|
||||||
{
|
|
||||||
OnPluginReloadCommand(string.Empty, string.Empty);
|
|
||||||
}
|
|
||||||
ImGui.EndMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndMainMenuBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isImguiDrawLogWindow)
|
|
||||||
{
|
|
||||||
this.isImguiDrawLogWindow = this.logWindow != null && this.logWindow.Draw();
|
|
||||||
|
|
||||||
if (this.isImguiDrawLogWindow == false)
|
|
||||||
{
|
|
||||||
this.logWindow?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isImguiDrawDataWindow)
|
|
||||||
{
|
|
||||||
this.isImguiDrawDataWindow = this.dataWindow != null && this.dataWindow.Draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isImguiDrawPluginWindow)
|
|
||||||
{
|
|
||||||
this.isImguiDrawPluginWindow = this.pluginWindow != null && this.pluginWindow.Draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isImguiDrawDemoWindow)
|
|
||||||
ImGui.ShowDemoWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private void SetupCommands() {
|
|
||||||
CommandManager.AddHandler("/xldclose", new CommandInfo(OnUnloadCommand) {
|
|
||||||
HelpMessage = "Unloads XIVLauncher in-game addon.",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xldreloadplugins", new CommandInfo(OnPluginReloadCommand) {
|
|
||||||
HelpMessage = "Reloads all plugins.",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xldsay", new CommandInfo(OnCommandDebugSay) {
|
|
||||||
HelpMessage = "Print to chat.",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlhelp", new CommandInfo(OnHelpCommand) {
|
|
||||||
HelpMessage = "Shows list of commands available."
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlmute", new CommandInfo(OnBadWordsAddCommand) {
|
|
||||||
HelpMessage = "Mute a word or sentence from appearing in chat. Usage: /xlmute <word or sentence>"
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlmutelist", new CommandInfo(OnBadWordsListCommand) {
|
|
||||||
HelpMessage = "List muted words or sentences."
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlunmute", new CommandInfo(OnBadWordsRemoveCommand) {
|
|
||||||
HelpMessage = "Unmute a word or sentence. Usage: /xlunmute <word or sentence>"
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xldstate", new CommandInfo(OnDebugPrintGameStateCommand) {
|
|
||||||
HelpMessage = "Print parsed game state",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/ll", new CommandInfo(OnLastLinkCommand) {
|
|
||||||
HelpMessage = "Open the last posted link in your default browser."
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlbotjoin", new CommandInfo(OnBotJoinCommand) {
|
|
||||||
HelpMessage = "Add the XIVLauncher discord bot you set up to your server."
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlbgmset", new CommandInfo(OnBgmSetCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = "Set the Game background music. Usage: /xlbgmset <BGM ID>"
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlitem", new CommandInfo(OnItemLinkCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = "Link an item by name. Usage: /xlitem <Item name>. For matching an item exactly, use /xlitem +<Item name>"
|
|
||||||
});
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
CommandManager.AddHandler("/xldzpi", new CommandInfo(OnDebugZoneDownInjectCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = "Inject zone down channel",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xlbonus", new CommandInfo(OnRouletteBonusNotifyCommand)
|
|
||||||
{
|
|
||||||
HelpMessage = "Notify when a roulette has a bonus you specified. Run without parameters for more info. Usage: /xlbonus <roulette name> <role name>"
|
|
||||||
});
|
|
||||||
|
|
||||||
CommandManager.AddHandler("/xldev", new CommandInfo(OnDebugDrawDevMenu) {
|
|
||||||
HelpMessage = "Draw dev menu DEBUG",
|
|
||||||
ShowInHelp = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUnloadCommand(string command, string arguments) {
|
|
||||||
Framework.Gui.Chat.Print("Unloading...");
|
|
||||||
Unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnHelpCommand(string command, string arguments) {
|
|
||||||
var showDebug = arguments.Contains("debug");
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print("Available commands:");
|
|
||||||
foreach (var cmd in CommandManager.Commands) {
|
|
||||||
if (!cmd.Value.ShowInHelp && !showDebug)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print($"{cmd.Key}: {cmd.Value.HelpMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCommandDebugSay(string command, string arguments) {
|
|
||||||
var parts = arguments.Split();
|
|
||||||
|
|
||||||
var chatType = (XivChatType) int.Parse(parts[0]);
|
|
||||||
var msg = string.Join(" ", parts.Take(1).ToArray());
|
|
||||||
|
|
||||||
Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(msg),
|
|
||||||
Name = "Xiv Launcher",
|
|
||||||
Type = chatType
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPluginReloadCommand(string command, string arguments) {
|
|
||||||
Framework.Gui.Chat.Print("Reloading...");
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.PluginManager.UnloadPlugins();
|
|
||||||
this.PluginManager.LoadPlugins();
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print("OK");
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Framework.Gui.Chat.PrintError("Reload failed.");
|
|
||||||
Log.Error(ex, "Plugin reload failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBadWordsAddCommand(string command, string arguments) {
|
|
||||||
if (this.Configuration.BadWords == null)
|
|
||||||
this.Configuration.BadWords = new List<string>();
|
|
||||||
|
|
||||||
this.Configuration.BadWords.Add(arguments);
|
|
||||||
|
|
||||||
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print($"Muted \"{arguments}\".");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBadWordsListCommand(string command, string arguments) {
|
|
||||||
if (this.Configuration.BadWords == null)
|
|
||||||
this.Configuration.BadWords = new List<string>();
|
|
||||||
|
|
||||||
if (this.Configuration.BadWords.Count == 0) {
|
|
||||||
Framework.Gui.Chat.Print("No muted words or sentences.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
||||||
|
|
||||||
foreach (var word in this.Configuration.BadWords) Framework.Gui.Chat.Print($"\"{word}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBadWordsRemoveCommand(string command, string arguments) {
|
|
||||||
if (this.Configuration.BadWords == null)
|
|
||||||
this.Configuration.BadWords = new List<string>();
|
|
||||||
|
|
||||||
this.Configuration.BadWords.RemoveAll(x => x == arguments);
|
|
||||||
|
|
||||||
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print($"Unmuted \"{arguments}\".");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLastLinkCommand(string command, string arguments) {
|
|
||||||
if (string.IsNullOrEmpty(ChatHandlers.LastLink)) {
|
|
||||||
Framework.Gui.Chat.Print("No last link...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print("Opening " + ChatHandlers.LastLink);
|
|
||||||
Process.Start(ChatHandlers.LastLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDebugPrintGameStateCommand(string command, string arguments) {
|
|
||||||
Framework.Gui.Chat.Print(this.ClientState.Actors.Length + " entries");
|
|
||||||
Framework.Gui.Chat.Print(this.ClientState.LocalPlayer.Name);
|
|
||||||
Framework.Gui.Chat.Print(this.ClientState.LocalPlayer.CurrentWorld.Name);
|
|
||||||
Framework.Gui.Chat.Print(this.ClientState.LocalPlayer.HomeWorld.Name);
|
|
||||||
Framework.Gui.Chat.Print(this.ClientState.LocalContentId.ToString("X"));
|
|
||||||
Framework.Gui.Chat.Print(Framework.Gui.Chat.LastLinkedItemId.ToString());
|
|
||||||
|
|
||||||
for (var i = 0; i < this.ClientState.Actors.Length; i++) {
|
|
||||||
var actor = this.ClientState.Actors[i];
|
|
||||||
|
|
||||||
Log.Debug(actor.Name);
|
|
||||||
Framework.Gui.Chat.Print(
|
|
||||||
$"{i} - {actor.Name} - {actor.Position.X} {actor.Position.Y} {actor.Position.Z}");
|
|
||||||
|
|
||||||
if (actor is Npc npc)
|
|
||||||
Framework.Gui.Chat.Print($"DataId: {npc.DataId}");
|
|
||||||
|
|
||||||
if (actor is Chara chara)
|
|
||||||
Framework.Gui.Chat.Print(
|
|
||||||
$"Level: {chara.Level} ClassJob: {chara.ClassJob.Name} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void OnBotJoinCommand(string command, string arguments) {
|
|
||||||
if (this.BotManager != null && this.BotManager.IsConnected)
|
|
||||||
Process.Start(
|
|
||||||
$"https://discordapp.com/oauth2/authorize?client_id={this.BotManager.UserId}&scope=bot&permissions=117760");
|
|
||||||
else
|
|
||||||
Framework.Gui.Chat.Print(
|
|
||||||
"The XIVLauncher discord bot was not set up correctly or could not connect to discord. Please check the settings and the FAQ.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBgmSetCommand(string command, string arguments)
|
|
||||||
{
|
|
||||||
Framework.Gui.SetBgm(ushort.Parse(arguments));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnItemLinkCommand(string command, string arguments) {
|
|
||||||
var exactSearch = false;
|
|
||||||
if (arguments.StartsWith("+"))
|
|
||||||
{
|
|
||||||
exactSearch = true;
|
|
||||||
arguments = arguments.Substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(async () => {
|
|
||||||
try {
|
|
||||||
dynamic results = await XivApi.Search(arguments, "Item", 1, exactSearch);
|
|
||||||
var itemId = (short) results.Results[0].ID;
|
|
||||||
var itemName = (string)results.Results[0].Name;
|
|
||||||
|
|
||||||
var hexData = new byte[] {
|
|
||||||
0x02, 0x13, 0x06, 0xFE, 0xFF, 0xF3, 0xF3, 0xF3, 0x03, 0x02, 0x27, 0x07, 0x03, 0xF2, 0x3A, 0x2F,
|
|
||||||
0x02, 0x01, 0x03, 0x02, 0x13, 0x06, 0xFE, 0xFF, 0xFF, 0x7B, 0x1A, 0x03, 0xEE, 0x82, 0xBB, 0x02,
|
|
||||||
0x13, 0x02, 0xEC, 0x03
|
|
||||||
};
|
|
||||||
|
|
||||||
var endTag = new byte[] {
|
|
||||||
0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03, 0x02, 0x13, 0x02, 0xEC, 0x03
|
|
||||||
};
|
|
||||||
|
|
||||||
BitConverter.GetBytes(itemId).Reverse().ToArray().CopyTo(hexData, 14);
|
|
||||||
|
|
||||||
hexData = hexData.Concat(Encoding.UTF8.GetBytes(itemName)).Concat(endTag).ToArray();
|
|
||||||
|
|
||||||
Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = hexData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Framework.Gui.Chat.PrintError("Could not find item.");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private void OnDebugZoneDownInjectCommand(string command, string arguments) {
|
|
||||||
var data = File.ReadAllBytes(arguments);
|
|
||||||
|
|
||||||
Framework.Network.InjectZoneProtoPacket(data);
|
|
||||||
Framework.Gui.Chat.Print($"{arguments} OK with {data.Length} bytes");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private void OnRouletteBonusNotifyCommand(string command, string arguments)
|
|
||||||
{
|
|
||||||
if (this.Configuration.DiscordFeatureConfig.CfPreferredRoleChannel == null)
|
|
||||||
Framework.Gui.Chat.PrintError("You have not set up a discord channel for these notifications - you will only receive them in chat. To do this, please use the XIVLauncher in-game settings.");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(arguments))
|
|
||||||
goto InvalidArgs;
|
|
||||||
|
|
||||||
var argParts = arguments.Split();
|
|
||||||
if (argParts.Length < 2)
|
|
||||||
goto InvalidArgs;
|
|
||||||
|
|
||||||
|
|
||||||
if (this.Configuration.PreferredRoleReminders == null)
|
|
||||||
this.Configuration.PreferredRoleReminders = new Dictionary<int, DalamudConfiguration.PreferredRole>();
|
|
||||||
|
|
||||||
var rouletteIndex = RouletteSlugToKey(argParts[0]);
|
|
||||||
|
|
||||||
if (rouletteIndex == 0)
|
|
||||||
goto InvalidArgs;
|
|
||||||
|
|
||||||
if (!Enum.TryParse(argParts[1].First().ToString().ToUpper() + argParts[1].ToLower().Substring(1), out DalamudConfiguration.PreferredRole role))
|
|
||||||
goto InvalidArgs;
|
|
||||||
|
|
||||||
if (this.Configuration.PreferredRoleReminders.ContainsKey(rouletteIndex))
|
|
||||||
this.Configuration.PreferredRoleReminders[rouletteIndex] = role;
|
|
||||||
else
|
|
||||||
this.Configuration.PreferredRoleReminders.Add(rouletteIndex, role);
|
|
||||||
|
|
||||||
Framework.Gui.Chat.Print($"Set bonus notifications for {argParts[0]}({rouletteIndex}) to {role}");
|
|
||||||
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
InvalidArgs:
|
|
||||||
Framework.Gui.Chat.PrintError("Unrecognized arguments.");
|
|
||||||
Framework.Gui.Chat.Print("Possible values for roulette: leveling, 506070, msq, guildhests, expert, trials, mentor, alliance, normal\n" +
|
|
||||||
"Possible values for role: tank, dps, healer, all, none/reset");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDebugDrawDevMenu(string command, string arguments) {
|
|
||||||
this.isImguiDrawDevMenu = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int RouletteSlugToKey(string slug) => slug.ToLower() switch {
|
|
||||||
"leveling" => 1,
|
|
||||||
"506070" => 2,
|
|
||||||
"msq" => 3,
|
|
||||||
"guildhests" => 4,
|
|
||||||
"expert" => 5,
|
|
||||||
"trials" => 6,
|
|
||||||
"mentor" => 8,
|
|
||||||
"alliance" => 9,
|
|
||||||
"normal" => 10,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
|
|
||||||
private DalamudConfiguration.PreferredRole RoleNameToPreferredRole(string name) => name.ToLower() switch
|
|
||||||
{
|
|
||||||
"Tank" => DalamudConfiguration.PreferredRole.Tank,
|
|
||||||
"Healer" => DalamudConfiguration.PreferredRole.Healer,
|
|
||||||
"Dps" => DalamudConfiguration.PreferredRole.Dps,
|
|
||||||
"All" => DalamudConfiguration.PreferredRole.All,
|
|
||||||
_ => DalamudConfiguration.PreferredRole.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,15 +5,16 @@
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<nullable>enable</nullable>
|
<nullable>enable</nullable>
|
||||||
|
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources> <!-- Required by CoreHook to build -->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Lumina" Version="1.0.0-preview7" />
|
<PackageReference Include="Lumina" Version="1.1.1" />
|
||||||
<PackageReference Include="Serilog" Version="2.6.0" />
|
<PackageReference Include="Serilog" Version="2.6.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.1.0" />
|
<PackageReference Include="Serilog.Sinks.Async" Version="1.1.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||||
|
<PackageReference Include="System.Resources.Extensions" Version="4.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\lib\CoreHook\src\CoreHook.CoreLoad\CoreHook.CoreLoad.csproj" />
|
<ProjectReference Include="..\lib\CoreHook\src\CoreHook\CoreHook.csproj" /> <!-- Required by CoreHook to build -->
|
||||||
<ProjectReference Include="..\lib\CoreHook\src\CoreHook\CoreHook.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using Dalamud.DiscordBot;
|
|
||||||
|
|
||||||
namespace Dalamud {
|
|
||||||
[Serializable]
|
|
||||||
public sealed class DalamudStartInfo
|
|
||||||
{
|
|
||||||
public string WorkingDirectory;
|
|
||||||
public string ConfigurationPath;
|
|
||||||
|
|
||||||
public string PluginDirectory;
|
|
||||||
public string DefaultPluginDirectory;
|
|
||||||
public ClientLanguage Language;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ClientLanguage
|
|
||||||
{
|
|
||||||
Japanese,
|
|
||||||
English,
|
|
||||||
German,
|
|
||||||
French
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Lumina.Data;
|
|
||||||
using Lumina.Excel;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
|
||||||
using LuminaOptions = Lumina.LuminaOptions;
|
|
||||||
using ParsedFilePath = Lumina.ParsedFilePath;
|
|
||||||
|
|
||||||
namespace Dalamud.Data
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
|
|
||||||
/// </summary>
|
|
||||||
public class DataManager {
|
|
||||||
private const string DataBaseUrl = "https://goaaats.github.io/ffxiv/tools/launcher/addons/Hooks/Data/";
|
|
||||||
|
|
||||||
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
|
||||||
/// </summary>
|
|
||||||
public ExcelModule Excel => this.gameData?.Excel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether Game Data is ready to be read.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsDataReady { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="Lumina"/> object which gives access to any excel/game data.
|
|
||||||
/// </summary>
|
|
||||||
private Lumina.Lumina gameData;
|
|
||||||
|
|
||||||
private ClientLanguage language;
|
|
||||||
|
|
||||||
public DataManager(ClientLanguage language)
|
|
||||||
{
|
|
||||||
// Set up default values so plugins do not null-reference when data is being loaded.
|
|
||||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
|
|
||||||
|
|
||||||
this.language = language;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Initialize()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Log.Verbose("Starting data download...");
|
|
||||||
|
|
||||||
using var client = new HttpClient()
|
|
||||||
{
|
|
||||||
BaseAddress = new Uri(DataBaseUrl)
|
|
||||||
};
|
|
||||||
|
|
||||||
var opCodeDict =
|
|
||||||
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
|
|
||||||
await client.GetStringAsync(DataBaseUrl + "serveropcode.json"));
|
|
||||||
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(opCodeDict);
|
|
||||||
|
|
||||||
Log.Verbose("Loaded {0} ServerOpCodes.", opCodeDict.Count);
|
|
||||||
|
|
||||||
|
|
||||||
var luminaOptions = new LuminaOptions
|
|
||||||
{
|
|
||||||
CacheFileResources = true
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (this.language)
|
|
||||||
{
|
|
||||||
case ClientLanguage.Japanese:
|
|
||||||
luminaOptions.DefaultExcelLanguage = Language.Japanese;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.English:
|
|
||||||
luminaOptions.DefaultExcelLanguage = Language.English;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.German:
|
|
||||||
luminaOptions.DefaultExcelLanguage = Language.German;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.French:
|
|
||||||
luminaOptions.DefaultExcelLanguage = Language.French;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(this.language), "Unknown Language: " + this.language);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameData = new Lumina.Lumina(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "sqpack"), luminaOptions);
|
|
||||||
|
|
||||||
Log.Information("Lumina is ready: {0}", gameData.DataPath);
|
|
||||||
|
|
||||||
IsDataReady = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not download data.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Lumina Wrappers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
|
||||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
|
||||||
public ExcelSheet<T> GetExcelSheet<T>() where T : IExcelRow
|
|
||||||
{
|
|
||||||
return this.Excel.GetSheet<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="FileResource"/> with the given path.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path inside of the game files.</param>
|
|
||||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
|
||||||
public FileResource GetFile(string path)
|
|
||||||
{
|
|
||||||
return this.GetFile<FileResource>(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of resource</typeparam>
|
|
||||||
/// <param name="path">The path inside of the game files.</param>
|
|
||||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
|
||||||
public T GetFile<T>(string path) where T : FileResource
|
|
||||||
{
|
|
||||||
ParsedFilePath filePath = Lumina.Lumina.ParseFilePath(path);
|
|
||||||
if (filePath == null)
|
|
||||||
return default(T);
|
|
||||||
Repository repository;
|
|
||||||
return this.gameData.Repositories.TryGetValue(filePath.Repository, out repository) ? repository.GetFile<T>(filePath.Category, filePath) : default(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,610 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Lumina.Excel;
|
|
||||||
|
|
||||||
namespace Dalamud.Data.TransientSheet
|
|
||||||
{
|
|
||||||
[SheetName("ContentFinderCondition")]
|
|
||||||
public class ContentFinderCondition : IExcelRow
|
|
||||||
{
|
|
||||||
// column defs from Thu, 13 Feb 2020 20:46:12 GMT
|
|
||||||
|
|
||||||
/* offset: 002c col: 0
|
|
||||||
* name: ShortCode
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0048 col: 1
|
|
||||||
* name: TerritoryType
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0054 col: 2
|
|
||||||
* name: ContentLinkType
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 004a col: 3
|
|
||||||
* name: Content
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 4
|
|
||||||
* name: PvP
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0055 col: 5
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0030 col: 6
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0034 col: 7
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0056 col: 8
|
|
||||||
* name: AcceptClassJobCategory
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0057 col: 9
|
|
||||||
* name: ContentMemberType
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0058 col: 10
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0059 col: 11
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005a col: 12
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0038 col: 13
|
|
||||||
* name: UnlockQuest
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 004c col: 14
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005b col: 15
|
|
||||||
* name: ClassJobLevel{Required}
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005c col: 16
|
|
||||||
* name: ClassJobLevel{Sync}
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 004e col: 17
|
|
||||||
* name: ItemLevel{Required}
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0050 col: 18
|
|
||||||
* name: ItemLevel{Sync}
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 19
|
|
||||||
* name: AllowUndersized
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 20
|
|
||||||
* name: AllowReplacement
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 21
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 22
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 23
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005d col: 24
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 25
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0061 col: 26
|
|
||||||
* name: HighEndDuty
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 27
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 28
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 29
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 30
|
|
||||||
* name: DutyRecorderAllowed
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 31
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 32
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0062 col: 33
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0000 col: 34
|
|
||||||
* name: Name
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005e col: 35
|
|
||||||
* name: ContentType
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 005f col: 36
|
|
||||||
* name: TransientKey
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 003c col: 37
|
|
||||||
* name: Transient
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0052 col: 38
|
|
||||||
* name: SortKey
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0040 col: 39
|
|
||||||
* name: Image
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0044 col: 40
|
|
||||||
* name: Icon
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0060 col: 41
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0004 col: 42
|
|
||||||
* name: LevelingRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0005 col: 43
|
|
||||||
* name: Level50/60Roulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0006 col: 44
|
|
||||||
* name: MSQRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0007 col: 45
|
|
||||||
* name: GuildHestRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0008 col: 46
|
|
||||||
* name: ExpertRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0009 col: 47
|
|
||||||
* name: TrialRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000a col: 48
|
|
||||||
* name: DailyFrontlineChallenge
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000b col: 49
|
|
||||||
* name: Level70Roulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000c col: 50
|
|
||||||
* name: MentorRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000d col: 51
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000e col: 52
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 000f col: 53
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0010 col: 54
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0011 col: 55
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0012 col: 56
|
|
||||||
* name: AllianceRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0013 col: 57
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0014 col: 58
|
|
||||||
* name: NormalRaidRoulette
|
|
||||||
* type:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0015 col: 59
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0016 col: 60
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0017 col: 61
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0018 col: 62
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0019 col: 63
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001a col: 64
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001b col: 65
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001c col: 66
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001d col: 67
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001e col: 68
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 001f col: 69
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0020 col: 70
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0021 col: 71
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0022 col: 72
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0023 col: 73
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0024 col: 74
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0025 col: 75
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0026 col: 76
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0027 col: 77
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0028 col: 78
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 0029 col: 79
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* offset: 002a col: 80
|
|
||||||
* no SaintCoinach definition found
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// col: 34 offset: 0000
|
|
||||||
public string Name;
|
|
||||||
|
|
||||||
// col: 42 offset: 0004
|
|
||||||
public bool LevelingRoulette;
|
|
||||||
|
|
||||||
// col: 43 offset: 0005
|
|
||||||
public bool Level5060Roulette;
|
|
||||||
|
|
||||||
// col: 44 offset: 0006
|
|
||||||
public bool MSQRoulette;
|
|
||||||
|
|
||||||
// col: 45 offset: 0007
|
|
||||||
public bool GuildHestRoulette;
|
|
||||||
|
|
||||||
// col: 46 offset: 0008
|
|
||||||
public bool ExpertRoulette;
|
|
||||||
|
|
||||||
// col: 47 offset: 0009
|
|
||||||
public bool TrialRoulette;
|
|
||||||
|
|
||||||
// col: 48 offset: 000a
|
|
||||||
public bool DailyFrontlineChallenge;
|
|
||||||
|
|
||||||
// col: 49 offset: 000b
|
|
||||||
public bool Level70Roulette;
|
|
||||||
|
|
||||||
// col: 50 offset: 000c
|
|
||||||
public bool MentorRoulette;
|
|
||||||
|
|
||||||
// col: 51 offset: 000d
|
|
||||||
public bool unknownd;
|
|
||||||
|
|
||||||
// col: 52 offset: 000e
|
|
||||||
public bool unknowne;
|
|
||||||
|
|
||||||
// col: 53 offset: 000f
|
|
||||||
public bool unknownf;
|
|
||||||
|
|
||||||
// col: 54 offset: 0010
|
|
||||||
public bool unknown10;
|
|
||||||
|
|
||||||
// col: 55 offset: 0011
|
|
||||||
public bool unknown11;
|
|
||||||
|
|
||||||
// col: 56 offset: 0012
|
|
||||||
public bool AllianceRoulette;
|
|
||||||
|
|
||||||
// col: 57 offset: 0013
|
|
||||||
public bool unknown13;
|
|
||||||
|
|
||||||
// col: 58 offset: 0014
|
|
||||||
public bool NormalRaidRoulette;
|
|
||||||
|
|
||||||
// col: 59 offset: 0015
|
|
||||||
public bool unknown15;
|
|
||||||
|
|
||||||
// col: 60 offset: 0016
|
|
||||||
public bool unknown16;
|
|
||||||
|
|
||||||
// col: 61 offset: 0017
|
|
||||||
public bool unknown17;
|
|
||||||
|
|
||||||
// col: 62 offset: 0018
|
|
||||||
public bool unknown18;
|
|
||||||
|
|
||||||
// col: 63 offset: 0019
|
|
||||||
public bool unknown19;
|
|
||||||
|
|
||||||
// col: 64 offset: 001a
|
|
||||||
public bool unknown1a;
|
|
||||||
|
|
||||||
// col: 65 offset: 001b
|
|
||||||
public bool unknown1b;
|
|
||||||
|
|
||||||
// col: 66 offset: 001c
|
|
||||||
public bool unknown1c;
|
|
||||||
|
|
||||||
// col: 67 offset: 001d
|
|
||||||
public bool unknown1d;
|
|
||||||
|
|
||||||
// col: 68 offset: 001e
|
|
||||||
public bool unknown1e;
|
|
||||||
|
|
||||||
// col: 69 offset: 001f
|
|
||||||
public bool unknown1f;
|
|
||||||
|
|
||||||
// col: 70 offset: 0020
|
|
||||||
public bool unknown20;
|
|
||||||
|
|
||||||
// col: 71 offset: 0021
|
|
||||||
public bool unknown21;
|
|
||||||
|
|
||||||
// col: 72 offset: 0022
|
|
||||||
public bool unknown22;
|
|
||||||
|
|
||||||
// col: 73 offset: 0023
|
|
||||||
public bool unknown23;
|
|
||||||
|
|
||||||
// col: 74 offset: 0024
|
|
||||||
public bool unknown24;
|
|
||||||
|
|
||||||
// col: 75 offset: 0025
|
|
||||||
public bool unknown25;
|
|
||||||
|
|
||||||
// col: 76 offset: 0026
|
|
||||||
public bool unknown26;
|
|
||||||
|
|
||||||
// col: 77 offset: 0027
|
|
||||||
public bool unknown27;
|
|
||||||
|
|
||||||
// col: 78 offset: 0028
|
|
||||||
public bool unknown28;
|
|
||||||
|
|
||||||
// col: 79 offset: 0029
|
|
||||||
public bool unknown29;
|
|
||||||
|
|
||||||
// col: 80 offset: 002a
|
|
||||||
public bool unknown2a;
|
|
||||||
|
|
||||||
// col: 00 offset: 002c
|
|
||||||
public string ShortCode;
|
|
||||||
|
|
||||||
// col: 06 offset: 0030
|
|
||||||
public uint unknown30;
|
|
||||||
|
|
||||||
// col: 07 offset: 0034
|
|
||||||
public uint unknown34;
|
|
||||||
|
|
||||||
// col: 13 offset: 0038
|
|
||||||
public uint UnlockQuest;
|
|
||||||
|
|
||||||
// col: 37 offset: 003c
|
|
||||||
public uint Transient;
|
|
||||||
|
|
||||||
// col: 39 offset: 0040
|
|
||||||
public uint Image;
|
|
||||||
|
|
||||||
// col: 40 offset: 0044
|
|
||||||
public uint Icon;
|
|
||||||
|
|
||||||
// col: 01 offset: 0048
|
|
||||||
public ushort TerritoryType;
|
|
||||||
|
|
||||||
// col: 03 offset: 004a
|
|
||||||
public ushort Content;
|
|
||||||
|
|
||||||
// col: 14 offset: 004c
|
|
||||||
public ushort unknown4c;
|
|
||||||
|
|
||||||
// col: 17 offset: 004e
|
|
||||||
public ushort ItemLevelRequired;
|
|
||||||
|
|
||||||
// col: 18 offset: 0050
|
|
||||||
public ushort ItemLevelSync;
|
|
||||||
|
|
||||||
// col: 38 offset: 0052
|
|
||||||
public ushort SortKey;
|
|
||||||
|
|
||||||
// col: 02 offset: 0054
|
|
||||||
public byte ContentLinkType;
|
|
||||||
|
|
||||||
// col: 05 offset: 0055
|
|
||||||
public byte unknown55;
|
|
||||||
|
|
||||||
// col: 08 offset: 0056
|
|
||||||
public byte AcceptClassJobCategory;
|
|
||||||
|
|
||||||
// col: 09 offset: 0057
|
|
||||||
public byte ContentMemberType;
|
|
||||||
|
|
||||||
// col: 10 offset: 0058
|
|
||||||
public byte unknown58;
|
|
||||||
|
|
||||||
// col: 11 offset: 0059
|
|
||||||
public byte unknown59;
|
|
||||||
|
|
||||||
// col: 12 offset: 005a
|
|
||||||
public byte unknown5a;
|
|
||||||
|
|
||||||
// col: 15 offset: 005b
|
|
||||||
public byte ClassJobLevelRequired;
|
|
||||||
|
|
||||||
// col: 16 offset: 005c
|
|
||||||
public byte ClassJobLevelSync;
|
|
||||||
|
|
||||||
// col: 24 offset: 005d
|
|
||||||
public byte unknown5d;
|
|
||||||
|
|
||||||
// col: 35 offset: 005e
|
|
||||||
public byte ContentType;
|
|
||||||
|
|
||||||
// col: 36 offset: 005f
|
|
||||||
public byte TransientKey;
|
|
||||||
|
|
||||||
// col: 41 offset: 0060
|
|
||||||
public sbyte unknown60;
|
|
||||||
|
|
||||||
// col: 04 offset: 0061
|
|
||||||
private byte packed61;
|
|
||||||
public bool PvP => (packed61 & 0x1) == 0x1;
|
|
||||||
public bool AllowUndersized => (packed61 & 0x2) == 0x2;
|
|
||||||
public bool AllowReplacement => (packed61 & 0x4) == 0x4;
|
|
||||||
public bool unknown61_8 => (packed61 & 0x8) == 0x8;
|
|
||||||
public bool unknown61_10 => (packed61 & 0x10) == 0x10;
|
|
||||||
public bool unknown61_20 => (packed61 & 0x20) == 0x20;
|
|
||||||
public bool unknown61_40 => (packed61 & 0x40) == 0x40;
|
|
||||||
public bool HighEndDuty => (packed61 & 0x80) == 0x80;
|
|
||||||
|
|
||||||
// col: 27 offset: 0062
|
|
||||||
private byte packed62;
|
|
||||||
public bool unknown62_1 => (packed62 & 0x1) == 0x1;
|
|
||||||
public bool unknown62_2 => (packed62 & 0x2) == 0x2;
|
|
||||||
public bool unknown62_4 => (packed62 & 0x4) == 0x4;
|
|
||||||
public bool DutyRecorderAllowed => (packed62 & 0x8) == 0x8;
|
|
||||||
public bool unknown62_10 => (packed62 & 0x10) == 0x10;
|
|
||||||
public bool unknown62_20 => (packed62 & 0x20) == 0x20;
|
|
||||||
public bool unknown62_40 => (packed62 & 0x40) == 0x40;
|
|
||||||
|
|
||||||
|
|
||||||
public int RowId { get; set; }
|
|
||||||
public int SubRowId { get; set; }
|
|
||||||
|
|
||||||
public void PopulateData(RowParser parser)
|
|
||||||
{
|
|
||||||
RowId = parser.Row;
|
|
||||||
SubRowId = parser.SubRow;
|
|
||||||
|
|
||||||
// col: 34 offset: 0000
|
|
||||||
Name = parser.ReadOffset<string>(0x0);
|
|
||||||
|
|
||||||
// col: 39 offset: 0040
|
|
||||||
Image = parser.ReadOffset<uint>(0x40);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,317 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Ports;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data.TransientSheet;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Discord;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.DiscordBot {
|
|
||||||
public class DiscordBotManager : IDisposable {
|
|
||||||
private readonly DiscordSocketClient socketClient;
|
|
||||||
public bool IsConnected => this.socketClient.ConnectionState == ConnectionState.Connected && this.isReady;
|
|
||||||
public ulong UserId => this.socketClient.CurrentUser.Id;
|
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
private readonly DiscordFeatureConfiguration config;
|
|
||||||
|
|
||||||
private bool isReady;
|
|
||||||
|
|
||||||
private readonly List<SocketMessage> recentMessages = new List<SocketMessage>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The FFXIV payload sequence to represent the name/world separator
|
|
||||||
/// </summary>
|
|
||||||
private readonly string worldIcon = Encoding.UTF8.GetString(new byte[] {
|
|
||||||
0x02, 0x12, 0x02, 0x59, 0x03
|
|
||||||
});
|
|
||||||
|
|
||||||
public DiscordBotManager(Dalamud dalamud, DiscordFeatureConfiguration config) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
this.config = config;
|
|
||||||
config.OwnerUserId = 123830058426040321;
|
|
||||||
|
|
||||||
this.socketClient = new DiscordSocketClient();
|
|
||||||
this.socketClient.Ready += SocketClientOnReady;
|
|
||||||
this.dalamud.NetworkHandlers.ProcessCfPop += ProcessCfPop;
|
|
||||||
}
|
|
||||||
|
|
||||||
private XivChatType GetChatTypeBySlug(string slug) {
|
|
||||||
var selectedType = XivChatType.None;
|
|
||||||
foreach (var chatType in Enum.GetValues(typeof(XivChatType)).Cast<XivChatType>()) {
|
|
||||||
var details = chatType.GetDetails();
|
|
||||||
|
|
||||||
if (details == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (slug == details.Slug)
|
|
||||||
selectedType = chatType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start() {
|
|
||||||
if (string.IsNullOrEmpty(this.config.Token)) {
|
|
||||||
Log.Error("Discord token is null or empty.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.socketClient.LoginAsync(TokenType.Bot, this.config.Token).GetAwaiter().GetResult();
|
|
||||||
this.socketClient.StartAsync().GetAwaiter().GetResult();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Discord bot login failed.");
|
|
||||||
this.dalamud.Framework.Gui.Chat.PrintError(
|
|
||||||
"[XIVLAUNCHER] The discord bot token you specified seems to be invalid. Please check the guide linked on the settings page for more details.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task SocketClientOnReady() {
|
|
||||||
Log.Information("Discord bot connected as " + this.socketClient.CurrentUser);
|
|
||||||
this.isReady = true;
|
|
||||||
|
|
||||||
this.socketClient.SetGameAsync("FINAL FANTASY XIV").GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessCfPop(ContentFinderCondition cfc) {
|
|
||||||
if (!this.IsConnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var contentName = cfc.Name;
|
|
||||||
|
|
||||||
if (this.config.CfNotificationChannel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var channel = await GetChannel(this.config.CfNotificationChannel);
|
|
||||||
|
|
||||||
var iconFolder = (cfc.Image / 1000) * 1000;
|
|
||||||
|
|
||||||
var embedBuilder = new EmbedBuilder {
|
|
||||||
Title = "Duty is ready: " + contentName,
|
|
||||||
Timestamp = DateTimeOffset.Now,
|
|
||||||
Color = new Color(0x297c00),
|
|
||||||
ImageUrl = "https://xivapi.com" + $"/i/{iconFolder}/{cfc.Image}.png"
|
|
||||||
};
|
|
||||||
|
|
||||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessCfPreferredRoleChange(string rouletteName, string prevRoleName, string currentRoleName)
|
|
||||||
{
|
|
||||||
if (this.config.CfPreferredRoleChannel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var channel = await GetChannel(this.config.CfPreferredRoleChannel);
|
|
||||||
|
|
||||||
var world = string.Empty;
|
|
||||||
|
|
||||||
if (this.dalamud.ClientState.Actors.Length > 0)
|
|
||||||
world = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Name;
|
|
||||||
|
|
||||||
var embedBuilder = new EmbedBuilder
|
|
||||||
{
|
|
||||||
Title = "Roulette bonus changed: " + rouletteName,
|
|
||||||
Description = $"From {prevRoleName} to {currentRoleName}",
|
|
||||||
Footer = new EmbedFooterBuilder {
|
|
||||||
Text = $"On {world} | XIVLauncher"
|
|
||||||
},
|
|
||||||
Timestamp = DateTimeOffset.Now,
|
|
||||||
Color = new Color(0xf5aa42),
|
|
||||||
};
|
|
||||||
|
|
||||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessRetainerSale(int itemId, int amount, bool isHq) {
|
|
||||||
if (this.config.RetainerNotificationChannel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var channel = await GetChannel(this.config.RetainerNotificationChannel);
|
|
||||||
|
|
||||||
dynamic item = XivApi.GetItem(itemId).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
var character = this.dalamud.ClientState.LocalPlayer;
|
|
||||||
var characterInfo = await GetCharacterInfo(character.Name, character.HomeWorld.Name);
|
|
||||||
|
|
||||||
var embedBuilder = new EmbedBuilder {
|
|
||||||
Title = (isHq ? "<:hq:593406013651156994> " : "") + item.Name,
|
|
||||||
Url = "https://www.garlandtools.org/db/#item/" + itemId,
|
|
||||||
Description = "Sold " + amount,
|
|
||||||
Timestamp = DateTimeOffset.Now,
|
|
||||||
Color = new Color(0xd89b0d),
|
|
||||||
ThumbnailUrl = "https://xivapi.com" + item.Icon,
|
|
||||||
Footer = new EmbedFooterBuilder {
|
|
||||||
Text = $"XIVLauncher | {character.Name}",
|
|
||||||
IconUrl = characterInfo.AvatarUrl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessChatMessage(XivChatType type, StdString message, StdString sender) {
|
|
||||||
// Special case for outgoing tells, these should be sent under Incoming tells
|
|
||||||
var wasOutgoingTell = false;
|
|
||||||
if (type == XivChatType.TellOutgoing) {
|
|
||||||
type = XivChatType.TellIncoming;
|
|
||||||
wasOutgoingTell = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var chatTypeConfigs =
|
|
||||||
this.config.ChatTypeConfigurations.Where(typeConfig => typeConfig.ChatType == type);
|
|
||||||
|
|
||||||
if (!chatTypeConfigs.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var chatTypeDetail = type.GetDetails();
|
|
||||||
var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult());
|
|
||||||
|
|
||||||
|
|
||||||
var parsedSender = SeString.Parse(sender.RawData);
|
|
||||||
var playerLink = parsedSender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload;
|
|
||||||
|
|
||||||
string senderName;
|
|
||||||
string senderWorld;
|
|
||||||
|
|
||||||
if (playerLink == null) {
|
|
||||||
// chat messages from the local player do not include a player link, and are just the raw name
|
|
||||||
// but we should still track other instances to know if this is ever an issue otherwise
|
|
||||||
if (parsedSender.TextValue != this.dalamud.ClientState.LocalPlayer.Name)
|
|
||||||
{
|
|
||||||
Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.RawData));
|
|
||||||
}
|
|
||||||
|
|
||||||
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : parsedSender.TextValue;
|
|
||||||
senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.Name;
|
|
||||||
} else {
|
|
||||||
playerLink.Resolve();
|
|
||||||
|
|
||||||
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : playerLink.PlayerName;
|
|
||||||
senderWorld = playerLink.ServerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawMessage = SeString.Parse(message.RawData).TextValue;
|
|
||||||
|
|
||||||
var avatarUrl = string.Empty;
|
|
||||||
var lodestoneId = string.Empty;
|
|
||||||
|
|
||||||
if (!this.config.DisableEmbeds) {
|
|
||||||
var searchResult = await GetCharacterInfo(senderName, senderWorld);
|
|
||||||
|
|
||||||
lodestoneId = searchResult.LodestoneId;
|
|
||||||
avatarUrl = searchResult.AvatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Sleep(this.config.ChatDelayMs);
|
|
||||||
|
|
||||||
var name = wasOutgoingTell
|
|
||||||
? "You"
|
|
||||||
: senderName + (string.IsNullOrEmpty(senderWorld) || string.IsNullOrEmpty(senderName)
|
|
||||||
? ""
|
|
||||||
: $" on {senderWorld}");
|
|
||||||
|
|
||||||
for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) {
|
|
||||||
if (!this.config.DisableEmbeds) {
|
|
||||||
var embedBuilder = new EmbedBuilder
|
|
||||||
{
|
|
||||||
Author = new EmbedAuthorBuilder
|
|
||||||
{
|
|
||||||
IconUrl = avatarUrl,
|
|
||||||
Name = name,
|
|
||||||
Url = !string.IsNullOrEmpty(lodestoneId) ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null
|
|
||||||
},
|
|
||||||
Description = rawMessage,
|
|
||||||
Timestamp = DateTimeOffset.Now,
|
|
||||||
Footer = new EmbedFooterBuilder { Text = type.GetDetails().FancyName },
|
|
||||||
Color = new Color((uint)(chatTypeConfigs.ElementAt(chatTypeIndex).Color & 0xFFFFFF))
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.config.CheckForDuplicateMessages)
|
|
||||||
{
|
|
||||||
var recentMsg = this.recentMessages.FirstOrDefault(
|
|
||||||
msg => msg.Embeds.FirstOrDefault(
|
|
||||||
embed => embed.Description == embedBuilder.Description &&
|
|
||||||
embed.Author.HasValue &&
|
|
||||||
embed.Author.Value.Name == embedBuilder.Author.Name &&
|
|
||||||
embed.Timestamp.HasValue &&
|
|
||||||
Math.Abs(
|
|
||||||
(embed.Timestamp.Value.ToUniversalTime().Date -
|
|
||||||
embedBuilder
|
|
||||||
.Timestamp.Value.ToUniversalTime().Date)
|
|
||||||
.Milliseconds) < 15000)
|
|
||||||
!= null);
|
|
||||||
|
|
||||||
if (recentMsg != null)
|
|
||||||
{
|
|
||||||
Log.Verbose("Duplicate message: [{0}] {1}", embedBuilder.Author.Name, embedBuilder.Description);
|
|
||||||
this.recentMessages.Remove(recentMsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await channels.ElementAt(chatTypeIndex).SendMessageAsync(embed: embedBuilder.Build());
|
|
||||||
} else {
|
|
||||||
var simpleMessage = $"{name}: {rawMessage}";
|
|
||||||
|
|
||||||
if (this.config.CheckForDuplicateMessages) {
|
|
||||||
var recentMsg = this.recentMessages.FirstOrDefault(
|
|
||||||
msg => msg.Content == simpleMessage);
|
|
||||||
|
|
||||||
if (recentMsg != null)
|
|
||||||
{
|
|
||||||
Log.Verbose("Duplicate message: {0}", simpleMessage);
|
|
||||||
this.recentMessages.Remove(recentMsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {rawMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<(string LodestoneId, string AvatarUrl)> GetCharacterInfo(string name, string worldName) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dynamic charCandidates = await XivApi.GetCharacterSearch(name, worldName);
|
|
||||||
|
|
||||||
if (charCandidates.Results.Count > 0)
|
|
||||||
{
|
|
||||||
var avatarUrl = charCandidates.Results[0].Avatar;
|
|
||||||
var lodestoneId = charCandidates.Results[0].ID;
|
|
||||||
|
|
||||||
return (lodestoneId, avatarUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not get XIVAPI character search result.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IMessageChannel> GetChannel(ChannelConfiguration channelConfig) {
|
|
||||||
if (channelConfig.Type == ChannelType.Guild)
|
|
||||||
return this.socketClient.GetGuild(channelConfig.GuildId).GetTextChannel(channelConfig.ChannelId);
|
|
||||||
return await this.socketClient.GetUser(channelConfig.ChannelId).GetOrCreateDMChannelAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.socketClient.LogoutAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
|
|
||||||
namespace Dalamud.DiscordBot
|
|
||||||
{
|
|
||||||
public enum ChannelType {
|
|
||||||
Guild,
|
|
||||||
User
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class ChannelConfiguration {
|
|
||||||
public ChannelType Type { get; set; }
|
|
||||||
|
|
||||||
public ulong GuildId { get; set; }
|
|
||||||
public ulong ChannelId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class ChatTypeConfiguration {
|
|
||||||
public XivChatType ChatType { get; set; }
|
|
||||||
|
|
||||||
public ChannelConfiguration Channel { get; set; }
|
|
||||||
public int Color { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class DiscordFeatureConfiguration
|
|
||||||
{
|
|
||||||
public string Token { get; set; }
|
|
||||||
|
|
||||||
public bool CheckForDuplicateMessages { get; set; }
|
|
||||||
public int ChatDelayMs { get; set; }
|
|
||||||
|
|
||||||
public bool DisableEmbeds { get; set; }
|
|
||||||
|
|
||||||
public ulong OwnerUserId { get; set; }
|
|
||||||
|
|
||||||
public List<ChatTypeConfiguration> ChatTypeConfigurations { get; set; }
|
|
||||||
|
|
||||||
public ChannelConfiguration CfNotificationChannel { get; set; }
|
|
||||||
public ChannelConfiguration CfPreferredRoleChannel { get; set; }
|
|
||||||
public ChannelConfiguration RetainerNotificationChannel { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
using System;
|
|
||||||
using CoreHook;
|
using CoreHook;
|
||||||
|
|
||||||
namespace Dalamud
|
namespace Dalamud
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
///
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class EntryPoint : IEntryPoint
|
public sealed class EntryPoint : IEntryPoint
|
||||||
{
|
{
|
||||||
public EntryPoint(IContext context, string rootDirectory) { }
|
public EntryPoint(IContext context, string rootDirectory) { }
|
||||||
|
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a parsed SeString payload.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class Payload
|
|
||||||
{
|
|
||||||
public abstract PayloadType Type { get; }
|
|
||||||
|
|
||||||
public abstract void Resolve();
|
|
||||||
|
|
||||||
public abstract byte[] Encode();
|
|
||||||
|
|
||||||
protected abstract void ProcessChunkImpl(BinaryReader reader, long endOfStream);
|
|
||||||
|
|
||||||
public static Payload Process(BinaryReader reader)
|
|
||||||
{
|
|
||||||
if ((byte)reader.PeekChar() != START_BYTE)
|
|
||||||
{
|
|
||||||
return ProcessText(reader);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ProcessChunk(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Payload ProcessChunk(BinaryReader reader)
|
|
||||||
{
|
|
||||||
Payload payload = null;
|
|
||||||
|
|
||||||
reader.ReadByte(); // START_BYTE
|
|
||||||
var chunkType = (SeStringChunkType)reader.ReadByte();
|
|
||||||
var chunkLen = GetInteger(reader);
|
|
||||||
|
|
||||||
var packetStart = reader.BaseStream.Position;
|
|
||||||
|
|
||||||
switch (chunkType)
|
|
||||||
{
|
|
||||||
case SeStringChunkType.Interactable:
|
|
||||||
{
|
|
||||||
var subType = (EmbeddedInfoType)reader.ReadByte();
|
|
||||||
switch (subType)
|
|
||||||
{
|
|
||||||
case EmbeddedInfoType.PlayerName:
|
|
||||||
payload = new PlayerPayload();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EmbeddedInfoType.ItemLink:
|
|
||||||
payload = new ItemPayload();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EmbeddedInfoType.Status:
|
|
||||||
payload = new StatusPayload();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EmbeddedInfoType.LinkTerminator:
|
|
||||||
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
|
||||||
default:
|
|
||||||
Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType);
|
|
||||||
// rewind so we capture the Interactable byte in the raw data
|
|
||||||
reader.BaseStream.Seek(-1, SeekOrigin.Current);
|
|
||||||
payload = new RawPayload((byte)chunkType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType);
|
|
||||||
payload = new RawPayload((byte)chunkType);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload?.ProcessChunkImpl(reader, reader.BaseStream.Position + chunkLen - 1);
|
|
||||||
|
|
||||||
// read through the rest of the packet
|
|
||||||
var readBytes = (int)(reader.BaseStream.Position - packetStart);
|
|
||||||
reader.ReadBytes(chunkLen - readBytes + 1); // +1 for the END_BYTE marker
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Payload ProcessText(BinaryReader reader)
|
|
||||||
{
|
|
||||||
var payload = new TextPayload();
|
|
||||||
payload.ProcessChunkImpl(reader, reader.BaseStream.Length);
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region parse constants and helpers
|
|
||||||
|
|
||||||
protected const byte START_BYTE = 0x02;
|
|
||||||
protected const byte END_BYTE = 0x03;
|
|
||||||
|
|
||||||
protected enum SeStringChunkType
|
|
||||||
{
|
|
||||||
Interactable = 0x27
|
|
||||||
}
|
|
||||||
|
|
||||||
protected enum EmbeddedInfoType
|
|
||||||
{
|
|
||||||
PlayerName = 0x01,
|
|
||||||
ItemLink = 0x03,
|
|
||||||
Status = 0x09,
|
|
||||||
|
|
||||||
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
|
||||||
}
|
|
||||||
|
|
||||||
protected enum IntegerType
|
|
||||||
{
|
|
||||||
Byte = 0xF0,
|
|
||||||
ByteTimes256 = 0xF1,
|
|
||||||
Int16 = 0xF2,
|
|
||||||
Int16Plus1Million = 0xF6,
|
|
||||||
Int24 = 0xFA,
|
|
||||||
Int32 = 0xFE
|
|
||||||
}
|
|
||||||
|
|
||||||
// made protected, unless we actually want to use it externally
|
|
||||||
// in which case it should probably go live somewhere else
|
|
||||||
protected static int GetInteger(BinaryReader input)
|
|
||||||
{
|
|
||||||
var t = input.ReadByte();
|
|
||||||
var type = (IntegerType)t;
|
|
||||||
return GetInteger(input, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetInteger(BinaryReader input, IntegerType type)
|
|
||||||
{
|
|
||||||
const byte ByteLengthCutoff = 0xF0;
|
|
||||||
|
|
||||||
var t = (byte)type;
|
|
||||||
if (t < ByteLengthCutoff)
|
|
||||||
return t - 1;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case IntegerType.Byte:
|
|
||||||
return input.ReadByte();
|
|
||||||
case IntegerType.ByteTimes256:
|
|
||||||
return input.ReadByte() * 256;
|
|
||||||
case IntegerType.Int16:
|
|
||||||
{
|
|
||||||
var v = 0;
|
|
||||||
v |= input.ReadByte() << 8;
|
|
||||||
v |= input.ReadByte();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
case IntegerType.Int16Plus1Million:
|
|
||||||
{
|
|
||||||
var v = 0;
|
|
||||||
v |= input.ReadByte() << 16;
|
|
||||||
v |= input.ReadByte() << 8;
|
|
||||||
v |= input.ReadByte();
|
|
||||||
// need the actual value since it's used as a flag
|
|
||||||
// v -= 1000000;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
case IntegerType.Int24:
|
|
||||||
{
|
|
||||||
var v = 0;
|
|
||||||
v |= input.ReadByte() << 16;
|
|
||||||
v |= input.ReadByte() << 8;
|
|
||||||
v |= input.ReadByte();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
case IntegerType.Int32:
|
|
||||||
{
|
|
||||||
var v = 0;
|
|
||||||
v |= input.ReadByte() << 24;
|
|
||||||
v |= input.ReadByte() << 16;
|
|
||||||
v |= input.ReadByte() << 8;
|
|
||||||
v |= input.ReadByte();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static byte[] MakeInteger(int value)
|
|
||||||
{
|
|
||||||
// clearly the epitome of efficiency
|
|
||||||
|
|
||||||
var bytesPadded = BitConverter.GetBytes(value);
|
|
||||||
Array.Reverse(bytesPadded);
|
|
||||||
return bytesPadded.SkipWhile(b => b == 0x00).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static IntegerType GetTypeForIntegerBytes(byte[] bytes)
|
|
||||||
{
|
|
||||||
// not the most scientific, exists mainly for laziness
|
|
||||||
|
|
||||||
if (bytes.Length == 1)
|
|
||||||
{
|
|
||||||
return IntegerType.Byte;
|
|
||||||
}
|
|
||||||
else if (bytes.Length == 2)
|
|
||||||
{
|
|
||||||
return IntegerType.Int16;
|
|
||||||
}
|
|
||||||
else if (bytes.Length == 3)
|
|
||||||
{
|
|
||||||
return IntegerType.Int24;
|
|
||||||
}
|
|
||||||
else if (bytes.Length == 4)
|
|
||||||
{
|
|
||||||
return IntegerType.Int32;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// All parsed types of SeString payloads.
|
|
||||||
/// </summary>
|
|
||||||
public enum PayloadType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An SeString payload representing a player link.
|
|
||||||
/// </summary>
|
|
||||||
Player,
|
|
||||||
/// <summary>
|
|
||||||
/// An SeString payload representing an Item link.
|
|
||||||
/// </summary>
|
|
||||||
Item,
|
|
||||||
/// <summary>
|
|
||||||
/// An SeString payload representing an Status Effect link.
|
|
||||||
/// </summary>
|
|
||||||
Status,
|
|
||||||
/// <summary>
|
|
||||||
/// An SeString payload representing raw, typed text.
|
|
||||||
/// </summary>
|
|
||||||
RawText,
|
|
||||||
/// <summary>
|
|
||||||
/// An SeString payload representing any data we don't handle.
|
|
||||||
/// </summary>
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|
||||||
{
|
|
||||||
public class ItemPayload : Payload
|
|
||||||
{
|
|
||||||
public override PayloadType Type => PayloadType.Item;
|
|
||||||
|
|
||||||
public int ItemId { get; private set; }
|
|
||||||
public string ItemName { get; private set; } = string.Empty;
|
|
||||||
public bool IsHQ { get; private set; } = false;
|
|
||||||
|
|
||||||
public ItemPayload() { }
|
|
||||||
|
|
||||||
public ItemPayload(int itemId, bool isHQ)
|
|
||||||
{
|
|
||||||
ItemId = itemId;
|
|
||||||
IsHQ = isHQ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Resolve()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ItemName))
|
|
||||||
{
|
|
||||||
dynamic item = XivApi.GetItem(ItemId).GetAwaiter().GetResult();
|
|
||||||
ItemName = item.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte[] Encode()
|
|
||||||
{
|
|
||||||
var actualItemId = IsHQ ? ItemId + 1000000 : ItemId;
|
|
||||||
var idBytes = MakeInteger(actualItemId);
|
|
||||||
bool hasName = !string.IsNullOrEmpty(ItemName);
|
|
||||||
|
|
||||||
var itemIdFlag = IsHQ ? IntegerType.Int16Plus1Million : IntegerType.Int16;
|
|
||||||
|
|
||||||
var chunkLen = idBytes.Length + 5;
|
|
||||||
if (hasName)
|
|
||||||
{
|
|
||||||
// 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself
|
|
||||||
chunkLen += (1 + 1 + ItemName.Length);
|
|
||||||
if (IsHQ)
|
|
||||||
{
|
|
||||||
chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = new List<byte>()
|
|
||||||
{
|
|
||||||
START_BYTE,
|
|
||||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink,
|
|
||||||
(byte)itemIdFlag
|
|
||||||
};
|
|
||||||
bytes.AddRange(idBytes);
|
|
||||||
// unk
|
|
||||||
bytes.AddRange(new byte[] { 0x02, 0x01 });
|
|
||||||
|
|
||||||
// Links don't have to include the name, but if they do, it requires additional work
|
|
||||||
if (hasName)
|
|
||||||
{
|
|
||||||
var nameLen = ItemName.Length + 1;
|
|
||||||
if (IsHQ)
|
|
||||||
{
|
|
||||||
nameLen += 4; // space plus 3 bytes for HQ symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes.AddRange(new byte[]
|
|
||||||
{
|
|
||||||
0xFF, // unk
|
|
||||||
(byte)nameLen
|
|
||||||
});
|
|
||||||
bytes.AddRange(Encoding.UTF8.GetBytes(ItemName));
|
|
||||||
|
|
||||||
if (IsHQ)
|
|
||||||
{
|
|
||||||
// space and HQ symbol
|
|
||||||
bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes.Add(END_BYTE);
|
|
||||||
|
|
||||||
return bytes.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Type} - ItemId: {ItemId}, ItemName: {ItemName}, IsHQ: {IsHQ}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
|
||||||
{
|
|
||||||
ItemId = GetInteger(reader);
|
|
||||||
|
|
||||||
if (ItemId > 1000000)
|
|
||||||
{
|
|
||||||
ItemId -= 1000000;
|
|
||||||
IsHQ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader.BaseStream.Position + 3 < endOfStream)
|
|
||||||
{
|
|
||||||
// unk
|
|
||||||
reader.ReadBytes(3);
|
|
||||||
|
|
||||||
var itemNameLen = GetInteger(reader);
|
|
||||||
var itemNameBytes = reader.ReadBytes(itemNameLen);
|
|
||||||
|
|
||||||
// HQ items have the HQ symbol as part of the name, but since we already recorded
|
|
||||||
// the HQ flag, we want just the bare name
|
|
||||||
if (IsHQ)
|
|
||||||
{
|
|
||||||
itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemName = Encoding.UTF8.GetString(itemNameBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|
||||||
{
|
|
||||||
public class PlayerPayload : Payload
|
|
||||||
{
|
|
||||||
public override PayloadType Type => PayloadType.Player;
|
|
||||||
|
|
||||||
public string PlayerName { get; private set; }
|
|
||||||
public int ServerId { get; private set; }
|
|
||||||
public string ServerName { get; private set; } = String.Empty;
|
|
||||||
|
|
||||||
public PlayerPayload() { }
|
|
||||||
|
|
||||||
public PlayerPayload(string playerName, int serverId)
|
|
||||||
{
|
|
||||||
PlayerName = playerName;
|
|
||||||
ServerId = serverId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Resolve()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ServerName))
|
|
||||||
{
|
|
||||||
dynamic server = XivApi.Get($"World/{ServerId}").GetAwaiter().GetResult();
|
|
||||||
ServerName = server.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte[] Encode()
|
|
||||||
{
|
|
||||||
var chunkLen = PlayerName.Length + 7;
|
|
||||||
var bytes = new List<byte>()
|
|
||||||
{
|
|
||||||
START_BYTE,
|
|
||||||
(byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName,
|
|
||||||
/* unk */ 0x01,
|
|
||||||
(byte)(ServerId+1), // I didn't want to deal with single-byte values in MakeInteger, so we have to do the +1 manually
|
|
||||||
/* unk */0x01, /* unk */0xFF, // these sometimes vary but are frequently this
|
|
||||||
(byte)(PlayerName.Length+1)
|
|
||||||
};
|
|
||||||
|
|
||||||
bytes.AddRange(Encoding.UTF8.GetBytes(PlayerName));
|
|
||||||
bytes.Add(END_BYTE);
|
|
||||||
|
|
||||||
// encoded names are followed by the name in plain text again
|
|
||||||
// use the payload parsing for consistency, as this is technically a new chunk
|
|
||||||
bytes.AddRange(new TextPayload(PlayerName).Encode());
|
|
||||||
|
|
||||||
// unsure about this entire packet, but it seems to always follow a name
|
|
||||||
bytes.AddRange(new byte[]
|
|
||||||
{
|
|
||||||
START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator,
|
|
||||||
0x01, 0x01, 0x01, 0xFF, 0x01,
|
|
||||||
END_BYTE
|
|
||||||
});
|
|
||||||
|
|
||||||
return bytes.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Type} - PlayerName: {PlayerName}, ServerId: {ServerId}, ServerName: {ServerName}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
|
||||||
{
|
|
||||||
// unk
|
|
||||||
reader.ReadByte();
|
|
||||||
|
|
||||||
ServerId = GetInteger(reader);
|
|
||||||
|
|
||||||
// unk
|
|
||||||
reader.ReadBytes(2);
|
|
||||||
|
|
||||||
var nameLen = GetInteger(reader);
|
|
||||||
PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|
||||||
{
|
|
||||||
public class RawPayload : Payload
|
|
||||||
{
|
|
||||||
public override PayloadType Type => PayloadType.Unknown;
|
|
||||||
|
|
||||||
public byte ChunkType { get; private set; }
|
|
||||||
public byte[] Data { get; private set; }
|
|
||||||
|
|
||||||
public RawPayload(byte chunkType)
|
|
||||||
{
|
|
||||||
ChunkType = chunkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Resolve()
|
|
||||||
{
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte[] Encode()
|
|
||||||
{
|
|
||||||
var chunkLen = Data.Length + 1;
|
|
||||||
|
|
||||||
var bytes = new List<byte>()
|
|
||||||
{
|
|
||||||
START_BYTE,
|
|
||||||
ChunkType,
|
|
||||||
(byte)chunkLen
|
|
||||||
};
|
|
||||||
bytes.AddRange(Data);
|
|
||||||
|
|
||||||
bytes.Add(END_BYTE);
|
|
||||||
|
|
||||||
return bytes.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Type} - Chunk type: {ChunkType:X}, Data: {BitConverter.ToString(Data).Replace("-", " ")}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
|
||||||
{
|
|
||||||
Data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|
||||||
{
|
|
||||||
public class StatusPayload : Payload
|
|
||||||
{
|
|
||||||
public override PayloadType Type => PayloadType.Status;
|
|
||||||
|
|
||||||
public int StatusId { get; private set; }
|
|
||||||
|
|
||||||
public string StatusName { get; private set; } = string.Empty;
|
|
||||||
|
|
||||||
public StatusPayload() { }
|
|
||||||
|
|
||||||
public StatusPayload(int statusId)
|
|
||||||
{
|
|
||||||
StatusId = statusId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Resolve()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(StatusName))
|
|
||||||
{
|
|
||||||
dynamic status = XivApi.Get($"Status/{StatusId}").GetAwaiter().GetResult();
|
|
||||||
//Console.WriteLine($"Resolved status {StatusId} to {status.Name}");
|
|
||||||
StatusName = status.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte[] Encode()
|
|
||||||
{
|
|
||||||
var idBytes = MakeInteger(StatusId);
|
|
||||||
var idPrefix = GetTypeForIntegerBytes(idBytes);
|
|
||||||
|
|
||||||
var chunkLen = idBytes.Length + 8;
|
|
||||||
var bytes = new List<byte>()
|
|
||||||
{
|
|
||||||
START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status,
|
|
||||||
(byte)idPrefix
|
|
||||||
};
|
|
||||||
|
|
||||||
bytes.AddRange(idBytes);
|
|
||||||
// unk
|
|
||||||
bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE });
|
|
||||||
|
|
||||||
return bytes.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Type} - StatusId: {StatusId}, StatusName: {StatusName}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
|
||||||
{
|
|
||||||
StatusId = GetInteger(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|
||||||
{
|
|
||||||
public class TextPayload : Payload
|
|
||||||
{
|
|
||||||
public override PayloadType Type => PayloadType.RawText;
|
|
||||||
|
|
||||||
private string textConverted = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Text of this text payload as an UTF-8 converted string.
|
|
||||||
/// Don't rely on this for accurate representation of SE payload data, please check RawData instead.
|
|
||||||
/// </summary>
|
|
||||||
public string Text {
|
|
||||||
get { return this.textConverted ??= Encoding.UTF8.GetString(RawData); }
|
|
||||||
set {
|
|
||||||
this.textConverted = value;
|
|
||||||
RawData = Encoding.UTF8.GetBytes(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The raw unconverted data of this text payload.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] RawData { get; set; }
|
|
||||||
|
|
||||||
public TextPayload() { }
|
|
||||||
|
|
||||||
public TextPayload(string text)
|
|
||||||
{
|
|
||||||
Text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Resolve()
|
|
||||||
{
|
|
||||||
// nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte[] Encode()
|
|
||||||
{
|
|
||||||
return Encoding.UTF8.GetBytes(Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Type} - Text: {Text}";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ProcessChunkImpl(BinaryReader reader, long endOfStream)
|
|
||||||
{
|
|
||||||
var text = new List<byte>();
|
|
||||||
|
|
||||||
while (reader.BaseStream.Position < endOfStream)
|
|
||||||
{
|
|
||||||
if ((byte)reader.PeekChar() == START_BYTE)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// not the most efficient, but the easiest
|
|
||||||
text.Add(reader.ReadByte());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.Count > 0)
|
|
||||||
{
|
|
||||||
// TODO: handling of the game's assorted special unicode characters
|
|
||||||
Text = Encoding.UTF8.GetString(text.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat.SeStringHandling
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a parsed SeString.
|
|
||||||
/// </summary>
|
|
||||||
public class SeString
|
|
||||||
{
|
|
||||||
public List<Payload> Payloads { get; }
|
|
||||||
|
|
||||||
public SeString(List<Payload> payloads)
|
|
||||||
{
|
|
||||||
Payloads = payloads;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper function to get all raw text from a message as a single joined string
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// All the raw text from the contained payloads, joined into a single string
|
|
||||||
/// </returns>
|
|
||||||
public string TextValue
|
|
||||||
{
|
|
||||||
get {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var p in Payloads)
|
|
||||||
{
|
|
||||||
if (p.Type == PayloadType.RawText)
|
|
||||||
{
|
|
||||||
sb.Append(((TextPayload)p).Text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parse an array of bytes to a SeString.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static SeString Parse(byte[] bytes)
|
|
||||||
{
|
|
||||||
var payloads = new List<Payload>();
|
|
||||||
|
|
||||||
using (var stream = new MemoryStream(bytes)) {
|
|
||||||
using var reader = new BinaryReader(stream);
|
|
||||||
|
|
||||||
while (stream.Position < bytes.Length)
|
|
||||||
{
|
|
||||||
var payload = Payload.Process(reader);
|
|
||||||
if (payload != null)
|
|
||||||
payloads.Add(payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SeString(payloads);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encode a parsed/created SeString to an array of bytes, to be used for injection.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="payloads"></param>
|
|
||||||
/// <returns>The bytes of the message.</returns>
|
|
||||||
public byte[] Encode()
|
|
||||||
{
|
|
||||||
var messageBytes = new List<byte>();
|
|
||||||
foreach (var p in Payloads)
|
|
||||||
{
|
|
||||||
messageBytes.AddRange(p.Encode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageBytes.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat {
|
|
||||||
public sealed class XivChatEntry {
|
|
||||||
public XivChatType Type { get; set; } = XivChatType.Debug;
|
|
||||||
|
|
||||||
public uint SenderId { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public byte[] MessageBytes { get; set; }
|
|
||||||
|
|
||||||
public IntPtr Parameters { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Chat {
|
|
||||||
/// <summary>
|
|
||||||
/// The FFXIV chat types as seen in the LogKind ex table.
|
|
||||||
/// </summary>
|
|
||||||
public enum XivChatType : ushort {
|
|
||||||
None = 0,
|
|
||||||
Debug = 1,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)]
|
|
||||||
Urgent = 2,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Notice", "notice", 0xFF9400D3)]
|
|
||||||
Notice = 3,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Say", "say", 0xFFFFFFFF)]
|
|
||||||
Say = 10,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Shout", "shout", 0xFFFF4500)]
|
|
||||||
Shout = 11,
|
|
||||||
TellOutgoing = 12,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)]
|
|
||||||
TellIncoming = 13,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
|
||||||
Party = 14,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)]
|
|
||||||
Alliance = 15,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)]
|
|
||||||
Ls1 = 16,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)]
|
|
||||||
Ls2 = 17,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)]
|
|
||||||
Ls3 = 18,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)]
|
|
||||||
Ls4 = 19,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)]
|
|
||||||
Ls5 = 20,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)]
|
|
||||||
Ls6 = 21,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)]
|
|
||||||
Ls7 = 22,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)]
|
|
||||||
Ls8 = 23,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)]
|
|
||||||
FreeCompany = 24,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)]
|
|
||||||
NoviceNetwork = 27,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)]
|
|
||||||
CustomEmote = 28,
|
|
||||||
[XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)]
|
|
||||||
StandardEmote = 29,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)]
|
|
||||||
Yell = 30,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Party", "party", 0xFF1E90FF)]
|
|
||||||
CrossParty = 32,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)]
|
|
||||||
PvPTeam = 36,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell1 = 37,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Echo", "echo", 0xFF808080)]
|
|
||||||
Echo = 56,
|
|
||||||
SystemError = 58,
|
|
||||||
GatheringSystemMessage = 60,
|
|
||||||
// not sure if this is used for anything else
|
|
||||||
RetainerSale = 71,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell2 = 101,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell3 = 102,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell4 = 103,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell5 = 104,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell6 = 105,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell7 = 106,
|
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)]
|
|
||||||
CrossLinkShell8 = 107
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class XivChatTypeExtensions {
|
|
||||||
public static XivChatTypeInfoAttribute GetDetails(this XivChatType p) {
|
|
||||||
return p.GetAttribute<XivChatTypeInfoAttribute>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class XivChatTypeInfoAttribute : Attribute {
|
|
||||||
internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) {
|
|
||||||
FancyName = fancyName;
|
|
||||||
Slug = slug;
|
|
||||||
DefaultColor = defaultColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FancyName { get; }
|
|
||||||
public string Slug { get; }
|
|
||||||
public uint DefaultColor { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class EnumExtensions {
|
|
||||||
public static TAttribute GetAttribute<TAttribute>(this Enum value)
|
|
||||||
where TAttribute : Attribute {
|
|
||||||
var type = value.GetType();
|
|
||||||
var name = Enum.GetName(type, value);
|
|
||||||
return type.GetField(name) // I prefer to get attributes this way
|
|
||||||
.GetCustomAttributes(false)
|
|
||||||
.OfType<TAttribute>()
|
|
||||||
.SingleOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game {
|
|
||||||
public class ChatHandlers {
|
|
||||||
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new Dictionary<string, string> {
|
|
||||||
{"", "<:ffxive071:585847382210642069>"},
|
|
||||||
{"", "<:ffxive083:585848592699490329>"}
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
private readonly Dictionary<XivChatType, Color> HandledChatTypeColors = new Dictionary<XivChatType, Color> {
|
|
||||||
{XivChatType.CrossParty, Color.DodgerBlue},
|
|
||||||
{XivChatType.Party, Color.DodgerBlue},
|
|
||||||
{XivChatType.FreeCompany, Color.DeepSkyBlue},
|
|
||||||
{XivChatType.CrossLinkShell1, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell2, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell3, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell4, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell5, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell6, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell7, Color.ForestGreen},
|
|
||||||
{XivChatType.CrossLinkShell8, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls1, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls2, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls3, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls4, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls5, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls6, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls7, Color.ForestGreen},
|
|
||||||
{XivChatType.Ls8, Color.ForestGreen},
|
|
||||||
{XivChatType.TellIncoming, Color.HotPink},
|
|
||||||
{XivChatType.PvPTeam, Color.SandyBrown},
|
|
||||||
{XivChatType.Urgent, Color.DarkViolet},
|
|
||||||
{XivChatType.NoviceNetwork, Color.SaddleBrown},
|
|
||||||
{XivChatType.Echo, Color.Gray}
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Regex rmtRegex =
|
|
||||||
new Regex(
|
|
||||||
@"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 NA",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex urlRegex =
|
|
||||||
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new Dictionary<ClientLanguage, Regex[]>() {
|
|
||||||
{
|
|
||||||
ClientLanguage.Japanese, new Regex[] {
|
|
||||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
|
||||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.English, new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.German, new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
|
||||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ClientLanguage.French, new Regex[]
|
|
||||||
{
|
|
||||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
|
||||||
|
|
||||||
public ChatHandlers(Dalamud dalamud) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
|
|
||||||
dalamud.Framework.Gui.Chat.OnChatMessageRaw += OnChatMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public string LastLink { get; private set; }
|
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref StdString sender,
|
|
||||||
ref StdString message, ref bool isHandled) {
|
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) {
|
|
||||||
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
|
||||||
|
|
||||||
this.dalamud.Framework.Gui.Chat.Print($"XIVLauncher in-game addon v{assemblyVersion} loaded.");
|
|
||||||
|
|
||||||
foreach (var plugin in this.dalamud.PluginManager.Plugins) {
|
|
||||||
this.dalamud.Framework.Gui.Chat.Print($" -> {plugin.Plugin.Name} v{plugin.GetType().Assembly.GetName().Version} loaded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasSeenLoadingMsg = true;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion)) {
|
|
||||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes("The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."),
|
|
||||||
Type = XivChatType.Urgent
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dalamud.Configuration.LastVersion = assemblyVersion;
|
|
||||||
this.dalamud.Configuration.Save(this.dalamud.StartInfo.ConfigurationPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !DEBUG && false
|
|
||||||
if (!this.hasSeenLoadingMsg)
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var matched = this.rmtRegex.IsMatch(message.Value);
|
|
||||||
if (matched) {
|
|
||||||
// This seems to be a RMT ad - let's not show it
|
|
||||||
Log.Debug("Handled RMT ad");
|
|
||||||
isHandled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageVal = message.Value;
|
|
||||||
var senderVal = sender.Value;
|
|
||||||
|
|
||||||
if (this.dalamud.Configuration.BadWords != null &&
|
|
||||||
this.dalamud.Configuration.BadWords.Any(x => messageVal.Contains(x))) {
|
|
||||||
// This seems to be in the user block list - let's not show it
|
|
||||||
Log.Debug("Blocklist triggered");
|
|
||||||
isHandled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == XivChatType.RetainerSale)
|
|
||||||
{
|
|
||||||
foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language])
|
|
||||||
{
|
|
||||||
var matchInfo = regex.Match(message.Value);
|
|
||||||
|
|
||||||
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
|
||||||
// but we'd be checking the main match anyway
|
|
||||||
var itemInfo = matchInfo.Groups["item"];
|
|
||||||
if (!itemInfo.Success)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var itemLink =
|
|
||||||
SeString.Parse(message.RawData).Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
|
|
||||||
|
|
||||||
if (itemLink == null) {
|
|
||||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.RawData));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.ItemId}, HQ {itemLink.IsHQ}");
|
|
||||||
|
|
||||||
var valueInfo = matchInfo.Groups["value"];
|
|
||||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
|
||||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out var itemValue))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.ItemId, itemValue, itemLink.IsHQ));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageCopy = message;
|
|
||||||
var senderCopy = sender;
|
|
||||||
Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, messageCopy, senderCopy));
|
|
||||||
|
|
||||||
// Handle all of this with SeString some day
|
|
||||||
if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
|
|
||||||
type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) && !message.Value.Contains((char)0x02)) {
|
|
||||||
var italicsStart = message.Value.IndexOf("*");
|
|
||||||
var italicsEnd = message.Value.IndexOf("*", italicsStart + 1);
|
|
||||||
|
|
||||||
var messageString = message.Value;
|
|
||||||
|
|
||||||
while (italicsEnd != -1) {
|
|
||||||
var it = MakeItalics(
|
|
||||||
messageString.Substring(italicsStart, italicsEnd - italicsStart + 1).Replace("*", ""));
|
|
||||||
messageString = messageString.Remove(italicsStart, italicsEnd - italicsStart + 1);
|
|
||||||
messageString = messageString.Insert(italicsStart, it);
|
|
||||||
italicsStart = messageString.IndexOf("*");
|
|
||||||
italicsEnd = messageString.IndexOf("*", italicsStart + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.RawData = Encoding.UTF8.GetBytes(messageString);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var linkMatch = this.urlRegex.Match(message.Value);
|
|
||||||
if (linkMatch.Value.Length > 0)
|
|
||||||
LastLink = linkMatch.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string MakeItalics(string text) {
|
|
||||||
return Encoding.UTF8.GetString(new byte[] {0x02, 0x1A, 0x02, 0x02, 0x03}) + text +
|
|
||||||
Encoding.UTF8.GetString(new byte[] {0x02, 0x1A, 0x02, 0x01, 0x03});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
|
||||||
/// <summary>
|
|
||||||
/// This collection represents the currently spawned FFXIV actors.
|
|
||||||
/// </summary>
|
|
||||||
public class ActorTable : ICollection {
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the actor table collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="addressResolver">Client state address resolver.</param>
|
|
||||||
public ActorTable(ClientStateAddressResolver addressResolver) {
|
|
||||||
Address = addressResolver;
|
|
||||||
|
|
||||||
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get an actor at the specified spawn index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Spawn index.</param>
|
|
||||||
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
|
||||||
public Actor this[int index] {
|
|
||||||
get {
|
|
||||||
if (index > Length)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
//Log.Information("Trying to get actor at {0}", index);
|
|
||||||
var tblIndex = Address.ActorTable + 8 + index * 8;
|
|
||||||
|
|
||||||
var offset = Marshal.ReadIntPtr(tblIndex);
|
|
||||||
|
|
||||||
//Log.Information("Actor at {0}", offset.ToString());
|
|
||||||
|
|
||||||
if (offset == IntPtr.Zero)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(offset);
|
|
||||||
|
|
||||||
//Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"),
|
|
||||||
// actorStruct.ObjectKind.ToString());
|
|
||||||
|
|
||||||
switch (actorStruct.ObjectKind)
|
|
||||||
{
|
|
||||||
case ObjectKind.Player: return new PlayerCharacter(actorStruct);
|
|
||||||
case ObjectKind.BattleNpc: return new BattleNpc(actorStruct);
|
|
||||||
default: return new Actor(actorStruct);
|
|
||||||
}
|
|
||||||
} catch (AccessViolationException) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ActorTableEnumerator : IEnumerator {
|
|
||||||
private readonly ActorTable table;
|
|
||||||
|
|
||||||
private int currentIndex;
|
|
||||||
|
|
||||||
public ActorTableEnumerator(ActorTable table) {
|
|
||||||
this.table = table;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MoveNext() {
|
|
||||||
this.currentIndex++;
|
|
||||||
return this.currentIndex != this.table.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset() {
|
|
||||||
this.currentIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Current => this.table[this.currentIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator GetEnumerator() {
|
|
||||||
return new ActorTableEnumerator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of currently spawned actors.
|
|
||||||
/// </summary>
|
|
||||||
public int Length => Marshal.ReadInt32(Address.ActorTable);
|
|
||||||
|
|
||||||
int ICollection.Count => Length;
|
|
||||||
|
|
||||||
bool ICollection.IsSynchronized => false;
|
|
||||||
|
|
||||||
object ICollection.SyncRoot => this;
|
|
||||||
|
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
|
||||||
for (var i = 0; i < Length; i++) {
|
|
||||||
array.SetValue(this[i], index);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
|
||||||
/// <summary>
|
|
||||||
/// Enum describing possible entity kinds.
|
|
||||||
/// </summary>
|
|
||||||
public enum ObjectKind : byte {
|
|
||||||
/// <summary>
|
|
||||||
/// Invalid actor.
|
|
||||||
/// </summary>
|
|
||||||
None = 0x00,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing player characters.
|
|
||||||
/// </summary>
|
|
||||||
Player = 0x01,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing battle NPCs.
|
|
||||||
/// </summary>
|
|
||||||
BattleNpc = 0x02,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing event NPCs.
|
|
||||||
/// </summary>
|
|
||||||
EventNpc = 0x03,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing treasures.
|
|
||||||
/// </summary>
|
|
||||||
Treasure = 0x04,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing aetherytes.
|
|
||||||
/// </summary>
|
|
||||||
Aetheryte = 0x05,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing gathering points.
|
|
||||||
/// </summary>
|
|
||||||
GatheringPoint = 0x06,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing event objects.
|
|
||||||
/// </summary>
|
|
||||||
EventObj = 0x07,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing mounts.
|
|
||||||
/// </summary>
|
|
||||||
MountType = 0x08,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing minions.
|
|
||||||
/// </summary>
|
|
||||||
Companion = 0x09, // Minion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing retainers.
|
|
||||||
/// </summary>
|
|
||||||
Retainer = 0x0A,
|
|
||||||
Area = 0x0B,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Objects representing housing objects.
|
|
||||||
/// </summary>
|
|
||||||
Housing = 0x0C,
|
|
||||||
Cutscene = 0x0D,
|
|
||||||
CardStand = 0x0E
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors {
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct Position3 {
|
|
||||||
public float X;
|
|
||||||
public float Z;
|
|
||||||
public float Y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This object represents a class or job.
|
|
||||||
/// </summary>
|
|
||||||
public class ClassJob {
|
|
||||||
/// <summary>
|
|
||||||
/// ID of the ClassJob.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int Id;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the ClassJob.
|
|
||||||
/// </summary>
|
|
||||||
public string Name => (string) XivApi.GetClassJob(this.Id).GetAwaiter().GetResult()["Name"];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the ClassJob resolver with the provided ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The ID of the world.</param>
|
|
||||||
public ClassJob(byte id) {
|
|
||||||
this.Id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This object represents a world a character can reside on.
|
|
||||||
/// </summary>
|
|
||||||
public class World {
|
|
||||||
/// <summary>
|
|
||||||
/// ID of the world.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int Id;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the world.
|
|
||||||
/// </summary>
|
|
||||||
public string Name => (string) XivApi.GetWorld(this.Id).GetAwaiter().GetResult()["Name"];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the world resolver with the provided ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The ID of the world.</param>
|
|
||||||
public World(byte id) {
|
|
||||||
this.Id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a basic FFXIV actor.
|
|
||||||
/// </summary>
|
|
||||||
public class Actor {
|
|
||||||
/// <summary>
|
|
||||||
/// The memory representation of the base actor.
|
|
||||||
/// </summary>
|
|
||||||
protected Structs.Actor actorStruct;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a representation of a basic FFXIV actor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
|
||||||
public Actor(Structs.Actor actorStruct) {
|
|
||||||
this.actorStruct = actorStruct;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Position of this <see cref="Actor" />.
|
|
||||||
/// </summary>
|
|
||||||
public Position3 Position => this.actorStruct.Position;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Displayname of this <see cref="Actor">Actor</see>.
|
|
||||||
/// </summary>
|
|
||||||
public string Name => this.actorStruct.Name;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Actor ID of this <see cref="Actor" />.
|
|
||||||
/// </summary>
|
|
||||||
public int ActorId => this.actorStruct.ActorId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Entity kind of this <see cref="Actor">actor</see>. See <see cref="ObjectKind">the ObjectKind enum</see> for
|
|
||||||
/// possible values.
|
|
||||||
/// </summary>
|
|
||||||
public ObjectKind ObjectKind => this.actorStruct.ObjectKind;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Resolvers;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents the base for non-static entities.
|
|
||||||
/// </summary>
|
|
||||||
public class Chara : Actor {
|
|
||||||
/// <summary>
|
|
||||||
/// Set up a new Chara with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
|
||||||
public Chara(Structs.Actor actorStruct) : base(actorStruct) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The level of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public byte Level => this.actorStruct.Level;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ClassJob of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public ClassJob ClassJob => new ClassJob(this.actorStruct.ClassJob);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current HP of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public int CurrentHp => this.actorStruct.CurrentHp;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum HP of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxHp => this.actorStruct.MaxHp;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current MP of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public int CurrentMp => this.actorStruct.CurrentMp;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum MP of this Chara.
|
|
||||||
/// </summary>
|
|
||||||
public int MaxMp => this.actorStruct.MaxMp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a battle NPC.
|
|
||||||
/// </summary>
|
|
||||||
public class BattleNpc : Npc {
|
|
||||||
/// <summary>
|
|
||||||
/// Set up a new BattleNpc with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
|
||||||
public BattleNpc(Structs.Actor actorStruct) : base(actorStruct) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
|
||||||
/// </summary>
|
|
||||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind) this.actorStruct.SubKind;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of this BattleNpc's owner.
|
|
||||||
/// </summary>
|
|
||||||
public int OwnerId => this.actorStruct.OwnerId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
|
||||||
/// <summary>
|
|
||||||
/// Enum describing possible BattleNpc kinds.
|
|
||||||
/// </summary>
|
|
||||||
public enum BattleNpcSubKind : byte {
|
|
||||||
/// <summary>
|
|
||||||
/// Invalid BattleNpc.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BattleNpc representing a Pet.
|
|
||||||
/// </summary>
|
|
||||||
Pet = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BattleNpc representing a standard enemy.
|
|
||||||
/// </summary>
|
|
||||||
Enemy = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a NPC.
|
|
||||||
/// </summary>
|
|
||||||
public class Npc : Chara {
|
|
||||||
/// <summary>
|
|
||||||
/// Set up a new NPC with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
|
||||||
public Npc(Structs.Actor actorStruct) : base(actorStruct) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The data ID of the NPC linking to their respective game data.
|
|
||||||
/// </summary>
|
|
||||||
public int DataId => this.actorStruct.DataId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Actors.Resolvers;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors.Types {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents a player character.
|
|
||||||
/// </summary>
|
|
||||||
public class PlayerCharacter : Chara {
|
|
||||||
/// <summary>
|
|
||||||
/// Set up a new player character with the provided memory representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
|
||||||
public PlayerCharacter(Structs.Actor actorStruct) : base(actorStruct) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current <see cref="World">world</see> of the character.
|
|
||||||
/// </summary>
|
|
||||||
public World CurrentWorld => new World(this.actorStruct.CurrentWorld);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The home <see cref="World">world</see> of the character.
|
|
||||||
/// </summary>
|
|
||||||
public World HomeWorld => new World(this.actorStruct.HomeWorld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.ClientState.Actors;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.Internal;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents the state of the game client at the time of access.
|
|
||||||
/// </summary>
|
|
||||||
public class ClientState : INotifyPropertyChanged {
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
|
|
||||||
public readonly ClientLanguage ClientLanguage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The table of all present actors.
|
|
||||||
/// </summary>
|
|
||||||
public readonly ActorTable Actors;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The local player character, if one is present.
|
|
||||||
/// </summary>
|
|
||||||
//public PlayerCharacter LocalPlayer { get; private set; }
|
|
||||||
public PlayerCharacter LocalPlayer {
|
|
||||||
get {
|
|
||||||
var actor = this.Actors[0];
|
|
||||||
|
|
||||||
if (actor is PlayerCharacter pc)
|
|
||||||
return pc;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//public PlayerCharacter LocalPlayer => null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The content ID of the local character.
|
|
||||||
/// </summary>
|
|
||||||
public ulong LocalContentId => (ulong) Marshal.ReadInt64(Address.LocalContentId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The class facilitating Job Gauge data access
|
|
||||||
/// </summary>
|
|
||||||
public JobGauges JobGauges;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set up client state access.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dalamud">Dalamud instance</param>
|
|
||||||
/// /// <param name="startInfo">StartInfo of the current Dalamud launch</param>
|
|
||||||
/// <param name="scanner">Sig scanner</param>
|
|
||||||
/// <param name="targetModule">Game process module</param>
|
|
||||||
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner, ProcessModule targetModule) {
|
|
||||||
Address = new ClientStateAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
|
||||||
|
|
||||||
this.ClientLanguage = startInfo.Language;
|
|
||||||
|
|
||||||
this.Actors = new ActorTable(Address);
|
|
||||||
|
|
||||||
this.JobGauges = new JobGauges(Address);
|
|
||||||
|
|
||||||
dalamud.Framework.OnUpdateEvent += FrameworkOnOnUpdateEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FrameworkOnOnUpdateEvent(Framework framework) {
|
|
||||||
//LocalPlayer = (PlayerCharacter) this.Actors[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Internal;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState
|
|
||||||
{
|
|
||||||
public sealed class ClientStateAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr ActorTable { get; private set; }
|
|
||||||
public IntPtr LocalContentId { get; private set; }
|
|
||||||
public IntPtr JobGaugeData { get; set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
ActorTable = sig.Module.BaseAddress + 0x1C62218;
|
|
||||||
LocalContentId = sig.Module.BaseAddress + 0x1C2E000;
|
|
||||||
JobGaugeData = sig.Module.BaseAddress + 0x1C5E4A0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState {
|
|
||||||
public class JobGauges {
|
|
||||||
private ClientStateAddressResolver Address { get; }
|
|
||||||
|
|
||||||
public JobGauges(ClientStateAddressResolver addressResolver) {
|
|
||||||
Address = addressResolver;
|
|
||||||
|
|
||||||
Log.Verbose("JobGaugeData address {JobGaugeData}", Address.ActorTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should only be called with the gauge types in
|
|
||||||
// ClientState.Structs.JobGauge
|
|
||||||
public T Get<T>() {
|
|
||||||
return Marshal.PtrToStructure<T>(Address.JobGaugeData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.ClientState.Actors;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Native memory representation of a FFXIV actor.
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct Actor {
|
|
||||||
[FieldOffset(0x30)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)] public string Name;
|
|
||||||
[FieldOffset(116)] public int ActorId;
|
|
||||||
[FieldOffset(128)] public int DataId;
|
|
||||||
[FieldOffset(132)] public int OwnerId;
|
|
||||||
[FieldOffset(140)] public ObjectKind ObjectKind;
|
|
||||||
[FieldOffset(141)] public byte SubKind;
|
|
||||||
[FieldOffset(160)] public Position3 Position;
|
|
||||||
[FieldOffset(6276)] public byte CurrentWorld;
|
|
||||||
[FieldOffset(6278)] public byte HomeWorld;
|
|
||||||
[FieldOffset(6328)] public int CurrentHp;
|
|
||||||
[FieldOffset(6332)] public int MaxHp;
|
|
||||||
[FieldOffset(6336)] public int CurrentMp;
|
|
||||||
[FieldOffset(6340)] public int MaxMp;
|
|
||||||
[FieldOffset(6358)] public byte ClassJob;
|
|
||||||
[FieldOffset(6360)] public byte Level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct ASTGauge {
|
|
||||||
[FieldOffset(4)] private CardType Card;
|
|
||||||
[FieldOffset(5)] private unsafe fixed byte Seals[3];
|
|
||||||
|
|
||||||
public CardType DrawnCard() {
|
|
||||||
return Card;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool ContainsSeal(SealType seal) {
|
|
||||||
if (Seals[0] == (byte)seal) return true;
|
|
||||||
if (Seals[1] == (byte)seal) return true;
|
|
||||||
if (Seals[2] == (byte)seal) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct BLMGauge {
|
|
||||||
[FieldOffset(0)] public short TimeUntilNextPolyglot; //eno timer (ms)
|
|
||||||
[FieldOffset(2)] public short ElementTimeRemaining; //ui/af timer
|
|
||||||
[FieldOffset(4)] private byte ElementStance; //ui/af
|
|
||||||
[FieldOffset(5)] public byte NumUmbralHearts; //number of umbral hearts
|
|
||||||
[FieldOffset(6)] public byte NumPolyglotStacks; //number of polyglot stacks
|
|
||||||
[FieldOffset(7)] private byte EnoState; //eno active?
|
|
||||||
|
|
||||||
public bool InUmbralIce() {
|
|
||||||
return ElementStance > 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool InAstralFire() {
|
|
||||||
return ElementStance > 0 && ElementStance < 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnoActive() {
|
|
||||||
return EnoState > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct BRDGauge {
|
|
||||||
[FieldOffset(0)] public short SongTimer;
|
|
||||||
[FieldOffset(2)] public byte NumSongStacks;
|
|
||||||
[FieldOffset(4)] public CurrentSong ActiveSong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public unsafe struct DNCGauge {
|
|
||||||
[FieldOffset(0)] public byte NumFeathers;
|
|
||||||
[FieldOffset(1)] public byte Esprit;
|
|
||||||
[FieldOffset(2)] private fixed byte StepOrder[4];
|
|
||||||
[FieldOffset(6)] public byte NumCompleteSteps;
|
|
||||||
|
|
||||||
public bool IsDancing() {
|
|
||||||
return StepOrder[0] != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong NextStep() {
|
|
||||||
return (ulong)(15999 + StepOrder[NumCompleteSteps] - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct DRGGauge {
|
|
||||||
[FieldOffset(0)] public short BOTDTimer;
|
|
||||||
[FieldOffset(2)] public BOTDState BOTDState;
|
|
||||||
[FieldOffset(3)] public byte EyeCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct DRKGauge {
|
|
||||||
[FieldOffset(0)] public byte Blood;
|
|
||||||
[FieldOffset(2)] public short DarksideTimeRemaining;
|
|
||||||
[FieldOffset(4)] private byte DarkArtsState;
|
|
||||||
[FieldOffset(6)] public short ShadowTimeRemaining;
|
|
||||||
|
|
||||||
public bool HasDarkArts() {
|
|
||||||
return DarkArtsState > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct GNBGauge {
|
|
||||||
[FieldOffset(0)] public byte NumAmmo;
|
|
||||||
[FieldOffset(2)] public short MaxTimerDuration;
|
|
||||||
[FieldOffset(4)] public byte AmmoComboStepNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
public enum SealType : byte {
|
|
||||||
NONE = 0,
|
|
||||||
SUN,
|
|
||||||
MOON,
|
|
||||||
CELESTIAL
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CardType : byte {
|
|
||||||
NONE = 0,
|
|
||||||
BALANCE,
|
|
||||||
BOLE,
|
|
||||||
ARROW,
|
|
||||||
SPEAR,
|
|
||||||
EWER,
|
|
||||||
SPIRE,
|
|
||||||
LORD = 0x70,
|
|
||||||
LADY = 0x80
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SummonPet : byte {
|
|
||||||
NONE = 0,
|
|
||||||
IFRIT = 3,
|
|
||||||
TITAN,
|
|
||||||
GARUDA
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PetGlam : byte {
|
|
||||||
NONE = 0,
|
|
||||||
EMERALD,
|
|
||||||
TOPAZ,
|
|
||||||
RUBY
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum Sen : byte {
|
|
||||||
NONE = 0,
|
|
||||||
SETSU = 1 << 0,
|
|
||||||
GETSU = 1 << 1,
|
|
||||||
KA = 1 << 2
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BOTDState : byte {
|
|
||||||
NONE = 0,
|
|
||||||
BOTD,
|
|
||||||
LOTD
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum CurrentSong : byte {
|
|
||||||
MAGE = 5,
|
|
||||||
ARMY = 0xA,
|
|
||||||
WANDERER = 0xF
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DismissedFairy : byte {
|
|
||||||
EOS = 6,
|
|
||||||
SELENE
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Mudras : byte {
|
|
||||||
TEN = 1,
|
|
||||||
CHI = 2,
|
|
||||||
JIN = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct MCHGauge{
|
|
||||||
|
|
||||||
[FieldOffset(0)] public short OverheatTimeRemaining;
|
|
||||||
[FieldOffset(2)] public short RobotTimeRemaining;
|
|
||||||
[FieldOffset(4)] public byte Heat;
|
|
||||||
[FieldOffset(5)] public byte Battery;
|
|
||||||
[FieldOffset(6)] public byte LastRobotBatteryPower;
|
|
||||||
[FieldOffset(7)] private byte TimerActive;
|
|
||||||
|
|
||||||
public bool IsOverheated() {
|
|
||||||
return (TimerActive & 1) != 0;
|
|
||||||
}
|
|
||||||
public bool IsRobotActive() {
|
|
||||||
return (TimerActive & 2) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct MNKGauge {
|
|
||||||
[FieldOffset(0)] public byte GLTimer;
|
|
||||||
[FieldOffset(2)] public byte NumGLStacks;
|
|
||||||
[FieldOffset(3)] public byte NumChakra;
|
|
||||||
[FieldOffset(4)] private byte GLTimerFreezeState;
|
|
||||||
|
|
||||||
public bool IsGLTimerFroze() {
|
|
||||||
return GLTimerFreezeState > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct NINGauge {
|
|
||||||
[FieldOffset(0)] public int HutonTimeLeft;
|
|
||||||
[FieldOffset(4)] public byte TCJMudrasUsed; //some sort of mask
|
|
||||||
[FieldOffset(5)] public byte Ninki;
|
|
||||||
[FieldOffset(6)] public byte NumHutonManualCasts; //wtf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct PLDGauge {
|
|
||||||
[FieldOffset(0)] public byte GaugeAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct RDMGauge {
|
|
||||||
[FieldOffset(0)] public byte WhiteGauge;
|
|
||||||
[FieldOffset(1)] public byte BlackGauge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct SAMGauge {
|
|
||||||
|
|
||||||
[FieldOffset(3)] public byte Kenki;
|
|
||||||
[FieldOffset(4)] public Sen Sen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct SCHGauge {
|
|
||||||
[FieldOffset(2)] public byte NumAetherflowStacks;
|
|
||||||
[FieldOffset(3)] public byte FairyGaugeAmount;
|
|
||||||
[FieldOffset(4)] public short SeraphTimer;
|
|
||||||
[FieldOffset(6)] public DismissedFairy DismissedFairy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct SMNGauge {
|
|
||||||
|
|
||||||
//Unfinished
|
|
||||||
[FieldOffset(0)] public short TimerRemaining;
|
|
||||||
[FieldOffset(2)] public SummonPet ReturnSummon;
|
|
||||||
[FieldOffset(3)] public PetGlam ReturnSummonGlam;
|
|
||||||
[FieldOffset(4)] public byte NumStacks;
|
|
||||||
|
|
||||||
public bool IsPhoenixReady() {
|
|
||||||
return (NumStacks & 0x10) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBahamutReady() {
|
|
||||||
return (NumStacks & 8) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasAetherflowStacks() {
|
|
||||||
return (NumStacks & 3) > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct WARGauge {
|
|
||||||
[FieldOffset(0)] public byte BeastGaugeAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Structs.JobGauge {
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public struct WHMGauge {
|
|
||||||
[FieldOffset(2)] public short LilyTimer; //Counts to 30k = 30s
|
|
||||||
[FieldOffset(4)] public byte NumLilies;
|
|
||||||
[FieldOffset(5)] public byte NumBloodLily;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
namespace Dalamud.Game.Command {
|
|
||||||
/// <summary>
|
|
||||||
/// This class describes a registered command.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CommandInfo {
|
|
||||||
/// <summary>
|
|
||||||
/// The function to be executed when the command is dispatched.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command itself.</param>
|
|
||||||
/// <param name="arguments">The arguments supplied to the command, ready for parsing.</param>
|
|
||||||
public delegate void HandlerDelegate(string command, string arguments);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="HandlerDelegate"/> which will be called when the command is dispatched.
|
|
||||||
/// </summary>
|
|
||||||
public HandlerDelegate Handler { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The help message for this command.
|
|
||||||
/// </summary>
|
|
||||||
public string HelpMessage { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If this command should be shown in the help output.
|
|
||||||
/// </summary>
|
|
||||||
public bool ShowInHelp { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new CommandInfo with the provided handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handler"></param>
|
|
||||||
public CommandInfo(HandlerDelegate handler) {
|
|
||||||
Handler = handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Command {
|
|
||||||
/// <summary>
|
|
||||||
/// This class manages registered in-game slash commands.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class CommandManager {
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
private readonly Dictionary<string, CommandInfo> commandMap = new Dictionary<string, CommandInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read-only list of all registered commands.
|
|
||||||
/// </summary>
|
|
||||||
public ReadOnlyDictionary<string, CommandInfo> Commands =>
|
|
||||||
new ReadOnlyDictionary<string, CommandInfo>(this.commandMap);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexEn =
|
|
||||||
new Regex(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexJp = new Regex(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexDe =
|
|
||||||
new Regex(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex commandRegexFr =
|
|
||||||
new Regex(@"^La commande texte “(?<command>.+)” n'existe pas\.$",
|
|
||||||
RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private readonly Regex currentLangCommandRegex;
|
|
||||||
|
|
||||||
|
|
||||||
public CommandManager(Dalamud dalamud, ClientLanguage language) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
|
|
||||||
switch (language) {
|
|
||||||
case ClientLanguage.Japanese:
|
|
||||||
this.currentLangCommandRegex = this.commandRegexJp;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.English:
|
|
||||||
this.currentLangCommandRegex = this.commandRegexEn;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.German:
|
|
||||||
this.currentLangCommandRegex = this.commandRegexDe;
|
|
||||||
break;
|
|
||||||
case ClientLanguage.French:
|
|
||||||
this.currentLangCommandRegex = this.commandRegexFr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
dalamud.Framework.Gui.Chat.OnChatMessageRaw += OnChatMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref StdString sender,
|
|
||||||
ref StdString message, ref bool isHandled) {
|
|
||||||
if (type == XivChatType.GatheringSystemMessage && senderId == 0) {
|
|
||||||
var cmdMatch = this.currentLangCommandRegex.Match(message.Value).Groups["command"];
|
|
||||||
if (cmdMatch.Success) {
|
|
||||||
// Yes, it's a chat command.
|
|
||||||
var command = cmdMatch.Value;
|
|
||||||
if (ProcessCommand(command)) isHandled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ProcessCommand(string content) {
|
|
||||||
string command;
|
|
||||||
string argument;
|
|
||||||
|
|
||||||
var speratorPosition = content.IndexOf(' ');
|
|
||||||
if (speratorPosition == -1 || speratorPosition + 1 >= content.Length) {
|
|
||||||
// If no space was found or ends with the space. Process them as a no argument
|
|
||||||
command = content;
|
|
||||||
argument = string.Empty;
|
|
||||||
} else {
|
|
||||||
// e.g.)
|
|
||||||
// /testcommand arg1
|
|
||||||
// => Total of 17 chars
|
|
||||||
// => command: 0-12 (12 chars)
|
|
||||||
// => argument: 13-17 (4 chars)
|
|
||||||
// => content.IndexOf(' ') == 12
|
|
||||||
command = content.Substring(0, speratorPosition);
|
|
||||||
|
|
||||||
var argStart = speratorPosition + 1;
|
|
||||||
argument = content.Substring(argStart, content.Length - argStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DispatchCommand(command, argument, handler);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispatch the handling of a command.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command to dispatch.</param>
|
|
||||||
/// <param name="argument">The provided arguments.</param>
|
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing this command.</param>
|
|
||||||
public void DispatchCommand(string command, string argument, CommandInfo info) {
|
|
||||||
try {
|
|
||||||
info.Handler(command, argument);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command,
|
|
||||||
argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a command handler, which you can use to add your own custom commands to the in-game chat.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command to register.</param>
|
|
||||||
/// <param name="info">A <see cref="CommandInfo"/> object describing the command.</param>
|
|
||||||
/// <returns>If adding was successful.</returns>
|
|
||||||
public bool AddHandler(string command, CommandInfo info) {
|
|
||||||
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.commandMap.Add(command, info);
|
|
||||||
return true;
|
|
||||||
} catch (ArgumentException) {
|
|
||||||
Log.Error("Command {CommandName} is already registered.", command);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a command from the command handlers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command">The command to remove.</param>
|
|
||||||
/// <returns>If the removal was successful.</returns>
|
|
||||||
public bool RemoveHandler(string command) {
|
|
||||||
return this.commandMap.Remove(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using EasyHook;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal
|
|
||||||
{
|
|
||||||
class AntiDebug : IDisposable
|
|
||||||
{
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
|
||||||
private delegate bool IsDebuggerPresentDelegate();
|
|
||||||
|
|
||||||
private Hook<IsDebuggerPresentDelegate> debuggerPresentHook;
|
|
||||||
|
|
||||||
public AntiDebug() {
|
|
||||||
this.debuggerPresentHook = new Hook<IsDebuggerPresentDelegate>(LocalHook.GetProcAddress("Kernel32", "IsDebuggerPresent"),
|
|
||||||
new IsDebuggerPresentDelegate(IsDebuggerPresentDetour));
|
|
||||||
|
|
||||||
Log.Verbose("IsDebuggerPresent address {IsDebuggerPresent}", this.debuggerPresentHook.Address);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.debuggerPresentHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.debuggerPresentHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsDebuggerPresentDetour() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
|
||||||
public abstract class BaseAddressResolver {
|
|
||||||
protected bool IsResolved { get; set; }
|
|
||||||
|
|
||||||
public void Setup(SigScanner scanner) {
|
|
||||||
// Because C# don't allow to call virtual function while in ctor
|
|
||||||
// we have to do this shit :\
|
|
||||||
|
|
||||||
if (IsResolved) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanner.Is32BitProcess) {
|
|
||||||
Setup32Bit(scanner);
|
|
||||||
} else {
|
|
||||||
Setup64Bit(scanner);
|
|
||||||
}
|
|
||||||
SetupInternal(scanner);
|
|
||||||
|
|
||||||
IsResolved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Setup32Bit(SigScanner scanner) {
|
|
||||||
throw new NotSupportedException("32 bit version is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Setup64Bit(SigScanner sig) {
|
|
||||||
throw new NotSupportedException("64 bit version is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void SetupInternal(SigScanner scanner) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
protected T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class {
|
|
||||||
// Get vtable
|
|
||||||
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
|
||||||
|
|
||||||
// Get an address to the function
|
|
||||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
|
||||||
|
|
||||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI {
|
|
||||||
public interface ISwapChainAddressResolver {
|
|
||||||
IntPtr Present { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI
|
|
||||||
{
|
|
||||||
public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver
|
|
||||||
{
|
|
||||||
public IntPtr Present { get; set; }
|
|
||||||
//public IntPtr ResizeBuffers { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig)
|
|
||||||
{
|
|
||||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "dxgi.dll");
|
|
||||||
|
|
||||||
Log.Debug($"Found DXGI: {module.BaseAddress.ToInt64():X}");
|
|
||||||
|
|
||||||
var scanner = new SigScanner(module);
|
|
||||||
|
|
||||||
// This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function.
|
|
||||||
Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37;
|
|
||||||
|
|
||||||
|
|
||||||
// seems unnecessary for now, but we may need to handle it
|
|
||||||
//ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 ?? 48 81 EC C0 00 00 00");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
using SharpDX.Direct3D;
|
|
||||||
using SharpDX.Direct3D11;
|
|
||||||
using SharpDX.DXGI;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using SharpDX.Windows;
|
|
||||||
using Device = SharpDX.Direct3D11.Device;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.DXGI
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* This method of getting the SwapChain Addresses is currently not used.
|
|
||||||
* If the normal AddressResolver(SigScanner) fails, we should use it as a fallback.(Linux?)
|
|
||||||
*/
|
|
||||||
public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver
|
|
||||||
{
|
|
||||||
private const int DxgiSwapchainMethodCount = 18;
|
|
||||||
private const int D3D11DeviceMethodCount = 43;
|
|
||||||
|
|
||||||
private static SwapChainDescription CreateSwapChainDescription(IntPtr renderForm) {
|
|
||||||
return new SwapChainDescription {
|
|
||||||
BufferCount = 1,
|
|
||||||
Flags = SwapChainFlags.None,
|
|
||||||
IsWindowed = true,
|
|
||||||
ModeDescription = new ModeDescription(100, 100, new Rational(60, 1), Format.R8G8B8A8_UNorm),
|
|
||||||
OutputHandle = renderForm,
|
|
||||||
SampleDescription = new SampleDescription(1, 0),
|
|
||||||
SwapEffect = SwapEffect.Discard,
|
|
||||||
Usage = Usage.RenderTargetOutput
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr[] GetVTblAddresses(IntPtr pointer, int numberOfMethods)
|
|
||||||
{
|
|
||||||
return GetVTblAddresses(pointer, 0, numberOfMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr[] GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods)
|
|
||||||
{
|
|
||||||
List<IntPtr> vtblAddresses = new List<IntPtr>();
|
|
||||||
IntPtr vTable = Marshal.ReadIntPtr(pointer);
|
|
||||||
for (int i = startIndex; i < startIndex + numberOfMethods; i++)
|
|
||||||
vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes
|
|
||||||
|
|
||||||
return vtblAddresses.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IntPtr> d3d11VTblAddresses = null;
|
|
||||||
private List<IntPtr> dxgiSwapChainVTblAddresses = null;
|
|
||||||
|
|
||||||
#region Internal device resources
|
|
||||||
|
|
||||||
private Device device;
|
|
||||||
private SwapChain swapChain;
|
|
||||||
private RenderForm renderForm;
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Addresses
|
|
||||||
|
|
||||||
public IntPtr Present { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
if (this.d3d11VTblAddresses == null) {
|
|
||||||
this.d3d11VTblAddresses = new List<IntPtr>();
|
|
||||||
this.dxgiSwapChainVTblAddresses = new List<IntPtr>();
|
|
||||||
|
|
||||||
#region Get Device and SwapChain method addresses
|
|
||||||
|
|
||||||
// Create temporary device + swapchain and determine method addresses
|
|
||||||
this.renderForm = new RenderForm();
|
|
||||||
Device.CreateWithSwapChain(
|
|
||||||
DriverType.Hardware,
|
|
||||||
DeviceCreationFlags.BgraSupport,
|
|
||||||
CreateSwapChainDescription(this.renderForm.Handle),
|
|
||||||
out this.device,
|
|
||||||
out this.swapChain
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.device != null && this.swapChain != null) {
|
|
||||||
this.d3d11VTblAddresses.AddRange(
|
|
||||||
GetVTblAddresses(this.device.NativePointer, D3D11DeviceMethodCount));
|
|
||||||
this.dxgiSwapChainVTblAddresses.AddRange(
|
|
||||||
GetVTblAddresses(this.swapChain.NativePointer, DxgiSwapchainMethodCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.device?.Dispose();
|
|
||||||
this.swapChain?.Dispose();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
Present = this.dxgiSwapChainVTblAddresses[8];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Game.Internal.Gui;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Game.Internal.Network;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
using Dalamud.Game.Internal.File;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
|
||||||
/// <summary>
|
|
||||||
/// This class represents the Framework of the native game client and grants access to various subsystems.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class Framework : IDisposable {
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
|
||||||
|
|
||||||
public delegate void OnUpdateDelegate(Framework framework);
|
|
||||||
|
|
||||||
public event OnUpdateDelegate OnUpdateEvent;
|
|
||||||
|
|
||||||
private Hook<OnUpdateDetour> updateHook;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A raw pointer to the instance of Client::Framework
|
|
||||||
/// </summary>
|
|
||||||
private FrameworkAddressResolver Address { get; }
|
|
||||||
|
|
||||||
#region Subsystems
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The GUI subsystem, used to access e.g. chat.
|
|
||||||
/// </summary>
|
|
||||||
public GameGui Gui { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Network subsystem, used to access network data.
|
|
||||||
/// </summary>
|
|
||||||
public GameNetwork Network { get; private set; }
|
|
||||||
|
|
||||||
//public ResourceManager Resource { get; private set; }
|
|
||||||
|
|
||||||
public LibcFunction Libc { get; private set; }
|
|
||||||
|
|
||||||
private AntiDebug antiDebug;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public Framework(SigScanner scanner, Dalamud dalamud) {
|
|
||||||
Address = new FrameworkAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("Framework address {FrameworkAddress}", Address.BaseAddress);
|
|
||||||
if (Address.BaseAddress == IntPtr.Zero) {
|
|
||||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook virtual functions
|
|
||||||
HookVTable();
|
|
||||||
|
|
||||||
// Initialize subsystems
|
|
||||||
Libc = new LibcFunction(scanner);
|
|
||||||
|
|
||||||
Gui = new GameGui(Address.GuiManager, scanner, dalamud);
|
|
||||||
|
|
||||||
Network = new GameNetwork(dalamud, scanner);
|
|
||||||
|
|
||||||
//Resource = new ResourceManager(dalamud, scanner);
|
|
||||||
|
|
||||||
this.antiDebug = new AntiDebug();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HookVTable() {
|
|
||||||
var vtable = Marshal.ReadIntPtr(Address.BaseAddress);
|
|
||||||
// Virtual function layout:
|
|
||||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
|
||||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
|
||||||
// .rdata:00000001411F1FF0 dq offset sub_1400936E0
|
|
||||||
// .rdata:00000001411F1FF8 dq offset sub_1400939E0
|
|
||||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
|
||||||
|
|
||||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
|
||||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, new OnUpdateDetour(HandleFrameworkUpdate), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.antiDebug.Enable();
|
|
||||||
Gui.Enable();
|
|
||||||
Network.Enable();
|
|
||||||
//Resource.Enable();
|
|
||||||
|
|
||||||
this.updateHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.antiDebug.Dispose();
|
|
||||||
Gui.Dispose();
|
|
||||||
Network.Dispose();
|
|
||||||
//Resource.Dispose();
|
|
||||||
|
|
||||||
this.updateHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HandleFrameworkUpdate(IntPtr framework) {
|
|
||||||
try {
|
|
||||||
Gui.Chat.UpdateQueue(this);
|
|
||||||
Network.UpdateQueue(this);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
OnUpdateEvent?.Invoke(this);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.updateHook.Original(framework);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal {
|
|
||||||
public sealed class FrameworkAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr BaseAddress { get; private set; }
|
|
||||||
|
|
||||||
public IntPtr GuiManager { get; private set; }
|
|
||||||
|
|
||||||
public IntPtr ScriptManager { get; private set; }
|
|
||||||
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
SetupFramework(sig);
|
|
||||||
|
|
||||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
|
||||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
|
||||||
GuiManager = Marshal.ReadIntPtr(BaseAddress, 0x2C08);
|
|
||||||
|
|
||||||
// Called from Framework::Init
|
|
||||||
ScriptManager = 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("48C705????????00000000 E8???????? 488D??????0000 E8???????? 488D");
|
|
||||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
|
||||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
|
||||||
|
|
||||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
|
||||||
BaseAddress = Marshal.ReadIntPtr(pFramework);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Game.Chat.SeStringHandling;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Discord.Rest;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public sealed class ChatGui : IDisposable {
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName,
|
|
||||||
IntPtr message,
|
|
||||||
uint senderId, IntPtr parameter);
|
|
||||||
|
|
||||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message,
|
|
||||||
ref bool isHandled);
|
|
||||||
|
|
||||||
public delegate void OnMessageRawDelegate(XivChatType type, uint senderId, ref StdString sender, ref StdString message,
|
|
||||||
ref bool isHandled);
|
|
||||||
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
|
||||||
|
|
||||||
private readonly Queue<XivChatEntry> chatQueue = new Queue<XivChatEntry>();
|
|
||||||
|
|
||||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
|
||||||
|
|
||||||
public event OnMessageDelegate OnChatMessage;
|
|
||||||
[Obsolete("Please use OnChatMessage instead. For modifications, it will take precedence.")]
|
|
||||||
public event OnMessageRawDelegate OnChatMessageRaw;
|
|
||||||
|
|
||||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
|
||||||
|
|
||||||
public int LastLinkedItemId { get; private set; }
|
|
||||||
public byte LastLinkedItemFlags { get; private set; }
|
|
||||||
|
|
||||||
private ChatGuiAddressResolver Address { get; }
|
|
||||||
|
|
||||||
private IntPtr baseAddress = IntPtr.Zero;
|
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
|
|
||||||
Address = new ChatGuiAddressResolver(baseAddress);
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("Chat manager address {ChatManager}", Address.BaseAddress);
|
|
||||||
|
|
||||||
this.printMessageHook =
|
|
||||||
new Hook<PrintMessageDelegate>(Address.PrintMessage, new PrintMessageDelegate(HandlePrintMessageDetour),
|
|
||||||
this);
|
|
||||||
this.populateItemLinkHook =
|
|
||||||
new Hook<PopulateItemLinkDelegate>(Address.PopulateItemLinkObject,
|
|
||||||
new PopulateItemLinkDelegate(HandlePopulateItemLinkDetour),
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.printMessageHook.Enable();
|
|
||||||
this.populateItemLinkHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.printMessageHook.Dispose();
|
|
||||||
this.populateItemLinkHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
|
|
||||||
try {
|
|
||||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
|
||||||
|
|
||||||
LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
|
||||||
LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
|
||||||
|
|
||||||
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{LastLinkedItemId}");
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
|
||||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage,
|
|
||||||
uint senderid, IntPtr parameter) {
|
|
||||||
var retVal = IntPtr.Zero;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var sender = StdString.ReadFromPointer(pSenderName);
|
|
||||||
var message = StdString.ReadFromPointer(pMessage);
|
|
||||||
|
|
||||||
var parsedSender = SeString.Parse(sender.RawData);
|
|
||||||
var parsedMessage = SeString.Parse(message.RawData);
|
|
||||||
|
|
||||||
//Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}");
|
|
||||||
|
|
||||||
var originalMessageData = (byte[]) message.RawData.Clone();
|
|
||||||
var oldEdited = parsedMessage.Encode();
|
|
||||||
|
|
||||||
// Call events
|
|
||||||
var isHandled = false;
|
|
||||||
OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
|
|
||||||
OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled);
|
|
||||||
|
|
||||||
var newEdited = parsedMessage.Encode();
|
|
||||||
|
|
||||||
if (!FastByteArrayCompare(oldEdited, newEdited)) {
|
|
||||||
Log.Verbose("SeString was edited, taking precedence over StdString edit.");
|
|
||||||
message.RawData = newEdited;
|
|
||||||
}
|
|
||||||
Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}");
|
|
||||||
|
|
||||||
var messagePtr = pMessage;
|
|
||||||
OwnedStdString allocatedString = null;
|
|
||||||
|
|
||||||
if (!FastByteArrayCompare(originalMessageData, message.RawData)) {
|
|
||||||
allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData);
|
|
||||||
Log.Debug(
|
|
||||||
$"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
|
||||||
messagePtr = allocatedString.Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the original chat if it's handled.
|
|
||||||
if (!isHandled)
|
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
|
|
||||||
|
|
||||||
if (this.baseAddress == IntPtr.Zero)
|
|
||||||
this.baseAddress = manager;
|
|
||||||
|
|
||||||
allocatedString?.Dispose();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
|
||||||
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
|
||||||
// Distributed under the MIT/X11 software license
|
|
||||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
// https://stackoverflow.com/a/8808245
|
|
||||||
static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2)
|
|
||||||
{
|
|
||||||
if (a1 == a2) return true;
|
|
||||||
if (a1 == null || a2 == null || a1.Length != a2.Length)
|
|
||||||
return false;
|
|
||||||
fixed (byte* p1 = a1, p2 = a2)
|
|
||||||
{
|
|
||||||
byte* x1 = p1, x2 = p2;
|
|
||||||
int l = a1.Length;
|
|
||||||
for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
|
|
||||||
if (*((long*)x1) != *((long*)x2)) return false;
|
|
||||||
if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
|
|
||||||
if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
|
|
||||||
if ((l & 1) != 0) if (*((byte*)x1) != *((byte*)x2)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
|
||||||
/// later to be processed when UpdateQueue() is called.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="chat">A message to send.</param>
|
|
||||||
public void PrintChat(XivChatEntry chat) {
|
|
||||||
this.chatQueue.Enqueue(chat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Print(string message) {
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(message)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PrintError(string message) {
|
|
||||||
PrintChat(new XivChatEntry {
|
|
||||||
MessageBytes = Encoding.UTF8.GetBytes(message),
|
|
||||||
Type = XivChatType.Urgent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process a chat queue.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateQueue(Framework framework) {
|
|
||||||
while (this.chatQueue.Count > 0) {
|
|
||||||
var chat = this.chatQueue.Dequeue();
|
|
||||||
|
|
||||||
var sender = chat.Name ?? "";
|
|
||||||
var message = chat.MessageBytes ?? new byte[0];
|
|
||||||
|
|
||||||
if (this.baseAddress != IntPtr.Zero)
|
|
||||||
using (var senderVec = framework.Libc.NewString(Encoding.UTF8.GetBytes(sender)))
|
|
||||||
using (var messageVec = framework.Libc.NewString(message))
|
|
||||||
{
|
|
||||||
Log.Verbose($"String allocated to {messageVec.Address.ToInt64():X}");
|
|
||||||
this.printMessageHook.Original(this.baseAddress, chat.Type, senderVec.Address,
|
|
||||||
messageVec.Address, chat.SenderId, chat.Parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr BaseAddress { get; }
|
|
||||||
|
|
||||||
public IntPtr PrintMessage { get; private set; }
|
|
||||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
|
||||||
|
|
||||||
public ChatGuiAddressResolver(IntPtr baseAddres) {
|
|
||||||
BaseAddress = baseAddres;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
--- for reference: 4.57 ---
|
|
||||||
|
|
||||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
|
||||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
|
||||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
|
||||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
|
||||||
.text:00000001405CD210
|
|
||||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
|
||||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
|
||||||
.text:00000001405CD210 var_210 = word ptr -210h
|
|
||||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
|
||||||
.text:00000001405CD210 var_200 = word ptr -200h
|
|
||||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
|
||||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
|
||||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
|
||||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
|
||||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
|
||||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
|
||||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
|
||||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
|
||||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
|
||||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
|
||||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
|
||||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
|
||||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
|
||||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
|
||||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
|
||||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
|
||||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
|
||||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
|
||||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
|
||||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
|
||||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
|
||||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
|
||||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
|
||||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
|
||||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
|
||||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
|
||||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
|
||||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
|
||||||
.text:00000001405CD210
|
|
||||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
|
||||||
.text:00000001405CD210 push rbp
|
|
||||||
.text:00000001405CD212 push rdi
|
|
||||||
.text:00000001405CD213 push r14
|
|
||||||
.text:00000001405CD215 push r15
|
|
||||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
|
||||||
.text:00000001405CD21F sub rsp, 228h
|
|
||||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
|
||||||
.text:00000001405CD22D xor rax, rsp
|
|
||||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
|
||||||
.text:00000001405CD237 xor r10b, r10b
|
|
||||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
|
||||||
.text:00000001405CD23F xor eax, eax
|
|
||||||
.text:00000001405CD241 mov r11, r9
|
|
||||||
.text:00000001405CD244 mov r14, r8
|
|
||||||
.text:00000001405CD247 mov r9d, eax
|
|
||||||
.text:00000001405CD24A movzx r15d, dx
|
|
||||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
|
||||||
.text:00000001405CD255 mov rdi, rcx
|
|
||||||
*/
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1???
|
|
||||||
PrintMessage =
|
|
||||||
sig.ScanText(
|
|
||||||
"4055 53 56 4154 4157 48 8d ac 24 ?? ?? ?? ?? 48 81 ec 20 02 00 00 48 8b 05"
|
|
||||||
);
|
|
||||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
|
||||||
|
|
||||||
//PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
|
||||||
|
|
||||||
//PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
|
||||||
|
|
||||||
//PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
|
||||||
PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Game.Chat;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public sealed class GameGui : IDisposable {
|
|
||||||
private GameGuiAddressResolver Address { get; }
|
|
||||||
|
|
||||||
public ChatGui Chat { get; private set; }
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr SetGlobalBgmDelegate(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6);
|
|
||||||
|
|
||||||
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
|
|
||||||
|
|
||||||
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
|
||||||
Address = new GameGuiAddressResolver(baseAddress);
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== G A M E G U I =====");
|
|
||||||
|
|
||||||
Log.Verbose("GameGuiManager address {Address}", Address.BaseAddress);
|
|
||||||
Log.Verbose("SetGlobalBgm address {Address}", Address.SetGlobalBgm);
|
|
||||||
|
|
||||||
Chat = new ChatGui(Address.ChatManager, scanner, dalamud);
|
|
||||||
|
|
||||||
this.setGlobalBgmHook =
|
|
||||||
new Hook<SetGlobalBgmDelegate>(Address.SetGlobalBgm,
|
|
||||||
new SetGlobalBgmDelegate(HandleSetGlobalBgmDetour),
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) {
|
|
||||||
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
|
|
||||||
|
|
||||||
Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal);
|
|
||||||
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
Chat.Enable();
|
|
||||||
this.setGlobalBgmHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
Chat.Dispose();
|
|
||||||
this.setGlobalBgmHook.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public sealed class GameGuiAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr BaseAddress { get; private set; }
|
|
||||||
|
|
||||||
public IntPtr ChatManager { get; private set; }
|
|
||||||
|
|
||||||
public IntPtr SetGlobalBgm { get; private set; }
|
|
||||||
|
|
||||||
public GameGuiAddressResolver(IntPtr baseAddress) {
|
|
||||||
BaseAddress = baseAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr GetChatManagerDelegate(IntPtr guiManager);
|
|
||||||
|
|
||||||
protected override void SetupInternal(SigScanner scanner) {
|
|
||||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
|
||||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
|
||||||
ChatManager = BaseAddress + 0x13E0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
//SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58 41 83 7A ?? ?? 76 51 4D 8B 92 ?? ?? ?? ?? 0F B6 44 24 ?? 49 81 C2 ?? ?? ?? ?? 66 41 89 4A ?? 33 C9 41 88 52 30 41 89 4A 14 66 41 89 4A ?? 41 88 42 12 49 89 4A 38 41 89 4A 40 49 89 4A 48 41 38 4A 30 74 14 8B 44 24 28 41 89 42 40 45 89 42 38");
|
|
||||||
SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
using Dalamud.Game.ClientState;
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
|
||||||
using Dalamud.Game.ClientState.Structs.JobGauge;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
public class TargetManager {
|
|
||||||
public delegate IntPtr GetTargetDelegate(IntPtr manager);
|
|
||||||
|
|
||||||
private Hook<GetTargetDelegate> getTargetHook;
|
|
||||||
|
|
||||||
private TargetManagerAddressResolver Address;
|
|
||||||
|
|
||||||
public unsafe TargetManager(Dalamud dalamud, SigScanner scanner) {
|
|
||||||
this.Address = new TargetManagerAddressResolver();
|
|
||||||
this.Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== T A R G E T M A N A G E R =====");
|
|
||||||
Log.Verbose("GetTarget address {GetTarget}", Address.GetTarget);
|
|
||||||
|
|
||||||
this.getTargetHook = new Hook<GetTargetDelegate>(this.Address.GetTarget, new GetTargetDelegate(GetTargetDetour), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.getTargetHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.getTargetHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetTargetDetour(IntPtr manager)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
var res = this.getTargetHook.Original(manager);
|
|
||||||
|
|
||||||
var test = Marshal.ReadInt32(res);
|
|
||||||
|
|
||||||
Log.Debug($"GetTargetDetour {manager.ToInt64():X} -> RET: {res:X}");
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Exception GetTargetDetour hook.");
|
|
||||||
return this.getTargetHook.Original(manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Gui {
|
|
||||||
class TargetManagerAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr GetTarget { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
this.GetTarget = sig.ScanText("40 57 48 83 EC 40 48 8B F9 48 8B 49 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 00 74 33 48 8B 4F 08 48 8B 01 FF 50 40 66 83 B8 CA 81 00 00 04 74");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
|
||||||
public sealed class LibcFunction {
|
|
||||||
// TODO: prolly callconv is not okay in x86
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)]byte[] content, IntPtr size);
|
|
||||||
|
|
||||||
// TODO: prolly callconv is not okay in x86
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
|
|
||||||
|
|
||||||
private LibcFunctionAddressResolver Address { get; }
|
|
||||||
|
|
||||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
|
||||||
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
|
||||||
|
|
||||||
public LibcFunction(SigScanner scanner) {
|
|
||||||
Address = new LibcFunctionAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(Address.StdStringFromCstring);
|
|
||||||
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(Address.StdStringDeallocate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OwnedStdString NewString(byte[] content) {
|
|
||||||
// While 0x70 bytes in the memory should be enough in DX11 version,
|
|
||||||
// I don't trust my analysis so we're just going to allocate almost two times more than that.
|
|
||||||
var pString = Marshal.AllocHGlobal(256);
|
|
||||||
|
|
||||||
// Initialize a string
|
|
||||||
var npos = new IntPtr(0xFFFFFFFF); // assumed to be -1 (0xFFFFFFFF in x86, 0xFFFFFFFF_FFFFFFFF in amd64)
|
|
||||||
var pReallocString = this.stdStringCtorCString(pString, content, npos);
|
|
||||||
|
|
||||||
//Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
|
|
||||||
|
|
||||||
return new OwnedStdString(pReallocString, DeallocateStdString);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DeallocateStdString(IntPtr address) {
|
|
||||||
this.stdStringDeallocate(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Security.Policy;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
|
||||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver {
|
|
||||||
private delegate IntPtr StringFromCString();
|
|
||||||
|
|
||||||
public IntPtr StdStringFromCstring { get; private set; }
|
|
||||||
public IntPtr StdStringDeallocate { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
|
||||||
StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
|
||||||
public sealed class OwnedStdString : IDisposable {
|
|
||||||
internal delegate void DeallocatorDelegate(IntPtr address);
|
|
||||||
|
|
||||||
// ala. the drop flag
|
|
||||||
private bool isDisposed;
|
|
||||||
|
|
||||||
private readonly DeallocatorDelegate dealloc;
|
|
||||||
|
|
||||||
public IntPtr Address { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Construct a wrapper around std::string
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Violating any of these might cause an undefined hehaviour.
|
|
||||||
/// 1. This function takes the ownership of the address.
|
|
||||||
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
|
|
||||||
/// 3. std::string object pointed by address must be initialized before calling this function.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="address"></param>
|
|
||||||
/// <param name="dealloc">A deallocator function.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) {
|
|
||||||
Address = address;
|
|
||||||
this.dealloc = dealloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
~OwnedStdString() {
|
|
||||||
ReleaseUnmanagedResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReleaseUnmanagedResources() {
|
|
||||||
if (Address == IntPtr.Zero) {
|
|
||||||
// Something got seriously fucked.
|
|
||||||
throw new AccessViolationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deallocate inner string first
|
|
||||||
this.dealloc(Address);
|
|
||||||
|
|
||||||
// Free the heap
|
|
||||||
Marshal.FreeHGlobal(Address);
|
|
||||||
|
|
||||||
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
|
|
||||||
Address = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
// No double free plz, kthx.
|
|
||||||
if (this.isDisposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.isDisposed = true;
|
|
||||||
|
|
||||||
ReleaseUnmanagedResources();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StdString Read() {
|
|
||||||
return StdString.ReadFromPointer(Address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
|
||||||
/// <summary>
|
|
||||||
/// Interation with std::string
|
|
||||||
/// </summary>
|
|
||||||
public class StdString {
|
|
||||||
public static StdString ReadFromPointer(IntPtr cstring) {
|
|
||||||
unsafe {
|
|
||||||
if (cstring == IntPtr.Zero) {
|
|
||||||
throw new ArgumentNullException(nameof(cstring));
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerAddress = Marshal.ReadIntPtr(cstring);
|
|
||||||
if (innerAddress == IntPtr.Zero) {
|
|
||||||
throw new NullReferenceException("Inner reference to the cstring is null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
// Count the number of chars. String is assumed to be zero-terminated.
|
|
||||||
while (Marshal.ReadByte(innerAddress + count) != 0) {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw copy, as UTF8 string conversion is lossy
|
|
||||||
var rawData = new byte[count];
|
|
||||||
Marshal.Copy(innerAddress, rawData, 0, count);
|
|
||||||
|
|
||||||
return new StdString {
|
|
||||||
RawData = rawData,
|
|
||||||
Value = Encoding.UTF8.GetString(rawData)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private StdString() { }
|
|
||||||
|
|
||||||
public string Value { get; private set; }
|
|
||||||
|
|
||||||
public byte[] RawData { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Network {
|
|
||||||
public sealed class GameNetwork : IDisposable {
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate void ProcessZonePacketDelegate(IntPtr a, IntPtr b, IntPtr dataPtr);
|
|
||||||
|
|
||||||
private readonly Hook<ProcessZonePacketDelegate> processZonePacketHook;
|
|
||||||
|
|
||||||
private GameNetworkAddressResolver Address { get; }
|
|
||||||
private IntPtr baseAddress;
|
|
||||||
|
|
||||||
public delegate void OnZonePacketDelegate(IntPtr dataPtr);
|
|
||||||
|
|
||||||
public OnZonePacketDelegate OnZonePacket;
|
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
private readonly Queue<byte[]> zoneInjectQueue = new Queue<byte[]>();
|
|
||||||
|
|
||||||
public GameNetwork(Dalamud dalamud, SigScanner scanner) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
Address = new GameNetworkAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
|
||||||
Log.Verbose("ProcessZonePacket address {ProcessZonePacket}", Address.ProcessZonePacket);
|
|
||||||
|
|
||||||
this.processZonePacketHook =
|
|
||||||
new Hook<ProcessZonePacketDelegate>(Address.ProcessZonePacket,
|
|
||||||
new ProcessZonePacketDelegate(ProcessZonePacketDetour),
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.processZonePacketHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.processZonePacketHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessZonePacketDetour(IntPtr a, IntPtr b, IntPtr dataPtr) {
|
|
||||||
this.baseAddress = a;
|
|
||||||
|
|
||||||
// Call events
|
|
||||||
this.OnZonePacket?.Invoke(dataPtr);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.processZonePacketHook.Original(a, b, dataPtr);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
string header;
|
|
||||||
try {
|
|
||||||
var data = new byte[32];
|
|
||||||
Marshal.Copy(dataPtr, data, 0, 32);
|
|
||||||
header = BitConverter.ToString(data);
|
|
||||||
} catch (Exception) {
|
|
||||||
header = "failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Error(ex, "Exception on ProcessZonePacket hook. Header: " + header);
|
|
||||||
|
|
||||||
this.processZonePacketHook.Original(a, b, dataPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
public void InjectZoneProtoPacket(byte[] data) {
|
|
||||||
this.zoneInjectQueue.Enqueue(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InjectActorControl(short cat, int param1) {
|
|
||||||
var packetData = new byte[] {
|
|
||||||
0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00
|
|
||||||
};
|
|
||||||
|
|
||||||
BitConverter.GetBytes((short) cat).CopyTo(packetData, 0x10);
|
|
||||||
|
|
||||||
BitConverter.GetBytes((UInt32) param1).CopyTo(packetData, 0x14);
|
|
||||||
|
|
||||||
InjectZoneProtoPacket(packetData);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process a chat queue.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateQueue(Framework framework)
|
|
||||||
{
|
|
||||||
while (this.zoneInjectQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var packetData = this.zoneInjectQueue.Dequeue();
|
|
||||||
|
|
||||||
var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length);
|
|
||||||
Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
|
|
||||||
|
|
||||||
if (this.baseAddress != IntPtr.Zero) {
|
|
||||||
this.processZonePacketHook.Original(this.baseAddress, IntPtr.Zero, unmanagedPacketData);
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(unmanagedPacketData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Network {
|
|
||||||
public sealed class GameNetworkAddressResolver : BaseAddressResolver {
|
|
||||||
public IntPtr ProcessZonePacket { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
|
||||||
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
|
||||||
ProcessZonePacket = sig.ScanText("48 89 74 24 ?? 57 48 83 EC 50 8B FA 49 8B F0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Game.Internal.Libc;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.File
|
|
||||||
{
|
|
||||||
public class ResourceManager {
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7);
|
|
||||||
private readonly Hook<GetResourceAsyncDelegate> getResourceAsyncHook;
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
|
||||||
private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6);
|
|
||||||
private readonly Hook<GetResourceSyncDelegate> getResourceSyncHook;
|
|
||||||
|
|
||||||
private ResourceManagerAddressResolver Address { get; }
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
class ResourceHandleHookInfo {
|
|
||||||
public string Path { get; set; }
|
|
||||||
public Stream DetourFile { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<IntPtr, ResourceHandleHookInfo> resourceHookMap = new Dictionary<IntPtr, ResourceHandleHookInfo>();
|
|
||||||
|
|
||||||
public ResourceManager(Dalamud dalamud, SigScanner scanner) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
Address = new ResourceManagerAddressResolver();
|
|
||||||
Address.Setup(scanner);
|
|
||||||
|
|
||||||
Log.Verbose("===== R E S O U R C E M A N A G E R =====");
|
|
||||||
Log.Verbose("GetResourceAsync address {GetResourceAsync}", Address.GetResourceAsync);
|
|
||||||
Log.Verbose("GetResourceSync address {GetResourceSync}", Address.GetResourceSync);
|
|
||||||
|
|
||||||
this.getResourceAsyncHook =
|
|
||||||
new Hook<GetResourceAsyncDelegate>(Address.GetResourceAsync,
|
|
||||||
new GetResourceAsyncDelegate(GetResourceAsyncDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
this.getResourceSyncHook =
|
|
||||||
new Hook<GetResourceSyncDelegate>(Address.GetResourceSync,
|
|
||||||
new GetResourceSyncDelegate(GetResourceSyncDetour),
|
|
||||||
this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable() {
|
|
||||||
this.getResourceAsyncHook.Enable();
|
|
||||||
this.getResourceSyncHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.getResourceAsyncHook.Dispose();
|
|
||||||
this.getResourceSyncHook.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
var path = Marshal.PtrToStringAnsi(a5);
|
|
||||||
|
|
||||||
var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7);
|
|
||||||
//var resourceHandle = IntPtr.Zero;
|
|
||||||
|
|
||||||
Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, a5, a6, a7, resourceHandle);
|
|
||||||
|
|
||||||
Log.Verbose($"->{path}");
|
|
||||||
|
|
||||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
|
||||||
|
|
||||||
return resourceHandle;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception on ReadResourceAsync hook.");
|
|
||||||
|
|
||||||
return this.getResourceAsyncHook.Original(manager, a2, a3, a4, a5, a6, a7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DumpMem(IntPtr address, int len = 512) {
|
|
||||||
if (address == IntPtr.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data = new byte[len];
|
|
||||||
Marshal.Copy(address, data, 0, len);
|
|
||||||
|
|
||||||
Log.Verbose($"MEMDMP at {address.ToInt64():X} for {len:X}\n{Util.ByteArrayToHex(data)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
|
||||||
|
|
||||||
Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, a5, a6, resourceHandle);
|
|
||||||
|
|
||||||
var path = Marshal.PtrToStringAnsi(a5);
|
|
||||||
|
|
||||||
Log.Verbose($"->{path}");
|
|
||||||
|
|
||||||
HandleGetResourceHookAcquire(resourceHandle, path);
|
|
||||||
|
|
||||||
return resourceHandle;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Exception on ReadResourceSync hook.");
|
|
||||||
|
|
||||||
return this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path) {
|
|
||||||
if (FilePathHasInvalidChars(path))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.resourceHookMap.ContainsKey(handlePtr)) {
|
|
||||||
Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hookInfo = new ResourceHandleHookInfo {
|
|
||||||
Path = path
|
|
||||||
};
|
|
||||||
|
|
||||||
var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path);
|
|
||||||
|
|
||||||
if (System.IO.File.Exists(hookPath)) {
|
|
||||||
hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open);
|
|
||||||
Log.Verbose("-> Added resource hook detour at {0}", hookPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resourceHookMap.Add(handlePtr, hookInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FilePathHasInvalidChars(string path)
|
|
||||||
{
|
|
||||||
|
|
||||||
return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.File
|
|
||||||
{
|
|
||||||
class ResourceManagerAddressResolver : BaseAddressResolver
|
|
||||||
{
|
|
||||||
public IntPtr GetResourceAsync { get; private set; }
|
|
||||||
public IntPtr GetResourceSync { get; private set; }
|
|
||||||
|
|
||||||
protected override void Setup64Bit(SigScanner sig) {
|
|
||||||
GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74");
|
|
||||||
GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6");
|
|
||||||
//ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Dalamud.Game.Network.Structures;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network {
|
|
||||||
internal class MarketBoardItemRequest {
|
|
||||||
public uint CatalogId { get; set; }
|
|
||||||
public byte AmountToArrive { get; set; }
|
|
||||||
|
|
||||||
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; set; }
|
|
||||||
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; set; }
|
|
||||||
|
|
||||||
public int ListingsRequestId { get; set; } = -1;
|
|
||||||
|
|
||||||
public bool IsDone => Listings.Count == AmountToArrive && History.Count != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using Dalamud.Game.Network.Structures;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders {
|
|
||||||
internal interface IMarketBoardUploader {
|
|
||||||
void Upload(MarketBoardItemRequest itemRequest);
|
|
||||||
void UploadTax(MarketTaxRates taxRates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
|
||||||
internal class UniversalisHistoryEntry {
|
|
||||||
[JsonProperty("hq")]
|
|
||||||
public bool Hq { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("pricePerUnit")]
|
|
||||||
public uint PricePerUnit { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("quantity")]
|
|
||||||
public uint Quantity { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("buyerName")]
|
|
||||||
public string BuyerName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("onMannequin")]
|
|
||||||
public bool OnMannequin { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("sellerID")]
|
|
||||||
public ulong SellerId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("buyerID")]
|
|
||||||
public ulong BuyerId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("timestamp")]
|
|
||||||
public long Timestamp { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
|
||||||
internal class UniversalisHistoryUploadRequest {
|
|
||||||
[JsonProperty("worldID")]
|
|
||||||
public int WorldId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("itemID")]
|
|
||||||
public uint ItemId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("entries")]
|
|
||||||
public List<UniversalisHistoryEntry> Entries { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("uploaderID")]
|
|
||||||
public ulong UploaderId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
|
||||||
internal class UniversalisItemListingsEntry {
|
|
||||||
[JsonProperty("listingID")]
|
|
||||||
public ulong ListingId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("hq")]
|
|
||||||
public bool Hq { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("pricePerUnit")]
|
|
||||||
public uint PricePerUnit { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("quantity")]
|
|
||||||
public uint Quantity { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("retainerName")]
|
|
||||||
public string RetainerName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("retainerID")]
|
|
||||||
public ulong RetainerId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("creatorName")]
|
|
||||||
public string CreatorName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("onMannequin")]
|
|
||||||
public bool OnMannequin { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("sellerID")]
|
|
||||||
public ulong SellerId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("creatorID")]
|
|
||||||
public ulong CreatorId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("stainID")]
|
|
||||||
public int StainId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("retainerCity")]
|
|
||||||
public int RetainerCity { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("lastReviewTime")]
|
|
||||||
public long LastReviewTime { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("materia")]
|
|
||||||
public List<UniversalisItemMateria> Materia { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
|
||||||
internal class UniversalisItemListingsUploadRequest {
|
|
||||||
[JsonProperty("worldID")]
|
|
||||||
public int WorldId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("itemID")]
|
|
||||||
public uint ItemId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("listings")]
|
|
||||||
public List<UniversalisItemListingsEntry> Listings { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("uploaderID")]
|
|
||||||
public ulong UploaderId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
|
||||||
internal class UniversalisItemMateria {
|
|
||||||
[JsonProperty("slotID")]
|
|
||||||
public int SlotId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("materiaID")]
|
|
||||||
public int MateriaId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
|
|
||||||
using Dalamud.Game.Network.Structures;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
|
||||||
internal class UniversalisMarketBoardUploader : IMarketBoardUploader {
|
|
||||||
private const string ApiBase = "https://universalis.app";
|
|
||||||
|
|
||||||
//private const string ApiBase = "https://127.0.0.1:443";
|
|
||||||
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
|
||||||
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
public UniversalisMarketBoardUploader(Dalamud dalamud) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Upload(MarketBoardItemRequest request) {
|
|
||||||
using (var client = new WebClient()) {
|
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
|
||||||
|
|
||||||
Log.Verbose("Starting Universalis upload.");
|
|
||||||
var uploader = this.dalamud.ClientState.LocalContentId;
|
|
||||||
|
|
||||||
var listingsRequestObject = new UniversalisItemListingsUploadRequest();
|
|
||||||
listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id;
|
|
||||||
listingsRequestObject.UploaderId = uploader;
|
|
||||||
listingsRequestObject.ItemId = request.CatalogId;
|
|
||||||
|
|
||||||
listingsRequestObject.Listings = new List<UniversalisItemListingsEntry>();
|
|
||||||
foreach (var marketBoardItemListing in request.Listings) {
|
|
||||||
var universalisListing = new UniversalisItemListingsEntry {
|
|
||||||
Hq = marketBoardItemListing.IsHq,
|
|
||||||
SellerId = marketBoardItemListing.RetainerOwnerId,
|
|
||||||
RetainerName = marketBoardItemListing.RetainerName,
|
|
||||||
RetainerId = marketBoardItemListing.RetainerId,
|
|
||||||
CreatorId = marketBoardItemListing.ArtisanId,
|
|
||||||
CreatorName = marketBoardItemListing.PlayerName,
|
|
||||||
OnMannequin = marketBoardItemListing.OnMannequin,
|
|
||||||
LastReviewTime = ((DateTimeOffset) marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
|
||||||
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
|
||||||
Quantity = marketBoardItemListing.ItemQuantity,
|
|
||||||
RetainerCity = marketBoardItemListing.RetainerCityId
|
|
||||||
};
|
|
||||||
|
|
||||||
universalisListing.Materia = new List<UniversalisItemMateria>();
|
|
||||||
foreach (var itemMateria in marketBoardItemListing.Materia)
|
|
||||||
universalisListing.Materia.Add(new UniversalisItemMateria {
|
|
||||||
MateriaId = itemMateria.MateriaId,
|
|
||||||
SlotId = itemMateria.Index
|
|
||||||
});
|
|
||||||
|
|
||||||
listingsRequestObject.Listings.Add(universalisListing);
|
|
||||||
}
|
|
||||||
|
|
||||||
var upload = JsonConvert.SerializeObject(listingsRequestObject);
|
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
|
|
||||||
Log.Verbose(upload);
|
|
||||||
|
|
||||||
var historyRequestObject = new UniversalisHistoryUploadRequest();
|
|
||||||
historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id;
|
|
||||||
historyRequestObject.UploaderId = uploader;
|
|
||||||
historyRequestObject.ItemId = request.CatalogId;
|
|
||||||
|
|
||||||
historyRequestObject.Entries = new List<UniversalisHistoryEntry>();
|
|
||||||
foreach (var marketBoardHistoryListing in request.History)
|
|
||||||
historyRequestObject.Entries.Add(new UniversalisHistoryEntry {
|
|
||||||
BuyerName = marketBoardHistoryListing.BuyerName,
|
|
||||||
Hq = marketBoardHistoryListing.IsHq,
|
|
||||||
OnMannequin = marketBoardHistoryListing.OnMannequin,
|
|
||||||
PricePerUnit = marketBoardHistoryListing.SalePrice,
|
|
||||||
Quantity = marketBoardHistoryListing.Quantity,
|
|
||||||
Timestamp = ((DateTimeOffset) marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds()
|
|
||||||
});
|
|
||||||
|
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
|
||||||
|
|
||||||
var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
|
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
|
||||||
Log.Verbose(historyUpload);
|
|
||||||
|
|
||||||
Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UploadTax(MarketTaxRates taxRates) {
|
|
||||||
using (var client = new WebClient())
|
|
||||||
{
|
|
||||||
var taxRatesRequest = new UniversalisTaxUploadRequest();
|
|
||||||
taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id;
|
|
||||||
taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId;
|
|
||||||
|
|
||||||
taxRatesRequest.TaxData = new UniversalisTaxData {
|
|
||||||
LimsaLominsa = taxRates.LimsaLominsaTax,
|
|
||||||
Gridania = taxRates.GridaniaTax,
|
|
||||||
Uldah = taxRates.UldahTax,
|
|
||||||
Ishgard = taxRates.IshgardTax,
|
|
||||||
Kugane = taxRates.KuganeTax,
|
|
||||||
Crystarium = taxRates.CrystariumTax
|
|
||||||
};
|
|
||||||
|
|
||||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
|
||||||
|
|
||||||
var historyUpload = JsonConvert.SerializeObject(taxRatesRequest);
|
|
||||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
|
||||||
Log.Verbose(historyUpload);
|
|
||||||
|
|
||||||
Log.Verbose("Universalis tax upload completed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis
|
|
||||||
{
|
|
||||||
class UniversalisTaxUploadRequest
|
|
||||||
{
|
|
||||||
[JsonProperty("uploaderID")]
|
|
||||||
public ulong UploaderId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("worldID")]
|
|
||||||
public int WorldId { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("marketTaxRates")]
|
|
||||||
public UniversalisTaxData TaxData { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
class UniversalisTaxData {
|
|
||||||
[JsonProperty("limsaLominsa")]
|
|
||||||
public uint LimsaLominsa { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("gridania")]
|
|
||||||
public uint Gridania { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("uldah")]
|
|
||||||
public uint Uldah { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("ishgard")]
|
|
||||||
public uint Ishgard { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("kugane")]
|
|
||||||
public uint Kugane { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("crystarium")]
|
|
||||||
public uint Crystarium { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data.TransientSheet;
|
|
||||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
|
||||||
using Dalamud.Game.Network.Structures;
|
|
||||||
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
|
|
||||||
using Lumina.Excel;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network {
|
|
||||||
public class NetworkHandlers {
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
|
|
||||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new List<MarketBoardItemRequest>();
|
|
||||||
|
|
||||||
private readonly bool optOutMbUploads;
|
|
||||||
private readonly IMarketBoardUploader uploader;
|
|
||||||
|
|
||||||
private byte[] lastPreferredRole;
|
|
||||||
|
|
||||||
public delegate Task CfPop(ContentFinderCondition cfc);
|
|
||||||
public event CfPop ProcessCfPop;
|
|
||||||
|
|
||||||
public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) {
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
this.optOutMbUploads = optOutMbUploads;
|
|
||||||
|
|
||||||
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
|
||||||
|
|
||||||
dalamud.Framework.Network.OnZonePacket += OnZonePacket;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnZonePacket(IntPtr dataPtr) {
|
|
||||||
if (!this.dalamud.Data.IsDataReady)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var opCode = (ushort) Marshal.ReadInt16(dataPtr, 2);
|
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
|
|
||||||
var data = new byte[64];
|
|
||||||
Marshal.Copy(dataPtr, data, 0, 64);
|
|
||||||
|
|
||||||
var notifyType = data[16];
|
|
||||||
var contentFinderConditionId = BitConverter.ToUInt16(data, 36);
|
|
||||||
|
|
||||||
if (notifyType != 3 || contentFinderConditionId == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var contentFinderCondition = this.dalamud.Data.GetExcelSheet<ContentFinderCondition>().GetRow(contentFinderConditionId);
|
|
||||||
|
|
||||||
if (contentFinderCondition == null)
|
|
||||||
{
|
|
||||||
Log.Error("CFC key {0} not in lumina data.", contentFinderConditionId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(async () => {
|
|
||||||
this.dalamud.Framework.Gui.Chat.Print($"Duty pop: " + contentFinderCondition.Name);
|
|
||||||
|
|
||||||
await this.ProcessCfPop?.Invoke(contentFinderCondition);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["CfPreferredRole"]) {
|
|
||||||
if (this.dalamud.Configuration.PreferredRoleReminders == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var data = new byte[64];
|
|
||||||
Marshal.Copy(dataPtr, data, 0, 32);
|
|
||||||
|
|
||||||
if (this.lastPreferredRole == null) {
|
|
||||||
this.lastPreferredRole = data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Run(async () => {
|
|
||||||
for (var rouletteIndex = 1; rouletteIndex < 11; rouletteIndex++) {
|
|
||||||
var currentRoleKey = data[16 + rouletteIndex];
|
|
||||||
var prevRoleKey = this.lastPreferredRole[16 + rouletteIndex];
|
|
||||||
|
|
||||||
Log.Verbose("CfPreferredRole: {0} - {1} => {2}", rouletteIndex, prevRoleKey, currentRoleKey);
|
|
||||||
|
|
||||||
if (currentRoleKey != prevRoleKey) {
|
|
||||||
var rouletteName = rouletteIndex switch {
|
|
||||||
1 => "Duty Roulette: Leveling",
|
|
||||||
2 => "Duty Roulette: Level 50/60/70 Dungeons",
|
|
||||||
3 => "Duty Roulette: Main Scenario",
|
|
||||||
4 => "Duty Roulette: Guildhests",
|
|
||||||
5 => "Duty Roulette: Expert",
|
|
||||||
6 => "Duty Roulette: Trials",
|
|
||||||
8 => "Duty Roulette: Mentor",
|
|
||||||
9 => "Duty Roulette: Alliance Raids",
|
|
||||||
10 => "Duty Roulette: Normal Raids",
|
|
||||||
_ => "Unknown ContentRoulette"
|
|
||||||
};
|
|
||||||
|
|
||||||
var prevRoleName = RoleKeyToPreferredRole(prevRoleKey);
|
|
||||||
var currentRoleName = RoleKeyToPreferredRole(currentRoleKey);
|
|
||||||
|
|
||||||
if (!this.dalamud.Configuration.PreferredRoleReminders.TryGetValue(rouletteIndex, out var roleToCheck))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (roleToCheck == DalamudConfiguration.PreferredRole.All || currentRoleName != roleToCheck)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.dalamud.Framework.Gui.Chat.Print($"Roulette bonus for {rouletteName} changed: {prevRoleName} => {currentRoleName}");
|
|
||||||
|
|
||||||
if (this.dalamud.BotManager.IsConnected)
|
|
||||||
await this.dalamud.BotManager.ProcessCfPreferredRoleChange(rouletteName, prevRoleName.ToString(), currentRoleName.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastPreferredRole = data;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.optOutMbUploads) {
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
|
|
||||||
var catalogId = (uint) Marshal.ReadInt32(dataPtr + 0x10);
|
|
||||||
var amount = Marshal.ReadByte(dataPtr + 0x1B);
|
|
||||||
|
|
||||||
this.marketBoardRequests.Add(new MarketBoardItemRequest {
|
|
||||||
CatalogId = catalogId,
|
|
||||||
AmountToArrive = amount,
|
|
||||||
Listings = new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
|
||||||
History = new List<MarketBoardHistory.MarketBoardHistoryListing>()
|
|
||||||
});
|
|
||||||
|
|
||||||
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
|
|
||||||
var listing = MarketBoardCurrentOfferings.Read(dataPtr + 0x10);
|
|
||||||
|
|
||||||
var request =
|
|
||||||
this.marketBoardRequests.LastOrDefault(
|
|
||||||
r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
|
||||||
|
|
||||||
if (request == null) {
|
|
||||||
Log.Error(
|
|
||||||
$"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) {
|
|
||||||
Log.Error(
|
|
||||||
$"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) {
|
|
||||||
Log.Error(
|
|
||||||
$"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.ListingsRequestId == -1 && request.Listings.Count > 0) {
|
|
||||||
Log.Error(
|
|
||||||
$"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.ListingsRequestId == -1) {
|
|
||||||
request.ListingsRequestId = listing.RequestId;
|
|
||||||
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
request.Listings.AddRange(listing.ItemListings);
|
|
||||||
|
|
||||||
Log.Verbose("Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
|
||||||
listing.ItemListings.Count, request.ListingsRequestId, request.Listings.Count,
|
|
||||||
request.AmountToArrive, request.CatalogId);
|
|
||||||
|
|
||||||
if (request.IsDone) {
|
|
||||||
Log.Verbose("Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
|
||||||
request.ListingsRequestId, request.CatalogId, request.AmountToArrive);
|
|
||||||
try {
|
|
||||||
Task.Run(() => this.uploader.Upload(request));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.Error(ex, "Market Board data upload failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) {
|
|
||||||
var listing = MarketBoardHistory.Read(dataPtr + 0x10);
|
|
||||||
|
|
||||||
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
|
|
||||||
|
|
||||||
if (request == null) {
|
|
||||||
Log.Error(
|
|
||||||
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.ListingsRequestId != -1) {
|
|
||||||
Log.Error(
|
|
||||||
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.History.AddRange(listing.HistoryListings);
|
|
||||||
|
|
||||||
Log.Verbose("Added history for item#{0}", listing.CatalogId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"])
|
|
||||||
{
|
|
||||||
var taxes = MarketTaxRates.Read(dataPtr + 0x10);
|
|
||||||
|
|
||||||
Log.Verbose("MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
|
|
||||||
taxes.LimsaLominsaTax, taxes.GridaniaTax, taxes.UldahTax, taxes.IshgardTax, taxes.KuganeTax, taxes.CrystariumTax);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Task.Run(() => this.uploader.UploadTax(taxes));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Market Board data upload failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DalamudConfiguration.PreferredRole RoleKeyToPreferredRole(int key) => key switch
|
|
||||||
{
|
|
||||||
1 => DalamudConfiguration.PreferredRole.Tank,
|
|
||||||
2 => DalamudConfiguration.PreferredRole.Dps,
|
|
||||||
3 => DalamudConfiguration.PreferredRole.Dps,
|
|
||||||
4 => DalamudConfiguration.PreferredRole.Healer,
|
|
||||||
_ => DalamudConfiguration.PreferredRole.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Structures
|
|
||||||
{
|
|
||||||
public class MarketBoardCurrentOfferings
|
|
||||||
{
|
|
||||||
public List<MarketBoardItemListing> ItemListings;
|
|
||||||
|
|
||||||
public int ListingIndexEnd;
|
|
||||||
public int ListingIndexStart;
|
|
||||||
public int RequestId;
|
|
||||||
|
|
||||||
public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr)
|
|
||||||
{
|
|
||||||
var output = new MarketBoardCurrentOfferings();
|
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
|
|
||||||
{
|
|
||||||
using (var reader = new BinaryReader(stream))
|
|
||||||
{
|
|
||||||
output.ItemListings = new List<MarketBoardItemListing>();
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
var listingEntry = new MarketBoardItemListing();
|
|
||||||
|
|
||||||
listingEntry.ListingId = reader.ReadUInt64();
|
|
||||||
listingEntry.RetainerId = reader.ReadUInt64();
|
|
||||||
listingEntry.RetainerOwnerId = reader.ReadUInt64();
|
|
||||||
listingEntry.ArtisanId = reader.ReadUInt64();
|
|
||||||
listingEntry.PricePerUnit = reader.ReadUInt32();
|
|
||||||
listingEntry.TotalTax = reader.ReadUInt32();
|
|
||||||
listingEntry.ItemQuantity = reader.ReadUInt32();
|
|
||||||
listingEntry.CatalogId = reader.ReadUInt32();
|
|
||||||
listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
|
|
||||||
|
|
||||||
reader.ReadUInt16(); // container
|
|
||||||
reader.ReadUInt32(); // slot
|
|
||||||
reader.ReadUInt16(); // durability
|
|
||||||
reader.ReadUInt16(); // spiritbond
|
|
||||||
|
|
||||||
listingEntry.Materia = new List<MarketBoardItemListing.ItemMateria>();
|
|
||||||
|
|
||||||
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
|
|
||||||
{
|
|
||||||
var materiaVal = reader.ReadUInt16();
|
|
||||||
|
|
||||||
var materiaEntry = new MarketBoardItemListing.ItemMateria();
|
|
||||||
materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
|
|
||||||
materiaEntry.Index = materiaVal & 0xF;
|
|
||||||
|
|
||||||
if (materiaEntry.MateriaId != 0)
|
|
||||||
listingEntry.Materia.Add(materiaEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.ReadUInt16();
|
|
||||||
reader.ReadUInt32();
|
|
||||||
|
|
||||||
listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
|
||||||
listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
|
||||||
listingEntry.IsHq = reader.ReadBoolean();
|
|
||||||
listingEntry.MateriaCount = reader.ReadByte();
|
|
||||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
|
||||||
listingEntry.RetainerCityId = reader.ReadByte();
|
|
||||||
listingEntry.StainId = reader.ReadUInt16();
|
|
||||||
|
|
||||||
reader.ReadUInt16();
|
|
||||||
reader.ReadUInt32();
|
|
||||||
|
|
||||||
if (listingEntry.CatalogId != 0)
|
|
||||||
output.ItemListings.Add(listingEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.ListingIndexEnd = reader.ReadByte();
|
|
||||||
output.ListingIndexStart = reader.ReadByte();
|
|
||||||
output.RequestId = reader.ReadUInt16();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MarketBoardItemListing
|
|
||||||
{
|
|
||||||
public ulong ArtisanId;
|
|
||||||
public uint CatalogId;
|
|
||||||
public bool IsHq;
|
|
||||||
public uint ItemQuantity;
|
|
||||||
public DateTime LastReviewTime;
|
|
||||||
public ulong ListingId;
|
|
||||||
|
|
||||||
public List<ItemMateria> Materia;
|
|
||||||
public int MateriaCount;
|
|
||||||
public bool OnMannequin;
|
|
||||||
public string PlayerName;
|
|
||||||
public uint PricePerUnit;
|
|
||||||
public int RetainerCityId;
|
|
||||||
public ulong RetainerId;
|
|
||||||
|
|
||||||
public string RetainerName;
|
|
||||||
public ulong RetainerOwnerId;
|
|
||||||
public int StainId;
|
|
||||||
public uint TotalTax;
|
|
||||||
|
|
||||||
public class ItemMateria
|
|
||||||
{
|
|
||||||
public int Index;
|
|
||||||
public int MateriaId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Structures {
|
|
||||||
public class MarketBoardHistory {
|
|
||||||
public uint CatalogId;
|
|
||||||
public uint CatalogId2;
|
|
||||||
|
|
||||||
public List<MarketBoardHistoryListing> HistoryListings;
|
|
||||||
|
|
||||||
public static unsafe MarketBoardHistory Read(IntPtr dataPtr) {
|
|
||||||
var output = new MarketBoardHistory();
|
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) {
|
|
||||||
using (var reader = new BinaryReader(stream)) {
|
|
||||||
output.CatalogId = reader.ReadUInt32();
|
|
||||||
output.CatalogId2 = reader.ReadUInt32();
|
|
||||||
|
|
||||||
output.HistoryListings = new List<MarketBoardHistoryListing>();
|
|
||||||
|
|
||||||
for (var i = 0; i < 10; i++) {
|
|
||||||
var listingEntry = new MarketBoardHistoryListing();
|
|
||||||
|
|
||||||
listingEntry.SalePrice = reader.ReadUInt32();
|
|
||||||
listingEntry.PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime;
|
|
||||||
listingEntry.Quantity = reader.ReadUInt32();
|
|
||||||
listingEntry.IsHq = reader.ReadBoolean();
|
|
||||||
|
|
||||||
reader.ReadBoolean();
|
|
||||||
|
|
||||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
|
||||||
listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000');
|
|
||||||
listingEntry.CatalogId = reader.ReadUInt32();
|
|
||||||
|
|
||||||
if (listingEntry.CatalogId != 0)
|
|
||||||
output.HistoryListings.Add(listingEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MarketBoardHistoryListing {
|
|
||||||
public string BuyerName;
|
|
||||||
|
|
||||||
public uint CatalogId;
|
|
||||||
public bool IsHq;
|
|
||||||
public bool OnMannequin;
|
|
||||||
public DateTime PurchaseTime;
|
|
||||||
public uint Quantity;
|
|
||||||
public uint SalePrice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Network.Structures
|
|
||||||
{
|
|
||||||
public class MarketTaxRates
|
|
||||||
{
|
|
||||||
public uint LimsaLominsaTax;
|
|
||||||
public uint GridaniaTax;
|
|
||||||
public uint UldahTax;
|
|
||||||
public uint IshgardTax;
|
|
||||||
public uint KuganeTax;
|
|
||||||
public uint CrystariumTax;
|
|
||||||
|
|
||||||
|
|
||||||
public static unsafe MarketTaxRates Read(IntPtr dataPtr)
|
|
||||||
{
|
|
||||||
var output = new MarketTaxRates();
|
|
||||||
|
|
||||||
using (var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544))
|
|
||||||
{
|
|
||||||
using (var reader = new BinaryReader(stream))
|
|
||||||
{
|
|
||||||
stream.Position += 8;
|
|
||||||
|
|
||||||
output.LimsaLominsaTax = reader.ReadUInt32();
|
|
||||||
output.GridaniaTax = reader.ReadUInt32();
|
|
||||||
output.UldahTax = reader.ReadUInt32();
|
|
||||||
output.IshgardTax = reader.ReadUInt32();
|
|
||||||
output.KuganeTax = reader.ReadUInt32();
|
|
||||||
output.CrystariumTax = reader.ReadUInt32();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
using Dalamud.Hooking;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Dalamud.Game
|
|
||||||
{
|
|
||||||
internal sealed class WinSockHandlers : IDisposable
|
|
||||||
{
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
|
||||||
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
|
|
||||||
private Hook<SocketDelegate> ws2SocketHook;
|
|
||||||
|
|
||||||
[DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi)]
|
|
||||||
private static extern int setsockopt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen);
|
|
||||||
|
|
||||||
public WinSockHandlers() {
|
|
||||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", new SocketDelegate(OnSocket));
|
|
||||||
this.ws2SocketHook.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr OnSocket(int af, int type, int protocol)
|
|
||||||
{
|
|
||||||
var socket = this.ws2SocketHook.Original(af, type, protocol);
|
|
||||||
|
|
||||||
// IPPROTO_TCP
|
|
||||||
if (type == 1)
|
|
||||||
{
|
|
||||||
// INVALID_SOCKET
|
|
||||||
if (socket != new IntPtr(-1))
|
|
||||||
{
|
|
||||||
// In case you're not aware of it: (albeit you should)
|
|
||||||
// https://linux.die.net/man/7/tcp
|
|
||||||
// https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf
|
|
||||||
var value = new IntPtr(1);
|
|
||||||
setsockopt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4);
|
|
||||||
|
|
||||||
// Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards.
|
|
||||||
value = new IntPtr(1);
|
|
||||||
setsockopt(socket, SocketOptionLevel.Tcp, (SocketOptionName)12, ref value, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
ws2SocketHook.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,251 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Dalamud.Game {
|
|
||||||
/// <summary>
|
|
||||||
/// A SigScanner facilitates searching for memory signatures in a given ProcessModule.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SigScanner : IDisposable {
|
|
||||||
/// <summary>
|
|
||||||
/// Set up the SigScanner.
|
|
||||||
/// </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) {
|
|
||||||
Module = module;
|
|
||||||
Is32BitProcess = !Environment.Is64BitProcess;
|
|
||||||
IsCopy = doCopy;
|
|
||||||
|
|
||||||
// Limit the search space to .text section.
|
|
||||||
SetupSearchSpace(module);
|
|
||||||
|
|
||||||
if (IsCopy)
|
|
||||||
SetupCopiedSegments();
|
|
||||||
|
|
||||||
Log.Verbose("Module base: {Address}", TextSectionBase);
|
|
||||||
Log.Verbose("Module size: {Size}", TextSectionSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the search on this module is performed on a copy.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsCopy { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the ProcessModule is 32-bit.
|
|
||||||
/// </summary>
|
|
||||||
public bool Is32BitProcess { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The base address of the search area. When copied, this will be the address of the copy.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr SearchBase => IsCopy ? this.moduleCopyPtr : Module.BaseAddress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The base address of the .text section search area.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr TextSectionBase => new IntPtr(SearchBase.ToInt64() + TextSectionOffset);
|
|
||||||
/// <summary>
|
|
||||||
/// The offset of the .text section from the base of the module.
|
|
||||||
/// </summary>
|
|
||||||
public long TextSectionOffset { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The size of the text section.
|
|
||||||
/// </summary>
|
|
||||||
public int TextSectionSize { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The base address of the .data section search area.
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr DataSectionBase => new IntPtr(SearchBase.ToInt64() + DataSectionOffset);
|
|
||||||
/// <summary>
|
|
||||||
/// The offset of the .data section from the base of the module.
|
|
||||||
/// </summary>
|
|
||||||
public long DataSectionOffset { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The size of the .data section.
|
|
||||||
/// </summary>
|
|
||||||
public int DataSectionSize { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ProcessModule on which the search is performed.
|
|
||||||
/// </summary>
|
|
||||||
public ProcessModule Module { get; }
|
|
||||||
|
|
||||||
private IntPtr TextSectionTop => TextSectionBase + TextSectionSize;
|
|
||||||
|
|
||||||
private void SetupSearchSpace(ProcessModule module) {
|
|
||||||
var baseAddress = module.BaseAddress;
|
|
||||||
|
|
||||||
// We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here.
|
|
||||||
var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C);
|
|
||||||
var ntHeader = baseAddress + ntNewOffset;
|
|
||||||
|
|
||||||
// IMAGE_NT_HEADER
|
|
||||||
var fileHeader = ntHeader + 4;
|
|
||||||
var numSections = Marshal.ReadInt16(ntHeader, 6);
|
|
||||||
|
|
||||||
// IMAGE_OPTIONAL_HEADER
|
|
||||||
var optionalHeader = fileHeader + 20;
|
|
||||||
|
|
||||||
IntPtr sectionHeader;
|
|
||||||
if (Is32BitProcess) // IMAGE_OPTIONAL_HEADER32
|
|
||||||
sectionHeader = optionalHeader + 224;
|
|
||||||
else // IMAGE_OPTIONAL_HEADER64
|
|
||||||
sectionHeader = optionalHeader + 240;
|
|
||||||
|
|
||||||
// IMAGE_SECTION_HEADER
|
|
||||||
var sectionCursor = sectionHeader;
|
|
||||||
for (var i = 0; i < numSections; i++) {
|
|
||||||
var sectionName = Marshal.ReadInt64(sectionCursor);
|
|
||||||
|
|
||||||
// .text
|
|
||||||
switch (sectionName) {
|
|
||||||
case 0x747865742E: // .text
|
|
||||||
TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
|
|
||||||
TextSectionSize = Marshal.ReadInt32(sectionCursor, 8);
|
|
||||||
break;
|
|
||||||
case 0x617461642E: // .data
|
|
||||||
DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12);
|
|
||||||
DataSectionSize = Marshal.ReadInt32(sectionCursor, 8);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionCursor += 40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr moduleCopyPtr;
|
|
||||||
private long moduleCopyOffset;
|
|
||||||
|
|
||||||
private unsafe void SetupCopiedSegments() {
|
|
||||||
Log.Verbose("module copy START");
|
|
||||||
// .text
|
|
||||||
this.moduleCopyPtr = Marshal.AllocHGlobal(Module.ModuleMemorySize);
|
|
||||||
Log.Verbose($"Alloc: {this.moduleCopyPtr.ToInt64():x}");
|
|
||||||
Buffer.MemoryCopy(Module.BaseAddress.ToPointer(), this.moduleCopyPtr.ToPointer(), Module.ModuleMemorySize,
|
|
||||||
Module.ModuleMemorySize);
|
|
||||||
|
|
||||||
this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - Module.BaseAddress.ToInt64();
|
|
||||||
|
|
||||||
Log.Verbose("copy OK!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Free the memory of the copied module search area on object disposal, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose() {
|
|
||||||
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for a byte signature in the .text section.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="signature">The signature.</param>
|
|
||||||
/// <returns>The real offset of the found signature.</returns>
|
|
||||||
public IntPtr ScanText(string signature) {
|
|
||||||
var mBase = IsCopy ? this.moduleCopyPtr : TextSectionBase;
|
|
||||||
|
|
||||||
var scanRet = Scan(mBase, TextSectionSize, signature);
|
|
||||||
|
|
||||||
if (IsCopy)
|
|
||||||
scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
|
|
||||||
|
|
||||||
return scanRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for a byte signature in the .data section.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="signature">The signature.</param>
|
|
||||||
/// <returns>The real offset of the found signature.</returns>
|
|
||||||
public IntPtr ScanData(string signature) {
|
|
||||||
var scanRet = Scan(DataSectionBase, DataSectionSize, signature);
|
|
||||||
|
|
||||||
if (IsCopy)
|
|
||||||
scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
|
|
||||||
|
|
||||||
return scanRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Scan for a byte signature in the whole module search area.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="signature">The signature.</param>
|
|
||||||
/// <returns>The real offset of the found signature.</returns>
|
|
||||||
public IntPtr ScanModule(string signature) {
|
|
||||||
var scanRet = Scan(SearchBase, Module.ModuleMemorySize, signature);
|
|
||||||
|
|
||||||
if (IsCopy)
|
|
||||||
scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset);
|
|
||||||
|
|
||||||
return scanRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntPtr Scan(IntPtr baseAddress, int size, string signature) {
|
|
||||||
var needle = SigToNeedle(signature);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
var pCursor = (byte*) baseAddress.ToPointer();
|
|
||||||
var pTop = (byte*) (baseAddress + size - needle.Length);
|
|
||||||
while (pCursor < pTop) {
|
|
||||||
if (IsMatch(pCursor, needle)) return (IntPtr) pCursor;
|
|
||||||
|
|
||||||
// Advance an offset
|
|
||||||
pCursor += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new KeyNotFoundException($"Can't find a signature of {signature}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) {
|
|
||||||
if (Is32BitProcess) throw new NotSupportedException("32 bit is not supported.");
|
|
||||||
|
|
||||||
return nextInstAddr + relOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private unsafe bool IsMatch(byte* pCursor, byte?[] needle) {
|
|
||||||
for (var i = 0; i < needle.Length; i++) {
|
|
||||||
var expected = needle[i];
|
|
||||||
if (expected == null) continue;
|
|
||||||
|
|
||||||
var actual = *(pCursor + i);
|
|
||||||
if (expected != actual) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private byte?[] SigToNeedle(string signature) {
|
|
||||||
// Strip all whitespaces
|
|
||||||
signature = signature.Replace(" ", "");
|
|
||||||
|
|
||||||
if (signature.Length % 2 != 0)
|
|
||||||
throw new ArgumentException("Signature without whitespaces must be divisible by two.",
|
|
||||||
nameof(signature));
|
|
||||||
|
|
||||||
var needleLength = signature.Length / 2;
|
|
||||||
var needle = new byte?[needleLength];
|
|
||||||
|
|
||||||
for (var i = 0; i < needleLength; i++) {
|
|
||||||
var hexString = signature.Substring(i * 2, 2);
|
|
||||||
if (hexString == "??" || hexString == "**") {
|
|
||||||
needle[i] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
return needle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using EasyHook;
|
|
||||||
|
|
||||||
namespace Dalamud.Hooking {
|
|
||||||
/// <summary>
|
|
||||||
/// Manages a hook which can be used to intercept a call to native function.
|
|
||||||
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
|
||||||
public sealed class Hook<T> : IDisposable where T : Delegate {
|
|
||||||
private bool isDisposed;
|
|
||||||
|
|
||||||
private readonly IntPtr address;
|
|
||||||
|
|
||||||
private readonly T original;
|
|
||||||
|
|
||||||
private readonly LocalHook hookInfo;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A memory address of the target function.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
|
||||||
public IntPtr Address {
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get {
|
|
||||||
CheckDisposed();
|
|
||||||
return this.address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A delegate function that can be used to call the actual function as if function is not hooked yet.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks></remarks>
|
|
||||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
|
||||||
public T Original {
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get {
|
|
||||||
CheckDisposed();
|
|
||||||
return this.original;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. Hook is not activated until Enable() method is called.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="moduleName">A name of the module currently loaded in the memory. (e.g. ws2_32.dll)</param>
|
|
||||||
/// <param name="exportName">A name of the exported function name (e.g. send)</param>
|
|
||||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
|
||||||
/// <param name="callbackParam">A callback object which can be accessed within the detour.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Hook<T> FromSymbol(string moduleName, string exportName, Delegate detour, object callbackParam = null) {
|
|
||||||
// Get a function address from the symbol name.
|
|
||||||
var address = LocalHook.GetProcAddress(moduleName, exportName);
|
|
||||||
|
|
||||||
return new Hook<T>(address, detour, callbackParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Createss a hook. Hook is not activated until Enable() method is called.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">A memory address to install a hook.</param>
|
|
||||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
|
||||||
/// <param name="callbackParam">A callback object which can be accessed within the detour.</param>
|
|
||||||
public Hook(IntPtr address, Delegate detour, object callbackParam = null) {
|
|
||||||
this.hookInfo = LocalHook.Create(address, detour, callbackParam); // Installs a hook here
|
|
||||||
this.address = address;
|
|
||||||
this.original = Marshal.GetDelegateForFunctionPointer<T>(this.hookInfo.HookBypassAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a hook from the current process.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose() {
|
|
||||||
if (this.isDisposed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hookInfo.Dispose();
|
|
||||||
|
|
||||||
this.isDisposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Enable() {
|
|
||||||
CheckDisposed();
|
|
||||||
|
|
||||||
this.hookInfo.ThreadACL.SetExclusiveACL(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops intercepting a call to the function.
|
|
||||||
/// </summary>
|
|
||||||
public void Disable() {
|
|
||||||
CheckDisposed();
|
|
||||||
|
|
||||||
this.hookInfo.ThreadACL.SetInclusiveACL(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private void CheckDisposed() {
|
|
||||||
if (this.isDisposed) {
|
|
||||||
throw new ObjectDisposedException("Hook is already disposed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dalamud.Data;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface
|
|
||||||
{
|
|
||||||
class DalamudDataWindow {
|
|
||||||
private DataManager dataMgr;
|
|
||||||
|
|
||||||
private bool wasReady;
|
|
||||||
private string serverOpString;
|
|
||||||
private string cfcString = "N/A";
|
|
||||||
|
|
||||||
private int currentKind;
|
|
||||||
|
|
||||||
public DalamudDataWindow(DataManager dataMgr) {
|
|
||||||
this.dataMgr = dataMgr;
|
|
||||||
|
|
||||||
Load();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Load() {
|
|
||||||
if (this.dataMgr.IsDataReady)
|
|
||||||
{
|
|
||||||
this.serverOpString = JsonConvert.SerializeObject(this.dataMgr.ServerOpCodes, Formatting.Indented);
|
|
||||||
this.wasReady = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Draw()
|
|
||||||
{
|
|
||||||
ImGui.SetNextWindowSize(new Vector2(500, 500), ImGuiCond.Always);
|
|
||||||
|
|
||||||
var isOpen = true;
|
|
||||||
|
|
||||||
if (!ImGui.Begin("Dalamud Data", ref isOpen, ImGuiWindowFlags.NoCollapse))
|
|
||||||
{
|
|
||||||
ImGui.End();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main window
|
|
||||||
if (ImGui.Button("Force Reload"))
|
|
||||||
Load();
|
|
||||||
ImGui.SameLine();
|
|
||||||
var copy = ImGui.Button("Copy all");
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Combo("Data kind", ref currentKind, new[] {"ServerOpCode", "ContentFinderCondition"}, 2);
|
|
||||||
|
|
||||||
ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar);
|
|
||||||
|
|
||||||
if (copy)
|
|
||||||
ImGui.LogToClipboard();
|
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
|
|
||||||
|
|
||||||
if (this.wasReady) {
|
|
||||||
switch (currentKind) {
|
|
||||||
case 0: ImGui.TextUnformatted(this.serverOpString);
|
|
||||||
break;
|
|
||||||
case 1: ImGui.TextUnformatted(this.cfcString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui.TextUnformatted("Data not ready.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
ImGui.End();
|
|
||||||
|
|
||||||
return isOpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface
|
|
||||||
{
|
|
||||||
class DalamudLogWindow : IDisposable {
|
|
||||||
private bool autoScroll = true;
|
|
||||||
private string logText = string.Empty;
|
|
||||||
|
|
||||||
public DalamudLogWindow() {
|
|
||||||
SerilogEventSink.Instance.OnLogLine += Serilog_OnLogLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
SerilogEventSink.Instance.OnLogLine -= Serilog_OnLogLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Serilog_OnLogLine(object sender, string e)
|
|
||||||
{
|
|
||||||
AddLog(e + "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() {
|
|
||||||
this.logText = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddLog(string line) {
|
|
||||||
this.logText += line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Draw() {
|
|
||||||
ImGui.SetNextWindowSize(new Vector2(500, 400), ImGuiCond.FirstUseEver);
|
|
||||||
|
|
||||||
var isOpen = true;
|
|
||||||
|
|
||||||
if (!ImGui.Begin("Dalamud LOG", ref isOpen, ImGuiWindowFlags.NoCollapse))
|
|
||||||
{
|
|
||||||
ImGui.End();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options menu
|
|
||||||
if (ImGui.BeginPopup("Options"))
|
|
||||||
{
|
|
||||||
ImGui.Checkbox("Auto-scroll", ref this.autoScroll);
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main window
|
|
||||||
if (ImGui.Button("Options"))
|
|
||||||
ImGui.OpenPopup("Options");
|
|
||||||
ImGui.SameLine();
|
|
||||||
var clear = ImGui.Button("Clear");
|
|
||||||
ImGui.SameLine();
|
|
||||||
var copy = ImGui.Button("Copy");
|
|
||||||
|
|
||||||
ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar);
|
|
||||||
|
|
||||||
if (clear)
|
|
||||||
Clear();
|
|
||||||
if (copy)
|
|
||||||
ImGui.LogToClipboard();
|
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
|
|
||||||
|
|
||||||
ImGui.TextUnformatted(this.logText);
|
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
|
|
||||||
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
|
|
||||||
ImGui.SetScrollHereY(1.0f);
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
ImGui.End();
|
|
||||||
|
|
||||||
return isOpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue