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 )
61 int block = SWAP32(*((uint32_t*) b));
64 m = block / 4500; // minutes
65 block = block - m * 4500; // minutes rest
66 s = block / 75; // seconds
67 d = block - s * 75; // seconds rest
69 m = ((m / 10) << 4) | m % 10;
70 s = ((s / 10) << 4) | s % 10;
71 d = ((d / 10) << 4) | d % 10;
79 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
84 if (time[1] == 60) { \
89 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
92 if (CDR_readTrack(time) == -1) return -1; \
93 buf = (void *)CDR_getBuffer(); \
94 if (buf == NULL) return -1; \
95 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
97 #define READDIR(_dir) \
99 memcpy(_dir, buf + 12, 2048); \
103 memcpy(_dir + 2048, buf + 12, 2048);
105 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
106 struct iso_directory_record *dir;
112 // only try to scan if a filename is given
113 if (!strlen(filename)) return -1;
117 dir = (struct iso_directory_record*) &mdir[i];
118 if (dir->length[0] == 0) {
121 i += (u8)dir->length[0];
123 if (dir->flags[0] & 0x2) { // it's a dir
124 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
125 if (filename[dir->name_len[0]] != '\\') continue;
127 filename += dir->name_len[0] + 1;
129 mmssdd(dir->extent, (char *)time);
135 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
136 mmssdd(dir->extent, (char *)time);
145 static const unsigned int gpu_ctl_def[] = {
146 0x00000000, 0x01000000, 0x03000000, 0x04000000,
147 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
150 static const unsigned int gpu_data_def[] = {
151 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
152 0xe5001000, 0xe6000000,
153 0x02000000, 0x00000000, 0x01ff03ff,
156 static void fake_bios_gpu_setup(void)
160 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
161 GPU_writeStatus(gpu_ctl_def[i]);
163 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
164 GPU_writeData(gpu_data_def[i]);
169 struct iso_directory_record *dir;
174 // not the best place to do it, but since BIOS boot logo killer
175 // is just below, do it here
176 fake_bios_gpu_setup();
178 if (!Config.HLE && !Config.SlowBoot) {
180 psxRegs.pc = psxRegs.GPR.n.ra;
184 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
188 // skip head and sub, and go to the root directory record
189 dir = (struct iso_directory_record*) &buf[12+156];
191 mmssdd(dir->extent, (char*)time);
195 // Load SYSTEM.CNF and scan for the main executable
196 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
197 // if SYSTEM.CNF is missing, start an existing PSX.EXE
198 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
203 // read the SYSTEM.CNF
206 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
207 if (GetCdromFile(mdir, time, exename) == -1) {
208 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
209 if (GetCdromFile(mdir, time, exename) == -1) {
210 char *ptr = strstr((char *)buf + 12, "cdrom:");
213 while (*ptr == '\\' || *ptr == '/') ptr++;
214 strncpy(exename, ptr, 255);
217 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
219 if (GetCdromFile(mdir, time, exename) == -1)
226 // Read the EXE-Header
230 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
232 psxRegs.pc = SWAP32(tmpHead.pc0);
233 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
234 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
235 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
237 tmpHead.t_size = SWAP32(tmpHead.t_size);
238 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
240 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
243 // Read the rest of the main executable
244 while (tmpHead.t_size & ~2047) {
245 void *ptr = (void *)PSXM(tmpHead.t_addr);
250 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
252 tmpHead.t_size -= 2048;
253 tmpHead.t_addr += 2048;
259 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
260 struct iso_directory_record *dir;
267 sscanf(filename, "cdrom:\\%255s", exename);
269 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
273 // skip head and sub, and go to the root directory record
274 dir = (struct iso_directory_record *)&buf[12 + 156];
276 mmssdd(dir->extent, (char*)time);
280 if (GetCdromFile(mdir, time, exename) == -1) return -1;
284 memcpy(head, buf + 12, sizeof(EXE_HEADER));
288 psxCpu->Clear(addr, size / 4);
291 while (size & ~2047) {
296 if (mem != INVALID_PTR)
297 memcpy(mem, buf + 12, 2048);
307 struct iso_directory_record *dir;
308 unsigned char time[4];
310 unsigned char mdir[4096];
318 time[2] = itob(0x10);
322 memset(CdromLabel, 0, sizeof(CdromLabel));
323 memset(CdromId, 0, sizeof(CdromId));
324 memset(exename, 0, sizeof(exename));
326 strncpy(CdromLabel, buf + 52, 32);
328 // skip head and sub, and go to the root directory record
329 dir = (struct iso_directory_record *)&buf[12 + 156];
331 mmssdd(dir->extent, (char *)time);
335 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
338 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
339 if (GetCdromFile(mdir, time, exename) == -1) {
340 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
341 if (GetCdromFile(mdir, time, exename) == -1) {
342 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
345 while (*ptr == '\\' || *ptr == '/') ptr++;
346 strncpy(exename, ptr, 255);
349 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
351 if (GetCdromFile(mdir, time, exename) == -1)
352 return -1; // main executable not found
357 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
358 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
360 size_t i, len = strlen(exename) - offset;
361 for (i = 0; i < len; i++)
362 exename[i] = exename[i + offset];
365 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
366 strcpy(exename, "PSX.EXE;1");
367 strcpy(CdromId, "SLUS99999");
369 return -1; // SYSTEM.CNF and PSX.EXE not found
371 if (CdromId[0] == '\0') {
372 len = strlen(exename);
374 for (i = 0; i < len; ++i) {
375 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
377 if (isalnum(exename[i]))
378 CdromId[c++] = exename[i];
382 if (CdromId[0] == '\0')
383 strcpy(CdromId, "SLUS99999");
385 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
387 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
388 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
389 !strncmp(CdromId, "DTLS3035", 8) ||
390 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
391 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
392 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
393 Config.PsxType = PSX_TYPE_PAL; // pal
394 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
397 if (CdromLabel[0] == ' ') {
398 strncpy(CdromLabel, CdromId, 9);
400 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
401 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
402 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
411 static int PSXGetFileType(FILE *f) {
412 unsigned long current;
418 fseek(f, 0L, SEEK_SET);
419 if (fread(&mybuf, sizeof(mybuf), 1, f) != sizeof(mybuf))
422 fseek(f, current, SEEK_SET);
424 exe_hdr = (EXE_HEADER *)mybuf;
425 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
428 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
431 coff_hdr = (FILHDR *)mybuf;
432 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
439 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
444 // temporary pandora workaround..
446 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
451 tmp = malloc(size * nmemb);
453 ret = fread(tmp, size, nmemb, stream);
454 memcpy(ptr, tmp, size * nmemb);
460 int Load(const char *ExePath) {
466 u32 section_address, section_size;
469 strcpy(CdromId, "SLUS99999");
470 strcpy(CdromLabel, "SLUS_999.99");
472 tmpFile = fopen(ExePath, "rb");
473 if (tmpFile == NULL) {
474 SysPrintf(_("Error opening file: %s.\n"), ExePath);
477 type = PSXGetFileType(tmpFile);
480 if (fread(&tmpHead, sizeof(EXE_HEADER), 1, tmpFile) != sizeof(EXE_HEADER))
482 section_address = SWAP32(tmpHead.t_addr);
483 section_size = SWAP32(tmpHead.t_size);
484 mem = PSXM(section_address);
485 if (mem != INVALID_PTR) {
486 fseek(tmpFile, 0x800, SEEK_SET);
487 fread_to_ram(mem, section_size, 1, tmpFile);
488 psxCpu->Clear(section_address, section_size / 4);
490 psxRegs.pc = SWAP32(tmpHead.pc0);
491 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
492 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
493 if (psxRegs.GPR.n.sp == 0)
494 psxRegs.GPR.n.sp = 0x801fff00;
498 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
500 if (fread(&opcode, sizeof(opcode), 1, tmpFile) != sizeof(opcode))
503 case 1: /* Section loading */
504 if (fread(§ion_address, sizeof(section_address), 1, tmpFile) != sizeof(section_address))
506 if (fread(§ion_size, sizeof(section_size), 1, tmpFile) != sizeof(section_size))
508 section_address = SWAPu32(section_address);
509 section_size = SWAPu32(section_size);
511 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
513 mem = PSXM(section_address);
514 if (mem != INVALID_PTR) {
515 fread_to_ram(mem, section_size, 1, tmpFile);
516 psxCpu->Clear(section_address, section_size / 4);
519 case 3: /* register loading (PC only?) */
520 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
521 if (fread(&psxRegs.pc, sizeof(psxRegs.pc), 1, tmpFile) != sizeof(psxRegs.pc))
523 psxRegs.pc = SWAPu32(psxRegs.pc);
525 case 0: /* End of file */
528 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
532 } while (opcode != 0 && retval == 0);
535 SysPrintf(_("COFF files not supported.\n"));
539 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
540 SysPrintf(_("(did you forget -cdfile ?)\n"));
548 CdromLabel[0] = '\0';
556 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
564 static void *zlib_open(const char *name, const char *mode)
566 return gzopen(name, mode);
569 static int zlib_read(void *file, void *buf, u32 len)
571 return gzread(file, buf, len);
574 static int zlib_write(void *file, const void *buf, u32 len)
576 return gzwrite(file, buf, len);
579 static long zlib_seek(void *file, long offs, int whence)
581 return gzseek(file, offs, whence);
584 static void zlib_close(void *file)
589 struct PcsxSaveFuncs SaveFuncs = {
590 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
593 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
595 // Savestate Versioning!
596 // If you make changes to the savestate version, please increment the value below.
597 static const u32 SaveVersion = 0x8b410006;
599 static int drc_is_lightrec(void)
601 #if defined(LIGHTREC)
608 int SaveState(const char *file) {
615 f = SaveFuncs.open(file, "wb");
616 if (f == NULL) return -1;
618 new_dyna_before_save();
620 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
621 lightrec_plugin_prepare_save_state();
623 SaveFuncs.write(f, (void *)PcsxHeader, 32);
624 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
625 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
627 pMem = (unsigned char *)malloc(128 * 96 * 3);
628 if (pMem == NULL) return -1;
629 GPU_getScreenPic(pMem);
630 SaveFuncs.write(f, pMem, 128 * 96 * 3);
636 SaveFuncs.write(f, psxM, 0x00200000);
637 SaveFuncs.write(f, psxR, 0x00080000);
638 SaveFuncs.write(f, psxH, 0x00010000);
639 // only partial save of psxRegisters to maintain savestate compat
640 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
643 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
644 gpufP->ulFreezeVersion = 1;
645 GPU_freeze(1, gpufP);
646 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
650 spufP = (SPUFreeze_t *) malloc(16);
651 SPU_freeze(2, spufP, psxRegs.cycle);
652 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
654 spufP = (SPUFreeze_t *) malloc(Size);
655 SPU_freeze(1, spufP, psxRegs.cycle);
656 SaveFuncs.write(f, spufP, Size);
664 new_dyna_freeze(f, 1);
668 new_dyna_after_save();
673 int LoadState(const char *file) {
682 f = SaveFuncs.open(file, "rb");
683 if (f == NULL) return -1;
685 SaveFuncs.read(f, header, sizeof(header));
686 SaveFuncs.read(f, &version, sizeof(u32));
687 SaveFuncs.read(f, &hle, sizeof(boolean));
689 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
698 if (!drc_is_lightrec() || Config.Cpu == CPU_INTERPRETER)
700 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
702 SaveFuncs.read(f, psxM, 0x00200000);
703 SaveFuncs.read(f, psxR, 0x00080000);
704 SaveFuncs.read(f, psxH, 0x00010000);
705 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
706 psxRegs.gteBusyCycle = psxRegs.cycle;
708 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
709 lightrec_plugin_prepare_load_state();
715 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
716 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
717 GPU_freeze(0, gpufP);
719 if (HW_GPU_STATUS == 0)
720 HW_GPU_STATUS = SWAP32(GPU_readStatus());
723 SaveFuncs.read(f, &Size, 4);
724 spufP = (SPUFreeze_t *)malloc(Size);
725 SaveFuncs.read(f, spufP, Size);
726 SPU_freeze(0, spufP, psxRegs.cycle);
734 new_dyna_freeze(f, 0);
741 int CheckState(const char *file) {
747 f = SaveFuncs.open(file, "rb");
748 if (f == NULL) return -1;
750 SaveFuncs.read(f, header, sizeof(header));
751 SaveFuncs.read(f, &version, sizeof(u32));
752 SaveFuncs.read(f, &hle, sizeof(boolean));
756 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
762 // NET Function Helpers
765 if (NET_recvData == NULL || NET_sendData == NULL)
768 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
769 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
770 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
771 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
772 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
773 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
781 if (NET_recvData == NULL || NET_sendData == NULL)
784 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
785 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
786 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
787 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
788 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
793 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
794 if (tmp != Config.Cpu) {
797 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
798 else psxCpu = &psxRec;
802 if (psxCpu->Init() == -1) {
803 SysClose(); return -1;
811 // remove the leading and trailing spaces in a string
812 void trim(char *str) {
816 // skip leading blanks
817 while (str[pos] <= ' ' && str[pos] > 0)
821 *(dest++) = str[pos];
825 *(dest--) = '\0'; // store the null
827 // remove trailing blanks
828 while (dest >= str && *dest <= ' ' && *dest > 0)
832 // lookup table for crc calculation
833 static unsigned short crctab[256] = {
834 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
835 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
836 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
837 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
838 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
839 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
840 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
841 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
842 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
843 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
844 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
845 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
846 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
847 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
848 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
849 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
850 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
851 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
852 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
853 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
854 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
855 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
856 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
857 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
858 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
859 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
860 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
861 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
862 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
865 u16 calcCrc(u8 *d, int len) {
869 for (i = 0; i < len; i++) {
870 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);