Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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()
Exemple #10
0
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
Exemple #11
0
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
Exemple #12
0
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