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.
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 static void mmssdd( char *b, char *p )
62 unsigned char *ub = (void *)b;
63 int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
66 m = block / 4500; // minutes
67 block = block - m * 4500; // minutes rest
68 s = block / 75; // seconds
69 d = block - s * 75; // seconds rest
71 m = ((m / 10) << 4) | m % 10;
72 s = ((s / 10) << 4) | s % 10;
73 d = ((d / 10) << 4) | d % 10;
81 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
86 if (time[1] == 60) { \
91 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
94 if (!CDR_readTrack(time)) return -1; \
95 buf = (void *)CDR_getBuffer(); \
96 if (buf == NULL) return -1; \
97 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
99 #define READDIR(_dir) \
101 memcpy(_dir, buf + 12, 2048); \
105 memcpy(_dir + 2048, buf + 12, 2048);
107 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
108 struct iso_directory_record *dir;
114 // only try to scan if a filename is given
115 if (!strlen(filename)) return -1;
119 dir = (struct iso_directory_record*) &mdir[i];
120 if (dir->length[0] == 0) {
123 i += (u8)dir->length[0];
125 if (dir->flags[0] & 0x2) { // it's a dir
126 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
127 if (filename[dir->name_len[0]] != '\\') continue;
129 filename += dir->name_len[0] + 1;
131 mmssdd(dir->extent, (char *)time);
137 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
138 mmssdd(dir->extent, (char *)time);
147 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
149 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
150 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
153 psxRegs.GPR.n.gp = gp;
154 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
155 psxRegs.GPR.n.fp = psxRegs.GPR.n.sp;
157 psxRegs.GPR.n.t0 = psxRegs.GPR.n.sp; // mimic A(43)
158 psxRegs.GPR.n.t3 = pc;
160 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
163 void BiosBootBypass() {
164 assert(psxRegs.pc == 0x80030000);
166 // skip BIOS logos and region check
167 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
168 psxRegs.pc = psxRegs.GPR.n.ra;
171 static void getFromCnf(char *buf, const char *key, u32 *val)
173 buf = strstr(buf, key);
175 buf = strchr(buf, '=');
177 *val = strtol(buf + 1, NULL, 16);
182 struct iso_directory_record *dir;
193 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
199 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
203 // skip head and sub, and go to the root directory record
204 dir = (struct iso_directory_record*) &buf[12+156];
206 mmssdd(dir->extent, (char*)time);
210 // Load SYSTEM.CNF and scan for the main executable
211 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
212 // if SYSTEM.CNF is missing, start an existing PSX.EXE
213 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
214 strcpy(exename, "PSX.EXE;1");
219 // read the SYSTEM.CNF
223 ret = sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
224 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
225 ret = sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
226 if (ret < 1 || GetCdromFile(mdir, time, exename) == -1) {
227 char *ptr = strstr((char *)buf + 12, "cdrom:");
230 while (*ptr == '\\' || *ptr == '/') ptr++;
231 strncpy(exename, ptr, 255);
234 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
236 if (GetCdromFile(mdir, time, exename) == -1)
242 getFromCnf((char *)buf + 12, "TCB", &cnf_tcb);
243 getFromCnf((char *)buf + 12, "EVENT", &cnf_event);
244 getFromCnf((char *)buf + 12, "STACK", &cnf_stack);
246 psxBiosCnfLoaded(cnf_tcb, cnf_event);
248 // Read the EXE-Header
252 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
254 SysPrintf("manual booting '%s' pc=%x\n", exename, SWAP32(tmpHead.pc0));
255 sp = SWAP32(tmpHead.s_addr);
258 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), sp);
260 tmpHead.t_size = SWAP32(tmpHead.t_size);
261 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
263 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
266 // Read the rest of the main executable
267 while (tmpHead.t_size & ~2047) {
268 void *ptr = (void *)PSXM(tmpHead.t_addr);
273 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
275 tmpHead.t_size -= 2048;
276 tmpHead.t_addr += 2048;
282 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
283 struct iso_directory_record *dir;
292 if ((p2 = strchr(p1, ':')))
296 snprintf(exename, sizeof(exename), "%s", p1);
298 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
302 // skip head and sub, and go to the root directory record
303 dir = (struct iso_directory_record *)&buf[12 + 156];
305 mmssdd(dir->extent, (char*)time);
309 if (GetCdromFile(mdir, time, exename) == -1) return -1;
313 memcpy(head, buf + 12, sizeof(EXE_HEADER));
317 psxCpu->Clear(addr, size / 4);
320 while (size & ~2047) {
325 if (mem != INVALID_PTR)
326 memcpy(mem, buf + 12, 2048);
336 struct iso_directory_record *dir;
337 unsigned char time[4];
339 unsigned char mdir[4096];
347 time[2] = itob(0x10);
351 memset(CdromLabel, 0, sizeof(CdromLabel));
352 memset(CdromId, 0, sizeof(CdromId));
353 memset(exename, 0, sizeof(exename));
355 strncpy(CdromLabel, buf + 52, 32);
357 // skip head and sub, and go to the root directory record
358 dir = (struct iso_directory_record *)&buf[12 + 156];
360 mmssdd(dir->extent, (char *)time);
364 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
367 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
368 if (GetCdromFile(mdir, time, exename) == -1) {
369 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
370 if (GetCdromFile(mdir, time, exename) == -1) {
371 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
374 while (*ptr == '\\' || *ptr == '/') ptr++;
375 strncpy(exename, ptr, 255);
378 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
380 if (GetCdromFile(mdir, time, exename) == -1)
381 return -1; // main executable not found
386 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
387 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
389 size_t i, len = strlen(exename) - offset;
390 for (i = 0; i < len; i++)
391 exename[i] = exename[i + offset];
394 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
395 strcpy(exename, "PSX.EXE;1");
396 strcpy(CdromId, "SLUS99999");
398 return -1; // SYSTEM.CNF and PSX.EXE not found
400 if (CdromId[0] == '\0') {
401 len = strlen(exename);
403 for (i = 0; i < len; ++i) {
404 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
406 if (isalnum(exename[i]))
407 CdromId[c++] = exename[i];
411 if (CdromId[0] == '\0')
412 strcpy(CdromId, "SLUS99999");
414 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
416 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
417 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
418 !strncmp(CdromId, "DTLS3035", 8) ||
419 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
420 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
421 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
422 Config.PsxType = PSX_TYPE_PAL; // pal
423 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
426 if (CdromLabel[0] == ' ') {
427 strncpy(CdromLabel, CdromId, 9);
429 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
430 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
431 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
440 static int PSXGetFileType(FILE *f) {
441 unsigned long current;
447 fseek(f, 0L, SEEK_SET);
448 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
451 fseek(f, current, SEEK_SET);
453 exe_hdr = (EXE_HEADER *)mybuf;
454 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
457 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
460 coff_hdr = (FILHDR *)mybuf;
461 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
468 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
473 // temporary pandora workaround..
475 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
480 tmp = malloc(size * nmemb);
482 ret = fread(tmp, size, nmemb, stream);
483 memcpy(ptr, tmp, size * nmemb);
489 int Load(const char *ExePath) {
495 u32 section_address, section_size;
498 strcpy(CdromId, "SLUS99999");
499 strcpy(CdromLabel, "SLUS_999.99");
501 tmpFile = fopen(ExePath, "rb");
502 if (tmpFile == NULL) {
503 SysPrintf(_("Error opening file: %s.\n"), ExePath);
506 type = PSXGetFileType(tmpFile);
509 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
511 section_address = SWAP32(tmpHead.t_addr);
512 section_size = SWAP32(tmpHead.t_size);
513 mem = PSXM(section_address);
514 if (mem != INVALID_PTR) {
515 fseek(tmpFile, 0x800, SEEK_SET);
516 fread_to_ram(mem, section_size, 1, tmpFile);
517 psxCpu->Clear(section_address, section_size / 4);
519 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
520 SWAP32(tmpHead.s_addr));
524 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
526 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
529 case 1: /* Section loading */
530 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
532 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
534 section_address = SWAPu32(section_address);
535 section_size = SWAPu32(section_size);
537 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
539 mem = PSXM(section_address);
540 if (mem != INVALID_PTR) {
541 fread_to_ram(mem, section_size, 1, tmpFile);
542 psxCpu->Clear(section_address, section_size / 4);
545 case 3: /* register loading (PC only?) */
546 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
547 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
549 psxRegs.pc = SWAPu32(psxRegs.pc);
551 case 0: /* End of file */
554 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
558 } while (opcode != 0 && retval == 0);
561 SysPrintf(_("COFF files not supported.\n"));
565 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
566 SysPrintf(_("(did you forget -cdfile ?)\n"));
574 CdromLabel[0] = '\0';
583 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
591 static void *zlib_open(const char *name, const char *mode)
593 return gzopen(name, mode);
596 static int zlib_read(void *file, void *buf, u32 len)
598 return gzread(file, buf, len);
601 static int zlib_write(void *file, const void *buf, u32 len)
603 return gzwrite(file, buf, len);
606 static long zlib_seek(void *file, long offs, int whence)
608 return gzseek(file, offs, whence);
611 static void zlib_close(void *file)
616 struct PcsxSaveFuncs SaveFuncs = {
617 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
620 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
622 // Savestate Versioning!
623 // If you make changes to the savestate version, please increment the value below.
624 static const u32 SaveVersion = 0x8b410006;
626 int SaveState(const char *file) {
629 SPUFreezeHdr_t *spufH;
634 f = SaveFuncs.open(file, "wb");
635 if (f == NULL) return -1;
637 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
639 SaveFuncs.write(f, (void *)PcsxHeader, 32);
640 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
641 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
643 pMem = (unsigned char *)malloc(128 * 96 * 3);
644 if (pMem == NULL) return -1;
645 GPU_getScreenPic(pMem);
646 SaveFuncs.write(f, pMem, 128 * 96 * 3);
652 SaveFuncs.write(f, psxM, 0x00200000);
653 SaveFuncs.write(f, psxR, 0x00080000);
654 SaveFuncs.write(f, psxH, 0x00010000);
655 // only partial save of psxRegisters to maintain savestate compat
656 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
659 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
660 gpufP->ulFreezeVersion = 1;
661 GPU_freeze(1, gpufP);
662 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
666 spufH = malloc(sizeof(*spufH));
667 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
668 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
670 spufP = (SPUFreeze_t *) malloc(Size);
671 SPU_freeze(1, spufP, psxRegs.cycle);
672 SaveFuncs.write(f, spufP, Size);
680 new_dyna_freeze(f, 1);
687 int LoadState(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));
703 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
712 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
713 SaveFuncs.read(f, psxM, 0x00200000);
714 SaveFuncs.read(f, psxR, 0x00080000);
715 SaveFuncs.read(f, psxH, 0x00010000);
716 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
717 psxRegs.gteBusyCycle = psxRegs.cycle;
719 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
725 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
726 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
727 GPU_freeze(0, gpufP);
729 if (HW_GPU_STATUS == 0)
730 HW_GPU_STATUS = SWAP32(GPU_readStatus());
733 SaveFuncs.read(f, &Size, 4);
734 spufP = (SPUFreeze_t *)malloc(Size);
735 SaveFuncs.read(f, spufP, Size);
736 SPU_freeze(0, spufP, psxRegs.cycle);
744 new_dyna_freeze(f, 0);
751 int CheckState(const char *file) {
757 f = SaveFuncs.open(file, "rb");
758 if (f == NULL) return -1;
760 SaveFuncs.read(f, header, sizeof(header));
761 SaveFuncs.read(f, &version, sizeof(u32));
762 SaveFuncs.read(f, &hle, sizeof(boolean));
766 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
772 // NET Function Helpers
775 if (NET_recvData == NULL || NET_sendData == NULL)
779 boolean SpuIrq_old = 0;
780 boolean RCntFix_old = 0;
781 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
782 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
783 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
784 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
785 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
786 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
794 if (NET_recvData == NULL || NET_sendData == NULL)
798 boolean SpuIrq_old = 0;
799 boolean RCntFix_old = 0;
800 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
801 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
802 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
803 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
804 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
807 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
808 if (tmp != Config.Cpu) {
811 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
812 else psxCpu = &psxRec;
816 if (psxCpu->Init() == -1) {
817 SysClose(); return -1;
820 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
826 // remove the leading and trailing spaces in a string
827 void trim(char *str) {
831 // skip leading blanks
832 while (str[pos] <= ' ' && str[pos] > 0)
836 *(dest++) = str[pos];
840 *(dest--) = '\0'; // store the null
842 // remove trailing blanks
843 while (dest >= str && *dest <= ' ' && *dest > 0)
847 // lookup table for crc calculation
848 static unsigned short crctab[256] = {
849 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
850 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
851 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
852 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
853 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
854 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
855 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
856 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
857 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
858 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
859 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
860 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
861 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
862 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
863 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
864 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
865 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
866 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
867 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
868 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
869 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
870 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
871 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
872 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
873 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
874 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
875 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
876 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
877 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
880 u16 calcCrc(u8 *d, int len) {
884 for (i = 0; i < len; i++) {
885 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);