Commit | Line | Data |
---|---|---|
ef79bbde P |
1 | /*************************************************************************** |
2 | * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team * | |
3 | * * | |
4 | * This program is free software; you can redistribute it and/or modify * | |
5 | * it under the terms of the GNU General Public License as published by * | |
6 | * the Free Software Foundation; either version 2 of the License, or * | |
7 | * (at your option) any later version. * | |
8 | * * | |
9 | * This program is distributed in the hope that it will be useful, * | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
12 | * GNU General Public License for more details. * | |
13 | * * | |
14 | * You should have received a copy of the GNU General Public License * | |
15 | * along with this program; if not, write to the * | |
16 | * Free Software Foundation, Inc., * | |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA. * | |
18 | ***************************************************************************/ | |
19 | ||
20 | /* | |
21 | * Handles all CD-ROM registers and functions. | |
22 | */ | |
23 | ||
24 | #include "cdrom.h" | |
25 | #include "ppf.h" | |
26 | ||
27 | cdrStruct cdr; | |
28 | ||
29 | /* CD-ROM magic numbers */ | |
30 | #define CdlSync 0 | |
31 | #define CdlNop 1 | |
32 | #define CdlSetloc 2 | |
33 | #define CdlPlay 3 | |
34 | #define CdlForward 4 | |
35 | #define CdlBackward 5 | |
36 | #define CdlReadN 6 | |
37 | #define CdlStandby 7 | |
38 | #define CdlStop 8 | |
39 | #define CdlPause 9 | |
40 | #define CdlInit 10 | |
41 | #define CdlMute 11 | |
42 | #define CdlDemute 12 | |
43 | #define CdlSetfilter 13 | |
44 | #define CdlSetmode 14 | |
45 | #define CdlGetmode 15 | |
46 | #define CdlGetlocL 16 | |
47 | #define CdlGetlocP 17 | |
48 | #define CdlReadT 18 | |
49 | #define CdlGetTN 19 | |
50 | #define CdlGetTD 20 | |
51 | #define CdlSeekL 21 | |
52 | #define CdlSeekP 22 | |
53 | #define CdlSetclock 23 | |
54 | #define CdlGetclock 24 | |
55 | #define CdlTest 25 | |
56 | #define CdlID 26 | |
57 | #define CdlReadS 27 | |
58 | #define CdlReset 28 | |
59 | #define CdlReadToc 30 | |
60 | ||
61 | #define AUTOPAUSE 249 | |
62 | #define READ_ACK 250 | |
63 | #define READ 251 | |
64 | #define REPPLAY_ACK 252 | |
65 | #define REPPLAY 253 | |
66 | #define ASYNC 254 | |
67 | /* don't set 255, it's reserved */ | |
68 | ||
69 | char *CmdName[0x100]= { | |
70 | "CdlSync", "CdlNop", "CdlSetloc", "CdlPlay", | |
71 | "CdlForward", "CdlBackward", "CdlReadN", "CdlStandby", | |
72 | "CdlStop", "CdlPause", "CdlInit", "CdlMute", | |
73 | "CdlDemute", "CdlSetfilter", "CdlSetmode", "CdlGetmode", | |
74 | "CdlGetlocL", "CdlGetlocP", "CdlReadT", "CdlGetTN", | |
75 | "CdlGetTD", "CdlSeekL", "CdlSeekP", "CdlSetclock", | |
76 | "CdlGetclock", "CdlTest", "CdlID", "CdlReadS", | |
77 | "CdlReset", NULL, "CDlReadToc", NULL | |
78 | }; | |
79 | ||
80 | unsigned char Test04[] = { 0 }; | |
81 | unsigned char Test05[] = { 0 }; | |
82 | unsigned char Test20[] = { 0x98, 0x06, 0x10, 0xC3 }; | |
83 | unsigned char Test22[] = { 0x66, 0x6F, 0x72, 0x20, 0x45, 0x75, 0x72, 0x6F }; | |
84 | unsigned char Test23[] = { 0x43, 0x58, 0x44, 0x32, 0x39 ,0x34, 0x30, 0x51 }; | |
85 | ||
86 | // 1x = 75 sectors per second | |
87 | // PSXCLK = 1 sec in the ps | |
88 | // so (PSXCLK / 75) = cdr read time (linuzappz) | |
89 | #define cdReadTime (PSXCLK / 75) | |
90 | ||
91 | static struct CdrStat stat; | |
92 | static struct SubQ *subq; | |
93 | ||
94 | #define CDR_INT(eCycle) { \ | |
95 | psxRegs.interrupt |= 0x4; \ | |
96 | psxRegs.intCycle[2 + 1] = eCycle; \ | |
ae602c19 | 97 | psxRegs.intCycle[2] = psxRegs.cycle; \ |
654e8cfb | 98 | new_dyna_set_event(0, eCycle); \ |
ae602c19 | 99 | } |
ef79bbde P |
100 | |
101 | #define CDREAD_INT(eCycle) { \ | |
102 | psxRegs.interrupt |= 0x40000; \ | |
103 | psxRegs.intCycle[2 + 16 + 1] = eCycle; \ | |
ae602c19 | 104 | psxRegs.intCycle[2 + 16] = psxRegs.cycle; \ |
654e8cfb | 105 | new_dyna_set_event(2, eCycle); \ |
ae602c19 | 106 | } |
ef79bbde P |
107 | |
108 | #define StartReading(type, eCycle) { \ | |
109 | cdr.Reading = type; \ | |
110 | cdr.FirstSector = 1; \ | |
111 | cdr.Readed = 0xff; \ | |
112 | AddIrqQueue(READ_ACK, eCycle); \ | |
113 | } | |
114 | ||
115 | #define StopReading() { \ | |
116 | if (cdr.Reading) { \ | |
117 | cdr.Reading = 0; \ | |
118 | psxRegs.interrupt &= ~0x40000; \ | |
119 | } \ | |
120 | cdr.StatP &= ~0x20;\ | |
121 | } | |
122 | ||
123 | #define StopCdda() { \ | |
124 | if (cdr.Play) { \ | |
125 | if (!Config.Cdda) CDR_stop(); \ | |
126 | cdr.StatP &= ~0x80; \ | |
127 | cdr.Play = FALSE; \ | |
128 | } \ | |
129 | } | |
130 | ||
131 | #define SetResultSize(size) { \ | |
132 | cdr.ResultP = 0; \ | |
133 | cdr.ResultC = size; \ | |
134 | cdr.ResultReady = 1; \ | |
135 | } | |
136 | ||
137 | static void ReadTrack() { | |
138 | cdr.Prev[0] = itob(cdr.SetSector[0]); | |
139 | cdr.Prev[1] = itob(cdr.SetSector[1]); | |
140 | cdr.Prev[2] = itob(cdr.SetSector[2]); | |
141 | ||
142 | #ifdef CDR_LOG | |
143 | CDR_LOG("ReadTrack() Log: KEY *** %x:%x:%x\n", cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]); | |
144 | #endif | |
145 | cdr.RErr = CDR_readTrack(cdr.Prev); | |
146 | } | |
147 | ||
148 | // cdr.Stat: | |
149 | #define NoIntr 0 | |
150 | #define DataReady 1 | |
151 | #define Complete 2 | |
152 | #define Acknowledge 3 | |
153 | #define DataEnd 4 | |
154 | #define DiskError 5 | |
155 | ||
156 | void AddIrqQueue(unsigned char irq, unsigned long ecycle) { | |
157 | cdr.Irq = irq; | |
158 | if (cdr.Stat) { | |
159 | cdr.eCycle = ecycle; | |
160 | } else { | |
161 | CDR_INT(ecycle); | |
162 | } | |
163 | } | |
164 | ||
165 | void cdrInterrupt() { | |
166 | int i; | |
167 | unsigned char Irq = cdr.Irq; | |
168 | ||
169 | if (cdr.Stat) { | |
170 | CDR_INT(0x1000); | |
171 | return; | |
172 | } | |
173 | ||
174 | cdr.Irq = 0xff; | |
175 | cdr.Ctrl &= ~0x80; | |
176 | ||
177 | switch (Irq) { | |
178 | case CdlSync: | |
179 | SetResultSize(1); | |
180 | cdr.StatP |= 0x2; | |
181 | cdr.Result[0] = cdr.StatP; | |
182 | cdr.Stat = Acknowledge; | |
183 | break; | |
184 | ||
185 | case CdlNop: | |
186 | SetResultSize(1); | |
187 | cdr.Result[0] = cdr.StatP; | |
188 | cdr.Stat = Acknowledge; | |
189 | i = stat.Status; | |
190 | if (CDR_getStatus(&stat) != -1) { | |
191 | if (stat.Type == 0xff) cdr.Stat = DiskError; | |
192 | if (stat.Status & 0x10) { | |
193 | cdr.Stat = DiskError; | |
194 | cdr.Result[0] |= 0x11; | |
195 | cdr.Result[0] &= ~0x02; | |
196 | } | |
197 | else if (i & 0x10) { | |
198 | cdr.StatP |= 0x2; | |
199 | cdr.Result[0] |= 0x2; | |
200 | CheckCdrom(); | |
201 | } | |
202 | } | |
203 | break; | |
204 | ||
205 | case CdlSetloc: | |
206 | cdr.CmdProcess = 0; | |
207 | SetResultSize(1); | |
208 | cdr.StatP |= 0x2; | |
209 | cdr.Result[0] = cdr.StatP; | |
210 | cdr.Stat = Acknowledge; | |
211 | break; | |
212 | ||
213 | case CdlPlay: | |
214 | cdr.CmdProcess = 0; | |
215 | SetResultSize(1); | |
216 | cdr.StatP |= 0x2; | |
217 | cdr.Result[0] = cdr.StatP; | |
218 | cdr.Stat = Acknowledge; | |
219 | cdr.StatP |= 0x80; | |
220 | // if ((cdr.Mode & 0x5) == 0x5) AddIrqQueue(REPPLAY, cdReadTime); | |
221 | break; | |
222 | ||
223 | case CdlForward: | |
224 | cdr.CmdProcess = 0; | |
225 | SetResultSize(1); | |
226 | cdr.StatP |= 0x2; | |
227 | cdr.Result[0] = cdr.StatP; | |
228 | cdr.Stat = Complete; | |
229 | break; | |
230 | ||
231 | case CdlBackward: | |
232 | cdr.CmdProcess = 0; | |
233 | SetResultSize(1); | |
234 | cdr.StatP |= 0x2; | |
235 | cdr.Result[0] = cdr.StatP; | |
236 | cdr.Stat = Complete; | |
237 | break; | |
238 | ||
239 | case CdlStandby: | |
240 | cdr.CmdProcess = 0; | |
241 | SetResultSize(1); | |
242 | cdr.StatP |= 0x2; | |
243 | cdr.Result[0] = cdr.StatP; | |
244 | cdr.Stat = Complete; | |
245 | break; | |
246 | ||
247 | case CdlStop: | |
248 | cdr.CmdProcess = 0; | |
249 | SetResultSize(1); | |
250 | cdr.StatP &= ~0x2; | |
251 | cdr.Result[0] = cdr.StatP; | |
252 | cdr.Stat = Complete; | |
253 | // cdr.Stat = Acknowledge; | |
254 | ||
255 | // check case open/close -shalma | |
256 | i = stat.Status; | |
257 | if (CDR_getStatus(&stat) != -1) { | |
258 | if (stat.Type == 0xff) cdr.Stat = DiskError; | |
259 | if (stat.Status & 0x10) { | |
260 | cdr.Stat = DiskError; | |
261 | cdr.Result[0] |= 0x11; | |
262 | cdr.Result[0] &= ~0x02; | |
263 | } | |
264 | else if (i & 0x10) { | |
265 | cdr.StatP |= 0x2; | |
266 | cdr.Result[0] |= 0x2; | |
267 | CheckCdrom(); | |
268 | } | |
269 | } | |
270 | break; | |
271 | ||
272 | case CdlPause: | |
273 | SetResultSize(1); | |
274 | cdr.Result[0] = cdr.StatP; | |
275 | cdr.Stat = Acknowledge; | |
276 | AddIrqQueue(CdlPause + 0x20, 0x1000); | |
277 | cdr.Ctrl |= 0x80; | |
278 | break; | |
279 | ||
280 | case CdlPause + 0x20: | |
281 | SetResultSize(1); | |
282 | cdr.StatP &= ~0x20; | |
283 | cdr.StatP |= 0x2; | |
284 | cdr.Result[0] = cdr.StatP; | |
285 | cdr.Stat = Complete; | |
286 | break; | |
287 | ||
288 | case CdlInit: | |
289 | SetResultSize(1); | |
290 | cdr.StatP = 0x2; | |
291 | cdr.Result[0] = cdr.StatP; | |
292 | cdr.Stat = Acknowledge; | |
293 | // if (!cdr.Init) { | |
294 | AddIrqQueue(CdlInit + 0x20, 0x1000); | |
295 | // } | |
296 | break; | |
297 | ||
298 | case CdlInit + 0x20: | |
299 | SetResultSize(1); | |
300 | cdr.Result[0] = cdr.StatP; | |
301 | cdr.Stat = Complete; | |
302 | cdr.Init = 1; | |
303 | break; | |
304 | ||
305 | case CdlMute: | |
306 | SetResultSize(1); | |
307 | cdr.StatP |= 0x2; | |
308 | cdr.Result[0] = cdr.StatP; | |
309 | cdr.Stat = Acknowledge; | |
310 | break; | |
311 | ||
312 | case CdlDemute: | |
313 | SetResultSize(1); | |
314 | cdr.StatP |= 0x2; | |
315 | cdr.Result[0] = cdr.StatP; | |
316 | cdr.Stat = Acknowledge; | |
317 | break; | |
318 | ||
319 | case CdlSetfilter: | |
320 | SetResultSize(1); | |
321 | cdr.StatP |= 0x2; | |
322 | cdr.Result[0] = cdr.StatP; | |
323 | cdr.Stat = Acknowledge; | |
324 | break; | |
325 | ||
326 | case CdlSetmode: | |
327 | SetResultSize(1); | |
328 | cdr.StatP |= 0x2; | |
329 | cdr.Result[0] = cdr.StatP; | |
330 | cdr.Stat = Acknowledge; | |
331 | break; | |
332 | ||
333 | case CdlGetmode: | |
334 | SetResultSize(6); | |
335 | cdr.StatP |= 0x2; | |
336 | cdr.Result[0] = cdr.StatP; | |
337 | cdr.Result[1] = cdr.Mode; | |
338 | cdr.Result[2] = cdr.File; | |
339 | cdr.Result[3] = cdr.Channel; | |
340 | cdr.Result[4] = 0; | |
341 | cdr.Result[5] = 0; | |
342 | cdr.Stat = Acknowledge; | |
343 | break; | |
344 | ||
345 | case CdlGetlocL: | |
346 | SetResultSize(8); | |
347 | // for (i = 0; i < 8; i++) | |
348 | // cdr.Result[i] = itob(cdr.Transfer[i]); | |
349 | for (i = 0; i < 8; i++) | |
350 | cdr.Result[i] = cdr.Transfer[i]; | |
351 | cdr.Stat = Acknowledge; | |
352 | break; | |
353 | ||
354 | case CdlGetlocP: | |
355 | SetResultSize(8); | |
356 | subq = (struct SubQ *)CDR_getBufferSub(); | |
357 | ||
358 | if (subq != NULL) { | |
359 | cdr.Result[0] = subq->TrackNumber; | |
360 | cdr.Result[1] = subq->IndexNumber; | |
361 | memcpy(cdr.Result + 2, subq->TrackRelativeAddress, 3); | |
362 | memcpy(cdr.Result + 5, subq->AbsoluteAddress, 3); | |
363 | ||
364 | // subQ integrity check | |
365 | if (calcCrc((u8 *)subq + 12, 10) != (((u16)subq->CRC[0] << 8) | subq->CRC[1])) { | |
366 | memset(cdr.Result + 2, 0, 3 + 3); // CRC wrong, wipe out time data | |
367 | } | |
368 | } else { | |
369 | cdr.Result[0] = 1; | |
370 | cdr.Result[1] = 1; | |
371 | ||
372 | cdr.Result[2] = btoi(cdr.Prev[0]); | |
373 | cdr.Result[3] = btoi(cdr.Prev[1]) - 2; | |
374 | cdr.Result[4] = cdr.Prev[2]; | |
375 | ||
376 | // m:s adjustment | |
377 | if ((s8)cdr.Result[3] < 0) { | |
378 | cdr.Result[3] += 60; | |
379 | cdr.Result[2] -= 1; | |
380 | } | |
381 | ||
382 | cdr.Result[2] = itob(cdr.Result[2]); | |
383 | cdr.Result[3] = itob(cdr.Result[3]); | |
384 | ||
385 | memcpy(cdr.Result + 5, cdr.Prev, 3); | |
386 | } | |
387 | ||
388 | cdr.Stat = Acknowledge; | |
389 | break; | |
390 | ||
391 | case CdlGetTN: | |
392 | cdr.CmdProcess = 0; | |
393 | SetResultSize(3); | |
394 | cdr.StatP |= 0x2; | |
395 | cdr.Result[0] = cdr.StatP; | |
396 | if (CDR_getTN(cdr.ResultTN) == -1) { | |
397 | cdr.Stat = DiskError; | |
398 | cdr.Result[0] |= 0x01; | |
399 | } else { | |
400 | cdr.Stat = Acknowledge; | |
401 | cdr.Result[1] = itob(cdr.ResultTN[0]); | |
402 | cdr.Result[2] = itob(cdr.ResultTN[1]); | |
403 | } | |
404 | break; | |
405 | ||
406 | case CdlGetTD: | |
407 | cdr.CmdProcess = 0; | |
408 | cdr.Track = btoi(cdr.Param[0]); | |
409 | SetResultSize(4); | |
410 | cdr.StatP |= 0x2; | |
411 | if (CDR_getTD(cdr.Track, cdr.ResultTD) == -1) { | |
412 | cdr.Stat = DiskError; | |
413 | cdr.Result[0] |= 0x01; | |
414 | } else { | |
415 | cdr.Stat = Acknowledge; | |
416 | cdr.Result[0] = cdr.StatP; | |
417 | cdr.Result[1] = itob(cdr.ResultTD[2]); | |
418 | cdr.Result[2] = itob(cdr.ResultTD[1]); | |
419 | cdr.Result[3] = itob(cdr.ResultTD[0]); | |
420 | } | |
421 | break; | |
422 | ||
423 | case CdlSeekL: | |
424 | SetResultSize(1); | |
425 | cdr.StatP |= 0x2; | |
426 | cdr.Result[0] = cdr.StatP; | |
427 | cdr.StatP |= 0x40; | |
428 | cdr.Stat = Acknowledge; | |
429 | cdr.Seeked = TRUE; | |
430 | AddIrqQueue(CdlSeekL + 0x20, 0x1000); | |
431 | break; | |
432 | ||
433 | case CdlSeekL + 0x20: | |
434 | SetResultSize(1); | |
435 | cdr.StatP |= 0x2; | |
436 | cdr.StatP &= ~0x40; | |
437 | cdr.Result[0] = cdr.StatP; | |
438 | cdr.Stat = Complete; | |
439 | break; | |
440 | ||
441 | case CdlSeekP: | |
442 | SetResultSize(1); | |
443 | cdr.StatP |= 0x2; | |
444 | cdr.Result[0] = cdr.StatP; | |
445 | cdr.StatP |= 0x40; | |
446 | cdr.Stat = Acknowledge; | |
447 | AddIrqQueue(CdlSeekP + 0x20, 0x1000); | |
448 | break; | |
449 | ||
450 | case CdlSeekP + 0x20: | |
451 | SetResultSize(1); | |
452 | cdr.StatP |= 0x2; | |
453 | cdr.StatP &= ~0x40; | |
454 | cdr.Result[0] = cdr.StatP; | |
455 | cdr.Stat = Complete; | |
456 | break; | |
457 | ||
458 | case CdlTest: | |
459 | cdr.Stat = Acknowledge; | |
460 | switch (cdr.Param[0]) { | |
461 | case 0x20: // System Controller ROM Version | |
462 | SetResultSize(4); | |
463 | memcpy(cdr.Result, Test20, 4); | |
464 | break; | |
465 | case 0x22: | |
466 | SetResultSize(8); | |
467 | memcpy(cdr.Result, Test22, 4); | |
468 | break; | |
469 | case 0x23: case 0x24: | |
470 | SetResultSize(8); | |
471 | memcpy(cdr.Result, Test23, 4); | |
472 | break; | |
473 | } | |
474 | break; | |
475 | ||
476 | case CdlID: | |
477 | SetResultSize(1); | |
478 | cdr.StatP |= 0x2; | |
479 | cdr.Result[0] = cdr.StatP; | |
480 | cdr.Stat = Acknowledge; | |
481 | AddIrqQueue(CdlID + 0x20, 0x1000); | |
482 | break; | |
483 | ||
484 | case CdlID + 0x20: | |
485 | SetResultSize(8); | |
486 | if (CDR_getStatus(&stat) == -1) { | |
487 | cdr.Result[0] = 0x00; // 0x08 and cdr.Result[1]|0x10 : audio cd, enters cd player | |
488 | cdr.Result[1] = 0x00; // 0x80 leads to the menu in the bios, else loads CD | |
489 | } | |
490 | else { | |
491 | if (stat.Type == 2) { | |
492 | cdr.Result[0] = 0x08; | |
493 | cdr.Result[1] = 0x10; | |
494 | } | |
495 | else { | |
496 | cdr.Result[0] = 0x00; | |
497 | cdr.Result[1] = 0x00; | |
498 | } | |
499 | } | |
500 | cdr.Result[1] |= 0x80; | |
501 | cdr.Result[2] = 0x00; | |
502 | cdr.Result[3] = 0x00; | |
503 | strncpy((char *)&cdr.Result[4], "PCSX", 4); | |
504 | cdr.Stat = Complete; | |
505 | break; | |
506 | ||
507 | case CdlReset: | |
508 | SetResultSize(1); | |
509 | cdr.StatP = 0x2; | |
510 | cdr.Result[0] = cdr.StatP; | |
511 | cdr.Stat = Acknowledge; | |
512 | break; | |
513 | ||
514 | case CdlReadToc: | |
515 | SetResultSize(1); | |
516 | cdr.StatP |= 0x2; | |
517 | cdr.Result[0] = cdr.StatP; | |
518 | cdr.Stat = Acknowledge; | |
519 | AddIrqQueue(CdlReadToc + 0x20, 0x1000); | |
520 | break; | |
521 | ||
522 | case CdlReadToc + 0x20: | |
523 | SetResultSize(1); | |
524 | cdr.StatP |= 0x2; | |
525 | cdr.Result[0] = cdr.StatP; | |
526 | cdr.Stat = Complete; | |
527 | break; | |
528 | ||
529 | case AUTOPAUSE: | |
530 | cdr.OCUP = 0; | |
531 | /* SetResultSize(1); | |
532 | StopCdda(); | |
533 | StopReading(); | |
534 | cdr.OCUP = 0; | |
535 | cdr.StatP&=~0x20; | |
536 | cdr.StatP|= 0x2; | |
537 | cdr.Result[0] = cdr.StatP; | |
538 | cdr.Stat = DataEnd; | |
539 | */ AddIrqQueue(CdlPause, 0x800); | |
540 | break; | |
541 | ||
542 | case READ_ACK: | |
543 | if (!cdr.Reading) return; | |
544 | ||
545 | SetResultSize(1); | |
546 | cdr.StatP |= 0x2; | |
547 | cdr.Result[0] = cdr.StatP; | |
548 | if (!cdr.Seeked) { | |
549 | cdr.Seeked = TRUE; | |
550 | cdr.StatP |= 0x40; | |
551 | } | |
552 | cdr.StatP |= 0x20; | |
553 | cdr.Stat = Acknowledge; | |
554 | ||
555 | // CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime); | |
556 | CDREAD_INT(0x80000); | |
557 | break; | |
558 | ||
559 | case REPPLAY_ACK: | |
560 | cdr.Stat = Acknowledge; | |
561 | cdr.Result[0] = cdr.StatP; | |
562 | SetResultSize(1); | |
563 | AddIrqQueue(REPPLAY, cdReadTime); | |
564 | break; | |
565 | ||
566 | case REPPLAY: | |
567 | if ((cdr.Mode & 5) != 5) break; | |
568 | /* if (CDR_getStatus(&stat) == -1) { | |
569 | cdr.Result[0] = 0; | |
570 | cdr.Result[1] = 0; | |
571 | cdr.Result[2] = 0; | |
572 | cdr.Result[3] = 0; | |
573 | cdr.Result[4] = 0; | |
574 | cdr.Result[5] = 0; | |
575 | cdr.Result[6] = 0; | |
576 | cdr.Result[7] = 0; | |
577 | } else memcpy(cdr.Result, &stat.Track, 8); | |
578 | cdr.Stat = 1; | |
579 | SetResultSize(8); | |
580 | AddIrqQueue(REPPLAY_ACK, cdReadTime); | |
581 | */ break; | |
582 | ||
583 | case 0xff: | |
584 | return; | |
585 | ||
586 | default: | |
587 | cdr.Stat = Complete; | |
588 | break; | |
589 | } | |
590 | ||
591 | if (cdr.Stat != NoIntr && cdr.Reg2 != 0x18) { | |
592 | psxHu32ref(0x1070) |= SWAP32((u32)0x4); | |
593 | } | |
594 | ||
595 | #ifdef CDR_LOG | |
596 | CDR_LOG("cdrInterrupt() Log: CDR Interrupt IRQ %x\n", Irq); | |
597 | #endif | |
598 | } | |
599 | ||
600 | void cdrReadInterrupt() { | |
601 | u8 *buf; | |
602 | ||
603 | if (!cdr.Reading) | |
604 | return; | |
605 | ||
606 | if (cdr.Stat) { | |
607 | CDREAD_INT(0x1000); | |
608 | return; | |
609 | } | |
610 | ||
611 | #ifdef CDR_LOG | |
612 | CDR_LOG("cdrReadInterrupt() Log: KEY END"); | |
613 | #endif | |
614 | ||
615 | cdr.OCUP = 1; | |
616 | SetResultSize(1); | |
617 | cdr.StatP |= 0x22; | |
618 | cdr.StatP &= ~0x40; | |
619 | cdr.Result[0] = cdr.StatP; | |
620 | ||
621 | ReadTrack(); | |
622 | ||
623 | buf = CDR_getBuffer(); | |
624 | if (buf == NULL) | |
625 | cdr.RErr = -1; | |
626 | ||
627 | if (cdr.RErr == -1) { | |
628 | #ifdef CDR_LOG | |
629 | fprintf(emuLog, "cdrReadInterrupt() Log: err\n"); | |
630 | #endif | |
631 | memset(cdr.Transfer, 0, DATA_SIZE); | |
632 | cdr.Stat = DiskError; | |
633 | cdr.Result[0] |= 0x01; | |
634 | CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime); | |
635 | return; | |
636 | } | |
637 | ||
638 | memcpy(cdr.Transfer, buf, DATA_SIZE); | |
639 | CheckPPFCache(cdr.Transfer, cdr.Prev[0], cdr.Prev[1], cdr.Prev[2]); | |
640 | ||
641 | cdr.Stat = DataReady; | |
642 | ||
643 | #ifdef CDR_LOG | |
644 | fprintf(emuLog, "cdrReadInterrupt() Log: cdr.Transfer %x:%x:%x\n", cdr.Transfer[0], cdr.Transfer[1], cdr.Transfer[2]); | |
645 | #endif | |
646 | ||
647 | if ((!cdr.Muted) && (cdr.Mode & 0x40) && (!Config.Xa) && (cdr.FirstSector != -1)) { // CD-XA | |
648 | if ((cdr.Transfer[4 + 2] & 0x4) && | |
649 | ((cdr.Mode & 0x8) ? (cdr.Transfer[4 + 1] == cdr.Channel) : 1) && | |
650 | (cdr.Transfer[4 + 0] == cdr.File)) { | |
651 | int ret = xa_decode_sector(&cdr.Xa, cdr.Transfer+4, cdr.FirstSector); | |
652 | ||
653 | if (!ret) { | |
654 | SPU_playADPCMchannel(&cdr.Xa); | |
655 | cdr.FirstSector = 0; | |
656 | } | |
657 | else cdr.FirstSector = -1; | |
658 | } | |
659 | } | |
660 | ||
661 | cdr.SetSector[2]++; | |
662 | if (cdr.SetSector[2] == 75) { | |
663 | cdr.SetSector[2] = 0; | |
664 | cdr.SetSector[1]++; | |
665 | if (cdr.SetSector[1] == 60) { | |
666 | cdr.SetSector[1] = 0; | |
667 | cdr.SetSector[0]++; | |
668 | } | |
669 | } | |
670 | ||
671 | cdr.Readed = 0; | |
672 | ||
673 | if ((cdr.Transfer[4 + 2] & 0x80) && (cdr.Mode & 0x2)) { // EOF | |
674 | #ifdef CDR_LOG | |
675 | CDR_LOG("cdrReadInterrupt() Log: Autopausing read\n"); | |
676 | #endif | |
677 | // AddIrqQueue(AUTOPAUSE, 0x1000); | |
678 | AddIrqQueue(CdlPause, 0x1000); | |
679 | } | |
680 | else { | |
681 | CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime); | |
682 | } | |
683 | psxHu32ref(0x1070) |= SWAP32((u32)0x4); | |
684 | } | |
685 | ||
686 | /* | |
687 | cdrRead0: | |
688 | bit 0 - 0 REG1 command send / 1 REG1 data read | |
689 | bit 1 - 0 data transfer finish / 1 data transfer ready/in progress | |
690 | bit 2 - unknown | |
691 | bit 3 - unknown | |
692 | bit 4 - unknown | |
693 | bit 5 - 1 result ready | |
694 | bit 6 - 1 dma ready | |
695 | bit 7 - 1 command being processed | |
696 | */ | |
697 | ||
698 | unsigned char cdrRead0(void) { | |
699 | if (cdr.ResultReady) | |
700 | cdr.Ctrl |= 0x20; | |
701 | else | |
702 | cdr.Ctrl &= ~0x20; | |
703 | ||
704 | if (cdr.OCUP) | |
705 | cdr.Ctrl |= 0x40; | |
706 | // else | |
707 | // cdr.Ctrl &= ~0x40; | |
708 | ||
709 | // What means the 0x10 and the 0x08 bits? I only saw it used by the bios | |
710 | cdr.Ctrl |= 0x18; | |
711 | ||
712 | #ifdef CDR_LOG | |
713 | CDR_LOG("cdrRead0() Log: CD0 Read: %x\n", cdr.Ctrl); | |
714 | #endif | |
715 | ||
716 | return psxHu8(0x1800) = cdr.Ctrl; | |
717 | } | |
718 | ||
719 | /* | |
720 | cdrWrite0: | |
721 | 0 - to send a command / 1 - to get the result | |
722 | */ | |
723 | ||
724 | void cdrWrite0(unsigned char rt) { | |
725 | #ifdef CDR_LOG | |
726 | CDR_LOG("cdrWrite0() Log: CD0 write: %x\n", rt); | |
727 | #endif | |
728 | cdr.Ctrl = rt | (cdr.Ctrl & ~0x3); | |
729 | ||
730 | if (rt == 0) { | |
731 | cdr.ParamP = 0; | |
732 | cdr.ParamC = 0; | |
733 | cdr.ResultReady = 0; | |
734 | } | |
735 | } | |
736 | ||
737 | unsigned char cdrRead1(void) { | |
738 | if (cdr.ResultReady) { // && cdr.Ctrl & 0x1) { | |
739 | psxHu8(0x1801) = cdr.Result[cdr.ResultP++]; | |
740 | if (cdr.ResultP == cdr.ResultC) | |
741 | cdr.ResultReady = 0; | |
742 | } else { | |
743 | psxHu8(0x1801) = 0; | |
744 | } | |
745 | #ifdef CDR_LOG | |
746 | CDR_LOG("cdrRead1() Log: CD1 Read: %x\n", psxHu8(0x1801)); | |
747 | #endif | |
748 | return psxHu8(0x1801); | |
749 | } | |
750 | ||
751 | void cdrWrite1(unsigned char rt) { | |
752 | int i; | |
753 | ||
754 | #ifdef CDR_LOG | |
755 | CDR_LOG("cdrWrite1() Log: CD1 write: %x (%s)\n", rt, CmdName[rt]); | |
756 | #endif | |
757 | // psxHu8(0x1801) = rt; | |
758 | cdr.Cmd = rt; | |
759 | cdr.OCUP = 0; | |
760 | ||
761 | #ifdef CDRCMD_DEBUG | |
762 | SysPrintf("cdrWrite1() Log: CD1 write: %x (%s)", rt, CmdName[rt]); | |
763 | if (cdr.ParamC) { | |
764 | SysPrintf(" Param[%d] = {", cdr.ParamC); | |
765 | for (i = 0; i < cdr.ParamC; i++) | |
766 | SysPrintf(" %x,", cdr.Param[i]); | |
767 | SysPrintf("}\n"); | |
768 | } else { | |
769 | SysPrintf("\n"); | |
770 | } | |
771 | #endif | |
772 | ||
773 | if (cdr.Ctrl & 0x1) return; | |
774 | ||
775 | switch (cdr.Cmd) { | |
776 | case CdlSync: | |
777 | cdr.Ctrl |= 0x80; | |
778 | cdr.Stat = NoIntr; | |
779 | AddIrqQueue(cdr.Cmd, 0x1000); | |
780 | break; | |
781 | ||
782 | case CdlNop: | |
783 | cdr.Ctrl |= 0x80; | |
784 | cdr.Stat = NoIntr; | |
785 | AddIrqQueue(cdr.Cmd, 0x1000); | |
786 | break; | |
787 | ||
788 | case CdlSetloc: | |
789 | StopReading(); | |
790 | cdr.Seeked = FALSE; | |
791 | for (i = 0; i < 3; i++) | |
792 | cdr.SetSector[i] = btoi(cdr.Param[i]); | |
793 | cdr.SetSector[3] = 0; | |
794 | /* if ((cdr.SetSector[0] | cdr.SetSector[1] | cdr.SetSector[2]) == 0) { | |
795 | *(u32 *)cdr.SetSector = *(u32 *)cdr.SetSectorSeek; | |
796 | }*/ | |
797 | cdr.Ctrl |= 0x80; | |
798 | cdr.Stat = NoIntr; | |
799 | AddIrqQueue(cdr.Cmd, 0x1000); | |
800 | break; | |
801 | ||
802 | case CdlPlay: | |
803 | if (!cdr.SetSector[0] & !cdr.SetSector[1] & !cdr.SetSector[2]) { | |
804 | if (CDR_getTN(cdr.ResultTN) != -1) { | |
805 | if (cdr.CurTrack > cdr.ResultTN[1]) | |
806 | cdr.CurTrack = cdr.ResultTN[1]; | |
807 | if (CDR_getTD((unsigned char)(cdr.CurTrack), cdr.ResultTD) != -1) { | |
808 | int tmp = cdr.ResultTD[2]; | |
809 | cdr.ResultTD[2] = cdr.ResultTD[0]; | |
810 | cdr.ResultTD[0] = tmp; | |
811 | if (!Config.Cdda) CDR_play(cdr.ResultTD); | |
812 | } | |
813 | } | |
814 | } else if (!Config.Cdda) { | |
815 | CDR_play(cdr.SetSector); | |
816 | } | |
817 | cdr.Play = TRUE; | |
818 | cdr.Ctrl |= 0x80; | |
819 | cdr.Stat = NoIntr; | |
820 | AddIrqQueue(cdr.Cmd, 0x1000); | |
821 | break; | |
822 | ||
823 | case CdlForward: | |
824 | if (cdr.CurTrack < 0xaa) | |
825 | cdr.CurTrack++; | |
826 | cdr.Ctrl |= 0x80; | |
827 | cdr.Stat = NoIntr; | |
828 | AddIrqQueue(cdr.Cmd, 0x1000); | |
829 | break; | |
830 | ||
831 | case CdlBackward: | |
832 | if (cdr.CurTrack > 1) | |
833 | cdr.CurTrack--; | |
834 | cdr.Ctrl |= 0x80; | |
835 | cdr.Stat = NoIntr; | |
836 | AddIrqQueue(cdr.Cmd, 0x1000); | |
837 | break; | |
838 | ||
839 | case CdlReadN: | |
840 | cdr.Irq = 0; | |
841 | StopReading(); | |
842 | cdr.Ctrl|= 0x80; | |
843 | cdr.Stat = NoIntr; | |
844 | StartReading(1, 0x1000); | |
845 | break; | |
846 | ||
847 | case CdlStandby: | |
848 | StopCdda(); | |
849 | StopReading(); | |
850 | cdr.Ctrl |= 0x80; | |
851 | cdr.Stat = NoIntr; | |
852 | AddIrqQueue(cdr.Cmd, 0x1000); | |
853 | break; | |
854 | ||
855 | case CdlStop: | |
856 | StopCdda(); | |
857 | StopReading(); | |
858 | cdr.Ctrl |= 0x80; | |
859 | cdr.Stat = NoIntr; | |
860 | AddIrqQueue(cdr.Cmd, 0x1000); | |
861 | break; | |
862 | ||
863 | case CdlPause: | |
864 | StopCdda(); | |
865 | StopReading(); | |
866 | cdr.Ctrl |= 0x80; | |
867 | cdr.Stat = NoIntr; | |
868 | AddIrqQueue(cdr.Cmd, 0x80000); | |
869 | break; | |
870 | ||
871 | case CdlReset: | |
872 | case CdlInit: | |
873 | StopCdda(); | |
874 | StopReading(); | |
875 | cdr.Ctrl |= 0x80; | |
876 | cdr.Stat = NoIntr; | |
877 | AddIrqQueue(cdr.Cmd, 0x1000); | |
878 | break; | |
879 | ||
880 | case CdlMute: | |
881 | cdr.Muted = TRUE; | |
882 | cdr.Ctrl |= 0x80; | |
883 | cdr.Stat = NoIntr; | |
884 | AddIrqQueue(cdr.Cmd, 0x1000); | |
885 | break; | |
886 | ||
887 | case CdlDemute: | |
888 | cdr.Muted = FALSE; | |
889 | cdr.Ctrl |= 0x80; | |
890 | cdr.Stat = NoIntr; | |
891 | AddIrqQueue(cdr.Cmd, 0x1000); | |
892 | break; | |
893 | ||
894 | case CdlSetfilter: | |
895 | cdr.File = cdr.Param[0]; | |
896 | cdr.Channel = cdr.Param[1]; | |
897 | cdr.Ctrl |= 0x80; | |
898 | cdr.Stat = NoIntr; | |
899 | AddIrqQueue(cdr.Cmd, 0x1000); | |
900 | break; | |
901 | ||
902 | case CdlSetmode: | |
903 | #ifdef CDR_LOG | |
904 | CDR_LOG("cdrWrite1() Log: Setmode %x\n", cdr.Param[0]); | |
905 | #endif | |
906 | cdr.Mode = cdr.Param[0]; | |
907 | cdr.Ctrl |= 0x80; | |
908 | cdr.Stat = NoIntr; | |
909 | AddIrqQueue(cdr.Cmd, 0x1000); | |
910 | break; | |
911 | ||
912 | case CdlGetmode: | |
913 | cdr.Ctrl |= 0x80; | |
914 | cdr.Stat = NoIntr; | |
915 | AddIrqQueue(cdr.Cmd, 0x1000); | |
916 | break; | |
917 | ||
918 | case CdlGetlocL: | |
919 | cdr.Ctrl |= 0x80; | |
920 | cdr.Stat = NoIntr; | |
921 | AddIrqQueue(cdr.Cmd, 0x1000); | |
922 | break; | |
923 | ||
924 | case CdlGetlocP: | |
925 | cdr.Ctrl |= 0x80; | |
926 | cdr.Stat = NoIntr; | |
927 | AddIrqQueue(cdr.Cmd, 0x1000); | |
928 | break; | |
929 | ||
930 | case CdlGetTN: | |
931 | cdr.Ctrl |= 0x80; | |
932 | cdr.Stat = NoIntr; | |
933 | AddIrqQueue(cdr.Cmd, 0x1000); | |
934 | break; | |
935 | ||
936 | case CdlGetTD: | |
937 | cdr.Ctrl |= 0x80; | |
938 | cdr.Stat = NoIntr; | |
939 | AddIrqQueue(cdr.Cmd, 0x1000); | |
940 | break; | |
941 | ||
942 | case CdlSeekL: | |
943 | // ((u32 *)cdr.SetSectorSeek)[0] = ((u32 *)cdr.SetSector)[0]; | |
944 | cdr.Ctrl |= 0x80; | |
945 | cdr.Stat = NoIntr; | |
946 | AddIrqQueue(cdr.Cmd, 0x1000); | |
947 | break; | |
948 | ||
949 | case CdlSeekP: | |
950 | // ((u32 *)cdr.SetSectorSeek)[0] = ((u32 *)cdr.SetSector)[0]; | |
951 | cdr.Ctrl |= 0x80; | |
952 | cdr.Stat = NoIntr; | |
953 | AddIrqQueue(cdr.Cmd, 0x1000); | |
954 | break; | |
955 | ||
956 | case CdlTest: | |
957 | cdr.Ctrl |= 0x80; | |
958 | cdr.Stat = NoIntr; | |
959 | AddIrqQueue(cdr.Cmd, 0x1000); | |
960 | break; | |
961 | ||
962 | case CdlID: | |
963 | cdr.Ctrl |= 0x80; | |
964 | cdr.Stat = NoIntr; | |
965 | AddIrqQueue(cdr.Cmd, 0x1000); | |
966 | break; | |
967 | ||
968 | case CdlReadS: | |
969 | cdr.Irq = 0; | |
970 | StopReading(); | |
971 | cdr.Ctrl |= 0x80; | |
972 | cdr.Stat = NoIntr; | |
973 | StartReading(2, 0x1000); | |
974 | break; | |
975 | ||
976 | case CdlReadToc: | |
977 | cdr.Ctrl |= 0x80; | |
978 | cdr.Stat = NoIntr; | |
979 | AddIrqQueue(cdr.Cmd, 0x1000); | |
980 | break; | |
981 | ||
982 | default: | |
983 | #ifdef CDR_LOG | |
984 | CDR_LOG("cdrWrite1() Log: Unknown command: %x\n", cdr.Cmd); | |
985 | #endif | |
986 | return; | |
987 | } | |
988 | if (cdr.Stat != NoIntr) { | |
989 | psxHu32ref(0x1070) |= SWAP32((u32)0x4); | |
990 | } | |
991 | } | |
992 | ||
993 | unsigned char cdrRead2(void) { | |
994 | unsigned char ret; | |
995 | ||
996 | if (cdr.Readed == 0) { | |
997 | ret = 0; | |
998 | } else { | |
999 | ret = *cdr.pTransfer++; | |
1000 | } | |
1001 | ||
1002 | #ifdef CDR_LOG | |
1003 | CDR_LOG("cdrRead2() Log: CD2 Read: %x\n", ret); | |
1004 | #endif | |
1005 | return ret; | |
1006 | } | |
1007 | ||
1008 | void cdrWrite2(unsigned char rt) { | |
1009 | #ifdef CDR_LOG | |
1010 | CDR_LOG("cdrWrite2() Log: CD2 write: %x\n", rt); | |
1011 | #endif | |
1012 | if (cdr.Ctrl & 0x1) { | |
1013 | switch (rt) { | |
1014 | case 0x07: | |
1015 | cdr.ParamP = 0; | |
1016 | cdr.ParamC = 0; | |
1017 | cdr.ResultReady = 1; //0; | |
1018 | cdr.Ctrl &= ~3; //cdr.Ctrl = 0; | |
1019 | break; | |
1020 | ||
1021 | default: | |
1022 | cdr.Reg2 = rt; | |
1023 | break; | |
1024 | } | |
1025 | } else if (!(cdr.Ctrl & 0x1) && cdr.ParamP < 8) { | |
1026 | cdr.Param[cdr.ParamP++] = rt; | |
1027 | cdr.ParamC++; | |
1028 | } | |
1029 | } | |
1030 | ||
1031 | unsigned char cdrRead3(void) { | |
1032 | if (cdr.Stat) { | |
1033 | if (cdr.Ctrl & 0x1) | |
1034 | psxHu8(0x1803) = cdr.Stat | 0xE0; | |
1035 | else | |
1036 | psxHu8(0x1803) = 0xff; | |
1037 | } else { | |
1038 | psxHu8(0x1803) = 0; | |
1039 | } | |
1040 | #ifdef CDR_LOG | |
1041 | CDR_LOG("cdrRead3() Log: CD3 Read: %x\n", psxHu8(0x1803)); | |
1042 | #endif | |
1043 | return psxHu8(0x1803); | |
1044 | } | |
1045 | ||
1046 | void cdrWrite3(unsigned char rt) { | |
1047 | #ifdef CDR_LOG | |
1048 | CDR_LOG("cdrWrite3() Log: CD3 write: %x\n", rt); | |
1049 | #endif | |
1050 | if (rt == 0x07 && cdr.Ctrl & 0x1) { | |
1051 | cdr.Stat = 0; | |
1052 | ||
1053 | if (cdr.Irq == 0xff) { | |
1054 | cdr.Irq = 0; | |
1055 | return; | |
1056 | } | |
1057 | if (cdr.Irq) | |
1058 | CDR_INT(cdr.eCycle); | |
1059 | if (cdr.Reading && !cdr.ResultReady) | |
1060 | CDREAD_INT((cdr.Mode & 0x80) ? (cdReadTime / 2) : cdReadTime); | |
1061 | ||
1062 | return; | |
1063 | } | |
1064 | if (rt == 0x80 && !(cdr.Ctrl & 0x1) && cdr.Readed == 0) { | |
1065 | cdr.Readed = 1; | |
1066 | cdr.pTransfer = cdr.Transfer; | |
1067 | ||
1068 | switch (cdr.Mode & 0x30) { | |
1069 | case 0x10: | |
1070 | case 0x00: | |
1071 | cdr.pTransfer += 12; | |
1072 | break; | |
1073 | default: | |
1074 | break; | |
1075 | } | |
1076 | } | |
1077 | } | |
1078 | ||
1079 | void psxDma3(u32 madr, u32 bcr, u32 chcr) { | |
1080 | u32 cdsize; | |
1081 | u8 *ptr; | |
1082 | ||
1083 | #ifdef CDR_LOG | |
1084 | CDR_LOG("psxDma3() Log: *** DMA 3 *** %x addr = %x size = %x\n", chcr, madr, bcr); | |
1085 | #endif | |
1086 | ||
1087 | switch (chcr) { | |
1088 | case 0x11000000: | |
1089 | case 0x11400100: | |
1090 | if (cdr.Readed == 0) { | |
1091 | #ifdef CDR_LOG | |
1092 | CDR_LOG("psxDma3() Log: *** DMA 3 *** NOT READY\n"); | |
1093 | #endif | |
1094 | break; | |
1095 | } | |
1096 | ||
1097 | cdsize = (bcr & 0xffff) * 4; | |
1098 | ||
1099 | ptr = (u8 *)PSXM(madr); | |
1100 | if (ptr == NULL) { | |
1101 | #ifdef CPU_LOG | |
1102 | CDR_LOG("psxDma3() Log: *** DMA 3 *** NULL Pointer!\n"); | |
1103 | #endif | |
1104 | break; | |
1105 | } | |
1106 | memcpy(ptr, cdr.pTransfer, cdsize); | |
1107 | psxCpu->Clear(madr, cdsize / 4); | |
1108 | cdr.pTransfer += cdsize; | |
1109 | break; | |
1110 | default: | |
1111 | #ifdef CDR_LOG | |
1112 | CDR_LOG("psxDma3() Log: Unknown cddma %x\n", chcr); | |
1113 | #endif | |
1114 | break; | |
1115 | } | |
1116 | ||
1117 | HW_DMA3_CHCR &= SWAP32(~0x01000000); | |
1118 | DMA_INTERRUPT(3); | |
1119 | } | |
1120 | ||
1121 | void cdrReset() { | |
1122 | memset(&cdr, 0, sizeof(cdr)); | |
1123 | cdr.CurTrack = 1; | |
1124 | cdr.File = 1; | |
1125 | cdr.Channel = 1; | |
1126 | } | |
1127 | ||
1128 | int cdrFreeze(gzFile f, int Mode) { | |
1129 | uintptr_t tmp; | |
1130 | ||
1131 | gzfreeze(&cdr, sizeof(cdr)); | |
1132 | ||
1133 | if (Mode == 1) | |
1134 | tmp = cdr.pTransfer - cdr.Transfer; | |
1135 | ||
1136 | gzfreeze(&tmp, sizeof(tmp)); | |
1137 | ||
1138 | if (Mode == 0) | |
1139 | cdr.pTransfer = cdr.Transfer + tmp; | |
1140 | ||
1141 | return 0; | |
1142 | } |