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.
31 char CdromId[10] = "";
32 char CdromLabel[33] = "";
34 // PSX Executable types
40 #define ISODCL(from, to) (to - from + 1)
42 struct iso_directory_record {
43 char length [ISODCL (1, 1)]; /* 711 */
44 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
45 char extent [ISODCL (3, 10)]; /* 733 */
46 char size [ISODCL (11, 18)]; /* 733 */
47 char date [ISODCL (19, 25)]; /* 7 by 711 */
48 char flags [ISODCL (26, 26)];
49 char file_unit_size [ISODCL (27, 27)]; /* 711 */
50 char interleave [ISODCL (28, 28)]; /* 711 */
51 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
52 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
56 void mmssdd( char *b, char *p )
60 unsigned char *u = (void *)b;
61 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
62 #elif defined(__BIGENDIAN__)
63 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
65 int block = *((int*)b);
69 m = block / 4500; // minutes
70 block = block - m * 4500; // minutes rest
71 s = block / 75; // seconds
72 d = block - s * 75; // seconds rest
74 m = ((m / 10) << 4) | m % 10;
75 s = ((s / 10) << 4) | s % 10;
76 d = ((d / 10) << 4) | d % 10;
84 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
89 if (time[1] == 60) { \
94 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
97 if (CDR_readTrack(time) == -1) return -1; \
98 buf = (void *)CDR_getBuffer(); \
99 if (buf == NULL) return -1; \
100 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
102 #define READDIR(_dir) \
104 memcpy(_dir, buf + 12, 2048); \
108 memcpy(_dir + 2048, buf + 12, 2048);
110 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
111 struct iso_directory_record *dir;
117 // only try to scan if a filename is given
118 if (!strlen(filename)) return -1;
122 dir = (struct iso_directory_record*) &mdir[i];
123 if (dir->length[0] == 0) {
126 i += (u8)dir->length[0];
128 if (dir->flags[0] & 0x2) { // it's a dir
129 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
130 if (filename[dir->name_len[0]] != '\\') continue;
132 filename += dir->name_len[0] + 1;
134 mmssdd(dir->extent, (char *)time);
140 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
141 mmssdd(dir->extent, (char *)time);
150 static const unsigned int gpu_ctl_def[] = {
151 0x00000000, 0x01000000, 0x03000000, 0x04000000,
152 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
155 static const unsigned int gpu_data_def[] = {
156 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
157 0xe5001000, 0xe6000000,
158 0x02000000, 0x00000000, 0x01ff03ff,
161 static void fake_bios_gpu_setup(void)
165 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
166 GPU_writeStatus(gpu_ctl_def[i]);
168 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
169 GPU_writeData(gpu_data_def[i]);
174 struct iso_directory_record *dir;
179 // not the best place to do it, but since BIOS boot logo killer
180 // is just below, do it here
181 fake_bios_gpu_setup();
183 if (!Config.HLE && !Config.SlowBoot) {
185 psxRegs.pc = psxRegs.GPR.n.ra;
189 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
193 // skip head and sub, and go to the root directory record
194 dir = (struct iso_directory_record*) &buf[12+156];
196 mmssdd(dir->extent, (char*)time);
200 // Load SYSTEM.CNF and scan for the main executable
201 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
202 // if SYSTEM.CNF is missing, start an existing PSX.EXE
203 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
208 // read the SYSTEM.CNF
211 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
212 if (GetCdromFile(mdir, time, exename) == -1) {
213 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
214 if (GetCdromFile(mdir, time, exename) == -1) {
215 char *ptr = strstr((char *)buf + 12, "cdrom:");
218 while (*ptr == '\\' || *ptr == '/') ptr++;
219 strncpy(exename, ptr, 255);
222 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
224 if (GetCdromFile(mdir, time, exename) == -1)
231 // Read the EXE-Header
235 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
237 psxRegs.pc = SWAP32(tmpHead.pc0);
238 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
239 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
240 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
242 tmpHead.t_size = SWAP32(tmpHead.t_size);
243 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
245 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
247 // Read the rest of the main executable
248 while (tmpHead.t_size & ~2047) {
249 void *ptr = (void *)PSXM(tmpHead.t_addr);
254 if (ptr != NULL) memcpy(ptr, buf+12, 2048);
256 tmpHead.t_size -= 2048;
257 tmpHead.t_addr += 2048;
263 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
264 struct iso_directory_record *dir;
271 sscanf(filename, "cdrom:\\%255s", exename);
273 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
277 // skip head and sub, and go to the root directory record
278 dir = (struct iso_directory_record *)&buf[12 + 156];
280 mmssdd(dir->extent, (char*)time);
284 if (GetCdromFile(mdir, time, exename) == -1) return -1;
288 memcpy(head, buf + 12, sizeof(EXE_HEADER));
292 psxCpu->Clear(addr, size / 4);
294 while (size & ~2047) {
300 memcpy(mem, buf + 12, 2048);
310 struct iso_directory_record *dir;
311 unsigned char time[4];
313 unsigned char mdir[4096];
321 time[2] = itob(0x10);
325 memset(CdromLabel, 0, sizeof(CdromLabel));
326 memset(CdromId, 0, sizeof(CdromId));
327 memset(exename, 0, sizeof(exename));
329 strncpy(CdromLabel, buf + 52, 32);
331 // skip head and sub, and go to the root directory record
332 dir = (struct iso_directory_record *)&buf[12 + 156];
334 mmssdd(dir->extent, (char *)time);
338 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
341 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
342 if (GetCdromFile(mdir, time, exename) == -1) {
343 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
344 if (GetCdromFile(mdir, time, exename) == -1) {
345 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
348 while (*ptr == '\\' || *ptr == '/') ptr++;
349 strncpy(exename, ptr, 255);
352 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
354 if (GetCdromFile(mdir, time, exename) == -1)
355 return -1; // main executable not found
360 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
361 strcpy(exename, "PSX.EXE;1");
362 strcpy(CdromId, "SLUS99999");
364 return -1; // SYSTEM.CNF and PSX.EXE not found
366 if (CdromId[0] == '\0') {
367 len = strlen(exename);
369 for (i = 0; i < len; ++i) {
370 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
372 if (isalnum(exename[i]))
373 CdromId[c++] = exename[i];
377 if (CdromId[0] == '\0')
378 strcpy(CdromId, "SLUS99999");
380 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
381 if (CdromId[2] == 'e' || CdromId[2] == 'E')
382 Config.PsxType = PSX_TYPE_PAL; // pal
383 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
386 if (CdromLabel[0] == ' ') {
387 strncpy(CdromLabel, CdromId, 9);
389 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
390 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
391 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
398 static int PSXGetFileType(FILE *f) {
399 unsigned long current;
405 fseek(f, 0L, SEEK_SET);
406 fread(mybuf, 2048, 1, f);
407 fseek(f, current, SEEK_SET);
409 exe_hdr = (EXE_HEADER *)mybuf;
410 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
413 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
416 coff_hdr = (FILHDR *)mybuf;
417 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
423 // temporary pandora workaround..
425 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
430 tmp = malloc(size * nmemb);
432 ret = fread(tmp, size, nmemb, stream);
433 memcpy(ptr, tmp, size * nmemb);
439 int Load(const char *ExePath) {
445 u32 section_address, section_size;
448 strncpy(CdromId, "SLUS99999", 9);
449 strncpy(CdromLabel, "SLUS_999.99", 11);
451 tmpFile = fopen(ExePath, "rb");
452 if (tmpFile == NULL) {
453 SysPrintf(_("Error opening file: %s.\n"), ExePath);
456 type = PSXGetFileType(tmpFile);
459 fread(&tmpHead,sizeof(EXE_HEADER),1,tmpFile);
460 section_address = SWAP32(tmpHead.t_addr);
461 section_size = SWAP32(tmpHead.t_size);
462 mem = PSXM(section_address);
464 fseek(tmpFile, 0x800, SEEK_SET);
465 fread_to_ram(mem, section_size, 1, tmpFile);
466 psxCpu->Clear(section_address, section_size / 4);
469 psxRegs.pc = SWAP32(tmpHead.pc0);
470 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
471 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
472 if (psxRegs.GPR.n.sp == 0)
473 psxRegs.GPR.n.sp = 0x801fff00;
477 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
479 fread(&opcode, 1, 1, tmpFile);
481 case 1: /* Section loading */
482 fread(§ion_address, 4, 1, tmpFile);
483 fread(§ion_size, 4, 1, tmpFile);
484 section_address = SWAPu32(section_address);
485 section_size = SWAPu32(section_size);
487 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
489 mem = PSXM(section_address);
491 fread_to_ram(mem, section_size, 1, tmpFile);
492 psxCpu->Clear(section_address, section_size / 4);
495 case 3: /* register loading (PC only?) */
496 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
497 fread(&psxRegs.pc, 4, 1, tmpFile);
498 psxRegs.pc = SWAPu32(psxRegs.pc);
500 case 0: /* End of file */
503 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
507 } while (opcode != 0 && retval == 0);
510 SysPrintf(_("COFF files not supported.\n"));
514 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
515 SysPrintf(_("(did you forget -cdfile ?)\n"));
523 CdromLabel[0] = '\0';
531 static void *zlib_open(const char *name, const char *mode)
533 return gzopen(name, mode);
536 static int zlib_read(void *file, void *buf, u32 len)
538 return gzread(file, buf, len);
541 static int zlib_write(void *file, const void *buf, u32 len)
543 return gzwrite(file, buf, len);
546 static long zlib_seek(void *file, long offs, int whence)
548 return gzseek(file, offs, whence);
551 static void zlib_close(void *file)
556 struct PcsxSaveFuncs SaveFuncs = {
557 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
560 static const char PcsxHeader[32] = "STv4 PCSX v" PACKAGE_VERSION;
562 // Savestate Versioning!
563 // If you make changes to the savestate version, please increment the value below.
564 static const u32 SaveVersion = 0x8b410006;
566 int SaveState(const char *file) {
573 f = SaveFuncs.open(file, "wb");
574 if (f == NULL) return -1;
576 new_dyna_before_save();
578 SaveFuncs.write(f, (void *)PcsxHeader, 32);
579 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
580 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
582 pMem = (unsigned char *)malloc(128 * 96 * 3);
583 if (pMem == NULL) return -1;
584 GPU_getScreenPic(pMem);
585 SaveFuncs.write(f, pMem, 128 * 96 * 3);
591 SaveFuncs.write(f, psxM, 0x00200000);
592 SaveFuncs.write(f, psxR, 0x00080000);
593 SaveFuncs.write(f, psxH, 0x00010000);
594 SaveFuncs.write(f, (void *)&psxRegs, sizeof(psxRegs));
597 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
598 gpufP->ulFreezeVersion = 1;
599 GPU_freeze(1, gpufP);
600 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
604 spufP = (SPUFreeze_t *) malloc(16);
605 SPU_freeze(2, spufP, psxRegs.cycle);
606 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
608 spufP = (SPUFreeze_t *) malloc(Size);
609 SPU_freeze(1, spufP, psxRegs.cycle);
610 SaveFuncs.write(f, spufP, Size);
618 new_dyna_freeze(f, 1);
622 new_dyna_after_save();
627 int LoadState(const char *file) {
636 f = SaveFuncs.open(file, "rb");
637 if (f == NULL) return -1;
639 SaveFuncs.read(f, header, sizeof(header));
640 SaveFuncs.read(f, &version, sizeof(u32));
641 SaveFuncs.read(f, &hle, sizeof(boolean));
643 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
653 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
655 SaveFuncs.read(f, psxM, 0x00200000);
656 SaveFuncs.read(f, psxR, 0x00080000);
657 SaveFuncs.read(f, psxH, 0x00010000);
658 SaveFuncs.read(f, (void *)&psxRegs, sizeof(psxRegs));
664 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
665 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
666 GPU_freeze(0, gpufP);
668 if (HW_GPU_STATUS == 0)
669 HW_GPU_STATUS = GPU_readStatus();
672 SaveFuncs.read(f, &Size, 4);
673 spufP = (SPUFreeze_t *)malloc(Size);
674 SaveFuncs.read(f, spufP, Size);
675 SPU_freeze(0, spufP, psxRegs.cycle);
683 new_dyna_freeze(f, 0);
690 int CheckState(const char *file) {
696 f = SaveFuncs.open(file, "rb");
697 if (f == NULL) return -1;
699 SaveFuncs.read(f, header, sizeof(header));
700 SaveFuncs.read(f, &version, sizeof(u32));
701 SaveFuncs.read(f, &hle, sizeof(boolean));
705 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
711 // NET Function Helpers
714 if (NET_recvData == NULL || NET_sendData == NULL)
717 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
718 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
719 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
720 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
721 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
722 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
730 if (NET_recvData == NULL || NET_sendData == NULL)
733 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
734 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
735 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
736 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
737 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
742 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
743 if (tmp != Config.Cpu) {
746 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
747 else psxCpu = &psxRec;
751 if (psxCpu->Init() == -1) {
752 SysClose(); return -1;
760 // remove the leading and trailing spaces in a string
761 void trim(char *str) {
765 // skip leading blanks
766 while (str[pos] <= ' ' && str[pos] > 0)
770 *(dest++) = str[pos];
774 *(dest--) = '\0'; // store the null
776 // remove trailing blanks
777 while (dest >= str && *dest <= ' ' && *dest > 0)
781 // lookup table for crc calculation
782 static unsigned short crctab[256] = {
783 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
784 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
785 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
786 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
787 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
788 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
789 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
790 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
791 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
792 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
793 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
794 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
795 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
796 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
797 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
798 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
799 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
800 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
801 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
802 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
803 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
804 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
805 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
806 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
807 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
808 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
809 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
810 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
811 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
814 u16 calcCrc(u8 *d, int len) {
818 for (i = 0; i < len; i++) {
819 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);