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 static void mmssdd( char *b, char *p )
61 unsigned char *ub = (void *)b;
62 int block = (ub[3] << 24) | (ub[2] << 16) | (ub[1] << 8) | ub[0];
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)) 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, 1, sizeof(mybuf), 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, 1, sizeof(EXE_HEADER), 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, 1, sizeof(opcode), tmpFile) != sizeof(opcode))
504 case 1: /* Section loading */
505 if (fread(§ion_address, 1, sizeof(section_address), tmpFile) != sizeof(section_address))
507 if (fread(§ion_size, 1, sizeof(section_size), 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, 1, sizeof(psxRegs.pc), 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';
558 SysPrintf(_("File IO error in <%s:%s>.\n"), __FILE__, __func__);
566 static void *zlib_open(const char *name, const char *mode)
568 return gzopen(name, mode);
571 static int zlib_read(void *file, void *buf, u32 len)
573 return gzread(file, buf, len);
576 static int zlib_write(void *file, const void *buf, u32 len)
578 return gzwrite(file, buf, len);
581 static long zlib_seek(void *file, long offs, int whence)
583 return gzseek(file, offs, whence);
586 static void zlib_close(void *file)
591 struct PcsxSaveFuncs SaveFuncs = {
592 zlib_open, zlib_read, zlib_write, zlib_seek, zlib_close
595 static const char PcsxHeader[32] = "STv4 PCSX v" PCSX_VERSION;
597 // Savestate Versioning!
598 // If you make changes to the savestate version, please increment the value below.
599 static const u32 SaveVersion = 0x8b410006;
601 int SaveState(const char *file) {
608 f = SaveFuncs.open(file, "wb");
609 if (f == NULL) return -1;
611 psxCpu->Notify(R3000ACPU_NOTIFY_BEFORE_SAVE, NULL);
613 SaveFuncs.write(f, (void *)PcsxHeader, 32);
614 SaveFuncs.write(f, (void *)&SaveVersion, sizeof(u32));
615 SaveFuncs.write(f, (void *)&Config.HLE, sizeof(boolean));
617 pMem = (unsigned char *)malloc(128 * 96 * 3);
618 if (pMem == NULL) return -1;
619 GPU_getScreenPic(pMem);
620 SaveFuncs.write(f, pMem, 128 * 96 * 3);
626 SaveFuncs.write(f, psxM, 0x00200000);
627 SaveFuncs.write(f, psxR, 0x00080000);
628 SaveFuncs.write(f, psxH, 0x00010000);
629 // only partial save of psxRegisters to maintain savestate compat
630 SaveFuncs.write(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
633 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
634 gpufP->ulFreezeVersion = 1;
635 GPU_freeze(1, gpufP);
636 SaveFuncs.write(f, gpufP, sizeof(GPUFreeze_t));
640 spufP = (SPUFreeze_t *) malloc(16);
641 SPU_freeze(2, spufP, psxRegs.cycle);
642 Size = spufP->Size; SaveFuncs.write(f, &Size, 4);
644 spufP = (SPUFreeze_t *) malloc(Size);
645 SPU_freeze(1, spufP, psxRegs.cycle);
646 SaveFuncs.write(f, spufP, Size);
654 new_dyna_freeze(f, 1);
661 int LoadState(const char *file) {
670 f = SaveFuncs.open(file, "rb");
671 if (f == NULL) return -1;
673 SaveFuncs.read(f, header, sizeof(header));
674 SaveFuncs.read(f, &version, sizeof(u32));
675 SaveFuncs.read(f, &hle, sizeof(boolean));
677 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion) {
686 SaveFuncs.seek(f, 128 * 96 * 3, SEEK_CUR);
687 SaveFuncs.read(f, psxM, 0x00200000);
688 SaveFuncs.read(f, psxR, 0x00080000);
689 SaveFuncs.read(f, psxH, 0x00010000);
690 SaveFuncs.read(f, &psxRegs, offsetof(psxRegisters, gteBusyCycle));
691 psxRegs.gteBusyCycle = psxRegs.cycle;
693 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
699 gpufP = (GPUFreeze_t *)malloc(sizeof(GPUFreeze_t));
700 SaveFuncs.read(f, gpufP, sizeof(GPUFreeze_t));
701 GPU_freeze(0, gpufP);
703 if (HW_GPU_STATUS == 0)
704 HW_GPU_STATUS = SWAP32(GPU_readStatus());
707 SaveFuncs.read(f, &Size, 4);
708 spufP = (SPUFreeze_t *)malloc(Size);
709 SaveFuncs.read(f, spufP, Size);
710 SPU_freeze(0, spufP, psxRegs.cycle);
718 new_dyna_freeze(f, 0);
725 int CheckState(const char *file) {
731 f = SaveFuncs.open(file, "rb");
732 if (f == NULL) return -1;
734 SaveFuncs.read(f, header, sizeof(header));
735 SaveFuncs.read(f, &version, sizeof(u32));
736 SaveFuncs.read(f, &hle, sizeof(boolean));
740 if (strncmp("STv4 PCSX", header, 9) != 0 || version != SaveVersion)
746 // NET Function Helpers
749 if (NET_recvData == NULL || NET_sendData == NULL)
753 boolean SpuIrq_old = 0;
754 boolean RCntFix_old = 0;
755 NET_sendData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
756 NET_sendData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
757 NET_sendData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
758 NET_sendData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
759 NET_sendData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
760 NET_sendData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
768 if (NET_recvData == NULL || NET_sendData == NULL)
772 boolean SpuIrq_old = 0;
773 boolean RCntFix_old = 0;
774 NET_recvData(&Config.Xa, sizeof(Config.Xa), PSE_NET_BLOCKING);
775 NET_recvData(&Sio_old, sizeof(Sio_old), PSE_NET_BLOCKING);
776 NET_recvData(&SpuIrq_old, sizeof(SpuIrq_old), PSE_NET_BLOCKING);
777 NET_recvData(&RCntFix_old, sizeof(RCntFix_old), PSE_NET_BLOCKING);
778 NET_recvData(&Config.PsxType, sizeof(Config.PsxType), PSE_NET_BLOCKING);
783 NET_recvData(&Config.Cpu, sizeof(Config.Cpu), PSE_NET_BLOCKING);
784 if (tmp != Config.Cpu) {
787 if (Config.Cpu == CPU_INTERPRETER) psxCpu = &psxInt;
788 else psxCpu = &psxRec;
792 if (psxCpu->Init() == -1) {
793 SysClose(); return -1;
796 psxCpu->Notify(R3000ACPU_NOTIFY_AFTER_LOAD, NULL);
802 // remove the leading and trailing spaces in a string
803 void trim(char *str) {
807 // skip leading blanks
808 while (str[pos] <= ' ' && str[pos] > 0)
812 *(dest++) = str[pos];
816 *(dest--) = '\0'; // store the null
818 // remove trailing blanks
819 while (dest >= str && *dest <= ' ' && *dest > 0)
823 // lookup table for crc calculation
824 static unsigned short crctab[256] = {
825 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108,
826 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210,
827 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B,
828 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401,
829 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE,
830 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6,
831 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D,
832 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
833 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5,
834 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC,
835 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4,
836 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD,
837 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13,
838 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
839 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E,
840 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
841 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1,
842 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB,
843 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0,
844 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8,
845 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657,
846 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9,
847 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882,
848 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
849 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E,
850 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07,
851 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D,
852 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
853 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
856 u16 calcCrc(u8 *d, int len) {
860 for (i = 0; i < len; i++) {
861 crc = crctab[(crc >> 8) ^ d[i]] ^ (crc << 8);