Attribute VB_Name = "WinSockLib"
Option Explicit

'Init/Destroy

Public Sub StartWinsock()
    Dim FoundError As Boolean

    WSAStartup 257, WSAdata
        If Err.LastDllError > 0 Then AddDebug 0, "WSAStartup() error: " + GetWinsockError(Err.LastDllError), RED: FoundError = True
    WinsockHandle = CreateWindowEx(0, "#32770", "SSBot Winsock", 0&, 0, 0, 1, 1, 0&, 0&, App.hInstance, 0&)
        If Err.LastDllError > 0 Then AddDebug 0, "CreateWindowEx() error: " + CStr(Err.LastDllError), RED: FoundError = True
    OldWndProc = SetWindowLong(WinsockHandle, -4, AddressOf WindowProc)
        If Err.LastDllError > 0 Then AddDebug 0, "SetWindowLong() error: " + CStr(Err.LastDllError), RED: FoundError = True

    If FoundError Then AddDebug 0, "Winsock could not be loaded.", RED
End Sub

Public Sub EndWinsock()
    Call SetWindowLong(WinsockHandle, -4, OldWndProc)
    Call DestroyWindow(WinsockHandle)
    Call WSACleanup
End Sub

'Secure socket language

Public Sub AddSecure(BotID As Integer, Message As String)
    Dim I As Integer

    For I = 1 To MAX_SECURES
        If Secures(I).ResendCount = 0 Then
            Secures(I).InitialTicks = GetTickCount
            Secures(I).Message = Message
            Secures(I).ResendCount = 1
            Secures(I).SourceBot = BotID
            BotList(BotID).TotalReliable = BotList(BotID).TotalReliable + 1
            If BotList(BotID).TotalReliable >= 100 Then
                BotList(BotID).TotalReliable = 1
                BotList(BotID).TotalLost = 0
            End If
            Exit Sub
        End If
    Next
End Sub

Public Sub ReceivedACK(ACKMessage As String)
    Dim I As Integer, L As Long

    For I = 1 To MAX_SECURES
        If Secures(I).ResendCount <> 0 And Mid(Secures(I).Message, 3, 2) = Mid(ACKMessage, 3, 2) Then
            Secures(I).ResendCount = 0
            Call UpdateLag(Secures(I).SourceBot, GetTickCount - Secures(I).InitialTicks)
            Exit Sub
        End If
    Next
End Sub

Public Sub ClearACKs(BotID As Integer)
    Dim L As Long

    For L = -32768 To 32767
        ACKs(BotID, L) = 0
    Next
End Sub

'Packet backlog

Public Function GetNext(LoopEntry As Integer) As Integer
    If LoopEntry = 32767 Then
        GetNext = -32768
    Else
        GetNext = LoopEntry + 1
    End If
End Function

Public Sub ClearBacklog(BotID As Integer)
    Dim I As Integer

    For I = 0 To MAX_BACKLOG
        Backlog(BotID, I).Closed = 0
    Next
End Sub

Public Sub CheckBacklog(BotID As Integer, UnencryptedPacket As String, PacketID As Integer)
On Error GoTo FlyPaper

    Dim I As Integer

    If PacketID = GetNext(LastPacketID(BotID)) Then
        'Handle this one
        LastPacketID(BotID) = PacketID
        Call HandleGamePacket(Mid(UnencryptedPacket, 7))
        'Check the backlog for queued entries
        For I = 0 To MAX_BACKLOG
            If Backlog(BotID, I).Closed = 1 Then
                If Backlog(BotID, I).PacketID = GetNext(LastPacketID(BotID)) Then
                    Call HandleGamePacket(Backlog(BotID, I).Data)
                    Backlog(BotID, I).Closed = 0
                    LastPacketID(BotID) = Backlog(BotID, I).PacketID
                    I = -1
                End If
            End If
        Next
    Else
        'Queue this message, wait for the next one in sequence
        For I = 0 To MAX_BACKLOG
            If Backlog(BotID, I).Closed = 0 Then
                Backlog(BotID, I).PacketID = PacketID
                Backlog(BotID, I).Data = Mid(UnencryptedPacket, 7)
                Backlog(BotID, I).Closed = 1
                Exit Sub
            End If
        Next
        'If it gets to this point, just flush the backlog and handle everything
        For I = 0 To MAX_BACKLOG
            Call HandleGamePacket(Backlog(BotID, I).Data)
            Backlog(BotID, I).Closed = 0
        Next
        Call HandleGamePacket(Mid(UnencryptedPacket, 7))
    End If

Exit Sub
FlyPaper:
    AddDebug BotID, "Error detected in CheckBacklog(" + CStr(PacketID) + "): " + Err.Description, RED
End Sub

Public Sub ResendMessages()
    Dim I As Integer

    For I = 1 To MAX_SECURES
        If Secures(I).ResendCount <> 0 Then
            UDPSend Secures(I).SourceBot, Secures(I).Message, False
            Secures(I).InitialTicks = GetTickCount

            If Secures(I).ResendCount = 1 Then
                BotList(Secures(I).SourceBot).TotalLost = BotList(Secures(I).SourceBot).TotalLost + 1
            ElseIf Secures(I).ResendCount >= 10 Then
                Secures(I).ResendCount = -1
            End If
            Secures(I).ResendCount = Secures(I).ResendCount + 1
        End If
    Next
End Sub

Public Sub ClearSecures(BotID As Integer)
    Dim I As Integer

    For I = 1 To MAX_SECURES
        If Secures(I).SourceBot = BotID Then Secures(I).ResendCount = 0
    Next
End Sub

'Lag stats

Public Sub UpdateLag(BotID As Integer, NewValue As Integer)
Dim L As Long

With BotList(BotID)

    If .AvgPing = 0 Then .AvgPing = NewValue: .PingTime = NewValue

    'Filter spikes

    If NewValue > .AvgPing * 2 Then Exit Sub

    'Use (lag interval) * 2 for the SSL timer

    On Error Resume Next

    MainFrm.SSL.Interval = NewValue + .PingTime
    MainFrm.SSL.Enabled = False
    MainFrm.SSL.Enabled = True
    .PingTime = MainFrm.SSL.Interval \ 2

    'Update the average ping time

    On Error GoTo TooBig

    L = .AvgPing * .TotalPings + .PingTime
    .AvgPing = Int(CInt(L) \ (.TotalPings + 1))
    .TotalPings = .TotalPings + 1

Exit Sub
TooBig:

    .AvgPing = .PingTime
    .TotalPings = 1

End With
End Sub

'UDP stuff

Private Sub UDPOnRecv(Message As String)
    Call HandlePacket(Message)

    BotList(CurrentBot).TotalReceived = BotList(CurrentBot).TotalReceived + 1
End Sub

Public Sub UDPSend(BotID As Integer, Message As String, SendSecure As Boolean)
    If SendSecure Then
        Message = Chr(0) + Chr(3) + NextSID(BotID) + Message
        AddSecure BotID, Message
    End If

    Call sendto(BotList(BotID).MySession.SockID, ByVal Message, Len(Message), 0, BotList(BotID).MySession.Remote, 16)

    BotList(BotID).TotalSent = BotList(BotID).TotalSent + 1
End Sub

Public Function UDPMakeSocket(Port As Integer) As SocketData
    Dim RetStruct As SocketData, S As Long

    S = socket(2, 2, 17)
        sockaddr.sin_addr = 0
        sockaddr.sin_port = htons(Port)
        sockaddr.sin_family = 2
    Call bind(S, sockaddr, 16)
        RetStruct.SockID = S
    Call WSAAsyncSelect(S, WinsockHandle, 5150, FD_ALL)

    UDPMakeSocket = RetStruct
End Function

Public Function UDPRoute(Port As Integer, IP As String) As sockaddr
    sockaddr.sin_addr = IP2Long(IP)
    sockaddr.sin_port = htons(Port)
    sockaddr.sin_family = 2
    UDPRoute = sockaddr
End Function

Public Sub UDPDestroySocket(SockData As SocketData)
    Call WSAAsyncSelect(SockData.SockID, WinsockHandle, 0, 0)
    closesocket SockData.SockID
End Sub

'Winsock window callback

Private Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Dim mStr As String * 520, mLen As Integer

    If uMsg = 5150 Then
        CurrentBot = FindBot(wParam)
        If CurrentBot = 0 Then Exit Function

        mLen = recvfrom(wParam, ByVal mStr, 520, 0, sockaddr, 16)
        If mLen > 0 Then UDPOnRecv Left(mStr, mLen)
    Else
        WindowProc = CallWindowProc(OldWndProc, hWnd, uMsg, wParam, ByVal lParam)
    End If
End Function

'Winsock utilities

Public Function Long2IP(Address As Long) As String
    Dim S4 As String * 4

    S4 = GetString(Address)
    Long2IP = CStr(Asc(Left(S4, 1))) + "." + CStr(Asc(Mid(S4, 2, 1))) + "." + CStr(Asc(Mid(S4, 3, 1))) + "." + CStr(Asc(Right(S4, 1)))
End Function

Public Function IP2Long(ByVal Hostname As String) As Long
    Dim L As Long, addrList As Long, retIP As Long

    retIP = inet_addr(Hostname)
    If retIP = &HFFFF Then
        L = gethostbyname(Hostname)
        If L <> 0 Then
            CopyMemory ByVal hostent, L, 16
            CopyMemory ByVal addrList, hostent.h_addr_list, 4
            CopyMemory ByVal retIP, addrList, hostent.h_length
        Else
            retIP = &HFFFF
        End If
    End If

    IP2Long = retIP
End Function

Function GetWinsockError(ErrorNumber As Integer) As String
    Select Case ErrorNumber
        Case 10004: GetWinsockError = "Interrupted system call."
        Case 10009: GetWinsockError = "Bad file number."
        Case 10013: GetWinsockError = "Permission Denied."
        Case 10014: GetWinsockError = "Bad Address."
        Case 10022: GetWinsockError = "Invalid Argument."
        Case 10024: GetWinsockError = "Too many open files."
        Case 10035: GetWinsockError = "Operation would block."
        Case 10036: GetWinsockError = "Operation now in progress."
        Case 10037: GetWinsockError = "Operation already in progress."
        Case 10038: GetWinsockError = "Socket operation on nonsocket."
        Case 10039: GetWinsockError = "Destination address required."
        Case 10040: GetWinsockError = "Message too long."
        Case 10041: GetWinsockError = "Protocol wrong type for socket."
        Case 10042: GetWinsockError = "Protocol not available."
        Case 10043: GetWinsockError = "Protocol not supported."
        Case 10044: GetWinsockError = "Socket type not supported."
        Case 10045: GetWinsockError = "Operation not supported on socket."
        Case 10046: GetWinsockError = "Protocol family not supported."
        Case 10047: GetWinsockError = "Address family not supported by protocol family."
        Case 10048: GetWinsockError = "Address already in use."
        Case 10049: GetWinsockError = "Can't assign requested address."
        Case 10050: GetWinsockError = "Network is down."
        Case 10051: GetWinsockError = "Network is unreachable."
        Case 10052: GetWinsockError = "Network dropped connection."
        Case 10053: GetWinsockError = "Software caused connection abort."
        Case 10054: GetWinsockError = "Connection reset by peer."
        Case 10055: GetWinsockError = "No buffer space available."
        Case 10056: GetWinsockError = "Socket is already connected."
        Case 10057: GetWinsockError = "Socket is not connected."
        Case 10058: GetWinsockError = "Can't send after socket shutdown."
        Case 10059: GetWinsockError = "Too many references: can't splice."
        Case 10060: GetWinsockError = "Connection timed out."
        Case 10061: GetWinsockError = "Connection refused."
        Case 10062: GetWinsockError = "Too many levels of symbolic links."
        Case 10063: GetWinsockError = "File name too long."
        Case 10064: GetWinsockError = "Host is down."
        Case 10065: GetWinsockError = "No route to host."
        Case 10066: GetWinsockError = "Directory not empty."
        Case 10067: GetWinsockError = "Too many processes."
        Case 10068: GetWinsockError = "Too many users."
        Case 10069: GetWinsockError = "Disk quota exceeded."
        Case 10070: GetWinsockError = "Stale NFS file handle."
        Case 10071: GetWinsockError = "Too many levels of remote in path."
        Case 10091: GetWinsockError = "Network subsystem is unusable."
        Case 10092: GetWinsockError = "Winsock DLL cannot support this application."
        Case 10093: GetWinsockError = "Winsock not initialized."
        Case 10101: GetWinsockError = "Disconnect."
        Case 11001: GetWinsockError = "Host not found."
        Case 11002: GetWinsockError = "Nonauthoritative host not found."
        Case 11003: GetWinsockError = "Nonrecoverable error."
        Case 11004: GetWinsockError = "Valid name, no data record of requested type."
        Case Else: GetWinsockError = "Non-winsock error."
    End Select
End Function
