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 (!BiosBooted) return 0; // custom BIOS
200 if (psxRegs.pc != 0x80030000) return 0; // BiosBootBypass'ed
201 if (Config.SlowBoot) return 0;
204 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
208 // skip head and sub, and go to the root directory record
209 dir = (struct iso_directory_record*) &buf[12+156];
211 mmssdd(dir->extent, (char*)time);
215 // Load SYSTEM.CNF and scan for the main executable
216 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
217 // if SYSTEM.CNF is missing, start an existing PSX.EXE
218 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
219 strcpy(exename, "PSX.EXE;1");
224 // read the SYSTEM.CNF
227 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
228 if (GetCdromFile(mdir, time, exename) == -1) {
229 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
230 if (GetCdromFile(mdir, time, exename) == -1) {
231 char *ptr = strstr((char *)buf + 12, "cdrom:");
234 while (*ptr == '\\' || *ptr == '/') ptr++;
235 strncpy(exename, ptr, 255);
238 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
240 if (GetCdromFile(mdir, time, exename) == -1)
247 // Read the EXE-Header
251 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
253 SysPrintf("manual booting '%s'\n", exename);
254 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0), SWAP32(tmpHead.s_addr));
256 tmpHead.t_size = SWAP32(tmpHead.t_size);
257 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
259 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
262 // Read the rest of the main executable
263 while (tmpHead.t_size & ~2047) {
264 void *ptr = (void *)PSXM(tmpHead.t_addr);
269 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
271 tmpHead.t_size -= 2048;
272 tmpHead.t_addr += 2048;
278 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
279 struct iso_directory_record *dir;
286 sscanf(filename, "cdrom:\\%255s", exename);
288 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
292 // skip head and sub, and go to the root directory record
293 dir = (struct iso_directory_record *)&buf[12 + 156];
295 mmssdd(dir->extent, (char*)time);
299 if (GetCdromFile(mdir, time, exename) == -1) return -1;
303 memcpy(head, buf + 12, sizeof(EXE_HEADER));
307 psxCpu->Clear(addr, size / 4);
310 while (size & ~2047) {
315 if (mem != INVALID_PTR)
316 memcpy(mem, buf + 12, 2048);
326 struct iso_directory_record *dir;
327 unsigned char time[4];
329 unsigned char mdir[4096];
337 time[2] = itob(0x10);
341 memset(CdromLabel, 0, sizeof(CdromLabel));
342 memset(CdromId, 0, sizeof(CdromId));
343 memset(exename, 0, sizeof(exename));
345 strncpy(CdromLabel, buf + 52, 32);
347 // skip head and sub, and go to the root directory record
348 dir = (struct iso_directory_record *)&buf[12 + 156];
350 mmssdd(dir->extent, (char *)time);
354 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
357 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
358 if (GetCdromFile(mdir, time, exename) == -1) {
359 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
360 if (GetCdromFile(mdir, time, exename) == -1) {
361 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
364 while (*ptr == '\\' || *ptr == '/') ptr++;
365 strncpy(exename, ptr, 255);
368 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
370 if (GetCdromFile(mdir, time, exename) == -1)
371 return -1; // main executable not found
376 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
377 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
379 size_t i, len = strlen(exename) - offset;
380 for (i = 0; i < len; i++)
381 exename[i] = exename[i + offset];
384 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
385 strcpy(exename, "PSX.EXE;1");
386 strcpy(CdromId, "SLUS99999");
388 return -1; // SYSTEM.CNF and PSX.EXE not found
390 if (CdromId[0] == '\0') {
391 len = strlen(exename);
393 for (i = 0; i < len; ++i) {
394 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
396 if (isalnum(exename[i]))
397 CdromId[c++] = exename[i];
401 if (CdromId[0] == '\0')
402 strcpy(CdromId, "SLUS99999");
404 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
406 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
407 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
408 !strncmp(CdromId, "DTLS3035", 8) ||
409 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
410 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
411 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
412 Config.PsxType = PSX_TYPE_PAL; // pal
413 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
416 if (CdromLabel[0] == ' ') {
417 strncpy(CdromLabel, CdromId, 9);
419 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
420 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
421 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
430 static int PSXGetFileType(FILE *f) {
431 unsigned long current;
437 fseek(f, 0L, SEEK_SET);
438 if (fread(&mybuf, 1, sizeof(mybuf), f) != sizeof(mybuf))
441 fseek(f, current, SEEK_SET);
443 exe_hdr = (EXE_HEADER *)mybuf;
444 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
447 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
450 coff_hdr = (FILHDR *)mybuf;
451 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
458 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
463 // temporary pandora workaround..
465 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
470 tmp = malloc(size * nmemb);
472 ret = fread(tmp, size, nmemb, stream);
473 memcpy(ptr, tmp, size * nmemb);
479 int Load(const char *ExePath) {
485 u32 section_address, section_size;
488 strcpy(CdromId, "SLUS99999");
489 strcpy(CdromLabel, "SLUS_999.99");
491 tmpFile = fopen(ExePath, "rb");
492 if (tmpFile == NULL) {
493 SysPrintf(_("Error opening file: %s.\n"), ExePath);
496 type = PSXGetFileType(tmpFile);
499 if (fread(&tmpHead, 1, sizeof(EXE_HEADER), tmpFile) != sizeof(EXE_HEADER))
501 section_address = SWAP32(tmpHead.t_addr);
502 section_size = SWAP32(tmpHead.t_size);
503 mem = PSXM(section_address);
504 if (mem != INVALID_PTR) {
505 fseek(tmpFile, 0x800, SEEK_SET);
506 fread_to_ram(mem, section_size, 1, tmpFile);
507 psxCpu->Clear(section_address, section_size / 4);
509 SetBootRegs(SWAP32(tmpHead.pc0), SWAP32(tmpHead.gp0),
510 SWAP32(tmpHead.s_addr));
514 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
516 if (fread(&opcode, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
519 case 1: /* Section loading */
520 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
522 if (fread(§ion_size, 1, sizeof(section_size), tmpFile) != sizeof(section_size))
524 section_address = SWAPu32(section_address);
525 section_size = SWAPu32(section_size);
527 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
529 mem = PSXM(section_address);
530 if (mem != INVALID_PTR) {
531 fread_to_ram(mem, section_size, 1, tmpFile);
532 psxCpu->Clear(section_address, section_size / 4);
535 case 3: /* register loading (PC only?) */
536 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
537 if (fread(&psxRegs.pc, 1, sizeof(psxRegs.pc), tmpFile) != sizeof(psxRegs.pc))
539 psxRegs.pc = SWAPu32(psxRegs.pc);
541 case 0: /* End of file */
544 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
548 } while (opcode != 0 && retval == 0);
551 SysPrintf(_("COFF files not supported.\n"));
555 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
556 SysPrintf(_("(did you forget -cdfile ?)\n"));
564 CdromLabel[0] = '\0';
573 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
581 static void *zlib_open(const char *name, const char *mode)
583 return gzopen(name, mode);
586 static int zlib_read(void *file, void *buf, u32 len)
588 return gzread(file, buf, len);
591 static int zlib_write(void *file, const void *buf, u32 len)
593 return gzwrite(file, buf, len);
596 static long zlib_seek(void *file, long offs, int whence)
598 return gzseek(file, offs, whence);
601 static void zlib_close(void *file)
606 struct PcsxSaveFuncs SaveFuncs = {
607 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
610 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
612 // Savestate Versioning!
613 // If you make changes to the savestate version, please increment the value below.
614 static const u32 SaveVersion = 0x8b410006;
616 int SaveState(const char *file) {
619 SPUFreezeHdr_t *spufH;
624 f = SaveFuncs.open(file, "wb");
625 if (f == NULL) return -1;
627 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
629 SaveFuncs.write(f, (void *)PcsxHeader, 32);
630 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
631 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
633 pMem = (unsigned char *)malloc(128 * 96 * 3);
634 if (pMem == NULL) return -1;
635 GPU_getScreenPic(pMem);
636 SaveFuncs.write(f, pMem, 128 * 96 * 3);
642 SaveFuncs.write(f, psxM, 0x00200000);
643 SaveFuncs.write(f, psxR, 0x00080000);
644 SaveFuncs.write(f, psxH, 0x00010000);
645 // only partial save of psxRegisters to maintain savestate compat
646 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
649 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
650 gpufP->ulFreezeVersion = 1;
651 GPU_freeze(1, gpufP);
652 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
656 spufH = malloc(sizeof(*spufH));
657 SPU_freeze(2, (SPUFreeze_t *)spufH, psxRegs.cycle);
658 Size = spufH->Size; SaveFuncs.write(f, &Size, 4);
660 spufP = (SPUFreeze_t *) malloc(Size);
661 SPU_freeze(1, spufP, psxRegs.cycle);
662 SaveFuncs.write(f, spufP, Size);
670 new_dyna_freeze(f, 1);
677 int LoadState(const char *file) {
686 f = SaveFuncs.open(file, "rb");
687 if (f == NULL) return -1;
689 SaveFuncs.read(f, header, sizeof(header));
690 SaveFuncs.read(f, &version, sizeof(u32));
691 SaveFuncs.read(f, &hle, sizeof(boolean));
693 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
702 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
703 SaveFuncs.read(f, psxM, 0x00200000);
704 SaveFuncs.read(f, psxR, 0x00080000);
705 SaveFuncs.read(f, psxH, 0x00010000);
706 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
707 psxRegs.gteBusyCycle = psxRegs.cycle;
709 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
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)
769 boolean SpuIrq_old = 0;
770 boolean RCntFix_old = 0;
771 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
772 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
773 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
774 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
775 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
776 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
784 if (NET_recvData == NULL || NET_sendData == NULL)
788 boolean SpuIrq_old = 0;
789 boolean RCntFix_old = 0;
790 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
791 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
792 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
793 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
794 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
799 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
800 if (tmp != Config.Cpu) {
803 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
804 else psxCpu = &psxRec;
808 if (psxCpu->Init() == -1) {
809 SysClose(); return -1;
812 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
818 // remove the leading and trailing spaces in a string
819 void trim(char *str) {
823 // skip leading blanks
824 while (str[pos] <= ' ' && str[pos] > 0)
828 *(dest++) = str[pos];
832 *(dest--) = '\0'; // store the null
834 // remove trailing blanks
835 while (dest >= str && *dest <= ' ' && *dest > 0)
839 // lookup table for crc calculation
840 static unsigned short crctab[256] = {
841 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
842 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
843 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
844 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
845 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
846 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
847 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
848 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
849 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
850 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
851 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
852 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
853 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
854 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
855 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
856 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
857 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
858 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
859 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
860 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
861 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
862 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
863 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
864 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
865 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
866 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
867 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
868 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
869 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
872 u16 calcCrc(u8 *d, int len) {
876 for (i = 0; i < len; i++) {
877 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);