diff --git a/d2char.c b/d2char.c index 5e90cb6..174aedf 100644 --- a/d2char.c +++ b/d2char.c @@ -136,6 +136,10 @@ size_t getLastPlayed(D2CharHeader* c, char* buf, size_t bufLen) { } const char* getSkillName(int skillID) { + if(skillID > D2S_SKILL_NUMSKILLS) { + fprintf(stderr,"libd2char error: skillID %d doesn't exist\n",skillID); + return NULL; + } return skills[skillID]; } diff --git a/d2char.h b/d2char.h index 785d0a3..562345b 100644 --- a/d2char.h +++ b/d2char.h @@ -17,7 +17,7 @@ #define D2S_HOTKEYS_LENGTH 64 #define D2S_CHAR_APPEARANCE_LENGTH 32 #define D2S_DIFFICULTY_LENGTH 3 -#define D2S_WAYPOINTS_LENGTH 81 +#define D2S_WAYPOINTS_LENGTH 80 #define D2S_NPCDATA_LENGTH 51 #define D2S_CHARSTATUS_HARDCORE 0x04 @@ -92,11 +92,18 @@ typedef struct __attribute__((packed)){ uint8_t unknown6[144]; // TODO D2QuestData questData; uint8_t waypointData[D2S_WAYPOINTS_LENGTH]; + uint8_t unknown7; // TODO. Apparently this is always 0x01 uint8_t NPCIntroductions[D2S_NPCDATA_LENGTH]; // TODO: Not implemented } D2CharHeader; // TODO: All setX functions +// TODO: Load from file. +// int loadD2CharFromFile(const char* file, D2CharHeader** header, void** data); + +// TODO: Write to file. +// int writeD2CharToFile(const char* file, D2CharHeader* header, void* charData,) + uint32_t calcChecksum(D2CharHeader* c, void* charData); int checkChecksum(D2CharHeader* c, void* charData); int isHardcore(D2CharHeader* c); diff --git a/d2mercs.c b/d2mercs.c index a1af155..79b019f 100644 --- a/d2mercs.c +++ b/d2mercs.c @@ -1,5 +1,7 @@ #include "d2mercs.h" +#include + int _getMercType(int mercID) { if(mercID >= 0 && mercID <= 5) { return D2S_MERCTYPE_ROGUE; @@ -16,5 +18,8 @@ int _getMercType(int mercID) { const char* _getMercName(int mercID, int mercNameID) { int offset = _getMercType(mercID); + if(offset == D2S_MERCTYPE_UNKNOWN) { + return NULL; + } return mercNames[mercNameID + offset]; } \ No newline at end of file diff --git a/d2mercs.h b/d2mercs.h index 218eaec..bf0cb67 100644 --- a/d2mercs.h +++ b/d2mercs.h @@ -3,6 +3,8 @@ #include "d2strings.h" +// TODO: return compound data (type, subtype, difficulty, not just a string) + // The values here are the offsets of each merc's names in the table. // i.e: Merc names from pos 41 to 61 are Desert mercs // diff --git a/d2quest.c b/d2quest.c index 0758132..058529a 100644 --- a/d2quest.c +++ b/d2quest.c @@ -1,7 +1,13 @@ #include "d2char.h" #include "d2quest.h" +#include + void getCheckpointDescriptions(unsigned int quest, const char* *descriptions[16]) { + if(quest > D2S_QUESTDATA_NUMQUESTS) { + fprintf(stderr,"libd2char error: quest %d doesn't exist\n",quest); + return; + } memcpy(descriptions,(&checkpointDescriptions) + (quest * 16 * sizeof(const char*)), 16 * sizeof(const char*)); } @@ -17,7 +23,8 @@ uint16_t getQuestStatus(D2QuestData* d, unsigned int quest, unsigned int difficu } else if(quest >= D2S_QUEST_SIEGE_ON_HARROGATH && quest <= D2S_QUEST_EVE_OF_DESTRUCTION) { return d->quests[difficulty].expansionAct.questCheckpoints[quest - D2S_QUEST_SIEGE_ON_HARROGATH]; } - return D2S_QUEST_UNKNOWN; + fprintf(stderr,"libd2char error: quest %d doesn't exist\n",quest); + return 0; } void setQuestStatus(D2QuestData* d, unsigned int quest, unsigned int difficulty, uint16_t questData) { @@ -31,6 +38,8 @@ void setQuestStatus(D2QuestData* d, unsigned int quest, unsigned int difficulty, d->quests[difficulty].actData[D2S_ACT4].questCheckpoints[quest - D2S_QUEST_FALLEN_ANGEL] = questData; } else if(quest >= D2S_QUEST_SIEGE_ON_HARROGATH && quest <= D2S_QUEST_EVE_OF_DESTRUCTION) { d->quests[difficulty].expansionAct.questCheckpoints[quest - D2S_QUEST_SIEGE_ON_HARROGATH] = questData; + } else { + fprintf(stderr,"libd2char error: quest %d doesn't exist\n",quest); } } @@ -98,6 +107,11 @@ int getSpecialQuestStatus(D2QuestData* d, unsigned int specialQuestState, unsign } void setSpecialQuestStatus(D2QuestData* d, unsigned int specialQuestState, unsigned int difficulty, int bool) { + if(difficulty != D2S_DIFFICULTY_NORMAL || + difficulty != D2S_DIFFICULTY_NIGHTMARE || + difficulty != D2S_DIFFICULTY_HELL) { + fprintf(stderr,"libd2char error: difficulty %d doesn't exist\n",difficulty); + } switch(specialQuestState) { case D2S_SPECIALQUEST_AKARA_RESPEC: // This operation only makes sense if the quest is actually completed, otherwise ignore request diff --git a/d2quest.h b/d2quest.h index f9e049e..5e762bc 100644 --- a/d2quest.h +++ b/d2quest.h @@ -6,6 +6,7 @@ #include "d2strings.h" #define D2S_QUESTDATA_HEADER_LENGTH 4 +#define D2S_QUESTDATA_NUMQUESTS 27 enum D2S_QUEST { D2S_QUEST_UNKNOWN = -1, @@ -500,17 +501,22 @@ typedef struct __attribute__((packed)) { typedef struct __attribute__((packed)) { D2ActData actData[4]; D2XActData expansionAct; + // My guess is that the following `uint16_t`s all represents some quest data that was added after + // the game was launched. This also explains why there's version data (supposedly) and length + // of the quest data in bytes in the header. uint16_t akaraRespecData; // This uint16_t determines if the player has used Akara's respec or not uint16_t unknown1[6]; } D2Quests; typedef struct __attribute__((packed)) { - const char* header[D2S_QUESTDATA_HEADER_LENGTH]; - uint32_t unknown1; + const char* header[D2S_QUESTDATA_HEADER_LENGTH]; // Woo! + uint32_t unknown1; // This is likely version data uint16_t size; // in bytes D2Quests quests[3]; // 1 set for each difficulty } D2QuestData; +// TODO: These functions are completely unsafe, and don't return success + // Populates `descriptions` with static strings from library memory, no need to free. // `descriptions` contains one string for each bit field of the quest's uint16_t data. // Empty entries (non-existant or unknown checkpoints) will have NULL value. Be careful! @@ -527,9 +533,10 @@ int isQuestCompleted(D2QuestData* d, unsigned int quest, unsigned int difficulty // Set bool to 1 to set the status or 0 to remove it void setQuestStarted(D2QuestData* d, unsigned int quest, unsigned int difficulty, int bool); void setQuestRewardCollected(D2QuestData* d, unsigned int quest, unsigned int difficulty, int bool); + // When called to set the request to NOT completed (`bool` = 0), this will NOT set the reward collected status // which *may* render the quest unobtainable, this is done in case we may want to further modify the quest state. -// If you want to mark the quest as not completed just to get the reward, please use `setQuestRewardCollected()` +// If you want to mark the quest as not completed just to get the reward, please use `setQuestRewardCollected(,,0)` // instead. void setQuestCompleted(D2QuestData* d, unsigned int quest, unsigned int difficulty, int bool); diff --git a/d2skills.h b/d2skills.h index 15a91e0..7ecf888 100644 --- a/d2skills.h +++ b/d2skills.h @@ -5,6 +5,8 @@ #include "d2strings.h" +#define D2S_SKILL_NUMSKILLS 357 + const char* const skills[] = { D2S_SKILL_0, D2S_SKILL_1, diff --git a/d2strings.h b/d2strings.h index 05cb654..3949da8 100644 --- a/d2strings.h +++ b/d2strings.h @@ -1035,4 +1035,45 @@ const char* D2S_CHARPROGRESS_EXPANSION_TIER3_NAME_HARDCORE = "Guardian"; #define D2S_QUEST_CHECKPOINT_430 NULL #define D2S_QUEST_CHECKPOINT_431 NULL +// Waypoints +#define D2S_WAYPOINT_0 "Rogue Encampment" +#define D2S_WAYPOINT_1 "Cold Plains" +#define D2S_WAYPOINT_2 "Stony Field" +#define D2S_WAYPOINT_3 "Dark Wood" +#define D2S_WAYPOINT_4 "Black Marsh" +#define D2S_WAYPOINT_5 "Outer Cloister" +#define D2S_WAYPOINT_6 "Jail Level 1" +#define D2S_WAYPOINT_7 "Inner Cloister" +#define D2S_WAYPOINT_8 "Catacombs Level 2" +#define D2S_WAYPOINT_9 "Lut Gholein" +#define D2S_WAYPOINT_10 "Sewers Level 2" +#define D2S_WAYPOINT_11 "Dry Hills" +#define D2S_WAYPOINT_12 "Halls of the Dead Level 2" +#define D2S_WAYPOINT_13 "Far Oasis" +#define D2S_WAYPOINT_14 "Lost City" +#define D2S_WAYPOINT_15 "Palace Cellar Level 1" +#define D2S_WAYPOINT_16 "Arcane Sanctuary" +#define D2S_WAYPOINT_17 "Canyon of the Magi" +#define D2S_WAYPOINT_18 "Kurast Docks" +#define D2S_WAYPOINT_19 "Spider Forest" +#define D2S_WAYPOINT_20 "Great Marsh" +#define D2S_WAYPOINT_21 "Flayer Jungle" +#define D2S_WAYPOINT_22 "Lower Kurast" +#define D2S_WAYPOINT_23 "Kurast Bazaar" +#define D2S_WAYPOINT_24 "Upper Kurast" +#define D2S_WAYPOINT_25 "Travincal" +#define D2S_WAYPOINT_26 "Durance of Hate Level 2" +#define D2S_WAYPOINT_27 "Pandemonium Fortress" +#define D2S_WAYPOINT_28 "City of the Damned" +#define D2S_WAYPOINT_29 "River of Flames" +#define D2S_WAYPOINT_30 "Harrogath" +#define D2S_WAYPOINT_31 "Frigid Highlands" +#define D2S_WAYPOINT_32 "Arreat Plateau" +#define D2S_WAYPOINT_33 "Crystalline Passage" +#define D2S_WAYPOINT_34 "Halls of Pain" +#define D2S_WAYPOINT_35 "Glacial Trail" +#define D2S_WAYPOINT_36 "Frozen Tundra" +#define D2S_WAYPOINT_37 "The Ancient's Way" +#define D2S_WAYPOINT_38 "Worldstone Keep Level 2" + #endif \ No newline at end of file