forked from nielsAD/gowarcraft3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
packets.go
2101 lines (1903 loc) · 62.7 KB
/
packets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Author: Niels A.D.
// Project: gowarcraft3 (https://github.com/nielsAD/gowarcraft3)
// License: Mozilla Public License, v2.0
package bncs
import (
"math/bits"
"net"
"strconv"
"github.com/nielsAD/gowarcraft3/protocol"
"github.com/nielsAD/gowarcraft3/protocol/w3gs"
)
func readPacketHeader(buf *protocol.Buffer) (byte, int) {
var bsize = buf.Size()
if bsize < 4 {
return 0, -1
}
buf.Skip(1)
var pid = buf.ReadUInt8()
var psize = int(buf.ReadUInt16())
if psize > bsize {
return pid, -1
}
return pid, psize
}
func readPacketSize(buf *protocol.Buffer) int {
var _, psize = readPacketHeader(buf)
return psize
}
// UnknownPacket is used to store unknown packets.
type UnknownPacket struct {
ID byte
Blob []byte
}
// Serialize encodes the struct into its binary form.
func (pkt *UnknownPacket) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(pkt.ID)
buf.WriteUInt16(uint16(4 + len(pkt.Blob)))
buf.WriteBlob(pkt.Blob)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *UnknownPacket) Deserialize(buf *protocol.Buffer) error {
var pid, size = readPacketHeader(buf)
if size < 4 {
return ErrInvalidPacketSize
}
pkt.ID = pid
pkt.Blob = buf.ReadBlob(size - 4)
return nil
}
// KeepAlive implements the [0x00] SID_Null packet (S -> C, C -> S).
//
// Keeps the connection alive. This message should be sent to the server every 8 minutes (approximately).
//
// The server will send this to you automatically, you do not have to reply to it. You should send this on your own never-ending timer for at least as often as Battle.net does (give or take a few seconds).
//
// This packet is used to detect if your TCP connection has gone dead, to the point where you will never receive data from the server ever again until you reconnect your connection. A situation such as this can be created by unplugging your internet connection for a few minutes, or if your internet is dropped for whatever reason.
//
// Format:
//
// [blank]
//
type KeepAlive struct {
// Empty
}
// Serialize encodes the struct into its binary form.
func (pkt *KeepAlive) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidNull)
buf.WriteUInt16(4)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *KeepAlive) Deserialize(buf *protocol.Buffer) error {
if readPacketSize(buf) != 4 {
return ErrInvalidPacketSize
}
return nil
}
// Ping implements the [0x25] SID_Ping packet (S -> C, C -> S).
//
// Used to calculate Client's ping. The received UINT32 should be sent directly back to Battle.net.
//
// The ping displayed when in chat can be artificially inflated by delaying before sending this packet, or deflated by responding before requested.
// Ping can be set to -1 (Strictly speaking, 0xFFFFFFFF, since ping is unsigned) by not responding to this packet at all.
//
// The received UINT32 is not what determines your ping, but it is actually a cookie for the Battle.net server. You should never change the UINT32.
//
// Format:
//
// (UINT32) Ping Value
//
type Ping struct {
Payload uint32
}
// Serialize encodes the struct into its binary form.
func (pkt *Ping) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidPing)
buf.WriteUInt16(8)
buf.WriteUInt32(pkt.Payload)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *Ping) Deserialize(buf *protocol.Buffer) error {
if readPacketSize(buf) != 8 {
return ErrInvalidPacketSize
}
pkt.Payload = buf.ReadUInt32()
return nil
}
// EnterChatReq implements the [0x0A] SID_EnterChat packet (C -> S).
//
// Joins Chat.
//
// Username: Null on WAR3/W3XP.
//
// StatString: Null on CDKey Products, except for D2DV and D2XP when on realm characters..
//
// Format:
//
// (STRING) Username
// (STRING) StatString
//
type EnterChatReq struct {
// Empty
}
// Serialize encodes the struct into its binary form.
func (pkt *EnterChatReq) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidEnterChat)
buf.WriteUInt16(6)
buf.WriteUInt16(0)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *EnterChatReq) Deserialize(buf *protocol.Buffer) error {
if readPacketSize(buf) != 6 {
return ErrInvalidPacketSize
}
if buf.ReadUInt16() != 0 {
return ErrUnexpectedConst
}
return nil
}
// EnterChatResp implements the [0x0A] SID_EnterChat packet (S -> C).
//
// Contains Client product, realm, statstring, and is sent as the response when the client sends SID_EnterChat. Unique name is the users unique name in chat (Which may be Arta, Arta#2, Arta#3, etc). Account name is the users account name (Which in all 3 previous examples would be Arta).
//
// Once you receive this packet, you are not in a channel, but can join/host games and join channels. Because you are not in a channel, you cannot send text messages yet (but you will not be disconnected if you do). See SID_JoinChannel.
//
// Format:
//
// (STRING) Unique name
// (STRING) StatString
// (STRING) Account name
//
type EnterChatResp struct {
UniqueName string
StatString string
AccountName string
}
// Serialize encodes the struct into its binary form.
func (pkt *EnterChatResp) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidEnterChat)
buf.WriteUInt16(uint16(7 + len(pkt.UniqueName) + len(pkt.StatString) + len(pkt.AccountName)))
buf.WriteCString(pkt.UniqueName)
buf.WriteCString(pkt.StatString)
buf.WriteCString(pkt.AccountName)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *EnterChatResp) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 7 {
return ErrInvalidPacketSize
}
var err error
if pkt.UniqueName, err = buf.ReadCString(); err != nil {
return err
}
if size < 7+len(pkt.UniqueName) {
return ErrInvalidPacketSize
}
if pkt.StatString, err = buf.ReadCString(); err != nil {
return err
}
if size < 7+len(pkt.UniqueName)+len(pkt.StatString) {
return ErrInvalidPacketSize
}
if pkt.AccountName, err = buf.ReadCString(); err != nil {
return err
}
if size != 7+len(pkt.UniqueName)+len(pkt.StatString)+len(pkt.AccountName) {
return ErrInvalidPacketSize
}
return nil
}
// JoinChannel implements the [0x0C] SID_JoinChannel packet (C -> S).
//
// Joins a channel after entering chat.
//
// The Channel name must be no greater than 31 characters, otherwise it becomes trimmed by Battle.net.
//
// The flags field may contain the following values:
// 0x00: NoCreate join
// 0x01: First join
// 0x02: Forced join
// 0x05: D2 first join
//
// NoCreate Join:
// This will only join the channel specified if it is not empty, and is used by clients when selecting a channel from the channels menu. If the channel is empty, Battle.net sends a SID_ChatEvent of type EID_CHANNELDOESNOTEXIST, upon which official clients prompt for confirmation that the user wishes to create the channel, in which case, it resends this packet with Flags set to Forced Join (0x02).
// First Join:
// Places user in a channel starting with their product and country, followed by a number, ie 'Brood War GBR-1'. Also automatically sends MOTD after entering the channel. When using this type, the Channel variable has no effect, but must be present anyway to avoid an IP ban. This is sent when first logging onto Battle.net
// Forced Join:
// This is sent when leaving a game, and joins the specified channel without an supplying an MOTD.
// D2 First Join:
// The same as First join, but is used for D2DV/D2XP clients.
//
// Format:
//
// (UINT32) Flags
// (STRING) Channel
//
type JoinChannel struct {
Flag JoinChannelFlag
Channel string
}
// Serialize encodes the struct into its binary form.
func (pkt *JoinChannel) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidJoinChannel)
buf.WriteUInt16(uint16(9 + len(pkt.Channel)))
buf.WriteUInt32(uint32(pkt.Flag))
buf.WriteCString(pkt.Channel)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *JoinChannel) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 9 {
return ErrInvalidPacketSize
}
pkt.Flag = JoinChannelFlag(buf.ReadUInt32())
var err error
if pkt.Channel, err = buf.ReadCString(); err != nil {
return err
}
if size != 9+len(pkt.Channel) {
return ErrInvalidPacketSize
}
return nil
}
// ChatCommand implements the [0x0E] SID_ChatCommand packet (C -> S).
//
// Send text or a command to Battle.net using this packet.
//
// For STAR/SEXP/SSHR/JSTR, Text is UTF-8 encoded (WIDESTRING).
//
// It is generally accepted as unwise to send any character below a space (0x20): this includes line feeds, carriage returns & control characters. The maximum number of characters is 224 per message including the null-terminator (so really only 223 characters), any longer and it becomes trimmed by Battle.net.
//
// If you send a line feed and/or a carriage return, Battle.net disconnects you and IP bans you for 5 minutes.
//
// Format:
//
// (STRING) Text
//
type ChatCommand struct {
Text string
}
// Serialize encodes the struct into its binary form.
func (pkt *ChatCommand) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidChatCommand)
buf.WriteUInt16(uint16(5 + len(pkt.Text)))
buf.WriteCString(pkt.Text)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *ChatCommand) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 5 {
return ErrInvalidPacketSize
}
var err error
if pkt.Text, err = buf.ReadCString(); err != nil {
return err
}
if size != 5+len(pkt.Text) {
return ErrInvalidPacketSize
}
return nil
}
// ChatEvent implements the [0x0F] SID_ChatEvent packet (S -> C).
//
// Contains all chat events.
//
// Text: For STAR/SEXP/SSHR/JSTR, this field is UTF-8 encoded. For all other clients, it is ISO 8859-1 encoded. It must also be no longer than 255 characters; official clients should only be able to send 224 characters (including the null-terminator).
//
// Event IDs:
// 0x01 EID_SHOWUSER: User in channel
// 0x02 EID_JOIN: User joined channel
// 0x03 EID_LEAVE: User left channel
// 0x04 EID_WHISPER: Recieved whisper
// 0x05 EID_TALK: Chat text
// 0x06 EID_BROADCAST: Server broadcast
// 0x07 EID_CHANNEL: Channel information
// 0x09 EID_USERFLAGS: Flags update
// 0x0A EID_WHISPERSENT: Sent whisper
// 0x0D EID_CHANNELFULL: Channel full
// 0x0E EID_CHANNELDOESNOTEXIST: Channel doesn't exist
// 0x0F EID_CHANNELRESTRICTED: Channel is restricted
// 0x12 EID_INFO: Information
// 0x13 EID_ERROR: Error message
// 0x15 EID_IGNORE: Notifies that a user has been ignored (DEFUNCT)
// 0x16 EID_ACCEPT: Notifies that a user has been unignored (DEFUNCT)
// 0x17 EID_EMOTE: Emote
//
//
// EID_SHOWUSER:
// This is sent for each user who is already in a channel when you join it, as opposed to EID_JOIN, which is sent when a user joins a channel you have already joined. It is also sent when logged on using D2XP/D2DV and a user requires an update to their statstring - for example, by logging a different character onto a realm.
//
// EID_JOIN:
// This is sent when a user enters the channel you are currently in.
//
// EID_LEAVE:
// This is sent when a user exits the channel you are currently in.
//
// EID_WHISPER:
// This is sent when a user whispers you.
//
// EID_TALK:
// This is sent when a user (excluding self) in chat speaks.
//
// EID_BROADCAST:
// This is sent when a server announcement is being made globally.
// The username supplied for this event is now always `Battle.net`. Historically, the username was the name of the Battle.net Administrator who sent the broadcast.
//
// EID_CHANNEL:
// The flags field for this event is used and indicates what special conditions exist for the channel in question.
//
// EID_USERFLAGS:
// This is sent to inform the client of an update to one or more user's flags.
// Battle.net usually sends this event for every user in the channel, even if only one user's flags have changed. This behavior can be exploited to detect invisible users, by performing an action (such as an unsquelch) to provoke a flags update. Users included in the flags update whose presence has not been indicated by `EID_JOIN` or `EID_SHOWUSER` can then be added to the userlist as invisible. Care should be taken, however, to account for the possibility that an asynchronous send error has occurred. Should an `EID_JOIN` or `EID_SHOWUSER` event occur for an invisible user, they should be marked as a normal user, not readded to the userlist.
//
// EID_WHISPERSENT:
// The Flags and Ping fields in this packet is equal to the originating user - the one who sent the whisper. In other words, `EID_WHISPERSENT` contains your flags & ping, not those of the person you whispered.
//
// EID_CHANNELDOESNOTEXIST:
// See info on `NoCreate Join` in SID_JoinChannel.
//
// EID_CHANNELRESTRICTED:
// This is sent when attempting to join a channel which your client is not allowed to join.
//
// EID_INFO:
// This is information supplied by Battle.net. This text is usually displayed by clients in yellow.
//
// EID_ERROR:
// This is error information supplied by Battle.net. This text is usually displayed by clients in red.
//
// EID_EMOTE:
// This is sent when any user (including self) uses the emote feature in chat.
//
// Format:
//
// (UINT32) Event ID
// (UINT32) User's Flags
// (UINT32) Ping
// (UINT32) IP Address (defunct 0x00000000)
// (UINT32) Account number (defunct 0x00000000)
// (UINT32) Registration Authority (defunct 0x00000000)
// (STRING) Username
// (STRING) Text
//
type ChatEvent struct {
Type ChatEventType
UserFlags ChatUserFlags
ChannelFlags ChatChannelFlags
Ping uint32
Username string
Text string
}
// Serialize encodes the struct into its binary form.
func (pkt *ChatEvent) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidChatEvent)
buf.WriteUInt16(uint16(30 + len(pkt.Username) + len(pkt.Text)))
buf.WriteUInt32(uint32(pkt.Type))
if pkt.Type == ChatChannelInfo {
buf.WriteUInt32(uint32(pkt.ChannelFlags))
} else {
buf.WriteUInt32(uint32(pkt.UserFlags))
}
buf.WriteUInt32(pkt.Ping)
buf.WriteUInt32(0)
buf.WriteUInt32(0xbaadf00d)
buf.WriteUInt32(0xbaadf00d)
buf.WriteCString(pkt.Username)
buf.WriteCString(pkt.Text)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *ChatEvent) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 30 {
return ErrInvalidPacketSize
}
pkt.Type = ChatEventType(buf.ReadUInt32())
if pkt.Type == ChatChannelInfo {
pkt.UserFlags = 0
pkt.ChannelFlags = ChatChannelFlags(buf.ReadUInt32())
} else {
pkt.ChannelFlags = 0
pkt.UserFlags = ChatUserFlags(buf.ReadUInt32())
}
pkt.Ping = buf.ReadUInt32()
if buf.ReadUInt32() != 0 || buf.ReadUInt32() != 0xbaadf00d || buf.ReadUInt32() != 0xbaadf00d {
return ErrUnexpectedConst
}
var err error
if pkt.Username, err = buf.ReadCString(); err != nil {
return err
}
if size < 30+len(pkt.Username) {
return ErrInvalidPacketSize
}
if pkt.Text, err = buf.ReadCString(); err != nil {
return err
}
if size != 30+len(pkt.Username)+len(pkt.Text) {
return ErrInvalidPacketSize
}
return nil
}
// FloodDetected implements the [0x13] SID_FloodDetected packet (S -> C).
//
// Sent prior to a disconnect along with SID_MessageBox to indicate that the client has flooded off.
//
// Format:
//
// [blank]
//
type FloodDetected struct {
// Empty
}
// Serialize encodes the struct into its binary form.
func (pkt *FloodDetected) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidFloodDetected)
buf.WriteUInt16(4)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *FloodDetected) Deserialize(buf *protocol.Buffer) error {
if readPacketSize(buf) != 4 {
return ErrInvalidPacketSize
}
return nil
}
// MessageBox implements the [0x19] SID_MessageBox packet (S -> C).
//
// Displays a message to the user. This message's fields are used as parameters for the Win32 MessageBox API (http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx), and can be passed directly to it.
//
// Format:
//
// (UINT32) Style
// (STRING) Text
// (STRING) Caption
//
type MessageBox struct {
Style uint32
Text string
Caption string
}
// Serialize encodes the struct into its binary form.
func (pkt *MessageBox) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidMessageBox)
buf.WriteUInt16(uint16(10 + len(pkt.Text) + len(pkt.Caption)))
buf.WriteUInt32(pkt.Style)
buf.WriteCString(pkt.Text)
buf.WriteCString(pkt.Caption)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *MessageBox) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 10 {
return ErrInvalidPacketSize
}
pkt.Style = buf.ReadUInt32()
var err error
if pkt.Text, err = buf.ReadCString(); err != nil {
return err
}
if size < 10+len(pkt.Text) {
return ErrInvalidPacketSize
}
if pkt.Caption, err = buf.ReadCString(); err != nil {
return err
}
if size != 10+len(pkt.Text)+len(pkt.Caption) {
return ErrInvalidPacketSize
}
return nil
}
// GameSettings stores the settings of a created game.
//
// This field still conforms to being a null-terminated string by encoding the data such that there are no null characters within.
// The first 9 `UINT8`s are characters representing hexadecimal integers (`0` through `9` and `a` through `f`).
// After this, the rest of the data is encoded in a manner in order to contain nulls when decoded, but be stored within this null-terminated string.
//
// Format:
// (CHAR) Number of free slots (ex: `7` for 7 free slots)
// (CHAR)[8] Host counter (reversed hexadecimal integer, ex: `20000000` for second time this host has hosted during his session)
// Encoded data:
// (UINT32) Map flags (combine the below settings):
// Game speed (mask 0x00000003, unique):
// `0x00000000`: Slow
// `0x00000001`: Normal
// `0x00000002`: Fast
// Visibility setting (mask 0x00000F00, unique):
// `0x00000100`: Hide Terrain
// `0x00000200`: Map Explored
// `0x00000400`: Always Visible
// `0x00000800`: Default
// Observers setting (mask 0x40003000, unique):
// `0x00000000`: No Observers
// `0x00002000`: Observers on Defeat
// `0x00003000`: Full Observers
// `0x40000000`: Referees
// Other advanced host settings (mask 0x07064000, combinable):
// `0x00004000`: Teams Together (team members are placed at neighbored starting locations)
// `0x00060000`: Lock Teams
// `0x01000000`: Full Shared Unit Control
// `0x02000000`: Random Hero
// `0x04000000`: Random Races
// (UINT8) Map null 1
// (UINT8) Map width (playable area)
// (UINT8) Map null 2
// (UINT8) Map height (playable area)
// (UINT8) Map null 3
// (UINT32) Map CRC
// (STRING) Map path
// (STRING) Game host name
// (UINT8) Map unknown (possibly a STRING with just the null terminator)
// (UINT8)[20] Unknown (probably a SHA1 hash)
//
// Format:
//
// (UINT32) Flags
// (UINT16) Map width
// (UINT16) Map height
// (UINT32) Map xoro
// (STRING) Map path
// (STRING) Host name
// (UINT8)[20] Map Sha1 hash
//
// Encoded as a null terminated string where every even byte-value was
// incremented by 1. So all encoded bytes are odd. A control-byte stores
// the transformations for the next 7 bytes.
//
type GameSettings struct {
SlotsFree uint8
HostCounter uint32
GameSettings w3gs.GameSettings
}
// Size of Serialize()
func (gs *GameSettings) Size() int {
return 9 + gs.GameSettings.Size()
}
// Serialize GameSettings into StatString
func (gs *GameSettings) Serialize(buf *protocol.Buffer) {
if gs.SlotsFree >= 10 {
buf.WriteUInt8('1' + '0' + gs.SlotsFree - 10)
} else {
buf.WriteUInt8('0' + gs.SlotsFree)
}
// Hexadecimal in reverse ordering with 0 padding until 8 bytes
var b = []byte{'0', '0', '0', '0', '0', '0', '0', '0'}
var c = bits.ReverseBytes32(gs.HostCounter)
c = c&0x0F0F0F0F<<4 | c&0xF0F0F0F0>>4
strconv.AppendInt(b[:0], int64(c), 16)
buf.WriteBlob(b)
gs.GameSettings.Serialize(buf)
}
// Deserialize GameSettings from StatString
func (gs *GameSettings) Deserialize(buf *protocol.Buffer) error {
gs.SlotsFree = buf.ReadUInt8()
if gs.SlotsFree >= '1'+'0' {
gs.SlotsFree -= '1' + '0' - 10
} else if gs.SlotsFree >= '0' {
gs.SlotsFree -= '0'
}
// Hexadecimal in reverse ordering with 0 padding until 8 bytes
var b = string(buf.ReadBlob(8))
if c, err := strconv.ParseUint(b, 16, 32); err == nil {
c = c&0x0F0F0F0F<<4 | c&0xF0F0F0F0>>4
gs.HostCounter = uint32(bits.ReverseBytes32(uint32(c)))
} else {
return err
}
if err := gs.GameSettings.Deserialize(buf); err != nil {
return err
}
return nil
}
// GetAdvListResp implements the [0x09] SID_GetAdvListEx packet (S -> C).
//
// Returns a list of available games and their information. Values vary depending on product.
//
// The Game settings field is a combination of values.
// WarCraft III (WAR3/W3XP): combine the below settings
// Game type (mask `0x000000FF`, unique)
// `0x00000001`: Custom
// `0x00000009`: Ladder
// Public/private (mask `0x00000800`, unique)
// `0x00000000`: Public game
// `0x00000800`: Private game
// Map author ID (mask `0x00006000`, combinable)
// `0x00002000`: Blizzard
// `0x00004000`: Custom
// Battle/scenario (mask `0x00018000`, unique)
// `0x00000000`: Battle
// `0x00010000`: Scenario
// Map size (mask `0x000E0000`, combinable)
// `0x00020000`: Small
// `0x00040000`: Medium
// `0x00080000`: Huge
// Observers (mask `0x00070000`, unique)
// `0x00100000`: Allowed observers ("Full Observers" and "Referees" options)
// `0x00200000`: Observers on defeat
// `0x00400000`: No observers
//
// The Address Family, Port, Host's IP, and sin_zero fields form a sockaddr_in(https://msdn.microsoft.com/en-us/library/zx63b042.aspx) structure.
//
// The Game status field varies by product.
// WarCraft III (WAR3/W3XP):
// `0x00000010`: Public
// `0x00000011`: Private
// When there are no entries returned, the Status field uses this list of results, as well.
// `0x00000000`: OK
// `0x00000001`: Game doesn't exist
// `0x00000002`: Incorrect password
// `0x00000003`: Game full
// `0x00000004`: Game already started
// `0x00000005`: Spawned CD-Key not allowed
// `0x00000006`: Too many server requests
//
// The Game name field is UTF-8 encoded.
//
// The Game password field is always empty on WarCraft III.
//
// The Game statstring field contains more information that is very specific to each product.
//
// Format:
//
// (UINT32) Number of games
//
// If count is 0:
// (UINT32) Status
//
// Otherwise, games are listed thus:
// For each list item:
// (UINT32) Game settings
// (UINT32) Language ID
// (UINT16) Address Family (Always AF_INET)
// (UINT16) Port
// (UINT32) Host's IP
// (UINT32) sin_zero (0)
// (UINT32) sin_zero (0)
// (UINT32) Game status
// (UINT32) Elapsed time (in seconds)
// (STRING) Game name
// (STRING) Game password
// (STRING) Game statstring
//
type GetAdvListResp struct {
Result AdvListResult //If count is 0
Games []GetAdvListGame //Otherwise
}
// GetAdvListGame stores a game in GetAdvList.
//
// Format:
// (UINT32) Game settings
// (UINT32) Language ID
// (UINT16) Address Family (Always AF_INET)
// (UINT16) Port
// (UINT32) Host's IP
// (UINT32) sin_zero (0)
// (UINT32) sin_zero (0)
// (UINT32) Game status
// (UINT32) Elapsed time (in seconds)
// (STRING) Game name
// (STRING) Game password
// (STRING) Game statstring
//
type GetAdvListGame struct {
GameFlags w3gs.GameFlags
LanguageID uint32
Addr protocol.SockAddr
GameStateFlags GameStateFlags
UptimeSec uint32
GameName string
GameSettings GameSettings
}
// Serialize encodes the struct into its binary form.
func (pkt *GetAdvListResp) Serialize(buf *protocol.Buffer) error {
var start = buf.Size()
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidGetAdvListEx)
// Placeholder for size
buf.WriteUInt16(0)
if len(pkt.Games) == 0 {
buf.WriteUInt32(0)
buf.WriteUInt32(uint32(pkt.Result))
} else {
buf.WriteUInt32(uint32(len(pkt.Games)))
for i := 0; i < len(pkt.Games); i++ {
buf.WriteUInt32(uint32(pkt.Games[i].GameFlags))
buf.WriteUInt32(pkt.Games[i].LanguageID)
buf.WriteSockAddr(&pkt.Games[i].Addr)
buf.WriteUInt32(uint32(pkt.Games[i].GameStateFlags))
buf.WriteUInt32(pkt.Games[i].UptimeSec)
buf.WriteCString(pkt.Games[i].GameName)
buf.WriteUInt8(0)
pkt.Games[i].GameSettings.Serialize(buf)
}
}
// Set size
buf.WriteUInt16At(start+2, uint16(buf.Size()-start))
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *GetAdvListResp) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 12 {
return ErrInvalidPacketSize
}
var numGames = int(buf.ReadUInt32())
if cap(pkt.Games) < numGames {
pkt.Games = make([]GetAdvListGame, 0, numGames)
}
pkt.Games = pkt.Games[:numGames]
if numGames == 0 {
if size != 12 {
return ErrInvalidPacketSize
}
pkt.Result = AdvListResult(buf.ReadUInt32())
return nil
}
pkt.Result = AdvListSuccess
size -= 8
for i := 0; i < len(pkt.Games); i++ {
if size < 35 {
return ErrInvalidPacketSize
}
pkt.Games[i].GameFlags = w3gs.GameFlags(buf.ReadUInt32())
pkt.Games[i].LanguageID = buf.ReadUInt32()
var err error
if pkt.Games[i].Addr, err = buf.ReadSockAddr(); err != nil {
return err
}
pkt.Games[i].GameStateFlags = GameStateFlags(buf.ReadUInt32())
pkt.Games[i].UptimeSec = buf.ReadUInt32()
if pkt.Games[i].GameName, err = buf.ReadCString(); err != nil {
return err
}
size -= 33 + len(pkt.Games[i].GameName)
if size < 2 {
return ErrInvalidPacketSize
}
if buf.ReadUInt8() != 0 {
return ErrUnexpectedConst
}
if err = pkt.Games[i].GameSettings.Deserialize(buf); err != nil {
return err
}
size -= 1 + pkt.Games[i].GameSettings.Size()
}
if size != 0 {
return ErrInvalidPacketSize
}
return nil
}
// GetAdvListReq implements the [0x09] SID_GetAdvListEx packet (C -> S).
//
// Retrieves a list of games.
//
// Viewing Filter:
// 0xFFFF is used to use the combination of values in this packet.
// 0xFF80 is used to show all games.
// For STAR/SEXP/SSHR/JSTR, viewing filter is set to 0x30.
// For DRTL/DSHR, viewing filter is set to 0xFFFF by the game, but setting it to 0x00 will disable any viewing limitations, letting you view all games.
// Reserved (0):
// This value is hardcoded to 0x00 by all games.
// Number of Games:
// This is the number of games to list. For a full listing, it's safe to use 0xFF. By default, DRTL/DSHR sets this to 0x19.
//
// Format:
//
// (UINT16) Game Type
// (UINT16) Sub Game Type
// (UINT32) Viewing Filter
// (UINT32) Reserved (0)
// (UINT32) Number of Games
// (STRING) Game Name
// (STRING) Game Password
// (STRING) Game Statstring
//
type GetAdvListReq struct {
Filter w3gs.GameFlags
FilterMask w3gs.GameFlags
NumberOfGames uint32
GameName string
}
// Serialize encodes the struct into its binary form.
func (pkt *GetAdvListReq) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidGetAdvListEx)
buf.WriteUInt16(uint16(23 + len(pkt.GameName)))
buf.WriteUInt32(uint32(pkt.Filter))
buf.WriteUInt32(uint32(pkt.FilterMask))
buf.WriteUInt32(0)
buf.WriteUInt32(pkt.NumberOfGames)
buf.WriteCString(pkt.GameName)
buf.WriteUInt8(0)
buf.WriteUInt8(0)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *GetAdvListReq) Deserialize(buf *protocol.Buffer) error {
var size = readPacketSize(buf)
if size < 23 {
return ErrInvalidPacketSize
}
pkt.Filter = w3gs.GameFlags(buf.ReadUInt32())
pkt.FilterMask = w3gs.GameFlags(buf.ReadUInt32())
if buf.ReadUInt32() != 0 {
return ErrUnexpectedConst
}
pkt.NumberOfGames = buf.ReadUInt32()
var err error
if pkt.GameName, err = buf.ReadCString(); err != nil {
return err
}
if size != 23+len(pkt.GameName) {
return ErrInvalidPacketSize
}
if buf.ReadUInt8() != 0 || buf.ReadUInt8() != 0 {
return ErrUnexpectedConst
}
return nil
}
// StartAdvex3Resp implements the [0x1C] SID_StartAdvex3 packet (S -> C).
//
// Possible values for Status:
// 0x00: Ok
// 0x01: Failed
//
// Format:
//
// (UINT32) Status
//
type StartAdvex3Resp struct {
Failed bool
}
// Serialize encodes the struct into its binary form.
func (pkt *StartAdvex3Resp) Serialize(buf *protocol.Buffer) error {
buf.WriteUInt8(ProtocolSig)
buf.WriteUInt8(PidStartAdvex3)
buf.WriteUInt16(8)
buf.WriteBool32(pkt.Failed)
return nil
}
// Deserialize decodes the binary data generated by Serialize.
func (pkt *StartAdvex3Resp) Deserialize(buf *protocol.Buffer) error {
if readPacketSize(buf) != 8 {
return ErrInvalidPacketSize
}
pkt.Failed = buf.ReadBool32()
return nil
}
// StartAdvex3Req implements the [0x1C] SID_StartAdvex3 packet (C -> S).
//
// Used by clients to inform the server that a game has been created, or that the state of a created game has changed.
//
// Bitwise flags for `State`:
//
// 0x01: Game is private
// 0x02: Game is full
// 0x04: Game contains players (other than creator)
// 0x08: Game is in progress
// 0x80: Game is a replay
//
//
// Possible values for `Game Type` (and `Sub Game Type`):
//
// 0x02: Melee
// 0x03: Free for All
// 0x04: 1 vs 1
// 0x05: Capture The Flag
// 0x06: Greed (Resources, 0x01: 2500, 0x02: 500, 0x03: 7500, 0x04: 10000)
// 0x07: Slaughter (Minutes, 0x01: 15, 0x02: 30, 0x03: 45, 0x04: 60)
// 0x08: Sudden Death
// 0x09: Ladder (Disconnects, 0x00: Not a loss, 0x01: Counts as a loss)
// 0x0A: Use Map Settings
// 0x0B: Team Melee (Number Of Teams, 0x01: 2 Teams, 0x02: 3 Teams, etc.)
// 0x0C: Team Free For All (Number Of Teams, 0x01: 2 Teams, 0x02: 3 Teams, etc.)
// 0x0D: Team Capture The Flag (Number Of Teams, 0x01: 2 Teams, 0x02: 3 Teams, etc.)