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 #include "lightrec/plugin.h"
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 void mmssdd( char *b, char *p )
63 unsigned char *u = (void *)b;
64 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
65 #elif defined(__BIGENDIAN__)
66 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
68 int block = *((int*)b);
72 m = block / 4500; // minutes
73 block = block - m * 4500; // minutes rest
74 s = block / 75; // seconds
75 d = block - s * 75; // seconds rest
77 m = ((m / 10) << 4) | m % 10;
78 s = ((s / 10) << 4) | s % 10;
79 d = ((d / 10) << 4) | d % 10;
87 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
92 if (time[1] == 60) { \
97 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
100 if (CDR_readTrack(time) == -1) return -1; \
101 buf = (void *)CDR_getBuffer(); \
102 if (buf == NULL) return -1; \
103 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
105 #define READDIR(_dir) \
107 memcpy(_dir, buf + 12, 2048); \
111 memcpy(_dir + 2048, buf + 12, 2048);
113 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
114 struct iso_directory_record *dir;
120 // only try to scan if a filename is given
121 if (!strlen(filename)) return -1;
125 dir = (struct iso_directory_record*) &mdir[i];
126 if (dir->length[0] == 0) {
129 i += (u8)dir->length[0];
131 if (dir->flags[0] & 0x2) { // it's a dir
132 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
133 if (filename[dir->name_len[0]] != '\\') continue;
135 filename += dir->name_len[0] + 1;
137 mmssdd(dir->extent, (char *)time);
143 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
144 mmssdd(dir->extent, (char *)time);
153 static const unsigned int gpu_ctl_def[] = {
154 0x00000000, 0x01000000, 0x03000000, 0x04000000,
155 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
158 static const unsigned int gpu_data_def[] = {
159 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
160 0xe5001000, 0xe6000000,
161 0x02000000, 0x00000000, 0x01ff03ff,
164 static void fake_bios_gpu_setup(void)
168 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
169 GPU_writeStatus(gpu_ctl_def[i]);
171 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
172 GPU_writeData(gpu_data_def[i]);
177 struct iso_directory_record *dir;
182 // not the best place to do it, but since BIOS boot logo killer
183 // is just below, do it here
184 fake_bios_gpu_setup();
186 if (!Config.HLE && !Config.SlowBoot) {
188 psxRegs.pc = psxRegs.GPR.n.ra;
192 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
196 // skip head and sub, and go to the root directory record
197 dir = (struct iso_directory_record*) &buf[12+156];
199 mmssdd(dir->extent, (char*)time);
203 // Load SYSTEM.CNF and scan for the main executable
204 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
205 // if SYSTEM.CNF is missing, start an existing PSX.EXE
206 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
211 // read the SYSTEM.CNF
214 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
215 if (GetCdromFile(mdir, time, exename) == -1) {
216 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
217 if (GetCdromFile(mdir, time, exename) == -1) {
218 char *ptr = strstr((char *)buf + 12, "cdrom:");
221 while (*ptr == '\\' || *ptr == '/') ptr++;
222 strncpy(exename, ptr, 255);
225 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
227 if (GetCdromFile(mdir, time, exename) == -1)
234 // Read the EXE-Header
238 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
240 psxRegs.pc = SWAP32(tmpHead.pc0);
241 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
242 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
243 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
245 tmpHead.t_size = SWAP32(tmpHead.t_size);
246 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
248 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
251 // Read the rest of the main executable
252 while (tmpHead.t_size & ~2047) {
253 void *ptr = (void *)PSXM(tmpHead.t_addr);
258 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
260 tmpHead.t_size -= 2048;
261 tmpHead.t_addr += 2048;
267 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
268 struct iso_directory_record *dir;
275 sscanf(filename, "cdrom:\\%255s", exename);
277 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
281 // skip head and sub, and go to the root directory record
282 dir = (struct iso_directory_record *)&buf[12 + 156];
284 mmssdd(dir->extent, (char*)time);
288 if (GetCdromFile(mdir, time, exename) == -1) return -1;
292 memcpy(head, buf + 12, sizeof(EXE_HEADER));
296 psxCpu->Clear(addr, size / 4);
299 while (size & ~2047) {
304 if (mem != INVALID_PTR)
305 memcpy(mem, buf + 12, 2048);
315 struct iso_directory_record *dir;
316 unsigned char time[4];
318 unsigned char mdir[4096];
326 time[2] = itob(0x10);
330 memset(CdromLabel, 0, sizeof(CdromLabel));
331 memset(CdromId, 0, sizeof(CdromId));
332 memset(exename, 0, sizeof(exename));
334 strncpy(CdromLabel, buf + 52, 32);
336 // skip head and sub, and go to the root directory record
337 dir = (struct iso_directory_record *)&buf[12 + 156];
339 mmssdd(dir->extent, (char *)time);
343 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
346 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
347 if (GetCdromFile(mdir, time, exename) == -1) {
348 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
349 if (GetCdromFile(mdir, time, exename) == -1) {
350 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
353 while (*ptr == '\\' || *ptr == '/') ptr++;
354 strncpy(exename, ptr, 255);
357 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
359 if (GetCdromFile(mdir, time, exename) == -1)
360 return -1; // main executable not found
365 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
366 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
368 size_t i, len = strlen(exename) - offset;
369 for (i = 0; i < len; i++)
370 exename[i] = exename[i + offset];
373 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
374 strcpy(exename, "PSX.EXE;1");
375 strcpy(CdromId, "SLUS99999");
377 return -1; // SYSTEM.CNF and PSX.EXE not found
379 if (CdromId[0] == '\0') {
380 len = strlen(exename);
382 for (i = 0; i < len; ++i) {
383 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
385 if (isalnum(exename[i]))
386 CdromId[c++] = exename[i];
390 if (CdromId[0] == '\0')
391 strcpy(CdromId, "SLUS99999");
393 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
395 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
396 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
397 !strncmp(CdromId, "DTLS3035", 8) ||
398 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
399 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
400 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
401 Config.PsxType = PSX_TYPE_PAL; // pal
402 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
405 if (CdromLabel[0] == ' ') {
406 strncpy(CdromLabel, CdromId, 9);
408 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
409 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
410 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
419 static int PSXGetFileType(FILE *f) {
420 unsigned long current;
426 fseek(f, 0L, SEEK_SET);
427 if (fread(&mybuf, sizeof(mybuf), 1, f) != sizeof(mybuf))
430 fseek(f, current, SEEK_SET);
432 exe_hdr = (EXE_HEADER *)mybuf;
433 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
436 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
439 coff_hdr = (FILHDR *)mybuf;
440 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
447 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
452 // temporary pandora workaround..
454 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
459 tmp = malloc(size * nmemb);
461 ret = fread(tmp, size, nmemb, stream);
462 memcpy(ptr, tmp, size * nmemb);
468 int Load(const char *ExePath) {
474 u32 section_address, section_size;
477 strcpy(CdromId, "SLUS99999");
478 strcpy(CdromLabel, "SLUS_999.99");
480 tmpFile = fopen(ExePath, "rb");
481 if (tmpFile == NULL) {
482 SysPrintf(_("Error opening file: %s.\n"), ExePath);
485 type = PSXGetFileType(tmpFile);
488 if (fread(&tmpHead, sizeof(EXE_HEADER), 1, tmpFile) != sizeof(EXE_HEADER))
490 section_address = SWAP32(tmpHead.t_addr);
491 section_size = SWAP32(tmpHead.t_size);
492 mem = PSXM(section_address);
493 if (mem != INVALID_PTR) {
494 fseek(tmpFile, 0x800, SEEK_SET);
495 fread_to_ram(mem, section_size, 1, tmpFile);
496 psxCpu->Clear(section_address, section_size / 4);
498 psxRegs.pc = SWAP32(tmpHead.pc0);
499 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
500 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
501 if (psxRegs.GPR.n.sp == 0)
502 psxRegs.GPR.n.sp = 0x801fff00;
506 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
508 if (fread(&opcode, sizeof(opcode), 1, tmpFile) != sizeof(opcode))
511 case 1: /* Section loading */
512 if (fread(§ion_address, sizeof(section_address), 1, tmpFile) != sizeof(section_address))
514 if (fread(§ion_size, sizeof(section_size), 1, tmpFile) != sizeof(section_size))
516 section_address = SWAPu32(section_address);
517 section_size = SWAPu32(section_size);
519 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
521 mem = PSXM(section_address);
522 if (mem != INVALID_PTR) {
523 fread_to_ram(mem, section_size, 1, tmpFile);
524 psxCpu->Clear(section_address, section_size / 4);
527 case 3: /* register loading (PC only?) */
528 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
529 if (fread(&psxRegs.pc, sizeof(psxRegs.pc), 1, tmpFile) != sizeof(psxRegs.pc))
531 psxRegs.pc = SWAPu32(psxRegs.pc);
533 case 0: /* End of file */
536 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
540 } while (opcode != 0 && retval == 0);
543 SysPrintf(_("COFF files not supported.\n"));
547 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
548 SysPrintf(_("(did you forget -cdfile ?)\n"));
556 CdromLabel[0] = '\0';
564 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
572 static void *zlib_open(const char *name, const char *mode)
574 return gzopen(name, mode);
577 static int zlib_read(void *file, void *buf, u32 len)
579 return gzread(file, buf, len);
582 static int zlib_write(void *file, const void *buf, u32 len)
584 return gzwrite(file, buf, len);
587 static long zlib_seek(void *file, long offs, int whence)
589 return gzseek(file, offs, whence);
592 static void zlib_close(void *file)
597 struct PcsxSaveFuncs SaveFuncs = {
598 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
601 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
603 // Savestate Versioning!
604 // If you make changes to the savestate version, please increment the value below.
605 static const u32 SaveVersion = 0x8b410006;
607 int SaveState(const char *file) {
614 f = SaveFuncs.open(file, "wb");
615 if (f == NULL) return -1;
617 new_dyna_before_save();
619 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
620 lightrec_plugin_prepare_save_state();
622 SaveFuncs.write(f, (void *)PcsxHeader, 32);
623 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
624 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
626 pMem = (unsigned char *)malloc(128 * 96 * 3);
627 if (pMem == NULL) return -1;
628 GPU_getScreenPic(pMem);
629 SaveFuncs.write(f, pMem, 128 * 96 * 3);
635 SaveFuncs.write(f, psxM, 0x00200000);
636 SaveFuncs.write(f, psxR, 0x00080000);
637 SaveFuncs.write(f, psxH, 0x00010000);
638 // only partial save of psxRegisters to maintain savestate compat
639 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
642 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
643 gpufP->ulFreezeVersion = 1;
644 GPU_freeze(1, gpufP);
645 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
649 spufP = (SPUFreeze_t *) malloc(16);
650 SPU_freeze(2, spufP, psxRegs.cycle);
651 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
653 spufP = (SPUFreeze_t *) malloc(Size);
654 SPU_freeze(1, spufP, psxRegs.cycle);
655 SaveFuncs.write(f, spufP, Size);
663 new_dyna_freeze(f, 1);
667 new_dyna_after_save();
672 int LoadState(const char *file) {
681 f = SaveFuncs.open(file, "rb");
682 if (f == NULL) return -1;
684 SaveFuncs.read(f, header, sizeof(header));
685 SaveFuncs.read(f, &version, sizeof(u32));
686 SaveFuncs.read(f, &hle, sizeof(boolean));
688 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
697 if (!drc_is_lightrec() || Config.Cpu == CPU_INTERPRETER)
699 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
701 SaveFuncs.read(f, psxM, 0x00200000);
702 SaveFuncs.read(f, psxR, 0x00080000);
703 SaveFuncs.read(f, psxH, 0x00010000);
704 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
705 psxRegs.gteBusyCycle = psxRegs.cycle;
707 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
708 lightrec_plugin_prepare_load_state();
714 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
715 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
716 GPU_freeze(0, gpufP);
718 if (HW_GPU_STATUS == 0)
719 HW_GPU_STATUS = GPU_readStatus();
722 SaveFuncs.read(f, &Size, 4);
723 spufP = (SPUFreeze_t *)malloc(Size);
724 SaveFuncs.read(f, spufP, Size);
725 SPU_freeze(0, spufP, psxRegs.cycle);
733 new_dyna_freeze(f, 0);
740 int CheckState(const char *file) {
746 f = SaveFuncs.open(file, "rb");
747 if (f == NULL) return -1;
749 SaveFuncs.read(f, header, sizeof(header));
750 SaveFuncs.read(f, &version, sizeof(u32));
751 SaveFuncs.read(f, &hle, sizeof(boolean));
755 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
761 // NET Function Helpers
764 if (NET_recvData == NULL || NET_sendData == NULL)
767 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
768 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
769 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
770 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
771 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
772 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
780 if (NET_recvData == NULL || NET_sendData == NULL)
783 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
784 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
785 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
786 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
787 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
792 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
793 if (tmp != Config.Cpu) {
796 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
797 else psxCpu = &psxRec;
801 if (psxCpu->Init() == -1) {
802 SysClose(); return -1;
810 // remove the leading and trailing spaces in a string
811 void trim(char *str) {
815 // skip leading blanks
816 while (str[pos] <= ' ' && str[pos] > 0)
820 *(dest++) = str[pos];
824 *(dest--) = '\0'; // store the null
826 // remove trailing blanks
827 while (dest >= str && *dest <= ' ' && *dest > 0)
831 // lookup table for crc calculation
832 static unsigned short crctab[256] = {
833 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
834 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
835 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
836 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
837 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
838 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
839 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
840 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
841 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
842 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
843 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
844 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
845 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
846 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
847 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
848 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
849 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
850 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
851 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
852 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
853 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
854 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
855 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
856 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
857 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
858 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
859 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
860 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
861 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
864 u16 calcCrc(u8 *d, int len) {
868 for (i = 0; i < len; i++) {
869 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);