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;
741 psxRegs.gpuIdleAfter = psxRegs.cycle - 1;
742 HW_GPU_STATUS &= SWAP32(~PSXGPU_nBUSY);
744 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
750 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
751 if (gpufP == NULL) goto cleanup;
752 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
753 GPU_freeze(0, gpufP);
758 SaveFuncs.read(f, &Size, 4);
759 spufP = (SPUFreeze_t *)malloc(Size);
760 if (spufP == NULL) goto cleanup;
761 SaveFuncs.read(f, spufP, Size);
762 SPU_freeze(0, spufP, psxRegs.cycle);
770 new_dyna_freeze(f, 0);
775 psxBiosCheckExe(biosBranchCheckOld, 0x60, 1);
783 int CheckState(const char *file) {
789 f = SaveFuncs.open(file, "rb");
790 if (f == NULL) return -1;
792 SaveFuncs.read(f, header, sizeof(header));
793 SaveFuncs.read(f, &version, sizeof(u32));
794 SaveFuncs.read(f, &hle, sizeof(boolean));
798 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
804 // NET Function Helpers
807 if (NET_recvData == NULL || NET_sendData == NULL)
811 boolean SpuIrq_old = 0;
812 boolean RCntFix_old = 0;
813 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
814 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
815 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
816 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
817 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
818 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
826 if (NET_recvData == NULL || NET_sendData == NULL)
830 boolean SpuIrq_old = 0;
831 boolean RCntFix_old = 0;
832 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
833 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
834 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
835 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
836 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
839 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
840 if (tmp != Config.Cpu) {
843 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
844 else psxCpu = &psxRec;
848 if (psxCpu->Init() == -1) {
849 SysClose(); return -1;
852 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
858 // remove the leading and trailing spaces in a string
859 void trim(char *str) {
863 // skip leading blanks
864 while (str[pos] <= ' ' && str[pos] > 0)
868 *(dest++) = str[pos];
872 *(dest--) = '\0'; // store the null
874 // remove trailing blanks
875 while (dest >= str && *dest <= ' ' && *dest > 0)
879 // lookup table for crc calculation
880 static unsigned short crctab[256] = {
881 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
882 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
883 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
884 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
885 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
886 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
887 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
888 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
889 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
890 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
891 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
892 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
893 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
894 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
895 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
896 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
897 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
898 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
899 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
900 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
901 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
902 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
903 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
904 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
905 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
906 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
907 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
908 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
909 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
912 u16 calcCrc(u8 *d, int len) {
916 for (i = 0; i < len; i++) {
917 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);