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.
34 char CdromId[10] = "";
35 char CdromLabel[33] = "";
37 // PSX Executable types
43 #define ISODCL(from, to) (to - from + 1)
45 struct iso_directory_record {
46 char length [ISODCL (1, 1)]; /* 711 */
47 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
48 char extent [ISODCL (3, 10)]; /* 733 */
49 char size [ISODCL (11, 18)]; /* 733 */
50 char date [ISODCL (19, 25)]; /* 7 by 711 */
51 char flags [ISODCL (26, 26)];
52 char file_unit_size [ISODCL (27, 27)]; /* 711 */
53 char interleave [ISODCL (28, 28)]; /* 711 */
54 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
55 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
59 static void mmssdd( char *b, char *p )
62 unsigned char *ub = (void *)b;
63 int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
66 m = block / 4500; // minutes
67 block = block - m * 4500; // minutes rest
68 s = block / 75; // seconds
69 d = block - s * 75; // seconds rest
71 m = ((m / 10) << 4) | m % 10;
72 s = ((s / 10) << 4) | s % 10;
73 d = ((d / 10) << 4) | d % 10;
81 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
86 if (time[1] == 60) { \
91 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
94 if (!CDR_readTrack(time)) return -1; \
95 buf = (void *)CDR_getBuffer(); \
96 if (buf == NULL) return -1; \
97 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
99 #define READDIR(_dir) \
101 memcpy(_dir, buf + 12, 2048); \
105 memcpy(_dir + 2048, buf + 12, 2048);
107 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
108 struct iso_directory_record *dir;
114 // only try to scan if a filename is given
115 if (filename == INVALID_PTR || !strlen(filename)) return -1;
119 dir = (struct iso_directory_record*) &mdir[i];
120 if (dir->length[0] == 0) {
123 i += (u8)dir->length[0];
125 if (dir->flags[0] & 0x2) { // it's a dir
126 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
127 if (filename[dir->name_len[0]] != '\\') continue;
129 filename += dir->name_len[0] + 1;
131 mmssdd(dir->extent, (char *)time);
137 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
138 mmssdd(dir->extent, (char *)time);
147 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
149 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
150 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
153 psxRegs.GPR.n.gp = gp;
154 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
155 psxRegs.GPR.n.fp = psxRegs.GPR.n.sp;
157 psxRegs.GPR.n.t0 = psxRegs.GPR.n.sp; // mimic A(43)
158 psxRegs.GPR.n.t3 = pc;
160 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
163 void BiosBootBypass() {
164 assert(psxRegs.pc == 0x80030000);
166 // skip BIOS logos and region check
167 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
168 psxRegs.pc = psxRegs.GPR.n.ra;
171 static void getFromCnf(char *buf, const char *key, u32 *val)
173 buf = strstr(buf, key);
175 buf = strchr(buf, '=');
177 *val = strtol(buf + 1, NULL, 16);
182 struct iso_directory_record *dir;
193 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
199 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
203 // skip head and sub, and go to the root directory record
204 dir = (struct iso_directory_record*) &buf[12+156];
206 mmssdd(dir->extent, (char*)time);
210 // Load SYSTEM.CNF and scan for the main executable
211 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
212 // if SYSTEM.CNF is missing, start an existing PSX.EXE
213 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
214 strcpy(exename, "PSX.EXE;1");
219 // read the SYSTEM.CNF
223 ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
224 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
225 ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
226 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
227 char *ptr = strstr((char *)buf + 12, "cdrom:");
230 while (*ptr == '\\' || *ptr == '/') ptr++;
231 strncpy(exename, ptr, 255);
234 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
236 if (GetCdromFile(mdir, time, exename) == -1)
242 getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
243 getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
244 getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
246 psxBiosCnfLoaded(cnf_tcb, cnf_event, cnf_stack);
248 // Read the EXE-Header
252 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
254 SysPrintf("manual booting '%s' pc=%x\n", exename, SWAP32(tmpHead.pc0));
255 sp = SWAP32(tmpHead.s_addr);
258 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), sp);
260 tmpHead.t_size = SWAP32(tmpHead.t_size);
261 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
263 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
266 // Read the rest of the main executable
267 while (tmpHead.t_size & ~2047) {
268 void *ptr = (void *)PSXM(tmpHead.t_addr);
273 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
275 tmpHead.t_size -= 2048;
276 tmpHead.t_addr += 2048;
282 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
283 struct iso_directory_record *dir;
291 if (filename == INVALID_PTR)
295 if ((p2 = strchr(p1, ':')))
299 snprintf(exename, sizeof(exename), "%s", p1);
301 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
305 // skip head and sub, and go to the root directory record
306 dir = (struct iso_directory_record *)&buf[12 + 156];
308 mmssdd(dir->extent, (char*)time);
312 if (GetCdromFile(mdir, time, exename) == -1) return -1;
316 memcpy(head, buf + 12, sizeof(EXE_HEADER));
317 size = SWAP32(head->t_size);
318 addr = SWAP32(head->t_addr);
320 psxCpu->Clear(addr, size / 4);
323 while (size & ~2047) {
328 if (mem != INVALID_PTR)
329 memcpy(mem, buf + 12, 2048);
339 struct iso_directory_record *dir;
340 unsigned char time[4];
342 unsigned char mdir[4096];
350 time[2] = itob(0x10);
354 memset(CdromLabel, 0, sizeof(CdromLabel));
355 memset(CdromId, 0, sizeof(CdromId));
356 memset(exename, 0, sizeof(exename));
358 strncpy(CdromLabel, buf + 52, 32);
360 // skip head and sub, and go to the root directory record
361 dir = (struct iso_directory_record *)&buf[12 + 156];
363 mmssdd(dir->extent, (char *)time);
367 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
370 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
371 if (GetCdromFile(mdir, time, exename) == -1) {
372 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
373 if (GetCdromFile(mdir, time, exename) == -1) {
374 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
377 while (*ptr == '\\' || *ptr == '/') ptr++;
378 strncpy(exename, ptr, 255);
381 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
383 if (GetCdromFile(mdir, time, exename) == -1)
384 return -1; // main executable not found
389 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
390 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
392 size_t i, len = strlen(exename) - offset;
393 for (i = 0; i < len; i++)
394 exename[i] = exename[i + offset];
397 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
398 strcpy(exename, "PSX.EXE;1");
399 strcpy(CdromId, "SLUS99999");
401 return -1; // SYSTEM.CNF and PSX.EXE not found
403 if (CdromId[0] == '\0') {
404 len = strlen(exename);
406 for (i = 0; i < len; ++i) {
407 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
409 if (isalnum(exename[i]))
410 CdromId[c++] = exename[i];
414 if (CdromId[0] == '\0')
415 strcpy(CdromId, "SLUS99999");
417 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
419 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
420 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
421 !strncmp(CdromId, "DTLS3035", 8) ||
422 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
423 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
424 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
425 Config.PsxType = PSX_TYPE_PAL; // pal
426 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
429 if (CdromLabel[0] == ' ') {
430 strncpy(CdromLabel, CdromId, 9);
432 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
433 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
434 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
443 static int PSXGetFileType(FILE *f) {
444 unsigned long current;
450 fseek(f, 0L, SEEK_SET);
451 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
454 fseek(f, current, SEEK_SET);
456 exe_hdr = (EXE_HEADER *)mybuf;
457 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
460 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
463 coff_hdr = (FILHDR *)mybuf;
464 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
471 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
476 // temporary pandora workaround..
478 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
483 tmp = malloc(size * nmemb);
485 ret = fread(tmp, size, nmemb, stream);
486 memcpy(ptr, tmp, size * nmemb);
492 int Load(const char *ExePath) {
498 u32 section_address, section_size;
501 strcpy(CdromId, "SLUS99999");
502 strcpy(CdromLabel, "SLUS_999.99");
504 tmpFile = fopen(ExePath, "rb");
505 if (tmpFile == NULL) {
506 SysPrintf(_("Error opening file: %s.\n"), ExePath);
509 type = PSXGetFileType(tmpFile);
512 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
514 section_address = SWAP32(tmpHead.t_addr);
515 section_size = SWAP32(tmpHead.t_size);
516 mem = PSXM(section_address);
517 if (mem != INVALID_PTR) {
518 fseek(tmpFile, 0x800, SEEK_SET);
519 fread_to_ram(mem, section_size, 1, tmpFile);
520 psxCpu->Clear(section_address, section_size / 4);
522 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
523 SWAP32(tmpHead.s_addr));
527 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
529 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
532 case 1: /* Section loading */
533 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
535 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
537 section_address = SWAPu32(section_address);
538 section_size = SWAPu32(section_size);
540 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
542 mem = PSXM(section_address);
543 if (mem != INVALID_PTR) {
544 fread_to_ram(mem, section_size, 1, tmpFile);
545 psxCpu->Clear(section_address, section_size / 4);
548 case 3: /* register loading (PC only?) */
549 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
550 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
552 psxRegs.pc = SWAPu32(psxRegs.pc);
554 case 0: /* End of file */
557 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
561 } while (opcode != 0 && retval == 0);
564 SysPrintf(_("COFF files not supported.\n"));
568 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
569 SysPrintf(_("(did you forget -cdfile ?)\n"));
577 CdromLabel[0] = '\0';
586 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
594 static void *zlib_open(const char *name, const char *mode)
596 return gzopen(name, mode);
599 static int zlib_read(void *file, void *buf, u32 len)
601 return gzread(file, buf, len);
604 static int zlib_write(void *file, const void *buf, u32 len)
606 return gzwrite(file, buf, len);
609 static long zlib_seek(void *file, long offs, int whence)
611 return gzseek(file, offs, whence);
614 static void zlib_close(void *file)
619 struct PcsxSaveFuncs SaveFuncs = {
620 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
623 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
625 // Savestate Versioning!
626 // If you make changes to the savestate version, please increment the value below.
627 static const u32 SaveVersion = 0x8b410006;
629 int SaveState(const char *file) {
631 GPUFreeze_t *gpufP = NULL;
632 SPUFreezeHdr_t spufH;
633 SPUFreeze_t *spufP = NULL;
634 unsigned char *pMem = NULL;
638 f = SaveFuncs.open(file, "wb");
639 if (f == NULL) return -1;
641 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
643 SaveFuncs.write(f, (void *)PcsxHeader, 32);
644 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
645 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
647 pMem = (unsigned char *)malloc(128 * 96 * 3);
648 if (pMem == NULL) goto cleanup;
649 GPU_getScreenPic(pMem);
650 SaveFuncs.write(f, pMem, 128 * 96 * 3);
656 SaveFuncs.write(f, psxM, 0x00200000);
657 SaveFuncs.write(f, psxR, 0x00080000);
658 SaveFuncs.write(f, psxH, 0x00010000);
659 // only partial save of psxRegisters to maintain savestate compat
660 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
663 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
664 if (gpufP == NULL) goto cleanup;
665 gpufP->ulFreezeVersion = 1;
666 GPU_freeze(1, gpufP);
667 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
668 free(gpufP); gpufP = NULL;
671 SPU_freeze(2, (SPUFreeze_t *)&spufH, psxRegs.cycle);
672 Size = spufH.Size; SaveFuncs.write(f, &Size, 4);
673 spufP = (SPUFreeze_t *) malloc(Size);
674 if (spufP == NULL) goto cleanup;
675 SPU_freeze(1, spufP, psxRegs.cycle);
676 SaveFuncs.write(f, spufP, Size);
677 free(spufP); spufP = NULL;
684 new_dyna_freeze(f, 1);
692 int LoadState(const char *file) {
694 GPUFreeze_t *gpufP = NULL;
695 SPUFreeze_t *spufP = NULL;
702 f = SaveFuncs.open(file, "rb");
703 if (f == NULL) return -1;
705 SaveFuncs.read(f, header, sizeof(header));
706 SaveFuncs.read(f, &version, sizeof(u32));
707 SaveFuncs.read(f, &hle, sizeof(boolean));
709 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
710 SysPrintf("incompatible savestate version %x\n", version);
718 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
719 SaveFuncs.read(f, psxM, 0x00200000);
720 SaveFuncs.read(f, psxR, 0x00080000);
721 SaveFuncs.read(f, psxH, 0x00010000);
722 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
723 psxRegs.gteBusyCycle = psxRegs.cycle;
725 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
731 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
732 if (gpufP == NULL) goto cleanup;
733 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
734 GPU_freeze(0, gpufP);
736 if (HW_GPU_STATUS == 0)
737 HW_GPU_STATUS = SWAP32(GPU_readStatus());
740 SaveFuncs.read(f, &Size, 4);
741 spufP = (SPUFreeze_t *)malloc(Size);
742 if (spufP == NULL) goto cleanup;
743 SaveFuncs.read(f, spufP, Size);
744 SPU_freeze(0, spufP, psxRegs.cycle);
752 new_dyna_freeze(f, 0);
760 int CheckState(const char *file) {
766 f = SaveFuncs.open(file, "rb");
767 if (f == NULL) return -1;
769 SaveFuncs.read(f, header, sizeof(header));
770 SaveFuncs.read(f, &version, sizeof(u32));
771 SaveFuncs.read(f, &hle, sizeof(boolean));
775 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
781 // NET Function Helpers
784 if (NET_recvData == NULL || NET_sendData == NULL)
788 boolean SpuIrq_old = 0;
789 boolean RCntFix_old = 0;
790 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
791 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
792 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
793 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
794 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
795 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
803 if (NET_recvData == NULL || NET_sendData == NULL)
807 boolean SpuIrq_old = 0;
808 boolean RCntFix_old = 0;
809 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
810 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
811 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
812 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
813 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
816 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
817 if (tmp != Config.Cpu) {
820 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
821 else psxCpu = &psxRec;
825 if (psxCpu->Init() == -1) {
826 SysClose(); return -1;
829 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
835 // remove the leading and trailing spaces in a string
836 void trim(char *str) {
840 // skip leading blanks
841 while (str[pos] <= ' ' && str[pos] > 0)
845 *(dest++) = str[pos];
849 *(dest--) = '\0'; // store the null
851 // remove trailing blanks
852 while (dest >= str && *dest <= ' ' && *dest > 0)
856 // lookup table for crc calculation
857 static unsigned short crctab[256] = {
858 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
859 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
860 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
861 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
862 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
863 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
864 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
865 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
866 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
867 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
868 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
869 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
870 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
871 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
872 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
873 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
874 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
875 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
876 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
877 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
878 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
879 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
880 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
881 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
882 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
883 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
884 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
885 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
886 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
889 u16 calcCrc(u8 *d, int len) {
893 for (i = 0; i < len; i++) {
894 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);