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 const unsigned int gpu_ctl_def[] = {
148 0x00000000, 0x01000000, 0x03000000, 0x04000000,
149 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
152 static const unsigned int gpu_data_def[] = {
153 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
154 0xe5001000, 0xe6000000,
155 0x02000000, 0x00000000, 0x01ff03ff,
158 void BiosLikeGPUSetup()
162 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
163 GPU_writeStatus(gpu_ctl_def[i]);
165 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
166 GPU_writeData(gpu_data_def[i]);
168 HW_GPU_STATUS |= SWAP32(PSXGPU_nBUSY);
171 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
173 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
174 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
177 psxRegs.GPR.n.gp = gp;
178 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
180 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
183 void BiosBootBypass() {
184 assert(psxRegs.pc == 0x80030000);
186 // skip BIOS logos and region check
187 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
188 psxRegs.pc = psxRegs.GPR.n.ra;
191 static void getFromCnf(char *buf, const char *key, u32 *val)
193 buf = strstr(buf, key);
195 buf = strchr(buf, '=');
197 *val = strtol(buf + 1, NULL, 16);
202 struct iso_directory_record *dir;
213 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
219 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
223 // skip head and sub, and go to the root directory record
224 dir = (struct iso_directory_record*) &buf[12+156];
226 mmssdd(dir->extent, (char*)time);
230 // Load SYSTEM.CNF and scan for the main executable
231 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
232 // if SYSTEM.CNF is missing, start an existing PSX.EXE
233 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
234 strcpy(exename, "PSX.EXE;1");
239 // read the SYSTEM.CNF
243 ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
244 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
245 ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
246 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
247 char *ptr = strstr((char *)buf + 12, "cdrom:");
250 while (*ptr == '\\' || *ptr == '/') ptr++;
251 strncpy(exename, ptr, 255);
254 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
256 if (GetCdromFile(mdir, time, exename) == -1)
262 getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
263 getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
264 getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
266 psxBiosCnfLoaded(cnf_tcb, cnf_event);
268 // Read the EXE-Header
272 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
274 SysPrintf("manual booting '%s'\n", exename);
275 sp = SWAP32(tmpHead.s_addr);
278 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), sp);
280 tmpHead.t_size = SWAP32(tmpHead.t_size);
281 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
283 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
286 // Read the rest of the main executable
287 while (tmpHead.t_size & ~2047) {
288 void *ptr = (void *)PSXM(tmpHead.t_addr);
293 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
295 tmpHead.t_size -= 2048;
296 tmpHead.t_addr += 2048;
302 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
303 struct iso_directory_record *dir;
312 if ((p2 = strchr(p1, ':')))
316 snprintf(exename, sizeof(exename), "%s", p1);
318 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
322 // skip head and sub, and go to the root directory record
323 dir = (struct iso_directory_record *)&buf[12 + 156];
325 mmssdd(dir->extent, (char*)time);
329 if (GetCdromFile(mdir, time, exename) == -1) return -1;
333 memcpy(head, buf + 12, sizeof(EXE_HEADER));
337 psxCpu->Clear(addr, size / 4);
340 while (size & ~2047) {
345 if (mem != INVALID_PTR)
346 memcpy(mem, buf + 12, 2048);
356 struct iso_directory_record *dir;
357 unsigned char time[4];
359 unsigned char mdir[4096];
367 time[2] = itob(0x10);
371 memset(CdromLabel, 0, sizeof(CdromLabel));
372 memset(CdromId, 0, sizeof(CdromId));
373 memset(exename, 0, sizeof(exename));
375 strncpy(CdromLabel, buf + 52, 32);
377 // skip head and sub, and go to the root directory record
378 dir = (struct iso_directory_record *)&buf[12 + 156];
380 mmssdd(dir->extent, (char *)time);
384 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
387 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
388 if (GetCdromFile(mdir, time, exename) == -1) {
389 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
390 if (GetCdromFile(mdir, time, exename) == -1) {
391 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
394 while (*ptr == '\\' || *ptr == '/') ptr++;
395 strncpy(exename, ptr, 255);
398 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
400 if (GetCdromFile(mdir, time, exename) == -1)
401 return -1; // main executable not found
406 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
407 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
409 size_t i, len = strlen(exename) - offset;
410 for (i = 0; i < len; i++)
411 exename[i] = exename[i + offset];
414 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
415 strcpy(exename, "PSX.EXE;1");
416 strcpy(CdromId, "SLUS99999");
418 return -1; // SYSTEM.CNF and PSX.EXE not found
420 if (CdromId[0] == '\0') {
421 len = strlen(exename);
423 for (i = 0; i < len; ++i) {
424 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
426 if (isalnum(exename[i]))
427 CdromId[c++] = exename[i];
431 if (CdromId[0] == '\0')
432 strcpy(CdromId, "SLUS99999");
434 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
436 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
437 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
438 !strncmp(CdromId, "DTLS3035", 8) ||
439 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
440 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
441 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
442 Config.PsxType = PSX_TYPE_PAL; // pal
443 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
446 if (CdromLabel[0] == ' ') {
447 strncpy(CdromLabel, CdromId, 9);
449 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
450 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
451 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
460 static int PSXGetFileType(FILE *f) {
461 unsigned long current;
467 fseek(f, 0L, SEEK_SET);
468 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
471 fseek(f, current, SEEK_SET);
473 exe_hdr = (EXE_HEADER *)mybuf;
474 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
477 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
480 coff_hdr = (FILHDR *)mybuf;
481 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
488 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
493 // temporary pandora workaround..
495 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
500 tmp = malloc(size * nmemb);
502 ret = fread(tmp, size, nmemb, stream);
503 memcpy(ptr, tmp, size * nmemb);
509 int Load(const char *ExePath) {
515 u32 section_address, section_size;
518 strcpy(CdromId, "SLUS99999");
519 strcpy(CdromLabel, "SLUS_999.99");
521 tmpFile = fopen(ExePath, "rb");
522 if (tmpFile == NULL) {
523 SysPrintf(_("Error opening file: %s.\n"), ExePath);
526 type = PSXGetFileType(tmpFile);
529 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
531 section_address = SWAP32(tmpHead.t_addr);
532 section_size = SWAP32(tmpHead.t_size);
533 mem = PSXM(section_address);
534 if (mem != INVALID_PTR) {
535 fseek(tmpFile, 0x800, SEEK_SET);
536 fread_to_ram(mem, section_size, 1, tmpFile);
537 psxCpu->Clear(section_address, section_size / 4);
539 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
540 SWAP32(tmpHead.s_addr));
544 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
546 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
549 case 1: /* Section loading */
550 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
552 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
554 section_address = SWAPu32(section_address);
555 section_size = SWAPu32(section_size);
557 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
559 mem = PSXM(section_address);
560 if (mem != INVALID_PTR) {
561 fread_to_ram(mem, section_size, 1, tmpFile);
562 psxCpu->Clear(section_address, section_size / 4);
565 case 3: /* register loading (PC only?) */
566 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
567 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
569 psxRegs.pc = SWAPu32(psxRegs.pc);
571 case 0: /* End of file */
574 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
578 } while (opcode != 0 && retval == 0);
581 SysPrintf(_("COFF files not supported.\n"));
585 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
586 SysPrintf(_("(did you forget -cdfile ?)\n"));
594 CdromLabel[0] = '\0';
603 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
611 static void *zlib_open(const char *name, const char *mode)
613 return gzopen(name, mode);
616 static int zlib_read(void *file, void *buf, u32 len)
618 return gzread(file, buf, len);
621 static int zlib_write(void *file, const void *buf, u32 len)
623 return gzwrite(file, buf, len);
626 static long zlib_seek(void *file, long offs, int whence)
628 return gzseek(file, offs, whence);
631 static void zlib_close(void *file)
636 struct PcsxSaveFuncs SaveFuncs = {
637 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
640 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
642 // Savestate Versioning!
643 // If you make changes to the savestate version, please increment the value below.
644 static const u32 SaveVersion = 0x8b410006;
646 int SaveState(const char *file) {
649 SPUFreezeHdr_t *spufH;
654 f = SaveFuncs.open(file, "wb");
655 if (f == NULL) return -1;
657 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
659 SaveFuncs.write(f, (void *)PcsxHeader, 32);
660 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
661 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
663 pMem = (unsigned char *)malloc(128 * 96 * 3);
664 if (pMem == NULL) return -1;
665 GPU_getScreenPic(pMem);
666 SaveFuncs.write(f, pMem, 128 * 96 * 3);
672 SaveFuncs.write(f, psxM, 0x00200000);
673 SaveFuncs.write(f, psxR, 0x00080000);
674 SaveFuncs.write(f, psxH, 0x00010000);
675 // only partial save of psxRegisters to maintain savestate compat
676 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
679 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
680 gpufP->ulFreezeVersion = 1;
681 GPU_freeze(1, gpufP);
682 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
686 spufH = malloc(sizeof(*spufH));
687 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
688 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
690 spufP = (SPUFreeze_t *) malloc(Size);
691 SPU_freeze(1, spufP, psxRegs.cycle);
692 SaveFuncs.write(f, spufP, Size);
700 new_dyna_freeze(f, 1);
707 int LoadState(const char *file) {
716 f = SaveFuncs.open(file, "rb");
717 if (f == NULL) return -1;
719 SaveFuncs.read(f, header, sizeof(header));
720 SaveFuncs.read(f, &version, sizeof(u32));
721 SaveFuncs.read(f, &hle, sizeof(boolean));
723 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
732 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
733 SaveFuncs.read(f, psxM, 0x00200000);
734 SaveFuncs.read(f, psxR, 0x00080000);
735 SaveFuncs.read(f, psxH, 0x00010000);
736 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
737 psxRegs.gteBusyCycle = psxRegs.cycle;
739 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
745 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
746 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
747 GPU_freeze(0, gpufP);
749 if (HW_GPU_STATUS == 0)
750 HW_GPU_STATUS = SWAP32(GPU_readStatus());
753 SaveFuncs.read(f, &Size, 4);
754 spufP = (SPUFreeze_t *)malloc(Size);
755 SaveFuncs.read(f, spufP, Size);
756 SPU_freeze(0, spufP, psxRegs.cycle);
764 new_dyna_freeze(f, 0);
771 int CheckState(const char *file) {
777 f = SaveFuncs.open(file, "rb");
778 if (f == NULL) return -1;
780 SaveFuncs.read(f, header, sizeof(header));
781 SaveFuncs.read(f, &version, sizeof(u32));
782 SaveFuncs.read(f, &hle, sizeof(boolean));
786 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
792 // NET Function Helpers
795 if (NET_recvData == NULL || NET_sendData == NULL)
799 boolean SpuIrq_old = 0;
800 boolean RCntFix_old = 0;
801 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
802 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
803 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
804 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
805 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
806 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
814 if (NET_recvData == NULL || NET_sendData == NULL)
818 boolean SpuIrq_old = 0;
819 boolean RCntFix_old = 0;
820 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
821 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
822 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
823 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
824 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
827 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
828 if (tmp != Config.Cpu) {
831 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
832 else psxCpu = &psxRec;
836 if (psxCpu->Init() == -1) {
837 SysClose(); return -1;
840 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
846 // remove the leading and trailing spaces in a string
847 void trim(char *str) {
851 // skip leading blanks
852 while (str[pos] <= ' ' && str[pos] > 0)
856 *(dest++) = str[pos];
860 *(dest--) = '\0'; // store the null
862 // remove trailing blanks
863 while (dest >= str && *dest <= ' ' && *dest > 0)
867 // lookup table for crc calculation
868 static unsigned short crctab[256] = {
869 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
870 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
871 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
872 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
873 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
874 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
875 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
876 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
877 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
878 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
879 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
880 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
881 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
882 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
883 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
884 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
885 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
886 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
887 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
888 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
889 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
890 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
891 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
892 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
893 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
894 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
895 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
896 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
897 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
900 u16 calcCrc(u8 *d, int len) {
904 for (i = 0; i < len; i++) {
905 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);