Add Nuke Build (and temporary nuke Dalamud, heh)

This commit is contained in:
Mino 2020-03-12 20:23:47 +09:00
parent c6a9879498
commit b262fd7e9d
122 changed files with 287 additions and 7894 deletions

1
.nuke Normal file
View file

@ -0,0 +1 @@
Dalamud.sln

View file

@ -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

View file

@ -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>

View file

@ -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;
} }
} }

View file

@ -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}

View file

@ -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));
}
}
}

View file

@ -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; }
}
}

View file

@ -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
};
}
}

View file

@ -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>

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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; }
}
}

View file

@ -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) { }

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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);
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -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; }
}
}

View file

@ -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();
}
}
}

View file

@ -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|VPK\.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});
}
}
}

View file

@ -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++;
}
}
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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];
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -1,7 +0,0 @@
using System;
namespace Dalamud.Game.Internal.DXGI {
public interface ISwapChainAddressResolver {
IntPtr Present { get; set; }
}
}

View file

@ -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");
}
}
}

View file

@ -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];
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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+201p
.text:00000001405CD210 ; sub_140141D10+220p ...
.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");
}
}
}

View file

@ -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();
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}

View file

@ -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; }
}
}

View file

@ -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);
}
}
}
}

View file

@ -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");
}
}
}

View file

@ -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);
}
}
}

View file

@ -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");
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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; }
}
}

View file

@ -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.");
}
}
}
}

View file

@ -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; }
}
}

View file

@ -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
};
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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.");
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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