def readFile(fname, palOnly = False): pal = None pic = None # data = [ord(c) for c in open(fname).read()] data = open(fname, "rb").read() dataLen = len(data) print ("Readfile name& length: ", fname, dataLen) pos = 0 while pos < dataLen: tag = bread(data[pos:pos+2]) pos += 2 segLen = bread(data[pos:pos+2]) pos += 2 print ("Hextag & seglen: ", hex(tag), segLen) if tag == 0x304D: pal = readPal(data[pos:pos+segLen]) if palOnly: break elif tag == 0x3058 and not palOnly: pic = readPic(data[pos:pos+segLen]) else: print ('Unknown Tag! You found it !') pos += segLen return pal, pic
def readData(dlPath): fname = dlPath + '/darkland.enm' data = map(ord, open(fname).read()) dataLen = len(data) enemyTypes = [] pos = 0 for i in xrange(0, 71): et = OrderedDict() et['image_group'] = sread(data[pos:pos+4]) ; pos += 4 et['name'] = sread(data[pos:pos+10]) ; pos += 10 et['num_variants'] = data[pos] ; pos += 1 et['pal_cnt'] = data[pos] ; pos += 1 # number of palettes in enemypal? et['unknown2'] = data[pos] ; pos += 1 et['pal_start'] = data[pos] ; pos += 1 # palettes starting index in enemypal et['unknown4'] = bread(data[pos:pos+2]) ; pos += 2 attrs = {} for attr in ('end','str','agl','per','int','chr','df'): attrs[attr] = data[pos] ; pos += 1 et['attrs'] = attrs skills = {} for s in ('wEdg','wImp','wFll','wPol','wThr','wBow','wMsl','alch','relg','virt','spkC','spkL','r_w','heal','artf','stlh','strW','ride','wdWs'): skills[s] = data[pos] ; pos += 1 et['skills'] = skills et['unknown5'] = data[pos] ; pos += 1 pos += 1 # const 0 et['unknown6'] = data[pos] ; pos += 1 # underground related? et['unknown7'] = data[pos] ; pos += 1 et['unknown8'] = data[pos:pos+0x42] ; pos += 0x42 et['unknown9'] = data[pos:pos+0x1e] ; pos += 0x1e et['unknown10'] = data[pos:pos+2] ; pos += 2 et['vital_arm_type'] = data[pos] ; pos += 1 et['limb_arm_typetype'] = data[pos] ; pos += 1 et['armor_q'] = data[pos] ; pos += 1 et['unknown11'] = data[pos] ; pos += 1 et['shield_type'] = data[pos] ; pos += 1 et['shield_q'] = data[pos] ; pos += 1 et['unknown12'] = data[pos:pos+6] ; pos += 6 et['unknown13'] = data[pos:pos+6] ; pos += 6 et['weapon_types'] = data[pos:pos+6] ; pos += 6 et['weapon_q'] = data[pos] ; pos += 1 et['unknown14'] = data[pos:pos+11] ; pos += 11 et['unknown15'] = data[pos:pos+20] ; pos += 20 enemyTypes.append(et) enemies = [] for i in xrange(0, 82): e = OrderedDict() e['type'] = bread(data[pos:pos+2]) ; pos += 2 e['type_str'] = enemyTypes[e['type']]['name'] e['name'] = sread(data[pos:pos+12]) ; pos += 12 pos += 8 # all 0 e['unknown'] = bread(data[pos:pos+2]) ; pos += 2 e['unknown_bin'] = bin(e['unknown']) enemies.append(e) return enemyTypes, enemies
def readPic(data): state = DecState() CurIndex = 0 Width = bread(data[CurIndex:CurIndex+2]) # Get next word (Width) CurIndex += 2 Height = bread(data[CurIndex:CurIndex+2]) # Get next word (Height) CurIndex += 2 CurWordValue=bread(data[CurIndex:CurIndex+2]) # Get next word (Image data:first word) CurIndex += 2 FormatFlag = CurWordValue&0xff # First byte of Cur word if (FormatFlag>0xb): # Command is > 0xb Fix it CurWordValue=(CurWordValue&0xff00)+0xb FormatFlag=0xb RepeatCount = 0 # Pixel Repeat count=0 CurPixelValue = 0 # Cur Pixel value; none at first! state.CurIndex = CurIndex state.Data = data state.CurWordValue = CurWordValue state.FormatFlag = FormatFlag picData = [] for CurY in range(Height): picData.append([0]*Width) # Parse Data into line, a pixel at a time for CurX in range(Width): if (RepeatCount>0):# "RLE" Repeat count RepeatCount -= 1 else: TempPixelValue = GetNextPixel(state) # If next value is not 'repeat last pixel flag' it is a new pixel if (TempPixelValue!=0x90): CurPixelValue = TempPixelValue # Set Pixel Value # else get the value following the repeat flag (repeat count) else: TempPixelValue = GetNextPixel(state) # If the repeat count is 0, the 90 is a pixel value, not a rep instrution! if (TempPixelValue==0): CurPixelValue = 0x90 # Otherwise, setup the repeat else: # Already done pixel 1 (before 90), # will do pixel 2 right now, so adjust count # to correct 'future repeat' values RepeatCount = TempPixelValue - 2 # Just put the pixel in the bitmap for now #!!! ImageData[CurY*Width+CurX]=(unsigned char)CurPixelValue; # print (CurPixelValue) picData[CurY][CurX] = CurPixelValue return picData
def readFonts(fname): data = map(ord, open(fname).read()) dataLen = len(data) #print fname, dataLen, 'B' pos = 0 cnt = bread(data[pos:pos + 2]) pos += 2 #print cnt, bread(data[pos:pos+1]) offsets = [] for i in xrange(0, cnt): offsets.append(bread(data[pos:pos + 2])) pos += 2 #print offsets fonts = [] for off in offsets: # charset header hdrpos = off - 8 startChr = data[hdrpos] # first char ASCII code endChr = data[hdrpos + 1] # last char ASCII code bw = data[hdrpos + 2] # width in bytes # 0 unknown bh = data[hdrpos + 4] # height in bytes # 1 unknown # 1 unknown # 0 unknown charCnt = endChr - startChr + 1 font = { 'startChar': startChr, 'endChar': endChr, 'height': bh, 'chars': [] } chars = font['chars'] widthOff = off - charCnt - 8 for i in xrange(0, charCnt): chWidth = data[widthOff + i] char = {'width': chWidth, 'lines': []} for j in xrange(0, bh): x = 0 lnData = [0] * chWidth for k in xrange(0, bw): lnB = data[off + (j * charCnt + i) * bw + k] for bc in xrange(0, 8): if x >= chWidth: break lnData[x] = 1 if lnB & 0x80 else 0 x += 1 lnB <<= 1 char['lines'].append(lnData) chars.append(char) fonts.append(font) return fonts
def readData(fname, frameCnt=None): data = reader_drle.readData(fname) fileSize = len(data) # heuristic pos = 60 frameCnt = bread(data[pos:pos + 2]) pos += 2 dataSize = bread(data[pos:pos + 2]) pos += 2 #print fileSize, dataSize, frameCnt, fileSize - 80 - 4 - 8 * 2 * frameCnt if dataSize != fileSize - pos - 8 * 2 * frameCnt: pos = 80 frameCnt = bread(data[pos:pos + 2]) pos += 2 dataSize = bread(data[pos:pos + 2]) pos += 2 imgOffs = [] for i in xrange(0, frameCnt * 8): imgOffs.append(bread(data[pos:pos + 2])) pos += 2 imgs = [] dataStart = pos for offs in imgOffs: pos = dataStart + offs * 16 w = data[pos] pos += 1 h = data[pos] pos += 1 img = [] for y in xrange(0, h): pc = data[pos] pos += 1 # pixel count ws = data[pos] pos += 1 # empty space cnt ln = [0] * w for x in xrange(ws, ws + pc): if i == 52: print data[pos], if pos < fileSize and x < w: ln[x] = data[pos] pos += 1 else: #print w,'x',h,' ', x,y #break pass if i == 52: print img.append(ln) imgs.append(img) return imgs
def readData(dlPath): fname = dlPath + '/darkland.map' data = map(ord, open(fname).read()) dataLen = len(data) #print fname, dataLen, 'B' pos = 0 max_x_size = rbread(data[pos:pos+2]) ; pos += 2 # This word is stored high-byte first max_y_size = rbread(data[pos:pos+2]) ; pos += 2 # This word is stored high-byte first #print max_x_size, max_y_size row_offsets = [0]*max_y_size for i in xrange(0, max_y_size): # typo in Wallace's doc - it's double word, not word row_offsets[i] = bread(data[pos:pos+4]) pos += 4 m = [] for i in xrange(0, max_y_size): line = [None]*max_x_size pos = row_offsets[i] #print i, pos, row_offsets[i+1] if i+1 < len(row_offsets) else dataLen x = 0 while x < max_x_size: b = data[pos] pos += 1 cnt = b >> 5 pal = (b >> 4) & 0x1 row = b & 0xf #print ' ', pos+x, x, cnt, pal, row for c in xrange(0, cnt): line[x] = (pal, row) x += 1 m.append(line) adjTiles = ((0,),(1,2,3,25),(1,2,3,25,27),(1,2,3,25,27),(4,),(5,),(6,),(7,),(8,),(9,),(10,),(11,),(12,),(13,),(14,),(15,), (16,),(17,),(18,),(19,),(20,),(21,),(22,),(23,),(24,25,27,29),(25,1,2,3),(26,25),(27,2,3),(),(29,),(),(),) # get cols for y in xrange(0, max_y_size): xc = 0 if y%2 else -1 for x in xrange(0, max_x_size): pal, row = m[y][x] # differs from Wallace - bits set just by adjanced _same_row_ tiles tv = pal*16+row col = 0 if y > 0: if xc+x>0 and xc+x<max_x_size: col += 1 if m[y-1][xc+x][0]*16+m[y-1][xc+x][1] in adjTiles[tv] else 0 else: col += 1 if xc+x+1<max_x_size: col += 2 if m[y-1][xc+x+1][0]*16+m[y-1][xc+x+1][1] in adjTiles[tv] else 0 else: col += 2 else: col += 1 + 2 if y+1 < max_y_size: if xc+x>0 and xc+x<max_x_size: col += 4 if m[y+1][xc+x][0]*16+m[y+1][xc+x][1] in adjTiles[tv] else 0 else: col += 4 if xc+x+1<max_x_size: col += 8 if m[y+1][xc+x+1][0]*16+m[y+1][xc+x+1][1] in adjTiles[tv] else 0 else: col += 8 else: col += 4 + 8 m[y][x] = (pal, row, col) return m
def readData(dlPath): fname = dlPath + '/darkland.loc' data = map(ord, open(fname).read()) dataLen = len(data) #print fname, dataLen, 'B' pos = 0 cnt = bread(data[pos:pos + 2]) #print cnt, bread(data[pos:pos+1]) pos += 2 locs = [] for i in xrange(0, cnt): c = OrderedDict() c['icon'] = bread(data[pos:pos + 2]) pos += 2 # (enum location_icon) #Map image for the location. Note that this basically corresponds to the 'type' of location. #print c['icon'] c['str_loc_type'] = locTypes[ c['icon']] if c['icon'] < len(locTypes) else str(c['icon']) c['unknown1'] = bread(data[pos:pos + 2]) pos += 2 #c['unknown1_bin'] = bin(c['unknown1']) #0 for cities, other locations range from 0x08-0x0e. c['coords'] = (bread(data[pos:pos + 2]), bread(data[pos + 2:pos + 4])) pos += 4 #Map coordinates. c['unknown2'] = bread(data[pos:pos + 2]) pos += 2 #Ranges from 1-10. #Seems to be 4 or 9 for live Raubritters (1 for dead); perhaps it's a strength? c['unknown3'] = bread(data[pos:pos + 2]) pos += 2 #Most range from 1-5, except pagan altars, which are 0x63 (99). c['menu'] = bread(data[pos:pos + 2]) pos += 2 #Card displayed on entering the location. c['unknown4'] = bread(data[pos:pos + 2]) pos += 2 #Always 0x62 except for castles currently occupied by Raubritters (icon=2); those are 0x92. c['unknown5'] = data[pos] pos += 1 c['city_size'] = data[pos] pos += 1 #Size of the city. #Cities range from 3 (small) to 8 (Koln); non-cities are always 1. c['local_rep'] = bread(data[pos:pos + 2]) pos += 2 #Local reputation. #In this file, this is always zero. The copy of this structure that lives in the saved game files gets non-zero values. #Ranges from -150 to 150 (although others claim to have observed numbers outside this range). c['unknown6'] = data[pos] pos += 1 #Zero seems to indicate an "active" site. #Ruins of a Raubritter castle get 0x04, as do destroyed villages. #Some other locations get 0x20 or 0x24. c['unknown7_c'] = bread(data[pos:pos + 3]) pos += 3 # [constant: { 0x19, 0x19, 0x19 }] c['inn_cache_idx'] = bread(data[pos:pos + 2]) pos += 2 #In this file, this is always 0xffff (-1). #In a saved game file, if the party stores items at an inn (in a city), this value becomes an index into cache_offsets (found in dksaveXX.sav). c['unknown8_c'] = bread(data[pos:pos + 2]) pos += 2 #[constant: 0x0000] c['unknown9'] = bread(data[pos:pos + 2]) pos += 2 #All are zero except for Nurnberg, which is 0xc0. c['unknown10_c'] = bread(data[pos:pos + 8]) pos += 8 # [constant: all 0x00] c['name'] = sread(data[pos:pos + 20]) pos += 20 locs.append(c) return locs
def readData(fname): data = map(ord, open(fname).read()) dLen = len(data) out = [] pos = 0 fc = 0 flags = 0 while pos + 2 < dLen: flags = (bread(data[pos:pos + 2]) << fc) | flags pos += 2 fc += 16 while 1: if fc - 1 <= 0: break #print "%20s %2d |"%(("%20s"%bin(flags)[2:]).replace(' ','0')[-fc:], fc), ' ', len(out), '/', pos #dumpb(out) #print f = flags & 1 flags >>= 1 fc -= 1 if f: # just copy next B out.append(data[pos]) pos += 1 else: # things get complicated if fc - 1 <= 0: flags = flags << 1 fc += 1 break f = flags & 1 flags >>= 1 fc -= 1 seqLength, seqStart = None, None if not f: # 00 - yet simple if fc - 1 < 2: flags = flags << 2 fc += 2 break # next 2 b from flags reversed = seqLength - 2 f = flags & 1 flags >>= 1 fc -= 1 seqLength = f << 1 f = flags & 1 flags >>= 1 fc -= 1 seqLength += f seqLength += 2 # seqStart = 255 - next B seqStart = 0xFF - data[pos] pos += 1 else: # 01 - and more complicated w = bread(data[pos:pos + 2]) pos += 2 seqStart = (((w >> (8 + 3)) | 0xE0) << 8) | (w & 0xFF) seqStart = 0xFFFF - seqStart seqLength = (w >> 8) & 0x07 if seqLength > 0: seqLength += 2 else: b = data[pos] pos += 1 if b > 1: seqLength = b seqLength += 1 else: # DONE!!! # side effect - dummy 00FF00 at the end of file #print pos break # start left of output end seqStart = len(out) - seqStart - 1 #print 'start', seqStart, '/', len(out), 'len', seqLength for i in xrange(0, seqLength): out.append(out[seqStart + i]) #if len(out) > 5*16: pos = dLen ; break; return out
import sys from utils import bread, sread # catalog filename fname = sys.argv[1] # output dir ddir = sys.argv[2] data = map(ord, open(fname).read()) dataLen = len(data) print fname, dataLen, 'B' pos = 0 cnt = bread(data[pos:pos + 2]) print cnt pos += 2 for i in xrange(0, cnt): fn = sread(data[pos:pos + 12]) pos += 12 # dword TS pos += 4 dataLen = bread(data[pos:pos + 4]) pos += 4 dataOffs = bread(data[pos:pos + 4]) pos += 4 print fn, dataOffs, dataLen fh = open(ddir + '/' + fn, "wb") fh.write(bytearray(data[dataOffs:dataOffs + dataLen])) fh.close()
def readData(dlPath): fname = dlPath + '/darkland.lst' data = map(ord, open(fname).read()) dataLen = len(data) #print fname, dataLen, 'B' itemCnt, saintCnt, formCnt = data[0], data[1], data[2] pos = 3 items = [] for i in xrange(0, itemCnt): c = OrderedDict() c['name'] = sread(data[pos:pos + 20]) pos += 20 c['short_name'] = sread(data[pos:pos + 10]) pos += 10 c['type'] = bread(data[pos:pos + 2]) pos += 2 flags = (('is_edged', 'is_impact', 'is_polearm', 'is_flail', 'is_thrown', 'is_bow', 'is_metal_armor', 'is_shield'), ('is_unknown1', 'is_unknown2', 'is_component', 'is_potion', 'is_relic', 'is_horse', 'is_quest_1', 'is_const0_1'), ('is_lockpicks', 'is_light', 'is_arrow', 'is_const0_2', 'is_quarrel', 'is_ball', 'is_const0_3', 'is_quest_2'), ('is_throw_potion', 'is_const0_4', 'is_nonmetal_armor', 'is_missile_weapon', 'is_unknown3', 'is_music', 'is_const0_6', 'is_const0_7'), ('is_unknown4', 'is_unknown5', 'is_const0_8', 'is_const0_9', 'is_const0_10', 'is_const0_11', 'is_const0_12', 'is_unknown6')) for f in flags: bits = data[pos] pos += 1 for b, n in enumerate(f): c[n] = True if bits & (1 << b) else False c['weight'] = data[pos] pos += 1 c['quality'] = data[pos] pos += 1 c['rarity'] = data[pos] pos += 1 # missing in Merle's doc! c['unknown1'] = bread(data[pos:pos + 2]) pos += 2 #Non-zero only for relics. #Ranges from 0x06 (St. Edward's Ring) to 0x50 (St. Gabriel's Horn). c['unknown2'] = bread(data[pos:pos + 2]) pos += 2 #Non-zero only for relics, and for the "residency permit" (which is unused by the game). #Ranges from 0x05 to 0x27 (residency permit). c['value'] = bread(data[pos:pos + 2]) pos += 2 items.append(c) saints = [] for i in xrange(0, saintCnt): s = '' while data[pos]: s += chr(data[pos]) pos += 1 pos += 1 saints.append({'name': s}) for i in xrange(0, saintCnt): s = '' while data[pos]: s += chr(data[pos]) pos += 1 pos += 1 saints[i]['short_name'] = s formulae = [] for i in xrange(0, formCnt): s = '' while data[pos]: s += chr(data[pos]) pos += 1 pos += 1 formulae.append({'name': s}) for i in xrange(0, formCnt): s = '' while data[pos]: s += chr(data[pos]) pos += 1 pos += 1 formulae[i]['short_name'] = s # read saints descriptions fname = dlPath + '/darkland.snt' data = map(ord, open(fname).read()) pos = 1 for i in xrange(0, len(saints)): saints[i]['description'] = sread(data[pos:pos + 0x168]) pos += 0x168 return items, saints, formulae
def GetNextPixel(state): TempIndex = 0 Index = 0 CurBits = 0 # if pixel stack is empty fill with decoded data from file if (state.StackTop==0): # Decode data from file buffer # Get current bits (?) from previous CurWordValue # (which will have some already decode bits, some undecoded bits) # Discard known used bits, prep CurBits for more CurBits = state.CurWordValue>>(16-state.DataBitCount) # Get enough more bits from file data to be certain we have # 1 full decodable bitstring while (state.DataBitCount < state.BitMaskCount): state.CurWordValue = bread(state.Data[state.CurIndex:state.CurIndex+2]) # Get next word (Image data:next word) state.CurIndex += 2 CurBits |= (state.CurWordValue<<state.DataBitCount)&0xffff state.DataBitCount += 0x10 # Update Databit count state.DataBitCount = state.DataBitCount-state.BitMaskCount # Get default Decode Lookup Index guesses Index=CurBits & state.BitMask TempIndex=Index # If default guess is invalid (or complex?) Set values to root lookup Index if (Index>=state.DecodeTableIndex): TempIndex = state.DecodeTableIndex Index = state.PrevIndex state.Stack[state.StackTop] = state.PrevPixel state.StackTop += 1 # Folow DecodeTable list, adding each item's pixel to the stack until # the end of the list (0xFFFF) while (state.DecodeTable[Index][0] != 0xFFFF): state.Stack[state.StackTop]=(Index&0xff00)+state.DecodeTable[Index][1] #PixelData state.StackTop += 1 Index = state.DecodeTable[Index][0]# next # Push last node's pixel data, and remember pixel in 'PrevPixel' state.PrevPixel = state.DecodeTable[Index][1] #PixelData state.Stack[state.StackTop]=state.PrevPixel state.StackTop += 1 # Set Decode Table data at this position state.DecodeTable[state.DecodeTableIndex] = (state.PrevIndex, state.PrevPixel) state.PrevIndex = TempIndex # Move to next 'initial' index and Update Bitflags if necessary state.DecodeTableIndex += 1 if (state.DecodeTableIndex > state.BitMask): state.BitMaskCount += 1 state.BitMask<<=1 state.BitMask|=1 # Reset Decode Table (and drop recorded pixel lists) if previous data grows too large if (state.BitMaskCount > state.FormatFlag): state.resetTable() #SetupDecodeTable(DecodeTable,BitMask,DecodeTableIndex,BitMaskCount); # Return pixel data from top of stack state.StackTop -= 1 return state.Stack[state.StackTop] & 0xff
def readData(dlPath): fname = dlPath + '/darkland.cty' data = map(ord, open(fname).read()) dataLen = len(data) #print fname, dataLen, 'B' pos = 0 cnt = data[pos] #print cnt, bread(data[pos:pos+1]) pos += 1 cities = [] for i in xrange(0, cnt): c = OrderedDict() c['short_name'] = sread(data[pos:pos + 32]) pos += 32 c['name'] = sread(data[pos:pos + 32]) pos += 32 #city_data = (size 0x2e) dataPos = pos c['city_size'] = bread(data[pos:pos + 2]) pos += 2 #Size of the city. Ranges from 3 (small) to 8 (Koln). c['entry_coords'] = (bread(data[pos:pos + 2]), bread(data[pos + 2:pos + 4])) pos += 4 # City location on the map. c['exit_coords'] = (bread(data[pos:pos + 2]), bread(data[pos + 2:pos + 4])) pos += 4 # Party coordinates when leaving a city. # When you leave a city, you don't exit at the same point as you entered. The exit coordinates were (usually) selected so as not to place you in an untenable position (the ocean, trapped by a river loop, etc). dests = [] for i in xrange(0, 4): tgt = bread(data[pos:pos + 2]) pos += 2 if tgt != 0xffff: dests.append(tgt) c['dock_destinations'] = dests # Dock destination cities. # This contains the indices (in the cities array) of the destinations available via the city docks the docks. # 0xffff is used for "no destination". Inland cities have all 0xffffs. c['coast'] = bread(data[pos:pos + 2]) pos += 2 #if coastal, side of the river ???[Hamburg] TODO. # Values are: 0xffff (inland), 0 (north of the river), 1 (south of the river) # 0 and 1 cities are on or near tidal zones (swamps), and may be subject to flooding. c['unknown_cd_1'] = bread(data[pos:pos + 2]) pos += 2 # [constant: 4] c['pseudo_ordinal'] = bread(data[pos:pos + 2]) pos += 2 # At first glance, this looks like an ordinal offset running from 0 to 0x5b, but 0x18 is missing, and 0x3c repeats. # This value is probably not used. c['city_type'] = bread(data[pos:pos + 2]) pos += 2 # Type of city c['unknown_cd_2'] = bread(data[pos:pos + 2]) pos += 2 # 0, 1, 2, or 3. c['unknown_cd_3'] = bread(data[pos:pos + 2]) pos += 2 # [constant: 0] #city_contents = bread(data[pos:pos+2])# bitmask[16 bits] city_contents = (data[pos] << 8) | data[pos + 1] # bitmask[16 bits] pos += 2 #Buildings and locations in the city. # Bits are on iff there is one of that type of building. opts = ('has_kloster', 'has_slums', 'has_unknown1', 'has_cathedral', 'has_unknown2', 'has_no_fortress', 'has_town_hall', 'has_polit', 'has_constant1', 'has_constant2', 'has_constant3', 'has_constant4', 'has_docks', 'has_unknown3', 'has_pawnshop', 'has_university') for i, o in enumerate(opts): c[o] = 1 if city_contents & (1 << (15 - i)) else 0 c['bin_city_contents'] = bin(city_contents) c['unknown_cd_4'] = bread(data[pos:pos + 2]) pos += 2 # [constant: 0] c['qual_black'] = data[pos] pos += 1 #Quality of the blacksmith. # This, and the other nine qualities, all seem to work in the same way. # A zero value indicates that the city does not have that particular shop. # Non-zero values do not exactly equal the quality of the items available, but merely indicate relative qualities! For example, Nurnberg has a 0x31 (49) listed for the armory, but offers q37 (0x25) armor. However, if one city has a higher value than another, then that city's items will be of equal or greater quality. # The quality of the healer is not stored here, but is apparently random. (TODO: verify?) # TODO: comments about Quality of the alchemist, university, pharmacist being the seed thing. c['qual_merch'] = data[pos] pos += 1 #Quality of the merchant. c['qual_sword'] = data[pos] pos += 1 #Quality of the swordsmith. c['qual_armor'] = data[pos] pos += 1 #Quality of the armorer. c['qual_unk1'] = data[pos] pos += 1 c['qual_bow'] = data[pos] pos += 1 #Quality of the bowyer. c['qual_tink'] = data[pos] pos += 1 #Quality of the tinker. c['qual_unk2'] = data[pos] pos += 1 c['qual_cloth'] = data[pos] pos += 1 # Quality of the clothing merchant. c['qual_unk3'] = data[pos] pos += 1 # [constant: 0] c['unknown_cd_5'] = data[pos] pos += 1 # unknown byte # Since the following byte is 0 or 1, this and that might actually be a single word value. c['unknown_cd_6'] = data[pos] pos += 1 # unknown byte # Either zero or one (only a couple of ones). c['unknown_cd_5-6'] = bread(data[pos - 2:pos]) # word c['leader_name'] = sread(data[pos:pos + 32]) pos += 32 c['ruler_name'] = sread(data[pos:pos + 32]) pos += 32 c['unknown'] = sread(data[pos:pos + 32]) pos += 32 #TODO: is this non-empty ever? (ditto for other two unknowns) c['polit_name'] = sread(data[pos:pos + 32]) pos += 32 #Name of the political center or town square. #TODO: describe what empty values look like c['town_hall_name'] = sread(data[pos:pos + 32]) pos += 32 c['fortress_name'] = sread(data[pos:pos + 32]) pos += 32 #Name of the city fortress or castle. c['cathedral_name'] = sread(data[pos:pos + 32]) pos += 32 c['church_name'] = sread(data[pos:pos + 32]) pos += 32 c['market_name'] = sread(data[pos:pos + 32]) pos += 32 #Name of the marketplace. c['unknown2'] = sread(data[pos:pos + 32]) pos += 32 #Often contains "Munzenplatz". Possibly this is "central square name". c['slum_name'] = sread(data[pos:pos + 32]) pos += 32 c['unknown3'] = sread(data[pos:pos + 32]) pos += 32 #Many places have "Zeughaus", which translates to "armoury"; others end in "-turm" (tower?) or "-tor" (gate?). Quite possible this is for one of the unused "rebellion" codepath. c['pawnshop_name'] = sread(data[pos:pos + 32]) pos += 32 #Name of the pawnshop. #All pawnshops are named the same; this is either 'Leifhaus' or is empty. c['kloster_name'] = sread(data[pos:pos + 32]) pos += 32 #Name of the kloster (church law and administration building). c['inn_name'] = sread(data[pos:pos + 32]) pos += 32 c['university_name'] = sread(data[pos:pos + 32]) pos += 32 cities.append(c) # read descriptions + generate some attrs fname = dlPath + '/darkland.dsc' data = map(ord, open(fname).read()) pos = 1 for c in cities: c['description'] = sread(data[pos:pos + 80]) pos += 80 c['str_dock_destinations'] = ', '.join( [cities[d]['short_name'] for d in c['dock_destinations']]) c['str_city_type'] = cityTypes[c['city_type']] return cities