PS2SDK
PS2 Homebrew Libraries
Loading...
Searching...
No Matches
rmman.c
1#include <intrman.h>
2#include <loadcore.h>
3#include <sifcmd.h>
4#include <sifman.h>
5#include <stdio.h>
6#include <thbase.h>
7#include <thevent.h>
8#include <vblank.h>
9#ifdef BUILDING_RMMAN2
10#ifdef BUILDING_RMMANX
11#include <iomanX.h>
12#else
13#include <cdvdman.h>
14#endif
15#else
16#include <rsio2man.h>
17#endif
18#include <irx.h>
19
20#include "rmman.h"
21
22#ifdef BUILDING_RMMAN2
23#ifdef BUILDING_RMMANX
24IRX_ID("rmmanx", 2, 4);
25// Based off the module from XOSD 2.14
26#else
27IRX_ID("rmman2", 2, 4);
28// Based off the module from DVD Player 3.10
29#endif
30#else
31IRX_ID("rmman", 1, 16);
32#endif
33
34#ifdef DEBUG
35#define DPRINTF(x...) printf("RMMAN: "x)
36#else
37#define DPRINTF(x...)
38#endif
39
40#define RM_EF_EXIT_THREAD 1
41#define RM_EF_EXIT_THREAD_DONE 2
42#define RM_EF_CLOSE_PORT 4
43#define RM_EF_CLOSE_PORT_DONE 8
44
45#ifdef BUILDING_RMMAN2
46#define RM_MAX_PORT 1
47#define RM_MAX_SLOT 1
48#define RM_EE_DATA_TYPE struct rmEEData2
49#define RM_RPCFUNC_END RMMAN2_RPCFUNC_END
50#define RM_RPCFUNC_INIT RMMAN2_RPCFUNC_INIT
51#define RM_RPCFUNC_CLOSE RMMAN2_RPCFUNC_CLOSE
52#define RM_RPCFUNC_OPEN RMMAN2_RPCFUNC_OPEN
53#define RM_RPCFUNC_VERSION RMMAN2_RPCFUNC_VERSION
54#define RM_RPCFUNC_REMOTE2_6 RMMAN2_RPCFUNC_REMOTE2_6
55#ifdef BUILDING_RMMANX
56#define RM_RPC_ID RMMANX_RPC_ID
57#else
58#define RM_RPC_ID RMMAN2_RPC_ID
59#endif
60#define RM_PACKET_CMD(packet) (packet->cmd.u.cmd2)
61#else
62#define RM_MAX_PORT 2
63#define RM_MAX_SLOT 4
64#define RM_EE_DATA_TYPE struct rmEEData
65#define RM_RPCFUNC_END RMMAN_RPCFUNC_END
66#define RM_RPCFUNC_INIT RMMAN_RPCFUNC_INIT
67#define RM_RPCFUNC_CLOSE RMMAN_RPCFUNC_CLOSE
68#define RM_RPCFUNC_OPEN RMMAN_RPCFUNC_OPEN
69#define RM_RPCFUNC_VERSION RMMAN_RPCFUNC_VERSION
70#define RM_RPC_ID RMMAN_RPC_ID
71#define RM_PACKET_CMD(packet) (packet->cmd.u.cmd1)
72#endif
73
74enum RM_TASK {
75 RM_TASK_QUERY = 0,
76 RM_TASK_INIT,
77 RM_TASK_POLL
78};
79
80struct RmData {
81 RM_EE_DATA_TYPE eeData;
82#ifndef BUILDING_RMMAN2
83 u32 unused1;
84 u8 inBuffer[32];
85#endif
86 u8 outBuffer[32];
87 u32 state;
88 u32 reqState;
89 u32 frame;
90 RM_EE_DATA_TYPE *eeBuffer;
91#ifndef BUILDING_RMMAN2
92 s32 port;
93 s32 slot;
94 u32 currentTask;
95 u32 counter;
96 u32 powerMode;
97 u32 closed;
98#endif
99 u32 connected;
100#ifndef BUILDING_RMMAN2
101 u32 eventFlagID;
102 u32 unused2;
103 sio2_transfer_data_t sio2Data;
104#endif
105};
106
107#ifdef BUILDING_RMMAN2
108#ifdef BUILDING_RMMANX
109extern struct irx_export_table _exp_rmmanx;
110#else
111extern struct irx_export_table _exp_rmman2;
112#endif
113#else
114extern struct irx_export_table _exp_rmman;
115#endif
116
117static struct RmData RmData[RM_MAX_PORT][RM_MAX_SLOT];
118static int portStatus[RM_MAX_PORT];
119static int eventFlagID;
120static int MainThreadID;
121static int IsInitialized;
122static int RpcThreadID;
123static SifRpcDataQueue_t RpcDataQueue;
124static SifRpcServerData_t RpcServerData;
125static struct rmRpcPacket RpcDataBuffer __attribute__((__aligned__(4)));
126
127static int CreateMainThread(void);
128static void MainThread(void *arg);
129static int HandleTasks(struct RmData *RmData);
130#ifndef BUILDING_RMMAN2
131static int FindRemote(struct RmData *RmData);
132static int InitFindRmCmd(struct RmData *RmData);
133#endif
134static int PollRemote(struct RmData *RmData);
135#ifndef BUILDING_RMMAN2
136static int InitPollRmCmd(struct RmData *RmData);
137static int RmExecute(struct RmData *RmData);
138#endif
139static int DmaSendEE(struct RmData *RmData);
140#ifndef BUILDING_RMMAN2
141static int HandleRmTaskFailed(struct RmData *RmData);
142static int InitRemote(struct RmData *RmData);
143static int InitInitRmCmd(struct RmData *RmData);
144#endif
145
146int _start(int argc, char *argv[])
147{
148 int result;
149 struct irx_export_table *export_table;
150
151 (void)argc;
152 (void)argv;
153
154#ifdef BUILDING_RMMAN2
155#ifdef BUILDING_RMMANX
156 export_table = &_exp_rmmanx;
157#else
158 export_table = &_exp_rmman2;
159#endif
160#else
161 export_table = &_exp_rmman;
162#endif
163
164 if(RegisterLibraryEntries(export_table) == 0)
165 {
166 result = CreateMainThread() <= 0 ? MODULE_NO_RESIDENT_END : MODULE_RESIDENT_END;
167 }
168 else result = MODULE_NO_RESIDENT_END;
169
170 return result;
171}
172
173int rmmanInit(void)
174{
175 iop_event_t EventFlagData;
176 iop_thread_t ThreadData;
177 int result;
178 int i;
179
180 for (i = 0; i < RM_MAX_PORT; i += 1)
181 {
182 portStatus[i] = 0;
183 }
184 IsInitialized = 1;
185 EventFlagData.attr = 2;
186 EventFlagData.bits = 0;
187 if((eventFlagID = CreateEventFlag(&EventFlagData)) != 0)
188 {
189 ThreadData.attr = TH_C;
190 ThreadData.thread = &MainThread;
191 ThreadData.priority = 0x2E;
192 ThreadData.stacksize = 0x800;
193 if((MainThreadID = CreateThread(&ThreadData)) != 0)
194 {
195 StartThread(MainThreadID, NULL);
196 result=MainThreadID;
197 }
198 else result=0;
199 }
200 else result=0;
201
202 return result;
203}
204
205int rmmanOpen(int port, int slot, void *buffer)
206{
207 int result;
208
209 if ((port >= RM_MAX_PORT) || (slot >= RM_MAX_SLOT))
210 {
211 return 0;
212 }
213
214 if(!((portStatus[port] >> slot) & 1))
215 {
216 struct RmData *pRmData;
217#ifndef BUILDING_RMMAN2
218 iop_event_t evf;
219#endif
220
221 pRmData = &RmData[port][slot];
222#ifndef BUILDING_RMMAN2
223 pRmData->port = port;
224 pRmData->slot = slot;
225#endif
226 pRmData->state = RM_STATE_EXECCMD;
227 pRmData->reqState = RM_RSTATE_COMPLETE;
228 pRmData->eeBuffer = buffer;
229#ifndef BUILDING_RMMAN2
230 pRmData->currentTask = RM_TASK_QUERY;
231 pRmData->counter = 0;
232#endif
233
234#ifndef BUILDING_RMMAN2
235 evf.attr = EA_MULTI;
236 evf.bits = 0;
237
238 if((pRmData->eventFlagID = CreateEventFlag(&evf)) != 0)
239 {
240 pRmData->powerMode = 0;
241 portStatus[port] |= (1 << slot);
242 result = 1;
243 } else
244 result = 0;
245#else
246 portStatus[port] |= (1 << slot);
247 result = 1;
248#endif
249 } else
250 result = 0;
251
252 return result;
253}
254
255int rmmanClose(int port, int slot)
256{
257 int result;
258
259 if ((port >= RM_MAX_PORT) || (slot >= RM_MAX_SLOT))
260 {
261 return 0;
262 }
263
264 if((portStatus[port] >> slot) & 1)
265 {
266#ifndef BUILDING_RMMAN2
267 struct RmData *pRmData;
268 u32 bits;
269
270 pRmData = &RmData[port][slot];
271
272 if(pRmData->state != RM_STATE_FINDRM)
273 {
274 SetEventFlag(pRmData->eventFlagID, RM_EF_EXIT_THREAD);
275 WaitEventFlag(pRmData->eventFlagID, RM_EF_EXIT_THREAD_DONE, WEF_AND|WEF_CLEAR, &bits);
276
277 if(pRmData->closed != 0)
278 { //Remote Control port was closed successfully.
279 portStatus[port] ^= (1 << slot);
280 result = 1;
281 } else {
282 result = 0;
283 }
284 } else {
285 portStatus[port] ^= (1 << slot);
286 result = 1;
287 }
288#else
289 portStatus[port] ^= (1 << slot);
290 result = 1;
291#endif
292 } else
293 result = 0;
294
295 return result;
296}
297
298int rmmanEnd(void)
299{
300 u32 bits;
301 int result;
302
303 if(IsInitialized != 0)
304 {
305 int port;
306 for(port = 0; port < RM_MAX_PORT; port++)
307 {
308 int slot;
309 for(slot = 0; slot < RM_MAX_SLOT; slot++)
310 { //If port,slot is opened, close it.
311 if((portStatus[port] >> slot) & 1)
312 rmmanClose(port, slot);
313 }
314 }
315
316 SetEventFlag(eventFlagID, RM_EF_EXIT_THREAD);
317 WaitEventFlag(eventFlagID, RM_EF_EXIT_THREAD_DONE, WEF_AND|WEF_CLEAR, &bits);
318 DeleteEventFlag(eventFlagID);
319 eventFlagID = 0;
320 if(DeleteThread(eventFlagID) == 0)
321 {
322 MainThreadID = 0;
323 IsInitialized = 0;
324
325 result = 1;
326 } else
327 result = 0;
328 } else
329 result = 1;
330
331 return result;
332}
333
334static void MainThread(void *arg)
335{
336 iop_event_info_t evfInfo;
337 int port, slot;
338
339 (void)arg;
340
341 while(1)
342 {
343 WaitVblankStart();
344
345 ReferEventFlagStatus(eventFlagID, &evfInfo);
346
347 if(evfInfo.currBits == RM_EF_EXIT_THREAD)
348 {
349 SetEventFlag(eventFlagID, RM_EF_EXIT_THREAD_DONE);
350 ExitThread();
351 }
352
353 for(port = 0; port < RM_MAX_PORT; port++)
354 {
355 for(slot = 0; slot < RM_MAX_SLOT; slot++)
356 {
357 if((portStatus[port] >> slot) & 1)
358 {
359#ifndef BUILDING_RMMAN2
360 ReferEventFlagStatus(RmData[port][slot].eventFlagID, &evfInfo);
361
362 if(evfInfo.currBits == RM_EF_CLOSE_PORT)
363 {
364 RmData[port][slot].powerMode = 3;
365 if(InitRemote(&RmData[port][slot]) == 0)
366 RmData[port][slot].closed = 0;
367 else
368 RmData[port][slot].closed = 1;
369
370 SetEventFlag(RmData[port][slot].eventFlagID, RM_EF_CLOSE_PORT_DONE);
371 } else {
372 HandleTasks(&RmData[port][slot]);
373 }
374#else
375 HandleTasks(&RmData[port][slot]);
376#endif
377 }
378 }
379 }
380 }
381}
382
383static int HandleTasks(struct RmData *RmData)
384{
385#ifndef BUILDING_RMMAN2
386 switch(RmData->currentTask)
387 {
388 case RM_TASK_QUERY:
389 if(RmData->counter == 0 && FindRemote(RmData) != 0)
390 {
391 RmData->counter = 0;
392 RmData->state = RM_STATE_EXECCMD;
393 RmData->currentTask++;
394 } else {
395 if(RmData->counter < 10)
396 {
397 RmData->counter++;
398 RmData->state = RM_STATE_FINDRM;
399 } else {
400 RmData->counter = 0;
401 }
402 }
403 break;
404 case RM_TASK_INIT:
405 if(InitRemote(RmData) != 0)
406 {
407 RmData->counter = 0;
408 RmData->currentTask++;
409 } else {
410 HandleRmTaskFailed(RmData);
411 }
412 break;
413 case RM_TASK_POLL:
414 RmData->state = RM_STATE_STABLE;
415 if(PollRemote(RmData) != 0)
416 {
417 RmData->counter = 0;
418
419 if(RmData->reqState == RM_RSTATE_BUSY)
420 RmData->reqState = RM_RSTATE_COMPLETE;
421 } else {
422 if(HandleRmTaskFailed(RmData) == 0 && RmData->reqState == RM_RSTATE_BUSY)
423 RmData->reqState = RM_RSTATE_FAILED;
424 }
425 break;
426 }
427#else
428 RmData->connected = PollRemote(RmData);
429#endif
430
431 DmaSendEE(RmData);
432
433 return 1;
434}
435
436#ifndef BUILDING_RMMAN2
437static int FindRemote(struct RmData *RmData)
438{
439 int result;
440
441 InitFindRmCmd(RmData);
442 if(RmExecute(RmData) != 0)
443 {
444 if(RmData->port < RM_MAX_PORT)
445 {
446 switch(RmData->outBuffer[1])
447 {
448 case 0x12:
449 result = 1;
450 break;
451 case 0x1F:
452 InitRemote(RmData);
453 result = 1;
454 break;
455 default:
456 RmData->connected = 0;
457 result = 0;
458 }
459 } else
460 result = 1;
461 } else
462 result = 0;
463
464 return result;
465}
466
467static int InitFindRmCmd(struct RmData *RmData)
468{
469 int i;
470
471 //The original was setting these, byte by byte.
472 RmData->sio2Data.port_ctrl1[RmData->port] = 0xFFC0050F;
473 RmData->sio2Data.port_ctrl2[RmData->port] = 0x00060014;
474
475 RmData->sio2Data.port_ctrl2[RmData->port] = (RmData->sio2Data.port_ctrl2[RmData->port] & ~0x03000000) | 0x01000000;
476
477 RmData->sio2Data.in = RmData->inBuffer;
478 RmData->sio2Data.out = RmData->outBuffer;
479 RmData->inBuffer[0] = 0x61;
480 RmData->sio2Data.in_size = 7;
481 RmData->sio2Data.out_size = 7;
482 RmData->sio2Data.regdata[1] = 0;
483 RmData->inBuffer[1] = 0x0F;
484
485 RmData->sio2Data.regdata[0] = (RmData->sio2Data.regdata[0] & ~3) | (RmData->port & 3);
486 RmData->sio2Data.regdata[0] = (RmData->sio2Data.regdata[0] & ~0x000000C0) | 0x40;
487 RmData->sio2Data.regdata[0] = (RmData->sio2Data.regdata[0] & ~0x0001FF00) | 0x00700;
488 RmData->sio2Data.regdata[0] = (RmData->sio2Data.regdata[0] & ~0x07FC0000) | 0x001C0000;
489
490 for(i = 2; i < 7; i++)
491 RmData->inBuffer[i] = 0;
492
493 return 1;
494}
495#endif
496
497static int PollRemote(struct RmData *RmData)
498{
499#ifndef BUILDING_RMMAN2
500 InitPollRmCmd(RmData);
501 return(RmExecute(RmData) > 0);
502#else
503 int i;
504 char rmbuf[16];
505
506#ifdef BUILDING_RMMANX
507 if (iomanX_devctl("dvr_misc:", 0x5668, 0, 0, rmbuf, 0xA) < 0)
508#else
509 if (sceCdApplySCmd(0x1E, 0, 0, rmbuf) == 0 || (rmbuf[0] & 0x80) != 0)
510#endif
511 {
512 RmData->state = RM_STATE_DISCONN;
513 return 0;
514 }
515#ifdef BUILDING_RMMANX
516 RmData->outBuffer[0] = rmbuf[0];
517 for (i = 1; i < 5; i += 1)
518 {
519 RmData->outBuffer[i] = rmbuf[(i - 1) * 2];
520 }
521 RmData->outBuffer[4] = rmbuf[8];
522#else
523 for (i = 0; i < 4; i += 1)
524 {
525 RmData->outBuffer[i] = rmbuf[i + 1];
526 }
527#endif
528 RmData->state = RM_STATE_STABLE;
529 return 1;
530#endif
531}
532
533#ifndef BUILDING_RMMAN2
534static int InitPollRmCmd(struct RmData *RmData)
535{
536 int i;
537
538 RmData->inBuffer[0] = 0x61;
539 RmData->inBuffer[1] = 0x04;
540 for(i = 2; i < 7; i++)
541 RmData->inBuffer[i] = 0;
542
543 return 1;
544}
545
546static int RmExecute(struct RmData *RmData)
547{
548 int result;
549
550 sio2_rm_transfer_init();
551 sio2_transfer(&RmData->sio2Data);
552 sio2_transfer_reset();
553
554 if(((RmData->sio2Data.stat6c >> 14) & 3) == 0)
555 {
556 RmData->connected = 1;
557 result = 1;
558 } else {
559 RmData->connected = 0;
560 result = 0;
561 }
562
563 return result;
564}
565
566static int HandleRmTaskFailed(struct RmData *RmData)
567{
568 if(RmData->counter + 1 < 10)
569 {
570 RmData->counter++;
571 return RmData->counter;
572 } else {
573 RmData->currentTask = RM_TASK_QUERY;
574 return 0;
575 }
576}
577#endif
578
579static int DmaSendEE(struct RmData *RmData)
580{
581 SifDmaTransfer_t dmat;
582 int i, OldState, dmatID;
583
584 RmData->eeData.frame = RmData->frame;
585 RmData->frame++;
586 for(i = 0; (unsigned int)i < sizeof(RmData->eeData.data); i++)
587 RmData->eeData.data[i] = RmData->outBuffer[i];
588
589 RmData->eeData.state = RmData->state;
590 RmData->eeData.connected = RmData->connected;
591
592 dmat.src = &RmData->eeData;
593 dmat.dest = ((RmData->frame & 1) == 0) ? (u8*)RmData->eeBuffer : (u8*)RmData->eeBuffer + 128;
594 dmat.size = 128;
595 dmat.attr = 0;
596
597 CpuSuspendIntr(&OldState);
598 dmatID = sceSifSetDma(&dmat, 1);
599 CpuResumeIntr(OldState);
600
601 return(dmatID > 0);
602}
603
604#ifndef BUILDING_RMMAN2
605static int InitRemote(struct RmData *RmData)
606{
607 InitInitRmCmd(RmData);
608 return(RmExecute(RmData) > 0);
609}
610
611static int InitInitRmCmd(struct RmData *RmData)
612{
613 int i;
614
615 RmData->inBuffer[0] = 0x61;
616 RmData->inBuffer[1] = 0x06;
617 RmData->inBuffer[2] = (u8)RmData->powerMode;
618
619 for(i = 3; i < 7; i++)
620 RmData->inBuffer[i] = 0;
621
622 return 1;
623}
624#endif
625
626static int rmmanVersion(void)
627{
628 return _irx_id.v;
629}
630
631#ifdef BUILDING_RMMAN2
632static int rmmanRemote2_6(u8 *res)
633{
634#ifdef BUILDING_RMMANX
635 *res = 1;
636 return 1;
637#else
638 char scmdbuf[16];
639
640 if (sceCdApplySCmd(0x20, 0, 0, scmdbuf) != 0)
641 {
642 if ((scmdbuf[0] & 0x80) == 0)
643 {
644 *res = scmdbuf[1];
645 return 1;
646 }
647 }
648 return 0;
649#endif
650}
651#endif
652
653static void *RmmanRpc_init(struct rmRpcPacket *packet)
654{
655 RM_PACKET_CMD(packet).result = rmmanInit();
656 return packet;
657}
658
659static void *RmmanRpc_version(struct rmRpcPacket *packet)
660{
661 RM_PACKET_CMD(packet).result = rmmanVersion();
662 return packet;
663}
664
665static void *RmmanRpc_open(struct rmRpcPacket *packet)
666{
667#ifndef BUILDING_RMMAN2
668 RM_PACKET_CMD(packet).result = rmmanOpen(RM_PACKET_CMD(packet).port, RM_PACKET_CMD(packet).slot, RM_PACKET_CMD(packet).data);
669#else
670 RM_PACKET_CMD(packet).result = rmmanOpen(0, 0, RM_PACKET_CMD(packet).data);
671#endif
672 return packet;
673}
674
675static void *RmmanRpc_end(struct rmRpcPacket *packet)
676{
677 RM_PACKET_CMD(packet).result = rmmanEnd();
678 return packet;
679}
680
681static void *RmmanRpc_close(struct rmRpcPacket *packet)
682{
683#ifndef BUILDING_RMMAN2
684 RM_PACKET_CMD(packet).result = rmmanClose(RM_PACKET_CMD(packet).port, RM_PACKET_CMD(packet).slot);
685#else
686 RM_PACKET_CMD(packet).result = rmmanClose(0, 0);
687#endif
688 return packet;
689}
690
691#ifdef BUILDING_RMMAN2
692static void *RmmanRpc_remote2_6(struct rmRpcPacket *packet)
693{
694 RM_PACKET_CMD(packet).result = rmmanRemote2_6((u8 *)&(RM_PACKET_CMD(packet).data));
695 return packet;
696}
697#endif
698
699static void *RpcHandler(int fno, void *buffer, int len)
700{
701 void *retBuff;
702
703 (void)fno;
704 (void)len;
705
706 switch(((struct rmRpcPacket *)buffer)->cmd.command)
707 {
708 case RM_RPCFUNC_END:
709 retBuff = RmmanRpc_end((struct rmRpcPacket *)buffer);
710 break;
711 case RM_RPCFUNC_INIT:
712 retBuff = RmmanRpc_init((struct rmRpcPacket *)buffer);
713 break;
714 case RM_RPCFUNC_CLOSE:
715 retBuff = RmmanRpc_close((struct rmRpcPacket *)buffer);
716 break;
717 case RM_RPCFUNC_OPEN:
718 retBuff = RmmanRpc_open((struct rmRpcPacket *)buffer);
719 break;
720 case RM_RPCFUNC_VERSION:
721 retBuff = RmmanRpc_version((struct rmRpcPacket *)buffer);
722 break;
723#ifdef BUILDING_RMMAN2
724 case RM_RPCFUNC_REMOTE2_6:
725 retBuff = RmmanRpc_remote2_6((struct rmRpcPacket *)buffer);
726 break;
727#endif
728 default:
729 DPRINTF("invalid function code (%03x)\n", (unsigned int)((struct rmRpcPacket *)buffer)->cmd.command);
730 retBuff = buffer;
731 }
732
733 return retBuff;
734}
735
736static void RpcThread(void *arg)
737{
738 (void)arg;
739
740 if(!sceSifCheckInit())
741 {
742 DPRINTF("yet sif hasn't been init\n");
743 sceSifInit();
744 }
745
746 sceSifInitRpc(0);
747
748 sceSifSetRpcQueue(&RpcDataQueue, GetThreadId());
749 sceSifRegisterRpc(&RpcServerData, RM_RPC_ID, &RpcHandler, &RpcDataBuffer, NULL, NULL, &RpcDataQueue);
750 sceSifRpcLoop(&RpcDataQueue);
751}
752
753static int CreateMainThread(void)
754{
755 iop_thread_t ThreadData;
756 int result;
757
758 ThreadData.attr=TH_C;
759 ThreadData.thread=&RpcThread;
760 ThreadData.priority=0x2E;
761 ThreadData.stacksize=0x800;
762 if((RpcThreadID=CreateThread(&ThreadData))!=0)
763 {
764 StartThread(RpcThreadID, NULL);
765 result = 1;
766 }
767 else result = 0;
768
769 return result;
770}
int CpuResumeIntr(int state)
Definition intrman.c:227
int CpuSuspendIntr(int *state)
Definition intrman.c:205
int sceCdApplySCmd(u8 cmdNum, const void *inBuff, u16 inBuffSize, void *outBuff)
Definition cdvdman.c:4363
Definition rmman.c:80
#define EA_MULTI
Definition thevent.h:35