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.
36 char CdromId[10] = "";
37 char CdromLabel[33] = "";
39 // PSX Executable types
45 #define ISODCL(from, to) (to - from + 1)
47 struct iso_directory_record {
48 char length [ISODCL (1, 1)]; /* 711 */
49 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
50 char extent [ISODCL (3, 10)]; /* 733 */
51 char size [ISODCL (11, 18)]; /* 733 */
52 char date [ISODCL (19, 25)]; /* 7 by 711 */
53 char flags [ISODCL (26, 26)];
54 char file_unit_size [ISODCL (27, 27)]; /* 711 */
55 char interleave [ISODCL (28, 28)]; /* 711 */
56 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
57 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
61 static void mmssdd( char *b, char *p )
64 unsigned char *ub = (void *)b;
65 int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
68 m = block / 4500; // minutes
69 block = block - m * 4500; // minutes rest
70 s = block / 75; // seconds
71 d = block - s * 75; // seconds rest
73 m = ((m / 10) << 4) | m % 10;
74 s = ((s / 10) << 4) | s % 10;
75 d = ((d / 10) << 4) | d % 10;
83 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
88 if (time[1] == 60) { \
93 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
96 if (!CDR_readTrack(time)) return -1; \
97 buf = (void *)CDR_getBuffer(); \
98 if (buf == NULL) return -1; \
99 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
101 #define READDIR(_dir) \
103 memcpy(_dir, buf + 12, 2048); \
107 memcpy(_dir + 2048, buf + 12, 2048);
109 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
110 struct iso_directory_record *dir;
116 // only try to scan if a filename is given
117 if (filename == INVALID_PTR || !strlen(filename)) return -1;
121 dir = (struct iso_directory_record*) &mdir[i];
122 if (dir->length[0] == 0) {
125 i += (u8)dir->length[0];
127 if (dir->flags[0] & 0x2) { // it's a dir
128 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
129 if (filename[dir->name_len[0]] != '\\') continue;
131 filename += dir->name_len[0] + 1;
133 mmssdd(dir->extent, (char *)time);
139 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
140 mmssdd(dir->extent, (char *)time);
149 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
151 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
152 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
155 psxRegs.GPR.n.gp = gp;
156 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
157 psxRegs.GPR.n.fp = psxRegs.GPR.n.sp;
159 psxRegs.GPR.n.t0 = psxRegs.GPR.n.sp; // mimic A(43)
160 psxRegs.GPR.n.t3 = pc;
162 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
165 void BiosBootBypass() {
166 assert(psxRegs.pc == 0x80030000);
168 // skip BIOS logos and region check
169 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
170 psxRegs.pc = psxRegs.GPR.n.ra;
173 static void getFromCnf(char *buf, const char *key, u32 *val)
175 buf = strstr(buf, key);
177 buf = strchr(buf, '=');
181 v = strtoul(buf + 1, NULL, 16);
190 u32 d[sizeof(EXE_HEADER) / sizeof(u32)];
192 struct iso_directory_record *dir;
205 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
211 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
215 // skip head and sub, and go to the root directory record
216 dir = (struct iso_directory_record*) &buf[12+156];
218 mmssdd(dir->extent, (char*)time);
222 // Load SYSTEM.CNF and scan for the main executable
223 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
224 // if SYSTEM.CNF is missing, start an existing PSX.EXE
225 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
226 strcpy(exename, "PSX.EXE;1");
231 // read the SYSTEM.CNF
235 ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
236 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
237 ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
238 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
239 char *ptr = strstr((char *)buf + 12, "cdrom:");
242 while (*ptr == '\\' || *ptr == '/') ptr++;
243 strncpy(exename, ptr, 255);
246 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
248 if (GetCdromFile(mdir, time, exename) == -1)
254 getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
255 getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
256 getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
258 psxBiosCnfLoaded(cnf_tcb, cnf_event, cnf_stack);
260 // Read the EXE-Header
264 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
265 for (i = 2; i < sizeof(tmpHead.d) / sizeof(tmpHead.d[0]); i++)
266 tmpHead.d[i] = SWAP32(tmpHead.d[i]);
268 SysPrintf("manual booting '%s' pc=%x\n", exename, tmpHead.h.pc0);
269 sp = tmpHead.h.s_addr;
272 SetBootRegs(tmpHead.h.pc0, tmpHead.h.gp0, sp);
274 // Read the rest of the main executable
275 for (t_addr = tmpHead.h.t_addr, t_size = tmpHead.h.t_size; t_size & ~2047; ) {
276 void *ptr = (void *)PSXM(t_addr);
281 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
287 psxCpu->Clear(tmpHead.h.t_addr, tmpHead.h.t_size / 4);
291 psxBiosCheckExe(tmpHead.h.t_addr, tmpHead.h.t_size, 0);
296 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
297 struct iso_directory_record *dir;
305 if (filename == INVALID_PTR)
309 if ((p2 = strchr(p1, ':')))
313 snprintf(exename, sizeof(exename), "%s", p1);
315 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
319 // skip head and sub, and go to the root directory record
320 dir = (struct iso_directory_record *)&buf[12 + 156];
322 mmssdd(dir->extent, (char*)time);
326 if (GetCdromFile(mdir, time, exename) == -1) return -1;
330 memcpy(head, buf + 12, sizeof(EXE_HEADER));
331 size = SWAP32(head->t_size);
332 addr = SWAP32(head->t_addr);
334 psxCpu->Clear(addr, size / 4);
337 while (size & ~2047) {
342 if (mem != INVALID_PTR)
343 memcpy(mem, buf + 12, 2048);
353 struct iso_directory_record *dir;
354 unsigned char time[4];
356 unsigned char mdir[4096];
364 time[2] = itob(0x10);
368 memset(CdromLabel, 0, sizeof(CdromLabel));
369 memset(CdromId, 0, sizeof(CdromId));
370 memset(exename, 0, sizeof(exename));
372 strncpy(CdromLabel, buf + 52, 32);
374 // skip head and sub, and go to the root directory record
375 dir = (struct iso_directory_record *)&buf[12 + 156];
377 mmssdd(dir->extent, (char *)time);
381 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
384 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
385 if (GetCdromFile(mdir, time, exename) == -1) {
386 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
387 if (GetCdromFile(mdir, time, exename) == -1) {
388 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
391 while (*ptr == '\\' || *ptr == '/') ptr++;
392 strncpy(exename, ptr, 255);
395 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
397 if (GetCdromFile(mdir, time, exename) == -1)
398 return -1; // main executable not found
403 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
404 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
406 size_t i, len = strlen(exename) - offset;
407 for (i = 0; i < len; i++)
408 exename[i] = exename[i + offset];
411 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
412 strcpy(exename, "PSX.EXE;1");
413 strcpy(CdromId, "SLUS99999");
415 return -1; // SYSTEM.CNF and PSX.EXE not found
417 if (CdromId[0] == '\0') {
418 len = strlen(exename);
420 for (i = 0; i < len; ++i) {
421 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
423 if (isalnum(exename[i]))
424 CdromId[c++] = exename[i];
428 if (CdromId[0] == '\0')
429 strcpy(CdromId, "SLUS99999");
431 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
433 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
434 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
435 !strncmp(CdromId, "DTLS3035", 8) ||
436 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
437 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
438 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
439 Config.PsxType = PSX_TYPE_PAL; // pal
440 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
443 if (CdromLabel[0] == ' ') {
444 strncpy(CdromLabel, CdromId, 9);
446 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
447 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
448 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
457 static int PSXGetFileType(FILE *f) {
458 unsigned long current;
464 fseek(f, 0L, SEEK_SET);
465 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
468 fseek(f, current, SEEK_SET);
470 exe_hdr = (EXE_HEADER *)mybuf;
471 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
474 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
477 coff_hdr = (FILHDR *)mybuf;
478 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
485 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
490 // temporary pandora workaround..
492 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
497 tmp = malloc(size * nmemb);
499 ret = fread(tmp, size, nmemb, stream);
500 memcpy(ptr, tmp, size * nmemb);
506 int Load(const char *ExePath) {
512 u32 section_address, section_size;
515 strcpy(CdromId, "SLUS99999");
516 strcpy(CdromLabel, "SLUS_999.99");
518 tmpFile = fopen(ExePath, "rb");
519 if (tmpFile == NULL) {
520 SysPrintf(_("Error opening file: %s.\n"), ExePath);
523 type = PSXGetFileType(tmpFile);
526 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
528 section_address = SWAP32(tmpHead.t_addr);
529 section_size = SWAP32(tmpHead.t_size);
530 mem = PSXM(section_address);
531 if (mem != INVALID_PTR) {
532 fseek(tmpFile, 0x800, SEEK_SET);
533 fread_to_ram(mem, section_size, 1, tmpFile);
534 psxCpu->Clear(section_address, section_size / 4);
536 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
537 SWAP32(tmpHead.s_addr));
541 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
543 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
546 case 1: /* Section loading */
547 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
549 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
551 section_address = SWAPu32(section_address);
552 section_size = SWAPu32(section_size);
554 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
556 mem = PSXM(section_address);
557 if (mem != INVALID_PTR) {
558 fread_to_ram(mem, section_size, 1, tmpFile);
559 psxCpu->Clear(section_address, section_size / 4);
562 case 3: /* register loading (PC only?) */
563 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
564 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
566 psxRegs.pc = SWAPu32(psxRegs.pc);
568 case 0: /* End of file */
571 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
575 } while (opcode != 0 && retval == 0);
578 SysPrintf(_("COFF files not supported.\n"));
582 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
583 SysPrintf(_("(did you forget -cdfile ?)\n"));
591 CdromLabel[0] = '\0';
600 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
608 static void *zlib_open(const char *name, const char *mode)
610 return gzopen(name, mode);
613 static int zlib_read(void *file, void *buf, u32 len)
615 return gzread(file, buf, len);
618 static int zlib_write(void *file, const void *buf, u32 len)
620 return gzwrite(file, buf, len);
623 static long zlib_seek(void *file, long offs, int whence)
625 return gzseek(file, offs, whence);
628 static void zlib_close(void *file)
633 struct PcsxSaveFuncs SaveFuncs = {
634 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
637 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
639 // Savestate Versioning!
640 // If you make changes to the savestate version, please increment the value below.
641 static const u32 SaveVersion = 0x8b410006;
643 int SaveState(const char *file) {
645 GPUFreeze_t *gpufP = NULL;
646 SPUFreezeHdr_t spufH;
647 SPUFreeze_t *spufP = NULL;
648 unsigned char *pMem = NULL;
652 f = SaveFuncs.open(file, "wb");
653 if (f == NULL) return -1;
655 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
657 SaveFuncs.write(f, (void *)PcsxHeader, 32);
658 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
659 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
661 pMem = (unsigned char *)malloc(128 * 96 * 3);
662 if (pMem == NULL) goto cleanup;
663 GPU_getScreenPic(pMem);
664 SaveFuncs.write(f, pMem, 128 * 96 * 3);
670 SaveFuncs.write(f, psxM, 0x00200000);
671 SaveFuncs.write(f, psxR, 0x00080000);
672 SaveFuncs.write(f, psxH, 0x00010000);
673 // only partial save of psxRegisters to maintain savestate compat
674 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
677 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
678 if (gpufP == NULL) goto cleanup;
679 gpufP->ulFreezeVersion = 1;
680 GPU_freeze(1, gpufP);
681 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
682 free(gpufP); gpufP = NULL;
685 SPU_freeze(2, (SPUFreeze_t *)&spufH, psxRegs.cycle);
686 Size = spufH.Size; SaveFuncs.write(f, &Size, 4);
687 spufP = (SPUFreeze_t *) malloc(Size);
688 if (spufP == NULL) goto cleanup;
689 SPU_freeze(1, spufP, psxRegs.cycle);
690 SaveFuncs.write(f, spufP, Size);
691 free(spufP); spufP = NULL;
698 new_dyna_freeze(f, 1);
707 int LoadState(const char *file) {
708 u32 biosBranchCheckOld = psxRegs.biosBranchCheck;
710 GPUFreeze_t *gpufP = NULL;
711 SPUFreeze_t *spufP = NULL;
718 f = SaveFuncs.open(file, "rb");
719 if (f == NULL) return -1;
721 SaveFuncs.read(f, header, sizeof(header));
722 SaveFuncs.read(f, &version, sizeof(u32));
723 SaveFuncs.read(f, &hle, sizeof(boolean));
725 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
726 SysPrintf("incompatible savestate version %x\n", version);
734 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
735 SaveFuncs.read(f, psxM, 0x00200000);
736 SaveFuncs.read(f, psxR, 0x00080000);
737 SaveFuncs.read(f, psxH, 0x00010000);
738 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
739 psxRegs.gteBusyCycle = psxRegs.cycle;
740 psxRegs.biosBranchCheck = ~0;
742 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
748 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
749 if (gpufP == NULL) goto cleanup;
750 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
751 GPU_freeze(0, gpufP);
753 if (HW_GPU_STATUS == 0)
754 HW_GPU_STATUS = SWAP32(GPU_readStatus());
757 SaveFuncs.read(f, &Size, 4);
758 spufP = (SPUFreeze_t *)malloc(Size);
759 if (spufP == NULL) goto cleanup;
760 SaveFuncs.read(f, spufP, Size);
761 SPU_freeze(0, spufP, psxRegs.cycle);
769 new_dyna_freeze(f, 0);
774 psxBiosCheckExe(biosBranchCheckOld, 0x60, 1);
782 int CheckState(const char *file) {
788 f = SaveFuncs.open(file, "rb");
789 if (f == NULL) return -1;
791 SaveFuncs.read(f, header, sizeof(header));
792 SaveFuncs.read(f, &version, sizeof(u32));
793 SaveFuncs.read(f, &hle, sizeof(boolean));
797 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
803 // NET Function Helpers
806 if (NET_recvData == NULL || NET_sendData == NULL)
810 boolean SpuIrq_old = 0;
811 boolean RCntFix_old = 0;
812 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
813 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
814 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
815 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
816 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
817 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
825 if (NET_recvData == NULL || NET_sendData == NULL)
829 boolean SpuIrq_old = 0;
830 boolean RCntFix_old = 0;
831 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
832 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
833 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
834 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
835 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
838 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
839 if (tmp != Config.Cpu) {
842 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
843 else psxCpu = &psxRec;
847 if (psxCpu->Init() == -1) {
848 SysClose(); return -1;
851 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
857 // remove the leading and trailing spaces in a string
858 void trim(char *str) {
862 // skip leading blanks
863 while (str[pos] <= ' ' && str[pos] > 0)
867 *(dest++) = str[pos];
871 *(dest--) = '\0'; // store the null
873 // remove trailing blanks
874 while (dest >= str && *dest <= ' ' && *dest > 0)
878 // lookup table for crc calculation
879 static unsigned short crctab[256] = {
880 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
881 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
882 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
883 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
884 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
885 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
886 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
887 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
888 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
889 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
890 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
891 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
892 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
893 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
894 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
895 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
896 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
897 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
898 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
899 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
900 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
901 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
902 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
903 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
904 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
905 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
906 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
907 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
908 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
911 u16 calcCrc(u8 *d, int len) {
915 for (i = 0; i < len; i++) {
916 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);