#include #include "d2char.h" #include "d2mercs.h" #include "d2skills.h" uint32_t calcChecksum(D2CharHeader* c, void* charData) { uint32_t origChecksum = c->checksum; c->checksum = 0; uint32_t sum = 0; void* data = malloc(c->fileSize); memcpy(data, (void*)c, D2S_HEADER_LENGTH); memcpy(data + D2S_HEADER_LENGTH, charData, c->fileSize - D2S_HEADER_LENGTH); for(int i = 0; i < c->fileSize; ++i) { sum = (sum << 1) + ((uint8_t*)data)[i]; } free(data); c->checksum = origChecksum; return sum; } int checkChecksum(D2CharHeader* c, void* charData) { uint32_t checksum = calcChecksum(c, charData); return c->checksum == checksum; } int isHardcore(D2CharHeader* c) { return c->charStatus & D2S_CHARSTATUS_HARDCORE; } void setHardcore(D2CharHeader* c, int bool) { if(bool) { c->charProgress |= D2S_CHARSTATUS_HARDCORE; } else { c->charProgress &= ~D2S_CHARSTATUS_HARDCORE; } } int hasDied(D2CharHeader* c) { return c->charStatus & D2S_CHARSTATUS_DIED; } void setDied(D2CharHeader* c, int bool) { if(bool) { c->charProgress |= D2S_CHARSTATUS_DIED; } else { c->charProgress &= ~D2S_CHARSTATUS_DIED; } } int isExpansion(D2CharHeader* c) { return c->charStatus & D2S_CHARSTATUS_EXPANSION; } void setExpansion(D2CharHeader* c, int bool) { if(bool) { c->charProgress |= D2S_CHARSTATUS_EXPANSION; } else { c->charProgress &= ~D2S_CHARSTATUS_EXPANSION; } } int isLadder(D2CharHeader* c) { return c->charStatus & D2S_CHARSTATUS_LADDER; } void setLadder(D2CharHeader* c, int bool) { if(bool) { c->charProgress |= D2S_CHARSTATUS_LADDER; } else { c->charProgress &= ~D2S_CHARSTATUS_LADDER; } } int isFemale(D2CharHeader* c) { return (c->charClass == D2S_CHARCLASS_AMAZON || c->charClass == D2S_CHARCLASS_ASSASSIN || c->charClass == D2S_CHARCLASS_SORCERESS); } const char* getCharacterTitle(D2CharHeader* c) { D2S_DIFFICULTY hardestCleared; D2S_ACT act; // We don't care, but need to provide it getProgress(c,&act,&hardestCleared); if(isExpansion(c)) { // Expansion if(isHardcore(c)) { // Expansion Hardcore switch(hardestCleared) { case D2S_DIFFICULTY_NORMAL: return D2S_CHARPROGRESS_EXPANSION_TIER1_NAME_HARDCORE; break; case D2S_DIFFICULTY_NIGHTMARE: return D2S_CHARPROGRESS_EXPANSION_TIER2_NAME_HARDCORE; break; case D2S_DIFFICULTY_HELL: return D2S_CHARPROGRESS_EXPANSION_TIER3_NAME_HARDCORE; break; } } else { // Expansion Softcore switch(hardestCleared) { case D2S_DIFFICULTY_NORMAL: return D2S_CHARPROGRESS_EXPANSION_TIER1_NAME; break; case D2S_DIFFICULTY_NIGHTMARE: return D2S_CHARPROGRESS_EXPANSION_TIER2_NAME; break; case D2S_DIFFICULTY_HELL: return isFemale(c) ? D2S_CHARPROGRESS_EXPANSION_TIER3_NAME_F : D2S_CHARPROGRESS_EXPANSION_TIER3_NAME_M; break; } } } else { // Classic if(isHardcore(c)) { // Classic Hardcore switch(hardestCleared) { case D2S_DIFFICULTY_NORMAL: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER1_NAME_HARDCORE_F : D2S_CHARPROGRESS_CLASSIC_TIER1_NAME_HARDCORE_M; break; case D2S_DIFFICULTY_NIGHTMARE: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER2_NAME_HARDCORE_F : D2S_CHARPROGRESS_CLASSIC_TIER2_NAME_HARDCORE_M; break; case D2S_DIFFICULTY_HELL: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER3_NAME_HARDCORE_F : D2S_CHARPROGRESS_CLASSIC_TIER3_NAME_HARDCORE_M; break; } } else { // Classic Softcore switch(hardestCleared) { case D2S_DIFFICULTY_NORMAL: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER1_NAME_F : D2S_CHARPROGRESS_CLASSIC_TIER1_NAME_M; break; case D2S_DIFFICULTY_NIGHTMARE: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER2_NAME_F : D2S_CHARPROGRESS_CLASSIC_TIER2_NAME_M; break; case D2S_DIFFICULTY_HELL: return isFemale(c) ? D2S_CHARPROGRESS_CLASSIC_TIER3_NAME_F : D2S_CHARPROGRESS_CLASSIC_TIER3_NAME_M; break; } } } return D2S_CHARPROGRESS_TIER0_NAME; } size_t getLastPlayed(D2CharHeader* c, char* buf, size_t bufLen) { // In amd64, since long is 64 bits long, time_t is also 64 bits long // thus needing conversion from the save file type, which is a uint32_t #ifdef __x86_64__ uint64_t convTimestamp = c->lastPlayed; #else uint32_t convTimestamp = c->lastPlayed; #endif struct tm* time = localtime((time_t*)&convTimestamp); size_t ret = strftime(buf, bufLen, "%c", time); if(!ret) { fprintf(stderr,"libd2char error: Provided buffer for time string was too small\n"); } return ret; } D2S_DIFFICULTY getCurrentDifficulty(D2CharHeader* c) { for(int i = D2S_DIFFICULTY_NORMAL; i <= D2S_DIFFICULTY_HELL; ++i) { if(c->difficulty[i] & D2S_DIFFICULTY_ACTIVE) { return i; } } return D2S_DIFFICULTY_UNKNOWN; } D2S_ACT getCurrentAct(D2CharHeader* c) { for(int i = D2S_DIFFICULTY_NORMAL; i <= D2S_DIFFICULTY_HELL; ++i) { if(c->difficulty[i] & D2S_DIFFICULTY_ACTIVE) { return (D2S_ACT)(c->difficulty[i] & 0x07); } } return D2S_ACT_UNKNOWN; } void getProgress(D2CharHeader* c, D2S_ACT* act, D2S_DIFFICULTY* difficulty) { if(isExpansion(c)) { *difficulty = c->charProgress / 5; *act = c->charProgress % 5; // Unfortunately, this won't distinguish between act IV and act V, because // the game does not increment the charProgress when you kill Diablo, so // the only way to know is check whether or not you started the act on the // quest data // NB: This doesn't happen in Classic if(*act == D2S_ACT4) { if(c->questData.quests[*difficulty].expansionAct.actStarted) { ++*act; } } } else { *difficulty = c->charProgress / 4; *act = c->charProgress % 4; } } int setProgress(D2CharHeader* c, D2S_ACT act, D2S_DIFFICULTY difficulty) { if(difficulty != D2S_DIFFICULTY_NORMAL || difficulty != D2S_DIFFICULTY_NIGHTMARE || difficulty != D2S_DIFFICULTY_HELL) { fprintf(stderr,"libd2char error: Invalid difficulty specified: %d\n", difficulty); return -1; } if(act != D2S_ACT1 || act != D2S_ACT2 || act != D2S_ACT3 || act != D2S_ACT4 || act != D2S_ACT5) { fprintf(stderr,"libd2char error: Invalid act specified: %d\n", act); return -1; } uint8_t progress = 0; if(isExpansion(c)) { progress += difficulty * 5; progress += act; if(act == D2S_ACT5) { // See comment above --progress; c->questData.quests[difficulty].expansionAct.actStarted = 1; } } else { progress = (difficulty * 4) + act; } c->charProgress = progress; return 0; }