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.
35 char CdromId[10] = "";
36 char CdromLabel[33] = "";
38 // PSX Executable types
44 #define ISODCL(from, to) (to - from + 1)
46 struct iso_directory_record {
47 char length [ISODCL (1, 1)]; /* 711 */
48 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
49 char extent [ISODCL (3, 10)]; /* 733 */
50 char size [ISODCL (11, 18)]; /* 733 */
51 char date [ISODCL (19, 25)]; /* 7 by 711 */
52 char flags [ISODCL (26, 26)];
53 char file_unit_size [ISODCL (27, 27)]; /* 711 */
54 char interleave [ISODCL (28, 28)]; /* 711 */
55 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
56 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
60 static void mmssdd( char *b, char *p )
63 unsigned char *ub = (void *)b;
64 int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
67 m = block / 4500; // minutes
68 block = block - m * 4500; // minutes rest
69 s = block / 75; // seconds
70 d = block - s * 75; // seconds rest
72 m = ((m / 10) << 4) | m % 10;
73 s = ((s / 10) << 4) | s % 10;
74 d = ((d / 10) << 4) | d % 10;
82 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
87 if (time[1] == 60) { \
92 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
95 if (!CDR_readTrack(time)) return -1; \
96 buf = (void *)CDR_getBuffer(); \
97 if (buf == NULL) return -1; \
98 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
100 #define READDIR(_dir) \
102 memcpy(_dir, buf + 12, 2048); \
106 memcpy(_dir + 2048, buf + 12, 2048);
108 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
109 struct iso_directory_record *dir;
115 // only try to scan if a filename is given
116 if (filename == INVALID_PTR || !strlen(filename)) return -1;
120 dir = (struct iso_directory_record*) &mdir[i];
121 if (dir->length[0] == 0) {
124 i += (u8)dir->length[0];
126 if (dir->flags[0] & 0x2) { // it's a dir
127 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
128 if (filename[dir->name_len[0]] != '\\') continue;
130 filename += dir->name_len[0] + 1;
132 mmssdd(dir->extent, (char *)time);
138 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
139 mmssdd(dir->extent, (char *)time);
148 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
150 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
151 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
154 psxRegs.GPR.n.gp = gp;
155 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
156 psxRegs.GPR.n.fp = psxRegs.GPR.n.sp;
158 psxRegs.GPR.n.t0 = psxRegs.GPR.n.sp; // mimic A(43)
159 psxRegs.GPR.n.t3 = pc;
161 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
164 void BiosBootBypass() {
165 assert(psxRegs.pc == 0x80030000);
167 // skip BIOS logos and region check
168 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
169 psxRegs.pc = psxRegs.GPR.n.ra;
172 static void getFromCnf(char *buf, const char *key, u32 *val)
174 buf = strstr(buf, key);
176 buf = strchr(buf, '=');
178 *val = strtol(buf + 1, NULL, 16);
184 u32 d[sizeof(EXE_HEADER) / sizeof(u32)];
186 struct iso_directory_record *dir;
199 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
205 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
209 // skip head and sub, and go to the root directory record
210 dir = (struct iso_directory_record*) &buf[12+156];
212 mmssdd(dir->extent, (char*)time);
216 // Load SYSTEM.CNF and scan for the main executable
217 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
218 // if SYSTEM.CNF is missing, start an existing PSX.EXE
219 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
220 strcpy(exename, "PSX.EXE;1");
225 // read the SYSTEM.CNF
229 ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
230 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
231 ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
232 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
233 char *ptr = strstr((char *)buf + 12, "cdrom:");
236 while (*ptr == '\\' || *ptr == '/') ptr++;
237 strncpy(exename, ptr, 255);
240 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
242 if (GetCdromFile(mdir, time, exename) == -1)
248 getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
249 getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
250 getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
252 psxBiosCnfLoaded(cnf_tcb, cnf_event, cnf_stack);
254 // Read the EXE-Header
258 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
259 for (i = 2; i < sizeof(tmpHead.d) / sizeof(tmpHead.d[0]); i++)
260 tmpHead.d[i] = SWAP32(tmpHead.d[i]);
262 SysPrintf("manual booting '%s' pc=%x\n", exename, tmpHead.h.pc0);
263 sp = tmpHead.h.s_addr;
266 SetBootRegs(tmpHead.h.pc0, tmpHead.h.gp0, sp);
268 // Read the rest of the main executable
269 for (t_addr = tmpHead.h.t_addr, t_size = tmpHead.h.t_size; t_size & ~2047; ) {
270 void *ptr = (void *)PSXM(t_addr);
275 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
281 psxCpu->Clear(tmpHead.h.t_addr, tmpHead.h.t_size / 4);
285 psxBiosCheckExe(tmpHead.h.t_addr, tmpHead.h.t_size);
290 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
291 struct iso_directory_record *dir;
299 if (filename == INVALID_PTR)
303 if ((p2 = strchr(p1, ':')))
307 snprintf(exename, sizeof(exename), "%s", p1);
309 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
313 // skip head and sub, and go to the root directory record
314 dir = (struct iso_directory_record *)&buf[12 + 156];
316 mmssdd(dir->extent, (char*)time);
320 if (GetCdromFile(mdir, time, exename) == -1) return -1;
324 memcpy(head, buf + 12, sizeof(EXE_HEADER));
325 size = SWAP32(head->t_size);
326 addr = SWAP32(head->t_addr);
328 psxCpu->Clear(addr, size / 4);
331 while (size & ~2047) {
336 if (mem != INVALID_PTR)
337 memcpy(mem, buf + 12, 2048);
347 struct iso_directory_record *dir;
348 unsigned char time[4];
350 unsigned char mdir[4096];
358 time[2] = itob(0x10);
362 memset(CdromLabel, 0, sizeof(CdromLabel));
363 memset(CdromId, 0, sizeof(CdromId));
364 memset(exename, 0, sizeof(exename));
366 strncpy(CdromLabel, buf + 52, 32);
368 // skip head and sub, and go to the root directory record
369 dir = (struct iso_directory_record *)&buf[12 + 156];
371 mmssdd(dir->extent, (char *)time);
375 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
378 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
379 if (GetCdromFile(mdir, time, exename) == -1) {
380 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
381 if (GetCdromFile(mdir, time, exename) == -1) {
382 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
385 while (*ptr == '\\' || *ptr == '/') ptr++;
386 strncpy(exename, ptr, 255);
389 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
391 if (GetCdromFile(mdir, time, exename) == -1)
392 return -1; // main executable not found
397 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
398 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
400 size_t i, len = strlen(exename) - offset;
401 for (i = 0; i < len; i++)
402 exename[i] = exename[i + offset];
405 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
406 strcpy(exename, "PSX.EXE;1");
407 strcpy(CdromId, "SLUS99999");
409 return -1; // SYSTEM.CNF and PSX.EXE not found
411 if (CdromId[0] == '\0') {
412 len = strlen(exename);
414 for (i = 0; i < len; ++i) {
415 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
417 if (isalnum(exename[i]))
418 CdromId[c++] = exename[i];
422 if (CdromId[0] == '\0')
423 strcpy(CdromId, "SLUS99999");
425 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
427 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
428 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
429 !strncmp(CdromId, "DTLS3035", 8) ||
430 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
431 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
432 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
433 Config.PsxType = PSX_TYPE_PAL; // pal
434 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
437 if (CdromLabel[0] == ' ') {
438 strncpy(CdromLabel, CdromId, 9);
440 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
441 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
442 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
451 static int PSXGetFileType(FILE *f) {
452 unsigned long current;
458 fseek(f, 0L, SEEK_SET);
459 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
462 fseek(f, current, SEEK_SET);
464 exe_hdr = (EXE_HEADER *)mybuf;
465 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
468 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
471 coff_hdr = (FILHDR *)mybuf;
472 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
479 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
484 // temporary pandora workaround..
486 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
491 tmp = malloc(size * nmemb);
493 ret = fread(tmp, size, nmemb, stream);
494 memcpy(ptr, tmp, size * nmemb);
500 int Load(const char *ExePath) {
506 u32 section_address, section_size;
509 strcpy(CdromId, "SLUS99999");
510 strcpy(CdromLabel, "SLUS_999.99");
512 tmpFile = fopen(ExePath, "rb");
513 if (tmpFile == NULL) {
514 SysPrintf(_("Error opening file: %s.\n"), ExePath);
517 type = PSXGetFileType(tmpFile);
520 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
522 section_address = SWAP32(tmpHead.t_addr);
523 section_size = SWAP32(tmpHead.t_size);
524 mem = PSXM(section_address);
525 if (mem != INVALID_PTR) {
526 fseek(tmpFile, 0x800, SEEK_SET);
527 fread_to_ram(mem, section_size, 1, tmpFile);
528 psxCpu->Clear(section_address, section_size / 4);
530 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
531 SWAP32(tmpHead.s_addr));
535 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
537 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
540 case 1: /* Section loading */
541 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
543 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
545 section_address = SWAPu32(section_address);
546 section_size = SWAPu32(section_size);
548 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
550 mem = PSXM(section_address);
551 if (mem != INVALID_PTR) {
552 fread_to_ram(mem, section_size, 1, tmpFile);
553 psxCpu->Clear(section_address, section_size / 4);
556 case 3: /* register loading (PC only?) */
557 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
558 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
560 psxRegs.pc = SWAPu32(psxRegs.pc);
562 case 0: /* End of file */
565 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
569 } while (opcode != 0 && retval == 0);
572 SysPrintf(_("COFF files not supported.\n"));
576 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
577 SysPrintf(_("(did you forget -cdfile ?)\n"));
585 CdromLabel[0] = '\0';
594 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
602 static void *zlib_open(const char *name, const char *mode)
604 return gzopen(name, mode);
607 static int zlib_read(void *file, void *buf, u32 len)
609 return gzread(file, buf, len);
612 static int zlib_write(void *file, const void *buf, u32 len)
614 return gzwrite(file, buf, len);
617 static long zlib_seek(void *file, long offs, int whence)
619 return gzseek(file, offs, whence);
622 static void zlib_close(void *file)
627 struct PcsxSaveFuncs SaveFuncs = {
628 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
631 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
633 // Savestate Versioning!
634 // If you make changes to the savestate version, please increment the value below.
635 static const u32 SaveVersion = 0x8b410006;
637 int SaveState(const char *file) {
639 GPUFreeze_t *gpufP = NULL;
640 SPUFreezeHdr_t spufH;
641 SPUFreeze_t *spufP = NULL;
642 unsigned char *pMem = NULL;
646 f = SaveFuncs.open(file, "wb");
647 if (f == NULL) return -1;
649 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
651 SaveFuncs.write(f, (void *)PcsxHeader, 32);
652 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
653 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
655 pMem = (unsigned char *)malloc(128 * 96 * 3);
656 if (pMem == NULL) goto cleanup;
657 GPU_getScreenPic(pMem);
658 SaveFuncs.write(f, pMem, 128 * 96 * 3);
664 SaveFuncs.write(f, psxM, 0x00200000);
665 SaveFuncs.write(f, psxR, 0x00080000);
666 SaveFuncs.write(f, psxH, 0x00010000);
667 // only partial save of psxRegisters to maintain savestate compat
668 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
671 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
672 if (gpufP == NULL) goto cleanup;
673 gpufP->ulFreezeVersion = 1;
674 GPU_freeze(1, gpufP);
675 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
676 free(gpufP); gpufP = NULL;
679 SPU_freeze(2, (SPUFreeze_t *)&spufH, psxRegs.cycle);
680 Size = spufH.Size; SaveFuncs.write(f, &Size, 4);
681 spufP = (SPUFreeze_t *) malloc(Size);
682 if (spufP == NULL) goto cleanup;
683 SPU_freeze(1, spufP, psxRegs.cycle);
684 SaveFuncs.write(f, spufP, Size);
685 free(spufP); spufP = NULL;
692 new_dyna_freeze(f, 1);
701 int LoadState(const char *file) {
702 u32 biosBranchCheckOld = psxRegs.biosBranchCheck;
704 GPUFreeze_t *gpufP = NULL;
705 SPUFreeze_t *spufP = NULL;
712 f = SaveFuncs.open(file, "rb");
713 if (f == NULL) return -1;
715 SaveFuncs.read(f, header, sizeof(header));
716 SaveFuncs.read(f, &version, sizeof(u32));
717 SaveFuncs.read(f, &hle, sizeof(boolean));
719 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
720 SysPrintf("incompatible savestate version %x\n", version);
728 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
729 SaveFuncs.read(f, psxM, 0x00200000);
730 SaveFuncs.read(f, psxR, 0x00080000);
731 SaveFuncs.read(f, psxH, 0x00010000);
732 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
733 psxRegs.gteBusyCycle = psxRegs.cycle;
734 psxRegs.biosBranchCheck = ~0;
736 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
742 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
743 if (gpufP == NULL) goto cleanup;
744 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
745 GPU_freeze(0, gpufP);
747 if (HW_GPU_STATUS == 0)
748 HW_GPU_STATUS = SWAP32(GPU_readStatus());
751 SaveFuncs.read(f, &Size, 4);
752 spufP = (SPUFreeze_t *)malloc(Size);
753 if (spufP == NULL) goto cleanup;
754 SaveFuncs.read(f, spufP, Size);
755 SPU_freeze(0, spufP, psxRegs.cycle);
763 new_dyna_freeze(f, 0);
767 psxBiosCheckExe(biosBranchCheckOld, 0x60);
775 int CheckState(const char *file) {
781 f = SaveFuncs.open(file, "rb");
782 if (f == NULL) return -1;
784 SaveFuncs.read(f, header, sizeof(header));
785 SaveFuncs.read(f, &version, sizeof(u32));
786 SaveFuncs.read(f, &hle, sizeof(boolean));
790 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
796 // NET Function Helpers
799 if (NET_recvData == NULL || NET_sendData == NULL)
803 boolean SpuIrq_old = 0;
804 boolean RCntFix_old = 0;
805 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
806 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
807 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
808 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
809 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
810 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
818 if (NET_recvData == NULL || NET_sendData == NULL)
822 boolean SpuIrq_old = 0;
823 boolean RCntFix_old = 0;
824 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
825 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
826 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
827 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
828 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
831 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
832 if (tmp != Config.Cpu) {
835 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
836 else psxCpu = &psxRec;
840 if (psxCpu->Init() == -1) {
841 SysClose(); return -1;
844 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
850 // remove the leading and trailing spaces in a string
851 void trim(char *str) {
855 // skip leading blanks
856 while (str[pos] <= ' ' && str[pos] > 0)
860 *(dest++) = str[pos];
864 *(dest--) = '\0'; // store the null
866 // remove trailing blanks
867 while (dest >= str && *dest <= ' ' && *dest > 0)
871 // lookup table for crc calculation
872 static unsigned short crctab[256] = {
873 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
874 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
875 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
876 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
877 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
878 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
879 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
880 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
881 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
882 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
883 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
884 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
885 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
886 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
887 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
888 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
889 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
890 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
891 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
892 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
893 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
894 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
895 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
896 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
897 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
898 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
899 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
900 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
901 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
904 u16 calcCrc(u8 *d, int len) {
908 for (i = 0; i < len; i++) {
909 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);