Disabling auto-refresh of game list by default, as it is causing bugs sometimes
[open-ps2-loader.git] / modules / mcemu / mcemu.c
blob8a4540ad03d21d735aa7685e79bd8104a4b65552
1 /*
2 Copyright 2006-2008, Romz
3 Copyright 2010, Polo
4 Licenced under Academic Free License version 3.0
5 Review OpenUsbLd README & LICENSE files for further details.
6 */
8 #include "mcemu.h"
11 static int readyToGo = -1;
12 void StartNow(void* param);
15 //---------------------------------------------------------------------------
16 // Entry point
17 //---------------------------------------------------------------------------
18 int _start (int argc, char *argv[])
20 int thid;
21 iop_thread_t param;
23 param.attr = TH_C;
24 param.thread = StartNow;
25 param.priority = 0x4f;
26 param.stacksize = 0xB00;
27 param.option = 0;
28 thid = CreateThread(&param);
29 StartThread(thid, 0);
30 while (!((readyToGo == 1) || (readyToGo == 0)))
32 DelayThread(100*1000);
34 DeleteThread(thid);
35 return readyToGo;
38 //------------------------------
39 //endfunc _start
40 //---------------------------------------------------------------------------
41 void StartNow(void* param)
43 register int r;
44 register void *exp;
46 DPRINTF("mcemu starting\n");
48 /* looking for LOADCORE's library entry table */
49 exp = GetExportTable("loadcore", 0x100);
50 if (exp == NULL)
52 DPRINTF("Unable to find loadcore exports.\n");
53 readyToGo = MODULE_NO_RESIDENT_END;
54 return;
57 /* configuring the virtual memory cards */
58 if(!(r = mc_configure(memcards)))
60 DPRINTF("mc_configure return %d.\n", r);
61 readyToGo = MODULE_NO_RESIDENT_END;
62 return;
65 /* hooking LOADCORE's RegisterLibraryEntires routine */
66 pRegisterLibraryEntires = (PtrRegisterLibraryEntires)HookExportEntry(exp, 6, hookRegisterLibraryEntires);
68 /* searching for a SIO2MAN export table */
69 exp = GetExportTable("sio2man", 0x201);
70 if (exp != NULL)
72 /* hooking SIO2MAN's routines */
73 InstallSio2manHook(exp);
75 else
77 DPRINTF("SIO2MAN exports not found.\n");
80 /* searching for a SECRMAN export table */
81 exp = GetExportTable("secrman", 0x101);
82 if (exp != NULL)
84 /* hooking SecrAuthCard entry */
85 InstallSecrmanHook(exp);
87 else
89 DPRINTF("SECRMAN exports not found.\n");
92 readyToGo = MODULE_RESIDENT_END;
94 //------------------------------
95 //endfunc _start
96 //---------------------------------------------------------------------------
97 /* Installs SecrAuthCard handler for the enabled virtual memory cards */
98 void InstallSecrmanHook(void *exp)
100 register int i;
101 register void *old;
102 register MemoryCard *mcds;
104 /* hooking SecrAuthCard entry */
105 old = HookExportEntry(exp, 6, hookSecrAuthCard);
106 if (old == NULL) old = DummySecrAuthCard;
108 for (i = 0, mcds = memcards; i < MCEMU_PORTS; i ++, mcds ++)
110 pSecrAuthCard[i] = (mcds->mcnum != -1) ? DummySecrAuthCard : (PtrSecrAuthCard)old;
113 //------------------------------
114 //endfunc
115 //---------------------------------------------------------------------------
116 /* Installs handlers for SIO2MAN's routine for enabled virtual memory cards */
117 void InstallSio2manHook(void *exp)
119 /* hooking SIO2MAN entry #25 (used by MCMAN and old PADMAN) */
120 pSio2man25 = HookExportEntry(exp, 25, hookSio2man25);
121 /* hooking SIO2MAN entry #51 (used by MC2_* modules and PADMAN) */
122 pSio2man51 = HookExportEntry(exp, 51, hookSio2man51);
123 pSio2man67 = HookExportEntry(exp, 67, hookSio2man67);
125 //------------------------------
126 //endfunc
127 //---------------------------------------------------------------------------
128 /* Install hooks for MCMAN's sceMcReadFast & sceMcWriteFast */
129 void InstallMcmanHook(void *exp)
131 register void *mcman63, *mcman68;
133 /* getting MCMAN's sceMcRead & sceMcWrite routines */
134 pMcRead = GetExportEntry(exp, 8);
135 pMcWrite = GetExportEntry(exp, 9);
136 /* hooking MCMAN's library entry #62 */
137 HookExportEntry(exp, 62, hookMcman62);
138 /* and choosing internal routines for sceMcReadFast & sceMcWriteFast */
139 mcman63 = hookMcman63;
140 mcman68 = hookMcman68;
142 /* hooking sceMcReadFast entry */
143 HookExportEntry(exp, 63, mcman63);
144 /* hooking sceMcWriteFast entry */
145 HookExportEntry(exp, 68, mcman68);
147 //------------------------------
148 //endfunc
149 //---------------------------------------------------------------------------
150 int hookDmac_request(u32 channel, void *addr, u32 size, u32 count, int dir);
151 void hookDmac_transfer(u32 channel);
153 static int CheckPatchMc2_s1() {
154 modinfo_t info;
155 smod_mod_info_t *mod_table = (smod_mod_info_t *)0x800;
156 const char modname[8] = "mc2_s1";
157 u32 start, end;
159 getModInfo("modload\0", &info);
160 if (info.version < 0x102) goto not_found; // I'm sure mc2_s1 won't work with this old iop kernel
162 if (getModInfo("mcman", &info)) goto not_found; // mcman and mc2_s1 are mutually exclusive
164 while (mod_table) {
165 int i;
166 for (i=0; i<8; i++) {
167 if (mod_table->name[i] != modname[i])
168 break;
170 if (i==8)
171 break;
172 mod_table = mod_table->next;
174 if(!mod_table) goto not_found_again; // mc2_s1 not found
175 start = mod_table->text_start;
176 end = start + mod_table->text_size + mod_table->data_size + mod_table->bss_size;
178 getModInfo("dmacman\0", &info);
180 // walk the import tables
181 iop_library_t *lib = (iop_library_t *)((u32)info.exports - 0x14);
182 struct irx_import_table *table = lib->caller;
183 struct irx_import_stub *stub;
184 while (table) {
185 stub = (struct irx_import_stub *) table->stubs;
186 if (((u32)stub > start) && ((u32)stub < end)) {
187 DPRINTF("bad mc2_s1 found\n");
188 while (stub->jump) {
189 switch (stub->fno) {
190 case 0x1c: // dmac_request
191 stub->jump = 0x08000000 | (((u32)hookDmac_request << 4) >> 6);
192 break;
193 case 0x20: // dmac_transfer
194 stub->jump = 0x08000000 | (((u32)hookDmac_transfer << 4) >> 6);
195 break;
196 default:
197 break;
199 stub++;
201 FlushDcache();
202 FlushIcache();
203 return 1;
205 table = table->next;
207 DPRINTF("mc2_s1 found but no dmac imports\n");
208 return 1;
209 not_found:
210 DPRINTF("mc2_s1 not found\n");
211 return 1;
212 not_found_again:
213 DPRINTF("mc2_s1 and mcman not found, try again\n");
214 return 0;
217 /* Returns "success" result to any SecrAuthCard calls to avoid real Magic Gate Authentication process */
218 int DummySecrAuthCard(int port, int slot, int cnum)
220 // port; slot; cnum;
221 DPRINTF("SecrAuthCard(0x%X, 0x%X, 0x%X)\n", port, slot, cnum);
223 // if(slot != 0)
224 // return 0;
226 return 1;
228 //------------------------------
229 //endfunc
230 //---------------------------------------------------------------------------
231 /* Hook for the LOADCORE's RegisterLibraryEntires call */
232 int hookRegisterLibraryEntires(iop_library_t *lib)
234 if (!strncmp(lib->name, "sio2man", 8))
236 /* hooking SIO2MAN's routines */
237 InstallSio2manHook(&lib[1]);
239 else if (!strncmp(lib->name, "secrman", 8))
241 /* hooking the SecrAuthCard() calls */
242 InstallSecrmanHook(&lib[1]);
244 else if (!strncmp(lib->name, "mcman", 8))
246 /* hooking MCMAN's sceMcReadFast() & sceMcWriteFast() calls */
247 if (lib->version >= 0x208) InstallMcmanHook(&lib[1]);
250 DPRINTF("registering library %s\n", lib->name);
252 return pRegisterLibraryEntires(lib);
254 //------------------------------
255 //endfunc
256 //---------------------------------------------------------------------------
257 /* Hook for SIO2MAN entry #25 (called by MCMAN) */
258 void hookSio2man25(Sio2Packet *sd)
260 hookSio2man(sd, pSio2man25);
262 //------------------------------
263 //endfunc
264 //---------------------------------------------------------------------------
265 /* Hook for SIO2MAN entry #51 (called by MC2* and PADMAN) */
266 void hookSio2man51(Sio2Packet *sd)
268 hookSio2man(sd, pSio2man51);
270 //------------------------------
271 //endfunc
272 //---------------------------------------------------------------------------
273 /* SIO2MAN generic hook routine */
274 void hookSio2man(Sio2Packet *sd, Sio2McProc sio2proc)
276 register u32 ctrl;
278 /* getting first SIO2 control code */
279 ctrl = sd->ctrl[0];
281 /* checking if virtual memory card is active */
282 if (memcards[ctrl & 0x1].mcnum != -1)
284 /* checking SIO2 transfer mode */
285 switch (ctrl & 0xF0)
287 /* getting command code from first DMA buffer */
288 case 0x70:
289 ctrl = *(u8*)sd->wrmaddr;
290 break;
291 /* getting command code from PIO buffer */
292 case 0x40:
293 ctrl = *(u8*)sd->pwr_buf;
294 break;
295 /* unknown transfer mode, setting wrong command code */
296 default:
297 ctrl = 0;
298 break;
301 /* checking SIO2 command code */
302 if (ctrl == 0x81) sio2proc = Sio2McEmu;
305 /* calling original SIO2MAN routine */
306 sio2proc(sd);
308 //------------------------------
309 //endfunc
310 //---------------------------------------------------------------------------
311 /* Hook for the SecrAuthCard call */
312 int hookSecrAuthCard(int port, int slot, int cnum)
314 static int check_done = 0;
315 // check for mc2_s1 once, secrauthcard should be called only after mc(2)man is loaded (I hope)
316 // check_done may never bet set for those games which use only mc2_d like GH3, but other games
317 // like GOW2 use both.
318 if (!check_done)
319 check_done = CheckPatchMc2_s1();
320 return pSecrAuthCard[port & 0x1](port, slot, cnum);
322 //------------------------------
323 //endfunc
324 //---------------------------------------------------------------------------
326 #define SIO2CTRL (*(volatile u32 *)(0xbf808268))
327 #define SIO2STAT (*(volatile u32 *)(0xbf80826c))
328 #define SIO2CMD ((volatile u32 *)(0xbf808200))
329 static Sio2Packet *temp_packet = NULL;
330 static int skip_sema_wait = 0;
332 int hookDmac_request(u32 channel, void *addr, u32 size, u32 count, int dir) {
333 int i;
334 int port = SIO2CMD[0] & 1;
336 DPRINTF("hookDmac_request port = %d, channel = %ld\n", port, channel);
337 if (memcards[port].mcnum == -1)
338 return dmac_request(channel, addr, size, count, dir);
340 switch(channel) {
341 case 11: // sio2in
342 // may have to copy the dma buffer, but isn't a problem right now
343 temp_packet = _SysAlloc(sizeof(Sio2Packet));
344 temp_packet->wrmaddr = addr;
345 temp_packet->wrwords = size;
346 temp_packet->wrcount = count;
347 return 1;
348 case 12: // sio2out
349 if(temp_packet == NULL) {
350 DPRINTF("sio2out request without sio2in\n");
351 return 0;
353 temp_packet->rdmaddr = addr;
354 temp_packet->rdwords = size;
355 temp_packet->rdcount = count;
356 for (i = 0; i < 16; i++)
357 temp_packet->ctrl[i] = SIO2CMD[i];
358 DPRINTF("hookDmac_request ctrl0 = %lX, cmd0 = %X, wrcount = %ld\n", temp_packet->ctrl[0], ((u8 *)temp_packet->wrmaddr)[1], temp_packet->wrcount);
359 Sio2McEmu(temp_packet);
360 SIO2CTRL |= 0x40; // reset it, pcsx2 suggests it's reset after every write
361 _SysFree(temp_packet);
362 temp_packet = NULL;
363 skip_sema_wait = 1;
364 return 1;
365 default:
366 DPRINTF("dmac_request invalid channel\n");
367 return 0;
371 void hookDmac_transfer(u32 channel) {
372 int port = SIO2CMD[0] & 1;
373 if (memcards[port].mcnum == -1)
374 return dmac_transfer(channel);
376 switch(channel) {
377 case 12:
378 break;
379 case 11:
380 if(temp_packet != NULL) break;
381 default:
382 DPRINTF("dmac_transfer invalid transfer\n");
383 break;
385 return;
388 u32 *hookSio2man67() {
389 static u32 fake_stat;
390 if(skip_sema_wait) {
391 if((SIO2STAT & 0xf000) != 0x1000) { // uh oh
392 u32 *ra;
393 __asm__("move %0, $ra\n":"=r"(ra));
394 if(ra[0]) {
395 if((ra[0] == 0x3c02bf80) && (ra[1] == 0x3442826c)) { // this really sucks
396 ra[0] = 0;
397 ra[1] = 0;
399 } else if((ra[0] == 0x8fa90050) && (ra[1] == 0) && (ra[2] == 0x8d220000)) {
400 ra[0] = 0; // there must a better way
401 ra[2] = 0x8c420000; // lw v0,0(v0)
403 else DPRINTF("hookSio2man67 failed to find stat6c check, mc access may fail\n");
404 FlushDcache();
405 FlushIcache();
408 skip_sema_wait = 0;
409 fake_stat = 0x1000;
410 return (&fake_stat);
412 pSio2man67();
413 fake_stat = SIO2STAT;
414 return (&fake_stat);
417 /* SIO2 Command handler */
418 void Sio2McEmu(Sio2Packet *sd)
420 if ((sd->ctrl[0] & 0xF0) == 0x70)
422 register u32 ddi, *pctl, result, length;
423 register u8 *wdma, *rdma;
424 register MemoryCard *mcd;
426 wdma = (u8*)sd->wrmaddr; /* address of write buffers */
427 rdma = (u8*)sd->rdmaddr; /* address of read buffers */
428 pctl = &sd->ctrl[0]; /* address of control codes */
430 for (result = 1, ddi = 0; (ddi < sd->wrcount) && (ddi < 0x10) && (*pctl); ddi ++, pctl ++)
432 if (wdma[0] == 0x81)
434 mcd = &memcards[*pctl & 0x1];
435 length = (*pctl >> 18) & 0x1FF;
437 if (mcd->mcnum != -1)
439 /* handling MC2 command code */
440 switch (wdma[1])
442 /* 0x81 0x12 - ? */
443 case 0x12: /* <- I/O status related */
444 SioResponse(mcd, rdma, length);
445 result = 1;
446 break;
447 /* 0x81 0x21 - Start erasing block of pages */
448 case 0x21: /* erase block */
449 SioResponse(mcd, rdma, length);
450 result = MceEraseBlock(mcd, GetInt(&wdma[2]));
451 break;
452 /* 0x81 0x22 - Start writing to a memory card */
453 case 0x22: /* write start */
454 result = MceStartWrite(mcd, GetInt(&wdma[2]));
455 SioResponse(mcd, rdma, length);
456 break;
457 /* 0x81 0x23 - Start reading from a memory card */
458 case 0x23: /* read start */
459 result = MceStartRead(mcd, GetInt(&wdma[2]));
460 SioResponse(mcd, rdma, length);
461 break;
462 /* 0x81 0x26 - Read memory card spec */
463 case 0x26:
464 rdma[0] = rdma[1] = 0xFF;
465 /* setting memory card flags */
466 rdma[2] = mcd->flags & 0xFF;
467 /* copying memory card spec */
468 mips_memcpy(&rdma[3], &mcd->cspec, sizeof(McSpec));
469 /* calculating EDC for memory card spec data */
470 rdma[11] = CalculateEDC(&rdma[3], sizeof(McSpec));
471 rdma[12] = mcd->tcode;
472 result = 1;
473 break;
474 /* 0x81 0x27 - Set new termination code */
475 case 0x27:
476 DPRINTF("0x81 0x27 - 0x%02X\n", wdma[2]);
477 mcd->tcode = wdma[2];
478 SioResponse(mcd, rdma, length);
479 result = 1;
480 break;
481 /* 0x81 0x28 - Probe card ? */
482 case 0x28:
483 SioResponse(mcd, rdma, 4);
484 rdma[4] = mcd->tcode;
485 result = 1;
486 break;
487 /* 0x81 0x42 - Write data to a memory card */
488 case 0x42:
489 SioResponse(mcd, rdma, length);
490 if (result) result = MceWrite(mcd, &wdma[3], wdma[2]);
491 else DPRINTF("skipping write command after I/O error.\n");
492 break;
493 /* 0x81 0x43 - Read data from a memory card */
494 case 0x43:
495 length = wdma[2]; /* length = (*pctl >> 18) & 0x1FF; */
496 SioResponse(mcd, rdma, 5);
497 if (result) result = MceRead(mcd, &rdma[4], length);
498 else DPRINTF("skipping read command after I/O error.\n");
499 if (!result) mips_memset(&rdma[4], 0xFF, length);
500 rdma[length + 4] = CalculateEDC(&rdma[4], length);
501 rdma[length + 5] = mcd->tcode; /* <- 'f' should be set on error, probably */
502 break;
503 /* Dummy handling for standard commands */
504 /* 0x81 0x11 - ? */
505 case 0x11: /* <- primary card detection */
506 /* 0x81 0x81 - ? */
507 case 0x81: /* <- read status ? */
508 /* 0x81 0x82 - ? */
509 case 0x82: /* <- erase/write status ? */
510 /* 0x81 0xBF - ?*/
511 case 0xBF:
512 /* 0x81 0xF3 - Reset Card Auth */
513 case 0xF3:
514 DPRINTF("mc2 command 0x81 0x%02X\n", wdma[1]);
515 SioResponse(mcd, rdma, length);
516 result = 1;
517 break;
518 /* Magic Gate command */
519 case 0xF0:
520 DPRINTF("Magic Gate command 0x81 0xF0 0x%02X\n", wdma[2]);
521 SioResponse(mcd, rdma, length);
522 result = 0;
523 break;
524 /* unknown commands */
525 default:
526 DPRINTF("unknown MC2 command 0x81 0x%02X\n", wdma[1]);
527 SioResponse(mcd, rdma, length);
528 result = 0;
529 break;
532 else
534 mips_memset(rdma, 0xFF, sd->rdwords * 4);
535 result = 0;
538 else
540 result = 0;
541 DPRINTF("unsupported SIO2 command: 0x%02X\n", wdma[0]);
544 wdma = &wdma[sd->wrwords * 4]; /* set address of next dma write-buffer */
545 rdma = &rdma[sd->rdwords * 4]; /* set address of next dma read-buffer */
548 sd->iostatus = (result > 0) ? 0x1000 : 0x1D000; /* <- set I/O flags */
549 sd->iostatus |= (ddi & 0xF) << 8;
551 else
553 sd->iostatus = 0x1D100;
554 /* DPRINTF("ctrl[0] = 0x%X\n", sd->ctrl[0]); */
557 /* DPRINTF("SIO2 status 0x%X\n", sd->iostatus); */
559 //------------------------------
560 //endfunc
561 //---------------------------------------------------------------------------
562 /* Generates a "0xFF 0xFF ... 0xFF 0x2B 0x55" sequence */
563 void SioResponse(MemoryCard *mcd, void *buf, int length)
565 register int i;
566 register u8 *p;
568 if (length < 2)
570 DPRINTF("invalid SIO2 data length.\n");
571 return;
574 i = length - 2;
575 p = (u8*)buf;
577 while (i --) *p ++ = 0xFF;
579 p[0] = 0x2B;
580 p[1] = mcd->tcode;
582 //------------------------------
583 //endfunc
584 //---------------------------------------------------------------------------
585 /* int cslogfd = -1;
586 int prnflag = 0; */
588 /* Erases memory card block */
589 int MceEraseBlock(MemoryCard *mcd, int page)
591 register int i, r;
593 DPRINTF("erasing at 0x%X\n", page);
595 /* creating clear buffer */
596 r = (mcd->flags & 0x10) ? 0x0 : 0xFF;
597 mips_memset(mcd->dbufp, r, mcd->cspec.PageSize);
599 for (i = 0; i < mcd->cspec.BlockSize; i ++)
601 r = mc_write(mcd->mcnum, mcd->dbufp, page + i);
602 if (!r)
604 DPRINTF("erase error\n");
605 return 0;
609 return 1;
611 //------------------------------
612 //endfunc
613 //---------------------------------------------------------------------------
614 static int do_read(MemoryCard *mcd) {
615 int r, i;
616 r = (mcd->flags & 0x10) ? 0xFF : 0x0;
617 mips_memset(mcd->cbufp, r, 0x10);
619 r = mc_read(mcd->mcnum, mcd->dbufp, mcd->rpage);
620 if (!r)
622 DPRINTF("read error\n");
623 return 0;
625 for(r = 0, i = 0; r < mcd->cspec.PageSize; r += 128, i += 3)
626 CalculateECC(&(mcd->dbufp[r]), &(mcd->cbufp[i]));
627 return 1;
630 int MceStartRead(MemoryCard *mcd, int page)
632 mcd->rpage = page;
633 mcd->rdoff = 0;
634 mcd->rcoff = 0;
636 return 1;
638 //------------------------------
639 //endfunc
640 //---------------------------------------------------------------------------
641 /* Reads memory card page */
642 int MceRead(MemoryCard *mcd, void *buf, u32 size)
644 u32 tot_size = size;
645 restart:
646 DPRINTF("read sector %X size %ld\n", mcd->rpage, size);
647 if (mcd->rcoff && !mcd->rdoff) {
648 u32 csize = (size<16)?size:16;
649 mips_memcpy(buf, mcd->cbufp, csize);
650 mcd->rcoff = (csize>12)?0:(mcd->rcoff-csize);
651 buf += csize;
652 size -= csize;
653 if(size <= 0) return 1;
655 if (mcd->rdoff < mcd->cspec.PageSize)
657 if (mcd->rdoff == 0)
658 if(!do_read(mcd)) return 0;
660 if ((size+mcd->rdoff) > mcd->cspec.PageSize)
661 size = mcd->cspec.PageSize - mcd->rdoff;
663 mips_memcpy(buf, &mcd->dbufp[mcd->rdoff], size);
664 mcd->rdoff += size;
665 mcd->rcoff += 3;
667 if (mcd->rdoff == mcd->cspec.PageSize)
669 buf += size;
670 size = tot_size - size;
671 mcd->rpage ++;
672 mcd->rdoff = 0;
673 if(size > 0) goto restart;
676 return 1;
678 //------------------------------
679 //endfunc
680 //---------------------------------------------------------------------------
681 int MceStartWrite(MemoryCard *mcd, int page)
683 mcd->wpage = page;
684 mcd->wroff = 0;
685 mcd->wcoff = 0;
687 return 1;
689 //------------------------------
690 //endfunc
691 //---------------------------------------------------------------------------
692 /* Writes memory card page */
693 int MceWrite(MemoryCard *mcd, void *buf, u32 size)
695 u32 tot_size = size;
696 restart:
697 DPRINTF("write sector %X size %ld\n", mcd->wpage, size);
698 if (mcd->wcoff && !mcd->wroff) {
699 u32 csize = (size<16)?size:16;
700 mcd->wcoff = (csize>12)?0:(mcd->wcoff-csize);
701 buf += csize;
702 size -= csize;
703 if(size <= 0) return 1;
705 if (mcd->wroff < mcd->cspec.PageSize)
707 if ((size+mcd->wroff) > mcd->cspec.PageSize)
708 size = mcd->cspec.PageSize - mcd->wroff;
710 mips_memcpy(&mcd->dbufp[mcd->wroff], buf, size);
711 mcd->wroff += size;
712 mcd->wcoff += 3;
714 if (mcd->wroff == mcd->cspec.PageSize)
716 register int r;
718 buf += size;
719 size = tot_size - size;
720 mcd->wroff = 0;
722 r = mc_write(mcd->mcnum, mcd->dbufp, mcd->wpage);
723 if (!r)
725 DPRINTF("write error.\n");
726 return 0;
729 mcd->wpage ++;
730 if(size > 0) goto restart;
733 return 1;
735 //------------------------------
736 //endfunc
737 //---------------------------------------------------------------------------
738 // End of file: mcemu.c
739 //---------------------------------------------------------------------------