1 /***************************************************************************
2 * Copyright (C) 2007 Ryan Schultz, PCSX-df Team, PCSX team *
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. *
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. *
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 ***************************************************************************/
21 * Miscellaneous functions, including savestates and CD-ROM loading.
33 char CdromId[10] = "";
34 char CdromLabel[33] = "";
36 // PSX Executable types
42 #define ISODCL(from, to) (to - from + 1)
44 struct iso_directory_record {
45 char length [ISODCL (1, 1)]; /* 711 */
46 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
47 char extent [ISODCL (3, 10)]; /* 733 */
48 char size [ISODCL (11, 18)]; /* 733 */
49 char date [ISODCL (19, 25)]; /* 7 by 711 */
50 char flags [ISODCL (26, 26)];
51 char file_unit_size [ISODCL (27, 27)]; /* 711 */
52 char interleave [ISODCL (28, 28)]; /* 711 */
53 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
54 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
58 void mmssdd( char *b, char *p )
62 unsigned char *u = (void *)b;
63 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
64 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
65 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
67 int block = *((int*)b);
71 m = block / 4500; // minutes
72 block = block - m * 4500; // minutes rest
73 s = block / 75; // seconds
74 d = block - s * 75; // seconds rest
76 m = ((m / 10) << 4) | m % 10;
77 s = ((s / 10) << 4) | s % 10;
78 d = ((d / 10) << 4) | d % 10;
86 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
91 if (time[1] == 60) { \
96 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
99 if (CDR_readTrack(time) == -1) return -1; \
100 buf = (void *)CDR_getBuffer(); \
101 if (buf == NULL) return -1; \
102 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
104 #define READDIR(_dir) \
106 memcpy(_dir, buf + 12, 2048); \
110 memcpy(_dir + 2048, buf + 12, 2048);
112 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
113 struct iso_directory_record *dir;
119 // only try to scan if a filename is given
120 if (!strlen(filename)) return -1;
124 dir = (struct iso_directory_record*) &mdir[i];
125 if (dir->length[0] == 0) {
128 i += (u8)dir->length[0];
130 if (dir->flags[0] & 0x2) { // it's a dir
131 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
132 if (filename[dir->name_len[0]] != '\\') continue;
134 filename += dir->name_len[0] + 1;
136 mmssdd(dir->extent, (char *)time);
142 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
143 mmssdd(dir->extent, (char *)time);
152 static const unsigned int gpu_ctl_def[] = {
153 0x00000000, 0x01000000, 0x03000000, 0x04000000,
154 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
157 static const unsigned int gpu_data_def[] = {
158 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
159 0xe5001000, 0xe6000000,
160 0x02000000, 0x00000000, 0x01ff03ff,
163 static void fake_bios_gpu_setup(void)
167 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
168 GPU_writeStatus(gpu_ctl_def[i]);
170 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
171 GPU_writeData(gpu_data_def[i]);
176 struct iso_directory_record *dir;
181 // not the best place to do it, but since BIOS boot logo killer
182 // is just below, do it here
183 fake_bios_gpu_setup();
185 if (!Config.HLE && !Config.SlowBoot) {
187 psxRegs.pc = psxRegs.GPR.n.ra;
191 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
195 // skip head and sub, and go to the root directory record
196 dir = (struct iso_directory_record*) &buf[12+156];
198 mmssdd(dir->extent, (char*)time);
202 // Load SYSTEM.CNF and scan for the main executable
203 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
204 // if SYSTEM.CNF is missing, start an existing PSX.EXE
205 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
210 // read the SYSTEM.CNF
213 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
214 if (GetCdromFile(mdir, time, exename) == -1) {
215 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
216 if (GetCdromFile(mdir, time, exename) == -1) {
217 char *ptr = strstr((char *)buf + 12, "cdrom:");
220 while (*ptr == '\\' || *ptr == '/') ptr++;
221 strncpy(exename, ptr, 255);
224 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
226 if (GetCdromFile(mdir, time, exename) == -1)
233 // Read the EXE-Header
237 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
239 psxRegs.pc = SWAP32(tmpHead.pc0);
240 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
241 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
242 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
244 tmpHead.t_size = SWAP32(tmpHead.t_size);
245 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
247 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
250 // Read the rest of the main executable
251 while (tmpHead.t_size & ~2047) {
252 void *ptr = (void *)PSXM(tmpHead.t_addr);
257 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
259 tmpHead.t_size -= 2048;
260 tmpHead.t_addr += 2048;
266 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
267 struct iso_directory_record *dir;
274 sscanf(filename, "cdrom:\\%255s", exename);
276 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
280 // skip head and sub, and go to the root directory record
281 dir = (struct iso_directory_record *)&buf[12 + 156];
283 mmssdd(dir->extent, (char*)time);
287 if (GetCdromFile(mdir, time, exename) == -1) return -1;
291 memcpy(head, buf + 12, sizeof(EXE_HEADER));
295 psxCpu->Clear(addr, size / 4);
298 while (size & ~2047) {
303 if (mem != INVALID_PTR)
304 memcpy(mem, buf + 12, 2048);
314 struct iso_directory_record *dir;
315 unsigned char time[4];
317 unsigned char mdir[4096];
325 time[2] = itob(0x10);
329 memset(CdromLabel, 0, sizeof(CdromLabel));
330 memset(CdromId, 0, sizeof(CdromId));
331 memset(exename, 0, sizeof(exename));
333 strncpy(CdromLabel, buf + 52, 32);
335 // skip head and sub, and go to the root directory record
336 dir = (struct iso_directory_record *)&buf[12 + 156];
338 mmssdd(dir->extent, (char *)time);
342 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
345 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
346 if (GetCdromFile(mdir, time, exename) == -1) {
347 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
348 if (GetCdromFile(mdir, time, exename) == -1) {
349 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
352 while (*ptr == '\\' || *ptr == '/') ptr++;
353 strncpy(exename, ptr, 255);
356 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
358 if (GetCdromFile(mdir, time, exename) == -1)
359 return -1; // main executable not found
364 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
365 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
367 size_t i, len = strlen(exename) - offset;
368 for (i = 0; i < len; i++)
369 exename[i] = exename[i + offset];
372 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
373 strcpy(exename, "PSX.EXE;1");
374 strcpy(CdromId, "SLUS99999");
376 return -1; // SYSTEM.CNF and PSX.EXE not found
378 if (CdromId[0] == '\0') {
379 len = strlen(exename);
381 for (i = 0; i < len; ++i) {
382 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
384 if (isalnum(exename[i]))
385 CdromId[c++] = exename[i];
389 if (CdromId[0] == '\0')
390 strcpy(CdromId, "SLUS99999");
392 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
394 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
395 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
396 !strncmp(CdromId, "DTLS3035", 8) ||
397 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
398 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
399 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
400 Config.PsxType = PSX_TYPE_PAL; // pal
401 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
404 if (CdromLabel[0] == ' ') {
405 strncpy(CdromLabel, CdromId, 9);
407 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
408 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
409 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
418 static int PSXGetFileType(FILE *f) {
419 unsigned long current;
425 fseek(f, 0L, SEEK_SET);
426 if (fread(&mybuf, sizeof(mybuf), 1, f) != sizeof(mybuf))
429 fseek(f, current, SEEK_SET);
431 exe_hdr = (EXE_HEADER *)mybuf;
432 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
435 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
438 coff_hdr = (FILHDR *)mybuf;
439 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
446 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
451 // temporary pandora workaround..
453 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
458 tmp = malloc(size * nmemb);
460 ret = fread(tmp, size, nmemb, stream);
461 memcpy(ptr, tmp, size * nmemb);
467 int Load(const char *ExePath) {
473 u32 section_address, section_size;
476 strcpy(CdromId, "SLUS99999");
477 strcpy(CdromLabel, "SLUS_999.99");
479 tmpFile = fopen(ExePath, "rb");
480 if (tmpFile == NULL) {
481 SysPrintf(_("Error opening file: %s.\n"), ExePath);
484 type = PSXGetFileType(tmpFile);
487 if (fread(&tmpHead, sizeof(EXE_HEADER), 1, tmpFile) != sizeof(EXE_HEADER))
489 section_address = SWAP32(tmpHead.t_addr);
490 section_size = SWAP32(tmpHead.t_size);
491 mem = PSXM(section_address);
492 if (mem != INVALID_PTR) {
493 fseek(tmpFile, 0x800, SEEK_SET);
494 fread_to_ram(mem, section_size, 1, tmpFile);
495 psxCpu->Clear(section_address, section_size / 4);
497 psxRegs.pc = SWAP32(tmpHead.pc0);
498 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
499 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
500 if (psxRegs.GPR.n.sp == 0)
501 psxRegs.GPR.n.sp = 0x801fff00;
505 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
507 if (fread(&opcode, sizeof(opcode), 1, tmpFile) != sizeof(opcode))
510 case 1: /* Section loading */
511 if (fread(§ion_address, sizeof(section_address), 1, tmpFile) != sizeof(section_address))
513 if (fread(§ion_size, sizeof(section_size), 1, tmpFile) != sizeof(section_size))
515 section_address = SWAPu32(section_address);
516 section_size = SWAPu32(section_size);
518 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
520 mem = PSXM(section_address);
521 if (mem != INVALID_PTR) {
522 fread_to_ram(mem, section_size, 1, tmpFile);
523 psxCpu->Clear(section_address, section_size / 4);
526 case 3: /* register loading (PC only?) */
527 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
528 if (fread(&psxRegs.pc, sizeof(psxRegs.pc), 1, tmpFile) != sizeof(psxRegs.pc))
530 psxRegs.pc = SWAPu32(psxRegs.pc);
532 case 0: /* End of file */
535 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
539 } while (opcode != 0 && retval == 0);
542 SysPrintf(_("COFF files not supported.\n"));
546 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
547 SysPrintf(_("(did you forget -cdfile ?)\n"));
555 CdromLabel[0] = '\0';
563 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
571 static void *zlib_open(const char *name, const char *mode)
573 return gzopen(name, mode);
576 static int zlib_read(void *file, void *buf, u32 len)
578 return gzread(file, buf, len);
581 static int zlib_write(void *file, const void *buf, u32 len)
583 return gzwrite(file, buf, len);
586 static long zlib_seek(void *file, long offs, int whence)
588 return gzseek(file, offs, whence);
591 static void zlib_close(void *file)
596 struct PcsxSaveFuncs SaveFuncs = {
597 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
600 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
602 // Savestate Versioning!
603 // If you make changes to the savestate version, please increment the value below.
604 static const u32 SaveVersion = 0x8b410006;
606 static int drc_is_lightrec(void)
608 #if defined(LIGHTREC)
615 int SaveState(const char *file) {
622 f = SaveFuncs.open(file, "wb");
623 if (f == NULL) return -1;
625 new_dyna_before_save();
627 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
628 lightrec_plugin_prepare_save_state();
630 SaveFuncs.write(f, (void *)PcsxHeader, 32);
631 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
632 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
634 pMem = (unsigned char *)malloc(128 * 96 * 3);
635 if (pMem == NULL) return -1;
636 GPU_getScreenPic(pMem);
637 SaveFuncs.write(f, pMem, 128 * 96 * 3);
643 SaveFuncs.write(f, psxM, 0x00200000);
644 SaveFuncs.write(f, psxR, 0x00080000);
645 SaveFuncs.write(f, psxH, 0x00010000);
646 // only partial save of psxRegisters to maintain savestate compat
647 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
650 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
651 gpufP->ulFreezeVersion = 1;
652 GPU_freeze(1, gpufP);
653 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
657 spufP = (SPUFreeze_t *) malloc(16);
658 SPU_freeze(2, spufP, psxRegs.cycle);
659 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
661 spufP = (SPUFreeze_t *) malloc(Size);
662 SPU_freeze(1, spufP, psxRegs.cycle);
663 SaveFuncs.write(f, spufP, Size);
671 new_dyna_freeze(f, 1);
675 new_dyna_after_save();
680 int LoadState(const char *file) {
689 f = SaveFuncs.open(file, "rb");
690 if (f == NULL) return -1;
692 SaveFuncs.read(f, header, sizeof(header));
693 SaveFuncs.read(f, &version, sizeof(u32));
694 SaveFuncs.read(f, &hle, sizeof(boolean));
696 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
705 if (!drc_is_lightrec() || Config.Cpu == CPU_INTERPRETER)
707 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
709 SaveFuncs.read(f, psxM, 0x00200000);
710 SaveFuncs.read(f, psxR, 0x00080000);
711 SaveFuncs.read(f, psxH, 0x00010000);
712 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
713 psxRegs.gteBusyCycle = psxRegs.cycle;
715 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
716 lightrec_plugin_prepare_load_state();
722 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
723 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
724 GPU_freeze(0, gpufP);
726 if (HW_GPU_STATUS == 0)
727 HW_GPU_STATUS = SWAP32(GPU_readStatus());
730 SaveFuncs.read(f, &Size, 4);
731 spufP = (SPUFreeze_t *)malloc(Size);
732 SaveFuncs.read(f, spufP, Size);
733 SPU_freeze(0, spufP, psxRegs.cycle);
741 new_dyna_freeze(f, 0);
748 int CheckState(const char *file) {
754 f = SaveFuncs.open(file, "rb");
755 if (f == NULL) return -1;
757 SaveFuncs.read(f, header, sizeof(header));
758 SaveFuncs.read(f, &version, sizeof(u32));
759 SaveFuncs.read(f, &hle, sizeof(boolean));
763 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
769 // NET Function Helpers
772 if (NET_recvData == NULL || NET_sendData == NULL)
775 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
776 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
777 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
778 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
779 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
780 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
788 if (NET_recvData == NULL || NET_sendData == NULL)
791 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
792 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
793 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
794 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
795 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
800 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
801 if (tmp != Config.Cpu) {
804 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
805 else psxCpu = &psxRec;
809 if (psxCpu->Init() == -1) {
810 SysClose(); return -1;
818 // remove the leading and trailing spaces in a string
819 void trim(char *str) {
823 // skip leading blanks
824 while (str[pos] <= ' ' && str[pos] > 0)
828 *(dest++) = str[pos];
832 *(dest--) = '\0'; // store the null
834 // remove trailing blanks
835 while (dest >= str && *dest <= ' ' && *dest > 0)
839 // lookup table for crc calculation
840 static unsigned short crctab[256] = {
841 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
842 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
843 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
844 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
845 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
846 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
847 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
848 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
849 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
850 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
851 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
852 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
853 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
854 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
855 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
856 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
857 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
858 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
859 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
860 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
861 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
862 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
863 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
864 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
865 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
866 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
867 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
868 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
869 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
872 u16 calcCrc(u8 *d, int len) {
876 for (i = 0; i < len; i++) {
877 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);