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;
310 sscanf(filename, "cdrom:\\%255s", exename);
312 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
316 // skip head and sub, and go to the root directory record
317 dir = (struct iso_directory_record *)&buf[12 + 156];
319 mmssdd(dir->extent, (char*)time);
323 if (GetCdromFile(mdir, time, exename) == -1) return -1;
327 memcpy(head, buf + 12, sizeof(EXE_HEADER));
331 psxCpu->Clear(addr, size / 4);
334 while (size & ~2047) {
339 if (mem != INVALID_PTR)
340 memcpy(mem, buf + 12, 2048);
350 struct iso_directory_record *dir;
351 unsigned char time[4];
353 unsigned char mdir[4096];
361 time[2] = itob(0x10);
365 memset(CdromLabel, 0, sizeof(CdromLabel));
366 memset(CdromId, 0, sizeof(CdromId));
367 memset(exename, 0, sizeof(exename));
369 strncpy(CdromLabel, buf + 52, 32);
371 // skip head and sub, and go to the root directory record
372 dir = (struct iso_directory_record *)&buf[12 + 156];
374 mmssdd(dir->extent, (char *)time);
378 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
381 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
382 if (GetCdromFile(mdir, time, exename) == -1) {
383 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
384 if (GetCdromFile(mdir, time, exename) == -1) {
385 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
388 while (*ptr == '\\' || *ptr == '/') ptr++;
389 strncpy(exename, ptr, 255);
392 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
394 if (GetCdromFile(mdir, time, exename) == -1)
395 return -1; // main executable not found
400 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
401 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
403 size_t i, len = strlen(exename) - offset;
404 for (i = 0; i < len; i++)
405 exename[i] = exename[i + offset];
408 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
409 strcpy(exename, "PSX.EXE;1");
410 strcpy(CdromId, "SLUS99999");
412 return -1; // SYSTEM.CNF and PSX.EXE not found
414 if (CdromId[0] == '\0') {
415 len = strlen(exename);
417 for (i = 0; i < len; ++i) {
418 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
420 if (isalnum(exename[i]))
421 CdromId[c++] = exename[i];
425 if (CdromId[0] == '\0')
426 strcpy(CdromId, "SLUS99999");
428 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
430 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
431 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
432 !strncmp(CdromId, "DTLS3035", 8) ||
433 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
434 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
435 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
436 Config.PsxType = PSX_TYPE_PAL; // pal
437 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
440 if (CdromLabel[0] == ' ') {
441 strncpy(CdromLabel, CdromId, 9);
443 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
444 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
445 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
454 static int PSXGetFileType(FILE *f) {
455 unsigned long current;
461 fseek(f, 0L, SEEK_SET);
462 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
465 fseek(f, current, SEEK_SET);
467 exe_hdr = (EXE_HEADER *)mybuf;
468 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
471 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
474 coff_hdr = (FILHDR *)mybuf;
475 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
482 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
487 // temporary pandora workaround..
489 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
494 tmp = malloc(size * nmemb);
496 ret = fread(tmp, size, nmemb, stream);
497 memcpy(ptr, tmp, size * nmemb);
503 int Load(const char *ExePath) {
509 u32 section_address, section_size;
512 strcpy(CdromId, "SLUS99999");
513 strcpy(CdromLabel, "SLUS_999.99");
515 tmpFile = fopen(ExePath, "rb");
516 if (tmpFile == NULL) {
517 SysPrintf(_("Error opening file: %s.\n"), ExePath);
520 type = PSXGetFileType(tmpFile);
523 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
525 section_address = SWAP32(tmpHead.t_addr);
526 section_size = SWAP32(tmpHead.t_size);
527 mem = PSXM(section_address);
528 if (mem != INVALID_PTR) {
529 fseek(tmpFile, 0x800, SEEK_SET);
530 fread_to_ram(mem, section_size, 1, tmpFile);
531 psxCpu->Clear(section_address, section_size / 4);
533 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
534 SWAP32(tmpHead.s_addr));
538 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
540 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
543 case 1: /* Section loading */
544 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
546 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
548 section_address = SWAPu32(section_address);
549 section_size = SWAPu32(section_size);
551 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
553 mem = PSXM(section_address);
554 if (mem != INVALID_PTR) {
555 fread_to_ram(mem, section_size, 1, tmpFile);
556 psxCpu->Clear(section_address, section_size / 4);
559 case 3: /* register loading (PC only?) */
560 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
561 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
563 psxRegs.pc = SWAPu32(psxRegs.pc);
565 case 0: /* End of file */
568 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
572 } while (opcode != 0 && retval == 0);
575 SysPrintf(_("COFF files not supported.\n"));
579 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
580 SysPrintf(_("(did you forget -cdfile ?)\n"));
588 CdromLabel[0] = '\0';
597 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
605 static void *zlib_open(const char *name, const char *mode)
607 return gzopen(name, mode);
610 static int zlib_read(void *file, void *buf, u32 len)
612 return gzread(file, buf, len);
615 static int zlib_write(void *file, const void *buf, u32 len)
617 return gzwrite(file, buf, len);
620 static long zlib_seek(void *file, long offs, int whence)
622 return gzseek(file, offs, whence);
625 static void zlib_close(void *file)
630 struct PcsxSaveFuncs SaveFuncs = {
631 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
634 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
636 // Savestate Versioning!
637 // If you make changes to the savestate version, please increment the value below.
638 static const u32 SaveVersion = 0x8b410006;
640 int SaveState(const char *file) {
643 SPUFreezeHdr_t *spufH;
648 f = SaveFuncs.open(file, "wb");
649 if (f == NULL) return -1;
651 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
653 SaveFuncs.write(f, (void *)PcsxHeader, 32);
654 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
655 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
657 pMem = (unsigned char *)malloc(128 * 96 * 3);
658 if (pMem == NULL) return -1;
659 GPU_getScreenPic(pMem);
660 SaveFuncs.write(f, pMem, 128 * 96 * 3);
666 SaveFuncs.write(f, psxM, 0x00200000);
667 SaveFuncs.write(f, psxR, 0x00080000);
668 SaveFuncs.write(f, psxH, 0x00010000);
669 // only partial save of psxRegisters to maintain savestate compat
670 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
673 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
674 gpufP->ulFreezeVersion = 1;
675 GPU_freeze(1, gpufP);
676 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
680 spufH = malloc(sizeof(*spufH));
681 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
682 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
684 spufP = (SPUFreeze_t *) malloc(Size);
685 SPU_freeze(1, spufP, psxRegs.cycle);
686 SaveFuncs.write(f, spufP, Size);
694 new_dyna_freeze(f, 1);
701 int LoadState(const char *file) {
710 f = SaveFuncs.open(file, "rb");
711 if (f == NULL) return -1;
713 SaveFuncs.read(f, header, sizeof(header));
714 SaveFuncs.read(f, &version, sizeof(u32));
715 SaveFuncs.read(f, &hle, sizeof(boolean));
717 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
726 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
727 SaveFuncs.read(f, psxM, 0x00200000);
728 SaveFuncs.read(f, psxR, 0x00080000);
729 SaveFuncs.read(f, psxH, 0x00010000);
730 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
731 psxRegs.gteBusyCycle = psxRegs.cycle;
733 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
739 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
740 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
741 GPU_freeze(0, gpufP);
743 if (HW_GPU_STATUS == 0)
744 HW_GPU_STATUS = SWAP32(GPU_readStatus());
747 SaveFuncs.read(f, &Size, 4);
748 spufP = (SPUFreeze_t *)malloc(Size);
749 SaveFuncs.read(f, spufP, Size);
750 SPU_freeze(0, spufP, psxRegs.cycle);
758 new_dyna_freeze(f, 0);
765 int CheckState(const char *file) {
771 f = SaveFuncs.open(file, "rb");
772 if (f == NULL) return -1;
774 SaveFuncs.read(f, header, sizeof(header));
775 SaveFuncs.read(f, &version, sizeof(u32));
776 SaveFuncs.read(f, &hle, sizeof(boolean));
780 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
786 // NET Function Helpers
789 if (NET_recvData == NULL || NET_sendData == NULL)
793 boolean SpuIrq_old = 0;
794 boolean RCntFix_old = 0;
795 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
796 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
797 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
798 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
799 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
800 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
808 if (NET_recvData == NULL || NET_sendData == NULL)
812 boolean SpuIrq_old = 0;
813 boolean RCntFix_old = 0;
814 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
815 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
816 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
817 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
818 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
821 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
822 if (tmp != Config.Cpu) {
825 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
826 else psxCpu = &psxRec;
830 if (psxCpu->Init() == -1) {
831 SysClose(); return -1;
834 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
840 // remove the leading and trailing spaces in a string
841 void trim(char *str) {
845 // skip leading blanks
846 while (str[pos] <= ' ' && str[pos] > 0)
850 *(dest++) = str[pos];
854 *(dest--) = '\0'; // store the null
856 // remove trailing blanks
857 while (dest >= str && *dest <= ' ' && *dest > 0)
861 // lookup table for crc calculation
862 static unsigned short crctab[256] = {
863 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
864 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
865 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
866 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
867 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
868 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
869 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
870 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
871 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
872 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
873 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
874 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
875 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
876 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
877 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
878 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
879 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
880 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
881 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
882 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
883 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
884 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
885 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
886 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
887 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
888 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
889 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
890 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
891 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
894 u16 calcCrc(u8 *d, int len) {
898 for (i = 0; i < len; i++) {
899 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);