1#!/usr/bin/env python 2# -*- Mode: Python; tab-width: 4 -*- 3# 4# Copyright (C) 2001 Gianluigi Tiesi <sherpya@netfarm.it> 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by the 8# Free Software Foundation; either version 2, or (at your option) any later 9# version. 10# 11# This program is distributed in the hope that it will be useful, but 12# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 13# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14# for more details. 15# 16# Ranking patch by <JEBs@shbe.net> 20020503 17# Small "unsigned int" fix by <JEBs@shbe.net> 20020818 18# 19# ========================================================================== 20__version__ = "0.9" 21 22 23### Only if CGI_MODE = 1 24CGI_MODE=0 25FILE="/opt/bnetd/var/ladders/ladder.D2DV" 26MAX=100 27 28from struct import unpack,calcsize 29from string import find,split,join 30from os import stat 31from sys import argv,exit,stdout 32from getopt import getopt 33 34 35#### Templates 36modes = [ 'html', 'ansi', 'ascii', 'python' ] 37templates = {} 38for m in modes: 39 templates[m] = {} 40 41 42### html ### 43 44# 45templates['html']['header']="""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 46<html> 47 <head> 48 <title>D2 Closed Realm Ladder</title> 49 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 50 </head> 51 <body bgcolor="#000000" text="#ffff00"> 52 <h2 style="color: lightgreen;" align="center">D2 Closed Realm Ladder</h2> 53 <table style="border: solid lightblue; border-width: 1px;" align="center" border="0" width="80%" summary=""> 54""" 55 56# 57templates['html']['footer']=""" </table> 58 <p style="color: lightblue;" align="center">Generated by ladder.py v %s - © 2001 <a style="color: lightgreen;" href="mailto:sherpya@netfarm.it">Sherpya</a></p> 59 </body> 60</html> 61""" % __version__ 62 63# %s for description of ladder type 64templates['html']['summary'] = """ <tr style="color: lightblue" bgcolor="#666666"><th colspan="5">Ladder for %s</th></tr> 65""" 66 67# 68templates['html']['tbheader'] = """<tr style="color: lightgreen;"><th align="center">#</th><th align="left">Charname</th><th align="right">level</th><th align="center">class</th><th align="right">exp</th></tr> 69""" 70# %s for charname 71templates['html']['normal'] = """%s""" 72templates['html']['hardcore'] = { 0 : """<span style="color: red;">%s</span>""", 73 1 : """<span style="color: orange;">%s</span>""" } 74 75# %s charname - %d level - %s class - %d experience 76templates['html']['entry'] = """<tr bgcolor="#222222"><td align="right">%d</td><td align="left">%s</td><td align="right">%d</td><td align="center">%s</td><td align="right">%d</td></tr> 77""" 78 79# 80templates['html']['separator'] = """<tr><td colspan="5"> </td></tr> 81""" 82 83#### html 84 85#### ascii / ansi 86line = '-' * 59 + '\n' 87s10 = ' ' * 10 88s14 = ' ' * 14 89s5 = ' ' * 5 90text = 'D2 Closed Ladder' 91esc = '\033' 92off = esc + '[0m' 93 94colors = { 95 'grey': esc + '[1;30m', 96 'red': esc + '[1;31m', 97 'green': esc + '[1;32m', 98 'yellow': esc + '[1;33m', 99 'blue': esc + '[1;34m', 100 'purple': esc + '[1;35m', 101 'magenta': esc + '[1;36m', 102 'white': esc + '[1;37m', 103 'green': esc + '[1;32m' 104 } 105 106templates['ascii']['header'] = line + (int((len(line) - len(text))/ 2)) * ' ' + text + '\n' + line 107templates['ascii']['footer'] = 'generated by ladder.py (c) Sherpya [sherpya@netfarm.it]\n' 108templates['ascii']['summary'] = 'Ladder for %s\n\n' 109templates['ascii']['tbheader'] = ' # charname' + s14 + 'level' + s10 + 'class' + s10 + 'exp' + '\n\n' 110templates['ascii']['normal'] = '%s' 111templates['ascii']['hardcore'] = { 0 : '*%s', 1: '^%s' } 112templates['ascii']['entry'] = '%3d %-23s %2d %16s %10d\n' 113templates['ascii']['separator'] = line + '\n' 114 115line = colors['blue'] + ( '-' * 59) + off + '\n' 116templates['ansi']['header'] = line + (int((len(line) - len(text) - 10)/ 2)) * ' ' + colors['green'] + text + off + '\n' + line 117templates['ansi']['footer'] = colors['green'] + 'generated by ' + colors['blue'] + 'ladder.py' + colors['green'] + ' (c) Sherpya [sherpya@netfarm.it]' + off + '\n' 118templates['ansi']['summary'] = colors['white'] + 'Ladder for %s' + off + '\n\n' 119templates['ansi']['tbheader'] = colors['green'] + ' # charname' + s14 + 'level' + s10 + 'class' + s10 + 'exp' + off + '\n\n' 120templates['ansi']['normal'] = colors['yellow'] + '%s' 121templates['ansi']['hardcore'] = { 0 : colors['red'] + '%s', 1: colors['grey'] + '%s' } 122templates['ansi']['entry'] = colors['yellow'] + '%3d %-30s %2d %16s %10d' + off + '\n' 123templates['ansi']['separator'] = line + '\n' 124 125 126del text 127#### ascii / ansi 128 129 130 131### Some struct from d2cs/d2dbs source 132# 133# ladder header (4 + 4 = 8): 134# bn_int maxtype 135# bn_int checksum 136LD_HEAD="<2i" 137szLD_HEAD = calcsize(LD_HEAD) 138 139# 140# ladder info (4 + 2 + 1 + 1 + 16 = 24): 141# bn_int experience 142# bn_short status 143# bn_byte level 144# bn_byte class; 145# char charname[16]; 146LD_INFO="<Ihbb16s" 147szLD_INFO = calcsize(LD_INFO) 148 149# 150# ladder index (4 + 4 + 4 = 12): 151# bn_int type 152# bn_int offset 153# bn_int number 154LD_INDEX="<3i" 155szLD_INDEX = calcsize(LD_INDEX) 156 157## Status flags 158S_INIT = 0x1 159S_EXP = 0x20 160S_HC = 0x04 161S_DEAD = 0x08 162 163 164classes = { 165 0x00 : ['Amazon', 'f'], 166 0x01 : ['Sorceress', 'f'], 167 0x02 : ['Necromancer', 'm'], 168 0x03 : ['Paladin', 'm'], 169 0x04 : ['Barbarian', 'm'], 170 0x05 : ['Druid', 'm'], 171 0x06 : ['Assassin', 'f'] 172 } 173 174desc = { 175 'nor': 'Diablo II', 176 'exp': 'Lord of Desctruction' 177 } 178 179 180diff = { 181 'nor': { 182 0x1: { 0 : { 'm': 'Sir', 'f': 'Dame' }, 183 1 : { 'm': 'Count', 'f': 'Countess' } 184 }, 185 186 0x2: { 0 : { 'm': 'Lord', 'f': 'Lady' }, 187 1 : { 'm': 'Duke', 'f': 'Duchess' } 188 }, 189 190 0x3: { 0 : { 'm': 'Baron', 'f': 'Baroness' }, 191 1 : { 'm': 'King', 'f': 'Queen' } 192 } 193 }, 194 195 'exp': { 196 0x1: { 0 : { 'm': 'Slayer', 'f': 'Slayer' }, 197 1 : { 'm': 'Destroyer', 'f': 'Destroyer' } 198 }, 199 200 0x2: { 0 : { 'm': 'Champion', 'f': 'Champion' }, 201 1 : { 'm': 'Conqueror', 'f': 'Conqueror' } 202 }, 203 204 0x3: { 0 : { 'm': 'Patriarch', 'f': 'Matriarch' }, 205 1 : { 'm': 'Guardian', 'f': 'Guardian' } 206 } 207 } 208 } 209 210## Utils 211 212 213def remove_null(text): 214 return split(text, chr(0))[0] 215 216 217def get_ladder(file): 218 try: 219 size = stat(file)[6] 220 data = open(file, "rb") 221 except: 222 print "Error opening %s for read" % file 223 exit() 224 225 maxtype, checksum = unpack(LD_HEAD, data.read(szLD_HEAD)) 226 227 size = size - szLD_HEAD 228 229 head = [] 230 231 for i in range(maxtype): 232 type, offset, number = unpack(LD_INDEX, data.read(szLD_INDEX)) 233 size = size - szLD_INDEX 234 head.append( 235 { 236 'type': type, 237 'offset': offset, 238 'number': number 239 }) 240 241 242 ladder = {} 243 ladder['nor'] = [] 244 ladder['exp'] = [] 245 246 temp = {} 247 temp['nor'] = [] 248 temp['exp'] = [] 249 250 251 while size > 0: 252 try: 253 experience, status, level, _class, charname = unpack(LD_INFO, data.read(szLD_INFO)) 254 except: 255 ### Bad data 256 size = size - szLD_INFO 257 continue 258 259 size = size - szLD_INFO 260 261 ## Avoid null chars 262 if not experience: 263 continue 264 265 charname = remove_null(charname) 266 died = 0 267 268 if status & S_EXP: 269 _type = 'exp' 270 difficulty = ((status >> 0x08) & 0x0f) / 5 271 else: 272 _type = 'nor' 273 difficulty = ((status >> 0x08) & 0x0f) / 5 274 275 if status & S_HC: 276 hc = 1 277 if status & S_DEAD: 278 died = 1 279 else: 280 hc = 0 281 282 c_class = classes[_class] 283 284 if difficulty and diff[_type].has_key(difficulty): 285 prefix = diff[_type][difficulty][hc][c_class[1]] 286 else: 287 prefix = None 288 289 char = (experience, { 290 'charname' : charname, 291 'prefix' : prefix, 292 'experience' : experience, 293 'class' : c_class[0], 294 'sex' : c_class[0], 295 'level' : level, 296 'type' : _type, 297 'difficulty' : difficulty, 298 'hc' : hc, 299 'died' : died 300 }) 301 ## Dupe char? why? 302 if char not in temp[_type]: 303 temp[_type].append(char) 304 305 data.close() 306 307 ## Sorting by exp 308 temp['nor'].sort() 309 temp['nor'].reverse() 310 temp['exp'].sort() 311 temp['exp'].reverse() 312 313 for _type in temp.keys(): 314 for ch in temp[_type]: 315 ladder[_type].append(ch[1]) 316 del temp 317 318 return ladder 319 320def generate(ladder, mode, output, max): 321 322 output.write(templates[mode]['header']) 323 324 for _type in ladder.keys(): 325 count = 1 326 output.write(templates[mode]['summary'] % desc[_type]) 327 output.write(templates[mode]['tbheader']) 328 329 for ch in ladder[_type]: 330 if ch['prefix']: 331 charname = "%s %s" % (ch['prefix'], ch['charname']) 332 else: 333 charname = ch['charname'] 334 335 if ch['hc']: 336 charname = templates[mode]['hardcore'][ch['died']] % charname 337 else: 338 charname = templates[mode]['normal'] % charname 339 340 output.write(templates[mode]['entry'] % (count, charname, ch['level'], ch['class'], ch['experience'])) 341 count = count + 1 342 if count > max: 343 break 344 345 output.write(templates[mode]['separator']) 346 347 output.write(templates[mode]['footer']) 348 349 350def pickle_to(ladder, output): 351 try: 352 from cPickle import dump 353 except: 354 from pickle import dump 355 356 try: 357 out = open(output, "wb") 358 except: 359 print "Cannot open %s for pickle dump" % output 360 exit() 361 362 dump(ladder, out) 363 out.close() 364 365 366### Main 367 368### CGI MODE 369if CGI_MODE: 370 print "Content-Type: text/html" 371 print 372 ladder = get_ladder(FILE) 373 generate(ladder, 'html', stdout, MAX) 374 exit() 375 376args = argv[1:] 377optlist, args = getopt(args, "hi:o:m:n:") 378if len(args): 379 for bad in args: 380 print "%s: Unrecognized option %s" % (argv[0], bad) 381 exit() 382 383### defaults 384file = None 385output = None # stdout 386mode = modes[0] 387real_max = 1000 388max = 100 389 390def show_help(): 391 print 392 print "ladder.py v%s - (c) 2001 Sherpya <sherpya@netfarm.it>" % __version__ 393 print "Usage: ladder.py -i ladder_file [-o outputfile] [-m mode] [-n max ladder chars]" 394 print 395 print " -i ladder_file, is the ladder file like ladder.D2DV" 396 print " -o output file, if omitted defaults to stdout" 397 print " -m mode, avaiables mode are: %s, defaults to %s" % (join(modes,', '), modes[0]) 398 print " -n max_char, max char to display in each ladder, defaults to %d" % max 399 print 400 print " note: python output mode creates a python object usable by pickle module" 401 print 402 403for opt in optlist: 404 405 # Help 406 if opt[0] == '-h': 407 show_help() 408 exit() 409 410 # Input file 411 if opt[0] == '-i': 412 file = opt[1] 413 continue 414 415 # Output file 416 if opt[0] == '-o': 417 output = opt[1] 418 continue 419 420 # Output mode (html, ansi, ascii, python) 421 if opt[0] == '-m': 422 if opt[1] in modes: 423 mode = opt[1] 424 continue 425 else: 426 print "Invalid mode %s, valid modes are %s" % (opt[1], join(modes, ', ')) 427 exit() 428 429 # Max chars in ladder 430 if opt[0] == '-n': 431 try: 432 max = int(opt[1]) 433 except: 434 max = 0 435 436 if (max < 2) or max > real_max: 437 print "Invalid value for max char in ladder must be > 1 and < %d" % real_max 438 exit() 439 continue 440 441if not file: 442 show_help() 443 exit() 444 445ladder = get_ladder(file) 446if mode == 'python': 447 if output: 448 pickle_to(ladder, output) 449 else: 450 print "Cannot dump python object to stdout" 451 exit() 452 453if output: 454 try: 455 output = open(output, "wb") 456 except: 457 print "Cannot open %s for writing" % output 458 exit() 459else: 460 output = stdout 461 462generate(ladder, mode, output, max) 463 464 465