d2char.c 7.4 KB
#include <stdio.h>

#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;
}