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.
32 char CdromId[10] = "";
33 char CdromLabel[33] = "";
35 // PSX Executable types
41 #define ISODCL(from, to) (to - from + 1)
43 struct iso_directory_record {
44 char length [ISODCL (1, 1)]; /* 711 */
45 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
46 char extent [ISODCL (3, 10)]; /* 733 */
47 char size [ISODCL (11, 18)]; /* 733 */
48 char date [ISODCL (19, 25)]; /* 7 by 711 */
49 char flags [ISODCL (26, 26)];
50 char file_unit_size [ISODCL (27, 27)]; /* 711 */
51 char interleave [ISODCL (28, 28)]; /* 711 */
52 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
53 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
57 void mmssdd( char *b, char *p )
61 unsigned char *u = (void *)b;
62 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
63 #elif defined(__BIGENDIAN__)
64 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
66 int block = *((int*)b);
70 m = block / 4500; // minutes
71 block = block - m * 4500; // minutes rest
72 s = block / 75; // seconds
73 d = block - s * 75; // seconds rest
75 m = ((m / 10) << 4) | m % 10;
76 s = ((s / 10) << 4) | s % 10;
77 d = ((d / 10) << 4) | d % 10;
85 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
90 if (time[1] == 60) { \
95 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
98 if (CDR_readTrack(time) == -1) return -1; \
99 buf = (void *)CDR_getBuffer(); \
100 if (buf == NULL) return -1; \
101 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
103 #define READDIR(_dir) \
105 memcpy(_dir, buf + 12, 2048); \
109 memcpy(_dir + 2048, buf + 12, 2048);
111 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
112 struct iso_directory_record *dir;
118 // only try to scan if a filename is given
119 if (!strlen(filename)) return -1;
123 dir = (struct iso_directory_record*) &mdir[i];
124 if (dir->length[0] == 0) {
127 i += (u8)dir->length[0];
129 if (dir->flags[0] & 0x2) { // it's a dir
130 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
131 if (filename[dir->name_len[0]] != '\\') continue;
133 filename += dir->name_len[0] + 1;
135 mmssdd(dir->extent, (char *)time);
141 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
142 mmssdd(dir->extent, (char *)time);
151 static const unsigned int gpu_ctl_def[] = {
152 0x00000000, 0x01000000, 0x03000000, 0x04000000,
153 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
156 static const unsigned int gpu_data_def[] = {
157 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
158 0xe5001000, 0xe6000000,
159 0x02000000, 0x00000000, 0x01ff03ff,
162 static void fake_bios_gpu_setup(void)
166 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
167 GPU_writeStatus(gpu_ctl_def[i]);
169 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
170 GPU_writeData(gpu_data_def[i]);
175 struct iso_directory_record *dir;
180 // not the best place to do it, but since BIOS boot logo killer
181 // is just below, do it here
182 fake_bios_gpu_setup();
186 psxRegs.pc = psxRegs.GPR.n.ra;
190 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
194 // skip head and sub, and go to the root directory record
195 dir = (struct iso_directory_record*) &buf[12+156];
197 mmssdd(dir->extent, (char*)time);
201 // Load SYSTEM.CNF and scan for the main executable
202 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
203 // if SYSTEM.CNF is missing, start an existing PSX.EXE
204 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
209 // read the SYSTEM.CNF
212 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
213 if (GetCdromFile(mdir, time, exename) == -1) {
214 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
215 if (GetCdromFile(mdir, time, exename) == -1) {
216 char *ptr = strstr((char *)buf + 12, "cdrom:");
219 while (*ptr == '\\' || *ptr == '/') ptr++;
220 strncpy(exename, ptr, 255);
223 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
225 if (GetCdromFile(mdir, time, exename) == -1)
232 // Read the EXE-Header
236 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
238 psxRegs.pc = SWAP32(tmpHead.pc0);
239 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
240 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
241 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
243 tmpHead.t_size = SWAP32(tmpHead.t_size);
244 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
246 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
248 // Read the rest of the main executable
249 while (tmpHead.t_size & ~2047) {
250 void *ptr = (void *)PSXM(tmpHead.t_addr);
255 if (ptr != NULL) memcpy(ptr, buf+12, 2048);
257 tmpHead.t_size -= 2048;
258 tmpHead.t_addr += 2048;
264 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
265 struct iso_directory_record *dir;
272 sscanf(filename, "cdrom:\\%255s", exename);
274 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
278 // skip head and sub, and go to the root directory record
279 dir = (struct iso_directory_record *)&buf[12 + 156];
281 mmssdd(dir->extent, (char*)time);
285 if (GetCdromFile(mdir, time, exename) == -1) return -1;
289 memcpy(head, buf + 12, sizeof(EXE_HEADER));
293 psxCpu->Clear(addr, size / 4);
295 while (size & ~2047) {
301 memcpy(mem, buf + 12, 2048);
311 struct iso_directory_record *dir;
312 unsigned char time[4];
314 unsigned char mdir[4096];
322 time[2] = itob(0x10);
326 memset(CdromLabel, 0, sizeof(CdromLabel));
327 memset(CdromId, 0, sizeof(CdromId));
328 memset(exename, 0, sizeof(exename));
330 strncpy(CdromLabel, buf + 52, 32);
332 // skip head and sub, and go to the root directory record
333 dir = (struct iso_directory_record *)&buf[12 + 156];
335 mmssdd(dir->extent, (char *)time);
339 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
342 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
343 if (GetCdromFile(mdir, time, exename) == -1) {
344 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
345 if (GetCdromFile(mdir, time, exename) == -1) {
346 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
349 while (*ptr == '\\' || *ptr == '/') ptr++;
350 strncpy(exename, ptr, 255);
353 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
355 if (GetCdromFile(mdir, time, exename) == -1)
356 return -1; // main executable not found
361 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
362 strcpy(exename, "PSX.EXE;1");
363 strcpy(CdromId, "SLUS99999");
365 return -1; // SYSTEM.CNF and PSX.EXE not found
367 if (CdromId[0] == '\0') {
368 len = strlen(exename);
370 for (i = 0; i < len; ++i) {
371 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
373 if (isalnum(exename[i]))
374 CdromId[c++] = exename[i];
378 if (CdromId[0] == '\0')
379 strcpy(CdromId, "SLUS99999");
381 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
382 if (CdromId[2] == 'e' || CdromId[2] == 'E')
383 Config.PsxType = PSX_TYPE_PAL; // pal
384 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
387 if (CdromLabel[0] == ' ') {
388 strncpy(CdromLabel, CdromId, 9);
390 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
391 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
392 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
401 static int PSXGetFileType(FILE *f) {
402 unsigned long current;
408 fseek(f, 0L, SEEK_SET);
409 fread(mybuf, 2048, 1, f);
410 fseek(f, current, SEEK_SET);
412 exe_hdr = (EXE_HEADER *)mybuf;
413 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
416 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
419 coff_hdr = (FILHDR *)mybuf;
420 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
426 // temporary pandora workaround..
428 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
433 tmp = malloc(size * nmemb);
435 ret = fread(tmp, size, nmemb, stream);
436 memcpy(ptr, tmp, size * nmemb);
442 int Load(const char *ExePath) {
448 u32 section_address, section_size;
451 strncpy(CdromId, "SLUS99999", 9);
452 strncpy(CdromLabel, "SLUS_999.99", 11);
454 tmpFile = fopen(ExePath, "rb");
455 if (tmpFile == NULL) {
456 SysPrintf(_("Error opening file: %s.\n"), ExePath);
459 type = PSXGetFileType(tmpFile);
462 fread(&tmpHead,sizeof(EXE_HEADER),1,tmpFile);
463 section_address = SWAP32(tmpHead.t_addr);
464 section_size = SWAP32(tmpHead.t_size);
465 mem = PSXM(section_address);
467 fseek(tmpFile, 0x800, SEEK_SET);
468 fread_to_ram(mem, section_size, 1, tmpFile);
469 psxCpu->Clear(section_address, section_size / 4);
472 psxRegs.pc = SWAP32(tmpHead.pc0);
473 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
474 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
475 if (psxRegs.GPR.n.sp == 0)
476 psxRegs.GPR.n.sp = 0x801fff00;
480 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
482 fread(&opcode, 1, 1, tmpFile);
484 case 1: /* Section loading */
485 fread(§ion_address, 4, 1, tmpFile);
486 fread(§ion_size, 4, 1, tmpFile);
487 section_address = SWAPu32(section_address);
488 section_size = SWAPu32(section_size);
490 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
492 mem = PSXM(section_address);
494 fread_to_ram(mem, section_size, 1, tmpFile);
495 psxCpu->Clear(section_address, section_size / 4);
498 case 3: /* register loading (PC only?) */
499 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
500 fread(&psxRegs.pc, 4, 1, tmpFile);
501 psxRegs.pc = SWAPu32(psxRegs.pc);
503 case 0: /* End of file */
506 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
510 } while (opcode != 0 && retval == 0);
513 SysPrintf(_("COFF files not supported.\n"));
517 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
518 SysPrintf(_("(did you forget -cdfile ?)\n"));
526 CdromLabel[0] = '\0';
534 static void *zlib_open(const char *name, const char *mode)
536 return gzopen(name, mode);
539 static int zlib_read(void *file, void *buf, u32 len)
541 return gzread(file, buf, len);
544 static int zlib_write(void *file, const void *buf, u32 len)
546 return gzwrite(file, buf, len);
549 static long zlib_seek(void *file, long offs, int whence)
551 return gzseek(file, offs, whence);
554 static void zlib_close(void *file)
559 struct PcsxSaveFuncs SaveFuncs = {
560 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
563 static const char PcsxHeader[32] = "STv4 PCSX v" PACKAGE_VERSION;
565 // Savestate Versioning!
566 // If you make changes to the savestate version, please increment the value below.
567 static const u32 SaveVersion = 0x8b410006;
569 int SaveState(const char *file) {
576 f = SaveFuncs.open(file, "wb");
577 if (f == NULL) return -1;
579 new_dyna_before_save();
581 SaveFuncs.write(f, (void *)PcsxHeader, 32);
582 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
583 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
585 pMem = (unsigned char *)malloc(128 * 96 * 3);
586 if (pMem == NULL) return -1;
587 GPU_getScreenPic(pMem);
588 SaveFuncs.write(f, pMem, 128 * 96 * 3);
594 SaveFuncs.write(f, psxM, 0x00200000);
595 SaveFuncs.write(f, psxR, 0x00080000);
596 SaveFuncs.write(f, psxH, 0x00010000);
597 SaveFuncs.write(f, (void *)&psxRegs, sizeof(psxRegs));
600 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
601 gpufP->ulFreezeVersion = 1;
602 GPU_freeze(1, gpufP);
603 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
607 spufP = (SPUFreeze_t *) malloc(16);
608 SPU_freeze(2, spufP, psxRegs.cycle);
609 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
611 spufP = (SPUFreeze_t *) malloc(Size);
612 SPU_freeze(1, spufP, psxRegs.cycle);
613 SaveFuncs.write(f, spufP, Size);
621 new_dyna_freeze(f, 1);
625 new_dyna_after_save();
630 int LoadState(const char *file) {
639 f = SaveFuncs.open(file, "rb");
640 if (f == NULL) return -1;
642 SaveFuncs.read(f, header, sizeof(header));
643 SaveFuncs.read(f, &version, sizeof(u32));
644 SaveFuncs.read(f, &hle, sizeof(boolean));
646 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
656 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
658 SaveFuncs.read(f, psxM, 0x00200000);
659 SaveFuncs.read(f, psxR, 0x00080000);
660 SaveFuncs.read(f, psxH, 0x00010000);
661 SaveFuncs.read(f, (void *)&psxRegs, sizeof(psxRegs));
667 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
668 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
669 GPU_freeze(0, gpufP);
671 if (HW_GPU_STATUS == 0)
672 HW_GPU_STATUS = GPU_readStatus();
675 SaveFuncs.read(f, &Size, 4);
676 spufP = (SPUFreeze_t *)malloc(Size);
677 SaveFuncs.read(f, spufP, Size);
678 SPU_freeze(0, spufP, psxRegs.cycle);
686 new_dyna_freeze(f, 0);
693 int CheckState(const char *file) {
699 f = SaveFuncs.open(file, "rb");
700 if (f == NULL) return -1;
702 SaveFuncs.read(f, header, sizeof(header));
703 SaveFuncs.read(f, &version, sizeof(u32));
704 SaveFuncs.read(f, &hle, sizeof(boolean));
708 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
714 // NET Function Helpers
717 if (NET_recvData == NULL || NET_sendData == NULL)
720 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
721 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
722 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
723 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
724 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
725 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
733 if (NET_recvData == NULL || NET_sendData == NULL)
736 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
737 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
738 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
739 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
740 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
745 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
746 if (tmp != Config.Cpu) {
749 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
750 else psxCpu = &psxRec;
754 if (psxCpu->Init() == -1) {
755 SysClose(); return -1;
763 // remove the leading and trailing spaces in a string
764 void trim(char *str) {
768 // skip leading blanks
769 while (str[pos] <= ' ' && str[pos] > 0)
773 *(dest++) = str[pos];
777 *(dest--) = '\0'; // store the null
779 // remove trailing blanks
780 while (dest >= str && *dest <= ' ' && *dest > 0)
784 // lookup table for crc calculation
785 static unsigned short crctab[256] = {
786 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
787 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
788 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
789 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
790 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
791 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
792 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
793 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
794 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
795 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
796 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
797 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
798 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
799 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
800 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
801 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
802 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
803 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
804 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
805 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
806 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
807 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
808 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
809 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
810 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
811 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
812 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
813 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
814 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
817 u16 calcCrc(u8 *d, int len) {
821 for (i = 0; i < len; i++) {
822 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);