mirror of
https://github.com/awgil/ffxiv_reverse.git
synced 2025-04-26 00:17:45 +00:00
Initial commit.
This commit is contained in:
commit
a17b143c81
17 changed files with 2110 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.vs/
|
||||||
|
obj/
|
||||||
|
bin/
|
||||||
|
*.user
|
||||||
|
logs/
|
216
idaplugins/ffnetwork.py
Normal file
216
idaplugins/ffnetwork.py
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
import idaapi
|
||||||
|
import ida_ida
|
||||||
|
import ida_bytes
|
||||||
|
import ida_search
|
||||||
|
|
||||||
|
packet_names = {
|
||||||
|
8: 'Logout',
|
||||||
|
14: 'CFNotify',
|
||||||
|
111: 'Playtime',
|
||||||
|
121: 'RSVData',
|
||||||
|
128: 'ExamineSearchInfo',
|
||||||
|
129: 'UpdateSearchInfo',
|
||||||
|
141: 'Countdown',
|
||||||
|
142: 'CountdownCancel',
|
||||||
|
163: 'MarketBoardItemListingCount',
|
||||||
|
164: 'MarketBoardItemListing',
|
||||||
|
166: 'MarketBoardPurchase',
|
||||||
|
168: 'MarketBoardItemListingHistory',
|
||||||
|
171: 'MarketBoardSearchResult',
|
||||||
|
173: 'FreeCompanyInfo',
|
||||||
|
176: 'FreeCompanyDialog',
|
||||||
|
201: 'StatusEffectList',
|
||||||
|
202: 'StatusEffectListEureka',
|
||||||
|
203: 'StatusEffectListBozja',
|
||||||
|
204: 'StatusEffectListDouble',
|
||||||
|
206: 'EffectResult1',
|
||||||
|
207: 'EffectResult4',
|
||||||
|
208: 'EffectResult8',
|
||||||
|
209: 'EffectResult16',
|
||||||
|
211: 'EffectResultBasic1',
|
||||||
|
212: 'EffectResultBasic4',
|
||||||
|
213: 'EffectResultBasic8',
|
||||||
|
214: 'EffectResultBasic16',
|
||||||
|
215: 'EffectResultBasic32',
|
||||||
|
216: 'EffectResultBasic64',
|
||||||
|
217: 'ActorControl',
|
||||||
|
218: 'ActorControlSelf',
|
||||||
|
219: 'ActorControlTarget',
|
||||||
|
220: 'UpdateHpMpTp',
|
||||||
|
221: 'ActionEffect1',
|
||||||
|
224: 'ActionEffect8',
|
||||||
|
225: 'ActionEffect16',
|
||||||
|
226: 'ActionEffect24',
|
||||||
|
227: 'ActionEffect32',
|
||||||
|
230: 'StatusEffectListPlayer',
|
||||||
|
232: 'UpdateRecastTimes',
|
||||||
|
234: 'UpdateAllianceNormal',
|
||||||
|
235: 'UpdateAllianceSmall',
|
||||||
|
236: 'UpdatePartyMemberPositions',
|
||||||
|
237: 'UpdateAllianceNormalMemberPositions',
|
||||||
|
238: 'UpdateAllianceSmallMemberPositions',
|
||||||
|
259: 'SpawnPlayer',
|
||||||
|
260: 'SpawnNPC',
|
||||||
|
261: 'SpawnBoss',
|
||||||
|
262: 'DespawnCharacter',
|
||||||
|
263: 'ActorMove',
|
||||||
|
266: 'ActorSetPos',
|
||||||
|
268: 'ActorCast',
|
||||||
|
271: 'InitZone',
|
||||||
|
272: 'ApplyIDScramble',
|
||||||
|
273: 'UpdateHate',
|
||||||
|
274: 'UpdateHater',
|
||||||
|
275: 'SpawnObject',
|
||||||
|
277: 'UpdateClassInfo',
|
||||||
|
278: 'UpdateClassInfoEureka',
|
||||||
|
279: 'UpdateClassInfoBozja',
|
||||||
|
280: 'PlayerSetup',
|
||||||
|
281: 'PlayerStats',
|
||||||
|
287: 'Examine',
|
||||||
|
294: 'RetainerInformation',
|
||||||
|
296: 'ItemMarketBoardInfo',
|
||||||
|
298: 'ItemInfo',
|
||||||
|
299: 'ContainerInfo',
|
||||||
|
300: 'InventoryTransactionFinish',
|
||||||
|
301: 'InventoryTransaction',
|
||||||
|
302: 'CurrencyCrystalInfo',
|
||||||
|
304: 'InventoryActionAck',
|
||||||
|
305: 'UpdateInventorySlot',
|
||||||
|
318: 'EventPlay',
|
||||||
|
319: 'EventPlay4',
|
||||||
|
320: 'EventPlay8',
|
||||||
|
321: 'EventPlay16',
|
||||||
|
322: 'EventPlay32',
|
||||||
|
323: 'EventPlay64',
|
||||||
|
324: 'EventPlay128',
|
||||||
|
325: 'EventPlay255',
|
||||||
|
327: 'EventStart',
|
||||||
|
328: 'EventFinish',
|
||||||
|
341: 'ResultDialog',
|
||||||
|
342: 'DesynthResult',
|
||||||
|
391: 'EnvControl',
|
||||||
|
397: 'SystemLogMessage1',
|
||||||
|
398: 'SystemLogMessage2',
|
||||||
|
399: 'SystemLogMessage4',
|
||||||
|
400: 'SystemLogMessage8',
|
||||||
|
401: 'SystemLogMessage16',
|
||||||
|
419: 'WeatherChange',
|
||||||
|
514: 'AirshipTimers',
|
||||||
|
518: 'WaymarkPreset',
|
||||||
|
519: 'Waymark',
|
||||||
|
531: 'AirshipStatusList',
|
||||||
|
532: 'AirshipStatus',
|
||||||
|
533: 'AirshipExplorationResult',
|
||||||
|
534: 'SubmarineStatusList',
|
||||||
|
535: 'SubmarineProgressionStatus',
|
||||||
|
536: 'SubmarineExplorationResult',
|
||||||
|
538: 'SubmarineTimers',
|
||||||
|
570: 'PrepareZoning',
|
||||||
|
571: 'ActorGauge',
|
||||||
|
654: 'IslandWorkshopSupplyDemand',
|
||||||
|
}
|
||||||
|
|
||||||
|
def find_next_func_by_sig(ea, pattern):
|
||||||
|
return ida_search.find_binary(ea, ida_ida.inf_get_max_ea(), pattern, 16, ida_search.SEARCH_DOWN)
|
||||||
|
|
||||||
|
def find_single_func_by_sig(pattern):
|
||||||
|
ea_first = find_next_func_by_sig(ida_ida.inf_get_min_ea(), pattern)
|
||||||
|
if ea_first == idaapi.BADADDR:
|
||||||
|
print(f'Could not find function by pattern {pattern}')
|
||||||
|
return 0
|
||||||
|
if find_next_func_by_sig(ea_first + 1, pattern) != idaapi.BADADDR:
|
||||||
|
print(f'Multiple functions match pattern {pattern}')
|
||||||
|
return 0
|
||||||
|
return ea_first
|
||||||
|
|
||||||
|
def read_signed_byte(ea):
|
||||||
|
v = ida_bytes.get_byte(ea)
|
||||||
|
return v - 0x100 if v & 0x80 else v
|
||||||
|
|
||||||
|
def read_signed_dword(ea):
|
||||||
|
v = ida_bytes.get_dword(ea)
|
||||||
|
return v - 0x100000000 if v & 0x80000000 else v
|
||||||
|
|
||||||
|
def read_rva(ea):
|
||||||
|
return ea + 4 + read_signed_dword(ea)
|
||||||
|
|
||||||
|
def get_vfoff_for_body(body):
|
||||||
|
# assume each case has the following body:
|
||||||
|
# mov rax, [rcx]
|
||||||
|
# lea r9, [r10+10h]
|
||||||
|
# jmp qword ptr [rax+<vfoff>]
|
||||||
|
if ida_bytes.get_byte(body) != 0x48 or ida_bytes.get_byte(body + 1) != 0x8B or ida_bytes.get_byte(body + 2) != 0x01:
|
||||||
|
return -1
|
||||||
|
if ida_bytes.get_byte(body + 3) != 0x4D or ida_bytes.get_byte(body + 4) != 0x8D or ida_bytes.get_byte(body + 5) != 0x4A or ida_bytes.get_byte(body + 6) != 0x10:
|
||||||
|
return -1
|
||||||
|
if ida_bytes.get_byte(body + 7) != 0x48 or ida_bytes.get_byte(body + 8) != 0xFF:
|
||||||
|
return -1
|
||||||
|
sz = ida_bytes.get_byte(body + 9)
|
||||||
|
if sz == 0x60:
|
||||||
|
return read_signed_byte(body + 10)
|
||||||
|
elif sz == 0xA0:
|
||||||
|
return read_signed_dword(body + 10)
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def vfoff_to_index(vfoff):
|
||||||
|
if vfoff < 0x10:
|
||||||
|
return -1 # first two vfs are dtor and exec
|
||||||
|
if (vfoff & 7) != 0:
|
||||||
|
return -1 # vf contains qwords
|
||||||
|
return (vfoff >> 3) - 2
|
||||||
|
|
||||||
|
class ffnetwork(idaapi.plugin_t):
|
||||||
|
flags = idaapi.PLUGIN_UNL
|
||||||
|
comment = 'Build opcode map'
|
||||||
|
help = ''
|
||||||
|
wanted_name = 'ffnetwork'
|
||||||
|
wanted_hotkey = ''
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
return idaapi.PLUGIN_OK
|
||||||
|
|
||||||
|
def run(self, arg=None):
|
||||||
|
# assume func starts with:
|
||||||
|
# mov rax, [r8+10h]
|
||||||
|
# mov r10, [rax+38h]
|
||||||
|
# movzx eax, word ptr [r10+2]
|
||||||
|
# add eax, -<min_case>
|
||||||
|
# cmp eax, <max_case-min_case>
|
||||||
|
# ja <default_off>
|
||||||
|
# lea r11, <__ImageBase_off>
|
||||||
|
# cdqe
|
||||||
|
# mov r9d, ds::<jumptable_rva>[r11+rax*4]
|
||||||
|
func = find_single_func_by_sig('49 8B 40 10 4C 8B 50 38 41 0F B7 42 02 83 C0 ?? 3D ?? ?? ?? ?? 0F 87 ?? ?? ?? ?? 4C 8D 1D ?? ?? ?? ?? 48 98 45 8B 8C 83 ?? ?? ?? ??')
|
||||||
|
if func == 0:
|
||||||
|
return
|
||||||
|
min_case = -read_signed_byte(func + 15) # this is a negative
|
||||||
|
jumptable_size = read_signed_dword(func + 17) + 1
|
||||||
|
def_addr = read_rva(func + 23)
|
||||||
|
imagebase = read_rva(func + 30)
|
||||||
|
jumptable = imagebase + read_signed_dword(func + 40)
|
||||||
|
opcodemap = {}
|
||||||
|
for i in range(jumptable_size):
|
||||||
|
body = imagebase + read_signed_dword(jumptable + 4 * i)
|
||||||
|
if body == def_addr:
|
||||||
|
continue
|
||||||
|
case = i + min_case
|
||||||
|
voff = get_vfoff_for_body(body)
|
||||||
|
index = vfoff_to_index(voff)
|
||||||
|
if index < 0:
|
||||||
|
print(f'Unexpected body for case {case}')
|
||||||
|
continue
|
||||||
|
if index in opcodemap:
|
||||||
|
print(f'Multiple opcodes map to single operation {index}: {hex(opcodemap[index])} and {hex(case)}')
|
||||||
|
continue
|
||||||
|
opcodemap[index] = case
|
||||||
|
for k, v in sorted(opcodemap.items()):
|
||||||
|
name = packet_names[k] if k in packet_names else f'Packet{k}'
|
||||||
|
print(f'{name} = {hex(v)}')
|
||||||
|
|
||||||
|
def term(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def PLUGIN_ENTRY():
|
||||||
|
return ffnetwork()
|
||||||
|
|
176
idaplugins/ffutil.py
Normal file
176
idaplugins/ffutil.py
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import idaapi
|
||||||
|
import ida_kernwin
|
||||||
|
import ida_struct
|
||||||
|
import ida_funcs
|
||||||
|
import ida_name
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
def functions():
|
||||||
|
start_ea = 0
|
||||||
|
while True:
|
||||||
|
nextfn = ida_funcs.get_next_func(start_ea)
|
||||||
|
if not nextfn:
|
||||||
|
break
|
||||||
|
start_ea = nextfn.start_ea
|
||||||
|
yield start_ea
|
||||||
|
|
||||||
|
def names():
|
||||||
|
for i in range(ida_name.get_nlist_size()):
|
||||||
|
yield (ida_name.get_nlist_ea(i), ida_name.get_nlist_name(i))
|
||||||
|
|
||||||
|
class dialog(QtWidgets.QDialog):
|
||||||
|
_layout = None
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
QtWidgets.QDialog.__init__(self)
|
||||||
|
self.setWindowTitle(name)
|
||||||
|
self._layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.setLayout(self._layout)
|
||||||
|
|
||||||
|
def add_widget(self, widget):
|
||||||
|
self._layout.addWidget(widget)
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def add_layout(self, layout):
|
||||||
|
self._layout.addLayout(layout)
|
||||||
|
return layout
|
||||||
|
|
||||||
|
def add_buttons(self, ok_name = 'OK'):
|
||||||
|
buttons = self.add_layout(QtWidgets.QHBoxLayout())
|
||||||
|
ok = QtWidgets.QPushButton(ok_name)
|
||||||
|
ok.setDefault(True)
|
||||||
|
ok.clicked.connect(self.accept)
|
||||||
|
buttons.addWidget(ok)
|
||||||
|
cancel = QtWidgets.QPushButton('Cancel')
|
||||||
|
cancel.clicked.connect(self.reject)
|
||||||
|
buttons.addWidget(cancel)
|
||||||
|
|
||||||
|
def run(self, ok_name = 'OK'):
|
||||||
|
self.add_buttons(ok_name)
|
||||||
|
return self.exec()
|
||||||
|
|
||||||
|
class smart_rename(idaapi.action_handler_t):
|
||||||
|
def __init__(self):
|
||||||
|
idaapi.action_handler_t.__init__(self)
|
||||||
|
|
||||||
|
def activate(self, ctx):
|
||||||
|
highlight = ida_kernwin.get_highlight(ida_kernwin.get_current_viewer())
|
||||||
|
struct = ida_struct.get_struc_id(highlight[0]) if highlight else idaapi.BADADDR
|
||||||
|
if struct == idaapi.BADADDR:
|
||||||
|
print('Select a structure to rename')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
original_name = ida_struct.get_struc_name(struct)
|
||||||
|
related_names = self._related_names(original_name)
|
||||||
|
|
||||||
|
dlg = dialog('Rename class and members')
|
||||||
|
dlg_rename = dlg.add_widget(QtWidgets.QLineEdit(original_name))
|
||||||
|
|
||||||
|
dlg_items = dlg.add_widget(QtWidgets.QListWidget())
|
||||||
|
dlg_items_list = []
|
||||||
|
for ea, name in related_names:
|
||||||
|
item = QtWidgets.QListWidgetItem(name, dlg_items)
|
||||||
|
item.setData(QtCore.Qt.UserRole, ea)
|
||||||
|
item.setCheckState(QtCore.Qt.Checked)
|
||||||
|
dlg_items_list.append(item)
|
||||||
|
|
||||||
|
if dlg.run('Rename') == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
new_name = dlg_rename.text()
|
||||||
|
if new_name == original_name:
|
||||||
|
print('Name is not changed, nothing to do...')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f'Renaming struct {original_name} to {new_name}')
|
||||||
|
ida_struct.set_struc_name(struct, new_name)
|
||||||
|
for item in dlg_items_list:
|
||||||
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
|
ea = item.data(QtCore.Qt.UserRole)
|
||||||
|
rel_ori_name = item.text()
|
||||||
|
rel_new_name = rel_ori_name.replace(original_name, new_name)
|
||||||
|
print(f'Renaming related {rel_ori_name} to {rel_new_name}')
|
||||||
|
ida_name.set_name(ea, rel_new_name)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def update(self, ctx):
|
||||||
|
return idaapi.AST_ENABLE_ALWAYS
|
||||||
|
|
||||||
|
def _related_names(self, struct_name):
|
||||||
|
return [(ea, name) for ea, name in names() if self._is_related_name(name, struct_name)]
|
||||||
|
|
||||||
|
def _is_related_name(self, name, struct_name):
|
||||||
|
adj_name = name[5:] if name.startswith('vtbl_') else name
|
||||||
|
pos = adj_name.find(struct_name)
|
||||||
|
if pos < 0:
|
||||||
|
return False
|
||||||
|
if pos > 0 and adj_name[pos-1] != ':' and adj_name[pos-1] != '.':
|
||||||
|
return False
|
||||||
|
pos += len(struct_name)
|
||||||
|
if pos > len(adj_name):
|
||||||
|
return False
|
||||||
|
return pos == len(adj_name) or adj_name[pos] == ':' or adj_name[pos] == '.'
|
||||||
|
|
||||||
|
class vtable_sync(idaapi.action_handler_t):
|
||||||
|
def __init__(self):
|
||||||
|
idaapi.action_handler_t.__init__(self)
|
||||||
|
|
||||||
|
def activate(self, ctx):
|
||||||
|
ea = ida_kernwin.get_screen_ea()
|
||||||
|
name = ida_name.get_name(ea)
|
||||||
|
if not name.startswith('vtbl_'):
|
||||||
|
print('Place cursor at the beginning of a virtual table and make sure it is called vtbl_ClassName')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
name = name[5:]
|
||||||
|
vt_struct = ida_struct.get_struc_id(name + '_vtbl')
|
||||||
|
if vt_struct != idaapi.BADADDR:
|
||||||
|
return self._sync_vtable(ea, name, vt_struct)
|
||||||
|
else:
|
||||||
|
return self._create_vtable(ea, name)
|
||||||
|
|
||||||
|
def update(self, ctx):
|
||||||
|
return idaapi.AST_ENABLE_ALWAYS
|
||||||
|
|
||||||
|
def _sync_vtable(self, ea, classname, vtstruct):
|
||||||
|
print('todo')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _create_vtable(self, ea, classname):
|
||||||
|
next_ea = min([addr for addr, name in names() if addr > ea])
|
||||||
|
size = int((next_ea - ea) / 8)
|
||||||
|
|
||||||
|
dlg = dialog(f'Create vtable for {classname}')
|
||||||
|
|
||||||
|
if dlg.run('Create') == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f'creating: max {size} entries')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class ffutil(idaapi.plugin_t):
|
||||||
|
flags = idaapi.PLUGIN_UNL
|
||||||
|
comment = 'A bunch of utilities to simplify reversing FF14'
|
||||||
|
help = ''
|
||||||
|
wanted_name = 'ffutil'
|
||||||
|
wanted_hotkey = 'Ctrl+Alt+M'
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
print('ffutil init')
|
||||||
|
self._register_action('smart_rename', 'Rename class and all methods', smart_rename(), 'Ctrl+Shift+N')
|
||||||
|
self._register_action('vtable_sync', 'Sync vtable definitions and signatures', vtable_sync(), 'Ctrl+Shift+V')
|
||||||
|
return idaapi.PLUGIN_KEEP
|
||||||
|
|
||||||
|
def run(self, arg=None):
|
||||||
|
print('ffutil run')
|
||||||
|
|
||||||
|
def term(self):
|
||||||
|
print('ffutil term')
|
||||||
|
|
||||||
|
def _register_action(self, name, label, handler, shortcut):
|
||||||
|
deco_name = 'ffutil:' + name
|
||||||
|
idaapi.unregister_action(deco_name)
|
||||||
|
idaapi.register_action(idaapi.action_desc_t(deco_name, label, handler, shortcut))
|
||||||
|
|
||||||
|
def PLUGIN_ENTRY():
|
||||||
|
return ffutil()
|
25
vnetlog/vnetlog.sln
Normal file
25
vnetlog/vnetlog.sln
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33205.214
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "vnetlog", "vnetlog\vnetlog.csproj", "{22EF6083-B8C1-46DC-BAF8-FFD9F76C3C6B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{22EF6083-B8C1-46DC-BAF8-FFD9F76C3C6B}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{22EF6083-B8C1-46DC-BAF8-FFD9F76C3C6B}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{22EF6083-B8C1-46DC-BAF8-FFD9F76C3C6B}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{22EF6083-B8C1-46DC-BAF8-FFD9F76C3C6B}.Release|x64.Build.0 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {F4AA702C-869D-44DC-A1A0-6CB909B4ACFC}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
128
vnetlog/vnetlog/MainWindow.cs
Normal file
128
vnetlog/vnetlog/MainWindow.cs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
class MainWindow : Window, IDisposable
|
||||||
|
{
|
||||||
|
private UITree _tree = new();
|
||||||
|
private PacketDecoder _decoder = new();
|
||||||
|
private PacketInterceptor _interceptor;
|
||||||
|
private HashSet<int> _hiddenPackets = new();
|
||||||
|
private bool _showRecvTime;
|
||||||
|
private bool _showPacketTarget;
|
||||||
|
private bool _showUnknown = true;
|
||||||
|
private DateTime _referenceTime;
|
||||||
|
|
||||||
|
public MainWindow() : base("Netlog", ImGuiWindowFlags.None)
|
||||||
|
{
|
||||||
|
Namespace = "vnetlog";
|
||||||
|
_interceptor = new(_decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_interceptor?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Clear"))
|
||||||
|
_interceptor.Output.Clear();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Copy to clipboard"))
|
||||||
|
ImGui.SetClipboardText(DumpFilteredPackets());
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button(_interceptor.Active ? "Stop" : "Start"))
|
||||||
|
{
|
||||||
|
if (_interceptor.Active)
|
||||||
|
_interceptor.Disable();
|
||||||
|
else
|
||||||
|
_interceptor.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var n in _tree.Node("Packet id -> opcode"))
|
||||||
|
for (int id = 0; id < _decoder.OpcodeMap.IDToOpcode.Count; ++id)
|
||||||
|
if (_decoder.OpcodeMap.IDToOpcode[id] is var opcode && opcode >= 0)
|
||||||
|
_tree.LeafNode($"{(ServerIPC.PacketID)id} == 0x{opcode:X4}");
|
||||||
|
foreach (var n in _tree.Node("Packet opcode -> id"))
|
||||||
|
for (int opcode = 0; opcode < _decoder.OpcodeMap.OpcodeToID.Count; ++opcode)
|
||||||
|
if (_decoder.OpcodeMap.OpcodeToID[opcode] is var id && id >= 0)
|
||||||
|
_tree.LeafNode($"Opcode 0x{opcode:X4} == {(ServerIPC.PacketID)id}");
|
||||||
|
_tree.LeafNode($"ID scramble delta: {_decoder.NetScrambleDelta} (== {_decoder.NetOffsetAdjusted} - {_decoder.NetOffsetBaseFixed} - {_decoder.NetOffsetBaseChanging})");
|
||||||
|
|
||||||
|
foreach (var n in _tree.Node($"Captured packets ({_interceptor.Output.Count})###packets", _interceptor.Output.Count == 0, 0xffffffff, ContextMenuCaptured))
|
||||||
|
{
|
||||||
|
foreach (var p in _tree.Nodes(FilteredCapturedPackets(), p => new($"{PacketTime(p.ts)} #{p.i}: {p.text}###{p.i}", (p.subnodes?.Count ?? 0) == 0), p => ContextMenuPacket(p.opcode), null, p => _referenceTime = p.ts))
|
||||||
|
{
|
||||||
|
DrawDecodedChildren(p.subnodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<(int i, DateTime ts, int opcode, string text, List<TextNode>? subnodes)> FilteredCapturedPackets()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _interceptor.Output.Count; ++i)
|
||||||
|
{
|
||||||
|
var p = _interceptor.Output[i];
|
||||||
|
if (!_showUnknown && !p.Decodable)
|
||||||
|
continue;
|
||||||
|
if (_hiddenPackets.Contains(p.Opcode))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var ts = _showRecvTime ? p.RecvTime : p.SendTime;
|
||||||
|
var actors = _showPacketTarget ? $"{p.SourceString}->{p.TargetString}" : $"{p.SourceString}";
|
||||||
|
var text = $"{_decoder.OpcodeMap.ID(p.Opcode)} (size={p.Payload.Length}, 0x{p.Opcode:X4} {actors}): {p.PayloadStrings.Text}";
|
||||||
|
yield return (i, ts, p.Opcode, text, p.PayloadStrings.Children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string PacketTime(DateTime ts) => ImGui.GetIO().KeyShift && _referenceTime != default ? $"{(ts - _referenceTime).TotalSeconds:f3}" : $"{ts:O}";
|
||||||
|
|
||||||
|
private void ContextMenuCaptured()
|
||||||
|
{
|
||||||
|
ImGui.MenuItem("Show recv timestamp instead of send timestamp", "", ref _showRecvTime);
|
||||||
|
ImGui.MenuItem("Show packet target (always player ID afaik)", "", ref _showPacketTarget);
|
||||||
|
ImGui.MenuItem("Show unknown packets", "", ref _showUnknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContextMenuPacket(int opcode)
|
||||||
|
{
|
||||||
|
if (ImGui.MenuItem("Clear filters"))
|
||||||
|
_hiddenPackets.Clear();
|
||||||
|
if (ImGui.MenuItem($"Hide packet {opcode:X4}"))
|
||||||
|
_hiddenPackets.Add(opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDecodedChildren(List<TextNode>? list)
|
||||||
|
{
|
||||||
|
if (list != null)
|
||||||
|
foreach (var n in _tree.Nodes(list, e => new(e.Text, (e.Children?.Count ?? 0) == 0)))
|
||||||
|
DrawDecodedChildren(n.Children);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DumpFilteredPackets()
|
||||||
|
{
|
||||||
|
var res = new StringBuilder();
|
||||||
|
foreach (var p in FilteredCapturedPackets())
|
||||||
|
{
|
||||||
|
res.AppendLine($"{PacketTime(p.ts)} #{p.i}: {p.text}");
|
||||||
|
DumpNodeChildren(res, p.subnodes, "-");
|
||||||
|
}
|
||||||
|
return res.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DumpNodeChildren(StringBuilder sb, List<TextNode>? list, string prefix)
|
||||||
|
{
|
||||||
|
if (list == null)
|
||||||
|
return;
|
||||||
|
foreach (var n in list)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{prefix} {n.Text}");
|
||||||
|
DumpNodeChildren(sb, n.Children, prefix + "-");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
vnetlog/vnetlog/OpcodeMap.cs
Normal file
35
vnetlog/vnetlog/OpcodeMap.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
// map from network message opcodes (which are randomized every build) to more-or-less stable indices
|
||||||
|
public class OpcodeMap
|
||||||
|
{
|
||||||
|
private List<int> _opcodeToID = new();
|
||||||
|
private List<int> _idToOpcode = new();
|
||||||
|
|
||||||
|
public IReadOnlyList<int> OpcodeToID => _opcodeToID;
|
||||||
|
public IReadOnlyList<int> IDToOpcode => _idToOpcode;
|
||||||
|
|
||||||
|
public ServerIPC.PacketID ID(int opcode) => (ServerIPC.PacketID)(opcode >= 0 && opcode < _opcodeToID.Count ? _opcodeToID[opcode] : -1);
|
||||||
|
public int Opcode(ServerIPC.PacketID id) => (int)id >= 0 && (int)id < _idToOpcode.Count ? _idToOpcode[(int)id] : -1;
|
||||||
|
|
||||||
|
public void AddMapping(int opcode, int id)
|
||||||
|
{
|
||||||
|
if (!AddEntry(_opcodeToID, opcode, id))
|
||||||
|
Service.LogWarn($"[OpcodeMap] Trying to define several mappings for opcode {opcode} ({ID(opcode)} and ({(ServerIPC.PacketID)id})");
|
||||||
|
if (!AddEntry(_idToOpcode, id, opcode))
|
||||||
|
Service.LogWarn($"[OpcodeMap] Trying to map multiple opcodes to same index {(ServerIPC.PacketID)id} ({_idToOpcode[id]} and {opcode})");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AddEntry(List<int> list, int index, int value)
|
||||||
|
{
|
||||||
|
if (list.Count <= index)
|
||||||
|
list.AddRange(Enumerable.Repeat(-1, index + 1 - list.Count));
|
||||||
|
if (list[index] != -1)
|
||||||
|
return false;
|
||||||
|
list[index] = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
63
vnetlog/vnetlog/OpcodeMapBuilder.cs
Normal file
63
vnetlog/vnetlog/OpcodeMapBuilder.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
unsafe static class OpcodeMapBuilder
|
||||||
|
{
|
||||||
|
public static OpcodeMap Build()
|
||||||
|
{
|
||||||
|
// look for an internal tracing function - it's a giant switch on opcode that calls virtual function corresponding to the opcode; we use vf indices as 'opcode index'
|
||||||
|
// function starts with:
|
||||||
|
// mov rax, [r8+10h]
|
||||||
|
// mov r10, [rax+38h]
|
||||||
|
// movzx eax, word ptr [r10+2]
|
||||||
|
// add eax, -<min_case>
|
||||||
|
// cmp eax, <max_case-min_case>
|
||||||
|
// ja <default_off>
|
||||||
|
// lea r11, <__ImageBase_off>
|
||||||
|
// cdqe
|
||||||
|
// mov r9d, ds::<jumptable_rva>[r11+rax*4]
|
||||||
|
var func = (byte*)Service.SigScanner.ScanText("49 8B 40 10 4C 8B 50 38 41 0F B7 42 02 83 C0 ?? 3D ?? ?? ?? ?? 0F 87 ?? ?? ?? ?? 4C 8D 1D ?? ?? ?? ?? 48 98 45 8B 8C 83 ?? ?? ?? ??");
|
||||||
|
var minCase = -*(sbyte*)(func + 15);
|
||||||
|
var jumptableSize = *(int*)(func + 17) + 1;
|
||||||
|
var defaultAddr = ReadRVA(func + 23);
|
||||||
|
var imagebase = ReadRVA(func + 30);
|
||||||
|
var jumptable = (int*)(imagebase + *(int*)(func + 40));
|
||||||
|
OpcodeMap res = new();
|
||||||
|
for (int i = 0; i < jumptableSize; ++i)
|
||||||
|
{
|
||||||
|
var bodyAddr = imagebase + jumptable[i];
|
||||||
|
if (bodyAddr == defaultAddr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var opcode = minCase + i;
|
||||||
|
var index = ReadIndexForCaseBody(bodyAddr);
|
||||||
|
if (index < 0)
|
||||||
|
Service.LogWarn($"[OpcodeMap] Unexpected body for opcode {opcode}");
|
||||||
|
else
|
||||||
|
res.AddMapping(opcode, index);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte* ReadRVA(byte* p) => p + 4 + *(int*)p;
|
||||||
|
|
||||||
|
// assume each case has the following body:
|
||||||
|
// mov rax, [rcx]
|
||||||
|
// lea r9, [r10+10h]
|
||||||
|
// jmp qword ptr [rax+<vfoff>]
|
||||||
|
private static byte[] BodyPrefix = { 0x48, 0x8B, 0x01, 0x4D, 0x8D, 0x4A, 0x10, 0x48, 0xFF };
|
||||||
|
private static int ReadIndexForCaseBody(byte* bodyAddr)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < BodyPrefix.Length; ++i)
|
||||||
|
if (bodyAddr[i] != BodyPrefix[i])
|
||||||
|
return -1;
|
||||||
|
var vtoff = bodyAddr[BodyPrefix.Length] switch
|
||||||
|
{
|
||||||
|
0x60 => *(bodyAddr + BodyPrefix.Length + 1),
|
||||||
|
0xA0 => *(int*)(bodyAddr + BodyPrefix.Length + 1),
|
||||||
|
_ => -1
|
||||||
|
};
|
||||||
|
if (vtoff < 0x10 || (vtoff & 7) != 0)
|
||||||
|
return -1; // first two vfs are dtor and exec, vtable contains qwords
|
||||||
|
return (vtoff >> 3) - 2;
|
||||||
|
}
|
||||||
|
}
|
37
vnetlog/vnetlog/Packet.cs
Normal file
37
vnetlog/vnetlog/Packet.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
public class TextNode
|
||||||
|
{
|
||||||
|
public string Text;
|
||||||
|
public List<TextNode>? Children;
|
||||||
|
|
||||||
|
public TextNode(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode AddChild(string text)
|
||||||
|
{
|
||||||
|
var child = new TextNode(text);
|
||||||
|
Children ??= new();
|
||||||
|
Children.Add(child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Packet
|
||||||
|
{
|
||||||
|
public DateTime RecvTime;
|
||||||
|
public DateTime SendTime;
|
||||||
|
public uint Source;
|
||||||
|
public uint Target;
|
||||||
|
public ushort Opcode;
|
||||||
|
public bool Decodable;
|
||||||
|
public byte[] Payload; // without ipc header!
|
||||||
|
public string SourceString;
|
||||||
|
public string TargetString;
|
||||||
|
public TextNode PayloadStrings;
|
||||||
|
}
|
287
vnetlog/vnetlog/PacketDecoder.cs
Normal file
287
vnetlog/vnetlog/PacketDecoder.cs
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Netlog.ServerIPC;
|
||||||
|
using System.Text;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
// utilities for decoding packets; passed to all decode functions
|
||||||
|
public unsafe class PacketDecoder
|
||||||
|
{
|
||||||
|
private int* _netOffsetBaseFixed;
|
||||||
|
private int* _netOffsetBaseChanging;
|
||||||
|
private int* _netOffsetAdjusted;
|
||||||
|
|
||||||
|
public OpcodeMap OpcodeMap;
|
||||||
|
|
||||||
|
public int NetOffsetBaseFixed => *_netOffsetBaseFixed; // this is set to rand() % 256 + 14 on static init
|
||||||
|
public int NetOffsetBaseChanging => *_netOffsetBaseChanging; // this is set to rand() % 255 + 1 on every zone change
|
||||||
|
public int NetOffsetAdjusted => *_netOffsetAdjusted; // this is set to (rand() % base-sum) if id is not scrambled (so < base-sum) -or- to base-sum + delta calculated from packet data (if scrambled) on every zone change
|
||||||
|
public int NetScrambleDelta => Math.Max(0, NetOffsetAdjusted - NetOffsetBaseFixed - NetOffsetBaseChanging); // if >0, this delta is added to some ids in packets sent by server
|
||||||
|
|
||||||
|
public PacketDecoder()
|
||||||
|
{
|
||||||
|
var scrambleAddr = Service.SigScanner.GetStaticAddressFromSig("44 89 05 ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 8B 05 ?? ?? ?? ?? 33 D2 44 03 05 ?? ?? ?? ?? 48 8B 5C 24");
|
||||||
|
Service.LogInfo($"Scramble address = 0x{scrambleAddr:X}");
|
||||||
|
_netOffsetBaseChanging = (int*)scrambleAddr;
|
||||||
|
_netOffsetAdjusted = _netOffsetBaseChanging + 1;
|
||||||
|
_netOffsetBaseFixed = _netOffsetBaseChanging + 3;
|
||||||
|
|
||||||
|
OpcodeMap = OpcodeMapBuilder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Vec3Str(Vector3 v) => $"[{v.X:f3}, {v.Y:f3}, {v.Z:f3}]";
|
||||||
|
|
||||||
|
public static string ObjStr(ulong objID)
|
||||||
|
{
|
||||||
|
var obj = Service.ObjectTable.SearchById(objID);
|
||||||
|
return obj != null ? $"{obj.DataId:X} '{obj.Name}' <{obj.ObjectId:X}>" : $"(not found) <{objID:X}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LogMsgStr(uint id) => $"{id} '{Service.LuminaRow<Lumina.Excel.GeneratedSheets.LogMessage>(id)?.Text}'";
|
||||||
|
|
||||||
|
public static string SpellStr(uint id) => Service.LuminaRow<Lumina.Excel.GeneratedSheets.Action>(id)?.Name ?? "<not found>";
|
||||||
|
public static string ItemStr(uint id)
|
||||||
|
{
|
||||||
|
// see Dalamud.Game.Text.SeStringHandling.Payloads.GetAdjustedId
|
||||||
|
// TODO: id > 500000 is "collectible", >2000000 is "event" ??
|
||||||
|
bool isHQ = id > 1000000;
|
||||||
|
string name = Service.LuminaRow<Lumina.Excel.GeneratedSheets.Item>(id % 1000000)?.Name ?? "<not found>";
|
||||||
|
return $"{name}{(isHQ ? " (HQ)" : "")}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ActionNameStr(ActionType type, uint id) => type switch
|
||||||
|
{
|
||||||
|
ActionType.Spell => SpellStr(id),
|
||||||
|
ActionType.Item => ItemStr(id),
|
||||||
|
_ => $""
|
||||||
|
};
|
||||||
|
public static string ActionStr(ActionType type, uint id) => $"{type} {id} '{ActionNameStr(type, id)}'";
|
||||||
|
|
||||||
|
public static string StatusStr(uint statusID) => $"{statusID} '{Service.LuminaRow<Lumina.Excel.GeneratedSheets.Status>(statusID)?.Name ?? "<not found>"}'";
|
||||||
|
public static string ClassJobAbbrev(byte classJob) => Service.LuminaRow<Lumina.Excel.GeneratedSheets.ClassJob>(classJob)?.Abbreviation ?? "<unknown>";
|
||||||
|
|
||||||
|
// coord: ((intCoord * 3.0518043) * 0.0099999998) - 1000.0 (0 => -1000, 65535 => +1000)
|
||||||
|
public static Vector3 IntToFloatCoords(ushort x, ushort y, ushort z)
|
||||||
|
{
|
||||||
|
float fx = x * (2000.0f / 65535) - 1000;
|
||||||
|
float fy = y * (2000.0f / 65535) - 1000;
|
||||||
|
float fz = z * (2000.0f / 65535) - 1000;
|
||||||
|
return new(fx, fy, fz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotation: 0 -> -180, 65535 -> +180
|
||||||
|
public static float IntToFloatAngleDeg(ushort rot)
|
||||||
|
{
|
||||||
|
return rot * (360.0f / 65535) - 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ByteArrayStr(byte* p, int len)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(len * 2 + 1);
|
||||||
|
for (int i = 0; i < len; ++i)
|
||||||
|
sb.Append($"{p[i]:X2}");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddStatuses(TextNode res, ServerIPC.Status* list, int count, int offset = 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
var s = list + i;
|
||||||
|
if (s->ID != 0)
|
||||||
|
res.AddChild($"[{i + offset}] {StatusStr(s->ID)} {s->Extra:X4} {s->RemainingTime:f3}s left, from {ObjStr(s->SourceID)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeStatusEffectList(StatusEffectList* p, string extra = "")
|
||||||
|
{
|
||||||
|
var res = new TextNode($"L{p->Level} {ClassJobAbbrev(p->ClassID)}, hp={p->CurHP}/{p->MaxHP}, mp={p->CurMP}/{p->MaxMP}, shield={p->ShieldValue}%{extra}, u={p->u2:X2} {p->u3:X2} {p->u12:X4} {p->u17C:X8}");
|
||||||
|
AddStatuses(res, (ServerIPC.Status*)p->Statuses, 30);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeStatusEffectListDouble(StatusEffectListDouble* p)
|
||||||
|
{
|
||||||
|
var res = DecodeStatusEffectList(&p->Data);
|
||||||
|
AddStatuses(res, (ServerIPC.Status*)p->SecondSet, 30, 30);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeStatusEffectListPlayer(StatusEffectListPlayer* p)
|
||||||
|
{
|
||||||
|
var res = new TextNode("");
|
||||||
|
AddStatuses(res, (ServerIPC.Status*)p->Statuses, 30);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeUpdateRecastTimes(UpdateRecastTimes* p)
|
||||||
|
{
|
||||||
|
var res = new TextNode("");
|
||||||
|
for (int i = 0; i < 80; ++i)
|
||||||
|
res.AddChild($"group {i}: {p->Elapsed[i]:f3}/{p->Total[i]:f3}s");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeEffectResult(EffectResultEntry* entries, int count)
|
||||||
|
{
|
||||||
|
var res = new TextNode($"{count} entries, u={*(uint*)(entries + count):X8}");
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
var e = entries + i;
|
||||||
|
var resEntry = res.AddChild($"[{i}] seq={e->RelatedActionSequence}/{e->RelatedTargetIndex}, actor={ObjStr(e->ActorID)}, class={ClassJobAbbrev(e->ClassID)}, hp={e->CurHP}/{e->MaxHP}, mp={e->CurMP}, shield={e->ShieldValue}, u={e->u16:X4}");
|
||||||
|
var cnt = Math.Min(4, (int)e->EffectCount);
|
||||||
|
var eff = (EffectResultEffect*)e->Effects;
|
||||||
|
for (int j = 0; j < cnt; ++j)
|
||||||
|
{
|
||||||
|
resEntry.AddChild($"#{eff->EffectIndex}: id={StatusStr(eff->StatusID)}, extra={eff->Extra:X2}, dur={eff->Duration:f3}s, src={ObjStr(eff->SourceID)}, pad={eff->pad1:X2} {eff->pad2:X4}");
|
||||||
|
++eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeEffectResultBasic(EffectResultBasicEntry* entries, int count)
|
||||||
|
{
|
||||||
|
var res = new TextNode($"{count} entries, u={*(uint*)(entries + count):X8}");
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
var e = entries + i;
|
||||||
|
res.AddChild($"[{i}] seq={e->RelatedActionSequence}/{e->RelatedTargetIndex}, actor={ObjStr(e->ActorID)}, hp={e->CurHP}, u={e->uD:X2} {e->uE:X4}");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeActorControl(ActorControlCategory category, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, ulong targetID)
|
||||||
|
{
|
||||||
|
var details = category switch
|
||||||
|
{
|
||||||
|
ActorControlCategory.CancelCast => $"{ActionStr((ActionType)p2, p3)}, interrupted={p4 == 1}", // note: some successful boss casts have this message on completion, seen param1=param4=0, param2=1; param1 is related to cast time?..
|
||||||
|
ActorControlCategory.RecastDetails => $"group {p1}: {p2 * 0.01f:f2}/{p3 * 0.01f:f2}s",
|
||||||
|
ActorControlCategory.Cooldown => $"group {p1}: action={ActionStr(ActionType.Spell, p2)}, time={p3 * 0.01f:f2}s",
|
||||||
|
ActorControlCategory.GainEffect => $"{StatusStr(p1)}: extra={p2:X4}",
|
||||||
|
ActorControlCategory.LoseEffect => $"{StatusStr(p1)}: extra={p2:X4}, source={ObjStr(p3)}, unk-update={p4 != 0}",
|
||||||
|
ActorControlCategory.UpdateEffect => $"#{p1} {StatusStr(p2)}: extra={p3:X4}",
|
||||||
|
ActorControlCategory.TargetIcon => $"{p1 - NetScrambleDelta}",
|
||||||
|
ActorControlCategory.Tether => $"#{p1}: {p2} -> {ObjStr(p3)} progress={p4}%",
|
||||||
|
ActorControlCategory.TetherCancel => $"#{p1}: {p2}",
|
||||||
|
ActorControlCategory.SetTarget => $"{ObjStr(targetID)}",
|
||||||
|
ActorControlCategory.SetAnimationState => $"#{p1} = {p2}",
|
||||||
|
ActorControlCategory.SetModelState => $"{p1}",
|
||||||
|
ActorControlCategory.PlayActionTimeline => $"{p1:X4}",
|
||||||
|
ActorControlCategory.EObjSetState => $"{p1:X4}, housing={(p3 != 0 ? p4 : null)}",
|
||||||
|
ActorControlCategory.EObjAnimation => $"{p1:X4} {p2:X4}",
|
||||||
|
ActorControlCategory.ActionRejected => $"{LogMsgStr(p1)}; action={ActionStr((ActionType)p2, p3)}, recast={p4 * 0.01f:f2}/{p5 * 0.01f:f2}, src-seq={p6}",
|
||||||
|
ActorControlCategory.IncrementRecast => $"group {p1}: dt=dt={p2 * 0.01f:f2}s",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
return new TextNode($"{category} {details} ({p1:X8} {p2:X8} {p3:X8} {p4:X8} {p5:X8} {p6:X8} {ObjStr(targetID)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeActionEffect(ActionEffectHeader* data, ActionEffect* effects, ulong* targetIDs, uint maxTargets, Vector3 targetPos)
|
||||||
|
{
|
||||||
|
var rot = IntToFloatAngleDeg(data->rotation);
|
||||||
|
var aid = (uint)(data->actionId - NetScrambleDelta);
|
||||||
|
var res = new TextNode($"#{data->globalEffectCounter} ({data->SourceSequence}) {ActionStr(data->actionType, aid)} ({data->actionId}/{data->actionAnimationId}), animTarget={ObjStr(data->animationTargetId)}, animLock={data->animationLockTime:f3}, rot={rot:f2}, pos={Vec3Str(targetPos)}, var={data->variation}, someTarget={ObjStr(data->SomeTargetID)}, u={data->unknown20:X2} {data->padding21:X4}");
|
||||||
|
res.Children = new();
|
||||||
|
var targets = Math.Min(data->NumTargets, maxTargets);
|
||||||
|
for (int i = 0; i < targets; ++i)
|
||||||
|
{
|
||||||
|
ulong targetId = targetIDs[i];
|
||||||
|
if (targetId == 0)
|
||||||
|
continue;
|
||||||
|
var resTarget = res.AddChild($"target {i} == {ObjStr(targetId)}");
|
||||||
|
for (int j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
ActionEffect* eff = effects + (i * 8) + j;
|
||||||
|
if (eff->Type == ActionEffectType.Nothing)
|
||||||
|
continue;
|
||||||
|
resTarget.AddChild($"effect {j} == {eff->Type}, params={eff->Param0:X2} {eff->Param1:X2} {eff->Param2:X2} {eff->Param3:X2} {eff->Param4:X2} {eff->Value:X4}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeActorCast(ActorCast* p)
|
||||||
|
{
|
||||||
|
uint aid = (uint)(p->ActionID - NetScrambleDelta);
|
||||||
|
return new($"{ActionStr(p->ActionType, aid)} ({ActionStr(ActionType.Spell, p->SpellID)}) @ {ObjStr(p->TargetID)}, time={p->CastTime:f3} ({p->BaseCastTime100ms * 0.1f:f1}), rot={IntToFloatAngleDeg(p->Rotation):f2}, targetpos={Vec3Str(IntToFloatCoords(p->PosX, p->PosY, p->PosZ))}, interruptible={p->Interruptible}, u1={p->u1:X2}, u2={ObjStr(p->u2_objID)}, u3={p->u3:X4}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeUpdateHate(UpdateHate* p)
|
||||||
|
{
|
||||||
|
var res = new TextNode($"{p->NumEntries} entries, pad={p->pad1:X2} {p->pad2:X4} {p->pad3:X8}");
|
||||||
|
var e = (UpdateHateEntry*)p->Entries;
|
||||||
|
for (int i = 0, cnt = Math.Min((int)p->NumEntries, 8); i < cnt; ++i, ++e)
|
||||||
|
res.AddChild($"{ObjStr(e->ObjectID)} = {e->Enmity}%");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeUpdateHater(UpdateHater* p)
|
||||||
|
{
|
||||||
|
var res = new TextNode($"{p->NumEntries} entries, pad={p->pad1:X2} {p->pad2:X4} {p->pad3:X8}");
|
||||||
|
var e = (UpdateHateEntry*)p->Entries;
|
||||||
|
for (int i = 0, cnt = Math.Min((int)p->NumEntries, 32); i < cnt; ++i, ++e)
|
||||||
|
res.AddChild($"{ObjStr(e->ObjectID)} = {e->Enmity}%");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextNode DecodeUpdateClassInfo(UpdateClassInfo* p, string extra = "") => new($"L{p->CurLevel}/{p->ClassLevel}/{p->SyncedLevel} {ClassJobAbbrev(p->ClassID)}, exp={p->CurExp}+{p->RestedExp}{extra}");
|
||||||
|
|
||||||
|
public TextNode DecodeWaymarkPreset(WaymarkPreset* p)
|
||||||
|
{
|
||||||
|
var res = new TextNode($"pad={p->pad1:X2} {p->pad2:X4}");
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
res.AddChild($"{(WaymarkID)i}: {(p->Mask & (1 << i)) != 0} at {Vec3Str(new(p->PosX[i] * 0.001f, p->PosY[i] * 0.001f, p->PosZ[i] * 0.001f))}");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public TextNode DecodeWaymark(Waymark* p) => new TextNode($"{p->ID}: {p->Active != 0} at {Vec3Str(new(p->PosX * 0.001f, p->PosY * 0.001f, p->PosZ * 0.001f))}, pad={p->pad2:X4}");
|
||||||
|
|
||||||
|
public TextNode? DecodePacket(ushort opcode, byte* payload, int length) => OpcodeMap.ID(opcode) switch
|
||||||
|
{
|
||||||
|
PacketID.RSVData when (RSVData*)payload is var p => new($"{MemoryHelper.ReadStringNullTerminated((nint)p->Key)} = {MemoryHelper.ReadString((nint)p->Value, p->ValueLength)} [{p->ValueLength}]"),
|
||||||
|
PacketID.Countdown when (Countdown*)payload is var p => new($"{p->Time}s from {ObjStr(p->SenderID)}{(p->FailedInCombat != 0 ? " fail-in-combat" : "")} '{MemoryHelper.ReadStringNullTerminated((nint)p->Text)}' u={p->u4:X4} {p->u9:X2} {p->u10:X2}"),
|
||||||
|
PacketID.CountdownCancel when (CountdownCancel*)payload is var p => new($"from {ObjStr(p->SenderID)} '{MemoryHelper.ReadStringNullTerminated((nint)p->Text)}' u={p->u4:X4} {p->u6:X4}"),
|
||||||
|
PacketID.StatusEffectList when (StatusEffectList*)payload is var p => DecodeStatusEffectList(p),
|
||||||
|
PacketID.StatusEffectListEureka when (StatusEffectListEureka*)payload is var p => DecodeStatusEffectList(&p->Data, $", rank={p->Rank}/{p->Element}/{p->u2}, pad={p->pad3:X2}"),
|
||||||
|
PacketID.StatusEffectListBozja when (StatusEffectListBozja*)payload is var p => DecodeStatusEffectList(&p->Data, $", rank={p->Rank}, pad={p->pad1:X2}{p->pad2:X4}"),
|
||||||
|
PacketID.StatusEffectListDouble when (StatusEffectListDouble*)payload is var p => DecodeStatusEffectListDouble(p),
|
||||||
|
PacketID.EffectResult1 when (EffectResultN*)payload is var p => DecodeEffectResult((EffectResultEntry*)p->Entries, Math.Min((int)p->NumEntries, 1)),
|
||||||
|
PacketID.EffectResult4 when (EffectResultN*)payload is var p => DecodeEffectResult((EffectResultEntry*)p->Entries, Math.Min((int)p->NumEntries, 4)),
|
||||||
|
PacketID.EffectResult8 when (EffectResultN*)payload is var p => DecodeEffectResult((EffectResultEntry*)p->Entries, Math.Min((int)p->NumEntries, 8)),
|
||||||
|
PacketID.EffectResult16 when (EffectResultN*)payload is var p => DecodeEffectResult((EffectResultEntry*)p->Entries, Math.Min((int)p->NumEntries, 16)),
|
||||||
|
PacketID.EffectResultBasic1 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 1)),
|
||||||
|
PacketID.EffectResultBasic4 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 4)),
|
||||||
|
PacketID.EffectResultBasic8 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 8)),
|
||||||
|
PacketID.EffectResultBasic16 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 16)),
|
||||||
|
PacketID.EffectResultBasic32 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 32)),
|
||||||
|
PacketID.EffectResultBasic64 when (EffectResultBasicN*)payload is var p => DecodeEffectResultBasic((EffectResultBasicEntry*)p->Entries, Math.Min((int)p->NumEntries, 64)),
|
||||||
|
PacketID.ActorControl when (ActorControl*)payload is var p => DecodeActorControl(p->category, p->param1, p->param2, p->param3, p->param4, 0, 0, 0xE0000000),
|
||||||
|
PacketID.ActorControlSelf when (ActorControlSelf*)payload is var p => DecodeActorControl(p->category, p->param1, p->param2, p->param3, p->param4, p->param5, p->param6, 0xE0000000),
|
||||||
|
PacketID.ActorControlTarget when (ActorControlTarget*)payload is var p => DecodeActorControl(p->category, p->param1, p->param2, p->param3, p->param4, 0, 0, p->TargetID),
|
||||||
|
PacketID.UpdateHpMpTp when (UpdateHpMpTp*)payload is var p => new($"hp={p->HP}, mp={p->MP}, gp={p->GP}"),
|
||||||
|
PacketID.ActionEffect1 when (ActionEffect1*)payload is var p => DecodeActionEffect(&p->Header, (ActionEffect*)p->Effects, p->TargetID, 1, new()),
|
||||||
|
PacketID.ActionEffect8 when (ActionEffect8*)payload is var p => DecodeActionEffect(&p->Header, (ActionEffect*)p->Effects, p->TargetID, 8, IntToFloatCoords(p->TargetX, p->TargetY, p->TargetZ)),
|
||||||
|
PacketID.ActionEffect16 when (ActionEffect16*)payload is var p => DecodeActionEffect(&p->Header, (ActionEffect*)p->Effects, p->TargetID, 16, IntToFloatCoords(p->TargetX, p->TargetY, p->TargetZ)),
|
||||||
|
PacketID.ActionEffect24 when (ActionEffect24*)payload is var p => DecodeActionEffect(&p->Header, (ActionEffect*)p->Effects, p->TargetID, 24, IntToFloatCoords(p->TargetX, p->TargetY, p->TargetZ)),
|
||||||
|
PacketID.ActionEffect32 when (ActionEffect32*)payload is var p => DecodeActionEffect(&p->Header, (ActionEffect*)p->Effects, p->TargetID, 32, IntToFloatCoords(p->TargetX, p->TargetY, p->TargetZ)),
|
||||||
|
PacketID.StatusEffectListPlayer when (StatusEffectListPlayer*)payload is var p => DecodeStatusEffectListPlayer(p),
|
||||||
|
PacketID.UpdateRecastTimes when (UpdateRecastTimes*)payload is var p => DecodeUpdateRecastTimes(p),
|
||||||
|
PacketID.ActorMove when (ActorMove*)payload is var p => new($"{Vec3Str(IntToFloatCoords(p->X, p->Y, p->Z))} {IntToFloatAngleDeg(p->Rotation):f2}, anim={p->AnimationFlags:X4}/{p->AnimationSpeed}, u={p->UnknownRotation:X2} {p->Unknown:X8}"),
|
||||||
|
PacketID.ActorSetPos when (ActorSetPos*)payload is var p => new($"{Vec3Str(new(p->X, p->Y, p->Z))} {IntToFloatAngleDeg(p->Rotation):f2}, u={p->u2:X2} {p->u3:X2} {p->u4:X8} {p->u14:X8}"),
|
||||||
|
PacketID.ActorCast when (ActorCast*)payload is var p => DecodeActorCast(p),
|
||||||
|
PacketID.UpdateHate when (UpdateHate*)payload is var p => DecodeUpdateHate(p),
|
||||||
|
PacketID.UpdateHater when (UpdateHater*)payload is var p => DecodeUpdateHater(p),
|
||||||
|
PacketID.UpdateClassInfo when (UpdateClassInfo*)payload is var p => DecodeUpdateClassInfo(p),
|
||||||
|
PacketID.UpdateClassInfoEureka when (UpdateClassInfoEureka*)payload is var p => DecodeUpdateClassInfo(&p->Data, $", rank={p->Rank}/{p->Element}/{p->u2}, pad={p->pad3:X2}"),
|
||||||
|
PacketID.UpdateClassInfoBozja when (UpdateClassInfoBozja*)payload is var p => DecodeUpdateClassInfo(&p->Data, $", rank={p->Rank}, pad={p->pad1:X2}{p->pad2:X4}"),
|
||||||
|
PacketID.EnvControl when (EnvControl*)payload is var p => new($"{p->DirectorID:X8}.{p->Index} = {p->State1:X4} {p->State2:X4}, pad={p->pad9:X2} {p->padA:X4} {p->padC:X8}"),
|
||||||
|
PacketID.WaymarkPreset when (WaymarkPreset*)payload is var p => DecodeWaymarkPreset(p),
|
||||||
|
PacketID.Waymark when (Waymark*)payload is var p => DecodeWaymark(p),
|
||||||
|
PacketID.ActorGauge when (ActorGauge*)payload is var p => new($"{ClassJobAbbrev(p->ClassJobID)} = {p->Payload:X16}"),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
77
vnetlog/vnetlog/PacketInterceptor.cs
Normal file
77
vnetlog/vnetlog/PacketInterceptor.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Pack = 1)]
|
||||||
|
unsafe struct ReceivedIPCPacket
|
||||||
|
{
|
||||||
|
[FieldOffset(0x20)] public uint SourceActor;
|
||||||
|
[FieldOffset(0x24)] public uint TargetActor;
|
||||||
|
[FieldOffset(0x30)] public ulong PacketSize;
|
||||||
|
[FieldOffset(0x38)] public ServerIPC.IPCHeader* PacketData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Pack = 1)]
|
||||||
|
unsafe struct ReceivedPacket
|
||||||
|
{
|
||||||
|
[FieldOffset(0x10)] public ReceivedIPCPacket* IPC;
|
||||||
|
[FieldOffset(0x18)] public long SendTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe class PacketInterceptor : IDisposable
|
||||||
|
{
|
||||||
|
public List<Packet> Output = new();
|
||||||
|
|
||||||
|
private PacketDecoder _decoder;
|
||||||
|
|
||||||
|
private delegate bool FetchReceivedPacketDelegate(void* self, ReceivedPacket* outData);
|
||||||
|
private Hook<FetchReceivedPacketDelegate> _fetchHook;
|
||||||
|
|
||||||
|
public PacketInterceptor(PacketDecoder decoder)
|
||||||
|
{
|
||||||
|
_decoder = decoder;
|
||||||
|
|
||||||
|
var fetchAddress = Service.SigScanner.ScanText("E8 ?? ?? ?? ?? 84 C0 0F 85 ?? ?? ?? ?? 44 0F B6 64 24");
|
||||||
|
Service.LogInfo($"Fetch address: 0x{fetchAddress:X}");
|
||||||
|
_fetchHook = Hook<FetchReceivedPacketDelegate>.FromAddress(fetchAddress, FetchReceivedPacketDetour);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Active => _fetchHook.IsEnabled;
|
||||||
|
public void Enable() => _fetchHook.Enable();
|
||||||
|
public void Disable() => _fetchHook.Disable();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_fetchHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FetchReceivedPacketDetour(void* self, ReceivedPacket* outData)
|
||||||
|
{
|
||||||
|
var res = _fetchHook.Original(self, outData);
|
||||||
|
if (outData->IPC != null)
|
||||||
|
{
|
||||||
|
var opcode = outData->IPC->PacketData->MessageType;
|
||||||
|
var payloadStart = (byte*)(outData->IPC->PacketData + 1);
|
||||||
|
var payloadSize = (int)outData->IPC->PacketSize - sizeof(ServerIPC.IPCHeader);
|
||||||
|
var payload = new Span<byte>(payloadStart, payloadSize).ToArray();
|
||||||
|
var decoded = _decoder.DecodePacket(opcode, payloadStart, payloadSize);
|
||||||
|
Output.Add(new()
|
||||||
|
{
|
||||||
|
RecvTime = DateTime.UtcNow,
|
||||||
|
SendTime = DateTimeOffset.FromUnixTimeMilliseconds(outData->SendTimestamp).DateTime,
|
||||||
|
Source = outData->IPC->SourceActor,
|
||||||
|
Target = outData->IPC->TargetActor,
|
||||||
|
Opcode = opcode,
|
||||||
|
Decodable = decoded != null,
|
||||||
|
Payload = payload,
|
||||||
|
SourceString = PacketDecoder.ObjStr(outData->IPC->SourceActor),
|
||||||
|
TargetString = PacketDecoder.ObjStr(outData->IPC->TargetActor),
|
||||||
|
PayloadStrings = decoded ?? new(PacketDecoder.ByteArrayStr(payloadStart, payloadSize))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
37
vnetlog/vnetlog/Plugin.cs
Normal file
37
vnetlog/vnetlog/Plugin.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
public sealed class Plugin : IDalamudPlugin
|
||||||
|
{
|
||||||
|
public string Name => "VNetlog";
|
||||||
|
|
||||||
|
public DalamudPluginInterface Dalamud { get; init; }
|
||||||
|
private CommandManager _cmdMgr;
|
||||||
|
|
||||||
|
public WindowSystem WindowSystem = new("VNetlog");
|
||||||
|
private MainWindow _wndMain;
|
||||||
|
|
||||||
|
public Plugin(DalamudPluginInterface dalamud, CommandManager cmd)
|
||||||
|
{
|
||||||
|
dalamud.Create<Service>();
|
||||||
|
|
||||||
|
Dalamud = dalamud;
|
||||||
|
_cmdMgr = cmd;
|
||||||
|
|
||||||
|
_wndMain = new();
|
||||||
|
WindowSystem.AddWindow(_wndMain);
|
||||||
|
|
||||||
|
Dalamud.UiBuilder.Draw += WindowSystem.Draw;
|
||||||
|
Dalamud.UiBuilder.OpenConfigUi += () => _wndMain.IsOpen = true;
|
||||||
|
_cmdMgr.AddHandler("/vnetlog", new((cmd, args) => _wndMain.IsOpen = true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
WindowSystem.RemoveAllWindows();
|
||||||
|
_cmdMgr.RemoveHandler("/vnetlog");
|
||||||
|
}
|
||||||
|
}
|
818
vnetlog/vnetlog/ServerIPC.cs
Normal file
818
vnetlog/vnetlog/ServerIPC.cs
Normal file
|
@ -0,0 +1,818 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Netlog.ServerIPC;
|
||||||
|
|
||||||
|
public enum PacketID
|
||||||
|
{
|
||||||
|
Logout = 8,
|
||||||
|
CFNotify = 14,
|
||||||
|
Playtime = 111,
|
||||||
|
RSVData = 121,
|
||||||
|
ExamineSearchInfo = 128,
|
||||||
|
UpdateSearchInfo = 129,
|
||||||
|
Countdown = 141,
|
||||||
|
CountdownCancel = 142,
|
||||||
|
MarketBoardItemListingCount = 163,
|
||||||
|
MarketBoardItemListing = 164,
|
||||||
|
MarketBoardPurchase = 166,
|
||||||
|
MarketBoardItemListingHistory = 168,
|
||||||
|
MarketBoardSearchResult = 171,
|
||||||
|
FreeCompanyInfo = 173,
|
||||||
|
FreeCompanyDialog = 176,
|
||||||
|
StatusEffectList = 201,
|
||||||
|
StatusEffectListEureka = 202,
|
||||||
|
StatusEffectListBozja = 203,
|
||||||
|
StatusEffectListDouble = 204,
|
||||||
|
EffectResult1 = 206,
|
||||||
|
EffectResult4 = 207,
|
||||||
|
EffectResult8 = 208,
|
||||||
|
EffectResult16 = 209,
|
||||||
|
EffectResultBasic1 = 211,
|
||||||
|
EffectResultBasic4 = 212,
|
||||||
|
EffectResultBasic8 = 213,
|
||||||
|
EffectResultBasic16 = 214,
|
||||||
|
EffectResultBasic32 = 215,
|
||||||
|
EffectResultBasic64 = 216,
|
||||||
|
ActorControl = 217,
|
||||||
|
ActorControlSelf = 218,
|
||||||
|
ActorControlTarget = 219,
|
||||||
|
UpdateHpMpTp = 220,
|
||||||
|
ActionEffect1 = 221,
|
||||||
|
ActionEffect8 = 224,
|
||||||
|
ActionEffect16 = 225,
|
||||||
|
ActionEffect24 = 226,
|
||||||
|
ActionEffect32 = 227,
|
||||||
|
StatusEffectListPlayer = 230,
|
||||||
|
UpdateRecastTimes = 232,
|
||||||
|
UpdateAllianceNormal = 234,
|
||||||
|
UpdateAllianceSmall = 235,
|
||||||
|
UpdatePartyMemberPositions = 236,
|
||||||
|
UpdateAllianceNormalMemberPositions = 237,
|
||||||
|
UpdateAllianceSmallMemberPositions = 238,
|
||||||
|
SpawnPlayer = 259,
|
||||||
|
SpawnNPC = 260,
|
||||||
|
SpawnBoss = 261,
|
||||||
|
DespawnCharacter = 262,
|
||||||
|
ActorMove = 263,
|
||||||
|
ActorSetPos = 266,
|
||||||
|
ActorCast = 268,
|
||||||
|
InitZone = 271,
|
||||||
|
ApplyIDScramble = 272,
|
||||||
|
UpdateHate = 273,
|
||||||
|
UpdateHater = 274,
|
||||||
|
SpawnObject = 275,
|
||||||
|
UpdateClassInfo = 277,
|
||||||
|
UpdateClassInfoEureka = 278,
|
||||||
|
UpdateClassInfoBozja = 279,
|
||||||
|
PlayerSetup = 280,
|
||||||
|
PlayerStats = 281,
|
||||||
|
Examine = 287,
|
||||||
|
RetainerInformation = 294,
|
||||||
|
ItemMarketBoardInfo = 296,
|
||||||
|
ItemInfo = 298,
|
||||||
|
ContainerInfo = 299,
|
||||||
|
InventoryTransactionFinish = 300,
|
||||||
|
InventoryTransaction = 301,
|
||||||
|
CurrencyCrystalInfo = 302,
|
||||||
|
InventoryActionAck = 304,
|
||||||
|
UpdateInventorySlot = 305,
|
||||||
|
EventPlay = 318,
|
||||||
|
EventPlay4 = 319,
|
||||||
|
EventPlay8 = 320,
|
||||||
|
EventPlay16 = 321,
|
||||||
|
EventPlay32 = 322,
|
||||||
|
EventPlay64 = 323,
|
||||||
|
EventPlay128 = 324,
|
||||||
|
EventPlay255 = 325,
|
||||||
|
EventStart = 327,
|
||||||
|
EventFinish = 328,
|
||||||
|
ResultDialog = 341,
|
||||||
|
DesynthResult = 342,
|
||||||
|
EnvControl = 391,
|
||||||
|
SystemLogMessage1 = 397,
|
||||||
|
SystemLogMessage2 = 398,
|
||||||
|
SystemLogMessage4 = 399,
|
||||||
|
SystemLogMessage8 = 400,
|
||||||
|
SystemLogMessage16 = 401,
|
||||||
|
WeatherChange = 419,
|
||||||
|
AirshipTimers = 514,
|
||||||
|
WaymarkPreset = 518,
|
||||||
|
Waymark = 519,
|
||||||
|
AirshipStatusList = 531,
|
||||||
|
AirshipStatus = 532,
|
||||||
|
AirshipExplorationResult = 533,
|
||||||
|
SubmarineStatusList = 534,
|
||||||
|
SubmarineProgressionStatus = 535,
|
||||||
|
SubmarineExplorationResult = 536,
|
||||||
|
SubmarineTimers = 538,
|
||||||
|
PrepareZoning = 570,
|
||||||
|
ActorGauge = 571,
|
||||||
|
IslandWorkshopSupplyDemand = 654,
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct IPCHeader
|
||||||
|
{
|
||||||
|
public ushort Magic; // 0x0014
|
||||||
|
public ushort MessageType;
|
||||||
|
public uint Unknown1;
|
||||||
|
public uint Epoch;
|
||||||
|
public uint Unknown2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct RSVData
|
||||||
|
{
|
||||||
|
public int ValueLength;
|
||||||
|
public fixed byte Key[48];
|
||||||
|
public fixed byte Value[1]; // variable-length
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct Countdown
|
||||||
|
{
|
||||||
|
public uint SenderID;
|
||||||
|
public ushort u4;
|
||||||
|
public ushort Time;
|
||||||
|
public byte FailedInCombat;
|
||||||
|
public byte u9;
|
||||||
|
public byte u10;
|
||||||
|
public fixed byte Text[37];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct CountdownCancel
|
||||||
|
{
|
||||||
|
public uint SenderID;
|
||||||
|
public ushort u4;
|
||||||
|
public ushort u6;
|
||||||
|
public fixed byte Text[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct Status
|
||||||
|
{
|
||||||
|
public ushort ID;
|
||||||
|
public ushort Extra;
|
||||||
|
public float RemainingTime;
|
||||||
|
public uint SourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct StatusEffectList
|
||||||
|
{
|
||||||
|
public byte ClassID;
|
||||||
|
public byte Level;
|
||||||
|
public byte u2;
|
||||||
|
public byte u3; // != 0 => set alliance member flag 8
|
||||||
|
public int CurHP;
|
||||||
|
public int MaxHP;
|
||||||
|
public ushort CurMP;
|
||||||
|
public ushort MaxMP;
|
||||||
|
public ushort ShieldValue;
|
||||||
|
public ushort u12;
|
||||||
|
public fixed byte Statuses[30 * 12]; // Status[30]
|
||||||
|
public uint u17C;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct StatusEffectListEureka
|
||||||
|
{
|
||||||
|
public byte Rank;
|
||||||
|
public byte Element;
|
||||||
|
public byte u2;
|
||||||
|
public byte pad3;
|
||||||
|
public StatusEffectList Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct StatusEffectListBozja
|
||||||
|
{
|
||||||
|
public byte Rank;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public StatusEffectList Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct StatusEffectListDouble
|
||||||
|
{
|
||||||
|
public fixed byte SecondSet[30 * 12]; // Status[30]
|
||||||
|
public StatusEffectList Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct EffectResultEffect
|
||||||
|
{
|
||||||
|
public byte EffectIndex;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort StatusID;
|
||||||
|
public ushort Extra;
|
||||||
|
public ushort pad2;
|
||||||
|
public float Duration;
|
||||||
|
public uint SourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct EffectResultEntry
|
||||||
|
{
|
||||||
|
public uint RelatedActionSequence;
|
||||||
|
public uint ActorID;
|
||||||
|
public uint CurHP;
|
||||||
|
public uint MaxHP;
|
||||||
|
public ushort CurMP;
|
||||||
|
public byte RelatedTargetIndex;
|
||||||
|
public byte ClassID;
|
||||||
|
public byte ShieldValue;
|
||||||
|
public byte EffectCount;
|
||||||
|
public ushort u16;
|
||||||
|
public fixed byte Effects[4 * 16]; // EffectResultEffect[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct EffectResultN
|
||||||
|
{
|
||||||
|
public byte NumEntries;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public fixed byte Entries[1 * 0x58]; // N=1/4/8/16
|
||||||
|
// followed by 1 dword padding
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct EffectResultBasicEntry
|
||||||
|
{
|
||||||
|
public uint RelatedActionSequence;
|
||||||
|
public uint ActorID;
|
||||||
|
public uint CurHP;
|
||||||
|
public byte RelatedTargetIndex;
|
||||||
|
public byte uD;
|
||||||
|
public ushort uE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct EffectResultBasicN
|
||||||
|
{
|
||||||
|
public byte NumEntries;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public fixed byte Entries[1 * 16]; // N=1/4/8/16/32/64
|
||||||
|
// followed by 1 dword padding
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActorControlCategory : ushort
|
||||||
|
{
|
||||||
|
ToggleWeapon = 0, // from dissector
|
||||||
|
AutoAttack = 1, // from dissector
|
||||||
|
SetStatus = 2, // from dissector
|
||||||
|
CastStart = 3, // from dissector
|
||||||
|
ToggleAggro = 4, // from dissector
|
||||||
|
ClassJobChange = 5, // from dissector
|
||||||
|
Death = 6, // dissector calls it DefeatMsg
|
||||||
|
GainExpMsg = 7, // from dissector
|
||||||
|
LevelUpEffect = 10, // from dissector
|
||||||
|
ExpChainMsg = 12, // from dissector
|
||||||
|
HpSetStat = 13, // from dissector
|
||||||
|
DeathAnimation = 14, // from dissector
|
||||||
|
CancelCast = 15, // dissector calls it CastInterrupt (ActorControl), machina calls it CancelAbility
|
||||||
|
RecastDetails = 16, // p1=group id, p2=elapsed, p3=total
|
||||||
|
Cooldown = 17, // dissector calls it ActionStart (ActorControlSelf)
|
||||||
|
GainEffect = 20, // note: this packet only causes log message and hit vfx to appear, it does not actually update statuses
|
||||||
|
LoseEffect = 21,
|
||||||
|
UpdateEffect = 22,
|
||||||
|
HoT_DoT = 23, // dissector calls it HPFloatingText
|
||||||
|
UpdateRestedExp = 24, // from dissector
|
||||||
|
Flee = 27, // from dissector
|
||||||
|
UnkVisControl = 30, // visibility control ??? (ActorControl, params=delay-after-spawn, visible, id, 0)
|
||||||
|
TargetIcon = 34, // dissector calls it CombatIndicationShow, this is for boss-related markers, param1 = marker id, param2=param3=param4=0
|
||||||
|
Tether = 35,
|
||||||
|
SpawnEffect = 37, // from dissector
|
||||||
|
ToggleInvisible = 38, // from dissector
|
||||||
|
ToggleActionUnlock = 41, // from dissector
|
||||||
|
UpdateUiExp = 43, // from dissector
|
||||||
|
DmgTakenMsg = 45, // from dissector
|
||||||
|
TetherCancel = 47,
|
||||||
|
SetTarget = 50, // from dissector
|
||||||
|
Targetable = 54, // dissector calls it ToggleNameHidden
|
||||||
|
SetAnimationState = 62, // example - ASSN beacon activation; param1 = animation set index (0 or 1), param2 = animation index (0-7)
|
||||||
|
SetModelState = 63, // example - TEA liquid hand (open/closed); param1=ModelState row index, rest unused
|
||||||
|
LimitBreakStart = 71, // from dissector
|
||||||
|
LimitBreakPartyStart = 72, // from dissector
|
||||||
|
BubbleText = 73, // from dissector
|
||||||
|
DamageEffect = 80, // from dissector
|
||||||
|
RaiseAnimation = 81, // from dissector
|
||||||
|
TreasureScreenMsg = 87, // from dissector
|
||||||
|
SetOwnerId = 89, // from dissector
|
||||||
|
ItemRepairMsg = 92, // from dissector
|
||||||
|
BluActionLearn = 99, // from dissector
|
||||||
|
DirectorInit = 100, // from dissector
|
||||||
|
DirectorClear = 101, // from dissector
|
||||||
|
LeveStartAnim = 102, // from dissector
|
||||||
|
LeveStartError = 103, // from dissector
|
||||||
|
DirectorEObjMod = 106, // from dissector
|
||||||
|
DirectorUpdate = 109,
|
||||||
|
ItemObtainMsg = 117, // from dissector
|
||||||
|
DutyQuestScreenMsg = 123, // from dissector
|
||||||
|
FatePosition = 125, // from dissector
|
||||||
|
ItemObtainIcon = 132, // from dissector
|
||||||
|
FateItemFailMsg = 133, // from dissector
|
||||||
|
FateFailMsg = 134, // from dissector
|
||||||
|
ActionLearnMsg1 = 135, // from dissector
|
||||||
|
FreeEventPos = 138, // from dissector
|
||||||
|
FateSync = 139, // from dissector
|
||||||
|
DailyQuestSeed = 144, // from dissector
|
||||||
|
SetBGM = 161, // from dissector
|
||||||
|
UnlockAetherCurrentMsg = 164, // from dissector
|
||||||
|
RemoveName = 168, // from dissector
|
||||||
|
ScreenFadeOut = 170, // from dissector
|
||||||
|
ZoneIn = 200, // from dissector
|
||||||
|
ZoneInDefaultPos = 201, // from dissector
|
||||||
|
TeleportStart = 203, // from dissector
|
||||||
|
TeleportDone = 205, // from dissector
|
||||||
|
TeleportDoneFadeOut = 206, // from dissector
|
||||||
|
DespawnZoneScreenMsg = 207, // from dissector
|
||||||
|
InstanceSelectDlg = 210, // from dissector
|
||||||
|
ActorDespawnEffect = 212, // from dissector
|
||||||
|
CompanionUnlock = 253, // from dissector
|
||||||
|
ObtainBarding = 254, // from dissector
|
||||||
|
EquipBarding = 255, // from dissector
|
||||||
|
CompanionMsg1 = 258, // from dissector
|
||||||
|
CompanionMsg2 = 259, // from dissector
|
||||||
|
ShowPetHotbar = 260, // from dissector
|
||||||
|
ActionLearnMsg = 265, // from dissector
|
||||||
|
ActorFadeOut = 266, // from dissector
|
||||||
|
ActorFadeIn = 267, // from dissector
|
||||||
|
WithdrawMsg = 268, // from dissector
|
||||||
|
OrderCompanion = 269, // from dissector
|
||||||
|
ToggleCompanion = 270, // from dissector
|
||||||
|
LearnCompanion = 271, // from dissector
|
||||||
|
ActorFateOut1 = 272, // from dissector
|
||||||
|
Emote = 290, // from dissector
|
||||||
|
EmoteInterrupt = 291, // from dissector
|
||||||
|
SetPose = 295, // from dissector
|
||||||
|
FishingLightChange = 300, // from dissector
|
||||||
|
GatheringSenseMsg = 304, // from dissector
|
||||||
|
PartyMsg = 305, // from dissector
|
||||||
|
GatheringSenseMsg1 = 306, // from dissector
|
||||||
|
GatheringSenseMsg2 = 312, // from dissector
|
||||||
|
FishingMsg = 320, // from dissector
|
||||||
|
FishingTotalFishCaught = 322, // from dissector
|
||||||
|
FishingBaitMsg = 325, // from dissector
|
||||||
|
FishingReachMsg = 327, // from dissector
|
||||||
|
FishingFailMsg = 328, // from dissector
|
||||||
|
WeeklyIntervalUpdateTime = 336, // from dissector
|
||||||
|
MateriaConvertMsg = 350, // from dissector
|
||||||
|
MeldSuccessMsg = 351, // from dissector
|
||||||
|
MeldFailMsg = 352, // from dissector
|
||||||
|
MeldModeToggle = 353, // from dissector
|
||||||
|
AetherRestoreMsg = 355, // from dissector
|
||||||
|
DyeMsg = 360, // from dissector
|
||||||
|
ToggleCrestMsg = 362, // from dissector
|
||||||
|
ToggleBulkCrestMsg = 363, // from dissector
|
||||||
|
MateriaRemoveMsg = 364, // from dissector
|
||||||
|
GlamourCastMsg = 365, // from dissector
|
||||||
|
GlamourRemoveMsg = 366, // from dissector
|
||||||
|
RelicInfuseMsg = 377, // from dissector
|
||||||
|
PlayerCurrency = 378, // from dissector
|
||||||
|
AetherReductionDlg = 381, // from dissector
|
||||||
|
PlayActionTimeline = 407, // seems to be equivalent to 412?..
|
||||||
|
EObjSetState = 409, // from dissector
|
||||||
|
Unk6 = 412, // from dissector
|
||||||
|
EObjAnimation = 413, // from dissector
|
||||||
|
SetTitle = 500, // from dissector
|
||||||
|
SetTargetSign = 502,
|
||||||
|
SetStatusIcon = 504, // from dissector
|
||||||
|
LimitBreakGauge = 505, // name from dissector
|
||||||
|
SetHomepoint = 507, // from dissector
|
||||||
|
SetFavorite = 508, // from dissector
|
||||||
|
LearnTeleport = 509, // from dissector
|
||||||
|
OpenRecommendationGuide = 512, // from dissector
|
||||||
|
ArmoryErrorMsg = 513, // from dissector
|
||||||
|
AchievementPopup = 515, // from dissector
|
||||||
|
LogMsg = 517, // from dissector
|
||||||
|
AchievementMsg = 518, // from dissector
|
||||||
|
SetItemLevel = 521, // from dissector
|
||||||
|
ChallengeEntryCompleteMsg = 523, // from dissector
|
||||||
|
ChallengeEntryUnlockMsg = 524, // from dissector
|
||||||
|
DesynthOrReductionResult = 527, // from dissector
|
||||||
|
GilTrailMsg = 529, // from dissector
|
||||||
|
HuntingLogRankUnlock = 541, // from dissector
|
||||||
|
HuntingLogEntryUpdate = 542, // from dissector
|
||||||
|
HuntingLogSectionFinish = 543, // from dissector
|
||||||
|
HuntingLogRankFinish = 544, // from dissector
|
||||||
|
SetMaxGearSets = 560, // from dissector
|
||||||
|
SetCharaGearParamUI = 608, // from dissector
|
||||||
|
ToggleWireframeRendering = 609, // from dissector
|
||||||
|
ActionRejected = 700, // from XivAlexander (ActorControlSelf)
|
||||||
|
ExamineError = 703, // from dissector
|
||||||
|
GearSetEquipMsg = 801, // from dissector
|
||||||
|
SetFestival = 902, // from dissector
|
||||||
|
ToggleOrchestrionUnlock = 918, // from dissector
|
||||||
|
SetMountSpeed = 927, // from dissector
|
||||||
|
Dismount = 929, // from dissector
|
||||||
|
BeginReplayAck = 930, // from dissector
|
||||||
|
EndReplayAck = 931, // from dissector
|
||||||
|
ShowBuildPresetUI = 1001, // from dissector
|
||||||
|
ShowEstateExternalAppearanceUI = 1002, // from dissector
|
||||||
|
ShowEstateInternalAppearanceUI = 1003, // from dissector
|
||||||
|
BuildPresetResponse = 1005, // from dissector
|
||||||
|
RemoveExteriorHousingItem = 1007, // from dissector
|
||||||
|
RemoveInteriorHousingItem = 1009, // from dissector
|
||||||
|
ShowHousingItemUI = 1015, // from dissector
|
||||||
|
HousingItemMoveConfirm = 1017, // from dissector
|
||||||
|
OpenEstateSettingsUI = 1023, // from dissector
|
||||||
|
HideAdditionalChambersDoor = 1024, // from dissector
|
||||||
|
HousingStoreroomStatus = 1049, // from dissector
|
||||||
|
TripleTriadCard = 1204, // from dissector
|
||||||
|
TripleTriadUnknown = 1205, // from dissector
|
||||||
|
FateNpc = 2351, // from dissector
|
||||||
|
FateInit = 2353, // from dissector
|
||||||
|
FateStart = 2357, // from dissector
|
||||||
|
FateEnd = 2358, // from dissector
|
||||||
|
FateProgress = 2366, // from dissector
|
||||||
|
SetPvPState = 1504, // from dissector
|
||||||
|
EndDuelSession = 1505, // from dissector
|
||||||
|
StartDuelCountdown = 1506, // from dissector
|
||||||
|
StartDuel = 1507, // from dissector
|
||||||
|
DuelResultScreen = 1508, // from dissector
|
||||||
|
SetDutyActionId = 1512, // from dissector
|
||||||
|
SetDutyActionHud = 1513, // from dissector
|
||||||
|
SetDutyActionActive = 1514, // from dissector
|
||||||
|
SetDutyActionRemaining = 1515, // from dissector
|
||||||
|
IncrementRecast = 1536, // p1=cooldown group, p2=delta time quantized to 100ms; example is brd mage ballad proc
|
||||||
|
EurekaStep = 1850, // from dissector
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorControl
|
||||||
|
{
|
||||||
|
public ActorControlCategory category;
|
||||||
|
public ushort padding0;
|
||||||
|
public uint param1;
|
||||||
|
public uint param2;
|
||||||
|
public uint param3;
|
||||||
|
public uint param4;
|
||||||
|
public uint padding1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorControlSelf
|
||||||
|
{
|
||||||
|
public ActorControlCategory category;
|
||||||
|
public ushort padding0;
|
||||||
|
public uint param1;
|
||||||
|
public uint param2;
|
||||||
|
public uint param3;
|
||||||
|
public uint param4;
|
||||||
|
public uint param5;
|
||||||
|
public uint param6;
|
||||||
|
public uint padding1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorControlTarget
|
||||||
|
{
|
||||||
|
public ActorControlCategory category;
|
||||||
|
public ushort padding0;
|
||||||
|
public uint param1;
|
||||||
|
public uint param2;
|
||||||
|
public uint param3;
|
||||||
|
public uint param4;
|
||||||
|
public uint padding1;
|
||||||
|
public ulong TargetID;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct UpdateHpMpTp
|
||||||
|
{
|
||||||
|
public uint HP;
|
||||||
|
public ushort MP;
|
||||||
|
public ushort GP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActionEffectType : byte
|
||||||
|
{
|
||||||
|
Nothing = 0,
|
||||||
|
Miss = 1,
|
||||||
|
FullResist = 2,
|
||||||
|
Damage = 3,
|
||||||
|
Heal = 4,
|
||||||
|
BlockedDamage = 5,
|
||||||
|
ParriedDamage = 6,
|
||||||
|
Invulnerable = 7,
|
||||||
|
NoEffectText = 8,
|
||||||
|
FailMissingStatus = 9,
|
||||||
|
MpLoss = 10, // 0x0A
|
||||||
|
MpGain = 11, // 0x0B
|
||||||
|
TpLoss = 12, // 0x0C
|
||||||
|
TpGain = 13, // 0x0D
|
||||||
|
ApplyStatusEffectTarget = 14, // 0x0E - dissector calls this "GpGain"
|
||||||
|
ApplyStatusEffectSource = 15, // 0x0F
|
||||||
|
RecoveredFromStatusEffect = 16, // 0x10
|
||||||
|
LoseStatusEffectTarget = 17, // 0x11
|
||||||
|
LoseStatusEffectSource = 18, // 0x12
|
||||||
|
Unknown_13 = 19, // 0x13 - sometimes part of pvp Purify & Empyrean Rain spells, related to afflictions removal?..
|
||||||
|
StatusNoEffect = 20, // 0x14
|
||||||
|
ThreatPosition = 24, // 0x18
|
||||||
|
EnmityAmountUp = 25, // 0x19
|
||||||
|
EnmityAmountDown = 26, // 0x1A
|
||||||
|
StartActionCombo = 27, // 0x1B
|
||||||
|
Retaliation = 29, // 0x1D - 'vengeance' has value = 7, 'arms length' has value = 0
|
||||||
|
Knockback = 32, // 0x20
|
||||||
|
Attract1 = 33, // 0x21
|
||||||
|
Attract2 = 34, // 0x22
|
||||||
|
AttractCustom1 = 35, // 0x23
|
||||||
|
AttractCustom2 = 36, // 0x24
|
||||||
|
AttractCustom3 = 37, // 0x25
|
||||||
|
Unknown_27 = 39, // 0x27
|
||||||
|
Mount = 40, // 0x28
|
||||||
|
unknown_30 = 48, // 0x30
|
||||||
|
unknown_31 = 49, // 0x31
|
||||||
|
Unknown_32 = 50, // 0x32
|
||||||
|
Unknown_33 = 51, // 0x33
|
||||||
|
FullResistStatus = 52, // 0x34
|
||||||
|
Unknown_37 = 55, // 0x37 - 'arms length' has value = 9 on source, is this 'attack speed slow'?
|
||||||
|
Unknown_38 = 56, // 0x38
|
||||||
|
Unknown_39 = 57, // 0x39
|
||||||
|
VFX = 59, // 0x3B
|
||||||
|
Gauge = 60, // 0x3C
|
||||||
|
Resource = 61, // 0x3D - value 0x34 = gain war gauge (amount == hitSeverity)
|
||||||
|
Unknown_40 = 64, // 0x40
|
||||||
|
Unknown_42 = 66, // 0x42
|
||||||
|
Unknown_46 = 70, // 0x46
|
||||||
|
Unknown_47 = 71, // 0x47
|
||||||
|
SetModelState = 72, // 0x48 - value == model state
|
||||||
|
SetHP = 73, // 0x49 - e.g. zodiark's kokytos
|
||||||
|
Partial_Invulnerable = 74, // 0x4A
|
||||||
|
Interrupt = 75, // 0x4B
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActionEffect
|
||||||
|
{
|
||||||
|
public ActionEffectType Type;
|
||||||
|
public byte Param0;
|
||||||
|
public byte Param1;
|
||||||
|
public byte Param2;
|
||||||
|
public byte Param3;
|
||||||
|
public byte Param4;
|
||||||
|
public ushort Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActionEffectHeader
|
||||||
|
{
|
||||||
|
public ulong animationTargetId; // who the animation targets
|
||||||
|
public uint actionId; // what the casting player casts, shown in battle log / ui
|
||||||
|
public uint globalEffectCounter;
|
||||||
|
public float animationLockTime;
|
||||||
|
public uint SomeTargetID;
|
||||||
|
public ushort SourceSequence; // 0 = initiated by server, otherwise corresponds to client request sequence id
|
||||||
|
public ushort rotation;
|
||||||
|
public ushort actionAnimationId;
|
||||||
|
public byte variation; // animation
|
||||||
|
public ActionType actionType;
|
||||||
|
public byte unknown20;
|
||||||
|
public byte NumTargets; // machina calls it 'effectCount', but it is misleading imo
|
||||||
|
public ushort padding21;
|
||||||
|
public ushort padding22;
|
||||||
|
public ushort padding23;
|
||||||
|
public ushort padding24;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ActionEffect1
|
||||||
|
{
|
||||||
|
public ActionEffectHeader Header;
|
||||||
|
public fixed ulong Effects[8]; // ActionEffect[8]
|
||||||
|
public ushort padding3;
|
||||||
|
public uint padding4;
|
||||||
|
public fixed ulong TargetID[1];
|
||||||
|
public uint padding5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ActionEffect8
|
||||||
|
{
|
||||||
|
public ActionEffectHeader Header;
|
||||||
|
public fixed ulong Effects[8 * 8]; // ActionEffect[8 * 8]
|
||||||
|
public ushort padding3;
|
||||||
|
public uint padding4;
|
||||||
|
public fixed ulong TargetID[8];
|
||||||
|
public ushort TargetX;
|
||||||
|
public ushort TargetY;
|
||||||
|
public ushort TargetZ;
|
||||||
|
public ushort padding5;
|
||||||
|
public uint padding6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ActionEffect16
|
||||||
|
{
|
||||||
|
public ActionEffectHeader Header;
|
||||||
|
public fixed ulong Effects[8 * 16]; // ActionEffect[8 * 16]
|
||||||
|
public ushort padding3;
|
||||||
|
public uint padding4;
|
||||||
|
public fixed ulong TargetID[16];
|
||||||
|
public ushort TargetX;
|
||||||
|
public ushort TargetY;
|
||||||
|
public ushort TargetZ;
|
||||||
|
public ushort padding5;
|
||||||
|
public uint padding6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ActionEffect24
|
||||||
|
{
|
||||||
|
public ActionEffectHeader Header;
|
||||||
|
public fixed ulong Effects[8 * 24]; // ActionEffect[8 * 24]
|
||||||
|
public ushort padding3;
|
||||||
|
public uint padding4;
|
||||||
|
public fixed ulong TargetID[24];
|
||||||
|
public ushort TargetX;
|
||||||
|
public ushort TargetY;
|
||||||
|
public ushort TargetZ;
|
||||||
|
public ushort padding5;
|
||||||
|
public uint padding6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ActionEffect32
|
||||||
|
{
|
||||||
|
public ActionEffectHeader Header;
|
||||||
|
public fixed ulong Effects[8 * 32]; // ActionEffect[8 * 32]
|
||||||
|
public ushort padding3;
|
||||||
|
public uint padding4;
|
||||||
|
public fixed ulong TargetID[32];
|
||||||
|
public ushort TargetX;
|
||||||
|
public ushort TargetY;
|
||||||
|
public ushort TargetZ;
|
||||||
|
public ushort padding5;
|
||||||
|
public uint padding6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct StatusEffectListPlayer
|
||||||
|
{
|
||||||
|
public fixed byte Statuses[30 * 12]; // Status[30]
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct UpdateRecastTimes
|
||||||
|
{
|
||||||
|
public fixed float Elapsed[80];
|
||||||
|
public fixed float Total[80];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorMove
|
||||||
|
{
|
||||||
|
public ushort Rotation;
|
||||||
|
public ushort AnimationFlags;
|
||||||
|
public byte AnimationSpeed;
|
||||||
|
public byte UnknownRotation;
|
||||||
|
public ushort X;
|
||||||
|
public ushort Y;
|
||||||
|
public ushort Z;
|
||||||
|
public uint Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorSetPos
|
||||||
|
{
|
||||||
|
public ushort Rotation;
|
||||||
|
public byte u2;
|
||||||
|
public byte u3;
|
||||||
|
public uint u4;
|
||||||
|
public float X;
|
||||||
|
public float Y;
|
||||||
|
public float Z;
|
||||||
|
public uint u14;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorCast
|
||||||
|
{
|
||||||
|
public ushort SpellID;
|
||||||
|
public ActionType ActionType;
|
||||||
|
public byte BaseCastTime100ms;
|
||||||
|
public uint ActionID; // also action ID; dissector calls it ItemId - matches actionId of ActionEffectHeader - e.g. when using KeyItem, action is generic 'KeyItem 1', Unknown1 is actual item id, probably similar for stuff like mounts etc.
|
||||||
|
public float CastTime;
|
||||||
|
public uint TargetID;
|
||||||
|
public ushort Rotation;
|
||||||
|
public byte Interruptible;
|
||||||
|
public byte u1;
|
||||||
|
public uint u2_objID;
|
||||||
|
public ushort PosX;
|
||||||
|
public ushort PosY;
|
||||||
|
public ushort PosZ;
|
||||||
|
public ushort u3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct UpdateHateEntry
|
||||||
|
{
|
||||||
|
public uint ObjectID;
|
||||||
|
public byte Enmity;
|
||||||
|
public byte pad5;
|
||||||
|
public ushort pad6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct UpdateHate
|
||||||
|
{
|
||||||
|
public byte NumEntries;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public fixed ulong Entries[8]; // UpdateHateEntry[8]
|
||||||
|
public uint pad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct UpdateHater
|
||||||
|
{
|
||||||
|
public byte NumEntries;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public fixed ulong Entries[32]; // UpdateHateEntry[32]
|
||||||
|
public uint pad3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct UpdateClassInfo
|
||||||
|
{
|
||||||
|
public byte ClassID;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort CurLevel;
|
||||||
|
public ushort ClassLevel;
|
||||||
|
public ushort SyncedLevel;
|
||||||
|
public uint CurExp;
|
||||||
|
public uint RestedExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct UpdateClassInfoEureka
|
||||||
|
{
|
||||||
|
public byte Rank;
|
||||||
|
public byte Element;
|
||||||
|
public byte u2;
|
||||||
|
public byte pad3;
|
||||||
|
public UpdateClassInfo Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct UpdateClassInfoBozja
|
||||||
|
{
|
||||||
|
public byte Rank;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public UpdateClassInfo Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct EnvControl
|
||||||
|
{
|
||||||
|
public uint DirectorID;
|
||||||
|
public ushort State1; // typically has 1 bit set
|
||||||
|
public ushort State2; // typically has 1 bit set
|
||||||
|
public byte Index;
|
||||||
|
public byte pad9;
|
||||||
|
public ushort padA;
|
||||||
|
public uint padC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WaymarkID : byte
|
||||||
|
{
|
||||||
|
A, B, C, D, N1, N2, N3, N4, Count
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct WaymarkPreset
|
||||||
|
{
|
||||||
|
public byte Mask;
|
||||||
|
public byte pad1;
|
||||||
|
public ushort pad2;
|
||||||
|
public fixed int PosX[8];// Xints[0] has X of waymark A, Xints[1] X of B, etc.
|
||||||
|
public fixed int PosY[8];// To calculate 'float' coords from these you cast them to float and then divide by 1000.0
|
||||||
|
public fixed int PosZ[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct Waymark
|
||||||
|
{
|
||||||
|
public WaymarkID ID;
|
||||||
|
public byte Active; // 0=off, 1=on
|
||||||
|
public ushort pad2;
|
||||||
|
public int PosX;
|
||||||
|
public int PosY;// To calculate 'float' coords from these you cast them to float and then divide by 1000.0
|
||||||
|
public int PosZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ActorGauge
|
||||||
|
{
|
||||||
|
public byte ClassJobID;
|
||||||
|
public ulong Payload;
|
||||||
|
}
|
23
vnetlog/vnetlog/Service.cs
Normal file
23
vnetlog/vnetlog/Service.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
class Service
|
||||||
|
{
|
||||||
|
[PluginService] public static DataManager DataManager { get; private set; } = null!;
|
||||||
|
[PluginService] public static ObjectTable ObjectTable { get; private set; } = null!;
|
||||||
|
[PluginService] public static SigScanner SigScanner { get; private set; } = null!;
|
||||||
|
|
||||||
|
public static Lumina.GameData? LuminaGameData => DataManager.GameData;
|
||||||
|
public static T? LuminaRow<T>(uint row) where T : Lumina.Excel.ExcelRow => LuminaGameData?.GetExcelSheet<T>(Lumina.Data.Language.English)?.GetRow(row);
|
||||||
|
|
||||||
|
public static void LogVerbose(string msg) => PluginLog.LogVerbose(msg);
|
||||||
|
public static void LogDebug(string msg) => PluginLog.LogDebug(msg);
|
||||||
|
public static void LogInfo(string msg) => PluginLog.LogInformation(msg);
|
||||||
|
public static void LogWarn(string msg) => PluginLog.LogWarning(msg);
|
||||||
|
public static void LogError(string msg) => PluginLog.LogError(msg);
|
||||||
|
}
|
98
vnetlog/vnetlog/UITree.cs
Normal file
98
vnetlog/vnetlog/UITree.cs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Netlog;
|
||||||
|
|
||||||
|
public class UITree
|
||||||
|
{
|
||||||
|
public struct NodeProperties
|
||||||
|
{
|
||||||
|
public string Text;
|
||||||
|
public bool Leaf;
|
||||||
|
public uint Color;
|
||||||
|
|
||||||
|
public NodeProperties(string text, bool leaf = false, uint color = 0xffffffff)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Leaf = leaf;
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint _selectedID;
|
||||||
|
|
||||||
|
// contains 0 elements (if node is closed) or single null (if node is opened)
|
||||||
|
// expected usage is 'foreach (_ in Node(...)) { draw subnodes... }'
|
||||||
|
public IEnumerable<object?> Node(string text, bool leaf = false, uint color = 0xffffffff, Action? contextMenu = null, Action? doubleClick = null, Action? select = null)
|
||||||
|
{
|
||||||
|
if (RawNode(text, leaf, color, contextMenu, doubleClick, select))
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw a node for each element in collection
|
||||||
|
public IEnumerable<T> Nodes<T>(IEnumerable<T> collection, Func<T, NodeProperties> map, Action<T>? contextMenu = null, Action<T>? doubleClick = null, Action<T>? select = null)
|
||||||
|
{
|
||||||
|
foreach (var t in collection)
|
||||||
|
{
|
||||||
|
var props = map(t);
|
||||||
|
if (RawNode(props.Text, props.Leaf, props.Color, contextMenu != null ? () => contextMenu(t) : null, doubleClick != null ? () => doubleClick(t) : null, select != null ? () => select(t) : null))
|
||||||
|
{
|
||||||
|
yield return t;
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LeafNode(string text, uint color = 0xffffffff, Action? contextMenu = null, Action? doubleClick = null, Action? select = null)
|
||||||
|
{
|
||||||
|
if (RawNode(text, true, color, contextMenu, doubleClick, select))
|
||||||
|
ImGui.TreePop();
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw leaf nodes for each element in collection
|
||||||
|
public void LeafNodes<T>(IEnumerable<T> collection, Func<T, string> map, Action<T>? contextMenu = null, Action<T>? doubleClick = null, Action<T>? select = null)
|
||||||
|
{
|
||||||
|
foreach (var t in collection)
|
||||||
|
{
|
||||||
|
if (RawNode(map(t), true, 0xffffffff, contextMenu != null ? () => contextMenu(t) : null, doubleClick != null ? () => doubleClick(t) : null, select != null ? () => select(t) : null))
|
||||||
|
ImGui.TreePop();
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle selection & id scopes
|
||||||
|
private bool RawNode(string text, bool leaf, uint color, Action? contextMenu, Action? doubleClick, Action? select)
|
||||||
|
{
|
||||||
|
var id = ImGui.GetID(text);
|
||||||
|
var flags = ImGuiTreeNodeFlags.None;
|
||||||
|
if (id == _selectedID)
|
||||||
|
flags |= ImGuiTreeNodeFlags.Selected;
|
||||||
|
if (leaf)
|
||||||
|
flags |= ImGuiTreeNodeFlags.Leaf;
|
||||||
|
|
||||||
|
ImGui.PushID((int)id);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Text, color);
|
||||||
|
bool open = ImGui.TreeNodeEx(text, flags);
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
_selectedID = id;
|
||||||
|
select?.Invoke();
|
||||||
|
}
|
||||||
|
if (doubleClick != null && ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
|
||||||
|
doubleClick();
|
||||||
|
if (contextMenu != null && ImGui.BeginPopupContextItem())
|
||||||
|
{
|
||||||
|
contextMenu();
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
}
|
13
vnetlog/vnetlog/packages.lock.json
Normal file
13
vnetlog/vnetlog/packages.lock.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"net7.0-windows7.0": {
|
||||||
|
"DalamudPackager": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.1.10, )",
|
||||||
|
"resolved": "2.1.10",
|
||||||
|
"contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
vnetlog/vnetlog/vnetlog.csproj
Normal file
64
vnetlog/vnetlog/vnetlog.csproj
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Authors></Authors>
|
||||||
|
<Company></Company>
|
||||||
|
<Version>0.0.0.1</Version>
|
||||||
|
<Description>Tools for logging, decoding and analyzing network messages.</Description>
|
||||||
|
<Copyright></Copyright>
|
||||||
|
<PackageProjectUrl>https://github.com/awgil/ffreverse</PackageProjectUrl>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
<RootNamespace>Netlog</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||||
|
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DalamudPackager" Version="2.1.10" />
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGui.NET">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGuiScene">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
8
vnetlog/vnetlog/vnetlog.json
Normal file
8
vnetlog/vnetlog/vnetlog.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Author": "veyn",
|
||||||
|
"Name": "Network reversing toolkit",
|
||||||
|
"Punchline": "Log, decode and analyze network messages",
|
||||||
|
"Description": "A collection of utilities for reversing network messages.",
|
||||||
|
"InternalName": "vnetlog",
|
||||||
|
"ApplicableVersion": "any"
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue