├── toydata.xls ├── .gitignore ├── Makefile ├── checksum.h ├── rijndael.h ├── Makefile.mingw ├── crypt.h ├── portalio.h ├── md5.h ├── fileio.h ├── skylander.h ├── README.md ├── crypt.cpp ├── usbtest.c ├── fileio.cpp ├── portalio_hidapi.cpp ├── md5.cpp ├── checksum.cpp ├── main.cpp ├── toynames.cpp ├── portalio_libusb.cpp ├── skylander.cpp ├── SkyReader.xcodeproj └── project.pbxproj ├── hidapi.h ├── portalio_iokit.cpp ├── portalio.cpp └── hid_win.c /toydata.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silicontrip/SkyReader/HEAD/toydata.xls -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | a.out 3 | *.pdf 4 | *.bat 5 | editor 6 | usbtest 7 | bin 8 | build 9 | skylanders 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | CFLAGS=-I/usr/local/include/libusb-1.0 -g 4 | LDFLAGS=-L/usr/local/lib -lusb-1.0 -framework CoreFoundation -framework IOKit 5 | OBJ= checksum.o fileio.o md5.o rijndael.o crypt.o skylander.o main.o hid.o toynames.o 6 | 7 | LIBUSBIO = portalio_libusb.o 8 | LIBHIDAPI = portalio_hidapi.o 9 | WIN = portalio.o 10 | IOKIT = portalio_iokit.o 11 | 12 | editor: $(OBJ) $(LIBHIDAPI) 13 | g++ $(LDFLAGS) -o $@ $^ 14 | 15 | usbtest: usbtest.o 16 | gcc $(LDFLAGS) -o $@ $< 17 | 18 | hid.o: hid.c 19 | gcc -c -o $@ $< 20 | 21 | %.o:%.cpp 22 | g++ $(CFLAGS) -c $< 23 | 24 | clean: 25 | rm -f *.o usbtest 26 | -------------------------------------------------------------------------------- /checksum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "crypt.h" 7 | 8 | 9 | class Checksum { 10 | 11 | public: 12 | bool validateChecksum(unsigned char *, int , int , bool ); 13 | bool ValidateAllChecksums(unsigned char *, bool ); 14 | unsigned short ComputeCcittCrc16(void const *, unsigned int ); 15 | 16 | protected: 17 | 18 | unsigned short UpdateCcittCrc16(unsigned short , unsigned char ); 19 | bool getChecksumParameters(int , unsigned int *, unsigned int *, unsigned int *); 20 | bool computeChecksum(int, void const *, unsigned short *); 21 | 22 | }; -------------------------------------------------------------------------------- /rijndael.h: -------------------------------------------------------------------------------- 1 | #ifndef H__RIJNDAEL 2 | #define H__RIJNDAEL 3 | 4 | int rijndaelSetupEncrypt(unsigned long *rk, const unsigned char *key, 5 | int keybits); 6 | int rijndaelSetupDecrypt(unsigned long *rk, const unsigned char *key, 7 | int keybits); 8 | void rijndaelEncrypt(const unsigned long *rk, int nrounds, 9 | const unsigned char plaintext[16], unsigned char ciphertext[16]); 10 | void rijndaelDecrypt(const unsigned long *rk, int nrounds, 11 | const unsigned char ciphertext[16], unsigned char plaintext[16]); 12 | 13 | #define KEYLENGTH(keybits) ((keybits)/8) 14 | #define RKLENGTH(keybits) ((keybits)/8+28) 15 | #define NROUNDS(keybits) ((keybits)/32+6) 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Makefile.mingw: -------------------------------------------------------------------------------- 1 | ################################################## 2 | # Makefile for MinGW32 on windows by jtp10181 3 | # To compile run: mingw32-make -f Makefile.mingw 4 | ################################################## 5 | CFLAGS=-g -c -pthread 6 | #LDFLAGS=-static -static-libgcc -static-libstdc++ -lusb-1.0 -lsetupapi 7 | LDFLAGS=-static -static-libgcc -static-libstdc++ -lsetupapi 8 | OBJ=checksum.o fileio.o md5.o rijndael.o crypt.o skylander.o toynames.o main.o hid.o 9 | 10 | LIBHIDAPI = portalio_hidapi.o 11 | LIBUSBIO = portalio_libusb.o 12 | WIN_WDK = portalio.o 13 | IOKIT = portalio_iokit.o 14 | 15 | editor: $(OBJ) $(LIBHIDAPI) 16 | g++ -g $^ $(LDFLAGS) -o bin\$@ 17 | 18 | usbtest: usbtest.o 19 | gcc -g $(LDFLAGS) -o $@ $< 20 | 21 | hid.o: hid_win.c 22 | gcc $(CFLAGS) $< -o $@ 23 | 24 | %.o: %.cpp 25 | g++ $(CFLAGS) $< 26 | 27 | clean: 28 | del /Q *.o 29 | del /Q bin\*.exe 30 | -------------------------------------------------------------------------------- /crypt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "md5.h" 4 | #include "rijndael.h" 5 | #include "crypt.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | 13 | class Crypt { 14 | 15 | public: 16 | int IsAccessControlBlock(unsigned int); 17 | void EncryptTagBlock(unsigned char*, unsigned int , unsigned char const* ); 18 | void DecryptTagBlock(unsigned char*, unsigned int , unsigned char const* ); 19 | void EncryptBuffer(unsigned char* ); 20 | void DecryptBuffer(unsigned char* ); 21 | 22 | private: 23 | int ShouldEncryptBlock(unsigned int ); 24 | void ComputeMD5(unsigned char [16], void const* , unsigned int ); 25 | void ComputeEncryptionKey(unsigned char [16], unsigned char const* , unsigned int ); 26 | void EncryptAES128ECB(unsigned char * , unsigned char const* , unsigned char* ); 27 | void DecryptAES128ECB(unsigned char * , unsigned char const* , unsigned char* ); 28 | }; -------------------------------------------------------------------------------- /portalio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // unix io functions 10 | #include 11 | 12 | 13 | #include "hidapi.h" 14 | #include "fileio.h" 15 | #include "crypt.h" 16 | 17 | #define rw_buf_size 0x21 18 | #define TIMEOUT 30000 19 | #define SKYLANDER_SIZE 1024 20 | #define MAX_STR 255 21 | 22 | 23 | typedef struct { 24 | unsigned char buf[rw_buf_size]; 25 | int dwBytesTransferred; 26 | } RWBlock; 27 | 28 | class PortalIO { 29 | 30 | hid_device *hPortalHandle; 31 | 32 | public: 33 | PortalIO() throw (int); 34 | ~PortalIO(); 35 | 36 | bool WriteSkylanderToPortal(unsigned char *, unsigned char *); 37 | 38 | bool ReadBlock (unsigned int , unsigned char [0x10], int ) throw (int); 39 | void SetPortalColor(unsigned char , unsigned char , unsigned char ) throw (int); 40 | bool WriteBlock(unsigned int , unsigned char [0x10], int ) throw (int); 41 | 42 | void flash (void) throw (int); 43 | 44 | private: 45 | void OpenPortalHandle() throw (int); 46 | void Write(RWBlock *) throw (int); 47 | void RestartPortal(void) throw (int); 48 | void ActivatePortal(int) throw (int); 49 | unsigned char PortalStatus() throw (int); 50 | void DisconnectPortal(void); 51 | void ConnectToPortal(void) throw (int); 52 | bool CheckResponse (RWBlock *, char ) throw (int); 53 | 54 | }; -------------------------------------------------------------------------------- /md5.h: -------------------------------------------------------------------------------- 1 | /* md5.h - header file for md5.c */ 2 | /* RSA Data Security, Inc., MD5 Message-Digest Algorithm */ 3 | 4 | /* NOTE: Numerous changes have been made; the following notice is 5 | included to satisfy legal requirements. 6 | 7 | Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 8 | rights reserved. 9 | 10 | License to copy and use this software is granted provided that it 11 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 12 | Algorithm" in all material mentioning or referencing this software 13 | or this function. 14 | 15 | License is also granted to make and use derivative works provided 16 | that such works are identified as "derived from the RSA Data 17 | Security, Inc. MD5 Message-Digest Algorithm" in all material 18 | mentioning or referencing the derived work. 19 | 20 | RSA Data Security, Inc. makes no representations concerning either 21 | the merchantability of this software or the suitability of this 22 | software for any particular purpose. It is provided "as is" 23 | without express or implied warranty of any kind. 24 | 25 | These notices must be retained in any copies of any part of this 26 | documentation and/or software. 27 | */ 28 | 29 | #ifndef H__MD5 30 | #define H__MD5 31 | 32 | typedef unsigned long UINT4; 33 | 34 | typedef struct 35 | { 36 | UINT4 state[4]; 37 | UINT4 count[2]; 38 | unsigned char buffer[64]; 39 | } MD5; 40 | 41 | void MD5Open(MD5 *); 42 | void MD5Digest(MD5 *, const void *, unsigned int); 43 | void MD5Close(MD5 *, unsigned char[16]); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /fileio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "skylander.h" 8 | #include "portalio.h" 9 | #include "crypt.h" 10 | 11 | #define BLOCK_SIZE 16 12 | 13 | class SkylanderIO { 14 | 15 | Skylander *sky; 16 | unsigned char * buffer; 17 | Crypt crypt; 18 | 19 | public: 20 | SkylanderIO(); 21 | ~SkylanderIO(); 22 | void fprinthex(FILE *, unsigned char *, unsigned int); 23 | void initWithUnencryptedFile(char *) throw (int); 24 | void initWithEncryptedFile(char *) throw (int); 25 | void initWithPortal(int) throw (int); 26 | 27 | void listSkylanders(); 28 | 29 | Skylander * getSkylander() { return sky; } 30 | 31 | // void setSkylander(Skylander *); 32 | 33 | bool writeSkylanderToPortal(int) throw (int); 34 | bool writeSkylanderToUnencryptedFile(char *) throw (int); 35 | bool writeSkylanderToEncryptedFile(char *) throw (int); 36 | 37 | enum IOException { 38 | Success, 39 | CannotOpenFile, 40 | InvalidFile, 41 | CannotWriteFile, 42 | UnableToGetUSBDevices, 43 | UnableToFindPortal, 44 | UnableToWriteToPortal, 45 | InvalidSkylanderBlock, 46 | UnableToReadFromPortal, 47 | PortalNotReady, 48 | WriteVerifyError, 49 | 50 | }; 51 | protected: 52 | 53 | void ReadSkylanderFile(char *name) throw (int); 54 | bool WriteSkylanderFile(char *name, unsigned char *) throw (int); 55 | void ReadPortal(unsigned char *, int) throw (int); 56 | 57 | void EncryptBuffer(unsigned char* ); 58 | void DecryptBuffer(unsigned char* ); 59 | 60 | }; -------------------------------------------------------------------------------- /skylander.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "crypt.h" 5 | #include "checksum.h" 6 | 7 | void MaxStats(unsigned char *buffer, char skill_path); 8 | 9 | 10 | class Skylander { 11 | #define SKYLANDER_SIZE 1024 12 | 13 | unsigned char data[SKYLANDER_SIZE]; 14 | char name[16]; 15 | int area; 16 | Checksum crc; 17 | 18 | public: 19 | 20 | Skylander (unsigned char *); 21 | void initSkylander(unsigned char *); 22 | unsigned char *getData(); 23 | unsigned long getSerial(); 24 | unsigned short getToyType(); 25 | const char * getToyTypeName(); 26 | const char * toyName(unsigned short); 27 | unsigned char * getTradingID(); 28 | unsigned int getXP(); 29 | void setXP(unsigned int); 30 | unsigned short getMoney(); 31 | void setMoney(unsigned short); 32 | unsigned char getArea0Sequence(); 33 | unsigned char getArea1Sequence(); 34 | unsigned short getSkill(); 35 | void setSkillLeft(unsigned short); 36 | void setSkillRight(unsigned short); 37 | void setSkill(unsigned short); 38 | const char * getPath(); 39 | unsigned char getPlatform(); 40 | const char * getPlatformName(); 41 | unsigned short getHat(); 42 | void setHat(unsigned short); 43 | char * getName(); 44 | unsigned short getHeroPoints(); 45 | void setHeroPoints(unsigned short); 46 | unsigned int getHeroicChallenges(); 47 | void setHeroicChallenges(unsigned int); 48 | 49 | void setAreaFromSequence(); 50 | 51 | int getArea(); 52 | void setArea(int); 53 | 54 | bool validateChecksum(); 55 | void computeChecksum(); 56 | 57 | void UpdateBuf( int , int , int , unsigned char ); 58 | void MaxXP(); 59 | void MaxMoney(); 60 | void MaxHeroPoints(); 61 | void MaxHeroicChallenges(); 62 | void MaxSkills(unsigned char); 63 | 64 | void dump(void); 65 | 66 | 67 | private: 68 | 69 | unsigned char getByte(int, int); 70 | unsigned short getShort(int,int); 71 | void setByte(int,int,unsigned char); 72 | void setShort(int, int, unsigned short); 73 | 74 | int getBlockNumberForArea(); 75 | void readName(); 76 | void fprinthex(FILE *, unsigned char *, unsigned int) ; 77 | 78 | } ; 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SkyReader 2 | ========= 3 | 4 | A Skylander portal reader/editor/writer for OSX. 5 | 6 | May compile on other platforms due to use of the cross platform hidapi usb library. Please check out the forks for other platform builds. 7 | 8 | HIDAPI can be found here http://www.signal11.us/oss/hidapi/ 9 | 10 | Usage: 11 | editor [-i |-p] [-s ] [-d] [-e] [-o |-P] [-M ] [-X experience] ... 12 | 13 | Reading/Writing: 14 | -i read skylander data from file, with option to decrypt the data. 15 | -p read skylander data from portal and decrypt the data. 16 | -s select which skylander. 17 | -d decrypt the data read from the file. 18 | -o write skylander data to . 19 | -P encrypt and write skylander data to the portal. 20 | -e encrypt data when writing file. 21 | -D dump the data of a skylander to the display. 22 | -l List skylanders on portal. 23 | 24 | Upgrade: 25 | -M upgrade skylander money (max 65,000). 26 | -X upgrade skylander Experience (level 10 = 33,000). 27 | -H upgrade skylander Hero Points (max 100). 28 | -C upgrade skylander challenges. 29 | -L upgrade the skylander skillpoints on the left path. 30 | -R upgrade the skylander skillpoints on the right path. 31 | -c update checksums. 32 | 33 | It is mandatory that you specify an input and an output. So input either from a file with the `-i` option or the Portal with the `-p` option. An output must also be specified either from a file with the `-o` option or to the Portal with the `-P` option. However the input and output cannot both be the Portal. 34 | 35 | Examples 36 | -------- 37 | editor -p -o spyro.bin 38 | This would save a copy of the figurine to the file spyro.bin 39 | 40 | editor -i spyro.bin -o spyro_upgrade.bin -L 65535 -M 65000 -X 33000 -H 100 41 | upgrade spyro.bin using skills on the LEFT path seen in the character menu 42 | and write it to file spyro_upgrade.bin 43 | 44 | editor -i spyro.bin -P -M 65000 -X 33000 45 | Upgrade skylander, leave skills as is, and write to the portal. 46 | 47 | editor -i spyro.bin -P 48 | Read file from spyro.bin and write it to the portal. 49 | -------------------------------------------------------------------------------- /crypt.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "crypt.h" 3 | 4 | /* 5 | data encryption 6 | Every block from 0x08 onward (with the exception of the access control blocks) is encrypted using a key unique to that block. 7 | The algorithm is 128-bit AES, ECB mode and zero-byte padding. As that's a symmetric key algorithm, the same key is used to 8 | both encrypt and decrypt. 9 | 10 | The key itself is the MD5 hash of the following 0x56 bytes: 11 | 12 | <1-byte block index> <0x35-byte constant> 13 | 14 | */ 15 | 16 | 17 | int Crypt::IsAccessControlBlock(unsigned int blockIndex) 18 | { 19 | return (((blockIndex % 4) != 3) ? 0 : 1); 20 | } 21 | 22 | int Crypt::ShouldEncryptBlock(unsigned int blockIndex) 23 | { 24 | if ((blockIndex >= 8) && (IsAccessControlBlock(blockIndex) == 0)) 25 | { 26 | return 1; 27 | } 28 | return 0; 29 | } 30 | 31 | 32 | void Crypt::ComputeMD5(unsigned char digest[16], void const* bytesIn, unsigned int inputLen) 33 | { 34 | MD5 md5; 35 | MD5Open(&md5); 36 | MD5Digest(&md5, bytesIn, inputLen); 37 | MD5Close(&md5, digest); 38 | } 39 | 40 | /* Compute key used by AES 41 | The key is the MD5 hash of the following 0x56 bytes: 42 | 43 | <1-byte block index> <0x35-byte constant> 44 | */ 45 | void Crypt::ComputeEncryptionKey(unsigned char keyOut[16], unsigned char const* tagBlocks0and1, unsigned int blockIndex) 46 | { 47 | // <0x35-byte constant> 48 | unsigned char hashConst[] = { 49 | 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x43, 0x29, 0x20, 0x32, // Copyright (C) 2 50 | 0x30, 0x31, 0x30, 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6F, 0x6E, 0x2E, 0x20, // 010 Activision. 51 | 0x41, 0x6C, 0x6C, 0x20, 0x52, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, // All Rights Reser 52 | 0x76, 0x65, 0x64, 0x2E, 0x20}; // ved. 53 | const int hashLen = 0x56; 54 | unsigned char hashBuf[hashLen]; 55 | unsigned char *numPtr = hashBuf; 56 | memcpy(numPtr, tagBlocks0and1, 0x20); 57 | numPtr += 0x20; 58 | numPtr[0] = (unsigned char)blockIndex; 59 | numPtr += 1; 60 | memcpy(numPtr, hashConst, 0x35); 61 | 62 | //fprinthex(stdout,hashBuf, 0x56); 63 | 64 | ComputeMD5(keyOut, hashBuf, 0x56); 65 | } 66 | 67 | 68 | #define KEYBITS 128 69 | void Crypt::EncryptAES128ECB(unsigned char * key, unsigned char const* plainTextIn, unsigned char* cipherTextOut) 70 | { 71 | unsigned long rk[RKLENGTH(KEYBITS)]; 72 | int nrounds; 73 | nrounds = rijndaelSetupEncrypt(rk, key, KEYBITS); 74 | rijndaelEncrypt(rk, nrounds, plainTextIn, cipherTextOut); 75 | } 76 | 77 | void Crypt::DecryptAES128ECB(unsigned char * key, unsigned char const* cipherTextIn, unsigned char* plainTextOut) 78 | { 79 | unsigned long rk[RKLENGTH(KEYBITS)]; 80 | int nrounds; 81 | nrounds = rijndaelSetupDecrypt(rk, key, KEYBITS); 82 | rijndaelDecrypt(rk, nrounds, cipherTextIn, plainTextOut); 83 | } 84 | 85 | void Crypt::EncryptTagBlock(unsigned char* blockData, unsigned int blockIndex, unsigned char const* tagBlocks0and1) 86 | { 87 | if (ShouldEncryptBlock(blockIndex) != 0) 88 | { 89 | unsigned char cipherText[16]; 90 | unsigned char aesKey[16]; 91 | ComputeEncryptionKey(aesKey, tagBlocks0and1, blockIndex); 92 | EncryptAES128ECB(aesKey, blockData, cipherText); 93 | memcpy(blockData, cipherText, 16); 94 | } 95 | } 96 | 97 | void Crypt::DecryptTagBlock(unsigned char* blockData, unsigned int blockIndex, unsigned char const* tagBlocks0and1) 98 | { 99 | if (ShouldEncryptBlock(blockIndex) != 0) 100 | { 101 | unsigned char plainText[16]; 102 | unsigned char aesKey[16]; 103 | ComputeEncryptionKey(aesKey, tagBlocks0and1, blockIndex); 104 | DecryptAES128ECB(aesKey, blockData, plainText); 105 | memcpy(blockData, plainText, 16); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /usbtest.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gcc -I/opt/local/include/libusb-1.0 -L/opt/local/lib -lusb-1.0 usbtest.c 3 | */ 4 | #include 5 | #include 6 | 7 | #define PORTAL_VENDOR 0x1430 8 | #define PORTAL_PRODUCT 0x0150 9 | #define BUFFER 128 10 | 11 | void fprinthex(FILE *f, char *c, unsigned int n) { 12 | unsigned int h,i; 13 | unsigned char j; 14 | 15 | 16 | for (h=0; h127) j='.'; 31 | fprintf (f,"%c",j); 32 | } else 33 | fprintf(f," "); 34 | } 35 | fprintf(f,"\n"); 36 | } 37 | } 38 | 39 | const char * endpoint_attribute (int epdesc) 40 | { 41 | 42 | int transfer_type = epdesc & 0x3; 43 | int sync_type = epdesc & 0xC; 44 | int usage_type = epdesc & 0x30; 45 | 46 | switch (transfer_type) { 47 | case LIBUSB_TRANSFER_TYPE_CONTROL: return "control"; 48 | case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: 49 | switch (sync_type) { 50 | case LIBUSB_ISO_SYNC_TYPE_NONE: return "isochronous_none"; 51 | case LIBUSB_ISO_SYNC_TYPE_ASYNC: return "isochronous_asynchronous"; 52 | case LIBUSB_ISO_SYNC_TYPE_ADAPTIVE: return "isochronous_adaptive"; 53 | case LIBUSB_ISO_SYNC_TYPE_SYNC: return "isochronous_synchronous"; 54 | } 55 | return "isochronous"; 56 | case LIBUSB_TRANSFER_TYPE_BULK: return "bulk"; 57 | case LIBUSB_TRANSFER_TYPE_INTERRUPT: return "interrupt"; 58 | } 59 | return "UNKNOWN"; 60 | 61 | } 62 | 63 | void printdev(libusb_device *dev) { 64 | struct libusb_device_descriptor desc; 65 | int r = libusb_get_device_descriptor(dev, &desc); 66 | if (r < 0) { 67 | printf("failed to get device descriptor\n"); 68 | return; 69 | } 70 | printf("Number of possible configurations: %d ",(int)desc.bNumConfigurations); 71 | printf("Device Class: %d ",(int)desc.bDeviceClass); 72 | printf("VendorID: %.4X ",desc.idVendor); 73 | printf("ProductID: %.4X\n",desc.idProduct); 74 | //cout<<"VendorID: "<bNumInterfaces); 79 | const struct libusb_interface *inter; 80 | const struct libusb_interface_descriptor *interdesc; 81 | const struct libusb_endpoint_descriptor *epdesc; 82 | int i; 83 | for( i=0; i<(int)config->bNumInterfaces; i++) { 84 | inter = &config->interface[i]; 85 | printf("Number of alternate settings: %d | ",inter->num_altsetting); 86 | int j; 87 | for( j=0; jnum_altsetting; j++) { 88 | interdesc = &inter->altsetting[j]; 89 | printf("Interface Number: %d | interface class: %d | Interface endpoints: %d ", 90 | (int)interdesc->bInterfaceNumber, 91 | (int)interdesc->bInterfaceClass, 92 | (int)interdesc->bNumEndpoints 93 | ); 94 | } 95 | } 96 | printf("\n"); 97 | libusb_free_config_descriptor(config); 98 | } 99 | 100 | 101 | void list_endpoints(libusb_device *dev) 102 | { 103 | struct libusb_config_descriptor *config; 104 | const struct libusb_interface *inter; 105 | const struct libusb_interface_descriptor *interdesc; 106 | const struct libusb_endpoint_descriptor *epdesc; 107 | int endpoints_no; 108 | int i; 109 | 110 | libusb_get_config_descriptor(dev, 0, &config); 111 | inter = &config->interface[0]; 112 | interdesc = &inter->altsetting[0]; 113 | endpoints_no=(int)interdesc->bNumEndpoints; 114 | 115 | printf("Number of endpoints: %d\n",endpoints_no); 116 | for(i=0;iendpoint[i]; 119 | if(epdesc->bEndpointAddress & 0x80) 120 | printf("found an IN End Point %d with attributes %s and address 0x%x\n",i,endpoint_attribute(epdesc->bmAttributes), epdesc->bEndpointAddress&0x7f); 121 | else 122 | printf("found an OUT End Point %d with attributes %s and address 0x%x\n",i,endpoint_attribute(epdesc->bmAttributes),epdesc->bEndpointAddress); 123 | } 124 | libusb_free_config_descriptor(config); 125 | 126 | return; 127 | } 128 | 129 | 130 | int main (int argc, char **argv) 131 | { 132 | 133 | unsigned char data[BUFFER]; 134 | unsigned short lang; 135 | int bytes; 136 | libusb_context *ctx; 137 | struct libusb_device_descriptor device_descriptor; 138 | 139 | libusb_init (&ctx); 140 | libusb_set_debug(ctx,0); 141 | 142 | // discover devices 143 | libusb_device **list; 144 | libusb_device *found = NULL; 145 | ssize_t cnt = libusb_get_device_list(ctx, &list); 146 | ssize_t i = 0; 147 | int err = 0; 148 | 149 | for (i = 0; i < cnt; i++) { 150 | libusb_device *device = list[i]; 151 | 152 | printdev(device); 153 | 154 | libusb_device_handle *handle; 155 | 156 | err = libusb_open(device, &handle); 157 | if (!err) 158 | { 159 | bytes = libusb_get_string_descriptor(handle, 0,0,data,BUFFER); 160 | 161 | lang = data[2]<<8|data[3]; 162 | 163 | if (bytes>=0) { 164 | // printf ("Bytes: %d\n",bytes); 165 | bytes = libusb_get_string_descriptor_ascii(handle, device_descriptor.iManufacturer,data,BUFFER); 166 | if (bytes>0) fprinthex(stdout, data,bytes); 167 | bytes = libusb_get_string_descriptor_ascii(handle, device_descriptor.iProduct,data,BUFFER); 168 | if (bytes>0) fprinthex(stdout, data,bytes); 169 | bytes = libusb_get_string_descriptor_ascii(handle, device_descriptor.iSerialNumber,data,BUFFER); 170 | if (bytes>0) fprinthex(stdout, data,bytes); 171 | 172 | } 173 | libusb_close(handle); 174 | 175 | } 176 | 177 | list_endpoints(device); 178 | 179 | printf("\n"); 180 | 181 | // printf ("%d %d %d\n",device_descriptor.iManufacturer,device_descriptor.iProduct,device_descriptor.iSerialNumber); 182 | 183 | if (device_descriptor.idVendor == PORTAL_VENDOR && device_descriptor.idProduct == PORTAL_PRODUCT) 184 | { 185 | found = device; 186 | } 187 | 188 | } 189 | 190 | if (found) { 191 | } 192 | 193 | libusb_free_device_list(list, 1); 194 | 195 | 196 | 197 | libusb_exit (ctx); 198 | return 0; 199 | } 200 | -------------------------------------------------------------------------------- /fileio.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "fileio.h" 3 | 4 | #define DEBUG 1 5 | 6 | void SkylanderIO::fprinthex(FILE *f, unsigned char *c, unsigned int n) { 7 | unsigned int h,i; 8 | unsigned char j; 9 | 10 | for (h=0; h=127) j='.'; 25 | fprintf (f,"%c",j); 26 | } else 27 | fprintf(f," "); 28 | } 29 | fprintf(f,"\n"); 30 | } 31 | } 32 | 33 | 34 | SkylanderIO::SkylanderIO () 35 | { 36 | sky = NULL; 37 | buffer=new unsigned char [1025]; 38 | } 39 | 40 | SkylanderIO::~SkylanderIO () 41 | { 42 | delete buffer; 43 | } 44 | 45 | 46 | void SkylanderIO::initWithUnencryptedFile(char * name) throw (int) 47 | { 48 | if (!sky) { 49 | ReadSkylanderFile(name); 50 | sky = new Skylander(buffer); 51 | } 52 | } 53 | 54 | void SkylanderIO::initWithEncryptedFile(char * name) throw (int) 55 | { 56 | if (!sky) { 57 | ReadSkylanderFile(name); 58 | DecryptBuffer(buffer); 59 | sky = new Skylander(buffer); 60 | } 61 | } 62 | 63 | void SkylanderIO::initWithPortal(int number) throw (int) { 64 | 65 | #if DEBUG 66 | printf(">>> SkylanderIO::initWithPortal\n"); 67 | #endif 68 | 69 | if (!sky) { 70 | //printf("Reading Skylander from portal.\n"); 71 | ReadPortal(buffer,number); 72 | DecryptBuffer(buffer); 73 | sky = new Skylander(buffer); 74 | // printf("\nSkylander read from portal.\n"); 75 | 76 | } 77 | #if DEBUG 78 | printf("<<< SkylanderIO::initWithPortal\n"); 79 | #endif 80 | } 81 | 82 | void SkylanderIO::ReadPortal(unsigned char *s, int number) throw (int) 83 | { 84 | 85 | #if DEBUG 86 | printf(">>> SkylanderIO::ReadPortal\n"); 87 | #endif 88 | unsigned char data[0x10]; 89 | unsigned char *ptr; 90 | 91 | PortalIO *port; 92 | 93 | port = new PortalIO(); 94 | 95 | // must start with a read of block zero 96 | port->ReadBlock(0, data, number); 97 | 98 | // I don't know that we need this, but the web driver sets the color when reading the data 99 | port->SetPortalColor(0xC8, 0xC8, 0xC8); 100 | 101 | ptr = s; 102 | memcpy(ptr, data, sizeof(data)); 103 | 104 | for(int block=1; block < 0x40; ++block) { 105 | ptr += 0x10; 106 | port->ReadBlock(block, data, number); 107 | memcpy(ptr, data, sizeof(data)); 108 | } 109 | 110 | delete port; 111 | #if DEBUG 112 | printf("<<< SkylanderIO::ReadPortal\n"); 113 | #endif 114 | } 115 | 116 | bool SkylanderIO::writeSkylanderToPortal(int number) throw (int) 117 | { 118 | bool bResult; 119 | bool bNewSkylander = false; 120 | unsigned char data[0x10]; 121 | 122 | unsigned char old[1024]; 123 | unsigned char skydata[1024]; 124 | 125 | Crypt crypt; 126 | 127 | if (sky) { 128 | 129 | PortalIO *port; 130 | 131 | ReadPortal(old,number); 132 | 133 | memcpy (skydata,sky->getData(),1024); 134 | 135 | EncryptBuffer(skydata); 136 | 137 | printf("\nWriting Skylander to portal.\n"); 138 | 139 | port = new PortalIO(); 140 | 141 | 142 | for(int i=0; i<2; i++) { 143 | // two pass write 144 | // write the access control blocks first 145 | bool selectAccessControlBlock; 146 | if(i == 0) { 147 | selectAccessControlBlock = 1; 148 | } else { 149 | selectAccessControlBlock = 0; 150 | } 151 | 152 | for(int block=0; block < 0x40; ++block) { 153 | bool changed, OK; 154 | int offset = block * BLOCK_SIZE; 155 | if(crypt.IsAccessControlBlock(block) == selectAccessControlBlock) { 156 | changed = (memcmp(old + offset, skydata+offset,BLOCK_SIZE) != 0); 157 | if(changed) { 158 | port->WriteBlock( block, skydata+offset, bNewSkylander); 159 | } 160 | } 161 | } 162 | } 163 | 164 | return true; 165 | } 166 | return false; 167 | } 168 | 169 | 170 | bool SkylanderIO::writeSkylanderToUnencryptedFile(char *name) throw (int) 171 | { 172 | if (sky) { 173 | WriteSkylanderFile(name,sky->getData()); 174 | } 175 | return true; 176 | } 177 | 178 | bool SkylanderIO::writeSkylanderToEncryptedFile(char *name) throw (int) 179 | { 180 | if (sky) { 181 | unsigned char skydata[1024]; 182 | 183 | memcpy (skydata,sky->getData(),1024); 184 | EncryptBuffer(skydata); 185 | WriteSkylanderFile(name,skydata); 186 | } 187 | return true; 188 | 189 | } 190 | 191 | // Encrypt entire buffer of character data 192 | // Buffer is entire 1024 byte block of character data. 193 | 194 | void SkylanderIO::EncryptBuffer(unsigned char* buffer) { 195 | unsigned int blockIndex; 196 | unsigned char* blockData; 197 | unsigned char const* tagBlocks0and1; 198 | 199 | tagBlocks0and1 = buffer; 200 | for(blockIndex = 0x08; blockIndex < 0x40; blockIndex++) { 201 | blockData = buffer + blockIndex * 0x10; 202 | crypt.EncryptTagBlock(blockData, blockIndex, tagBlocks0and1); 203 | } 204 | } 205 | 206 | // Decrypt entire buffer of character data 207 | // Buffer is entire 1024 byte block of character data. 208 | void SkylanderIO::DecryptBuffer(unsigned char* buffer) { 209 | unsigned int blockIndex; 210 | unsigned char* blockData; 211 | unsigned char const* tagBlocks0and1; 212 | 213 | tagBlocks0and1 = buffer; 214 | for(blockIndex = 0x08; blockIndex < 0x40; blockIndex++) { 215 | blockData = buffer + blockIndex * 0x10; 216 | crypt.DecryptTagBlock(blockData, blockIndex, tagBlocks0and1); 217 | } 218 | } 219 | 220 | 221 | 222 | void SkylanderIO::ReadSkylanderFile(char *name) throw (int) 223 | { 224 | FILE *file; 225 | unsigned long fileLen; 226 | 227 | //Open file 228 | file = fopen(name, "rb"); 229 | if (!file) 230 | { 231 | throw 1; 232 | // fprintf(stderr, "Unable to open file %s\n", name); 233 | // return NULL; 234 | } 235 | 236 | //Get file length 237 | fseek(file, 0, SEEK_END); 238 | fileLen=ftell(file); 239 | fseek(file, 0, SEEK_SET); 240 | 241 | if(fileLen != 1024){ 242 | throw 2; 243 | } 244 | //Read file contents into buffer 245 | fread(buffer, fileLen, 1, file); 246 | fclose(file); 247 | 248 | 249 | } 250 | 251 | bool SkylanderIO::WriteSkylanderFile(char *name, unsigned char * filedata) throw (int) 252 | { 253 | FILE *file; 254 | bool OK = true; 255 | int count; 256 | 257 | file = fopen(name, "wb"); 258 | if (!file) 259 | { 260 | throw 1; 261 | // fprintf(stderr, "Unable to open file %s for writing.\n", name); 262 | // return false; 263 | } 264 | 265 | count = fwrite(filedata, 1024, 1, file); 266 | if(count < 1) { 267 | fclose(file); 268 | throw 3; 269 | OK = false; 270 | } 271 | 272 | fclose(file); 273 | return OK; 274 | } 275 | 276 | void SkylanderIO::listSkylanders() { 277 | 278 | PortalIO *port; 279 | Skylander *sky; 280 | unsigned char data[0x10]; 281 | 282 | 283 | port = new PortalIO(); 284 | sky = new Skylander(buffer); 285 | 286 | try { 287 | for (int s=0; s<3; s++) 288 | { 289 | memset(data,0,0x10); 290 | // must start with a read of block zero 291 | port->ReadBlock(1, data, s); 292 | 293 | printf("%0d: %s\n",s,sky->toyName(data[0] + data[1] * 0x100)); 294 | } 295 | delete sky; 296 | delete port; 297 | 298 | } catch (int e) { 299 | delete sky; 300 | delete port; 301 | if (e != 8) 302 | throw e; 303 | } 304 | 305 | } 306 | 307 | -------------------------------------------------------------------------------- /portalio_hidapi.cpp: -------------------------------------------------------------------------------- 1 | #include "portalio.h" 2 | 3 | #define DEBUG 1 4 | 5 | //Port usleep to windows 6 | #ifdef _WIN32 7 | #include 8 | #else 9 | #include 10 | int Sleep(int sleepMs) {return usleep(sleepMs * 1000); } 11 | #endif 12 | 13 | /* 14 | Number of possible configurations: 1 Device Class: 0 VendorID: 1430 ProductID: 0150 15 | Total interface number: 1 ||| Number of alternate settings: 1 | Interface Number: 0 | 16 | Number of endpoints: 2 17 | found an IN End Point 0 with attributes interrupt and address 0x1 18 | found an OUT End Point 1 with attributes interrupt and address 0x1 19 | */ 20 | 21 | void PortalIO::OpenPortalHandle() throw (int) 22 | { 23 | struct hid_device_info *list, *attributes; 24 | 25 | wchar_t wstr[MAX_STR]; 26 | 27 | hPortalHandle = NULL; 28 | 29 | list = hid_enumerate(0x0, 0x0); 30 | 31 | if (!list) { 32 | throw 4; 33 | } 34 | attributes = list; 35 | while (attributes) { 36 | if (((attributes->vendor_id == 0x12ba) || (attributes->vendor_id == 0x54c)) || (attributes->vendor_id == 0x1430)) 37 | { 38 | if ((attributes->product_id == 0x150) || (attributes->product_id == 0x967)) 39 | { 40 | printf("Found portal usb device\n"); 41 | int err; 42 | 43 | hPortalHandle = hid_open(attributes->vendor_id, attributes->product_id, NULL); 44 | if (hPortalHandle == NULL) 45 | { 46 | printf ("Error communicating with Portal.\n "); 47 | } 48 | 49 | /* 50 | wstr[0] = 0x0000; 51 | err = hid_get_manufacturer_string(hPortalHandle, wstr, MAX_STR); 52 | if (err < 0) 53 | printf("Unable to read manufacturer string\n"); 54 | printf("Manufacturer String: %ls\n", wstr); 55 | 56 | // Read the Product String 57 | wstr[0] = 0x0000; 58 | err = hid_get_product_string(hPortalHandle, wstr, MAX_STR); 59 | if (err < 0) 60 | printf("Unable to read product string\n"); 61 | printf("Product String: %ls\n", wstr); 62 | 63 | // Read the Serial Number String 64 | wstr[0] = 0x0000; 65 | err = hid_get_serial_number_string(hPortalHandle, wstr, MAX_STR); 66 | if (err < 0) 67 | printf("Unable to read serial number string\n"); 68 | printf("Serial Number String: (%d) %ls", wstr[0], wstr); 69 | printf("\n"); 70 | */ 71 | 72 | break; 73 | } 74 | } 75 | attributes = attributes->next; 76 | } 77 | hid_free_enumeration(list); 78 | 79 | if (!hPortalHandle) 80 | throw 5; 81 | 82 | } 83 | 84 | 85 | // Send a command to the portal 86 | void PortalIO::Write(RWBlock *pb) throw (int) { 87 | 88 | #if DEBUG 89 | printf(">>> PortalIO::Write\n"); 90 | #endif 91 | pb->buf[0] = 0; // Use report 0 92 | 93 | /* 94 | SkylanderIO *skio; 95 | skio = new SkylanderIO(); 96 | printf(">>>\n"); 97 | skio->fprinthex(stdout,pb->buf, 0x21); 98 | delete skio; 99 | */ 100 | if (hid_write(hPortalHandle, pb->buf, 0x21) == -1) 101 | { 102 | #if DEBUG 103 | printf("<<< PortalIO::Write throw 6\n"); 104 | #endif 105 | throw 6; 106 | } 107 | #if DEBUG 108 | printf("<<< PortalIO::Write\n"); 109 | #endif 110 | 111 | } 112 | 113 | bool PortalIO::CheckResponse (RWBlock *res, char expect) throw (int) 114 | { 115 | 116 | #if DEBUG 117 | printf(">>> PortalIO::CheckResponse\n"); 118 | #endif 119 | int b = hid_read_timeout(hPortalHandle, res->buf, rw_buf_size, TIMEOUT); 120 | 121 | if(b<0) 122 | throw 8; 123 | 124 | #if DEBUG 125 | printf("PortalIO::CheckResponse hid_read_timeout bytes read = %d\n",b); 126 | #endif 127 | 128 | res->dwBytesTransferred = b; 129 | 130 | // this is here to debug the different responses from the portal. 131 | 132 | #if DEBUG 133 | SkylanderIO *skio; 134 | skio = new SkylanderIO(); 135 | printf("<<<\n"); 136 | skio->fprinthex(stdout,res->buf, 0x21); 137 | delete skio; 138 | #endif 139 | 140 | 141 | // found wireless USB but portal is not connected 142 | if (res->buf[0] == 'Z') 143 | throw 9; 144 | 145 | // Status says no skylander on portal 146 | if (res->buf[0] == 'Q' && res->buf[1] == 0) 147 | throw 11; 148 | 149 | 150 | #if DEBUG 151 | printf("<<< PortalIO::CheckResponse\n"); 152 | #endif 153 | return (res->buf[0] != expect); 154 | 155 | } 156 | 157 | bool PortalIO::ReadBlock(unsigned int block, unsigned char data[0x10], int skylander) throw (int) { 158 | RWBlock req, res; 159 | bool gotData; 160 | unsigned char followup; 161 | 162 | #if DEBUG 163 | printf(">>> PortalIO:ReadBlock\n"); 164 | #endif 165 | 166 | if(block >= 0x40) { 167 | #if DEBUG 168 | printf("<<< PortalIO:ReadBlock throw 7\n"); 169 | #endif 170 | throw 7; 171 | } 172 | 173 | // Send query request 174 | 175 | 176 | for(int attempt=0;attempt<15;attempt++) 177 | { 178 | int i=0; 179 | gotData = false; 180 | 181 | memset(req.buf, 0, rw_buf_size); 182 | req.buf[1] = 'Q'; 183 | 184 | followup = 0x10 + skylander ; 185 | req.buf[2] = followup; 186 | 187 | if(block == 0) { 188 | req.buf[2] = followup + 0x10; 189 | } 190 | 191 | req.buf[3] = (unsigned char)block; 192 | 193 | memset(&(res.buf), 0, rw_buf_size); 194 | 195 | 196 | do { Write(&req); } while (CheckResponse(&res,'Q')); 197 | 198 | if(res.buf[0] == 'Q' && res.buf[2] == (unsigned char)block) { 199 | // Got our query back 200 | if(res.buf[1] == followup) { 201 | /* got the query back with no error */ 202 | memcpy(data, res.buf + 3, 0x10); 203 | #if DEBUG 204 | printf("<<< PortalIO:ReadBlock\n"); 205 | #endif 206 | return true; 207 | } 208 | } 209 | 210 | 211 | } // retries 212 | 213 | #if DEBUG 214 | printf("<<< PortalIO:ReadBlock throw 8\n"); 215 | #endif 216 | throw 8; 217 | } 218 | 219 | bool PortalIO::WriteBlock(unsigned int block, unsigned char data[0x10], int skylander) throw (int) 220 | { 221 | RWBlock req; 222 | unsigned char verify[0x10]; 223 | bool OK; 224 | 225 | // printf("."); 226 | 227 | for(int retries=0; retries < 3; retries++) 228 | { 229 | // Write request 230 | // W 57 10 <0x10 bytes of data> 231 | memset(req.buf, 0, rw_buf_size); 232 | req.buf[1] = 'W'; 233 | req.buf[2] = 0x10 + skylander; 234 | req.buf[3] = (unsigned char) block; 235 | memcpy(req.buf+4, data, 0x10); 236 | 237 | try { 238 | 239 | Write(&req); 240 | Sleep(100); //Wait 0.1 seconds for write to take effect 241 | 242 | memset(verify, 0xCD, sizeof(verify)); 243 | 244 | ReadBlock(block, verify, skylander); 245 | if (memcmp(data, verify, sizeof(verify)) ) { throw 22; } 246 | return true; 247 | } catch (int e) { 248 | ; 249 | } 250 | } 251 | throw 22; 252 | } 253 | 254 | 255 | // 256 | unsigned char PortalIO::PortalStatus() throw (int) 257 | { 258 | RWBlock req,res; 259 | 260 | memset(req.buf, 0, rw_buf_size); 261 | req.buf[1] = 'S'; 262 | do { Write(&req); } while (CheckResponse(&res,'S')); 263 | 264 | return res.buf[1]; 265 | } 266 | 267 | 268 | // Start portal 269 | void PortalIO::RestartPortal() throw (int) 270 | { 271 | RWBlock req,res; 272 | 273 | memset(req.buf, 0, rw_buf_size); 274 | req.buf[1] = 'R'; 275 | do { Write(&req); } while (CheckResponse(&res,'R')); 276 | 277 | } 278 | 279 | // Antenna up / activate 280 | void PortalIO::ActivatePortal(int active) throw (int) 281 | { 282 | RWBlock req,res; 283 | 284 | memset(req.buf, 0, rw_buf_size); 285 | req.buf[1] = 'A'; 286 | req.buf[2] = active; 287 | do { Write(&req); } while (CheckResponse(&res,'A')); 288 | 289 | } 290 | 291 | // Set the portal color 292 | void PortalIO::SetPortalColor(unsigned char r, unsigned char g, unsigned char b) throw (int) 293 | { 294 | RWBlock req,res; 295 | 296 | memset(req.buf, 0, rw_buf_size); 297 | req.buf[1] = 'C'; 298 | req.buf[2] = r; // R 299 | req.buf[3] = g; // G 300 | req.buf[4] = b; // B 301 | 302 | // no response for this one. 303 | Write(&req); 304 | 305 | } 306 | 307 | // Release hPortalInstance 308 | PortalIO::~PortalIO() { 309 | 310 | ActivatePortal(0); 311 | 312 | hid_close(hPortalHandle); 313 | 314 | } 315 | 316 | void PortalIO::flash() throw (int) 317 | { 318 | 319 | for (;;) { 320 | ActivatePortal(1); 321 | ActivatePortal(0); 322 | } 323 | } 324 | 325 | 326 | PortalIO::PortalIO() throw (int) 327 | { 328 | //printf("Connecting to portal.\n"); 329 | 330 | OpenPortalHandle(); 331 | RestartPortal(); 332 | ActivatePortal(1); 333 | SetPortalColor(0xC8, 0xC8, 0xC8); 334 | 335 | printf ("Portal Status: %d\n",PortalStatus()); 336 | 337 | } 338 | 339 | 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /md5.cpp: -------------------------------------------------------------------------------- 1 | /* md5.c - RSA Data Security, Inc., MD5 Message-Digest Algorithm */ 2 | 3 | /* NOTE: Numerous changes have been made; the following notice is 4 | included to satisfy legal requirements. 5 | 6 | Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 7 | rights reserved. 8 | 9 | License to copy and use this software is granted provided that it 10 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 11 | Algorithm" in all material mentioning or referencing this software 12 | or this function. 13 | 14 | License is also granted to make and use derivative works provided 15 | that such works are identified as "derived from the RSA Data 16 | Security, Inc. MD5 Message-Digest Algorithm" in all material 17 | mentioning or referencing the derived work. 18 | 19 | RSA Data Security, Inc. makes no representations concerning either 20 | the merchantability of this software or the suitability of this 21 | software for any particular purpose. It is provided "as is" 22 | without express or implied warranty of any kind. 23 | 24 | These notices must be retained in any copies of any part of this 25 | documentation and/or software. 26 | */ 27 | 28 | #include 29 | #include "md5.h" 30 | 31 | void MD5Open(MD5 *md5) 32 | { 33 | md5->count[0] = md5->count[1] = 0; 34 | /* Load magic initialization constants.*/ 35 | md5->state[0] = 0x67452301; 36 | md5->state[1] = 0xefcdab89; 37 | md5->state[2] = 0x98badcfe; 38 | md5->state[3] = 0x10325476; 39 | } 40 | 41 | /* Constants for MD5Transform routine. */ 42 | 43 | #define S11 7 44 | #define S12 12 45 | #define S13 17 46 | #define S14 22 47 | #define S21 5 48 | #define S22 9 49 | #define S23 14 50 | #define S24 20 51 | #define S31 4 52 | #define S32 11 53 | #define S33 16 54 | #define S34 23 55 | #define S41 6 56 | #define S42 10 57 | #define S43 15 58 | #define S44 21 59 | 60 | /* F, G, H and I are basic MD5 functions. */ 61 | 62 | #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) 63 | #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) 64 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 65 | #define I(x, y, z) ((y) ^ ((x) | (~z))) 66 | 67 | /* ROTATE_LEFT rotates x left n bits. */ 68 | 69 | #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) 70 | 71 | /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. 72 | Rotation is separate from addition to prevent recomputation. 73 | */ 74 | 75 | #define FF(a, b, c, d, x, s, ac) { \ 76 | (a) += F((b), (c), (d)) + (x) + (UINT4)(ac); \ 77 | (a) = ROTATE_LEFT((a), (s)); \ 78 | (a) += (b); \ 79 | } 80 | #define GG(a, b, c, d, x, s, ac) { \ 81 | (a) += G((b), (c), (d)) + (x) + (UINT4)(ac); \ 82 | (a) = ROTATE_LEFT((a), (s)); \ 83 | (a) += (b); \ 84 | } 85 | #define HH(a, b, c, d, x, s, ac) { \ 86 | (a) += H((b), (c), (d)) + (x) + (UINT4)(ac); \ 87 | (a) = ROTATE_LEFT((a), (s)); \ 88 | (a) += (b); \ 89 | } 90 | #define II(a, b, c, d, x, s, ac) { \ 91 | (a) += I((b), (c), (d)) + (x) + (UINT4)(ac); \ 92 | (a) = ROTATE_LEFT((a), (s)); \ 93 | (a) += (b); \ 94 | } 95 | 96 | 97 | /* MD5 basic transformation. Transforms state based on block. */ 98 | 99 | static void MD5Transform(UINT4 state[4], const unsigned char block[64]) 100 | { 101 | UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; 102 | /* Move contents of block to x, putting bytes in little-endian order. */ 103 | #ifdef LITTLE_ENDIAN 104 | memcpy(x, block, 64); 105 | #else 106 | { 107 | unsigned int i, j; 108 | for (i = j = 0; i < 16; i++, j+= 4) 109 | { 110 | x[i] = (UINT4) block[j] | (UINT4) block[j+1] << 8 | 111 | (UINT4) block[j+2] << 16 | (UINT4) block[j+3] << 24; 112 | } 113 | } 114 | #endif 115 | /* Round 1 */ 116 | FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ 117 | FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ 118 | FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ 119 | FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ 120 | FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ 121 | FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ 122 | FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ 123 | FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ 124 | FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ 125 | FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ 126 | FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ 127 | FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ 128 | FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ 129 | FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ 130 | FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ 131 | FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ 132 | /* Round 2 */ 133 | GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ 134 | GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ 135 | GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ 136 | GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ 137 | GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ 138 | GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ 139 | GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ 140 | GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ 141 | GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ 142 | GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ 143 | GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ 144 | GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ 145 | GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ 146 | GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ 147 | GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ 148 | GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ 149 | /* Round 3 */ 150 | HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ 151 | HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ 152 | HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ 153 | HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ 154 | HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ 155 | HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ 156 | HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ 157 | HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ 158 | HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ 159 | HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ 160 | HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ 161 | HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ 162 | HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ 163 | HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ 164 | HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ 165 | HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ 166 | /* Round 4 */ 167 | II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ 168 | II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ 169 | II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ 170 | II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ 171 | II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ 172 | II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ 173 | II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ 174 | II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ 175 | II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ 176 | II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ 177 | II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ 178 | II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ 179 | II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ 180 | II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ 181 | II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ 182 | II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ 183 | state[0] += a; 184 | state[1] += b; 185 | state[2] += c; 186 | state[3] += d; 187 | /* Zeroize sensitive information. */ 188 | memset(x, 0, sizeof(x)); 189 | } 190 | 191 | void MD5Digest(MD5 *md5, const void *input, unsigned int inputLen) 192 | { 193 | unsigned int i, index, partLen; 194 | /* Compute number of bytes mod 64 */ 195 | index = (unsigned int)((md5->count[0] >> 3) & 0x3F); 196 | /* Update number of bits */ 197 | if ((md5->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3)) 198 | md5->count[1]++; 199 | md5->count[1] += ((UINT4)inputLen >> 29); 200 | partLen = 64 - index; 201 | /* Transform as many times as possible.*/ 202 | if (inputLen >= partLen) 203 | { 204 | memcpy(&md5->buffer[index], input, partLen); 205 | MD5Transform(md5->state, md5->buffer); 206 | for (i = partLen; i + 63 < inputLen; i += 64) 207 | MD5Transform(md5->state, (unsigned char *) input + i); 208 | index = 0; 209 | } 210 | else 211 | i = 0; 212 | /* Buffer remaining input */ 213 | memcpy(&md5->buffer[index], (char *) input + i, inputLen-i); 214 | } 215 | 216 | /* ENCODE packs a 32-bit unsigned integer into 4 bytes in little-endian 217 | order. 218 | */ 219 | 220 | #ifdef LITTLE_ENDIAN 221 | #define ENCODE(p,n) *(UINT4 *)(p) = n 222 | #else 223 | #define ENCODE(p,n) (p)[0]=n,(p)[1]=n>>8,(p)[2]=n>>16,(p)[3]=n>>24 224 | #endif 225 | 226 | void MD5Close(MD5 *md5, unsigned char digest[16]) 227 | { 228 | unsigned char bits[8]; 229 | unsigned int index, padLen; 230 | static unsigned char PADDING[64] = 231 | { 232 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 234 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 235 | }; 236 | /* Save number of bits */ 237 | ENCODE(bits, md5->count[0]); 238 | ENCODE(bits+4, md5->count[1]); 239 | /* Pad out to 56 mod 64. */ 240 | index = (unsigned int)((md5->count[0] >> 3) & 0x3f); 241 | padLen = (index < 56) ? (56 - index) : (120 - index); 242 | MD5Digest(md5, PADDING, padLen); 243 | /* Append length (before padding) */ 244 | MD5Digest(md5, bits, 8); 245 | /* Store state in digest */ 246 | ENCODE(digest, md5->state[0]); 247 | ENCODE(digest+4, md5->state[1]); 248 | ENCODE(digest+8, md5->state[2]); 249 | ENCODE(digest+12, md5->state[3]); 250 | /* Zeroize sensitive information. */ 251 | memset(md5, 0, sizeof(MD5)); 252 | } 253 | -------------------------------------------------------------------------------- /checksum.cpp: -------------------------------------------------------------------------------- 1 | #include "checksum.h" 2 | 3 | /* 4 | data checksums 5 | The checksums are a mess. There are four "types" of checksums: 6 | Type 0: this is a CRC16 checksum of the first 0x1E unsigned chars of sector 0. The checksum itself is stored in block 0x01, offset 0x0E. 7 | Type 1: this is a CRC16 checksum of the data area header. As there are two data areas, there are two of these checksums. 8 | One is at block 0x08, offset 0x0E, and the other is at block 0x24, offset 0x0E. 9 | Type 2: this is a CRC16 checksum of the data area. As there are two data areas, there are two of these checksums. 10 | One is at block 0x08, offset 0x0C, and the other is at block 0x24, offset 0x0C. 11 | Type 3: this is another CRC16 checksum of the data area, except padded with zeroes. As there are two data areas, 12 | there are two of these checksums. One is at block 0x08, offset 0x0A, and the other is at block 0x24, offset 0x0A. 13 | As type 0 is a checksum of a *supposedly* read-only sector, it's not all that important. It's also very straightforward to understand. 14 | 15 | The type 1 checksum is a checksum of just one block, the data area header (0x08 and 0x24). As it's also stored WITHIN the 16 | data area header, a default value must be supplied for the checksum before actually calculating it. That value is 0x0005. 17 | 18 | The type 2 checksum is actually only a checksum of the first 4 blocks (EXCLUDING the data area header, and the access control blocks). 19 | 20 | The type 3 checksum is a checksum of the next 4 blocks (EXCLUDING the data area header, and the access control blocks), 21 | and then 0x0E blocks of zeroes. 22 | 23 | Just to re-iterate, the encryption is applied AFTER all this checksum mess is done. 24 | */ 25 | 26 | // CCITT CRC Code 27 | // Update the CRC for transmitted and received data using 28 | // the CCITT 16bit algorithm (X^16 + X^12 + X^5 + 1). 29 | unsigned short Checksum::UpdateCcittCrc16(unsigned short crc16, unsigned char data) 30 | { 31 | unsigned short num2 = (unsigned short) (data << 8); 32 | for (unsigned int i = 0; i < 8; i++) 33 | { 34 | int num3; 35 | if ((crc16 ^ num2) > 0x7fff) 36 | { 37 | num3 = 1; 38 | } 39 | else 40 | { 41 | num3 = 0; 42 | } 43 | crc16 = (unsigned short) ((crc16 << 1) ^ (num3 * 0x1021)); 44 | num2 = (unsigned short) (num2 << 1); 45 | } 46 | return crc16; 47 | } 48 | 49 | unsigned short Checksum::ComputeCcittCrc16(void const* data, unsigned int bytes) 50 | { 51 | unsigned short crc = 0xffff; 52 | unsigned char const* numPtr = (unsigned char const*)data; 53 | for (unsigned int i = 0; i < bytes; i++) 54 | { 55 | crc = UpdateCcittCrc16(crc, *((unsigned char const*) (numPtr + i))); 56 | } 57 | return crc; 58 | } 59 | 60 | bool Checksum::getChecksumParameters(int checksumType, unsigned int* checksumOffset, unsigned int* dataOffset, unsigned int* dataLength) 61 | { 62 | switch (checksumType) 63 | { 64 | case 0: 65 | // Type 0 checksum. 66 | *checksumOffset = 0x1E; // The checksum itself is stored in block 0x01, offset 0x0E. 67 | *dataOffset = 0; 68 | *dataLength = 0x1E; // checksum of the first 0x1E unsigned chars of sector 0. 69 | break; 70 | 71 | case 1: 72 | // Type 1 checksum. 73 | // Type 1: this is a CRC16 checksum of the data area header. As there are two data areas, 74 | // there are two of these checksums. One is at block 0x08, offset 0x0E, and the other is at block 0x24, offset 0x0E. 75 | *checksumOffset = 0x0E; // Checksum is stored within the data header block. 76 | *dataOffset = 0; 77 | *dataLength = 0x10; // The type 1 checksum is a checksum of just one block, the data area header (blocks 0x08 and 0x24). 78 | break; 79 | 80 | case 2: 81 | // Type 2 checksum. 82 | // Type 2: this is a CRC16 checksum of the data area. As there are two data areas, there are two of these checksums. 83 | // One is at block 0x08, offset 0x0C, and the other is at block 0x24, offset 0x0C. 84 | *checksumOffset = 0x0C; 85 | *dataOffset = 0x10; 86 | *dataLength = 0x40; // Checksum of the first 4 blocks (EXCLUDING the data area header, and the access control blocks). 87 | break; 88 | 89 | case 3: 90 | // Type 3 checksum. 91 | // Type 3: this is another CRC16 checksum of the data area, except padded with zeroes. As there are two data areas, 92 | // there are two of these checksums. One is at block 0x08, offset 0x0A, and the other is at block 0x24, offset 0x0A. 93 | // The type 3 checksum is a checksum of the next 4 blocks after the type 2 checksum 94 | // (EXCLUDING the data area header, and the access control blocks), and then 0x0E blocks of zeroes. 95 | *checksumOffset = 0x0A; 96 | *dataOffset = 0x50; 97 | *dataLength = 0x40; 98 | break; 99 | 100 | default: 101 | return false; 102 | } 103 | return true; 104 | } 105 | 106 | bool Checksum::computeChecksum(int type, void const* memoryIn, unsigned short* checksum) 107 | { 108 | unsigned int startBlock; 109 | unsigned int cntBlock; 110 | unsigned int block; 111 | unsigned char const* numPtr = (unsigned char const*)memoryIn; 112 | 113 | Crypt crypt; 114 | 115 | if ((type == 0) || (type == 1)) 116 | { 117 | unsigned int dataLength; 118 | unsigned int dataOffset; 119 | unsigned int checksumOffset; 120 | 121 | if (!getChecksumParameters(type, &checksumOffset, &dataOffset, &dataLength)) 122 | { 123 | return false; 124 | } 125 | if (type == 1) 126 | { 127 | unsigned char header[0x10]; 128 | memcpy(header, (void const*) (numPtr + dataOffset), 0x10); 129 | *(header + 14) = 5; 130 | *(header + 15) = 0; 131 | *checksum = ComputeCcittCrc16((void const*) &header, dataLength); 132 | } 133 | else 134 | { 135 | *checksum = ComputeCcittCrc16((void const*) (numPtr + dataOffset), dataLength); 136 | } 137 | return true; 138 | } 139 | if (type == 2) 140 | { 141 | startBlock = 1; 142 | cntBlock = 4; 143 | } 144 | else if (type == 3) 145 | { 146 | startBlock = 5; 147 | cntBlock = 4; 148 | } 149 | else 150 | { 151 | return false; 152 | } 153 | numPtr += (startBlock * 0x10); 154 | *checksum = 0xffff; 155 | block = startBlock; 156 | while(true) 157 | { 158 | if (block >= (startBlock + cntBlock)) 159 | { 160 | if (type != 3) 161 | { 162 | return true; 163 | } 164 | block = startBlock + cntBlock; 165 | break; 166 | } 167 | if (!crypt.IsAccessControlBlock(block)) 168 | { 169 | for (unsigned int i = 0; i < 0x10; i++) 170 | { 171 | *checksum = UpdateCcittCrc16(*checksum, *((unsigned char*) (numPtr + i))); 172 | } 173 | } 174 | numPtr += 0x10; 175 | block++; 176 | } 177 | 178 | // Pad Type 3 checksum with 0x0E blocks of zeroes 179 | while (block < 0x1c) 180 | { 181 | if (!crypt.IsAccessControlBlock(block)) 182 | { 183 | for (unsigned int j = 0; j < 0x10; j++) 184 | { 185 | *checksum = UpdateCcittCrc16(*checksum, 0); 186 | } 187 | } 188 | block++; 189 | } 190 | return true; 191 | } 192 | 193 | 194 | // validateChecksum 195 | // buffer: pointer to entire decrypted character as single chunk of memory 196 | // type: checksum type 197 | // dataArea: dataArea to validate. 198 | // A value of 0 indicates the first data area starting at 0x08 199 | // A value of 1 indicates the second data area starting at 0x24 200 | // overwrite: if true, replace checksum in buffer with newly computed checksum 201 | // 202 | // returns true if old checksum in buffer matches computed checksum. 203 | bool Checksum::validateChecksum(unsigned char *buffer, int type, int dataArea, bool overwrite) 204 | { 205 | unsigned int checksumOffset; 206 | unsigned int areaSequenceOffset; 207 | unsigned short computedChecksum; 208 | unsigned int dataLength; 209 | unsigned int dataOffset; 210 | unsigned int offset = 0; 211 | unsigned char *ptr; 212 | bool match; 213 | 214 | if (!getChecksumParameters(type, &checksumOffset, &dataOffset, &dataLength)) 215 | { 216 | return false; 217 | } 218 | 219 | if (type != 0) 220 | { 221 | int dataAreaBlock; 222 | if (dataArea == 0) 223 | { 224 | dataAreaBlock = 0x08; 225 | } 226 | else 227 | { 228 | dataAreaBlock = 0x24; 229 | } 230 | offset += (unsigned int) (dataAreaBlock * 0x10); 231 | } 232 | 233 | ptr = buffer + offset; 234 | 235 | if(overwrite && type == 1) { 236 | // Before computing checksum 1 (and after computing checksum 2 and 3) 237 | // update sequence number. 238 | areaSequenceOffset = 0x09; 239 | ptr[areaSequenceOffset]++; // increment sequence 240 | } 241 | 242 | if (!computeChecksum(type, ptr, &computedChecksum)) 243 | { 244 | return false; 245 | } 246 | unsigned short oldChecksum = (unsigned short) ((ptr[checksumOffset] & 0xff) | ((ptr[(int) (checksumOffset + 1)] & 0xff) << 8)); 247 | match = (oldChecksum == computedChecksum); 248 | 249 | if(overwrite) { 250 | // overwrite old value with newly computed checksum 251 | ptr[checksumOffset] = computedChecksum & 0xff; 252 | ptr[checksumOffset+1] = (computedChecksum >> 8) & 0xff; 253 | } 254 | 255 | return match; 256 | } 257 | 258 | bool Checksum::ValidateAllChecksums(unsigned char *buffer, bool overwrite) 259 | { 260 | bool OK = true; 261 | bool res; 262 | int dataArea; 263 | int type; 264 | // When computing checksums for overwrite, they have to be done in the following order. 265 | // Compute checksum 3 and 2, then increment the area sequence number by 1, 266 | // then compute checksum 1. 267 | // 268 | // In the logic below, the area sequence number is set just prior to computing checksum 1. 269 | for(dataArea = 0; dataArea <= 1; dataArea++) { 270 | for(type = 3; type >=0; type--) { 271 | res = validateChecksum(buffer, type, dataArea, overwrite); 272 | if(!res && !overwrite) { 273 | fprintf(stderr, "Checksum failure for checksum type %d, data area %d", type, dataArea); 274 | } 275 | OK = OK && res; 276 | } 277 | } 278 | return OK; 279 | } 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "checksum.h" 7 | #include "crypt.h" 8 | #include "fileio.h" 9 | #include "portalio.h" 10 | #include "skylander.h" 11 | 12 | using namespace std; 13 | 14 | void usage() 15 | { 16 | printf("\n" 17 | "Usage:\n" 18 | "editor [-i |-p] [-s ] [-d] [-e] [-o |-P] [-M ] [-X experience] ... \n" 19 | "\n" 20 | "Reading/Writing:\n" 21 | "-i \tread skylander data from file, with option to decrypt the data.\n" 22 | "-p\t\tread skylander data from portal and decrypt the data.\n" 23 | "-s select which skylander.\n" 24 | "-d\t\tdecrypt the data read from the file.\n" 25 | "-o \twrite skylander data to .\n" 26 | "-a\t\twrite skylander data to automatic filename.\n" 27 | "-P\t\tencrypt and write skylander data to the portal.\n" 28 | "-e\t\tencrypt data when writing file.\n" 29 | "-D\t\tdump the data of a skylander to the display.\n" 30 | "-l\t\tList skylanders on portal.\n" 31 | 32 | "\nUpgrade:\n" 33 | "-M \tupgrade skylander money (max 65,000).\n" 34 | "-X \t\tupgrade skylander Experience (level 10 = 33,000).\n" 35 | "-H \t\tupgrade skylander Hero Points (max 100).\n" 36 | "-C \tupgrade skylander challenges.\n" 37 | "-L \tupgrade the skillpoints left path. (0 = set path)\n" 38 | "-R \tupgrade the skillpoints right path. (0 = set path)\n" 39 | "-c\t\tupdate checksums.\n" 40 | "\n" 41 | "Examples: \n" 42 | "editor -p -o spyro.bin\n" 43 | "This would save a copy of the figurine to the file dspyro.bak\n" 44 | "editor -i spyro.bin -o spyro_upgrade.bin -L 65535 -M 65000 -X 33000 -H 100\n" 45 | "upgrade spyro.bin using skills on the LEFT path seen in the character menu\n" 46 | "and write it to file spyro_upgrade.bin\n" 47 | "\n" 48 | "editor -i spyro.bin -P -M 65000 -X 33000\n" 49 | "Upgrade skylander, leave skills as is, and write to the portal.\n" 50 | "\n" 51 | "editor -i spyro.bin -P\n" 52 | "Read file from spyro.bin and write it to the portal.\n"); 53 | } 54 | 55 | void printquad (unsigned int i) 56 | { 57 | 58 | printf("%02X ",i & 0xff); 59 | printf("%02X ",(i & 0xff00 )/ 0x100); 60 | printf("%02X ",(i & 0xff0000) / 0x10000); 61 | printf("%02X ",(i & 0xff000000) / 0x1000000); 62 | printf("\n"); 63 | 64 | } 65 | 66 | int main(int argc, char* argv[]) 67 | { 68 | unsigned char *buffer, *original_data; 69 | bool OK, OK2; 70 | 71 | bool encrypt,decrypt,portalIn,portalOut,dump,upgrade,flash,list,autoFile; 72 | 73 | char * inFile, *outFile; 74 | 75 | const static char *legal_flags = "alFePpcDo:i:dM:X:H:C:L:R:s:"; 76 | 77 | encrypt = false; 78 | decrypt = false; 79 | portalIn = false; 80 | portalOut = false; 81 | upgrade = false; 82 | dump = false; 83 | inFile = NULL; 84 | outFile = NULL; 85 | flash = false; 86 | list = false; 87 | autoFile = false; 88 | 89 | unsigned int money, xp, hp, challenges, skillleft, skillright,skylander_number; 90 | bool pathleft, pathright; 91 | 92 | money = 0; 93 | xp = 0; 94 | hp = 0; 95 | challenges = 0; 96 | skillleft = 0; 97 | pathleft = false; 98 | skillright = 0; 99 | pathright = false; 100 | skylander_number = 0; 101 | 102 | SkylanderIO *skio; 103 | Checksum crc; 104 | 105 | int k; 106 | 107 | while ((k = getopt (argc, argv, legal_flags)) != -1) { 108 | switch (k) { 109 | case 'e': encrypt = true; break; 110 | case 'd': decrypt = true; break; 111 | case 'P': portalOut = true; break; 112 | case 'p': portalIn = true; break; 113 | case 'D': dump = true; break; 114 | case 'F': flash = true; break; 115 | case 'i': 116 | inFile = new char[strlen(optarg)+1]; 117 | strcpy(inFile,optarg); 118 | break; 119 | case 'o': 120 | outFile = new char[strlen(optarg)+1]; 121 | strcpy(outFile,optarg); 122 | break; 123 | case 'a': 124 | autoFile = true; 125 | outFile = new char[65]; 126 | strcpy(outFile,"TEMP"); 127 | break; 128 | case 'M': 129 | money = atoi(optarg); 130 | upgrade = true; 131 | break; 132 | case 'X': 133 | xp = atoi(optarg); 134 | upgrade = true; 135 | break; 136 | case 'H': 137 | hp = atoi(optarg); 138 | upgrade = true; 139 | break; 140 | case 'C': 141 | challenges = atoi(optarg); 142 | upgrade = true; 143 | break; 144 | case 'L': 145 | skillleft = atoi(optarg); 146 | pathleft = true; 147 | upgrade = true; 148 | break; 149 | case 'R': 150 | skillright = atoi(optarg); 151 | pathright = true; 152 | upgrade = true; 153 | break; 154 | case 's': 155 | skylander_number = atoi(optarg); 156 | break; 157 | case 'c': 158 | upgrade = true; 159 | break; 160 | case 'l': 161 | list = true; 162 | break; 163 | default: 164 | usage () ; 165 | exit (0); 166 | 167 | } 168 | } 169 | 170 | try { 171 | 172 | // some entertainment. 173 | if (flash) { 174 | PortalIO *pio ; 175 | pio = new PortalIO(); 176 | 177 | pio->flash(); 178 | exit (0); 179 | } 180 | 181 | if (list) { 182 | printf ("Listing Skylanders.\n\n"); 183 | 184 | skio = new SkylanderIO(); 185 | skio->listSkylanders(); 186 | 187 | exit (0); 188 | } 189 | 190 | // validate command line options 191 | if ( (!inFile && !portalIn) || (inFile && portalIn)) { 192 | printf ("Must Choose One of: read from file -i or read from portal -p\n"); 193 | usage(); 194 | exit(0); 195 | } 196 | 197 | if (!outFile && !portalOut && !dump) { 198 | printf ("Nothing to write. Choose file -o, portal -P or dump -D\n"); 199 | usage(); 200 | exit(0); 201 | } 202 | 203 | if (portalIn && upgrade) { 204 | printf ("It is not recommended to upgrade directly from the portal (write to a file first)\n"); 205 | usage(); 206 | exit(0); 207 | } 208 | 209 | if (portalIn && portalOut) { 210 | printf ("It is not recommended to read and write directly from the portal (write to a file first)\n"); 211 | usage(); 212 | exit(0); 213 | } 214 | 215 | skio = new SkylanderIO(); 216 | 217 | printf ("====================================================\n\n"); 218 | printf ("Reading Skylander\n\n"); 219 | 220 | if (portalIn) { 221 | skio->initWithPortal(skylander_number); 222 | } 223 | if (inFile) { 224 | if (decrypt) { 225 | skio->initWithEncryptedFile(inFile); 226 | } else { 227 | skio->initWithUnencryptedFile(inFile); 228 | } 229 | } 230 | 231 | if(! skio->getSkylander()->validateChecksum()) { 232 | fprintf(stderr, "Warning. Skylander data read from portal, but checksums are incorrect. File may be corrupt.\n"); 233 | } 234 | /* 235 | else { 236 | printf ("Skylander Checksum OK.\n"); 237 | } 238 | */ 239 | if (dump) { 240 | Skylander * sky ; 241 | sky = skio->getSkylander() ; 242 | 243 | printf("Serial Number: %08lX\n",sky->getSerial()); 244 | printf("Toy Type: %s (%d)\n\n",sky->getToyTypeName(),sky->getToyType()); 245 | printf ("Trading ID: "); 246 | skio->fprinthex(stdout,sky->getTradingID(),8); 247 | 248 | //Debugging Use 249 | printf("Area 0 sequence: %d\n",sky->getArea0Sequence()); 250 | printf("Area 1 sequence: %d\n",sky->getArea1Sequence()); 251 | printf("Area %d selected (higher sequence)\n\n",sky->getArea()); 252 | 253 | printf("Experience: %d\n",sky->getXP()); 254 | printf("Money: %d\n",sky->getMoney()); 255 | printf("Skills: %04X - %s\n",sky->getSkill(), sky->getPath()); 256 | printf("Platforms: %s\n",sky->getPlatformName()); 257 | printf("Nickname: %s\n",sky->getName()); 258 | printf("Hat: %d\n",sky->getHat()); 259 | printf("Hero Points: %d\n",sky->getHeroPoints()); 260 | printf("Heroic Challenges: %08x\n",sky->getHeroicChallenges()); 261 | printf("\n"); 262 | } 263 | 264 | if (upgrade) { 265 | if (money) { skio->getSkylander()->setMoney(money); } 266 | if (xp) { skio->getSkylander()->setXP(xp); } 267 | if (hp) { skio->getSkylander()->setHeroPoints(hp); } 268 | if (challenges) { skio->getSkylander()->setHeroicChallenges(challenges); } 269 | if (pathleft) { skio->getSkylander()->setSkillLeft(skillleft); } 270 | if (pathright) { skio->getSkylander()->setSkillRight(skillright); } 271 | 272 | skio->getSkylander()->computeChecksum(); 273 | } 274 | 275 | if (outFile || portalOut) { 276 | printf ("Writing Skylander.\n"); 277 | } 278 | if (outFile) { 279 | if (autoFile) { 280 | Skylander * sky; 281 | sky = skio->getSkylander(); 282 | unsigned long num = sky->getSerial(); 283 | unsigned long serial = ((num>>24)&0xff) | // move byte 3 to byte 0 284 | ((num<<8)&0xff0000) | // move byte 1 to byte 2 285 | ((num>>8)&0xff00) | // move byte 2 to byte 1 286 | ((num<<24)&0xff000000); // byte 0 to byte 3 287 | 288 | sprintf(outFile, "%s - %s - %08lX.dmp", sky->getToyTypeName(), sky->getPath(), serial); 289 | printf("Saving to automatic filename: %s\n", outFile); 290 | } 291 | 292 | if (encrypt) { 293 | skio->writeSkylanderToEncryptedFile(outFile); 294 | } else { 295 | skio->writeSkylanderToUnencryptedFile(outFile); 296 | } 297 | } 298 | if (portalOut) { 299 | skio->writeSkylanderToPortal(skylander_number); 300 | } 301 | 302 | delete skio; 303 | 304 | 305 | printf("\nSuccess!\n\n"); 306 | return 0; 307 | 308 | } catch (int e) { 309 | 310 | switch (e) { 311 | case 1: printf ("Cannot open File.\n"); break; 312 | case 2: printf ("Invalid Skylander File.\n"); break; 313 | case 3: printf ("Cannot write to File.\n"); break; 314 | case 4: printf ("Unable to get USB Device List.\n"); break; 315 | case 5: printf ("Cannot Find Portal USB.\n"); break; 316 | case 6: printf ("Unable to write to Portal.\n"); break; 317 | case 7: printf ("Invalid Skylander Block.\n"); break; 318 | case 8: printf ("Unable to read Skylander from Portal.\n"); break; 319 | case 9: printf ("Wireless portal not connected.\n"); break; 320 | case 10: printf ("Skylander Write Verify Error.\n"); break; 321 | case 11: printf ("No Skylander detected on portal.\n"); break; 322 | default: printf ("Unknown exception: %d.\n",e); break; 323 | } 324 | 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /toynames.cpp: -------------------------------------------------------------------------------- 1 | #include "skylander.h" 2 | 3 | //Easier to maintain list of all the ID numbers with the names 4 | //See toydata.xls for all the data that makes this easy to update 5 | //Need to eventually figure out a good way to incorporate the Series / Variant info also. 6 | //Credit for most of the info: 7 | // https://github.com/reedstrm/SkyReader 8 | // https://github.com/bettse/SkyReader/tree/more_info 9 | // https://github.com/Proxmark/proxmark3/blob/master/client/lualibs/default_toys.lua 10 | 11 | const char * Skylander::toyName(unsigned short toyID) { 12 | 13 | switch (toyID) { 14 | 15 | case 0 : return "Whirlwind"; //0000|0030|regular|air 16 | case 1 : return "Sonic Boom"; //0100|0030|regular|air 17 | case 2 : return "Warnado"; //0200|0030|regular|air 18 | case 3 : return "Lightning Rod"; //0300|0030|regular|air 19 | case 4 : return "Bash"; //0400|0030|regular|earth 20 | case 5 : return "Terrafin"; //0500|0030|regular|earth 21 | case 6 : return "Dino-Rang"; //0600|0030|regular|earth 22 | case 7 : return "Prism Break"; //0700|0030|regular|earth 23 | case 8 : return "Sunburn"; //0800|0030|regular|fire 24 | case 9 : return "Eruptor"; //0900|0030|regular|fire 25 | case 10 : return "Ignitor"; //0a00|0030|regular|fire 26 | case 11 : return "Flameslinger"; //0b00|0030|regular|fire 27 | case 12 : return "Zap"; //0c00|0030|regular|water 28 | case 13 : return "Wham-Shell"; //0d00|0030|regular|water 29 | case 14 : return "Gill Grunt"; //0e00|0030|regular|water 30 | case 15 : return "Slam Bam"; //0f00|0030|regular|water 31 | case 16 : return "Spyro"; //1000|0030|regular|magic 32 | case 17 : return "Voodood"; //1100|0030|regular|magic 33 | case 18 : return "Double Trouble"; //1200|0030|regular|magic 34 | case 19 : return "Trigger Happy"; //1300|0030|regular|tech 35 | case 20 : return "Drobot"; //1400|0030|regular|tech 36 | case 21 : return "Drill Sergeant"; //1500|0030|regular|tech 37 | case 22 : return "Boomer"; //1600|0030|regular|tech 38 | case 23 : return "Wrecking Ball"; //1700|0030|regular|magic 39 | case 24 : return "Camo"; //1800|0030|regular|life 40 | case 25 : return "Zook"; //1900|0030|regular|life 41 | case 26 : return "Stealth Elf"; //1a00|0030|regular|life 42 | case 27 : return "Stump Smash"; //1b00|0030|regular|life 43 | case 28 : return "Dark Spyro"; //1c00|0030|regular|magic 44 | case 29 : return "Hex"; //1d00|0030|regular|undead 45 | case 30 : return "Chop Chop"; //1e00|0030|regular|undead 46 | case 31 : return "Ghost Roaster"; //1f00|0030|regular|undead 47 | case 32 : return "Cynder"; //2000|0030|regular|undead 48 | case 100 : return "Jet Vac"; //6400|0030|regular|air 49 | case 101 : return "Swarm"; //6500|0030|giant|air 50 | case 102 : return "Crusher"; //6600|0030|giant|earth 51 | case 103 : return "Flashwing"; //6700|0030|regular|earth 52 | case 104 : return "Hot Head"; //6800|0030|giant|fire 53 | case 105 : return "Hot Dog"; //6900|0030|regular|fire 54 | case 106 : return "Chill"; //6a00|0030|regular|water 55 | case 107 : return "Thumpback"; //6b00|0030|giant|water 56 | case 108 : return "Pop Fizz"; //6c00|0030|regular|magic 57 | case 109 : return "Ninjini"; //6d00|0030|giant|magic 58 | case 110 : return "Bouncer"; //6e00|0030|giant|tech 59 | case 111 : return "Sprocket"; //6f00|0030|regular|tech 60 | case 112 : return "Tree Rex"; //7000|0030|giant|life 61 | case 113 : return "Shroomboom"; //7100|0030|regular|life 62 | case 114 : return "Eye-Brawl"; //7200|0030|giant|undead 63 | case 115 : return "Fright Rider"; //7300|0030|regular|undead 64 | case 200 : return "Anvil Rain"; //c800|0030|item|none 65 | case 201 : return "Treasure Chest"; //c900|0030|item|none 66 | case 202 : return "Healing Elixer"; //ca00|0030|item|none 67 | case 203 : return "Ghost Swords"; //cb00|0030|item|none 68 | case 204 : return "Time Twister"; //cc00|0030|item|none 69 | case 205 : return "Sky-Iron Shield"; //cd00|0030|item|none 70 | case 206 : return "Winged Boots"; //ce00|0030|item|none 71 | case 207 : return "Sparx Dragonfly"; //cf00|0030|item|none 72 | case 208 : return "Dragonfire Cannon"; //d000|0030|item|none 73 | case 209 : return "Scorpion Striker Catapult"; //d100|0030|item|none 74 | case 230 : return "Hand Of Fate"; //e600|0030|item|none 75 | case 231 : return "Piggy Bank"; //e700|0030|item|none 76 | case 232 : return "Rocket Ram"; //e800|0030|item|none 77 | case 233 : return "Tiki Speaky"; //e900|0030|item|none 78 | case 300 : return "Dragons Peak"; //2c01|0030|location|none 79 | case 301 : return "Empire of Ice"; //2d01|0030|location|none 80 | case 302 : return "Pirate Seas"; //2e01|0030|location|none 81 | case 303 : return "Darklight Crypt"; //2f01|0030|location|none 82 | case 304 : return "Volcanic Vault"; //3001|0030|location|none 83 | case 305 : return "Mirror Of Mystery"; //3101|0030|location|none 84 | case 306 : return "Nightmare Express"; //3201|0030|location|none 85 | case 307 : return "Sunscraper Spire"; //3301|0030|location|light 86 | case 308 : return "Midnight Museum"; //3401|0030|location|dark 87 | case 404 : return "Bash"; //9401|0030|legendary|earth 88 | case 416 : return "Spyro"; //a001|0030|legendary|magic 89 | case 419 : return "Trigger Happy"; //a301|0030|legendary|tech 90 | case 430 : return "Chop Chop"; //ae01|0030|legendary|undead 91 | case 450 : return "Gusto"; //c201|0030|trapmaster|air 92 | case 451 : return "Thunderbolt"; //c301|0030|trapmaster|air 93 | case 452 : return "Fling Kong"; //c401|0030|regular|air 94 | case 453 : return "Blades"; //c501|0030|regular|air 95 | case 454 : return "Wallop"; //c601|0030|trapmaster|earth 96 | case 455 : return "Head Rush"; //c701|0030|trapmaster|earth 97 | case 456 : return "Fist Bump"; //c801|0030|regular|earth 98 | case 457 : return "Rocky Roll"; //c901|0030|regular|earth 99 | case 458 : return "Wildfire"; //ca01|0030|trapmaster|fire 100 | case 459 : return "Ka Boom"; //cb01|0030|trapmaster|fire 101 | case 460 : return "Trail Blazer"; //cc01|0030|regular|fire 102 | case 461 : return "Torch"; //cd01|0030|regular|fire 103 | case 462 : return "Snap Shot"; //ce01|0030|trapmaster|water 104 | case 463 : return "Lob Star"; //cf01|0030|trapmaster|water 105 | case 464 : return "Flip Wreck"; //d001|0030|regular|water 106 | case 465 : return "Echo"; //d101|0030|regular|water 107 | case 466 : return "Blastermind"; //d201|0030|trapmaster|magic 108 | case 467 : return "Enigma"; //d301|0030|trapmaster|magic 109 | case 468 : return "Deja Vu"; //d401|0030|regular|magic 110 | case 469 : return "Cobra Cadabra"; //d501|0030|regular|magic 111 | case 470 : return "Jawbreaker"; //d601|0030|trapmaster|tech 112 | case 471 : return "Gearshift"; //d701|0030|trapmaster|tech 113 | case 472 : return "Chopper"; //d801|0030|regular|tech 114 | case 473 : return "Tread Head"; //d901|0030|regular|tech 115 | case 474 : return "Bushwhack"; //da01|0030|trapmaster|life 116 | case 475 : return "Tuff Luck"; //db01|0030|trapmaster|life 117 | case 476 : return "Food Fight"; //dc01|0030|regular|life 118 | case 477 : return "High Five"; //dd01|0030|regular|life 119 | case 478 : return "Krypt King"; //de01|0030|trapmaster|undead 120 | case 479 : return "Short Cut"; //df01|0030|trapmaster|undead 121 | case 480 : return "Bat Spin"; //e001|0030|regular|undead 122 | case 481 : return "Funny Bone"; //e101|0030|regular|undead 123 | case 482 : return "Knight light"; //e201|0030|trapmaster|light 124 | case 483 : return "Spotlight"; //e301|0030|regular|light 125 | case 484 : return "Knight Mare"; //e401|0030|trapmaster|dark 126 | case 485 : return "Blackout"; //e501|0030|regular|dark 127 | case 502 : return "Bop"; //f601|0030|mini|earth 128 | case 503 : return "Spry"; //f701|0030|mini|magic 129 | case 504 : return "Hijinx"; //f801|0030|mini|undead 130 | case 505 : return "Terrabite"; //f901|0030|mini|earth 131 | case 506 : return "Breeze"; //fa01|0030|mini|air 132 | case 507 : return "Weeruptor"; //fb01|0030|mini|fire 133 | case 508 : return "Pet Vac"; //fc01|0030|mini|air 134 | case 509 : return "Small Fry"; //fd01|0030|mini|fire 135 | case 510 : return "Drobit"; //fe01|0030|mini|tech 136 | case 514 : return "Gill Runt"; //0202|0030|mini|water 137 | case 519 : return "Trigger Snappy"; //0702|0030|mini|tech 138 | case 526 : return "Whisper Elf"; //0e02|0030|mini|life 139 | case 540 : return "Barkley"; //1c02|0030|mini|life 140 | case 541 : return "Thumpling"; //1d02|0030|mini|water 141 | case 542 : return "Mini Jini"; //1e02|0030|mini|magic 142 | case 543 : return "Eye Small"; //1f02|0030|mini|undead 143 | case 1004 : return "Blast Zone"; //||swapforce|fire 144 | case 1015 : return "Wash Buckler"; //||swapforce|water 145 | case 2004 : return "Blast Zone (Head)"; //||swapforce|fire 146 | case 2015 : return "Wash Buckler (Head)"; //||swapforce|water 147 | case 3000 : return "Scratch"; //b80b|0030|regular|air 148 | case 3001 : return "Pop Thorn"; //b90b|0030|regular|air 149 | case 3002 : return "Slobber Tooth"; //ba0b|0030|regular|earth 150 | case 3003 : return "Scorp"; //bb0b|0030|regular|earth 151 | case 3004 : return "Fryno"; //bc0b|0030|regular|fire 152 | case 3005 : return "Smolderdash"; //bd0b|0030|regular|fire 153 | case 3006 : return "Bumble Blast"; //be0b|0030|regular|life 154 | case 3007 : return "Zoo Lou"; //bf0b|0030|regular|life 155 | case 3008 : return "Dune Bug"; //c00b|0030|regular|magic 156 | case 3009 : return "Star Strike"; //c10b|0030|regular|magic 157 | case 3010 : return "Countdown"; //c20b|0030|regular|tech 158 | case 3011 : return "Wind Up"; //c30b|0030|regular|tech 159 | case 3012 : return "Roller Brawl"; //c40b|0030|regular|undead 160 | case 3013 : return "Grim Creeper"; //c50b|0030|regular|undead 161 | case 3014 : return "Rip Tide"; //c60b|0030|regular|water 162 | case 3015 : return "Punk Shock"; //c70b|0030|regular|water 163 | 164 | //Default fallback option with toyID 165 | default : 166 | char* toyName = new char[25]; 167 | sprintf(toyName, "zzUNKNOWN_%hu", toyID); 168 | return toyName; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /portalio_libusb.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | #include 8 | #include 9 | 10 | 11 | #include "fileio.h" 12 | #include "crypt.h" 13 | 14 | libusb_context *g_ctx = NULL; 15 | libusb_device_handle *g_hPortalHandle = NULL; 16 | 17 | #define OVERLAP_SIZE 8192 18 | 19 | const char * libusb_error(int err) { 20 | 21 | switch(err) 22 | { 23 | case 0: return "SUCCESS"; 24 | case LIBUSB_ERROR_IO: return "IO error"; 25 | case LIBUSB_ERROR_INVALID_PARAM: "Invalid Parameter"; 26 | case LIBUSB_ERROR_ACCESS: "Access denied"; 27 | case LIBUSB_ERROR_NO_DEVICE: return "No such device"; 28 | case LIBUSB_ERROR_NOT_FOUND: return "Entity not found"; 29 | case LIBUSB_ERROR_BUSY: return "Resource busy"; 30 | case LIBUSB_ERROR_TIMEOUT: return "Operation timed out"; 31 | case LIBUSB_ERROR_PIPE: return "PIPE"; 32 | case LIBUSB_ERROR_INTERRUPTED: "call interrupted"; 33 | case LIBUSB_ERROR_OVERFLOW: return "OVERFLOW"; 34 | case LIBUSB_ERROR_NO_MEM: return "Insufficient memory"; 35 | case LIBUSB_ERROR_NOT_SUPPORTED: return "Operation not supported"; 36 | case LIBUSB_ERROR_OTHER: return "Other error"; 37 | default: return "UNKNOWN"; 38 | } 39 | 40 | 41 | } 42 | 43 | void fprinthex(FILE *f, unsigned char *c, unsigned int n) { 44 | unsigned int h,i; 45 | unsigned char j; 46 | 47 | 48 | for (h=0; h127) j='.'; 63 | fprintf (f,"%c",j); 64 | } else 65 | fprintf(f," "); 66 | } 67 | fprintf(f,"\n"); 68 | } 69 | } 70 | 71 | 72 | bool OpenPortalHandle(libusb_device_handle **phPortalHandle) 73 | { 74 | int OK; 75 | libusb_device **list; 76 | ssize_t i = 0; 77 | struct libusb_device_descriptor attributes; 78 | 79 | *phPortalHandle = NULL; 80 | 81 | libusb_init (&g_ctx); 82 | libusb_set_debug(g_ctx,0); 83 | 84 | ssize_t cnt = libusb_get_device_list(g_ctx, &list); 85 | 86 | if (cnt < 0) 87 | return false; 88 | 89 | for (i = 0; i < cnt; i++) { 90 | libusb_device *device = list[i]; 91 | libusb_get_device_descriptor(device,&attributes); 92 | 93 | if (((attributes.idVendor == 0x12ba) || (attributes.idVendor == 0x54c)) || (attributes.idVendor == 0x1430)) 94 | { 95 | if ((attributes.idProduct == 0x150) || (attributes.idProduct == 0x967)) 96 | { 97 | printf("Found portal usb device\n"); 98 | int err; 99 | libusb_ref_device(device); 100 | err= libusb_open(device, phPortalHandle); 101 | printf ("usb open: %s\n",libusb_error(err)); 102 | 103 | err = libusb_claim_interface(*phPortalHandle, 0); 104 | printf ("claim interface: %s\n",libusb_error(err)); 105 | 106 | break; 107 | } 108 | } 109 | } 110 | libusb_free_device_list(list, 1); 111 | 112 | // HidD_GetHidGuid(&guid); 113 | // OK = OpenPortalHandleFromGUID(guid, phPortalHandle); 114 | 115 | return (*phPortalHandle != NULL); 116 | } 117 | 118 | 119 | #define rw_buf_size 0x21 120 | 121 | typedef struct { 122 | unsigned char buf[rw_buf_size]; 123 | int dwBytesTransferred; 124 | } RWBlock; 125 | 126 | /* 127 | Number of possible configurations: 1 Device Class: 0 VendorID: 1430 ProductID: 0150 128 | Total interface number: 1 ||| Number of alternate settings: 1 | Interface Number: 0 | 129 | Number of endpoints: 2 130 | found an IN End Point 0 with attributes interrupt and address 0x1 131 | found an OUT End Point 1 with attributes interrupt and address 0x1 132 | */ 133 | 134 | // Send a command to the portal 135 | bool Write(libusb_device_handle *hPortalHandle, RWBlock *pb) { 136 | 137 | int transferred; 138 | int err; 139 | 140 | printf("Write\n"); 141 | 142 | pb->buf[0] = 0; // Use report 0 143 | 144 | fprinthex(stdout,pb->buf,0x21); 145 | 146 | err = libusb_interrupt_transfer (hPortalHandle, 0x1, pb->buf, 0x21, &transferred, 30000); 147 | 148 | 149 | printf("Write, %d bytes transferred (err = %s)\n",transferred,libusb_error(err)); 150 | 151 | return err == 0; 152 | 153 | // return HidD_SetOutputReport(hPortalHandle, pb->buf, 0x21); 154 | } 155 | 156 | bool ReadBlock(libusb_device_handle *hPortalHandle, unsigned int block, unsigned char data[0x10], 157 | bool isNEWskylander) { 158 | RWBlock req, res; 159 | bool running = true; 160 | bool gotData; 161 | unsigned char ovlr[OVERLAP_SIZE]; 162 | unsigned short err; 163 | unsigned char followup; 164 | 165 | printf("ReadBlock\n"); 166 | 167 | 168 | if(block >= 0x40) { 169 | return false; 170 | } 171 | 172 | printf("."); 173 | 174 | for(int retries = 0; retries < 3; retries++) 175 | { 176 | // Send query request 177 | memset(req.buf, 0, rw_buf_size); 178 | req.buf[1] = 'Q'; 179 | if(isNEWskylander) { 180 | followup = 0x11; 181 | if(block == 0) { 182 | req.buf[2] = 0x21; 183 | } else { 184 | req.buf[2] = followup; 185 | } 186 | } else { 187 | followup = 0x10; 188 | if(block == 0) { 189 | req.buf[2] = 0x20; 190 | } else { 191 | req.buf[2] = followup; 192 | } 193 | } 194 | 195 | req.buf[3] = (unsigned char)block; 196 | 197 | memset(&(res.buf), 0, rw_buf_size); 198 | 199 | 200 | // Must set the Offset and OffsetHigh members of the OVERLAPPED structure to zero. 201 | // memset(&ovlr, 0, sizeof(ovlr)); 202 | 203 | // ovlr.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // read event 204 | 205 | int i=0; 206 | gotData = false; 207 | 208 | 209 | Write(hPortalHandle, &req); 210 | // Don't wait. Start polling for result immediately 211 | 212 | for(; i<40; ++i) // try up to 40 reads 213 | { 214 | int b; 215 | // bool b = ReadFile(hPortalHandle, res.buf, rw_buf_size, &(res.dwBytesTransferred), &ovlr); 216 | b = libusb_interrupt_transfer (hPortalHandle, 0x81, res.buf, rw_buf_size, &(res.dwBytesTransferred), 30000); 217 | 218 | 219 | if (b>=0) fprinthex(stdout,res.buf,res.dwBytesTransferred); 220 | 221 | 222 | if(b<0) 223 | { 224 | 225 | printf("error reading from usb handle: %s\n",libusb_error(b)); 226 | 227 | /* failed to get data immediately*/ 228 | // err = GetLastError(); 229 | if(b == 0) 230 | { 231 | /* wait for data */ 232 | // b = GetOverlappedResult(hPortalHandle, &ovlr, &res.dwBytesTransferred, TRUE); 233 | if(!b) 234 | { 235 | /* wait failed */ 236 | break; 237 | } 238 | } 239 | else 240 | { 241 | /* some other error */ 242 | break; 243 | } 244 | } 245 | if(res.dwBytesTransferred > 0) 246 | { 247 | /* has data */ 248 | if(res.buf[1] == 'Q' && res.buf[3] == (unsigned char)block) { 249 | // Got our query back 250 | if(res.buf[2] == followup) { 251 | /* got the query back with no error */ 252 | gotData = true; 253 | break; 254 | } 255 | } 256 | 257 | res.buf[0] = 0; // make sure we are using report 0 258 | } 259 | } /* read loop */ 260 | 261 | // CloseHandle(ovlr.hEvent); 262 | 263 | if(gotData) { 264 | break; 265 | } 266 | 267 | } // retries 268 | 269 | if(gotData) { 270 | memcpy(data, res.buf + 4, 0x10); 271 | } 272 | 273 | return gotData; 274 | } 275 | 276 | bool WriteBlock(libusb_device_handle *hPortalHandle, unsigned int block, unsigned char data[0x10], 277 | bool isNEWskylander) { 278 | RWBlock req; 279 | unsigned char verify[0x10]; 280 | bool OK; 281 | 282 | printf("."); 283 | 284 | for(int retries=0; retries < 3; retries++) 285 | { 286 | // Write request 287 | // W 57 10 <0x10 bytes of data> 288 | memset(req.buf, 0, rw_buf_size); 289 | req.buf[1] = 'W'; 290 | req.buf[2] = 0x10; 291 | req.buf[3] = (unsigned char) block; 292 | memcpy(req.buf+4, data, 0x10); 293 | 294 | Write(hPortalHandle, &req); 295 | usleep(100000); // wait for write to take effect. 296 | 297 | memset(verify, 0xCD, sizeof(verify)); 298 | 299 | OK = ReadBlock(hPortalHandle, block, verify, isNEWskylander); 300 | OK = OK && (memcmp(data, verify, sizeof(verify)) == 0); 301 | if(OK) break; 302 | } 303 | 304 | return OK; 305 | } 306 | 307 | 308 | 309 | // Start portal 310 | void RestartPortal(libusb_device_handle *hPortalHandle) 311 | { 312 | RWBlock req; 313 | 314 | memset(req.buf, 0, rw_buf_size); 315 | req.buf[1] = 'R'; 316 | Write(hPortalHandle, &req); 317 | } 318 | 319 | // Antenna up / activate 320 | void ActivatePortal(libusb_device_handle *hPortalHandle) 321 | { 322 | RWBlock req; 323 | 324 | memset(req.buf, 0, rw_buf_size); 325 | req.buf[1] = 'A'; 326 | req.buf[2] = 0x01; 327 | Write(hPortalHandle, &req); 328 | } 329 | 330 | // Set the portal color 331 | void SetPortalColor(libusb_device_handle *hPortalHandle, unsigned char r, unsigned char g, unsigned char b) 332 | { 333 | RWBlock req; 334 | 335 | memset(req.buf, 0, rw_buf_size); 336 | req.buf[1] = 'C'; 337 | req.buf[2] = r; // R 338 | req.buf[3] = g; // G 339 | req.buf[4] = b; // B 340 | Write(hPortalHandle, &req); 341 | } 342 | 343 | // Release hPortalInstance 344 | void DisconnectPortal(void) { 345 | libusb_release_interface(g_hPortalHandle,0); 346 | libusb_unref_device(libusb_get_device(g_hPortalHandle)); 347 | libusb_close(g_hPortalHandle); 348 | libusb_exit (g_ctx); 349 | 350 | } 351 | 352 | void ConnectToPortal(void) { 353 | static bool initialized = false; 354 | 355 | if(!initialized) 356 | { 357 | printf("Connecting to portal.\n"); 358 | 359 | initialized = true; 360 | 361 | // Try to stop the SpyroService if we can 362 | //StopSpyroService(); 363 | 364 | // setup hPortalInstance 365 | int bResult = true; 366 | int bNewSkylander = false; 367 | 368 | bResult = OpenPortalHandle(&g_hPortalHandle); 369 | if(!bResult) 370 | { 371 | return; 372 | } 373 | 374 | RestartPortal(g_hPortalHandle); 375 | ActivatePortal(g_hPortalHandle); 376 | 377 | // disconnect on program exit 378 | atexit(DisconnectPortal); 379 | } 380 | } 381 | 382 | unsigned char *ReadSkylanderFromPortal(void) { 383 | bool bResult; 384 | bool bNewSkylander = false; 385 | unsigned char data[0x10]; 386 | unsigned char *buffer; 387 | unsigned char *ptr; 388 | 389 | ConnectToPortal(); 390 | 391 | printf("Reading Skylander from portal.\n"); 392 | 393 | // must start with a read of block zero 394 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 395 | 396 | if(!bResult) { 397 | bNewSkylander = !bNewSkylander; 398 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 399 | } 400 | 401 | if(!bResult) { 402 | fprintf(stderr, "\nCould not read Skylander on portal. Please disconnect/reconnect device.\n"); 403 | return NULL; 404 | } 405 | 406 | // I don't know that we need this, but the web driver sets the color when reading the data 407 | SetPortalColor(g_hPortalHandle, 0xC8, 0xC8, 0xC8); 408 | 409 | buffer = (unsigned char *)malloc(1024); 410 | if(!buffer) { 411 | fprintf(stderr, "\nCould not allocate memory to read Skylander data.\n"); 412 | return NULL; 413 | } 414 | 415 | ptr = buffer; 416 | memcpy(ptr, data, sizeof(data)); 417 | 418 | for(int block=1; block < 0x40; ++block) { 419 | ptr += 0x10; 420 | bResult = ReadBlock(g_hPortalHandle, block, data, bNewSkylander); 421 | memcpy(ptr, data, sizeof(data)); 422 | } 423 | 424 | printf("\nSkylander read from portal.\n"); 425 | return buffer; 426 | } 427 | 428 | bool WriteSkylanderToPortal(unsigned char *encrypted_new_data, unsigned char *encrypted_old_data) 429 | { 430 | bool bResult; 431 | bool bNewSkylander = false; 432 | unsigned char data[0x10]; 433 | 434 | ConnectToPortal(); 435 | 436 | // must start with a read of block zero 437 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 438 | 439 | if(!bResult) { 440 | bNewSkylander = !bNewSkylander; 441 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 442 | if(!bResult) { 443 | fprintf(stderr, "Abort before write. Could not read data from Skylander portal.\n"); 444 | return false; 445 | } 446 | } 447 | 448 | if(encrypted_old_data == NULL) { 449 | encrypted_old_data = ReadSkylanderFromPortal(); 450 | } 451 | 452 | printf("\nWriting Skylander to portal.\n"); 453 | 454 | for(int i=0; i<2; i++) { 455 | // two pass write 456 | // write the access control blocks first 457 | bool selectAccessControlBlock; 458 | if(i == 0) { 459 | selectAccessControlBlock = 1; 460 | } else { 461 | selectAccessControlBlock = 0; 462 | } 463 | 464 | for(int block=0; block < 0x40; ++block) { 465 | bool changed, OK; 466 | int offset = block * 0x10; 467 | changed = (memcmp(encrypted_old_data+offset, encrypted_new_data+offset, 0x10) != 0); 468 | if(changed) { 469 | if(IsAccessControlBlock(block) == selectAccessControlBlock) { 470 | OK = WriteBlock(g_hPortalHandle, block, encrypted_new_data+offset, bNewSkylander); 471 | if(!OK) { 472 | fprintf(stderr, "Failed to write block %d. Aborting.\n", block); 473 | return false; 474 | } 475 | } 476 | } 477 | } 478 | } 479 | return true; 480 | } 481 | 482 | 483 | -------------------------------------------------------------------------------- /skylander.cpp: -------------------------------------------------------------------------------- 1 | #include "skylander.h" 2 | 3 | /* 4 | character data contents 5 | Even though there are two "data areas" (headers at blocks 0x08 and 0x24, data starts at blocks 0x09 and 0x25), some data is stored outside of the area, so here's a breakdown of the whole 1KB: 6 | 7 | Block Block Offset Size Description 8 | Area 0 Area 1 (bytes) 9 | 0x00 N/A 0x00 0x04 Unique serial number for the toy. 10 | 0x00 N/A 0x04 0x0E Unknown. 11 | 0x01 N/A 0x00 0x02 Identifier for the character/toy type. In the dump above, you can see it's 0E 00 (Little Endian), or 0x000E (Gill Grunt). 12 | 0x01 N/A 0x04 0x08 Trading card ID. 13 | 0x01 N/A 0x0C 0x02 Alter Egos / Trap Type (e.g. 0030 for Krypt King / 0234 for Nitro Krypt King; with Toy ID d600 (tech trap): 0030 Tech Totem, 0730 Automatic Angel, 0930 Factory Flower, etc) 14 | 0x01 N/A 0x0E 0x02 Type 0 CRC16 checksum. AKA CRC16CCITT with a seed of 0xFFFF 15 | 0x08 0x24 0x00 0x03 24-bit experience/level value. Maximum 33000 here. 16 | 0x08 0x24 0x03 0x02 16-bit money value. Maximum 65000. Set it higher and the game rounds down to 65000. 17 | 0x08 0x24 0x05 0x02 Unknown. 18 | 0x08 0x24 0x07 0x02 Unknown. Zeroes for me. 19 | 0x08 0x24 0x09 0x01 8-bit sequence value for this data area. The area with the higher value is the last save location. 20 | 0x08 0x24 0x0A 0x02 Type 3 CRC16 checksum. 21 | 0x08 0x24 0x0C 0x02 Type 2 CRC16 checksum. 22 | 0x08 0x24 0x0E 0x02 Type 1 CRC16 checksum. 23 | 0x09 0x25 0x00 0x02 Skills given by Fairy. Bit 7 = path chosen. FD0F = Left, FF0F = Right 24 | 0x09 0x25 0x02 0x01 Unknown. Zeroes for me. 25 | 0x09 0x25 0x03 0x01 8-bit value, bitmap of platforms the character has touched. Bit 0 is the Wii and bit 1 is the Xbox 360, evidently. 26 | 0x09 0x25 0x04 0x02 ID of hat the character is currently wearing. 27 | 0x09 0x25 0x06 0x02 Unknown. Zeroes for me. 28 | 0x09 0x25 0x08 0x08 Unknown. I've seen FF BF 1B 7F FF 2F B9 7E and FF 83 EE 7E FF 19 30 7F. 29 | 0x0A 0x26 0x00 0x10 First half of Unicode name of character, zero-terminated, maximum 14 characters. 30 | 0x0C 0x28 0x00 0x10 Second half of Unicode name of character, zero-terminated, maximum 14 characters. 31 | 0x0D 0x29 0x00 0x0A Unknown. 32 | 0x0D 0x29 0x0A 0x02 16-bit hero points value. Maximum 100. 33 | 0x0D 0x29 0x0C 0x03 Unknown. Zeroes for me. 34 | 0x0D 0x29 0x0E 0x01 Unknown. 01 for me. 35 | 0x10 0x2C 0x00 0x0C Unknown. Zeroes for me. 36 | 0x10 0x2C 0x0C 0x04 32 bit flag value indicating heroic challenges completed. 37 | 38 | */ 39 | 40 | void Skylander::fprinthex(FILE *f, unsigned char *c, unsigned int n) { 41 | unsigned int h,i; 42 | unsigned char j; 43 | 44 | 45 | for (h=0; h=127) j='.'; 60 | fprintf (f,"%c",j); 61 | } else 62 | fprintf(f," "); 63 | } 64 | fprintf(f,"\n"); 65 | } 66 | } 67 | 68 | void Skylander::dump(void) 69 | { 70 | fprinthex(stdout,data,SKYLANDER_SIZE); 71 | } 72 | 73 | Skylander::Skylander(unsigned char *in) 74 | { 75 | initSkylander(in); 76 | } 77 | 78 | void Skylander::initSkylander(unsigned char *in) 79 | { 80 | memcpy (data,in,SKYLANDER_SIZE); 81 | setAreaFromSequence(); 82 | readName(); 83 | } 84 | 85 | unsigned char *Skylander::getData() { return data; } 86 | 87 | 88 | unsigned char Skylander::getByte (int block, int offset) { return data[block * 16 + offset]; } 89 | unsigned short Skylander::getShort (int block, int offset) { return data[block * 16 + offset] + data[block * 16 + offset + 1] * 0x100; } 90 | void Skylander::setByte(int block, int offset, unsigned char b) { data[block * 16 + offset] = b; } 91 | void Skylander::setShort(int block, int offset, unsigned short b) 92 | { 93 | data[block * 16 + offset] = b & 0xff; 94 | data[block * 16 + offset + 1] = (b & 0xff00) / 0x100; 95 | 96 | } 97 | 98 | 99 | void Skylander::readName() 100 | { 101 | 102 | int block = getBlockNumberForArea() + 2; 103 | for (int i=0; i<15; i++) 104 | { 105 | int offset = (i * 2) & 0xf; 106 | if (i == 8) { block += 2; } 107 | 108 | unsigned short utf = getShort(block,offset); 109 | 110 | name[i] = utf & 0xff; 111 | 112 | } 113 | 114 | } 115 | 116 | void Skylander::setAreaFromSequence() 117 | { 118 | area = 0; 119 | if (getArea0Sequence() < getArea1Sequence()) { area = 1;} 120 | } 121 | int Skylander::getBlockNumberForArea() { return (area == 0) ? 0x08 : 0x24; } 122 | 123 | // should validate for 0 or 1 124 | void Skylander::setArea(int a) { if (a == 1 || a == 0) {area = a;} } 125 | int Skylander::getArea() { return area; } 126 | 127 | unsigned long Skylander::getSerial() { return getShort(0x00,0x00) + getShort(0x00,0x02) * 0x10000; } 128 | unsigned short Skylander::getToyType() { return getShort(0x01,0x00); } 129 | const char * Skylander::getToyTypeName() { return toyName(getToyType()); } 130 | unsigned char * Skylander::getTradingID() { return data + 20; } 131 | 132 | unsigned int Skylander::getXP() 133 | { 134 | int block = getBlockNumberForArea(); 135 | return getByte(block,0) + getByte(block,1) * 0x100 + getByte(block,2) * 0x10000; 136 | } 137 | 138 | void Skylander::setXP(unsigned int xp) 139 | { 140 | 141 | if (xp < 0x1000000 ) 142 | { 143 | int block = getBlockNumberForArea(); 144 | 145 | setByte(block,0, xp & 0xff); 146 | setByte(block,1, (xp & 0xff00) / 0x100); 147 | setByte(block,2, (xp & 0xff0000) / 0x10000); 148 | } 149 | 150 | } 151 | 152 | 153 | unsigned short Skylander::getMoney() 154 | { 155 | int block = getBlockNumberForArea(); 156 | return getShort(block, 0x03); 157 | } 158 | 159 | void Skylander::setMoney(unsigned short money) 160 | { 161 | int block = getBlockNumberForArea(); 162 | setShort(block,0x03,money); 163 | } 164 | 165 | // apparently the largest here gives the area. 166 | unsigned char Skylander::getArea0Sequence() { return getByte(0x08, 0x09); } 167 | unsigned char Skylander::getArea1Sequence() { return getByte(0x24, 0x09); } 168 | 169 | unsigned short Skylander::getSkill() { 170 | int block = getBlockNumberForArea(); 171 | return getShort(block+1, 0x00); 172 | } 173 | 174 | void Skylander::setSkillLeft(unsigned short skill) 175 | { 176 | if (skill <= 0) skill = getSkill(); 177 | skill = skill & 0xFFFD; //Sets bit 7 same way every time 178 | printf ("Setting Skill %04x LEFT\n\n", skill); 179 | setSkill(skill); 180 | } 181 | 182 | void Skylander::setSkillRight(unsigned short skill) 183 | { 184 | if (skill <= 0) skill = getSkill(); 185 | skill = skill & 0xFFFD; //Sets bit 7 same way every time 186 | skill = skill | 0x2; //Flip bit 7 to other path 187 | printf ("Setting Skill %04x RIGHT\n\n", skill); 188 | setSkill(skill); 189 | } 190 | 191 | 192 | void Skylander::setSkill(unsigned short skill) 193 | { 194 | int block = getBlockNumberForArea(); 195 | setShort(block+1, 0x00, skill); 196 | } 197 | 198 | const char * Skylander::getPath() { 199 | int block = getBlockNumberForArea(); 200 | unsigned short p = getByte(block+1, 0x0); 201 | if ((p & 1) == 0) { return "No Path"; } 202 | else if ((p & 2) == 0) { return "Path A (Left)"; } 203 | else { return "Path B (Right)"; } 204 | } 205 | 206 | unsigned char Skylander::getPlatform() 207 | { 208 | int block = getBlockNumberForArea(); 209 | return getByte(block+1, 0x03); 210 | } 211 | 212 | const char * Skylander::getPlatformName() 213 | { 214 | char platform = getPlatform(); 215 | if ( (platform & 1) == 1) return "Wii"; 216 | if ( (platform & 2) == 2) return "Xbox 360"; 217 | if ( (platform & 4) == 4) return "PS3"; 218 | 219 | return "UNKNOWN"; 220 | } 221 | 222 | unsigned short Skylander::getHat() 223 | { 224 | int block = getBlockNumberForArea(); 225 | return getShort(block+1, 0x04); 226 | } 227 | 228 | void Skylander::setHat(unsigned short hat) 229 | { 230 | int block = getBlockNumberForArea(); 231 | return setShort(block+1, 4,hat); 232 | } 233 | 234 | 235 | char * Skylander::getName() 236 | { 237 | return name; 238 | } 239 | 240 | 241 | unsigned short Skylander::getHeroPoints() 242 | { 243 | int block = getBlockNumberForArea(); 244 | return getShort(block+5, 0x0A); 245 | } 246 | 247 | void Skylander::setHeroPoints(unsigned short hp) 248 | { 249 | int block = getBlockNumberForArea(); 250 | setShort(block+5,0x0A,hp); 251 | } 252 | 253 | unsigned int Skylander::getHeroicChallenges() 254 | { 255 | int block = getBlockNumberForArea() + 8; 256 | return getByte(block , 0xc) + getByte(block , 0xd) * 0x100 + getByte(block , 0xe) * 0x10000 + getByte(block , 0xf) * 0x1000000; 257 | } 258 | 259 | 260 | void Skylander::setHeroicChallenges(unsigned int hc) 261 | { 262 | int block = getBlockNumberForArea() + 8 ; 263 | setByte(block , 0xc, hc & 0xff); 264 | setByte(block , 0xd, (hc & 0xff00) / 0x100); 265 | setByte(block , 0xe, (hc & 0xff0000) / 0x10000); 266 | setByte(block , 0xf, (hc & 0xff000000) / 0x1000000); 267 | } 268 | 269 | 270 | void Skylander::UpdateBuf( int block, int offset, int size, unsigned char val) 271 | { 272 | unsigned char *ptr = data; 273 | ptr += block * 0x10 + offset; 274 | for(int i=0; i 31 | 32 | #ifdef _WIN32 33 | #define HID_API_EXPORT __declspec(dllexport) 34 | #define HID_API_CALL 35 | #else 36 | #define HID_API_EXPORT /**< API export macro */ 37 | #define HID_API_CALL /**< API call macro */ 38 | #endif 39 | 40 | #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | struct hid_device_; 46 | typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ 47 | 48 | /** hidapi info structure */ 49 | struct hid_device_info { 50 | /** Platform-specific device path */ 51 | char *path; 52 | /** Device Vendor ID */ 53 | unsigned short vendor_id; 54 | /** Device Product ID */ 55 | unsigned short product_id; 56 | /** Serial Number */ 57 | wchar_t *serial_number; 58 | /** Device Release Number in binary-coded decimal, 59 | also known as Device Version Number */ 60 | unsigned short release_number; 61 | /** Manufacturer String */ 62 | wchar_t *manufacturer_string; 63 | /** Product string */ 64 | wchar_t *product_string; 65 | /** Usage Page for this Device/Interface 66 | (Windows/Mac only). */ 67 | unsigned short usage_page; 68 | /** Usage for this Device/Interface 69 | (Windows/Mac only).*/ 70 | unsigned short usage; 71 | /** The USB interface which this logical device 72 | represents. Valid on both Linux implementations 73 | in all cases, and valid on the Windows implementation 74 | only if the device contains more than one interface. */ 75 | int interface_number; 76 | 77 | /** Pointer to the next device */ 78 | struct hid_device_info *next; 79 | }; 80 | 81 | 82 | /** @brief Initialize the HIDAPI library. 83 | 84 | This function initializes the HIDAPI library. Calling it is not 85 | strictly necessary, as it will be called automatically by 86 | hid_enumerate() and any of the hid_open_*() functions if it is 87 | needed. This function should be called at the beginning of 88 | execution however, if there is a chance of HIDAPI handles 89 | being opened by different threads simultaneously. 90 | 91 | @ingroup API 92 | 93 | @returns 94 | This function returns 0 on success and -1 on error. 95 | */ 96 | int HID_API_EXPORT HID_API_CALL hid_init(void); 97 | 98 | /** @brief Finalize the HIDAPI library. 99 | 100 | This function frees all of the static data associated with 101 | HIDAPI. It should be called at the end of execution to avoid 102 | memory leaks. 103 | 104 | @ingroup API 105 | 106 | @returns 107 | This function returns 0 on success and -1 on error. 108 | */ 109 | int HID_API_EXPORT HID_API_CALL hid_exit(void); 110 | 111 | /** @brief Enumerate the HID Devices. 112 | 113 | This function returns a linked list of all the HID devices 114 | attached to the system which match vendor_id and product_id. 115 | If @p vendor_id is set to 0 then any vendor matches. 116 | If @p product_id is set to 0 then any product matches. 117 | If @p vendor_id and @p product_id are both set to 0, then 118 | all HID devices will be returned. 119 | 120 | @ingroup API 121 | @param vendor_id The Vendor ID (VID) of the types of device 122 | to open. 123 | @param product_id The Product ID (PID) of the types of 124 | device to open. 125 | 126 | @returns 127 | This function returns a pointer to a linked list of type 128 | struct #hid_device, containing information about the HID devices 129 | attached to the system, or NULL in the case of failure. Free 130 | this linked list by calling hid_free_enumeration(). 131 | */ 132 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); 133 | 134 | /** @brief Free an enumeration Linked List 135 | 136 | This function frees a linked list created by hid_enumerate(). 137 | 138 | @ingroup API 139 | @param devs Pointer to a list of struct_device returned from 140 | hid_enumerate(). 141 | */ 142 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); 143 | 144 | /** @brief Open a HID device using a Vendor ID (VID), Product ID 145 | (PID) and optionally a serial number. 146 | 147 | If @p serial_number is NULL, the first device with the 148 | specified VID and PID is opened. 149 | 150 | @ingroup API 151 | @param vendor_id The Vendor ID (VID) of the device to open. 152 | @param product_id The Product ID (PID) of the device to open. 153 | @param serial_number The Serial Number of the device to open 154 | (Optionally NULL). 155 | 156 | @returns 157 | This function returns a pointer to a #hid_device object on 158 | success or NULL on failure. 159 | */ 160 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); 161 | 162 | /** @brief Open a HID device by its path name. 163 | 164 | The path name be determined by calling hid_enumerate(), or a 165 | platform-specific path name can be used (eg: /dev/hidraw0 on 166 | Linux). 167 | 168 | @ingroup API 169 | @param path The path name of the device to open 170 | 171 | @returns 172 | This function returns a pointer to a #hid_device object on 173 | success or NULL on failure. 174 | */ 175 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); 176 | 177 | /** @brief Write an Output report to a HID device. 178 | 179 | The first byte of @p data[] must contain the Report ID. For 180 | devices which only support a single report, this must be set 181 | to 0x0. The remaining bytes contain the report data. Since 182 | the Report ID is mandatory, calls to hid_write() will always 183 | contain one more byte than the report contains. For example, 184 | if a hid report is 16 bytes long, 17 bytes must be passed to 185 | hid_write(), the Report ID (or 0x0, for devices with a 186 | single report), followed by the report data (16 bytes). In 187 | this example, the length passed in would be 17. 188 | 189 | hid_write() will send the data on the first OUT endpoint, if 190 | one exists. If it does not, it will send the data through 191 | the Control Endpoint (Endpoint 0). 192 | 193 | @ingroup API 194 | @param device A device handle returned from hid_open(). 195 | @param data The data to send, including the report number as 196 | the first byte. 197 | @param length The length in bytes of the data to send. 198 | 199 | @returns 200 | This function returns the actual number of bytes written and 201 | -1 on error. 202 | */ 203 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); 204 | 205 | /** @brief Read an Input report from a HID device with timeout. 206 | 207 | Input reports are returned 208 | to the host through the INTERRUPT IN endpoint. The first byte will 209 | contain the Report number if the device uses numbered reports. 210 | 211 | @ingroup API 212 | @param device A device handle returned from hid_open(). 213 | @param data A buffer to put the read data into. 214 | @param length The number of bytes to read. For devices with 215 | multiple reports, make sure to read an extra byte for 216 | the report number. 217 | @param milliseconds timeout in milliseconds or -1 for blocking wait. 218 | 219 | @returns 220 | This function returns the actual number of bytes read and 221 | -1 on error. If no packet was available to be read within 222 | the timeout period, this function returns 0. 223 | */ 224 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); 225 | 226 | /** @brief Read an Input report from a HID device. 227 | 228 | Input reports are returned 229 | to the host through the INTERRUPT IN endpoint. The first byte will 230 | contain the Report number if the device uses numbered reports. 231 | 232 | @ingroup API 233 | @param device A device handle returned from hid_open(). 234 | @param data A buffer to put the read data into. 235 | @param length The number of bytes to read. For devices with 236 | multiple reports, make sure to read an extra byte for 237 | the report number. 238 | 239 | @returns 240 | This function returns the actual number of bytes read and 241 | -1 on error. If no packet was available to be read and 242 | the handle is in non-blocking mode, this function returns 0. 243 | */ 244 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); 245 | 246 | /** @brief Set the device handle to be non-blocking. 247 | 248 | In non-blocking mode calls to hid_read() will return 249 | immediately with a value of 0 if there is no data to be 250 | read. In blocking mode, hid_read() will wait (block) until 251 | there is data to read before returning. 252 | 253 | Nonblocking can be turned on and off at any time. 254 | 255 | @ingroup API 256 | @param device A device handle returned from hid_open(). 257 | @param nonblock enable or not the nonblocking reads 258 | - 1 to enable nonblocking 259 | - 0 to disable nonblocking. 260 | 261 | @returns 262 | This function returns 0 on success and -1 on error. 263 | */ 264 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); 265 | 266 | /** @brief Send a Feature report to the device. 267 | 268 | Feature reports are sent over the Control endpoint as a 269 | Set_Report transfer. The first byte of @p data[] must 270 | contain the Report ID. For devices which only support a 271 | single report, this must be set to 0x0. The remaining bytes 272 | contain the report data. Since the Report ID is mandatory, 273 | calls to hid_send_feature_report() will always contain one 274 | more byte than the report contains. For example, if a hid 275 | report is 16 bytes long, 17 bytes must be passed to 276 | hid_send_feature_report(): the Report ID (or 0x0, for 277 | devices which do not use numbered reports), followed by the 278 | report data (16 bytes). In this example, the length passed 279 | in would be 17. 280 | 281 | @ingroup API 282 | @param device A device handle returned from hid_open(). 283 | @param data The data to send, including the report number as 284 | the first byte. 285 | @param length The length in bytes of the data to send, including 286 | the report number. 287 | 288 | @returns 289 | This function returns the actual number of bytes written and 290 | -1 on error. 291 | */ 292 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); 293 | 294 | /** @brief Get a feature report from a HID device. 295 | 296 | Set the first byte of @p data[] to the Report ID of the 297 | report to be read. Make sure to allow space for this 298 | extra byte in @p data[]. Upon return, the first byte will 299 | still contain the Report ID, and the report data will 300 | start in data[1]. 301 | 302 | @ingroup API 303 | @param device A device handle returned from hid_open(). 304 | @param data A buffer to put the read data into, including 305 | the Report ID. Set the first byte of @p data[] to the 306 | Report ID of the report to be read, or set it to zero 307 | if your device does not use numbered reports. 308 | @param length The number of bytes to read, including an 309 | extra byte for the report ID. The buffer can be longer 310 | than the actual report. 311 | 312 | @returns 313 | This function returns the number of bytes read plus 314 | one for the report ID (which is still in the first 315 | byte), or -1 on error. 316 | */ 317 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); 318 | 319 | /** @brief Close a HID device. 320 | 321 | @ingroup API 322 | @param device A device handle returned from hid_open(). 323 | */ 324 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); 325 | 326 | /** @brief Get The Manufacturer String from a HID device. 327 | 328 | @ingroup API 329 | @param device A device handle returned from hid_open(). 330 | @param string A wide string buffer to put the data into. 331 | @param maxlen The length of the buffer in multiples of wchar_t. 332 | 333 | @returns 334 | This function returns 0 on success and -1 on error. 335 | */ 336 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); 337 | 338 | /** @brief Get The Product String from a HID device. 339 | 340 | @ingroup API 341 | @param device A device handle returned from hid_open(). 342 | @param string A wide string buffer to put the data into. 343 | @param maxlen The length of the buffer in multiples of wchar_t. 344 | 345 | @returns 346 | This function returns 0 on success and -1 on error. 347 | */ 348 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); 349 | 350 | /** @brief Get The Serial Number String from a HID device. 351 | 352 | @ingroup API 353 | @param device A device handle returned from hid_open(). 354 | @param string A wide string buffer to put the data into. 355 | @param maxlen The length of the buffer in multiples of wchar_t. 356 | 357 | @returns 358 | This function returns 0 on success and -1 on error. 359 | */ 360 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); 361 | 362 | /** @brief Get a string from a HID device, based on its string index. 363 | 364 | @ingroup API 365 | @param device A device handle returned from hid_open(). 366 | @param string_index The index of the string to get. 367 | @param string A wide string buffer to put the data into. 368 | @param maxlen The length of the buffer in multiples of wchar_t. 369 | 370 | @returns 371 | This function returns 0 on success and -1 on error. 372 | */ 373 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); 374 | 375 | /** @brief Get a string describing the last error which occurred. 376 | 377 | @ingroup API 378 | @param device A device handle returned from hid_open(). 379 | 380 | @returns 381 | This function returns a string containing the last error 382 | which occurred or NULL if none has occurred. 383 | */ 384 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); 385 | 386 | #ifdef __cplusplus 387 | } 388 | #endif 389 | 390 | #endif 391 | 392 | -------------------------------------------------------------------------------- /portalio_iokit.cpp: -------------------------------------------------------------------------------- 1 | // Code to read and write from USB portal device. 2 | // Tested on DS3 portal and PS3 portal. For XBox see below. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | #include "fileio.h" 15 | #include "crypt.h" 16 | 17 | #include 18 | 19 | #include "portalio.h" 20 | 21 | 22 | IOUSBDeviceInterface300** g_hPortalHandle = NULL; 23 | bool g_bIsXboxPortal; 24 | mach_port_t masterPort; 25 | 26 | // Attempted to add support for XBox portal based on 27 | // XBoxSpyroPortal and XBoxSpyroManager in .NET decompilation of 28 | // FlashPortal.exe in the drivers from spyrowebportaldriver.exe. 29 | // I'm pretty sure the part where I get the handle for the portal is correct, 30 | // however, I doubt the code to talk to it as an HID device will work 31 | // because the XBox driver does not appear to be an HID device. 32 | // May need to set up pipes as seen in the XBoxSpyroPortal class. 33 | // Another approach you could try is to use the WriteFile command 34 | // in the Write function below. The Write function is the only thing 35 | // that actually uses the HID commands. 36 | 37 | // XBox Portal GUID 38 | // {B323AD0E-ADA8-4d64-AA53-4B46E5843312} 39 | DEFINE_GUID(xbox_guid, 0xB323AD0E, 0xADA8, 0x4d64, 40 | 0xAA, 0x53, 0x4B, 0x46, 0xE5, 0x84, 0x33, 0x12); 41 | 42 | bool OpenPortalHandleFromGUID(CFUUIDBytes guid, IOUSBDeviceInterface300*** phPortalHandle) 43 | { 44 | io_iterator_t iterator = 0; 45 | io_service_t usbRef; 46 | bool bResult = true; 47 | SInt32 score; 48 | HDEVINFO hDevInfo; 49 | SP_DEVICE_INTERFACE_DATA deviceInterfaceData; 50 | IOUSBConfigurationDescriptorPtr config; 51 | IOCFPlugInInterface** plugin; 52 | HIDD_ATTRIBUTES attributes; 53 | CFMutableDictionaryRef matchingDict = NULL; 54 | 55 | 56 | ULONG requiredLength=0; 57 | 58 | //Set up matching dictionary for class IOUSBDevice and its subclasses 59 | matchingDict = IOServiceMatching(kIOUSBDeviceClassName); 60 | if (!matchingDict) { 61 | printf("Couldn’t create a USB matching dictionary\n"); 62 | return false; 63 | } 64 | 65 | IOServiceGetMatchingServices(kIOMasterPortDefault, 66 | matchingDict, &iterator); 67 | 68 | usbRef = IOIteratorNext(iterator); 69 | 70 | 71 | *phPortalHandle = NULL; 72 | 73 | while (usbRef != 0) { 74 | IOCreatePlugInInterfaceForService(usbRef, kIOUSBDeviceUserClientTypeID, 75 | kIOCFPlugInInterfaceID, &plugin, &score); 76 | IOObjectRelease(usbRef); 77 | 78 | (*plugin)->QueryInterface(plugin, 79 | guid, 80 | (LPVOID)phPortalHandle); 81 | 82 | (*plugin)->Release(plugin); 83 | 84 | 85 | if(guid == xbox_guid) { 86 | break; // we are done. xbox_guid does not have HID attributes 87 | } 88 | 89 | if (*phPortalHandle != NULL) 90 | { 91 | UInt16 VendorID, ProductID; 92 | 93 | (**phPortalHandle)->GetDeviceVendor(*phPortalHandle, &VendorID); 94 | (**phPortalHandle)->GetDeviceProduct(*phPortalHandle, &ProductID); 95 | if (((VendorID == 0x12ba) || (VendorID == 0x54c)) || (VendorID == 0x1430)) 96 | { 97 | if ((ProductID == 0x150) || (ProductID == 0x967)) 98 | { 99 | (**phPortalHandle)->USBDeviceOpen(*phPortalHandle); 100 | ret = (**phPortalHandle)->GetConfigurationDescriptorPtr(*phPortalHandle, 0, &config); 101 | (**phPortalHandle)->SetConfiguration(*phPortalHandle, config->bConfigurationValue); 102 | 103 | break; // found the portal. leave the handle open 104 | } 105 | } 106 | } 107 | (**phPortalHandle)->Release(*phPortalHandle); 108 | *phPortalHandle = NULL; 109 | } 110 | usbRef = IOIteratorNext(iterator); 111 | 112 | } 113 | IOObjectRelease(iterator); 114 | return (*phPortalHandle != NULL); 115 | } 116 | 117 | 118 | bool OpenPortalHandle(IOUSBDeviceInterface300*** phPortalHandle) 119 | { 120 | bool OK; 121 | CFUUIDBytes guid; 122 | 123 | // Try to open XBox portal handle first 124 | OK = OpenPortalHandleFromGUID(xbox_guid, phPortalHandle); 125 | if(OK) { 126 | g_bIsXboxPortal = true; 127 | return OK; 128 | } 129 | 130 | g_bIsXboxPortal = false; 131 | guid = CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300); 132 | OK = OpenPortalHandleFromGUID(guid, phPortalHandle); 133 | 134 | return OK; 135 | } 136 | 137 | 138 | const int rw_buf_size = 0x21; 139 | typedef struct { 140 | unsigned char buf[rw_buf_size]; 141 | UInt16 dwBytesTransferred; 142 | } RWBlock; 143 | 144 | 145 | // In theory, should be able to use WriteFile to create an asynchronous request using 146 | // an overlapped structure as shown in WriteOverlapped. 147 | // In practice, I couldn't get it to work, had to use output report executed immediately 148 | // for HID portals. 149 | bool WriteHID(HANDLE hPortalHandle, RWBlock *pb) { 150 | pb->buf[0] = 0; // Use report 0 151 | return HidD_SetOutputReport(hPortalHandle, pb->buf, 0x21); 152 | } 153 | 154 | // Write to the portal using asynchronous IO. 155 | bool WriteOverlapped(IOUSBDeviceInterface300** hPortalHandle, RWBlock *pb) { 156 | OVERLAPPED ovlr; 157 | DWORD error_cc; 158 | DWORD err; 159 | 160 | // Must set the Offset and OffsetHigh members of the OVERLAPPED structure to zero. 161 | memset(&ovlr, 0, sizeof(ovlr)); 162 | ovlr.hEvent = CreateEvent(NULL, true, false, NULL); // write event 163 | 164 | pb->buf[0] = 0; // Use report 0 165 | 166 | error_cc = WriteFile(hPortalHandle, pb->buf, rw_buf_size, NULL, &ovlr); 167 | 168 | err = GetLastError(); 169 | if (error_cc == false && err != ERROR_IO_PENDING) { 170 | fprintf(stderr, "Error in sending command to portal. Error %d.\n", GetLastError()); 171 | return false; 172 | } 173 | 174 | //Sleep(2000); 175 | 176 | /* 177 | // Block until command has been sent 178 | if(err == ERROR_IO_PENDING) { 179 | int cnt = 0; 180 | while(!HasOverlappedIoCompleted(&ovlr) && cnt++ < 10) { 181 | Sleep(10); 182 | } 183 | } 184 | 185 | if(!HasOverlappedIoCompleted(&ovlr)) { 186 | return false; 187 | } 188 | */ 189 | 190 | return true; 191 | } 192 | 193 | // Send a command to the portal 194 | bool Write(IOUSBDeviceInterface300** hPortalHandle, RWBlock *pb) { 195 | if(!g_bIsXboxPortal) { 196 | return WriteHID(hPortalHandle, pb); 197 | } else { 198 | return WriteOverlapped(hPortalHandle, pb); 199 | } 200 | } 201 | 202 | #if 0 203 | // Synchronous immediate read 204 | bool Read(IOUSBDeviceInterface300** hPortalHandle, RWBlock *pb) { 205 | pb->buf[0] = 0; // Use report 0 206 | return HidD_GetInputReport(hPortalHandle, pb->buf, 0x21); 207 | } 208 | #endif 209 | 210 | bool ReadBlock(IOUSBDeviceInterface300** hPortalHandle, unsigned int block, unsigned char data[0x10], 211 | bool isNEWskylander) { 212 | RWBlock req, res; 213 | bool running = true; 214 | bool gotData; 215 | OVERLAPPED ovlr; 216 | DWORD err; 217 | unsigned char followup; 218 | 219 | if(block >= 0x40) { 220 | return false; 221 | } 222 | 223 | printf("."); 224 | 225 | for(int retries = 0; retries < 3; retries++) 226 | { 227 | // Send query request 228 | memset(req.buf, 0, rw_buf_size); 229 | req.buf[1] = 'Q'; 230 | if(isNEWskylander) { 231 | followup = 0x11; 232 | if(block == 0) { 233 | req.buf[2] = 0x21; 234 | } else { 235 | req.buf[2] = followup; 236 | } 237 | } else { 238 | followup = 0x10; 239 | if(block == 0) { 240 | req.buf[2] = 0x20; 241 | } else { 242 | req.buf[2] = followup; 243 | } 244 | } 245 | 246 | req.buf[3] = (unsigned char)block; 247 | 248 | memset(&(res.buf), 0, rw_buf_size); 249 | 250 | 251 | // Must set the Offset and OffsetHigh members of the OVERLAPPED structure to zero. 252 | memset(&ovlr, 0, sizeof(ovlr)); 253 | 254 | ovlr.hEvent = CreateEvent(NULL, true, false, NULL); // read event 255 | 256 | int i=0; 257 | gotData = false; 258 | 259 | 260 | Write(hPortalHandle, &req); 261 | // Don't wait. Start polling for result immediately 262 | 263 | for(; i<40; ++i) // try up to 40 reads 264 | { 265 | bool b = ReadFile(hPortalHandle, res.buf, rw_buf_size, &(res.dwBytesTransferred), &ovlr); 266 | if(!b) 267 | { 268 | /* failed to get data immediately*/ 269 | err = GetLastError(); 270 | if(err == ERROR_IO_PENDING) 271 | { 272 | /* wait for data */ 273 | b = GetOverlappedResult(hPortalHandle, &ovlr, &res.dwBytesTransferred, true); 274 | if(!b) 275 | { 276 | /* wait failed */ 277 | break; 278 | } 279 | } 280 | else 281 | { 282 | /* some other error */ 283 | break; 284 | } 285 | } 286 | if(res.dwBytesTransferred > 0) 287 | { 288 | /* has data */ 289 | if(res.buf[1] == 'Q' && res.buf[3] == (unsigned char)block) { 290 | // Got our query back 291 | if(res.buf[2] == followup) { 292 | /* got the query back with no error */ 293 | gotData = true; 294 | break; 295 | } 296 | } 297 | 298 | res.buf[0] = 0; // make sure we are using report 0 299 | } 300 | } /* read loop */ 301 | 302 | CloseHandle(ovlr.hEvent); 303 | 304 | if(gotData) { 305 | break; 306 | } 307 | 308 | } // retries 309 | 310 | if(gotData) { 311 | memcpy(data, res.buf + 4, 0x10); 312 | } 313 | 314 | return gotData; 315 | } 316 | 317 | 318 | bool WriteBlock(IOUSBDeviceInterface300** hPortalHandle, unsigned int block, unsigned char data[0x10], 319 | bool isNEWskylander) { 320 | RWBlock req; 321 | unsigned char verify[0x10]; 322 | bool OK; 323 | 324 | printf("."); 325 | 326 | for(int retries=0; retries < 3; retries++) 327 | { 328 | // Write request 329 | // W 57 10 <0x10 bytes of data> 330 | memset(req.buf, 0, rw_buf_size); 331 | req.buf[1] = 'W'; 332 | req.buf[2] = 0x10; 333 | req.buf[3] = (unsigned char) block; 334 | memcpy(req.buf+4, data, 0x10); 335 | 336 | Write(hPortalHandle, &req); 337 | Sleep(100); // wait for write to take effect. 338 | 339 | memset(verify, 0xCD, sizeof(verify)); 340 | 341 | OK = ReadBlock(hPortalHandle, block, verify, isNEWskylander); 342 | OK = OK && (memcmp(data, verify, sizeof(verify)) == 0); 343 | if(OK) break; 344 | } 345 | 346 | return OK; 347 | } 348 | 349 | // Start portal 350 | void RestartPortal(IOUSBDeviceInterface300** hPortalHandle) 351 | { 352 | RWBlock req; 353 | 354 | memset(req.buf, 0, rw_buf_size); 355 | req.buf[1] = 'R'; 356 | Write(hPortalHandle, &req); 357 | } 358 | 359 | // Antenna up / activate 360 | void ActivatePortal(IOUSBDeviceInterface300** hPortalHandle) 361 | { 362 | RWBlock req; 363 | 364 | memset(req.buf, 0, rw_buf_size); 365 | req.buf[1] = 'A'; 366 | req.buf[2] = 0x01; 367 | Write(hPortalHandle, &req); 368 | } 369 | 370 | // Set the portal color 371 | void SetPortalColor(IOUSBDeviceInterface300** hPortalHandle, unsigned char r, unsigned char g, unsigned char b) 372 | { 373 | RWBlock req; 374 | 375 | memset(req.buf, 0, rw_buf_size); 376 | req.buf[1] = 'C'; 377 | req.buf[2] = r; // R 378 | req.buf[3] = g; // G 379 | req.buf[4] = b; // B 380 | Write(hPortalHandle, &req); 381 | } 382 | 383 | // Release hPortalInstance 384 | void DisconnectPortal(void) { 385 | CloseHandle(g_hPortalHandle); 386 | } 387 | 388 | void ConnectToPortal(void) { 389 | static bool initialized = false; 390 | kern_return_t kr; 391 | 392 | if(!initialized) 393 | { 394 | printf("Connecting to portal.\n"); 395 | 396 | initialized = true; 397 | 398 | 399 | // setup hPortalInstance 400 | bool bResult = true; 401 | bool bNewSkylander = false; 402 | 403 | //Create a master port for communication with the I/O Kit 404 | 405 | kr = IOMasterPort(MACH_PORT_NULL, &masterPort); 406 | if (kr || !masterPort) { 407 | printf("ERR: Couldn’t create a master I/O Kit port(%08x)\n", kr); 408 | return -1; 409 | } 410 | 411 | 412 | bResult = OpenPortalHandle(&g_hPortalHandle); 413 | if(!bResult) 414 | { 415 | return; 416 | } 417 | 418 | RestartPortal(g_hPortalHandle); 419 | ActivatePortal(g_hPortalHandle); 420 | 421 | // disconnect on program exit 422 | atexit(DisconnectPortal); 423 | } 424 | } 425 | 426 | #if 0 427 | // stub for testing 428 | unsigned char *ReadSkylanderFromPortal(void) { 429 | return ReadSkylanderFile("D:\\Skylanders\\Editor\\Debug\\dspyro.spy"); // emulate portal for testing 430 | } 431 | 432 | // stub for testing 433 | void WriteSkylanderToPortal(unsigned char *encrypted_new_data, unsigned char *encrypted_old_data) 434 | { 435 | WriteSkylanderFile("D:\\Skylanders\\Editor\\Debug\\dspyro_u.spy", encrypted_new_data); // emulate portal for testing 436 | } 437 | 438 | #endif 439 | 440 | unsigned char *ReadSkylanderFromPortal(void) { 441 | bool bResult; 442 | bool bNewSkylander = false; 443 | unsigned char data[0x10]; 444 | unsigned char *buffer; 445 | unsigned char *ptr; 446 | 447 | ConnectToPortal(); 448 | 449 | printf("Reading Skylander from portal.\n"); 450 | 451 | // must start with a read of block zero 452 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 453 | 454 | if(!bResult) { 455 | bNewSkylander = !bNewSkylander; 456 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 457 | } 458 | 459 | if(!bResult) { 460 | fprintf(stderr, "\nCould not read Skylander on portal. Please disconnect/reconnect device.\n"); 461 | return NULL; 462 | } 463 | 464 | // I don't know that we need this, but the web driver sets the color when reading the data 465 | SetPortalColor(g_hPortalHandle, 0xC8, 0xC8, 0xC8); 466 | 467 | buffer = (unsigned char *)malloc(1024); 468 | if(!buffer) { 469 | fprintf(stderr, "\nCould not allocate memory to read Skylander data.\n"); 470 | return NULL; 471 | } 472 | 473 | ptr = buffer; 474 | memcpy(ptr, data, sizeof(data)); 475 | 476 | for(int block=1; block < 0x40; ++block) { 477 | ptr += 0x10; 478 | bResult = ReadBlock(g_hPortalHandle, block, data, bNewSkylander); 479 | memcpy(ptr, data, sizeof(data)); 480 | } 481 | 482 | printf("\nSkylander read from portal.\n"); 483 | return buffer; 484 | } 485 | 486 | 487 | bool WriteSkylanderToPortal(unsigned char *encrypted_new_data, unsigned char *encrypted_old_data) 488 | { 489 | bool bResult; 490 | bool bNewSkylander = false; 491 | unsigned char data[0x10]; 492 | 493 | ConnectToPortal(); 494 | 495 | // must start with a read of block zero 496 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 497 | 498 | if(!bResult) { 499 | bNewSkylander = !bNewSkylander; 500 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 501 | if(!bResult) { 502 | fprintf(stderr, "Abort before write. Could not read data from Skylander portal.\n"); 503 | return false; 504 | } 505 | } 506 | 507 | if(encrypted_old_data == NULL) { 508 | encrypted_old_data = ReadSkylanderFromPortal(); 509 | } 510 | 511 | printf("\nWriting Skylander to portal.\n"); 512 | 513 | for(int i=0; i<2; i++) { 514 | // two pass write 515 | // write the access control blocks first 516 | bool selectAccessControlBlock; 517 | if(i == 0) { 518 | selectAccessControlBlock = 1; 519 | } else { 520 | selectAccessControlBlock = 0; 521 | } 522 | 523 | for(int block=0; block < 0x40; ++block) { 524 | bool changed, OK; 525 | int offset = block * 0x10; 526 | changed = (memcmp(encrypted_old_data+offset, encrypted_new_data+offset, 0x10) != 0); 527 | if(changed) { 528 | if(IsAccessControlBlock(block) == selectAccessControlBlock) { 529 | OK = WriteBlock(g_hPortalHandle, block, encrypted_new_data+offset, bNewSkylander); 530 | if(!OK) { 531 | fprintf(stderr, "Failed to write block %d. Aborting.\n", block); 532 | return false; 533 | } 534 | } 535 | } 536 | } 537 | } 538 | return true; 539 | } 540 | 541 | -------------------------------------------------------------------------------- /portalio.cpp: -------------------------------------------------------------------------------- 1 | // Code to read and write from USB portal device. 2 | // Tested on DS3 portal and PS3 portal. For XBox see below. 3 | // 4 | 5 | // Include Windows headers 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | // HID library 13 | extern "C" { 14 | #include 15 | }; 16 | 17 | // Include WinUSB headers 18 | #include 19 | #include 20 | #include 21 | 22 | #include "fileio.h" 23 | #include "crypt.h" 24 | 25 | #include 26 | 27 | #include "portalio.h" 28 | 29 | 30 | // Linked libraries 31 | #pragma comment (lib , "setupapi.lib" ) 32 | #pragma comment (lib , "winusb.lib" ) 33 | #pragma comment (lib , "hid.lib" ) 34 | 35 | HANDLE g_hPortalHandle = INVALID_HANDLE_VALUE; 36 | BOOL g_bIsXboxPortal = FALSE; 37 | 38 | BOOL StopSpyroService() 39 | { 40 | // Shutting down a service requires administrator priviledges. 41 | 42 | SERVICE_STATUS strServiceStatus; 43 | SC_HANDLE schManager; 44 | SC_HANDLE schService; 45 | char *pszServiceName; 46 | DWORD dwError; 47 | if( (schManager = OpenSCManager( NULL, SERVICES_ACTIVE_DATABASE, 48 | SC_MANAGER_ALL_ACCESS )) == NULL ) { 49 | return FALSE; 50 | } 51 | 52 | pszServiceName = "SpyroService"; 53 | 54 | if( (schService = OpenService( schManager, pszServiceName, SERVICE_STOP)) == NULL ) { 55 | return FALSE; 56 | } 57 | 58 | if( ControlService( schService, SERVICE_CONTROL_STOP, &strServiceStatus )!=0 ) { 59 | switch( dwError = GetLastError() ) { 60 | case ERROR_ACCESS_DENIED: 61 | case ERROR_DEPENDENT_SERVICES_RUNNING: 62 | case ERROR_INVALID_HANDLE: 63 | case ERROR_INVALID_PARAMETER: 64 | case ERROR_INVALID_SERVICE_CONTROL: 65 | case ERROR_SERVICE_CANNOT_ACCEPT_CTRL: 66 | case ERROR_SERVICE_REQUEST_TIMEOUT: 67 | default: 68 | return FALSE; 69 | 70 | case NO_ERROR: 71 | case ERROR_SERVICE_NOT_ACTIVE: 72 | case ERROR_SHUTDOWN_IN_PROGRESS: 73 | return TRUE; 74 | } 75 | } 76 | if( strServiceStatus.dwCurrentState != SERVICE_STOPPED ) return FALSE; 77 | return TRUE; 78 | } 79 | 80 | // Attempted to add support for XBox portal based on 81 | // XBoxSpyroPortal and XBoxSpyroManager in .NET decompilation of 82 | // FlashPortal.exe in the drivers from spyrowebportaldriver.exe. 83 | // I'm pretty sure the part where I get the handle for the portal is correct, 84 | // however, I doubt the code to talk to it as an HID device will work 85 | // because the XBox driver does not appear to be an HID device. 86 | // May need to set up pipes as seen in the XBoxSpyroPortal class. 87 | // Another approach you could try is to use the WriteFile command 88 | // in the Write function below. The Write function is the only thing 89 | // that actually uses the HID commands. 90 | 91 | // XBox Portal GUID 92 | // {B323AD0E-ADA8-4d64-AA53-4B46E5843312} 93 | DEFINE_GUID(xbox_guid, 0xB323AD0E, 0xADA8, 0x4d64, 94 | 0xAA, 0x53, 0x4B, 0x46, 0xE5, 0x84, 0x33, 0x12); 95 | 96 | BOOL OpenPortalHandleFromGUID(GUID guid, PHANDLE phPortalHandle) 97 | { 98 | int memberIndex = 0; 99 | BOOL bResult = TRUE; 100 | HDEVINFO hDevInfo; 101 | SP_DEVICE_INTERFACE_DATA deviceInterfaceData; 102 | PSP_DEVICE_INTERFACE_DETAIL_DATA pInterfaceDetailData = NULL; 103 | HIDD_ATTRIBUTES attributes; 104 | 105 | ULONG requiredLength=0; 106 | 107 | hDevInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); 108 | deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA); 109 | *phPortalHandle = NULL; 110 | 111 | for(memberIndex = 0; 112 | SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &guid, memberIndex, &deviceInterfaceData); 113 | memberIndex++) 114 | { 115 | SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredLength, NULL); 116 | 117 | //we got the size, allocate buffer 118 | pInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, requiredLength); 119 | assert(pInterfaceDetailData); 120 | 121 | //get the interface detailed data 122 | pInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); 123 | if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pInterfaceDetailData, requiredLength, &requiredLength, NULL)) 124 | { 125 | continue; 126 | } 127 | 128 | *phPortalHandle = CreateFile ( 129 | pInterfaceDetailData->DevicePath, 130 | GENERIC_EXECUTE | GENERIC_ALL, 131 | FILE_SHARE_READ | FILE_SHARE_WRITE, 132 | NULL, 133 | OPEN_EXISTING, 134 | FILE_FLAG_OVERLAPPED, 135 | NULL); 136 | 137 | LocalFree(pInterfaceDetailData); 138 | pInterfaceDetailData = NULL; 139 | 140 | if(guid == xbox_guid) { 141 | break; // we are done. xbox_guid does not have HID attributes 142 | } 143 | 144 | if (*phPortalHandle != INVALID_HANDLE_VALUE) 145 | { 146 | if (HidD_GetAttributes(*phPortalHandle , &attributes)) 147 | { 148 | if (((attributes.VendorID == 0x12ba) || (attributes.VendorID == 0x54c)) || (attributes.VendorID == 0x1430)) 149 | { 150 | if ((attributes.ProductID == 0x150) || (attributes.ProductID == 0x967)) 151 | { 152 | break; // found the portal. leave the handle open 153 | } 154 | } 155 | } 156 | CloseHandle(*phPortalHandle); 157 | *phPortalHandle = NULL; 158 | } 159 | } 160 | SetupDiDestroyDeviceInfoList(hDevInfo); 161 | return (*phPortalHandle != NULL); 162 | } 163 | 164 | 165 | BOOL OpenPortalHandle(PHANDLE phPortalHandle) 166 | { 167 | BOOL OK; 168 | GUID guid; 169 | 170 | // Try to open XBox portal handle first 171 | OK = OpenPortalHandleFromGUID(xbox_guid, phPortalHandle); 172 | if(OK) { 173 | g_bIsXboxPortal = TRUE; 174 | return OK; 175 | } 176 | 177 | g_bIsXboxPortal = FALSE; 178 | HidD_GetHidGuid(&guid); 179 | OK = OpenPortalHandleFromGUID(guid, phPortalHandle); 180 | 181 | return OK; 182 | } 183 | 184 | 185 | const int rw_buf_size = 0x21; 186 | typedef struct { 187 | unsigned char buf[rw_buf_size]; 188 | DWORD dwBytesTransferred; 189 | } RWBlock; 190 | 191 | 192 | // In theory, should be able to use WriteFile to create an asynchronous request using 193 | // an overlapped structure as shown in WriteOverlapped. 194 | // In practice, I couldn't get it to work, had to use output report executed immediately 195 | // for HID portals. 196 | BOOL WriteHID(HANDLE hPortalHandle, RWBlock *pb) { 197 | pb->buf[0] = 0; // Use report 0 198 | return HidD_SetOutputReport(hPortalHandle, pb->buf, 0x21); 199 | } 200 | 201 | // Write to the portal using asynchronous IO. 202 | BOOL WriteOverlapped(HANDLE hPortalHandle, RWBlock *pb) { 203 | OVERLAPPED ovlr; 204 | DWORD error_cc; 205 | DWORD err; 206 | 207 | // Must set the Offset and OffsetHigh members of the OVERLAPPED structure to zero. 208 | memset(&ovlr, 0, sizeof(ovlr)); 209 | ovlr.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // write event 210 | 211 | pb->buf[0] = 0; // Use report 0 212 | 213 | error_cc = WriteFile(hPortalHandle, pb->buf, rw_buf_size, NULL, &ovlr); 214 | 215 | err = GetLastError(); 216 | if (error_cc == FALSE && err != ERROR_IO_PENDING) { 217 | fprintf(stderr, "Error in sending command to portal. Error %d.\n", GetLastError()); 218 | return FALSE; 219 | } 220 | 221 | //Sleep(2000); 222 | 223 | /* 224 | // Block until command has been sent 225 | if(err == ERROR_IO_PENDING) { 226 | int cnt = 0; 227 | while(!HasOverlappedIoCompleted(&ovlr) && cnt++ < 10) { 228 | Sleep(10); 229 | } 230 | } 231 | 232 | if(!HasOverlappedIoCompleted(&ovlr)) { 233 | return FALSE; 234 | } 235 | */ 236 | 237 | return TRUE; 238 | } 239 | 240 | // Send a command to the portal 241 | BOOL Write(HANDLE hPortalHandle, RWBlock *pb) { 242 | if(!g_bIsXboxPortal) { 243 | return WriteHID(hPortalHandle, pb); 244 | } else { 245 | return WriteOverlapped(hPortalHandle, pb); 246 | } 247 | } 248 | 249 | #if 0 250 | // Synchronous immediate read 251 | BOOL Read(HANDLE hPortalHandle, RWBlock *pb) { 252 | pb->buf[0] = 0; // Use report 0 253 | return HidD_GetInputReport(hPortalHandle, pb->buf, 0x21); 254 | } 255 | #endif 256 | 257 | BOOL ReadBlock(HANDLE hPortalHandle, unsigned int block, unsigned char data[0x10], 258 | BOOL isNEWskylander) { 259 | RWBlock req, res; 260 | BOOL running = TRUE; 261 | BOOL gotData; 262 | OVERLAPPED ovlr; 263 | DWORD err; 264 | unsigned char followup; 265 | 266 | if(block >= 0x40) { 267 | return FALSE; 268 | } 269 | 270 | printf("."); 271 | 272 | for(int retries = 0; retries < 3; retries++) 273 | { 274 | // Send query request 275 | memset(req.buf, 0, rw_buf_size); 276 | req.buf[1] = 'Q'; 277 | if(isNEWskylander) { 278 | followup = 0x11; 279 | if(block == 0) { 280 | req.buf[2] = 0x21; 281 | } else { 282 | req.buf[2] = followup; 283 | } 284 | } else { 285 | followup = 0x10; 286 | if(block == 0) { 287 | req.buf[2] = 0x20; 288 | } else { 289 | req.buf[2] = followup; 290 | } 291 | } 292 | 293 | req.buf[3] = (unsigned char)block; 294 | 295 | memset(&(res.buf), 0, rw_buf_size); 296 | 297 | 298 | // Must set the Offset and OffsetHigh members of the OVERLAPPED structure to zero. 299 | memset(&ovlr, 0, sizeof(ovlr)); 300 | 301 | ovlr.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // read event 302 | 303 | int i=0; 304 | gotData = FALSE; 305 | 306 | 307 | Write(hPortalHandle, &req); 308 | // Don't wait. Start polling for result immediately 309 | 310 | for(; i<40; ++i) // try up to 40 reads 311 | { 312 | BOOL b = ReadFile(hPortalHandle, res.buf, rw_buf_size, &(res.dwBytesTransferred), &ovlr); 313 | if(!b) 314 | { 315 | /* failed to get data immediately*/ 316 | err = GetLastError(); 317 | if(err == ERROR_IO_PENDING) 318 | { 319 | /* wait for data */ 320 | b = GetOverlappedResult(hPortalHandle, &ovlr, &res.dwBytesTransferred, TRUE); 321 | if(!b) 322 | { 323 | /* wait failed */ 324 | break; 325 | } 326 | } 327 | else 328 | { 329 | /* some other error */ 330 | break; 331 | } 332 | } 333 | if(res.dwBytesTransferred > 0) 334 | { 335 | /* has data */ 336 | if(res.buf[1] == 'Q' && res.buf[3] == (unsigned char)block) { 337 | // Got our query back 338 | if(res.buf[2] == followup) { 339 | /* got the query back with no error */ 340 | gotData = TRUE; 341 | break; 342 | } 343 | } 344 | 345 | res.buf[0] = 0; // make sure we are using report 0 346 | } 347 | } /* read loop */ 348 | 349 | CloseHandle(ovlr.hEvent); 350 | 351 | if(gotData) { 352 | break; 353 | } 354 | 355 | } // retries 356 | 357 | if(gotData) { 358 | memcpy(data, res.buf + 4, 0x10); 359 | } 360 | 361 | return gotData; 362 | } 363 | 364 | 365 | BOOL WriteBlock(HANDLE hPortalHandle, unsigned int block, unsigned char data[0x10], 366 | BOOL isNEWskylander) { 367 | RWBlock req; 368 | unsigned char verify[0x10]; 369 | BOOL OK; 370 | 371 | printf("."); 372 | 373 | for(int retries=0; retries < 3; retries++) 374 | { 375 | // Write request 376 | // W 57 10 <0x10 bytes of data> 377 | memset(req.buf, 0, rw_buf_size); 378 | req.buf[1] = 'W'; 379 | req.buf[2] = 0x10; 380 | req.buf[3] = (unsigned char) block; 381 | memcpy(req.buf+4, data, 0x10); 382 | 383 | Write(hPortalHandle, &req); 384 | Sleep(100); // wait for write to take effect. 385 | 386 | memset(verify, 0xCD, sizeof(verify)); 387 | 388 | OK = ReadBlock(hPortalHandle, block, verify, isNEWskylander); 389 | OK = OK && (memcmp(data, verify, sizeof(verify)) == 0); 390 | if(OK) break; 391 | } 392 | 393 | return OK; 394 | } 395 | 396 | // Start portal 397 | void RestartPortal(HANDLE hPortalHandle) 398 | { 399 | RWBlock req; 400 | 401 | memset(req.buf, 0, rw_buf_size); 402 | req.buf[1] = 'R'; 403 | Write(hPortalHandle, &req); 404 | } 405 | 406 | // Antenna up / activate 407 | void ActivatePortal(HANDLE hPortalHandle) 408 | { 409 | RWBlock req; 410 | 411 | memset(req.buf, 0, rw_buf_size); 412 | req.buf[1] = 'A'; 413 | req.buf[2] = 0x01; 414 | Write(hPortalHandle, &req); 415 | } 416 | 417 | // Set the portal color 418 | void SetPortalColor(HANDLE hPortalHandle, unsigned char r, unsigned char g, unsigned char b) 419 | { 420 | RWBlock req; 421 | 422 | memset(req.buf, 0, rw_buf_size); 423 | req.buf[1] = 'C'; 424 | req.buf[2] = r; // R 425 | req.buf[3] = g; // G 426 | req.buf[4] = b; // B 427 | Write(hPortalHandle, &req); 428 | } 429 | 430 | // Release hPortalInstance 431 | void DisconnectPortal(void) { 432 | CloseHandle(g_hPortalHandle); 433 | } 434 | 435 | void ConnectToPortal(void) { 436 | static bool initialized = false; 437 | 438 | if(!initialized) 439 | { 440 | printf("Connecting to portal.\n"); 441 | 442 | initialized = true; 443 | 444 | // Try to stop the SpyroService if we can 445 | StopSpyroService(); 446 | 447 | // setup hPortalInstance 448 | BOOL bResult = TRUE; 449 | BOOL bNewSkylander = FALSE; 450 | 451 | bResult = OpenPortalHandle(&g_hPortalHandle); 452 | if(!bResult) 453 | { 454 | return; 455 | } 456 | 457 | RestartPortal(g_hPortalHandle); 458 | ActivatePortal(g_hPortalHandle); 459 | 460 | // disconnect on program exit 461 | atexit(DisconnectPortal); 462 | } 463 | } 464 | 465 | #if 1 466 | 467 | // stub for testing 468 | unsigned char *ReadSkylanderFromPortal(void) { 469 | return ReadSkylanderFile("D:\\Skylanders\\Editor\\Debug\\dspyro.spy"); // emulate portal for testing 470 | } 471 | 472 | // stub for testing 473 | void WriteSkylanderToPortal(unsigned char *encrypted_new_data, unsigned char *encrypted_old_data) 474 | { 475 | WriteSkylanderFile("D:\\Skylanders\\Editor\\Debug\\dspyro_u.spy", encrypted_new_data); // emulate portal for testing 476 | } 477 | 478 | #endif 479 | 480 | #if 0 481 | unsigned char *ReadSkylanderFromPortal(void) { 482 | BOOL bResult; 483 | BOOL bNewSkylander = FALSE; 484 | unsigned char data[0x10]; 485 | unsigned char *buffer; 486 | unsigned char *ptr; 487 | 488 | ConnectToPortal(); 489 | 490 | printf("Reading Skylander from portal.\n"); 491 | 492 | // must start with a read of block zero 493 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 494 | 495 | if(!bResult) { 496 | bNewSkylander = !bNewSkylander; 497 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 498 | } 499 | 500 | if(!bResult) { 501 | fprintf(stderr, "\nCould not read Skylander on portal. Please disconnect/reconnect device.\n"); 502 | return NULL; 503 | } 504 | 505 | // I don't know that we need this, but the web driver sets the color when reading the data 506 | SetPortalColor(g_hPortalHandle, 0xC8, 0xC8, 0xC8); 507 | 508 | buffer = (unsigned char *)malloc(1024); 509 | if(!buffer) { 510 | fprintf(stderr, "\nCould not allocate memory to read Skylander data.\n"); 511 | return NULL; 512 | } 513 | 514 | ptr = buffer; 515 | memcpy(ptr, data, sizeof(data)); 516 | 517 | for(int block=1; block < 0x40; ++block) { 518 | ptr += 0x10; 519 | bResult = ReadBlock(g_hPortalHandle, block, data, bNewSkylander); 520 | memcpy(ptr, data, sizeof(data)); 521 | } 522 | 523 | printf("\nSkylander read from portal.\n"); 524 | return buffer; 525 | } 526 | 527 | 528 | BOOL WriteSkylanderToPortal(unsigned char *encrypted_new_data, unsigned char *encrypted_old_data) 529 | { 530 | BOOL bResult; 531 | BOOL bNewSkylander = FALSE; 532 | unsigned char data[0x10]; 533 | 534 | ConnectToPortal(); 535 | 536 | // must start with a read of block zero 537 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 538 | 539 | if(!bResult) { 540 | bNewSkylander = !bNewSkylander; 541 | bResult = ReadBlock(g_hPortalHandle, 0, data, bNewSkylander); 542 | if(!bResult) { 543 | fprintf(stderr, "Abort before write. Could not read data from Skylander portal.\n"); 544 | return FALSE; 545 | } 546 | } 547 | 548 | if(encrypted_old_data == NULL) { 549 | encrypted_old_data = ReadSkylanderFromPortal(); 550 | } 551 | 552 | printf("\nWriting Skylander to portal.\n"); 553 | 554 | for(int i=0; i<2; i++) { 555 | // two pass write 556 | // write the access control blocks first 557 | BOOL selectAccessControlBlock; 558 | if(i == 0) { 559 | selectAccessControlBlock = 1; 560 | } else { 561 | selectAccessControlBlock = 0; 562 | } 563 | 564 | for(int block=0; block < 0x40; ++block) { 565 | BOOL changed, OK; 566 | int offset = block * 0x10; 567 | changed = (memcmp(encrypted_old_data+offset, encrypted_new_data+offset, 0x10) != 0); 568 | if(changed) { 569 | if(IsAccessControlBlock(block) == selectAccessControlBlock) { 570 | OK = WriteBlock(g_hPortalHandle, block, encrypted_new_data+offset, bNewSkylander); 571 | if(!OK) { 572 | fprintf(stderr, "Failed to write block %d. Aborting.\n", block); 573 | return FALSE; 574 | } 575 | } 576 | } 577 | } 578 | } 579 | return TRUE; 580 | } 581 | #endif 582 | -------------------------------------------------------------------------------- /hid_win.c: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | 5 | Alan Ott 6 | Signal 11 Software 7 | 8 | 8/22/2009 9 | 10 | Copyright 2009, All Rights Reserved. 11 | 12 | At the discretion of the user of this library, 13 | this software may be licensed under the terms of the 14 | GNU General Public License v3, a BSD-Style license, or the 15 | original HIDAPI license as outlined in the LICENSE.txt, 16 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 17 | files located at the root of the source distribution. 18 | These files may also be found in the public source 19 | code repository located at: 20 | http://github.com/signal11/hidapi . 21 | ********************************************************/ 22 | 23 | #include 24 | 25 | #ifndef _NTDEF_ 26 | typedef LONG NTSTATUS; 27 | #endif 28 | 29 | #ifdef __MINGW32__ 30 | #include 31 | #include 32 | #endif 33 | 34 | #ifdef __CYGWIN__ 35 | #include 36 | #define _wcsdup wcsdup 37 | #endif 38 | 39 | /*#define HIDAPI_USE_DDK*/ 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif 44 | #include 45 | #include 46 | #ifdef HIDAPI_USE_DDK 47 | #include 48 | #endif 49 | 50 | /* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */ 51 | #define HID_OUT_CTL_CODE(id) \ 52 | CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) 53 | #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) 54 | 55 | #ifdef __cplusplus 56 | } /* extern "C" */ 57 | #endif 58 | 59 | #include 60 | #include 61 | 62 | 63 | #include "hidapi.h" 64 | 65 | #ifdef _MSC_VER 66 | /* Thanks Microsoft, but I know how to use strncpy(). */ 67 | #pragma warning(disable:4996) 68 | #endif 69 | 70 | #ifdef __cplusplus 71 | extern "C" { 72 | #endif 73 | 74 | #ifndef HIDAPI_USE_DDK 75 | /* Since we're not building with the DDK, and the HID header 76 | files aren't part of the SDK, we have to define all this 77 | stuff here. In lookup_functions(), the function pointers 78 | defined below are set. */ 79 | typedef struct _HIDD_ATTRIBUTES{ 80 | ULONG Size; 81 | USHORT VendorID; 82 | USHORT ProductID; 83 | USHORT VersionNumber; 84 | } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; 85 | 86 | typedef USHORT USAGE; 87 | typedef struct _HIDP_CAPS { 88 | USAGE Usage; 89 | USAGE UsagePage; 90 | USHORT InputReportByteLength; 91 | USHORT OutputReportByteLength; 92 | USHORT FeatureReportByteLength; 93 | USHORT Reserved[17]; 94 | USHORT fields_not_used_by_hidapi[10]; 95 | } HIDP_CAPS, *PHIDP_CAPS; 96 | typedef void* PHIDP_PREPARSED_DATA; 97 | #define HIDP_STATUS_SUCCESS 0x110000 98 | 99 | typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); 100 | typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); 101 | typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); 102 | typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); 103 | typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); 104 | typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); 105 | typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); 106 | typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data); 107 | typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data); 108 | typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps); 109 | typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); 110 | 111 | static HidD_GetAttributes_ HidD_GetAttributes; 112 | static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; 113 | static HidD_GetManufacturerString_ HidD_GetManufacturerString; 114 | static HidD_GetProductString_ HidD_GetProductString; 115 | static HidD_SetFeature_ HidD_SetFeature; 116 | static HidD_GetFeature_ HidD_GetFeature; 117 | static HidD_GetIndexedString_ HidD_GetIndexedString; 118 | static HidD_GetPreparsedData_ HidD_GetPreparsedData; 119 | static HidD_FreePreparsedData_ HidD_FreePreparsedData; 120 | static HidP_GetCaps_ HidP_GetCaps; 121 | static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; 122 | 123 | static HMODULE lib_handle = NULL; 124 | static BOOLEAN initialized = FALSE; 125 | #endif /* HIDAPI_USE_DDK */ 126 | 127 | struct hid_device_ { 128 | HANDLE device_handle; 129 | BOOL blocking; 130 | USHORT output_report_length; 131 | size_t input_report_length; 132 | void *last_error_str; 133 | DWORD last_error_num; 134 | BOOL read_pending; 135 | char *read_buf; 136 | OVERLAPPED ol; 137 | }; 138 | 139 | static hid_device *new_hid_device() 140 | { 141 | hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); 142 | dev->device_handle = INVALID_HANDLE_VALUE; 143 | dev->blocking = TRUE; 144 | dev->output_report_length = 0; 145 | dev->input_report_length = 0; 146 | dev->last_error_str = NULL; 147 | dev->last_error_num = 0; 148 | dev->read_pending = FALSE; 149 | dev->read_buf = NULL; 150 | memset(&dev->ol, 0, sizeof(dev->ol)); 151 | dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); 152 | 153 | return dev; 154 | } 155 | 156 | static void free_hid_device(hid_device *dev) 157 | { 158 | CloseHandle(dev->ol.hEvent); 159 | CloseHandle(dev->device_handle); 160 | LocalFree(dev->last_error_str); 161 | free(dev->read_buf); 162 | free(dev); 163 | } 164 | 165 | static void register_error(hid_device *device, const char *op) 166 | { 167 | WCHAR *ptr, *msg; 168 | 169 | FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 170 | FORMAT_MESSAGE_FROM_SYSTEM | 171 | FORMAT_MESSAGE_IGNORE_INSERTS, 172 | NULL, 173 | GetLastError(), 174 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 175 | (LPVOID)&msg, 0/*sz*/, 176 | NULL); 177 | 178 | /* Get rid of the CR and LF that FormatMessage() sticks at the 179 | end of the message. Thanks Microsoft! */ 180 | ptr = msg; 181 | while (*ptr) { 182 | if (*ptr == '\r') { 183 | *ptr = 0x0000; 184 | break; 185 | } 186 | ptr++; 187 | } 188 | 189 | /* Store the message off in the Device entry so that 190 | the hid_error() function can pick it up. */ 191 | LocalFree(device->last_error_str); 192 | device->last_error_str = msg; 193 | } 194 | 195 | #ifndef HIDAPI_USE_DDK 196 | static int lookup_functions() 197 | { 198 | lib_handle = LoadLibraryA("hid.dll"); 199 | if (lib_handle) { 200 | #define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; 201 | RESOLVE(HidD_GetAttributes); 202 | RESOLVE(HidD_GetSerialNumberString); 203 | RESOLVE(HidD_GetManufacturerString); 204 | RESOLVE(HidD_GetProductString); 205 | RESOLVE(HidD_SetFeature); 206 | RESOLVE(HidD_GetFeature); 207 | RESOLVE(HidD_GetIndexedString); 208 | RESOLVE(HidD_GetPreparsedData); 209 | RESOLVE(HidD_FreePreparsedData); 210 | RESOLVE(HidP_GetCaps); 211 | RESOLVE(HidD_SetNumInputBuffers); 212 | #undef RESOLVE 213 | } 214 | else 215 | return -1; 216 | 217 | return 0; 218 | } 219 | #endif 220 | 221 | static HANDLE open_device(const char *path, BOOL enumerate) 222 | { 223 | HANDLE handle; 224 | DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ); 225 | DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; 226 | 227 | handle = CreateFileA(path, 228 | desired_access, 229 | share_mode, 230 | NULL, 231 | OPEN_EXISTING, 232 | FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/ 233 | 0); 234 | 235 | return handle; 236 | } 237 | 238 | int HID_API_EXPORT hid_init(void) 239 | { 240 | #ifndef HIDAPI_USE_DDK 241 | if (!initialized) { 242 | if (lookup_functions() < 0) { 243 | hid_exit(); 244 | return -1; 245 | } 246 | initialized = TRUE; 247 | } 248 | #endif 249 | return 0; 250 | } 251 | 252 | int HID_API_EXPORT hid_exit(void) 253 | { 254 | #ifndef HIDAPI_USE_DDK 255 | if (lib_handle) 256 | FreeLibrary(lib_handle); 257 | lib_handle = NULL; 258 | initialized = FALSE; 259 | #endif 260 | return 0; 261 | } 262 | 263 | struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) 264 | { 265 | BOOL res; 266 | struct hid_device_info *root = NULL; /* return object */ 267 | struct hid_device_info *cur_dev = NULL; 268 | 269 | /* Windows objects for interacting with the driver. */ 270 | GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} }; 271 | SP_DEVINFO_DATA devinfo_data; 272 | SP_DEVICE_INTERFACE_DATA device_interface_data; 273 | SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; 274 | HDEVINFO device_info_set = INVALID_HANDLE_VALUE; 275 | int device_index = 0; 276 | int i; 277 | 278 | if (hid_init() < 0) 279 | return NULL; 280 | 281 | /* Initialize the Windows objects. */ 282 | memset(&devinfo_data, 0x0, sizeof(devinfo_data)); 283 | devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); 284 | device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); 285 | 286 | /* Get information for all the devices belonging to the HID class. */ 287 | device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); 288 | 289 | /* Iterate over each device in the HID class, looking for the right one. */ 290 | 291 | for (;;) { 292 | HANDLE write_handle = INVALID_HANDLE_VALUE; 293 | DWORD required_size = 0; 294 | HIDD_ATTRIBUTES attrib; 295 | 296 | res = SetupDiEnumDeviceInterfaces(device_info_set, 297 | NULL, 298 | &InterfaceClassGuid, 299 | device_index, 300 | &device_interface_data); 301 | 302 | if (!res) { 303 | /* A return of FALSE from this function means that 304 | there are no more devices. */ 305 | break; 306 | } 307 | 308 | /* Call with 0-sized detail size, and let the function 309 | tell us how long the detail struct needs to be. The 310 | size is put in &required_size. */ 311 | res = SetupDiGetDeviceInterfaceDetailA(device_info_set, 312 | &device_interface_data, 313 | NULL, 314 | 0, 315 | &required_size, 316 | NULL); 317 | 318 | /* Allocate a long enough structure for device_interface_detail_data. */ 319 | device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); 320 | device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); 321 | 322 | /* Get the detailed data for this device. The detail data gives us 323 | the device path for this device, which is then passed into 324 | CreateFile() to get a handle to the device. */ 325 | res = SetupDiGetDeviceInterfaceDetailA(device_info_set, 326 | &device_interface_data, 327 | device_interface_detail_data, 328 | required_size, 329 | NULL, 330 | NULL); 331 | 332 | if (!res) { 333 | /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); 334 | Continue to the next device. */ 335 | goto cont; 336 | } 337 | 338 | /* Make sure this device is of Setup Class "HIDClass" and has a 339 | driver bound to it. */ 340 | for (i = 0; ; i++) { 341 | char driver_name[256]; 342 | 343 | /* Populate devinfo_data. This function will return failure 344 | when there are no more interfaces left. */ 345 | res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data); 346 | if (!res) 347 | goto cont; 348 | 349 | res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, 350 | SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); 351 | if (!res) 352 | goto cont; 353 | 354 | if (strcmp(driver_name, "HIDClass") == 0) { 355 | /* See if there's a driver bound. */ 356 | res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, 357 | SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); 358 | if (res) 359 | break; 360 | } 361 | } 362 | 363 | //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); 364 | 365 | /* Open a handle to the device */ 366 | write_handle = open_device(device_interface_detail_data->DevicePath, TRUE); 367 | 368 | /* Check validity of write_handle. */ 369 | if (write_handle == INVALID_HANDLE_VALUE) { 370 | /* Unable to open the device. */ 371 | //register_error(dev, "CreateFile"); 372 | goto cont_close; 373 | } 374 | 375 | 376 | /* Get the Vendor ID and Product ID for this device. */ 377 | attrib.Size = sizeof(HIDD_ATTRIBUTES); 378 | HidD_GetAttributes(write_handle, &attrib); 379 | //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); 380 | 381 | /* Check the VID/PID to see if we should add this 382 | device to the enumeration list. */ 383 | if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && 384 | (product_id == 0x0 || attrib.ProductID == product_id)) { 385 | 386 | #define WSTR_LEN 512 387 | const char *str; 388 | struct hid_device_info *tmp; 389 | PHIDP_PREPARSED_DATA pp_data = NULL; 390 | HIDP_CAPS caps; 391 | BOOLEAN res; 392 | NTSTATUS nt_res; 393 | wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ 394 | size_t len; 395 | 396 | /* VID/PID match. Create the record. */ 397 | tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); 398 | if (cur_dev) { 399 | cur_dev->next = tmp; 400 | } 401 | else { 402 | root = tmp; 403 | } 404 | cur_dev = tmp; 405 | 406 | /* Get the Usage Page and Usage for this device. */ 407 | res = HidD_GetPreparsedData(write_handle, &pp_data); 408 | if (res) { 409 | nt_res = HidP_GetCaps(pp_data, &caps); 410 | if (nt_res == HIDP_STATUS_SUCCESS) { 411 | cur_dev->usage_page = caps.UsagePage; 412 | cur_dev->usage = caps.Usage; 413 | } 414 | 415 | HidD_FreePreparsedData(pp_data); 416 | } 417 | 418 | /* Fill out the record */ 419 | cur_dev->next = NULL; 420 | str = device_interface_detail_data->DevicePath; 421 | if (str) { 422 | len = strlen(str); 423 | cur_dev->path = (char*) calloc(len+1, sizeof(char)); 424 | strncpy(cur_dev->path, str, len+1); 425 | cur_dev->path[len] = '\0'; 426 | } 427 | else 428 | cur_dev->path = NULL; 429 | 430 | /* Serial Number */ 431 | res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); 432 | wstr[WSTR_LEN-1] = 0x0000; 433 | if (res) { 434 | cur_dev->serial_number = _wcsdup(wstr); 435 | } 436 | 437 | /* Manufacturer String */ 438 | res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); 439 | wstr[WSTR_LEN-1] = 0x0000; 440 | if (res) { 441 | cur_dev->manufacturer_string = _wcsdup(wstr); 442 | } 443 | 444 | /* Product String */ 445 | res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); 446 | wstr[WSTR_LEN-1] = 0x0000; 447 | if (res) { 448 | cur_dev->product_string = _wcsdup(wstr); 449 | } 450 | 451 | /* VID/PID */ 452 | cur_dev->vendor_id = attrib.VendorID; 453 | cur_dev->product_id = attrib.ProductID; 454 | 455 | /* Release Number */ 456 | cur_dev->release_number = attrib.VersionNumber; 457 | 458 | /* Interface Number. It can sometimes be parsed out of the path 459 | on Windows if a device has multiple interfaces. See 460 | http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or 461 | search for "Hardware IDs for HID Devices" at MSDN. If it's not 462 | in the path, it's set to -1. */ 463 | cur_dev->interface_number = -1; 464 | if (cur_dev->path) { 465 | char *interface_component = strstr(cur_dev->path, "&mi_"); 466 | if (interface_component) { 467 | char *hex_str = interface_component + 4; 468 | char *endptr = NULL; 469 | cur_dev->interface_number = strtol(hex_str, &endptr, 16); 470 | if (endptr == hex_str) { 471 | /* The parsing failed. Set interface_number to -1. */ 472 | cur_dev->interface_number = -1; 473 | } 474 | } 475 | } 476 | } 477 | 478 | cont_close: 479 | CloseHandle(write_handle); 480 | cont: 481 | /* We no longer need the detail data. It can be freed */ 482 | free(device_interface_detail_data); 483 | 484 | device_index++; 485 | 486 | } 487 | 488 | /* Close the device information handle. */ 489 | SetupDiDestroyDeviceInfoList(device_info_set); 490 | 491 | return root; 492 | 493 | } 494 | 495 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) 496 | { 497 | /* TODO: Merge this with the Linux version. This function is platform-independent. */ 498 | struct hid_device_info *d = devs; 499 | while (d) { 500 | struct hid_device_info *next = d->next; 501 | free(d->path); 502 | free(d->serial_number); 503 | free(d->manufacturer_string); 504 | free(d->product_string); 505 | free(d); 506 | d = next; 507 | } 508 | } 509 | 510 | 511 | HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) 512 | { 513 | /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ 514 | struct hid_device_info *devs, *cur_dev; 515 | const char *path_to_open = NULL; 516 | hid_device *handle = NULL; 517 | 518 | devs = hid_enumerate(vendor_id, product_id); 519 | cur_dev = devs; 520 | while (cur_dev) { 521 | if (cur_dev->vendor_id == vendor_id && 522 | cur_dev->product_id == product_id) { 523 | if (serial_number) { 524 | if (wcscmp(serial_number, cur_dev->serial_number) == 0) { 525 | path_to_open = cur_dev->path; 526 | break; 527 | } 528 | } 529 | else { 530 | path_to_open = cur_dev->path; 531 | break; 532 | } 533 | } 534 | cur_dev = cur_dev->next; 535 | } 536 | 537 | if (path_to_open) { 538 | /* Open the device */ 539 | handle = hid_open_path(path_to_open); 540 | } 541 | 542 | hid_free_enumeration(devs); 543 | 544 | return handle; 545 | } 546 | 547 | HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) 548 | { 549 | hid_device *dev; 550 | HIDP_CAPS caps; 551 | PHIDP_PREPARSED_DATA pp_data = NULL; 552 | BOOLEAN res; 553 | NTSTATUS nt_res; 554 | 555 | if (hid_init() < 0) { 556 | return NULL; 557 | } 558 | 559 | dev = new_hid_device(); 560 | 561 | /* Open a handle to the device */ 562 | dev->device_handle = open_device(path, FALSE); 563 | 564 | /* Check validity of write_handle. */ 565 | if (dev->device_handle == INVALID_HANDLE_VALUE) { 566 | /* Unable to open the device. */ 567 | register_error(dev, "CreateFile"); 568 | goto err; 569 | } 570 | 571 | /* Set the Input Report buffer size to 64 reports. */ 572 | res = HidD_SetNumInputBuffers(dev->device_handle, 64); 573 | if (!res) { 574 | register_error(dev, "HidD_SetNumInputBuffers"); 575 | goto err; 576 | } 577 | 578 | /* Get the Input Report length for the device. */ 579 | res = HidD_GetPreparsedData(dev->device_handle, &pp_data); 580 | if (!res) { 581 | register_error(dev, "HidD_GetPreparsedData"); 582 | goto err; 583 | } 584 | nt_res = HidP_GetCaps(pp_data, &caps); 585 | if (nt_res != HIDP_STATUS_SUCCESS) { 586 | register_error(dev, "HidP_GetCaps"); 587 | goto err_pp_data; 588 | } 589 | dev->output_report_length = caps.OutputReportByteLength; 590 | dev->input_report_length = caps.InputReportByteLength; 591 | HidD_FreePreparsedData(pp_data); 592 | 593 | dev->read_buf = (char*) malloc(dev->input_report_length); 594 | 595 | return dev; 596 | 597 | err_pp_data: 598 | HidD_FreePreparsedData(pp_data); 599 | err: 600 | free_hid_device(dev); 601 | return NULL; 602 | } 603 | 604 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) 605 | { 606 | DWORD bytes_written; 607 | BOOL res; 608 | 609 | OVERLAPPED ol; 610 | unsigned char *buf; 611 | memset(&ol, 0, sizeof(ol)); 612 | 613 | /* Make sure the right number of bytes are passed to WriteFile. Windows 614 | expects the number of bytes which are in the _longest_ report (plus 615 | one for the report number) bytes even if the data is a report 616 | which is shorter than that. Windows gives us this value in 617 | caps.OutputReportByteLength. If a user passes in fewer bytes than this, 618 | create a temporary buffer which is the proper size. */ 619 | if (length >= dev->output_report_length) { 620 | /* The user passed the right number of bytes. Use the buffer as-is. */ 621 | buf = (unsigned char *) data; 622 | } else { 623 | /* Create a temporary buffer and copy the user's data 624 | into it, padding the rest with zeros. */ 625 | buf = (unsigned char *) malloc(dev->output_report_length); 626 | memcpy(buf, data, length); 627 | memset(buf + length, 0, dev->output_report_length - length); 628 | length = dev->output_report_length; 629 | } 630 | 631 | res = WriteFile(dev->device_handle, buf, length, NULL, &ol); 632 | 633 | if (!res) { 634 | if (GetLastError() != ERROR_IO_PENDING) { 635 | /* WriteFile() failed. Return error. */ 636 | register_error(dev, "WriteFile"); 637 | bytes_written = -1; 638 | goto end_of_function; 639 | } 640 | } 641 | 642 | /* Wait here until the write is done. This makes 643 | hid_write() synchronous. */ 644 | res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/); 645 | if (!res) { 646 | /* The Write operation failed. */ 647 | register_error(dev, "WriteFile"); 648 | bytes_written = -1; 649 | goto end_of_function; 650 | } 651 | 652 | end_of_function: 653 | if (buf != data) 654 | free(buf); 655 | 656 | return bytes_written; 657 | } 658 | 659 | 660 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) 661 | { 662 | DWORD bytes_read = 0; 663 | size_t copy_len = 0; 664 | BOOL res; 665 | 666 | /* Copy the handle for convenience. */ 667 | HANDLE ev = dev->ol.hEvent; 668 | 669 | if (!dev->read_pending) { 670 | /* Start an Overlapped I/O read. */ 671 | dev->read_pending = TRUE; 672 | memset(dev->read_buf, 0, dev->input_report_length); 673 | ResetEvent(ev); 674 | res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol); 675 | 676 | if (!res) { 677 | if (GetLastError() != ERROR_IO_PENDING) { 678 | /* ReadFile() has failed. 679 | Clean up and return error. */ 680 | CancelIo(dev->device_handle); 681 | dev->read_pending = FALSE; 682 | goto end_of_function; 683 | } 684 | } 685 | } 686 | 687 | if (milliseconds >= 0) { 688 | /* See if there is any data yet. */ 689 | res = WaitForSingleObject(ev, milliseconds); 690 | if (res != WAIT_OBJECT_0) { 691 | /* There was no data this time. Return zero bytes available, 692 | but leave the Overlapped I/O running. */ 693 | return 0; 694 | } 695 | } 696 | 697 | /* Either WaitForSingleObject() told us that ReadFile has completed, or 698 | we are in non-blocking mode. Get the number of bytes read. The actual 699 | data has been copied to the data[] array which was passed to ReadFile(). */ 700 | res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); 701 | 702 | /* Set pending back to false, even if GetOverlappedResult() returned error. */ 703 | dev->read_pending = FALSE; 704 | 705 | if (res && bytes_read > 0) { 706 | if (dev->read_buf[0] == 0x0) { 707 | /* If report numbers aren't being used, but Windows sticks a report 708 | number (0x0) on the beginning of the report anyway. To make this 709 | work like the other platforms, and to make it work more like the 710 | HID spec, we'll skip over this byte. */ 711 | bytes_read--; 712 | copy_len = length > bytes_read ? bytes_read : length; 713 | memcpy(data, dev->read_buf+1, copy_len); 714 | } 715 | else { 716 | /* Copy the whole buffer, report number and all. */ 717 | copy_len = length > bytes_read ? bytes_read : length; 718 | memcpy(data, dev->read_buf, copy_len); 719 | } 720 | } 721 | 722 | end_of_function: 723 | if (!res) { 724 | register_error(dev, "GetOverlappedResult"); 725 | return -1; 726 | } 727 | 728 | return copy_len; 729 | } 730 | 731 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) 732 | { 733 | return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); 734 | } 735 | 736 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) 737 | { 738 | dev->blocking = !nonblock; 739 | return 0; /* Success */ 740 | } 741 | 742 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) 743 | { 744 | BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length); 745 | if (!res) { 746 | register_error(dev, "HidD_SetFeature"); 747 | return -1; 748 | } 749 | 750 | return length; 751 | } 752 | 753 | 754 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) 755 | { 756 | BOOL res; 757 | #if 0 758 | res = HidD_GetFeature(dev->device_handle, data, length); 759 | if (!res) { 760 | register_error(dev, "HidD_GetFeature"); 761 | return -1; 762 | } 763 | return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */ 764 | #else 765 | DWORD bytes_returned; 766 | 767 | OVERLAPPED ol; 768 | memset(&ol, 0, sizeof(ol)); 769 | 770 | res = DeviceIoControl(dev->device_handle, 771 | IOCTL_HID_GET_FEATURE, 772 | data, length, 773 | data, length, 774 | &bytes_returned, &ol); 775 | 776 | if (!res) { 777 | if (GetLastError() != ERROR_IO_PENDING) { 778 | /* DeviceIoControl() failed. Return error. */ 779 | register_error(dev, "Send Feature Report DeviceIoControl"); 780 | return -1; 781 | } 782 | } 783 | 784 | /* Wait here until the write is done. This makes 785 | hid_get_feature_report() synchronous. */ 786 | res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); 787 | if (!res) { 788 | /* The operation failed. */ 789 | register_error(dev, "Send Feature Report GetOverLappedResult"); 790 | return -1; 791 | } 792 | 793 | /* bytes_returned does not include the first byte which contains the 794 | report ID. The data buffer actually contains one more byte than 795 | bytes_returned. */ 796 | bytes_returned++; 797 | 798 | return bytes_returned; 799 | #endif 800 | } 801 | 802 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) 803 | { 804 | if (!dev) 805 | return; 806 | CancelIo(dev->device_handle); 807 | free_hid_device(dev); 808 | } 809 | 810 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) 811 | { 812 | BOOL res; 813 | 814 | res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * maxlen); 815 | if (!res) { 816 | register_error(dev, "HidD_GetManufacturerString"); 817 | return -1; 818 | } 819 | 820 | return 0; 821 | } 822 | 823 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) 824 | { 825 | BOOL res; 826 | 827 | res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * maxlen); 828 | if (!res) { 829 | register_error(dev, "HidD_GetProductString"); 830 | return -1; 831 | } 832 | 833 | return 0; 834 | } 835 | 836 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) 837 | { 838 | BOOL res; 839 | 840 | res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * maxlen); 841 | if (!res) { 842 | register_error(dev, "HidD_GetSerialNumberString"); 843 | return -1; 844 | } 845 | 846 | return 0; 847 | } 848 | 849 | int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) 850 | { 851 | BOOL res; 852 | 853 | res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * maxlen); 854 | if (!res) { 855 | register_error(dev, "HidD_GetIndexedString"); 856 | return -1; 857 | } 858 | 859 | return 0; 860 | } 861 | 862 | 863 | HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) 864 | { 865 | return (wchar_t*)dev->last_error_str; 866 | } 867 | 868 | 869 | /*#define PICPGM*/ 870 | /*#define S11*/ 871 | #define P32 872 | #ifdef S11 873 | unsigned short VendorID = 0xa0a0; 874 | unsigned short ProductID = 0x0001; 875 | #endif 876 | 877 | #ifdef P32 878 | unsigned short VendorID = 0x04d8; 879 | unsigned short ProductID = 0x3f; 880 | #endif 881 | 882 | 883 | #ifdef PICPGM 884 | unsigned short VendorID = 0x04d8; 885 | unsigned short ProductID = 0x0033; 886 | #endif 887 | 888 | 889 | #if 0 890 | int __cdecl main(int argc, char* argv[]) 891 | { 892 | int res; 893 | unsigned char buf[65]; 894 | 895 | UNREFERENCED_PARAMETER(argc); 896 | UNREFERENCED_PARAMETER(argv); 897 | 898 | /* Set up the command buffer. */ 899 | memset(buf,0x00,sizeof(buf)); 900 | buf[0] = 0; 901 | buf[1] = 0x81; 902 | 903 | 904 | /* Open the device. */ 905 | int handle = open(VendorID, ProductID, L"12345"); 906 | if (handle < 0) 907 | printf("unable to open device\n"); 908 | 909 | 910 | /* Toggle LED (cmd 0x80) */ 911 | buf[1] = 0x80; 912 | res = write(handle, buf, 65); 913 | if (res < 0) 914 | printf("Unable to write()\n"); 915 | 916 | /* Request state (cmd 0x81) */ 917 | buf[1] = 0x81; 918 | write(handle, buf, 65); 919 | if (res < 0) 920 | printf("Unable to write() (2)\n"); 921 | 922 | /* Read requested state */ 923 | read(handle, buf, 65); 924 | if (res < 0) 925 | printf("Unable to read()\n"); 926 | 927 | /* Print out the returned buffer. */ 928 | for (int i = 0; i < 4; i++) 929 | printf("buf[%d]: %d\n", i, buf[i]); 930 | 931 | return 0; 932 | } 933 | #endif 934 | 935 | #ifdef __cplusplus 936 | } /* extern "C" */ 937 | #endif 938 | --------------------------------------------------------------------------------