To bring some more light to the code, I now had a look at the DirectPlay interfaces.
----
The most interesting first: In the middle of the packet send routine I found a
Sleep call.
When all CPUs were slow and had just one core, this likely was necessary to prevent blocking the render thread and others.
Nowerdays the Sleep call most likely just delays the whole send process and can be nopped.
Or at least the idle time can be set to 0, which is a special value, that allows the OS to
reassign CPU cycles to another process, but immediately returns when there is no other work.
In addition, for any values between 1-20 milliseconds, the Sleep timer is not much accurate.
And sending masses of tiny packets, it cumulates and can heavily impact the transfer speed!
1000 packets of 4Eh bytes TC reported -2Ch header size then already makes
1-2 seconds delay per 34 kb data transfer...
Actually ALL the Sleep calls should be checked! If they also called it to delay turn processing,
no surprise it is that slow!
----
c.f. IDirectPlay3:
Code: Select all
#define INTERFACE IDirectPlay3
DECLARE_INTERFACE_( IDirectPlay3, IDirectPlay2 )
{
/*** IUnknown methods ***/
00: STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID * ppvObj) PURE;
04: STDMETHOD_(ULONG,AddRef) (THIS) PURE;
08: STDMETHOD_(ULONG,Release) (THIS) PURE;
/*** IDirectPlay2 methods ***/
0C: STDMETHOD(AddPlayerToGroup) (THIS_ DPID, DPID) PURE;
10: STDMETHOD(Close) (THIS) PURE;
14: STDMETHOD(CreateGroup) (THIS_ LPDPID,LPDPNAME,LPVOID,DWORD,DWORD) PURE;
18: STDMETHOD(CreatePlayer) (THIS_ LPDPID,LPDPNAME,HANDLE,LPVOID,DWORD,DWORD) PURE;
1C: STDMETHOD(DeletePlayerFromGroup)(THIS_ DPID,DPID) PURE;
20: STDMETHOD(DestroyGroup) (THIS_ DPID) PURE;
24: STDMETHOD(DestroyPlayer) (THIS_ DPID) PURE;
28: STDMETHOD(EnumGroupPlayers) (THIS_ DPID,LPGUID,LPDPENUMPLAYERSCALLBACK2,LPVOID,DWORD) PURE;
2C: STDMETHOD(EnumGroups) (THIS_ LPGUID,LPDPENUMPLAYERSCALLBACK2,LPVOID,DWORD) PURE;
30: STDMETHOD(EnumPlayers) (THIS_ LPGUID,LPDPENUMPLAYERSCALLBACK2,LPVOID,DWORD) PURE;
34: STDMETHOD(EnumSessions) (THIS_ LPDPSESSIONDESC2,DWORD,LPDPENUMSESSIONSCALLBACK2,LPVOID,DWORD) PURE;
38: STDMETHOD(GetCaps) (THIS_ LPDPCAPS,DWORD) PURE;
3C: STDMETHOD(GetGroupData) (THIS_ DPID,LPVOID,LPDWORD,DWORD) PURE;
40: STDMETHOD(GetGroupName) (THIS_ DPID,LPVOID,LPDWORD) PURE;
44: STDMETHOD(GetMessageCount) (THIS_ DPID, LPDWORD) PURE;
48: STDMETHOD(GetPlayerAddress) (THIS_ DPID,LPVOID,LPDWORD) PURE;
4C: STDMETHOD(GetPlayerCaps) (THIS_ DPID,LPDPCAPS,DWORD) PURE;
50: STDMETHOD(GetPlayerData) (THIS_ DPID,LPVOID,LPDWORD,DWORD) PURE;
54: STDMETHOD(GetPlayerName) (THIS_ DPID,LPVOID,LPDWORD) PURE;
58: STDMETHOD(GetSessionDesc) (THIS_ LPVOID,LPDWORD) PURE;
5C: STDMETHOD(Initialize) (THIS_ LPGUID) PURE;
60: STDMETHOD(Open) (THIS_ LPDPSESSIONDESC2,DWORD) PURE;
64: STDMETHOD(Receive) (THIS_ LPDPID,LPDPID,DWORD,LPVOID,LPDWORD) PURE;
68: STDMETHOD(Send) (THIS_ DPID, DPID, DWORD, LPVOID, DWORD) PURE;
6C: STDMETHOD(SetGroupData) (THIS_ DPID,LPVOID,DWORD,DWORD) PURE;
70: STDMETHOD(SetGroupName) (THIS_ DPID,LPDPNAME,DWORD) PURE;
74: STDMETHOD(SetPlayerData) (THIS_ DPID,LPVOID,DWORD,DWORD) PURE;
78: STDMETHOD(SetPlayerName) (THIS_ DPID,LPDPNAME,DWORD) PURE;
7C: STDMETHOD(SetSessionDesc) (THIS_ LPDPSESSIONDESC2,DWORD) PURE;
/*** IDirectPlay3 methods ***/
80: STDMETHOD(AddGroupToGroup) (THIS_ DPID, DPID) PURE;
84: STDMETHOD(CreateGroupInGroup) (THIS_ DPID,LPDPID,LPDPNAME,LPVOID,DWORD,DWORD) PURE;
88: STDMETHOD(DeleteGroupFromGroup) (THIS_ DPID,DPID) PURE;
8C: STDMETHOD(EnumConnections) (THIS_ LPCGUID,LPDPENUMCONNECTIONSCALLBACK,LPVOID,DWORD) PURE;
90: STDMETHOD(EnumGroupsInGroup) (THIS_ DPID,LPGUID,LPDPENUMPLAYERSCALLBACK2,LPVOID,DWORD) PURE;
94: STDMETHOD(GetGroupConnectionSettings)(THIS_ DWORD, DPID, LPVOID, LPDWORD) PURE;
98: STDMETHOD(InitializeConnection) (THIS_ LPVOID,DWORD) PURE;
9C: STDMETHOD(SecureOpen) (THIS_ LPCDPSESSIONDESC2,DWORD,LPCDPSECURITYDESC,LPCDPCREDENTIALS) PURE;
A0: STDMETHOD(SendChatMessage) (THIS_ DPID,DPID,DWORD,LPDPCHAT) PURE;
A4: STDMETHOD(SetGroupConnectionSettings)(THIS_ DWORD,DPID,LPDPLCONNECTION) PURE;
A8: STDMETHOD(StartSession) (THIS_ DWORD,DPID) PURE;
AC: STDMETHOD(GetGroupFlags) (THIS_ DPID,LPDWORD) PURE;
B0: STDMETHOD(GetGroupParent) (THIS_ DPID,LPDPID) PURE;
B4: STDMETHOD(GetPlayerAccount) (THIS_ DPID, DWORD, LPVOID, LPDWORD) PURE;
B8: STDMETHOD(GetPlayerFlags) (THIS_ DPID,LPDWORD) PURE;
};
https://github.com/google/sagetv/blob/m ... de/dplay.h
https://github.com/github/VisualStudio/ ... st/dplay.h
This is of course not DirectPlay 6A or what they used for BOTF, but the DirectPlay interfaces build up on each other, so the method sequence should match. Somewhere I have downloaded the actual DirectPlay 6 SDK a long while ago, but would need to search for it.
Plus I only found methods called from the DirectPlay 3 interface.
The following both are server side send routines I found for:
DPRESULT Send(DPID idFrom, DPID idTo, SendFlags dwFlags, LPVOID lpData, DWORD dwDataSize);
This I could determine from calculating the ds:DPMPInterface offset of 68h being called.
When you check the IDirectPlay interfaces, it is the 27th method, that is method 0 + 26 * 4 Bytes = 104 = 68h
By checking them all, I could also verify that tis is the sole packet send routine,
and I also found that the Receive counter part is located at:
AUTO:0054BC96 FF 57 64 call dword ptr [edi+64h]
Code: Select all
AUTO:0054BC21 51 push ecx ; DWORD dwDataSize
AUTO:0054BC22 53 push ebx ; LPVOID lpData
AUTO:0054BC23 6A 01 push 1 ; SendFlags dwFlags
AUTO:0054BC25 52 push edx ; DPID idTo, the receiver ID
AUTO:0054BC26 A1 30 07 5A 00 mov eax, ds:DPMPInterface
AUTO:0054BC2B 56 push esi ; DPID idFrom, the sender ID
AUTO:0054BC2C 8B 38 mov edi, [eax]
AUTO:0054BC2E 50 push eax ; this-pointer of the IDirectPlay interface instance
AUTO:0054BC2F FF 57 68 call dword ptr [edi+68h] ; IDirectPlay::Send
AUTO:0054BC32 89 C2 mov edx, eax
Code: Select all
AUTO:0054BC55
AUTO:0054BC55 loc_54BC55:
AUTO:0054BC55 51 push ecx ; DWORD dwDataSize
AUTO:0054BC56 53 push ebx ; LPVOID lpData
AUTO:0054BC57 6A 01 push 1 ; SendFlags dwFlags
AUTO:0054BC59 6A 00 push 0 ; DPID idTo, the receiver ID, with 0 most likely being the server ID
AUTO:0054BC5B A1 30 07 5A 00 mov eax, ds:DPMPInterface
AUTO:0054BC60 56 push esi ; DPID idFrom, the sender ID
AUTO:0054BC61 8B 10 mov edx, [eax]
AUTO:0054BC63 50 push eax ; this-pointer of the IDirectPlay interface instance
AUTO:0054BC64 FF 52 68 call dword ptr [edx+68h] ; Indirect Call Near Procedure
AUTO:0054BC67 89 C2 mov edx, eax
AUTO:0054BC69 EB CA jmp short loc_54BC35 ; Jump
The packet data size here is set at:
Code: Select all
AUTO:0054DFB0 BA 2C 00 00 00 mov edx, 2Ch ; -> packet header size
AUTO:0054DFB5 8B 44 24 78 mov eax, [esp+78h] ; -> arg_0 total data size to send
AUTO:0054DFB9 66 89 54 24 58 mov [esp+58h], dx ; -> locally cache the packet header size WORD
AUTO:0054DFC0 B9 D2 01 00 00 mov ecx, 1D2h ; -> the max packet data size: 1D2h = 466 bytes
; ( by asm_0054E094: 466 data bytes + 44 byte header + 2 bytes = 512 bytes total )
AUTO:0054DFC8 F7 F9 idiv ecx ; -> devide total eax data size by the max packet data size
AUTO:0054DFCA 40 inc eax ; -> increment the packet count by one for the remaining data
AUTO:0054DFCB 89 44 24 30 mov [esp+30h], eax ; -> locally cache the packet count to send
AUTO:0054DFCF 8B 44 24 30 mov eax, [esp+30h] ; -> redundantly read back the packet count to send
AUTO:0054DFD3 BB D2 01 00 00 mov ebx, 1D2h ; -> the max packet data size
AUTO:0054DFD8 48 dec eax ; -> decrement packet count by 1 for full data packets
AUTO:0054DFD9 0F AF C3 imul eax, ebx ; -> multiply eax by the max packet data size
AUTO:0054DFDC 8B 54 24 78 mov edx, [esp+78h] ; -> arg_0 total data size to send
AUTO:0054DFE0 29 C2 sub edx, eax ; -> subtract eax for final packet data size remainder
AUTO:0054DFE2 89 5C 24 64 mov [esp+64h], ebx ; -> cache full packet data size
AUTO:0054DFE6 89 54 24 60 mov [esp+60h], edx ; -> cache final packet data size
...
AUTO:0054E01F 8B 7C 24 30 mov edi, [esp+30h] ; -> read packet count to send
AUTO:0054E035 89 54 24 40 mov [esp+40h], edx ; -> initialize current packet number to 0
AUTO:0054E042 89 54 24 34 mov [esp+34h], edx ; -> reset sent packet count
AUTO:0054E052 8D 47 FF lea eax, [edi-1] ; -> reduce by 1 for final packet number
AUTO:0054E055 89 44 24 38 mov [esp+38h], eax ; -> save final packet number
...
AUTO:0054E059 8B 44 24 40 mov eax, [esp+40h] ; -> read current packet number
AUTO:0054E05D 3B 44 24 38 cmp eax, [esp+38h] ; -> read final packet number
AUTO:0054E061 0F 8D E8 01 00 00 jge loc_54E24F ; -> check whether it's the final packet to send
AUTO:0054E067 8B 44 24 64 mov eax, [esp+64h] ; -> full packet data size if not
AUTO:0054E24F 8B 44 24 60 mov eax, [esp+60h] ; -> final packet data size if reached
AUTO:0054E06B 66 89 44 24 50 mov [esp+50h], ax ; -> locally cache the packet data size WORD
...
AUTO:0054E08C 8B 44 24 50 mov eax, [esp+50h] ; -> the packet data size
AUTO:0054E090 03 44 24 58 add eax, [esp+58h] ; -> add packet header size
AUTO:0054E094 05 02 00 00 00 add eax, 2 ; -> add another two bytes for checksum or whatever
AUTO:0054E099 66 89 44 24 5C mov [esp+5Ch], ax ; -> locally cache the total packet size to send
...
AUTO:0054E0FB 8B 44 24 5A mov eax, [esp+5Ah] ; -> read back the total packet size, but read 16bits ahead of [esp+5Ch]
AUTO:0054E103 C1 F8 10 sar eax, 10h ; -> shift by 16bit to read the WORD value from [esp+5Ch]:
; Since we are in a little endian world, with [esp+5Ah] the low value bits
; from [esp+5Ch] were read into the high value bits of eax.
; The right shift ensures to only read a word value to 32bit eax.
AUTO:0054E106 B9 2C 00 00 00 mov ecx, 2Ch ; -> the header size or data offset
AUTO:0054E10B E8 C0 4D F4 FF call System_Memory_AllocFromNetworklPool ;
...
AUTO:0054E161 8B 4C 24 5A mov ecx, [esp+5Ah] ; -> read back the total packet size
AUTO:0054E16F C1 F9 10 sar ecx, 10h ; -> align for [esp+5Ch] WORD value
AUTO:0054E172 E8 99 DA FF FF call DPMP_54BC10 ; -> call to try send the message and repeat by the surrounding loop
-> AUTO:0054BC21 51 push ecx ; -> read back dwDataSize posted above
-> AUTO:0054BC2F FF 57 68 call dword ptr [edi+68h] ; -> call DirectPlay->Send
...
AUTO:0054E1CD 6A 0A push 0Ah ; -> 10 dwMilliseconds to idle
AUTO:0054E1CF 8B 6C 24 44 mov ebp, [esp+44h] ; -> read current packet number [esp+40h], offset by the push
AUTO:0054E1D3 2E FF 15 94 45 57 00 call cs:__imp_Sleep ; -> Idle at least the pushed milliseconds before sending next packet.
; Note that this potentially can cause a huge bottleneck.
AUTO:0054E1DA 8B 44 24 34 mov eax, [esp+34h] ; -> read sent packet count
AUTO:0054E1DE 8B 54 24 30 mov edx, [esp+30h] ; -> read final packet number
AUTO:0054E1E2 45 inc ebp ; increment current packet number
AUTO:0054E1E3 40 inc eax ; increment packet count sent
AUTO:0054E1E4 89 6C 24 40 mov [esp+40h], ebp ; save current packet number
AUTO:0054E1E8 89 44 24 34 mov [esp+34h], eax ; save packet count sent
AUTO:0054E1EC 39 D5 cmp ebp, edx ; compare current with final packet number
AUTO:0054E1EE 0F 8C 65 FE FF FF jl loc_54E059 ; if not reached, loop sending more packets
The fact that thunderchero experiences even saller packets, might be related to how DirectPlay actually handles delivering them.
It very well might split them up and re-package them. It however also might be, that the outer calls already pass in such small data pieces to send.
Plus note, that DirectPlay has a throttling mechanism, so the awkwardly slow message handling on the receiver side might very well impact DirectPlay packet delivery as well.
The DirectPlay protocol's throttling mechanism guarantees that the client will not receive messages faster than they can be handled.
c.f.
https://documentation.help/DirectPlay/o ... kusage.htm
P.S.: Note that I still didn't do any debugging or testing on it. It is all poor code analysis.
