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.
33 char CdromId[10] = "";
34 char CdromLabel[33] = "";
36 // PSX Executable types
42 #define ISODCL(from, to) (to - from + 1)
44 struct iso_directory_record {
45 char length [ISODCL (1, 1)]; /* 711 */
46 char ext_attr_length [ISODCL (2, 2)]; /* 711 */
47 char extent [ISODCL (3, 10)]; /* 733 */
48 char size [ISODCL (11, 18)]; /* 733 */
49 char date [ISODCL (19, 25)]; /* 7 by 711 */
50 char flags [ISODCL (26, 26)];
51 char file_unit_size [ISODCL (27, 27)]; /* 711 */
52 char interleave [ISODCL (28, 28)]; /* 711 */
53 char volume_sequence_number [ISODCL (29, 32)]; /* 723 */
54 unsigned char name_len [ISODCL (33, 33)]; /* 711 */
58 void mmssdd( char *b, char *p )
62 unsigned char *u = (void *)b;
63 int block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
64 #elif defined(__BIGENDIAN__)
65 int block = (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16) | (b[3] << 24);
67 int block = *((int*)b);
71 m = block / 4500; // minutes
72 block = block - m * 4500; // minutes rest
73 s = block / 75; // seconds
74 d = block - s * 75; // seconds rest
76 m = ((m / 10) << 4) | m % 10;
77 s = ((s / 10) << 4) | s % 10;
78 d = ((d / 10) << 4) | d % 10;
86 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
91 if (time[1] == 60) { \
96 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
99 if (CDR_readTrack(time) == -1) return -1; \
100 buf = (void *)CDR_getBuffer(); \
101 if (buf == NULL) return -1; \
102 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
104 #define READDIR(_dir) \
106 memcpy(_dir, buf + 12, 2048); \
110 memcpy(_dir + 2048, buf + 12, 2048);
112 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
113 struct iso_directory_record *dir;
119 // only try to scan if a filename is given
120 if (!strlen(filename)) return -1;
124 dir = (struct iso_directory_record*) &mdir[i];
125 if (dir->length[0] == 0) {
128 i += (u8)dir->length[0];
130 if (dir->flags[0] & 0x2) { // it's a dir
131 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
132 if (filename[dir->name_len[0]] != '\\') continue;
134 filename += dir->name_len[0] + 1;
136 mmssdd(dir->extent, (char *)time);
142 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
143 mmssdd(dir->extent, (char *)time);
152 static const unsigned int gpu_ctl_def[] = {
153 0x00000000, 0x01000000, 0x03000000, 0x04000000,
154 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
157 static const unsigned int gpu_data_def[] = {
158 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
159 0xe5001000, 0xe6000000,
160 0x02000000, 0x00000000, 0x01ff03ff,
163 static void fake_bios_gpu_setup(void)
167 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
168 GPU_writeStatus(gpu_ctl_def[i]);
170 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
171 GPU_writeData(gpu_data_def[i]);
176 struct iso_directory_record *dir;
181 // not the best place to do it, but since BIOS boot logo killer
182 // is just below, do it here
183 fake_bios_gpu_setup();
185 if (!Config.HLE && !Config.SlowBoot) {
187 psxRegs.pc = psxRegs.GPR.n.ra;
191 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
195 // skip head and sub, and go to the root directory record
196 dir = (struct iso_directory_record*) &buf[12+156];
198 mmssdd(dir->extent, (char*)time);
202 // Load SYSTEM.CNF and scan for the main executable
203 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
204 // if SYSTEM.CNF is missing, start an existing PSX.EXE
205 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
210 // read the SYSTEM.CNF
213 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
214 if (GetCdromFile(mdir, time, exename) == -1) {
215 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
216 if (GetCdromFile(mdir, time, exename) == -1) {
217 char *ptr = strstr((char *)buf + 12, "cdrom:");
220 while (*ptr == '\\' || *ptr == '/') ptr++;
221 strncpy(exename, ptr, 255);
224 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
226 if (GetCdromFile(mdir, time, exename) == -1)
233 // Read the EXE-Header
237 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
239 psxRegs.pc = SWAP32(tmpHead.pc0);
240 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
241 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
242 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
244 tmpHead.t_size = SWAP32(tmpHead.t_size);
245 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
247 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
250 // Read the rest of the main executable
251 while (tmpHead.t_size & ~2047) {
252 void *ptr = (void *)PSXM(tmpHead.t_addr);
257 if (ptr != NULL) memcpy(ptr, buf+12, 2048);
259 tmpHead.t_size -= 2048;
260 tmpHead.t_addr += 2048;
266 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
267 struct iso_directory_record *dir;
274 sscanf(filename, "cdrom:\\%255s", exename);
276 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
280 // skip head and sub, and go to the root directory record
281 dir = (struct iso_directory_record *)&buf[12 + 156];
283 mmssdd(dir->extent, (char*)time);
287 if (GetCdromFile(mdir, time, exename) == -1) return -1;
291 memcpy(head, buf + 12, sizeof(EXE_HEADER));
295 psxCpu->Clear(addr, size / 4);
298 while (size & ~2047) {
304 memcpy(mem, buf + 12, 2048);
314 struct iso_directory_record *dir;
315 unsigned char time[4];
317 unsigned char mdir[4096];
325 time[2] = itob(0x10);
329 memset(CdromLabel, 0, sizeof(CdromLabel));
330 memset(CdromId, 0, sizeof(CdromId));
331 memset(exename, 0, sizeof(exename));
333 strncpy(CdromLabel, buf + 52, 32);
335 // skip head and sub, and go to the root directory record
336 dir = (struct iso_directory_record *)&buf[12 + 156];
338 mmssdd(dir->extent, (char *)time);
342 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
345 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
346 if (GetCdromFile(mdir, time, exename) == -1) {
347 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
348 if (GetCdromFile(mdir, time, exename) == -1) {
349 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
352 while (*ptr == '\\' || *ptr == '/') ptr++;
353 strncpy(exename, ptr, 255);
356 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
358 if (GetCdromFile(mdir, time, exename) == -1)
359 return -1; // main executable not found
364 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
365 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
367 size_t i, len = strlen(exename) - offset;
368 for (i = 0; i < len; i++)
369 exename[i] = exename[i + offset];
372 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
373 strcpy(exename, "PSX.EXE;1");
374 strcpy(CdromId, "SLUS99999");
376 return -1; // SYSTEM.CNF and PSX.EXE not found
378 if (CdromId[0] == '\0') {
379 len = strlen(exename);
381 for (i = 0; i < len; ++i) {
382 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
384 if (isalnum(exename[i]))
385 CdromId[c++] = exename[i];
389 if (CdromId[0] == '\0')
390 strcpy(CdromId, "SLUS99999");
392 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
394 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
395 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
396 !strncmp(CdromId, "DTLS3035", 8) ||
397 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
398 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
399 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
400 Config.PsxType = PSX_TYPE_PAL; // pal
401 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
404 if (CdromLabel[0] == ' ') {
405 strncpy(CdromLabel, CdromId, 9);
407 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
408 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
409 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
418 static int PSXGetFileType(FILE *f) {
419 unsigned long current;
425 fseek(f, 0L, SEEK_SET);
426 if (fread(&mybuf, sizeof(mybuf), 1, f) != sizeof(mybuf))
429 fseek(f, current, SEEK_SET);
431 exe_hdr = (EXE_HEADER *)mybuf;
432 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
435 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
438 coff_hdr = (FILHDR *)mybuf;
439 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
446 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
451 // temporary pandora workaround..
453 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
458 tmp = malloc(size * nmemb);
460 ret = fread(tmp, size, nmemb, stream);
461 memcpy(ptr, tmp, size * nmemb);
467 int Load(const char *ExePath) {
473 u32 section_address, section_size;
476 strcpy(CdromId, "SLUS99999");
477 strcpy(CdromLabel, "SLUS_999.99");
479 tmpFile = fopen(ExePath, "rb");
480 if (tmpFile == NULL) {
481 SysPrintf(_("Error opening file: %s.\n"), ExePath);
484 type = PSXGetFileType(tmpFile);
487 if (fread(&tmpHead, sizeof(EXE_HEADER), 1, tmpFile) != sizeof(EXE_HEADER))
489 section_address = SWAP32(tmpHead.t_addr);
490 section_size = SWAP32(tmpHead.t_size);
491 mem = PSXM(section_address);
493 fseek(tmpFile, 0x800, SEEK_SET);
494 fread_to_ram(mem, section_size, 1, tmpFile);
495 psxCpu->Clear(section_address, section_size / 4);
497 psxRegs.pc = SWAP32(tmpHead.pc0);
498 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
499 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
500 if (psxRegs.GPR.n.sp == 0)
501 psxRegs.GPR.n.sp = 0x801fff00;
505 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
507 if (fread(&opcode, sizeof(opcode), 1, tmpFile) != sizeof(opcode))
510 case 1: /* Section loading */
511 if (fread(§ion_address, sizeof(section_address), 1, tmpFile) != sizeof(section_address))
513 if (fread(§ion_size, sizeof(section_size), 1, tmpFile) != sizeof(section_size))
515 section_address = SWAPu32(section_address);
516 section_size = SWAPu32(section_size);
518 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
520 mem = PSXM(section_address);
522 fread_to_ram(mem, section_size, 1, tmpFile);
523 psxCpu->Clear(section_address, section_size / 4);
526 case 3: /* register loading (PC only?) */
527 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
528 if (fread(&psxRegs.pc, sizeof(psxRegs.pc), 1, tmpFile) != sizeof(psxRegs.pc))
530 psxRegs.pc = SWAPu32(psxRegs.pc);
532 case 0: /* End of file */
535 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
539 } while (opcode != 0 && retval == 0);
542 SysPrintf(_("COFF files not supported.\n"));
546 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
547 SysPrintf(_("(did you forget -cdfile ?)\n"));
555 CdromLabel[0] = '\0';
563 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
571 static void *zlib_open(const char *name, const char *mode)
573 return gzopen(name, mode);
576 static int zlib_read(void *file, void *buf, u32 len)
578 return gzread(file, buf, len);
581 static int zlib_write(void *file, const void *buf, u32 len)
583 return gzwrite(file, buf, len);
586 static long zlib_seek(void *file, long offs, int whence)
588 return gzseek(file, offs, whence);
591 static void zlib_close(void *file)
596 struct PcsxSaveFuncs SaveFuncs = {
597 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
600 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
602 // Savestate Versioning!
603 // If you make changes to the savestate version, please increment the value below.
604 static const u32 SaveVersion = 0x8b410006;
606 int SaveState(const char *file) {
613 f = SaveFuncs.open(file, "wb");
614 if (f == NULL) return -1;
616 new_dyna_before_save();
618 SaveFuncs.write(f, (void *)PcsxHeader, 32);
619 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
620 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
622 pMem = (unsigned char *)malloc(128 * 96 * 3);
623 if (pMem == NULL) return -1;
624 GPU_getScreenPic(pMem);
625 SaveFuncs.write(f, pMem, 128 * 96 * 3);
631 SaveFuncs.write(f, psxM, 0x00200000);
632 SaveFuncs.write(f, psxR, 0x00080000);
633 SaveFuncs.write(f, psxH, 0x00010000);
634 // only partial save of psxRegisters to maintain savestate compat
635 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
638 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
639 gpufP->ulFreezeVersion = 1;
640 GPU_freeze(1, gpufP);
641 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
645 spufP = (SPUFreeze_t *) malloc(16);
646 SPU_freeze(2, spufP, psxRegs.cycle);
647 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
649 spufP = (SPUFreeze_t *) malloc(Size);
650 SPU_freeze(1, spufP, psxRegs.cycle);
651 SaveFuncs.write(f, spufP, Size);
659 new_dyna_freeze(f, 1);
663 new_dyna_after_save();
668 int LoadState(const char *file) {
677 f = SaveFuncs.open(file, "rb");
678 if (f == NULL) return -1;
680 SaveFuncs.read(f, header, sizeof(header));
681 SaveFuncs.read(f, &version, sizeof(u32));
682 SaveFuncs.read(f, &hle, sizeof(boolean));
684 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
693 #if defined(LIGHTREC)
694 if (Config.Cpu != CPU_INTERPRETER)
695 psxCpu->Clear(0, UINT32_MAX); //clear all
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;
711 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
712 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
713 GPU_freeze(0, gpufP);
715 if (HW_GPU_STATUS == 0)
716 HW_GPU_STATUS = GPU_readStatus();
719 SaveFuncs.read(f, &Size, 4);
720 spufP = (SPUFreeze_t *)malloc(Size);
721 SaveFuncs.read(f, spufP, Size);
722 SPU_freeze(0, spufP, psxRegs.cycle);
730 new_dyna_freeze(f, 0);
737 int CheckState(const char *file) {
743 f = SaveFuncs.open(file, "rb");
744 if (f == NULL) return -1;
746 SaveFuncs.read(f, header, sizeof(header));
747 SaveFuncs.read(f, &version, sizeof(u32));
748 SaveFuncs.read(f, &hle, sizeof(boolean));
752 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
758 // NET Function Helpers
761 if (NET_recvData == NULL || NET_sendData == NULL)
764 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
765 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
766 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
767 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
768 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
769 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
777 if (NET_recvData == NULL || NET_sendData == NULL)
780 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
781 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
782 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
783 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
784 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
789 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
790 if (tmp != Config.Cpu) {
793 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
794 else psxCpu = &psxRec;
798 if (psxCpu->Init() == -1) {
799 SysClose(); return -1;
807 // remove the leading and trailing spaces in a string
808 void trim(char *str) {
812 // skip leading blanks
813 while (str[pos] <= ' ' && str[pos] > 0)
817 *(dest++) = str[pos];
821 *(dest--) = '\0'; // store the null
823 // remove trailing blanks
824 while (dest >= str && *dest <= ' ' && *dest > 0)
828 // lookup table for crc calculation
829 static unsigned short crctab[256] = {
830 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
831 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
832 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
833 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
834 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
835 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
836 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
837 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
838 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
839 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
840 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
841 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
842 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
843 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
844 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
845 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
846 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
847 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
848 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
849 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
850 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
851 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
852 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
853 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
854 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
855 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
856 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
857 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
858 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
861 u16 calcCrc(u8 *d, int len) {
865 for (i = 0; i < len; i++) {
866 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);