/****************************************************************************
* Diablo 2 1.09 Itemcounter *
****************************************************************************
* This program reads the Diablo 2 1.09 character files described in [1] *
* and counts the number of items, printing the item count for every char *
* and a total count. Counting includes *
* - low quality items *
* - high quality items *
* - normal quality items *
* - magical items *
* - set items *
* - rare items *
* - uniq items *
* - crafted items *
* - Stone of Jordan (SoJ) *
* *
* [1]: http://www.ladderhall.com/ericjwin/109/trevin/trevinfileformat.html *
****************************************************************************
* Written by Florian 'fw' Weingarten *
* This file is free software and published under the GPL *
* (GNU General Public License) *
****************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#define CHARSAVEDIR "/home/diablo/var/charsave/"
#define ITEM_LOWQUAL 1
#define ITEM_NORMALQUAL 2
#define ITEM_HIGHQUAL 3
#define ITEM_MAGIC 4
#define ITEM_SET 5
#define ITEM_RARE 6
#define ITEM_UNIQ 7
#define ITEM_CRAFTED 8
#define ITEM_UNIQ_SOJ 122
int debug=0;
struct filebuf* readfile(char*);
struct itemlist* find_itemlist(struct filebuf*);
struct stats* parse_itemlist(struct itemlist*);
void print_stats(struct stats*, char*);
void xerror(char*, char*);
void xerrorexit(char*, char*);
struct itemlist {
struct d2s_item *item;
struct itemlist *next;
};
struct filebuf {
char *buf;
int bufsize;
};
struct stats {
int total;
int lowqual;
int normalqual;
int highqual;
int magic;
int set;
int rare;
int uniq;
int crafted;
int soj;
};
struct d2s_item {
int dontcare1:32;
int dontcare2:5;
int simple:1; // Bit position 37
int dontcare3:32;
int dontcare4:6;
int c1:8; // bit position 76 (37+1+32+6)
int c2:8;
int c3:8;
int c4:8;
int dontcare5:32; // position 140
int dontcare6:10; // +10
int quality:4; // +32 = Bit position 150 (37+1+11)
int isring:1;
// from now on, the length is not fixed
// if(isring==1): 3 bits for ring picture
// ... see Trevin Beatties website
int ringpic:3;
int classspec:1;
int uniqident:12;
} __attribute__((__packed__));
int main(int argc, char *argv[])
{
struct filebuf *fb;
struct stats overallstats;
struct stats *s;
DIR *dir;
struct dirent *dirlist;
int size;
char *filename;
if(argc > 1 && strcmp(argv[1], "-d") == 0) {
debug = 1;
}
if((dir = opendir(CHARSAVEDIR)) == NULL) {
xerrorexit("Error opening directory "CHARSAVEDIR, "opendir()");
}
memset(&overallstats, 0, sizeof(struct stats));
while((dirlist = readdir(dir)) != NULL) {
if(strcmp(dirlist->d_name, ".") == 0 || strcmp(dirlist->d_name, "..") == 0) {
continue;
}
size = strlen(CHARSAVEDIR) + 1 + strlen(dirlist->d_name) + 1;
if((filename = malloc(size)) == NULL) {
xerrorexit("Memory allocation error", "main()");
}
snprintf(filename, size, "%s/%s", CHARSAVEDIR, dirlist->d_name);
fb = readfile(filename);
s = parse_itemlist(find_itemlist(fb));
print_stats(s, dirlist->d_name);
overallstats.total += s->total;
overallstats.lowqual += s->lowqual;
overallstats.normalqual += s->normalqual;
overallstats.highqual += s->highqual;
overallstats.magic += s->magic;
overallstats.set += s->set;
overallstats.rare += s->rare;
overallstats.uniq += s->uniq;
overallstats.crafted += s->crafted;
overallstats.soj += s->soj;
free(filename);
}
puts("");
print_stats(&overallstats, "Total");
puts("");
return EXIT_SUCCESS;
}
void print_stats(struct stats *s, char *prefix)
{
printf("%15s: %4d items, %3d low, %3d norm, %3d high, %4d magic, %4d set, %4d rare, %4d uniq, %4d crafted, %2d soj\n", \
prefix, s->total, s->lowqual, s->normalqual, s->highqual, s->magic, s->set, s->rare, s->uniq, s->crafted, s->soj);
}
struct stats* parse_itemlist(struct itemlist* items)
{
struct stats *ret = malloc(sizeof(struct stats));
struct itemlist *ptr = items;
// zero the struct
memset(ret, 0, sizeof(struct stats));
while(ptr != NULL && ptr->item != NULL) {
if(debug) printf("DEBUG: ptr: %p, ptr->next: %p, ptr->item: %p\n", (void*)ptr, (void*)ptr->next, (void*)ptr->item);
if(debug) printf("DEBUG: item type=\"%c%c%c%c\"\n", ptr->item->c1, ptr->item->c2, ptr->item->c3, ptr->item->c4);
ret->total++;
switch(ptr->item->quality) {
case ITEM_LOWQUAL: ret->lowqual++; break;
case ITEM_NORMALQUAL: ret->normalqual++; break;
case ITEM_HIGHQUAL: ret->highqual++; break;
case ITEM_MAGIC: ret->magic++; break;
case ITEM_SET: ret->set++; break;
case ITEM_RARE: ret->rare++; break;
case ITEM_UNIQ: ret->uniq++; break;
case ITEM_CRAFTED: ret->crafted++; break;
}
// if the item is uniq, a ring, not class specific and has the SoJ identifier...
if(ptr->item->quality == ITEM_UNIQ && ptr->item->isring && !ptr->item->classspec && ptr->item->uniqident == ITEM_UNIQ_SOJ) {
if(debug) puts("DEBUG: Stone of Jordan (SoJ) found");
ret->soj++;
}
ptr = ptr->next;
}
return ret;
}
void xerror(char *message, char *cause)
{
fprintf(stderr, "%s: %s\n", cause, message);
}
void xerrorexit(char *message, char *cause)
{
fprintf(stderr, "%s: %s\n", cause, message);
exit(EXIT_FAILURE);
}
struct itemlist* find_itemlist(struct filebuf* mybuf)
{
char *buf = mybuf->buf;
int i, gotfirst=0;
struct itemlist *ptr = malloc(sizeof(struct itemlist));
struct itemlist *retval = ptr;
if(!ptr) {
xerrorexit("Memory allocation error", "find_itemlist()");
}
ptr->next = NULL;
ptr->item = NULL;
for(i=0; i < mybuf->bufsize; i++) {
if(i+1 < mybuf->bufsize)
if(buf[i] == 'J' && buf[i+1] == 'M') {
// got first JM. Itemlist starts here
if(gotfirst == 0) {
if(debug) printf("DEBUG: Got first JM at byte offset %d, item list starts here!\n", i);
gotfirst = 1; continue;
}
// got last JM. Itemlist ends here
if(i+3 < mybuf->bufsize && buf[i+2] == 0 && buf[i+3] == 0) {
if(debug) printf("DEBUG: Got last JM at byte offset %d, item list ends here!\n", i);
break;
}
// got a JM which is not the first and not the last one
if(debug) printf("DEBUG: \tGot item JM at byte offset %d, here starts an item!\n", i);
ptr->item = (struct d2s_item*)(buf + i);
if(ptr->item->simple) {
if(debug) printf("DEBUG: \t\tItem is not extended. Skipping.\n");
ptr->item = NULL;
continue;
}
ptr->next = malloc(sizeof(struct itemlist));
if(!(ptr->next)) xerrorexit("Memory allocation error", "find_itemlist()");
ptr->next->next = NULL;
ptr = ptr->next;
}
}
return retval;
}
struct filebuf* readfile(char *filename)
{
FILE *file;
char *buf;
struct stat *filestruct = malloc(sizeof(struct stat));
struct filebuf *mybuf = malloc(sizeof(struct filebuf));
int bufsize, size=0;
if(stat(filename,filestruct) == -1) {
xerror(strerror(errno), filename);
return NULL;
}
bufsize = filestruct->st_size;
if((buf = malloc(bufsize)) == NULL) {
xerror("Memory allocation error: buf, readfile()", filename);
return NULL;
}
if((file = fopen(filename, "r")) == NULL) {
xerror(strerror(errno), filename);
return NULL;
}
while(!feof(file)) {
size = fread(buf, 1, bufsize+1, file);
}
if(debug) printf("DEBUG: read %d bytes (of total %d) from %s\n", size, bufsize, filename);
mybuf->buf = buf;
mybuf->bufsize = bufsize;
return mybuf;
}