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 const unsigned int gpu_ctl_def[] = {
148 0x00000000, 0x01000000, 0x03000000, 0x04000000,
149 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
152 static const unsigned int gpu_data_def[] = {
153 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
154 0xe5001000, 0xe6000000,
155 0x02000000, 0x00000000, 0x01ff03ff,
158 void BiosLikeGPUSetup()
162 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
163 GPU_writeStatus(gpu_ctl_def[i]);
165 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
166 GPU_writeData(gpu_data_def[i]);
168 HW_GPU_STATUS |= SWAP32(PSXGPU_nBUSY);
171 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
173 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
174 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
177 psxRegs.GPR.n.gp = gp;
178 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
180 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
183 void BiosBootBypass() {
184 assert(psxRegs.pc == 0x80030000);
186 // skip BIOS logos and region check
187 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
188 psxRegs.pc = psxRegs.GPR.n.ra;
193 struct iso_directory_record *dir;
199 if (psxRegs.pc != 0x80030000) // BiosBootBypass'ed or custom BIOS?
205 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
209 // skip head and sub, and go to the root directory record
210 dir = (struct iso_directory_record*) &buf[12+156];
212 mmssdd(dir->extent, (char*)time);
216 // Load SYSTEM.CNF and scan for the main executable
217 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
218 // if SYSTEM.CNF is missing, start an existing PSX.EXE
219 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
220 strcpy(exename, "PSX.EXE;1");
225 // read the SYSTEM.CNF
228 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
229 if (GetCdromFile(mdir, time, exename) == -1) {
230 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
231 if (GetCdromFile(mdir, time, exename) == -1) {
232 char *ptr = strstr((char *)buf + 12, "cdrom:");
235 while (*ptr == '\\' || *ptr == '/') ptr++;
236 strncpy(exename, ptr, 255);
239 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
241 if (GetCdromFile(mdir, time, exename) == -1)
248 // Read the EXE-Header
252 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
254 SysPrintf("manual booting '%s'\n", exename);
255 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), SWAP32(tmpHead.s_addr));
257 tmpHead.t_size = SWAP32(tmpHead.t_size);
258 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
260 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
263 // Read the rest of the main executable
264 while (tmpHead.t_size & ~2047) {
265 void *ptr = (void *)PSXM(tmpHead.t_addr);
270 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
272 tmpHead.t_size -= 2048;
273 tmpHead.t_addr += 2048;
279 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
280 struct iso_directory_record *dir;
287 sscanf(filename, "cdrom:\\%255s", exename);
289 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
293 // skip head and sub, and go to the root directory record
294 dir = (struct iso_directory_record *)&buf[12 + 156];
296 mmssdd(dir->extent, (char*)time);
300 if (GetCdromFile(mdir, time, exename) == -1) return -1;
304 memcpy(head, buf + 12, sizeof(EXE_HEADER));
308 psxCpu->Clear(addr, size / 4);
311 while (size & ~2047) {
316 if (mem != INVALID_PTR)
317 memcpy(mem, buf + 12, 2048);
327 struct iso_directory_record *dir;
328 unsigned char time[4];
330 unsigned char mdir[4096];
338 time[2] = itob(0x10);
342 memset(CdromLabel, 0, sizeof(CdromLabel));
343 memset(CdromId, 0, sizeof(CdromId));
344 memset(exename, 0, sizeof(exename));
346 strncpy(CdromLabel, buf + 52, 32);
348 // skip head and sub, and go to the root directory record
349 dir = (struct iso_directory_record *)&buf[12 + 156];
351 mmssdd(dir->extent, (char *)time);
355 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
358 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
359 if (GetCdromFile(mdir, time, exename) == -1) {
360 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
361 if (GetCdromFile(mdir, time, exename) == -1) {
362 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
365 while (*ptr == '\\' || *ptr == '/') ptr++;
366 strncpy(exename, ptr, 255);
369 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
371 if (GetCdromFile(mdir, time, exename) == -1)
372 return -1; // main executable not found
377 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
378 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
380 size_t i, len = strlen(exename) - offset;
381 for (i = 0; i < len; i++)
382 exename[i] = exename[i + offset];
385 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
386 strcpy(exename, "PSX.EXE;1");
387 strcpy(CdromId, "SLUS99999");
389 return -1; // SYSTEM.CNF and PSX.EXE not found
391 if (CdromId[0] == '\0') {
392 len = strlen(exename);
394 for (i = 0; i < len; ++i) {
395 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
397 if (isalnum(exename[i]))
398 CdromId[c++] = exename[i];
402 if (CdromId[0] == '\0')
403 strcpy(CdromId, "SLUS99999");
405 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
407 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
408 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
409 !strncmp(CdromId, "DTLS3035", 8) ||
410 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
411 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
412 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
413 Config.PsxType = PSX_TYPE_PAL; // pal
414 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
417 if (CdromLabel[0] == ' ') {
418 strncpy(CdromLabel, CdromId, 9);
420 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
421 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
422 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
431 static int PSXGetFileType(FILE *f) {
432 unsigned long current;
438 fseek(f, 0L, SEEK_SET);
439 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
442 fseek(f, current, SEEK_SET);
444 exe_hdr = (EXE_HEADER *)mybuf;
445 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
448 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
451 coff_hdr = (FILHDR *)mybuf;
452 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
459 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
464 // temporary pandora workaround..
466 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
471 tmp = malloc(size * nmemb);
473 ret = fread(tmp, size, nmemb, stream);
474 memcpy(ptr, tmp, size * nmemb);
480 int Load(const char *ExePath) {
486 u32 section_address, section_size;
489 strcpy(CdromId, "SLUS99999");
490 strcpy(CdromLabel, "SLUS_999.99");
492 tmpFile = fopen(ExePath, "rb");
493 if (tmpFile == NULL) {
494 SysPrintf(_("Error opening file: %s.\n"), ExePath);
497 type = PSXGetFileType(tmpFile);
500 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
502 section_address = SWAP32(tmpHead.t_addr);
503 section_size = SWAP32(tmpHead.t_size);
504 mem = PSXM(section_address);
505 if (mem != INVALID_PTR) {
506 fseek(tmpFile, 0x800, SEEK_SET);
507 fread_to_ram(mem, section_size, 1, tmpFile);
508 psxCpu->Clear(section_address, section_size / 4);
510 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
511 SWAP32(tmpHead.s_addr));
515 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
517 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
520 case 1: /* Section loading */
521 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
523 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
525 section_address = SWAPu32(section_address);
526 section_size = SWAPu32(section_size);
528 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
530 mem = PSXM(section_address);
531 if (mem != INVALID_PTR) {
532 fread_to_ram(mem, section_size, 1, tmpFile);
533 psxCpu->Clear(section_address, section_size / 4);
536 case 3: /* register loading (PC only?) */
537 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
538 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
540 psxRegs.pc = SWAPu32(psxRegs.pc);
542 case 0: /* End of file */
545 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
549 } while (opcode != 0 && retval == 0);
552 SysPrintf(_("COFF files not supported.\n"));
556 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
557 SysPrintf(_("(did you forget -cdfile ?)\n"));
565 CdromLabel[0] = '\0';
574 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
582 static void *zlib_open(const char *name, const char *mode)
584 return gzopen(name, mode);
587 static int zlib_read(void *file, void *buf, u32 len)
589 return gzread(file, buf, len);
592 static int zlib_write(void *file, const void *buf, u32 len)
594 return gzwrite(file, buf, len);
597 static long zlib_seek(void *file, long offs, int whence)
599 return gzseek(file, offs, whence);
602 static void zlib_close(void *file)
607 struct PcsxSaveFuncs SaveFuncs = {
608 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
611 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
613 // Savestate Versioning!
614 // If you make changes to the savestate version, please increment the value below.
615 static const u32 SaveVersion = 0x8b410006;
617 int SaveState(const char *file) {
620 SPUFreezeHdr_t *spufH;
625 f = SaveFuncs.open(file, "wb");
626 if (f == NULL) return -1;
628 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
630 SaveFuncs.write(f, (void *)PcsxHeader, 32);
631 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
632 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
634 pMem = (unsigned char *)malloc(128 * 96 * 3);
635 if (pMem == NULL) return -1;
636 GPU_getScreenPic(pMem);
637 SaveFuncs.write(f, pMem, 128 * 96 * 3);
643 SaveFuncs.write(f, psxM, 0x00200000);
644 SaveFuncs.write(f, psxR, 0x00080000);
645 SaveFuncs.write(f, psxH, 0x00010000);
646 // only partial save of psxRegisters to maintain savestate compat
647 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
650 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
651 gpufP->ulFreezeVersion = 1;
652 GPU_freeze(1, gpufP);
653 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
657 spufH = malloc(sizeof(*spufH));
658 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
659 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
661 spufP = (SPUFreeze_t *) malloc(Size);
662 SPU_freeze(1, spufP, psxRegs.cycle);
663 SaveFuncs.write(f, spufP, Size);
671 new_dyna_freeze(f, 1);
678 int LoadState(const char *file) {
687 f = SaveFuncs.open(file, "rb");
688 if (f == NULL) return -1;
690 SaveFuncs.read(f, header, sizeof(header));
691 SaveFuncs.read(f, &version, sizeof(u32));
692 SaveFuncs.read(f, &hle, sizeof(boolean));
694 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
703 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
704 SaveFuncs.read(f, psxM, 0x00200000);
705 SaveFuncs.read(f, psxR, 0x00080000);
706 SaveFuncs.read(f, psxH, 0x00010000);
707 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
708 psxRegs.gteBusyCycle = psxRegs.cycle;
710 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
716 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
717 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
718 GPU_freeze(0, gpufP);
720 if (HW_GPU_STATUS == 0)
721 HW_GPU_STATUS = SWAP32(GPU_readStatus());
724 SaveFuncs.read(f, &Size, 4);
725 spufP = (SPUFreeze_t *)malloc(Size);
726 SaveFuncs.read(f, spufP, Size);
727 SPU_freeze(0, spufP, psxRegs.cycle);
735 new_dyna_freeze(f, 0);
742 int CheckState(const char *file) {
748 f = SaveFuncs.open(file, "rb");
749 if (f == NULL) return -1;
751 SaveFuncs.read(f, header, sizeof(header));
752 SaveFuncs.read(f, &version, sizeof(u32));
753 SaveFuncs.read(f, &hle, sizeof(boolean));
757 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
763 // NET Function Helpers
766 if (NET_recvData == NULL || NET_sendData == NULL)
770 boolean SpuIrq_old = 0;
771 boolean RCntFix_old = 0;
772 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
773 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
774 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
775 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
776 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
777 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
785 if (NET_recvData == NULL || NET_sendData == NULL)
789 boolean SpuIrq_old = 0;
790 boolean RCntFix_old = 0;
791 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
792 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
793 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
794 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
795 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
800 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
801 if (tmp != Config.Cpu) {
804 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
805 else psxCpu = &psxRec;
809 if (psxCpu->Init() == -1) {
810 SysClose(); return -1;
813 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
819 // remove the leading and trailing spaces in a string
820 void trim(char *str) {
824 // skip leading blanks
825 while (str[pos] <= ' ' && str[pos] > 0)
829 *(dest++) = str[pos];
833 *(dest--) = '\0'; // store the null
835 // remove trailing blanks
836 while (dest >= str && *dest <= ' ' && *dest > 0)
840 // lookup table for crc calculation
841 static unsigned short crctab[256] = {
842 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
843 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
844 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
845 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
846 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
847 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
848 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
849 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
850 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
851 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
852 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
853 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
854 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
855 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
856 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
857 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
858 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
859 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
860 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
861 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
862 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
863 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
864 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
865 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
866 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
867 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
868 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
869 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
870 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
873 u16 calcCrc(u8 *d, int len) {
877 for (i = 0; i < len; i++) {
878 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);