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.
33 char CdromId[10] = "";
34 char CdromLabel[33] = "";
36 // PSX Executable types
42 #define ISODCL(from, to) (to - from + 1)
44 struct iso_directory_record {
45 char length [ISODCL (1, 1)]; /* 711 */
46 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
47 char extent [ISODCL (3, 10)]; /* 733 */
48 char size [ISODCL (11, 18)]; /* 733 */
49 char date [ISODCL (19, 25)]; /* 7 by 711 */
50 char flags [ISODCL (26, 26)];
51 char file_unit_size [ISODCL (27, 27)]; /* 711 */
52 char interleave [ISODCL (28, 28)]; /* 711 */
53 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
54 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
58 void mmssdd( char *b, char *p )
62 unsigned char *u = (void *)b;
63 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
64 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
65 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
67 int block = *((int*)b);
71 m = block / 4500; // minutes
72 block = block - m * 4500; // minutes rest
73 s = block / 75; // seconds
74 d = block - s * 75; // seconds rest
76 m = ((m / 10) << 4) | m % 10;
77 s = ((s / 10) << 4) | s % 10;
78 d = ((d / 10) << 4) | d % 10;
86 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
91 if (time[1] == 60) { \
96 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
99 if (CDR_readTrack(time) == -1) return -1; \
100 buf = (void *)CDR_getBuffer(); \
101 if (buf == NULL) return -1; \
102 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
104 #define READDIR(_dir) \
106 memcpy(_dir, buf + 12, 2048); \
110 memcpy(_dir + 2048, buf + 12, 2048);
112 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
113 struct iso_directory_record *dir;
119 // only try to scan if a filename is given
120 if (!strlen(filename)) return -1;
124 dir = (struct iso_directory_record*) &mdir[i];
125 if (dir->length[0] == 0) {
128 i += (u8)dir->length[0];
130 if (dir->flags[0] & 0x2) { // it's a dir
131 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
132 if (filename[dir->name_len[0]] != '\\') continue;
134 filename += dir->name_len[0] + 1;
136 mmssdd(dir->extent, (char *)time);
142 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
143 mmssdd(dir->extent, (char *)time);
152 static const unsigned int gpu_ctl_def[] = {
153 0x00000000, 0x01000000, 0x03000000, 0x04000000,
154 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
157 static const unsigned int gpu_data_def[] = {
158 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
159 0xe5001000, 0xe6000000,
160 0x02000000, 0x00000000, 0x01ff03ff,
163 static void fake_bios_gpu_setup(void)
167 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
168 GPU_writeStatus(gpu_ctl_def[i]);
170 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
171 GPU_writeData(gpu_data_def[i]);
176 struct iso_directory_record *dir;
181 // not the best place to do it, but since BIOS boot logo killer
182 // is just below, do it here
183 fake_bios_gpu_setup();
187 psxRegs.pc = psxRegs.GPR.n.ra;
191 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
195 // skip head and sub, and go to the root directory record
196 dir = (struct iso_directory_record*) &buf[12+156];
198 mmssdd(dir->extent, (char*)time);
202 // Load SYSTEM.CNF and scan for the main executable
203 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
204 // if SYSTEM.CNF is missing, start an existing PSX.EXE
205 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
210 // read the SYSTEM.CNF
213 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
214 if (GetCdromFile(mdir, time, exename) == -1) {
215 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
216 if (GetCdromFile(mdir, time, exename) == -1) {
217 char *ptr = strstr((char *)buf + 12, "cdrom:");
220 while (*ptr == '\\' || *ptr == '/') ptr++;
221 strncpy(exename, ptr, 255);
224 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
226 if (GetCdromFile(mdir, time, exename) == -1)
233 // Read the EXE-Header
237 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
239 psxRegs.pc = SWAP32(tmpHead.pc0);
240 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
241 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
242 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
244 tmpHead.t_size = SWAP32(tmpHead.t_size);
245 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
247 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
250 // Read the rest of the main executable
251 while (tmpHead.t_size & ~2047) {
252 void *ptr = (void *)PSXM(tmpHead.t_addr);
257 if (ptr != NULL) memcpy(ptr, buf+12, 2048);
259 tmpHead.t_size -= 2048;
260 tmpHead.t_addr += 2048;
266 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
267 struct iso_directory_record *dir;
274 sscanf(filename, "cdrom:\\%255s", exename);
276 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
280 // skip head and sub, and go to the root directory record
281 dir = (struct iso_directory_record *)&buf[12 + 156];
283 mmssdd(dir->extent, (char*)time);
287 if (GetCdromFile(mdir, time, exename) == -1) return -1;
291 memcpy(head, buf + 12, sizeof(EXE_HEADER));
295 psxCpu->Clear(addr, size / 4);
298 while (size & ~2047) {
304 memcpy(mem, buf + 12, 2048);
314 struct iso_directory_record *dir;
315 unsigned char time[4];
317 unsigned char mdir[4096];
325 time[2] = itob(0x10);
329 memset(CdromLabel, 0, sizeof(CdromLabel));
330 memset(CdromId, 0, sizeof(CdromId));
331 memset(exename, 0, sizeof(exename));
333 strncpy(CdromLabel, buf + 52, 32);
335 // skip head and sub, and go to the root directory record
336 dir = (struct iso_directory_record *)&buf[12 + 156];
338 mmssdd(dir->extent, (char *)time);
342 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
345 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
346 if (GetCdromFile(mdir, time, exename) == -1) {
347 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
348 if (GetCdromFile(mdir, time, exename) == -1) {
349 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
352 while (*ptr == '\\' || *ptr == '/') ptr++;
353 strncpy(exename, ptr, 255);
356 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
358 if (GetCdromFile(mdir, time, exename) == -1)
359 return -1; // main executable not found
364 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
365 strcpy(exename, "PSX.EXE;1");
366 strcpy(CdromId, "SLUS99999");
368 return -1; // SYSTEM.CNF and PSX.EXE not found
370 if (CdromId[0] == '\0') {
371 len = strlen(exename);
373 for (i = 0; i < len; ++i) {
374 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
376 if (isalnum(exename[i]))
377 CdromId[c++] = exename[i];
381 if (CdromId[0] == '\0')
382 strcpy(CdromId, "SLUS99999");
384 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
386 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
387 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
388 !strncmp(CdromId, "DTLS3035", 8) ||
389 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
390 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
391 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
392 Config.PsxType = PSX_TYPE_PAL; // pal
393 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
396 if (CdromLabel[0] == ' ') {
397 strncpy(CdromLabel, CdromId, 9);
399 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
400 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
401 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
410 static int PSXGetFileType(FILE *f) {
411 unsigned long current;
417 fseek(f, 0L, SEEK_SET);
418 fread(mybuf, 2048, 1, f);
419 fseek(f, current, SEEK_SET);
421 exe_hdr = (EXE_HEADER *)mybuf;
422 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
425 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
428 coff_hdr = (FILHDR *)mybuf;
429 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
435 // temporary pandora workaround..
437 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
442 tmp = malloc(size * nmemb);
444 ret = fread(tmp, size, nmemb, stream);
445 memcpy(ptr, tmp, size * nmemb);
451 int Load(const char *ExePath) {
457 u32 section_address, section_size;
460 strncpy(CdromId, "SLUS99999", 9);
461 strncpy(CdromLabel, "SLUS_999.99", 11);
463 tmpFile = fopen(ExePath, "rb");
464 if (tmpFile == NULL) {
465 SysPrintf(_("Error opening file: %s.\n"), ExePath);
468 type = PSXGetFileType(tmpFile);
471 fread(&tmpHead,sizeof(EXE_HEADER),1,tmpFile);
472 section_address = SWAP32(tmpHead.t_addr);
473 section_size = SWAP32(tmpHead.t_size);
474 mem = PSXM(section_address);
476 fseek(tmpFile, 0x800, SEEK_SET);
477 fread_to_ram(mem, section_size, 1, tmpFile);
478 psxCpu->Clear(section_address, section_size / 4);
481 psxRegs.pc = SWAP32(tmpHead.pc0);
482 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
483 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
484 if (psxRegs.GPR.n.sp == 0)
485 psxRegs.GPR.n.sp = 0x801fff00;
489 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
491 fread(&opcode, 1, 1, tmpFile);
493 case 1: /* Section loading */
494 fread(§ion_address, 4, 1, tmpFile);
495 fread(§ion_size, 4, 1, tmpFile);
496 section_address = SWAPu32(section_address);
497 section_size = SWAPu32(section_size);
499 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
501 mem = PSXM(section_address);
503 fread_to_ram(mem, section_size, 1, tmpFile);
504 psxCpu->Clear(section_address, section_size / 4);
507 case 3: /* register loading (PC only?) */
508 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
509 fread(&psxRegs.pc, 4, 1, tmpFile);
510 psxRegs.pc = SWAPu32(psxRegs.pc);
512 case 0: /* End of file */
515 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
519 } while (opcode != 0 && retval == 0);
522 SysPrintf(_("COFF files not supported.\n"));
526 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
527 SysPrintf(_("(did you forget -cdfile ?)\n"));
535 CdromLabel[0] = '\0';
543 static void *zlib_open(const char *name, const char *mode)
545 return gzopen(name, mode);
548 static int zlib_read(void *file, void *buf, u32 len)
550 return gzread(file, buf, len);
553 static int zlib_write(void *file, const void *buf, u32 len)
555 return gzwrite(file, buf, len);
558 static long zlib_seek(void *file, long offs, int whence)
560 return gzseek(file, offs, whence);
563 static void zlib_close(void *file)
568 struct PcsxSaveFuncs SaveFuncs = {
569 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
572 static const char PcsxHeader[32] = "STv4 PCSX v" PACKAGE_VERSION;
574 // Savestate Versioning!
575 // If you make changes to the savestate version, please increment the value below.
576 static const u32 SaveVersion = 0x8b410006;
578 int SaveState(const char *file) {
585 f = SaveFuncs.open(file, "wb");
586 if (f == NULL) return -1;
588 new_dyna_before_save();
590 SaveFuncs.write(f, (void *)PcsxHeader, 32);
591 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
592 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
594 pMem = (unsigned char *)malloc(128 * 96 * 3);
595 if (pMem == NULL) return -1;
596 GPU_getScreenPic(pMem);
597 SaveFuncs.write(f, pMem, 128 * 96 * 3);
603 SaveFuncs.write(f, psxM, 0x00200000);
604 SaveFuncs.write(f, psxR, 0x00080000);
605 SaveFuncs.write(f, psxH, 0x00010000);
606 // only partial save of psxRegisters to maintain savestate compat
607 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
610 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
611 gpufP->ulFreezeVersion = 1;
612 GPU_freeze(1, gpufP);
613 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
617 spufP = (SPUFreeze_t *) malloc(16);
618 SPU_freeze(2, spufP, psxRegs.cycle);
619 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
621 spufP = (SPUFreeze_t *) malloc(Size);
622 SPU_freeze(1, spufP, psxRegs.cycle);
623 SaveFuncs.write(f, spufP, Size);
631 new_dyna_freeze(f, 1);
635 new_dyna_after_save();
640 int LoadState(const char *file) {
649 f = SaveFuncs.open(file, "rb");
650 if (f == NULL) return -1;
652 SaveFuncs.read(f, header, sizeof(header));
653 SaveFuncs.read(f, &version, sizeof(u32));
654 SaveFuncs.read(f, &hle, sizeof(boolean));
656 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
666 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
668 SaveFuncs.read(f, psxM, 0x00200000);
669 SaveFuncs.read(f, psxR, 0x00080000);
670 SaveFuncs.read(f, psxH, 0x00010000);
671 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
672 psxRegs.gteBusyCycle = psxRegs.cycle;
678 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
679 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
680 GPU_freeze(0, gpufP);
682 if (HW_GPU_STATUS == 0)
683 HW_GPU_STATUS = GPU_readStatus();
686 SaveFuncs.read(f, &Size, 4);
687 spufP = (SPUFreeze_t *)malloc(Size);
688 SaveFuncs.read(f, spufP, Size);
689 SPU_freeze(0, spufP, psxRegs.cycle);
697 new_dyna_freeze(f, 0);
704 int CheckState(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));
719 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
725 // NET Function Helpers
728 if (NET_recvData == NULL || NET_sendData == NULL)
731 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
732 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
733 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
734 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
735 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
736 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
744 if (NET_recvData == NULL || NET_sendData == NULL)
747 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
748 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
749 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
750 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
751 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
756 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
757 if (tmp != Config.Cpu) {
760 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
761 else psxCpu = &psxRec;
765 if (psxCpu->Init() == -1) {
766 SysClose(); return -1;
774 // remove the leading and trailing spaces in a string
775 void trim(char *str) {
779 // skip leading blanks
780 while (str[pos] <= ' ' && str[pos] > 0)
784 *(dest++) = str[pos];
788 *(dest--) = '\0'; // store the null
790 // remove trailing blanks
791 while (dest >= str && *dest <= ' ' && *dest > 0)
795 // lookup table for crc calculation
796 static unsigned short crctab[256] = {
797 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
798 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
799 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
800 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
801 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
802 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
803 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
804 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
805 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
806 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
807 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
808 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
809 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
810 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
811 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
812 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
813 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
814 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
815 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
816 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
817 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
818 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
819 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
820 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
821 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
822 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
823 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
824 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
825 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
828 u16 calcCrc(u8 *d, int len) {
832 for (i = 0; i < len; i++) {
833 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);