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.
31 #include "lightrec/plugin.h"
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 void mmssdd( char *b, char *p )
62 int block = SWAP32(*((uint32_t*) b));
65 m = block / 4500; // minutes
66 block = block - m * 4500; // minutes rest
67 s = block / 75; // seconds
68 d = block - s * 75; // seconds rest
70 m = ((m / 10) << 4) | m % 10;
71 s = ((s / 10) << 4) | s % 10;
72 d = ((d / 10) << 4) | d % 10;
80 time[0] = btoi(time[0]); time[1] = btoi(time[1]); time[2] = btoi(time[2]); \
85 if (time[1] == 60) { \
90 time[0] = itob(time[0]); time[1] = itob(time[1]); time[2] = itob(time[2]);
93 if (CDR_readTrack(time) == -1) return -1; \
94 buf = (void *)CDR_getBuffer(); \
95 if (buf == NULL) return -1; \
96 else CheckPPFCache((u8 *)buf, time[0], time[1], time[2]);
98 #define READDIR(_dir) \
100 memcpy(_dir, buf + 12, 2048); \
104 memcpy(_dir + 2048, buf + 12, 2048);
106 int GetCdromFile(u8 *mdir, u8 *time, char *filename) {
107 struct iso_directory_record *dir;
113 // only try to scan if a filename is given
114 if (!strlen(filename)) return -1;
118 dir = (struct iso_directory_record*) &mdir[i];
119 if (dir->length[0] == 0) {
122 i += (u8)dir->length[0];
124 if (dir->flags[0] & 0x2) { // it's a dir
125 if (!strnicmp((char *)&dir->name[0], filename, dir->name_len[0])) {
126 if (filename[dir->name_len[0]] != '\\') continue;
128 filename += dir->name_len[0] + 1;
130 mmssdd(dir->extent, (char *)time);
136 if (!strnicmp((char *)&dir->name[0], filename, strlen(filename))) {
137 mmssdd(dir->extent, (char *)time);
146 static const unsigned int gpu_ctl_def[] = {
147 0x00000000, 0x01000000, 0x03000000, 0x04000000,
148 0x05000800, 0x06c60260, 0x0703fc10, 0x08000027,
151 static const unsigned int gpu_data_def[] = {
152 0xe100360b, 0xe2000000, 0xe3000800, 0xe4077e7f,
153 0xe5001000, 0xe6000000,
154 0x02000000, 0x00000000, 0x01ff03ff,
157 static void fake_bios_gpu_setup(void)
161 for (i = 0; i < sizeof(gpu_ctl_def) / sizeof(gpu_ctl_def[0]); i++)
162 GPU_writeStatus(gpu_ctl_def[i]);
164 for (i = 0; i < sizeof(gpu_data_def) / sizeof(gpu_data_def[0]); i++)
165 GPU_writeData(gpu_data_def[i]);
170 struct iso_directory_record *dir;
175 // not the best place to do it, but since BIOS boot logo killer
176 // is just below, do it here
177 fake_bios_gpu_setup();
179 if (!Config.HLE && !Config.SlowBoot) {
181 psxRegs.pc = psxRegs.GPR.n.ra;
185 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
189 // skip head and sub, and go to the root directory record
190 dir = (struct iso_directory_record*) &buf[12+156];
192 mmssdd(dir->extent, (char*)time);
196 // Load SYSTEM.CNF and scan for the main executable
197 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") == -1) {
198 // if SYSTEM.CNF is missing, start an existing PSX.EXE
199 if (GetCdromFile(mdir, time, "PSX.EXE;1") == -1) return -1;
204 // read the SYSTEM.CNF
207 sscanf((char *)buf + 12, "BOOT = cdrom:\\%255s", exename);
208 if (GetCdromFile(mdir, time, exename) == -1) {
209 sscanf((char *)buf + 12, "BOOT = cdrom:%255s", exename);
210 if (GetCdromFile(mdir, time, exename) == -1) {
211 char *ptr = strstr((char *)buf + 12, "cdrom:");
214 while (*ptr == '\\' || *ptr == '/') ptr++;
215 strncpy(exename, ptr, 255);
218 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
220 if (GetCdromFile(mdir, time, exename) == -1)
227 // Read the EXE-Header
231 memcpy(&tmpHead, buf + 12, sizeof(EXE_HEADER));
233 psxRegs.pc = SWAP32(tmpHead.pc0);
234 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
235 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
236 if (psxRegs.GPR.n.sp == 0) psxRegs.GPR.n.sp = 0x801fff00;
238 tmpHead.t_size = SWAP32(tmpHead.t_size);
239 tmpHead.t_addr = SWAP32(tmpHead.t_addr);
241 psxCpu->Clear(tmpHead.t_addr, tmpHead.t_size / 4);
244 // Read the rest of the main executable
245 while (tmpHead.t_size & ~2047) {
246 void *ptr = (void *)PSXM(tmpHead.t_addr);
251 if (ptr != INVALID_PTR) memcpy(ptr, buf+12, 2048);
253 tmpHead.t_size -= 2048;
254 tmpHead.t_addr += 2048;
260 int LoadCdromFile(const char *filename, EXE_HEADER *head) {
261 struct iso_directory_record *dir;
268 sscanf(filename, "cdrom:\\%255s", exename);
270 time[0] = itob(0); time[1] = itob(2); time[2] = itob(0x10);
274 // skip head and sub, and go to the root directory record
275 dir = (struct iso_directory_record *)&buf[12 + 156];
277 mmssdd(dir->extent, (char*)time);
281 if (GetCdromFile(mdir, time, exename) == -1) return -1;
285 memcpy(head, buf + 12, sizeof(EXE_HEADER));
289 psxCpu->Clear(addr, size / 4);
292 while (size & ~2047) {
297 if (mem != INVALID_PTR)
298 memcpy(mem, buf + 12, 2048);
308 struct iso_directory_record *dir;
309 unsigned char time[4];
311 unsigned char mdir[4096];
319 time[2] = itob(0x10);
323 memset(CdromLabel, 0, sizeof(CdromLabel));
324 memset(CdromId, 0, sizeof(CdromId));
325 memset(exename, 0, sizeof(exename));
327 strncpy(CdromLabel, buf + 52, 32);
329 // skip head and sub, and go to the root directory record
330 dir = (struct iso_directory_record *)&buf[12 + 156];
332 mmssdd(dir->extent, (char *)time);
336 if (GetCdromFile(mdir, time, "SYSTEM.CNF;1") != -1) {
339 sscanf(buf + 12, "BOOT = cdrom:\\%255s", exename);
340 if (GetCdromFile(mdir, time, exename) == -1) {
341 sscanf(buf + 12, "BOOT = cdrom:%255s", exename);
342 if (GetCdromFile(mdir, time, exename) == -1) {
343 char *ptr = strstr(buf + 12, "cdrom:"); // possibly the executable is in some subdir
346 while (*ptr == '\\' || *ptr == '/') ptr++;
347 strncpy(exename, ptr, 255);
350 while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n') ptr++;
352 if (GetCdromFile(mdir, time, exename) == -1)
353 return -1; // main executable not found
358 /* Workaround for Wild Arms EU/US which has non-standard string causing incorrect region detection */
359 if (exename[0] == 'E' && exename[1] == 'X' && exename[2] == 'E' && exename[3] == '\\') {
361 size_t i, len = strlen(exename) - offset;
362 for (i = 0; i < len; i++)
363 exename[i] = exename[i + offset];
366 } else if (GetCdromFile(mdir, time, "PSX.EXE;1") != -1) {
367 strcpy(exename, "PSX.EXE;1");
368 strcpy(CdromId, "SLUS99999");
370 return -1; // SYSTEM.CNF and PSX.EXE not found
372 if (CdromId[0] == '\0') {
373 len = strlen(exename);
375 for (i = 0; i < len; ++i) {
376 if (exename[i] == ';' || c >= sizeof(CdromId) - 1)
378 if (isalnum(exename[i]))
379 CdromId[c++] = exename[i];
383 if (CdromId[0] == '\0')
384 strcpy(CdromId, "SLUS99999");
386 if (Config.PsxAuto) { // autodetect system (pal or ntsc)
388 /* Make sure Wild Arms SCUS-94608 is not detected as a PAL game. */
389 ((CdromId[0] == 's' || CdromId[0] == 'S') && (CdromId[2] == 'e' || CdromId[2] == 'E')) ||
390 !strncmp(CdromId, "DTLS3035", 8) ||
391 !strncmp(CdromId, "PBPX95001", 9) || // according to redump.org, these PAL
392 !strncmp(CdromId, "PBPX95007", 9) || // discs have a non-standard ID;
393 !strncmp(CdromId, "PBPX95008", 9)) // add more serials if they are discovered.
394 Config.PsxType = PSX_TYPE_PAL; // pal
395 else Config.PsxType = PSX_TYPE_NTSC; // ntsc
398 if (CdromLabel[0] == ' ') {
399 strncpy(CdromLabel, CdromId, 9);
401 SysPrintf(_("CD-ROM Label: %.32s\n"), CdromLabel);
402 SysPrintf(_("CD-ROM ID: %.9s\n"), CdromId);
403 SysPrintf(_("CD-ROM EXE Name: %.255s\n"), exename);
412 static int PSXGetFileType(FILE *f) {
413 unsigned long current;
419 fseek(f, 0L, SEEK_SET);
420 if (fread(&mybuf, sizeof(mybuf), 1, f) != sizeof(mybuf))
423 fseek(f, current, SEEK_SET);
425 exe_hdr = (EXE_HEADER *)mybuf;
426 if (memcmp(exe_hdr->id, "PS-X EXE", 8) == 0)
429 if (mybuf[0] == 'C' && mybuf[1] == 'P' && mybuf[2] == 'E')
432 coff_hdr = (FILHDR *)mybuf;
433 if (SWAPu16(coff_hdr->f_magic) == 0x0162)
440 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
445 // temporary pandora workaround..
447 size_t fread_to_ram(void *ptr, size_t size, size_t nmemb, FILE *stream)
452 tmp = malloc(size * nmemb);
454 ret = fread(tmp, size, nmemb, stream);
455 memcpy(ptr, tmp, size * nmemb);
461 int Load(const char *ExePath) {
467 u32 section_address, section_size;
470 strcpy(CdromId, "SLUS99999");
471 strcpy(CdromLabel, "SLUS_999.99");
473 tmpFile = fopen(ExePath, "rb");
474 if (tmpFile == NULL) {
475 SysPrintf(_("Error opening file: %s.\n"), ExePath);
478 type = PSXGetFileType(tmpFile);
481 if (fread(&tmpHead, sizeof(EXE_HEADER), 1, tmpFile) != sizeof(EXE_HEADER))
483 section_address = SWAP32(tmpHead.t_addr);
484 section_size = SWAP32(tmpHead.t_size);
485 mem = PSXM(section_address);
486 if (mem != INVALID_PTR) {
487 fseek(tmpFile, 0x800, SEEK_SET);
488 fread_to_ram(mem, section_size, 1, tmpFile);
489 psxCpu->Clear(section_address, section_size / 4);
491 psxRegs.pc = SWAP32(tmpHead.pc0);
492 psxRegs.GPR.n.gp = SWAP32(tmpHead.gp0);
493 psxRegs.GPR.n.sp = SWAP32(tmpHead.s_addr);
494 if (psxRegs.GPR.n.sp == 0)
495 psxRegs.GPR.n.sp = 0x801fff00;
499 fseek(tmpFile, 6, SEEK_SET); /* Something tells me we should go to 4 and read the "08 00" here... */
501 if (fread(&opcode, sizeof(opcode), 1, tmpFile) != sizeof(opcode))
504 case 1: /* Section loading */
505 if (fread(§ion_address, sizeof(section_address), 1, tmpFile) != sizeof(section_address))
507 if (fread(§ion_size, sizeof(section_size), 1, tmpFile) != sizeof(section_size))
509 section_address = SWAPu32(section_address);
510 section_size = SWAPu32(section_size);
512 EMU_LOG("Loading %08X bytes from %08X to %08X\n", section_size, ftell(tmpFile), section_address);
514 mem = PSXM(section_address);
515 if (mem != INVALID_PTR) {
516 fread_to_ram(mem, section_size, 1, tmpFile);
517 psxCpu->Clear(section_address, section_size / 4);
520 case 3: /* register loading (PC only?) */
521 fseek(tmpFile, 2, SEEK_CUR); /* unknown field */
522 if (fread(&psxRegs.pc, sizeof(psxRegs.pc), 1, tmpFile) != sizeof(psxRegs.pc))
524 psxRegs.pc = SWAPu32(psxRegs.pc);
526 case 0: /* End of file */
529 SysPrintf(_("Unknown CPE opcode %02x at position %08x.\n"), opcode, ftell(tmpFile) - 1);
533 } while (opcode != 0 && retval == 0);
536 SysPrintf(_("COFF files not supported.\n"));
540 SysPrintf(_("This file does not appear to be a valid PSX EXE file.\n"));
541 SysPrintf(_("(did you forget -cdfile ?)\n"));
549 CdromLabel[0] = '\0';
557 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
565 static void *zlib_open(const char *name, const char *mode)
567 return gzopen(name, mode);
570 static int zlib_read(void *file, void *buf, u32 len)
572 return gzread(file, buf, len);
575 static int zlib_write(void *file, const void *buf, u32 len)
577 return gzwrite(file, buf, len);
580 static long zlib_seek(void *file, long offs, int whence)
582 return gzseek(file, offs, whence);
585 static void zlib_close(void *file)
590 struct PcsxSaveFuncs SaveFuncs = {
591 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
594 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
596 // Savestate Versioning!
597 // If you make changes to the savestate version, please increment the value below.
598 static const u32 SaveVersion = 0x8b410006;
600 int SaveState(const char *file) {
607 f = SaveFuncs.open(file, "wb");
608 if (f == NULL) return -1;
610 new_dyna_before_save();
612 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
613 lightrec_plugin_prepare_save_state();
615 SaveFuncs.write(f, (void *)PcsxHeader, 32);
616 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
617 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
619 pMem = (unsigned char *)malloc(128 * 96 * 3);
620 if (pMem == NULL) return -1;
621 GPU_getScreenPic(pMem);
622 SaveFuncs.write(f, pMem, 128 * 96 * 3);
628 SaveFuncs.write(f, psxM, 0x00200000);
629 SaveFuncs.write(f, psxR, 0x00080000);
630 SaveFuncs.write(f, psxH, 0x00010000);
631 // only partial save of psxRegisters to maintain savestate compat
632 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
635 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
636 gpufP->ulFreezeVersion = 1;
637 GPU_freeze(1, gpufP);
638 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
642 spufP = (SPUFreeze_t *) malloc(16);
643 SPU_freeze(2, spufP, psxRegs.cycle);
644 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
646 spufP = (SPUFreeze_t *) malloc(Size);
647 SPU_freeze(1, spufP, psxRegs.cycle);
648 SaveFuncs.write(f, spufP, Size);
656 new_dyna_freeze(f, 1);
660 new_dyna_after_save();
665 int LoadState(const char *file) {
674 f = SaveFuncs.open(file, "rb");
675 if (f == NULL) return -1;
677 SaveFuncs.read(f, header, sizeof(header));
678 SaveFuncs.read(f, &version, sizeof(u32));
679 SaveFuncs.read(f, &hle, sizeof(boolean));
681 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
690 if (!drc_is_lightrec() || Config.Cpu == CPU_INTERPRETER)
692 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
694 SaveFuncs.read(f, psxM, 0x00200000);
695 SaveFuncs.read(f, psxR, 0x00080000);
696 SaveFuncs.read(f, psxH, 0x00010000);
697 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
698 psxRegs.gteBusyCycle = psxRegs.cycle;
700 if (drc_is_lightrec() && Config.Cpu != CPU_INTERPRETER)
701 lightrec_plugin_prepare_load_state();
707 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
708 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
709 GPU_freeze(0, gpufP);
711 if (HW_GPU_STATUS == 0)
712 HW_GPU_STATUS = SWAP32(GPU_readStatus());
715 SaveFuncs.read(f, &Size, 4);
716 spufP = (SPUFreeze_t *)malloc(Size);
717 SaveFuncs.read(f, spufP, Size);
718 SPU_freeze(0, spufP, psxRegs.cycle);
726 new_dyna_freeze(f, 0);
733 int CheckState(const char *file) {
739 f = SaveFuncs.open(file, "rb");
740 if (f == NULL) return -1;
742 SaveFuncs.read(f, header, sizeof(header));
743 SaveFuncs.read(f, &version, sizeof(u32));
744 SaveFuncs.read(f, &hle, sizeof(boolean));
748 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
754 // NET Function Helpers
757 if (NET_recvData == NULL || NET_sendData == NULL)
760 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
761 NET_sendData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
762 NET_sendData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
763 NET_sendData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
764 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
765 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
773 if (NET_recvData == NULL || NET_sendData == NULL)
776 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
777 NET_recvData(&Config.Sio, sizeof(Config.Sio), PSE_NET_BLOCKING);
778 NET_recvData(&Config.SpuIrq, sizeof(Config.SpuIrq), PSE_NET_BLOCKING);
779 NET_recvData(&Config.RCntFix, sizeof(Config.RCntFix), PSE_NET_BLOCKING);
780 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
785 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
786 if (tmp != Config.Cpu) {
789 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
790 else psxCpu = &psxRec;
794 if (psxCpu->Init() == -1) {
795 SysClose(); return -1;
803 // remove the leading and trailing spaces in a string
804 void trim(char *str) {
808 // skip leading blanks
809 while (str[pos] <= ' ' && str[pos] > 0)
813 *(dest++) = str[pos];
817 *(dest--) = '\0'; // store the null
819 // remove trailing blanks
820 while (dest >= str && *dest <= ' ' && *dest > 0)
824 // lookup table for crc calculation
825 static unsigned short crctab[256] = {
826 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
827 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
828 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
829 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
830 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
831 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
832 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
833 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
834 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
835 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
836 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
837 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
838 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
839 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
840 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
841 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
842 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
843 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
844 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
845 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
846 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
847 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
848 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
849 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
850 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
851 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
852 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
853 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
854 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
857 u16 calcCrc(u8 *d, int len) {
861 for (i = 0; i < len; i++) {
862 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);