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 (!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);
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) {
632 SPUFreezeHdr_t *spufH;
637 f = SaveFuncs.open(file, "wb");
638 if (f == NULL) return -1;
640 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
642 SaveFuncs.write(f, (void *)PcsxHeader, 32);
643 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
644 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
646 pMem = (unsigned char *)malloc(128 * 96 * 3);
647 if (pMem == NULL) return -1;
648 GPU_getScreenPic(pMem);
649 SaveFuncs.write(f, pMem, 128 * 96 * 3);
655 SaveFuncs.write(f, psxM, 0x00200000);
656 SaveFuncs.write(f, psxR, 0x00080000);
657 SaveFuncs.write(f, psxH, 0x00010000);
658 // only partial save of psxRegisters to maintain savestate compat
659 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
662 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
663 gpufP->ulFreezeVersion = 1;
664 GPU_freeze(1, gpufP);
665 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
669 spufH = malloc(sizeof(*spufH));
670 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
671 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
673 spufP = (SPUFreeze_t *) malloc(Size);
674 SPU_freeze(1, spufP, psxRegs.cycle);
675 SaveFuncs.write(f, spufP, Size);
683 new_dyna_freeze(f, 1);
690 int LoadState(const char *file) {
699 f = SaveFuncs.open(file, "rb");
700 if (f == NULL) return -1;
702 SaveFuncs.read(f, header, sizeof(header));
703 SaveFuncs.read(f, &version, sizeof(u32));
704 SaveFuncs.read(f, &hle, sizeof(boolean));
706 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
715 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
716 SaveFuncs.read(f, psxM, 0x00200000);
717 SaveFuncs.read(f, psxR, 0x00080000);
718 SaveFuncs.read(f, psxH, 0x00010000);
719 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
720 psxRegs.gteBusyCycle = psxRegs.cycle;
722 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
728 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
729 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
730 GPU_freeze(0, gpufP);
732 if (HW_GPU_STATUS == 0)
733 HW_GPU_STATUS = SWAP32(GPU_readStatus());
736 SaveFuncs.read(f, &Size, 4);
737 spufP = (SPUFreeze_t *)malloc(Size);
738 SaveFuncs.read(f, spufP, Size);
739 SPU_freeze(0, spufP, psxRegs.cycle);
747 new_dyna_freeze(f, 0);
754 int CheckState(const char *file) {
760 f = SaveFuncs.open(file, "rb");
761 if (f == NULL) return -1;
763 SaveFuncs.read(f, header, sizeof(header));
764 SaveFuncs.read(f, &version, sizeof(u32));
765 SaveFuncs.read(f, &hle, sizeof(boolean));
769 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
775 // NET Function Helpers
778 if (NET_recvData == NULL || NET_sendData == NULL)
782 boolean SpuIrq_old = 0;
783 boolean RCntFix_old = 0;
784 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
785 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
786 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
787 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
788 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
789 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
797 if (NET_recvData == NULL || NET_sendData == NULL)
801 boolean SpuIrq_old = 0;
802 boolean RCntFix_old = 0;
803 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
804 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
805 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
806 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
807 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
810 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
811 if (tmp != Config.Cpu) {
814 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
815 else psxCpu = &psxRec;
819 if (psxCpu->Init() == -1) {
820 SysClose(); return -1;
823 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
829 // remove the leading and trailing spaces in a string
830 void trim(char *str) {
834 // skip leading blanks
835 while (str[pos] <= ' ' && str[pos] > 0)
839 *(dest++) = str[pos];
843 *(dest--) = '\0'; // store the null
845 // remove trailing blanks
846 while (dest >= str && *dest <= ' ' && *dest > 0)
850 // lookup table for crc calculation
851 static unsigned short crctab[256] = {
852 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
853 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
854 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
855 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
856 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
857 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
858 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
859 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
860 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
861 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
862 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
863 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
864 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
865 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
866 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
867 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
868 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
869 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
870 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
871 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
872 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
873 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
874 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
875 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
876 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
877 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
878 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
879 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
880 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
883 u16 calcCrc(u8 *d, int len) {
887 for (i = 0; i < len; i++) {
888 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);