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]);
169 static void SetBootRegs(u32 pc, u32 gp, u32 sp)
171 //printf("%s %08x %08x %08x\n", __func__, pc, gp, sp);
172 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
175 psxRegs.GPR.n.gp = gp;
176 psxRegs.GPR.n.sp = sp ? sp : 0x801fff00;
178 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
181 void BiosBootBypass() {
182 assert(psxRegs.pc == 0x80030000);
184 // skip BIOS logos and region check
185 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
186 psxRegs.pc = psxRegs.GPR.n.ra;
191 struct iso_directory_record *dir;
197 if (!BiosBooted) return 0; // custom BIOS
198 if (psxRegs.pc != 0x80030000) return 0; // BiosBootBypass'ed
199 if (Config.SlowBoot) return 0;
202 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
206 // skip head and sub, and go to the root directory record
207 dir = (struct iso_directory_record*) &buf[12+156];
209 mmssdd(dir->extent, (char*)time);
213 // Load SYSTEM.CNF and scan for the main executable
214 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
215 // if SYSTEM.CNF is missing, start an existing PSX.EXE
216 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
217 strcpy(exename, "PSX.EXE;1");
222 // read the SYSTEM.CNF
225 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
226 if (GetCdromFile(mdir, time, exename) == -1) {
227 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
228 if (GetCdromFile(mdir, time, exename) == -1) {
229 char *ptr = strstr((char *)buf + 12, "cdrom:");
232 while (*ptr == '\\' || *ptr == '/') ptr++;
233 strncpy(exename, ptr, 255);
236 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
238 if (GetCdromFile(mdir, time, exename) == -1)
245 // Read the EXE-Header
249 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
251 SysPrintf("manual booting '%s'\n", exename);
252 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), SWAP32(tmpHead.s_addr));
254 tmpHead.t_size = SWAP32(tmpHead.t_size);
255 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
257 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
260 // Read the rest of the main executable
261 while (tmpHead.t_size & ~2047) {
262 void *ptr = (void *)PSXM(tmpHead.t_addr);
267 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
269 tmpHead.t_size -= 2048;
270 tmpHead.t_addr += 2048;
276 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
277 struct iso_directory_record *dir;
284 sscanf(filename, "cdrom:\\%255s", exename);
286 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
290 // skip head and sub, and go to the root directory record
291 dir = (struct iso_directory_record *)&buf[12 + 156];
293 mmssdd(dir->extent, (char*)time);
297 if (GetCdromFile(mdir, time, exename) == -1) return -1;
301 memcpy(head, buf + 12, sizeof(EXE_HEADER));
305 psxCpu->Clear(addr, size / 4);
308 while (size & ~2047) {
313 if (mem != INVALID_PTR)
314 memcpy(mem, buf + 12, 2048);
324 struct iso_directory_record *dir;
325 unsigned char time[4];
327 unsigned char mdir[4096];
335 time[2] = itob(0x10);
339 memset(CdromLabel, 0, sizeof(CdromLabel));
340 memset(CdromId, 0, sizeof(CdromId));
341 memset(exename, 0, sizeof(exename));
343 strncpy(CdromLabel, buf + 52, 32);
345 // skip head and sub, and go to the root directory record
346 dir = (struct iso_directory_record *)&buf[12 + 156];
348 mmssdd(dir->extent, (char *)time);
352 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
355 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
356 if (GetCdromFile(mdir, time, exename) == -1) {
357 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
358 if (GetCdromFile(mdir, time, exename) == -1) {
359 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
362 while (*ptr == '\\' || *ptr == '/') ptr++;
363 strncpy(exename, ptr, 255);
366 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
368 if (GetCdromFile(mdir, time, exename) == -1)
369 return -1; // main executable not found
374 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
375 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
377 size_t i, len = strlen(exename) - offset;
378 for (i = 0; i < len; i++)
379 exename[i] = exename[i + offset];
382 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
383 strcpy(exename, "PSX.EXE;1");
384 strcpy(CdromId, "SLUS99999");
386 return -1; // SYSTEM.CNF and PSX.EXE not found
388 if (CdromId[0] == '\0') {
389 len = strlen(exename);
391 for (i = 0; i < len; ++i) {
392 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
394 if (isalnum(exename[i]))
395 CdromId[c++] = exename[i];
399 if (CdromId[0] == '\0')
400 strcpy(CdromId, "SLUS99999");
402 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
404 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
405 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
406 !strncmp(CdromId, "DTLS3035", 8) ||
407 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
408 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
409 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
410 Config.PsxType = PSX_TYPE_PAL; // pal
411 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
414 if (CdromLabel[0] == ' ') {
415 strncpy(CdromLabel, CdromId, 9);
417 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
418 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
419 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
428 static int PSXGetFileType(FILE *f) {
429 unsigned long current;
435 fseek(f, 0L, SEEK_SET);
436 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
439 fseek(f, current, SEEK_SET);
441 exe_hdr = (EXE_HEADER *)mybuf;
442 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
445 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
448 coff_hdr = (FILHDR *)mybuf;
449 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
456 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
461 // temporary pandora workaround..
463 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
468 tmp = malloc(size * nmemb);
470 ret = fread(tmp, size, nmemb, stream);
471 memcpy(ptr, tmp, size * nmemb);
477 int Load(const char *ExePath) {
483 u32 section_address, section_size;
486 strcpy(CdromId, "SLUS99999");
487 strcpy(CdromLabel, "SLUS_999.99");
489 tmpFile = fopen(ExePath, "rb");
490 if (tmpFile == NULL) {
491 SysPrintf(_("Error opening file: %s.\n"), ExePath);
494 type = PSXGetFileType(tmpFile);
497 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
499 section_address = SWAP32(tmpHead.t_addr);
500 section_size = SWAP32(tmpHead.t_size);
501 mem = PSXM(section_address);
502 if (mem != INVALID_PTR) {
503 fseek(tmpFile, 0x800, SEEK_SET);
504 fread_to_ram(mem, section_size, 1, tmpFile);
505 psxCpu->Clear(section_address, section_size / 4);
507 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
508 SWAP32(tmpHead.s_addr));
512 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
514 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
517 case 1: /* Section loading */
518 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
520 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
522 section_address = SWAPu32(section_address);
523 section_size = SWAPu32(section_size);
525 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
527 mem = PSXM(section_address);
528 if (mem != INVALID_PTR) {
529 fread_to_ram(mem, section_size, 1, tmpFile);
530 psxCpu->Clear(section_address, section_size / 4);
533 case 3: /* register loading (PC only?) */
534 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
535 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
537 psxRegs.pc = SWAPu32(psxRegs.pc);
539 case 0: /* End of file */
542 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
546 } while (opcode != 0 && retval == 0);
549 SysPrintf(_("COFF files not supported.\n"));
553 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
554 SysPrintf(_("(did you forget -cdfile ?)\n"));
562 CdromLabel[0] = '\0';
571 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
579 static void *zlib_open(const char *name, const char *mode)
581 return gzopen(name, mode);
584 static int zlib_read(void *file, void *buf, u32 len)
586 return gzread(file, buf, len);
589 static int zlib_write(void *file, const void *buf, u32 len)
591 return gzwrite(file, buf, len);
594 static long zlib_seek(void *file, long offs, int whence)
596 return gzseek(file, offs, whence);
599 static void zlib_close(void *file)
604 struct PcsxSaveFuncs SaveFuncs = {
605 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
608 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
610 // Savestate Versioning!
611 // If you make changes to the savestate version, please increment the value below.
612 static const u32 SaveVersion = 0x8b410006;
614 int SaveState(const char *file) {
617 SPUFreezeHdr_t *spufH;
622 f = SaveFuncs.open(file, "wb");
623 if (f == NULL) return -1;
625 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
627 SaveFuncs.write(f, (void *)PcsxHeader, 32);
628 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
629 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
631 pMem = (unsigned char *)malloc(128 * 96 * 3);
632 if (pMem == NULL) return -1;
633 GPU_getScreenPic(pMem);
634 SaveFuncs.write(f, pMem, 128 * 96 * 3);
640 SaveFuncs.write(f, psxM, 0x00200000);
641 SaveFuncs.write(f, psxR, 0x00080000);
642 SaveFuncs.write(f, psxH, 0x00010000);
643 // only partial save of psxRegisters to maintain savestate compat
644 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
647 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
648 gpufP->ulFreezeVersion = 1;
649 GPU_freeze(1, gpufP);
650 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
654 spufH = malloc(sizeof(*spufH));
655 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
656 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
658 spufP = (SPUFreeze_t *) malloc(Size);
659 SPU_freeze(1, spufP, psxRegs.cycle);
660 SaveFuncs.write(f, spufP, Size);
668 new_dyna_freeze(f, 1);
675 int LoadState(const char *file) {
684 f = SaveFuncs.open(file, "rb");
685 if (f == NULL) return -1;
687 SaveFuncs.read(f, header, sizeof(header));
688 SaveFuncs.read(f, &version, sizeof(u32));
689 SaveFuncs.read(f, &hle, sizeof(boolean));
691 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
700 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 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
713 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
714 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
715 GPU_freeze(0, gpufP);
717 if (HW_GPU_STATUS == 0)
718 HW_GPU_STATUS = SWAP32(GPU_readStatus());
721 SaveFuncs.read(f, &Size, 4);
722 spufP = (SPUFreeze_t *)malloc(Size);
723 SaveFuncs.read(f, spufP, Size);
724 SPU_freeze(0, spufP, psxRegs.cycle);
732 new_dyna_freeze(f, 0);
739 int CheckState(const char *file) {
745 f = SaveFuncs.open(file, "rb");
746 if (f == NULL) return -1;
748 SaveFuncs.read(f, header, sizeof(header));
749 SaveFuncs.read(f, &version, sizeof(u32));
750 SaveFuncs.read(f, &hle, sizeof(boolean));
754 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
760 // NET Function Helpers
763 if (NET_recvData == NULL || NET_sendData == NULL)
767 boolean SpuIrq_old = 0;
768 boolean RCntFix_old = 0;
769 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
770 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
771 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
772 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
773 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
774 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
782 if (NET_recvData == NULL || NET_sendData == NULL)
786 boolean SpuIrq_old = 0;
787 boolean RCntFix_old = 0;
788 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
789 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
790 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
791 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
792 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
797 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
798 if (tmp != Config.Cpu) {
801 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
802 else psxCpu = &psxRec;
806 if (psxCpu->Init() == -1) {
807 SysClose(); return -1;
810 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
816 // remove the leading and trailing spaces in a string
817 void trim(char *str) {
821 // skip leading blanks
822 while (str[pos] <= ' ' && str[pos] > 0)
826 *(dest++) = str[pos];
830 *(dest--) = '\0'; // store the null
832 // remove trailing blanks
833 while (dest >= str && *dest <= ' ' && *dest > 0)
837 // lookup table for crc calculation
838 static unsigned short crctab[256] = {
839 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
840 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
841 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
842 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
843 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
844 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
845 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
846 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
847 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
848 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
849 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
850 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
851 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
852 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
853 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
854 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
855 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
856 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
857 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
858 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
859 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
860 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
861 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
862 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
863 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
864 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
865 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
866 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
867 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
870 u16 calcCrc(u8 *d, int len) {
874 for (i = 0; i < len; i++) {
875 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);