예제 #1
0
def get_next_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]

    next_prop_num = 0
    if obj:
        if prop_num == 0:
            prop_start = get_prop_list_start(env, obj)
            next_prop_num = get_prop_num(env, prop_start)
        else:
            prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
            if prop_data_ptr == 0:
                msg = 'get_next_prop: passed nonexistant prop '
                msg += str(prop_num)+' for obj '+str(obj)+' ('+get_obj_str(env,obj)+')'
                print_prop_list(env, obj)
                err(msg)
            sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
            size = get_prop_size(env, sizenum_ptr)
            next_prop_num = get_prop_num(env, prop_data_ptr + size)
    set_var(env, opinfo.store_var, next_prop_num)

    if DBG:
        warn('    prop_num', prop_num)
        warn('    next_prop_num', next_prop_num)
        print_prop_list(env, obj)
예제 #2
0
def get_next_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]

    next_prop_num = 0
    if obj:
        if prop_num == 0:
            prop_start = get_prop_list_start(env, obj)
            next_prop_num = get_prop_num(env, prop_start)
        else:
            prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
            if prop_data_ptr == 0:
                msg = 'get_next_prop: passed nonexistant prop '
                msg += str(prop_num) + ' for obj ' + str(
                    obj) + ' (' + get_obj_str(env, obj) + ')'
                print_prop_list(env, obj)
                err(msg)
            sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
            size = get_prop_size(env, sizenum_ptr)
            next_prop_num = get_prop_num(env, prop_data_ptr + size)
    set_var(env, opinfo.store_var, next_prop_num)

    if DBG:
        warn('    prop_num', prop_num)
        warn('    next_prop_num', next_prop_num)
        print_prop_list(env, obj)
예제 #3
0
def fill_text_buffer(env, user_input, text_buffer):

    text_buf_len = env.mem[text_buffer]
    if text_buf_len < 2:
        err('read error: malformed text buffer')

    text_buf_ptr = text_buffer + 1

    # TODO: make sure I'm interpreting this right.
    # Finding in test suites that you should be able
    # to edit the prefilled text, despite spec seeming
    # to say that any new input goes after prefilled
    # input. Maybe that directive includes ^H's?
    if env.hdr.version >= 5:
        text_buf_ptr += 1

    max_len = text_buf_len-(text_buf_ptr-text_buffer)
    text_len = min(len(user_input), max_len)
    for i in range(text_len):
        env.write8(text_buf_ptr, user_input[i])
        text_buf_ptr += 1

    if env.hdr.version >= 5:
        env.write8(text_buffer + 1, text_buf_ptr-text_buffer-2)
    else:
        env.write8(text_buf_ptr, 0)
예제 #4
0
def put_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]
    val = opinfo.operands[2]

    prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
    if prop_data_ptr == 0:
        msg = 'illegal op: put_prop on nonexistant property'
        msg += ' - prop ' + str(prop_num)
        msg += ' not found on obj ' + str(obj) + ' (' + get_obj_str(env,
                                                                    obj) + ')'
        err(msg)

    sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
    size = get_prop_size(env, sizenum_ptr)
    if size == 2:
        env.write16(prop_data_ptr, val)
    elif size == 1:
        env.write8(prop_data_ptr, val & 0xff)
    else:
        msg = 'illegal op: put_prop on outsized prop (not 1-2 bytes)'
        msg += ' - prop ' + str(prop_num)
        msg += ' of obj ' + str(obj) + ' (' + get_obj_str(obj) + ')'
        msg += ' (sized at ' + size + ' bytes)'
        err(msg)

    if DBG:
        warn('    obj', obj, '(', get_obj_str(env, obj), ')')
        warn('    prop_num', prop_num)
        warn('    val', val)
        print_prop_list(env, obj)
예제 #5
0
def fill_text_buffer(env, user_input, text_buffer):

    text_buf_len = env.mem[text_buffer]
    if text_buf_len < 2:
        err('read error: malformed text buffer')

    text_buf_ptr = text_buffer + 1

    # TODO: make sure I'm interpreting this right.
    # Finding in test suites that you should be able
    # to edit the prefilled text, despite spec seeming
    # to say that any new input goes after prefilled
    # input. Maybe that directive includes ^H's?
    if env.hdr.version >= 5:
        text_buf_ptr += 1

    max_len = text_buf_len-(text_buf_ptr-text_buffer)
    text_len = min(len(user_input), max_len)
    for i in range(text_len):
        env.write8(text_buf_ptr, user_input[i])
        text_buf_ptr += 1

    if env.hdr.version >= 5:
        env.write8(text_buffer + 1, text_buf_ptr-text_buffer-2)
    else:
        env.write8(text_buf_ptr, 0)
예제 #6
0
def get_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]

    prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
    is_default_prop = prop_data_ptr == 0
    if is_default_prop:
        base = env.hdr.obj_tab_base
        result = env.u16(base + 2 * (prop_num - 1))
    else:
        sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
        size = get_prop_size(env, sizenum_ptr)
        if size == 1:
            result = env.mem[prop_data_ptr]
        elif size == 2 or FORGIVING_GET_PROP:
            result = env.u16(prop_data_ptr)
        else:
            msg = 'illegal op: get_prop on outsized prop (not 1-2 bytes)'
            msg += ' - prop ' + str(prop_num)
            msg += ' of obj ' + str(obj) + ' (' + get_obj_str(env, obj) + ')'
            msg += ' (sized at ' + str(size) + ' bytes)'
            print_prop_list(env, obj)
            err(msg)

    set_var(env, opinfo.store_var, result)

    if DBG:
        warn('    obj', obj, '(', get_obj_str(env, obj), ')')
        warn('    prop_num', prop_num)
        warn('    result', result)
        warn('    is_default_prop', is_default_prop)
        print_prop_list(env, obj)
예제 #7
0
def get_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]

    prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
    is_default_prop = prop_data_ptr == 0
    if is_default_prop:
        base = env.hdr.obj_tab_base
        result = env.u16(base + 2*(prop_num-1))
    else:
        sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
        size = get_prop_size(env, sizenum_ptr)
        if size == 1:
            result = env.mem[prop_data_ptr]
        elif size == 2 or FORGIVING_GET_PROP:
            result = env.u16(prop_data_ptr)
        else:
            msg = 'illegal op: get_prop on outsized prop (not 1-2 bytes)'
            msg += ' - prop '+str(prop_num)
            msg += ' of obj '+str(obj)+' ('+get_obj_str(env, obj)+')'
            msg += ' (sized at '+str(size)+' bytes)'
            print_prop_list(env, obj)
            err(msg)

    set_var(env, opinfo.store_var, result)

    if DBG:
        warn('    obj', obj,'(',get_obj_str(env,obj),')')
        warn('    prop_num', prop_num)
        warn('    result', result)
        warn('    is_default_prop', is_default_prop)
        print_prop_list(env, obj)
예제 #8
0
def put_prop(env, opinfo):
    obj = opinfo.operands[0]
    prop_num = opinfo.operands[1]
    val = opinfo.operands[2]

    prop_data_ptr = get_prop_data_ptr_from_obj(env, obj, prop_num)
    if prop_data_ptr == 0:
        msg = 'illegal op: put_prop on nonexistant property'
        msg += ' - prop '+str(prop_num)
        msg += ' not found on obj '+str(obj)+' ('+get_obj_str(env, obj)+')' 
        err(msg)

    sizenum_ptr = get_sizenum_ptr(env, prop_data_ptr)
    size = get_prop_size(env, sizenum_ptr)
    if size == 2:
        env.write16(prop_data_ptr, val)
    elif size == 1:
        env.write8(prop_data_ptr, val & 0xff)
    else:
        msg = 'illegal op: put_prop on outsized prop (not 1-2 bytes)'
        msg += ' - prop '+str(prop_num)
        msg += ' of obj '+str(obj)+' ('+get_obj_str(obj)+')'
        msg += ' (sized at '+size+' bytes)'
        err(msg)

    if DBG:
        warn('    obj', obj,'(',get_obj_str(env,obj),')')
        warn('    prop_num', prop_num)
        warn('    val', val)
        print_prop_list(env, obj)
예제 #9
0
def set_window(env, opinfo):
    env.screen.finish_wrapping()

    env.current_window = opinfo.operands[0]
    if env.current_window == 1:
        env.cursor[1] = (0,0)

    if env.current_window not in [0,1]:
        err('set_window: requested unknown window:', env.current_window)
예제 #10
0
def set_window(env, opinfo):
    env.screen.finish_wrapping()

    env.current_window = opinfo.operands[0]
    if env.current_window == 1:
        env.cursor[1] = (0, 0)

    if env.current_window not in [0, 1]:
        err('set_window: requested unknown window:', env.current_window)
예제 #11
0
파일: main.py 프로젝트: jan-g/xyfny
def make_env(file):
    with open(file, 'rb') as f:
        mem = f.read()
    if blorb.is_blorb(mem):
        mem = blorb.get_code(mem)
    env = Env(mem, None)

    if env.hdr.version not in [1,2,3,4,5,7,8]:
        err('unsupported z-machine version: '+str(env.hdr.version))

    return env
예제 #12
0
def read_char(env, opinfo):
    # NOTE: operands[0] must be 1, but I ran into a z5 that passed no operands
    # (strictz) so let's just ignore the first operand instead...
    if len(opinfo.operands) > 1:
        if len(opinfo.operands) != 3:
            err('read_char: num operands must be 1 or 3')
        if opinfo.operands[1] != 0 or opinfo.operands[2] != 0:
            if DBG:
                warn('read_char: interrupts not impl\'d yet!')
    c = ascii_to_zscii(env.screen.getch_or_esc_seq())[0]
    set_var(env, opinfo.store_var, c)
예제 #13
0
파일: blorb.py 프로젝트: jan-g/xyfny
def get_code(filedata):
    formChunk = FormChunk.from_chunk(Chunk.from_data(filedata))
    for chunk in formChunk.chunks:
        if chunk.name == b'RIdx':
            rIdxChunk = RIdxChunk.from_chunk(chunk)
            for r in rIdxChunk.resources:
                if r.usage == b'Exec' and r.number == 0:
                    codeChunk = Chunk.from_data(filedata[r.start:])
                    if codeChunk.name == b'ZCOD':
                        return codeChunk.data
    err('no ZCOD chunk found in blorb resource index')
예제 #14
0
def read_char(env, opinfo):
    # NOTE: operands[0] must be 1, but I ran into a z5 that passed no operands
    # (strictz) so let's just ignore the first operand instead...
    if len(opinfo.operands) > 1:
        if len(opinfo.operands) != 3:
            err('read_char: num operands must be 1 or 3')
        if opinfo.operands[1] != 0 or opinfo.operands[2] != 0:
            if DBG:
                warn('read_char: interrupts not impl\'d yet!')
    c = ascii_to_zscii(env.screen.getch_or_esc_seq())[0]
    set_var(env, opinfo.store_var, c)
예제 #15
0
파일: __main__.py 프로젝트: tj-murphy/xyppy
def main():

    if len(sys.argv) < 2:
        # I prefer a non-auto-gen'd zero arg screen
        if sys.argv[0].endswith('__main__.py'):
            name = '-m xyppy'
        else:
            name = sys.argv[0]
        print('usage examples:')
        print('    python ' + name + ' STORY_FILE.z5')
        print('    python ' + name + ' http://example.com/STORY_FILE.z5')
        print()
        print('    for more, try --help')
        sys.exit(1)

    parser = argparse.ArgumentParser()
    parser.add_argument('--no-slow-scroll',
                        action='store_true',
                        help='remove the artificial scrolling delay')
    parser.add_argument('STORY_FILE_OR_URL')
    args = parser.parse_args()

    url = args.STORY_FILE_OR_URL
    #This block of logic will create a save folder if needed, and switch into it.
    #This allows us to localize saves to where the game file is at.
    dir_of_game = "/".join(url.split("/")[:-1])
    os.chdir(dir_of_game)
    if not os.path.exists("saves"):
        os.makedirs("saves")
    os.chdir("saves")

    if any(map(url.startswith, ['http://', 'https://', 'ftp://'])):
        f = urllib.request.urlopen(url)
        mem = f.read()
        f.close()
    else:
        with open(url, 'rb') as f:
            mem = f.read()
    if blorb.is_blorb(mem):
        mem = blorb.get_code(mem)
    env = Env(mem, args)

    if env.hdr.version not in [1, 2, 3, 4, 5, 7, 8]:
        err('unsupported z-machine version: ' + str(env.hdr.version))

    term.init(env)
    env.screen.first_draw()
    ops.setup_opcodes(env)
    try:
        while True:
            step(env)
    except KeyboardInterrupt:
        pass
예제 #16
0
def set_colour(env, opinfo):
    fg_col = opinfo.operands[0]
    bg_col = opinfo.operands[1]
    if fg_col > 9 or bg_col > 9 or fg_col < 0 or bg_col < 0:
        err('set_color attempted illegal color')
    if fg_col == 1:
        fg_col = env.hdr.default_fg_color
    if fg_col != 0:
        env.fg_color = fg_col
    if bg_col == 1:
        bg_col = env.hdr.default_bg_color
    if bg_col != 0:
        env.bg_color = bg_col
예제 #17
0
def set_colour(env, opinfo):
    fg_col = opinfo.operands[0]
    bg_col = opinfo.operands[1]
    if fg_col > 9 or bg_col > 9 or fg_col < 0 or bg_col < 0:
        err('set_color attempted illegal color')
    if fg_col == 1:
        fg_col = env.hdr.default_fg_color
    if fg_col != 0:
        env.fg_color = fg_col
    if bg_col == 1:
        bg_col = env.hdr.default_bg_color
    if bg_col != 0:
        env.bg_color = bg_col
예제 #18
0
def get_text_buffer_as_str(env, text_buffer):

    text_buf_len = env.mem[text_buffer]
    if text_buf_len < 2:
        err('read error: malformed text buffer')

    text_buf_ptr = text_buffer + 1

    # does input exist?
    if env.hdr.version >= 5 and env.mem[text_buf_ptr]:
        input_len = env.mem[text_buf_ptr]
        text_buf_ptr += 1
        chars = env.mem[text_buf_ptr:text_buf_ptr+input_len]
        return ''.join(map(chr, chars))
    return ''
예제 #19
0
def get_text_buffer_as_str(env, text_buffer):

    text_buf_len = env.mem[text_buffer]
    if text_buf_len < 2:
        err('read error: malformed text buffer')

    text_buf_ptr = text_buffer + 1

    # does input exist?
    if env.hdr.version >= 5 and env.mem[text_buf_ptr]:
        input_len = env.mem[text_buf_ptr]
        text_buf_ptr += 1
        chars = env.mem[text_buf_ptr:text_buf_ptr+input_len]
        return ''.join(map(chr, chars))
    return ''
예제 #20
0
def main():

    if len(sys.argv) < 2:
        # I prefer a non-auto-gen'd zero arg screen
        if sys.argv[0].endswith('__main__.py'):
            name = '-m xyppy'
        else:
            name = sys.argv[0]
        print('usage examples:')
        print('    python ' + name + ' STORY_FILE.z5')
        print('    python ' + name + ' http://example.com/STORY_FILE.z5')
        print()
        print('    for more, try --help')
        sys.exit(1)

    parser = argparse.ArgumentParser()
    parser.add_argument('--no-slow-scroll',
                        action='store_true',
                        help='remove the artificial scrolling delay')
    parser.add_argument('STORY_FILE_OR_URL')
    args = parser.parse_args()

    url = args.STORY_FILE_OR_URL
    if any(map(url.startswith, ['http://', 'https://', 'ftp://'])):
        f = urllib.request.urlopen(url)
        mem = f.read()
        f.close()
    else:
        try:
            with open(url, 'rb') as f:
                mem = f.read()
        except IOError as e:
            err('could not load file:', e)
    if blorb.is_blorb(mem):
        mem = blorb.get_code(mem)
    env = Env(mem, args)

    if env.hdr.version not in [1, 2, 3, 4, 5, 7, 8]:
        err('unsupported z-machine version: ' + str(env.hdr.version))

    term.init(env)
    env.screen.first_draw()
    ops.setup_opcodes(env)
    try:
        while True:
            step(env)
    except KeyboardInterrupt:
        pass
예제 #21
0
def get_prop_size(env, sizenum_ptr):
    if env.hdr.version < 4:
        return (env.mem[sizenum_ptr] >> 5) + 1
    else:
        first_byte = env.mem[sizenum_ptr]
        if first_byte & 128:
            size_byte = env.mem[sizenum_ptr+1]
            if not (size_byte & 128):
                msg = 'malformed prop size byte: '+bin(size_byte)
                msg += ' - first_byte:'+bin(first_byte)
                msg += ' - sizenum_ptr:'+hex(sizenum_ptr)
                err(msg)
            return (size_byte & 63) or 64 # zero len == 64
        if first_byte & 64:
            return 2
        return 1
예제 #22
0
def handle_return(env, return_val):
    frame = env.callstack.pop()
    if frame.return_addr == 0:
        err('returned from unreturnable/nonexistant function!')
    if frame.return_val_loc != None:
        set_var(env, frame.return_val_loc, return_val)
    env.pc = frame.return_addr

    if DBG:
        warn('    helper: handle_return')
        warn('        return_val', return_val)
        if frame.return_val_loc:
            warn('        return_val_loc', get_var_name(frame.return_val_loc))
        else:
            warn('        return_val_loc None')
        warn('        return_addr', hex(frame.return_addr))
예제 #23
0
def handle_return(env, return_val):
    frame = env.callstack.pop()
    if frame.return_addr == 0:
        err('returned from unreturnable/nonexistant function!')
    if frame.return_val_loc != None:
        set_var(env, frame.return_val_loc, return_val)
    env.pc = frame.return_addr

    if DBG:
        warn('    helper: handle_return')
        warn('        return_val', return_val)
        if frame.return_val_loc:
            warn('        return_val_loc', get_var_name(frame.return_val_loc))
        else:
            warn('        return_val_loc None')
        warn('        return_addr', hex(frame.return_addr))
예제 #24
0
def split_window(env, opinfo):
    env.screen.finish_wrapping()

    old_height = env.top_window_height

    # an unfortunate hack, but makes Inform games look better,
    # as they intentionally don't fill up the entire status bar (so
    # this is me trying to keep the Trinity trick and those bars both
    # looking good). only doing it on 0 to 1-bar transitions,
    # because those sound like status bars being made, right?
    if opinfo.operands[0] == 1 and env.top_window_height == 0:
        env.screen.scroll_top_line_only()

    env.top_window_height = opinfo.operands[0]
    if env.top_window_height > env.hdr.screen_height_units:
        err('split_window: requested split bigger than screen:', env.top_window_height)
예제 #25
0
def get_prop_size(env, sizenum_ptr):
    if env.hdr.version < 4:
        return (env.mem[sizenum_ptr] >> 5) + 1
    else:
        first_byte = env.mem[sizenum_ptr]
        if first_byte & 128:
            size_byte = env.mem[sizenum_ptr+1]
            if not (size_byte & 128):
                msg = 'malformed prop size byte: '+bin(size_byte)
                msg += ' - first_byte:'+bin(first_byte)
                msg += ' - sizenum_ptr:'+hex(sizenum_ptr)
                err(msg)
            return (size_byte & 63) or 64 # zero len == 64
        if first_byte & 64:
            return 2
        return 1
예제 #26
0
def split_window(env, opinfo):
    env.screen.finish_wrapping()

    old_height = env.top_window_height

    # an unfortunate hack, but makes Inform games look better,
    # as they intentionally don't fill up the entire status bar (so
    # this is me trying to keep the Trinity trick and those bars both
    # looking good). only doing it on 0 to 1-bar transitions,
    # because those sound like status bars being made, right?
    if opinfo.operands[0] == 1 and env.top_window_height == 0:
        env.screen.scroll_top_line_only()

    env.top_window_height = opinfo.operands[0]
    if env.top_window_height > env.hdr.screen_height_units:
        err('split_window: requested split bigger than screen:',
            env.top_window_height)
예제 #27
0
def handle_read(env, text_buffer, parse_buffer, time=0, routine=0):

    if time != 0 or routine != 0:
        if DBG:
            err('interrupts requested but not impl\'d yet!')

    prefilled = get_text_buffer_as_str(env, text_buffer)
    user_input = ascii_to_zscii(env.screen.get_line_of_input(prompt='', prefilled=prefilled).lower())

    fill_text_buffer(env, user_input, text_buffer)

    if env.hdr.version < 5 or parse_buffer != 0:
        handle_parse(env, text_buffer, parse_buffer)

    # return ord('\r') as term char for now... 
    # TODO: the right thing 
    return ord('\r')
예제 #28
0
def parse_call_header(env, call_addr):
    num_locals = env.mem[call_addr]

    if num_locals > 15:
        err('calling a non-function (more than 15 local vars)')

    if env.hdr.version < 5:
        locals_ptr = call_addr + 1
        locals = []
        for i in range(num_locals):
            locals.append(env.u16(locals_ptr))
            locals_ptr += 2
        code_ptr = locals_ptr
    else:
        locals = [0] * num_locals
        code_ptr = call_addr + 1

    return locals, code_ptr
예제 #29
0
def parse_call_header(env, call_addr):
    num_locals = env.mem[call_addr]

    if num_locals > 15:
        err('calling a non-function (more than 15 local vars)')

    if env.hdr.version < 5:
        locals_ptr = call_addr + 1
        locals = []
        for i in range(num_locals):
            locals.append(env.u16(locals_ptr))
            locals_ptr += 2
        code_ptr = locals_ptr
    else:
        locals = [0] * num_locals
        code_ptr = call_addr + 1

    return locals, code_ptr
예제 #30
0
def handle_read(env, text_buffer, parse_buffer, time=0, routine=0):

    if time != 0 or routine != 0:
        if DBG:
            err('interrupts requested but not impl\'d yet!')

    prefilled = get_text_buffer_as_str(env, text_buffer)
    user_input = ascii_to_zscii(
        env.screen.get_line_of_input(prompt='', prefilled=prefilled).lower())

    fill_text_buffer(env, user_input, text_buffer)

    if env.hdr.version < 5 or parse_buffer != 0:
        handle_parse(env, text_buffer, parse_buffer)

    # return ord('\r') as term char for now...
    # TODO: the right thing
    return ord('\r')
예제 #31
0
def get_var(env, var_num, pop_stack=True):
    # if DBG:
    #     warn('    get_var(', get_var_name(var_num), ', pop_stack =', pop_stack, ')')

    if var_num == 0:
        frame = env.callstack[-1]
        if pop_stack:
            return frame.stack.pop()
        else:
            return frame.stack[-1]
    elif var_num < 16:
        frame = env.callstack[-1]
        return frame.locals[var_num - 1]
    elif var_num < 256:
        g_idx = var_num - 16
        g_base = env.hdr.global_var_base
        return env.u16(g_base + 2 * g_idx)
    else:
        err('illegal var num: ' + str(var_num))
예제 #32
0
def get_var(env, var_num, pop_stack=True):
    # if DBG:
    #     warn('    get_var(', get_var_name(var_num), ', pop_stack =', pop_stack, ')')

    if var_num == 0:
        frame = env.callstack[-1]
        if pop_stack:
            return frame.stack.pop()
        else:
            return frame.stack[-1]
    elif var_num < 16:
        frame = env.callstack[-1]
        return frame.locals[var_num - 1]
    elif var_num < 256:
        g_idx = var_num - 16
        g_base = env.hdr.global_var_base
        return env.u16(g_base + 2*g_idx)
    else:
        err('illegal var num: '+str(var_num))
예제 #33
0
def main():

    if len(sys.argv) < 2:
        # I prefer a non-auto-gen'd zero arg screen
        if sys.argv[0].endswith('__main__.py'):
            name = '-m xyppy'
        else:
            name = sys.argv[0]
        print('usage examples:')
        print('    python '+name+' STORY_FILE.z5')
        print('    python '+name+' http://example.com/STORY_FILE.z5')
        print()
        print('    for more, try --help')
        sys.exit(1)

    parser = argparse.ArgumentParser()
    parser.add_argument('--no-slow-scroll', action='store_true', help='remove the artificial scrolling delay')
    parser.add_argument('STORY_FILE_OR_URL')
    args = parser.parse_args()

    url = args.STORY_FILE_OR_URL
    if any(map(url.startswith, ['http://', 'https://', 'ftp://'])):
        f = urllib.request.urlopen(url)
        mem = f.read()
        f.close()
    else:
        with open(url, 'rb') as f:
            mem = f.read()
    if blorb.is_blorb(mem):
        mem = blorb.get_code(mem)
    env = Env(mem, args)

    if env.hdr.version not in [1,2,3,4,5,7,8]:
        err('unsupported z-machine version: '+str(env.hdr.version))

    term.init(env)
    env.screen.first_draw()
    ops.setup_opcodes(env)
    try:
        while True:
            step(env)
    except KeyboardInterrupt:
        pass
예제 #34
0
def set_var(env, var_num, result, push_stack=True):
    # if DBG:
    #     warn('    set_var(', get_var_name(var_num), ',', result, ', push_stack =', push_stack, ')')

    result &= 0xffff

    if var_num == 0:
        frame = env.callstack[-1]
        if push_stack:
            frame.stack.append(result)
        else:
            frame.stack[-1] = result
    elif var_num < 16:
        frame = env.callstack[-1]
        frame.locals[var_num - 1] = result
    elif var_num < 256:
        g_idx = var_num - 16
        g_base = env.hdr.global_var_base
        env.write16(g_base + 2 * g_idx, result)
    else:
        err('set_var: illegal var_num: ' + str(var_num))
예제 #35
0
def set_var(env, var_num, result, push_stack=True):
    # if DBG:
    #     warn('    set_var(', get_var_name(var_num), ',', result, ', push_stack =', push_stack, ')')

    result &= 0xffff

    if var_num == 0:
        frame = env.callstack[-1]
        if push_stack:
            frame.stack.append(result)
        else:
            frame.stack[-1] = result
    elif var_num < 16:
        frame = env.callstack[-1]
        frame.locals[var_num - 1] = result
    elif var_num < 256:
        g_idx = var_num - 16
        g_base = env.hdr.global_var_base
        env.write16(g_base + 2*g_idx, result)
    else:
        err('set_var: illegal var_num: '+str(var_num))
예제 #36
0
def getch_impl():
    # TODO: This windows impl keeps pipes/redirects from working. Need ReadFile for that,
    # with more complicated handling (personally, I'm just going to keep using unix/cygwin
    # for pipe-y debug stuff...)
    # TODO: Windows escape seqs via ReadConsoleInput, convert to VT100 seqs for more commonality.
    if is_windows:
        stdin_handle = ctypes.windll.kernel32.GetStdHandle(ctypes.c_ulong(-10))
        one_char_buf = ctypes.c_uint32()
        chars_read = ctypes.c_uint32()
        # NOTE: W version of this function == ERROR_NOACCESS after text color set in photopia!?
        result = ctypes.windll.kernel32.ReadConsoleA(
            stdin_handle, ctypes.byref(one_char_buf), 1,
            ctypes.byref(chars_read), 0)

        if result == 0 or chars_read.value != 1:
            last_err = ctypes.windll.kernel32.GetLastError()
            print('LAST ERR', last_err)
            err('failed to read console')

        return chr(one_char_buf.value)
    else:  #Unix
        return sys.stdin.read(1)
예제 #37
0
def output_stream(env, opinfo):
    stream = to_signed_word(opinfo.operands[0])
    if stream < 0:
        stream = abs(stream)
        if stream == 3:
            table_addr = env.memory_ostream_stack.pop()
            zscii_buffer = ascii_to_zscii(env.output_buffer[stream])
            buflen = len(zscii_buffer)
            env.write16(table_addr, buflen)
            for i in range(len(zscii_buffer)):
                env.write8(table_addr+2+i, zscii_buffer[i])
            env.output_buffer[stream] = ''
            if len(env.memory_ostream_stack) == 0:
                env.selected_ostreams.discard(stream)
        else:
            env.selected_ostreams.discard(stream)
    elif stream > 0:
        env.selected_ostreams.add(stream)
        if stream == 3:
            table_addr = opinfo.operands[1]
            if len(env.memory_ostream_stack) == 16:
                err('too many memory-based ostreams (>16)')
            env.memory_ostream_stack.append(table_addr)
예제 #38
0
def output_stream(env, opinfo):
    stream = to_signed_word(opinfo.operands[0])
    if stream < 0:
        stream = abs(stream)
        if stream == 3:
            table_addr = env.memory_ostream_stack.pop()
            zscii_buffer = ascii_to_zscii(env.output_buffer[stream])
            buflen = len(zscii_buffer)
            env.write16(table_addr, buflen)
            for i in range(len(zscii_buffer)):
                env.write8(table_addr + 2 + i, zscii_buffer[i])
            env.output_buffer[stream] = ''
            if len(env.memory_ostream_stack) == 0:
                env.selected_ostreams.discard(stream)
        else:
            env.selected_ostreams.discard(stream)
    elif stream > 0:
        env.selected_ostreams.add(stream)
        if stream == 3:
            table_addr = opinfo.operands[1]
            if len(env.memory_ostream_stack) == 16:
                err('too many memory-based ostreams (>16)')
            env.memory_ostream_stack.append(table_addr)
예제 #39
0
def getch_impl():
    # TODO: This windows impl keeps pipes/redirects from working. Need ReadFile for that,
    # with more complicated handling (personally, I'm just going to keep using unix/cygwin
    # for pipe-y debug stuff...)
    # TODO: Windows escape seqs via ReadConsoleInput, convert to VT100 seqs for more commonality.
    if is_windows:
        stdin_handle = ctypes.windll.kernel32.GetStdHandle(ctypes.c_ulong(-10))
        one_char_buf = ctypes.c_uint32()
        chars_read = ctypes.c_uint32()
        # NOTE: W version of this function == ERROR_NOACCESS after text color set in photopia!?
        result = ctypes.windll.kernel32.ReadConsoleA(stdin_handle,
                                                     ctypes.byref(one_char_buf),
                                                     1,
                                                     ctypes.byref(chars_read),
                                                     0)

        if result == 0 or chars_read.value != 1:
            last_err = ctypes.windll.kernel32.GetLastError()
            print('LAST ERR', last_err)
            err('failed to read console')

        return chr(one_char_buf.value)
    else: #Unix
        return sys.stdin.read(1)
예제 #40
0
 def check_dyn_mem(self, i):
     if i >= self.hdr.static_mem_base:
         err('game tried to write in static mem: ' + str(i))
     elif i <= 0x36 and i != 0x10:
         err('game tried to write in non-dyn header bytes: ' + str(i))
예제 #41
0
 def check_dyn_mem(self, i):
     if i >= self.hdr.static_mem_base:
         err('game tried to write in static mem: '+str(i))
     elif i <= 0x36 and i != 0x10:
         err('game tried to write in non-dyn header bytes: '+str(i))
예제 #42
0
def decode(env, pc):

    opcode = env.mem[pc]
    form = get_opcode_form(env, opcode)
    count = get_operand_count(opcode, form)

    if form == ExtForm:
        opcode = env.mem[pc+1]

    if form == ShortForm:
        szbyte = (opcode >> 4) & 3
        szbyte = (szbyte << 6) | 0x3f
        operand_ptr = pc+1
        sizes = get_operand_sizes(szbyte)
    elif form == VarForm:
        szbyte = env.mem[pc+1]
        operand_ptr = pc+2
        sizes = get_operand_sizes(szbyte)
        # handle call_vn2/vs2's extra szbyte
        if opcode in (236, 250):
            szbyte2 = env.mem[pc+2]
            sizes += get_operand_sizes(szbyte2)
            operand_ptr = pc+3
    elif form == ExtForm:
        szbyte = env.mem[pc+2]
        operand_ptr = pc+3
        sizes = get_operand_sizes(szbyte)
    elif form == LongForm:
        operand_ptr = pc+1
        sizes = []
        for offset in (6,5):
            if (opcode >> offset) & 1:
                sizes.append(VarSize)
            else:
                sizes.append(ByteSize)
    else:
        err('unknown opform specified: ' + str(form))

    operands = []
    var_op_info = []
    for i in range(len(sizes)):
        size = sizes[i]
        if size == WordSize:
            operands.append(env.u16(operand_ptr))
            operand_ptr += 2
        elif size == ByteSize:
            operands.append(env.mem[operand_ptr])
            operand_ptr += 1
        elif size == VarSize:
            operands.append(None) #this is fixedup after every load from icache
            var_num = env.mem[operand_ptr]
            var_op_info.append( (i,var_num) )
            operand_ptr += 1
        else:
            err('unknown operand size specified: ' + str(size))

    if form == ExtForm:
        dispatch = ops.ext_dispatch
        has_store_var = ops.ext_has_store_var
        has_branch_var = ops.ext_has_branch_var
    else:
        dispatch = ops.dispatch
        has_store_var = ops.has_store_var
        has_branch_var = ops.has_branch_var

    opinfo = OpInfo(operands, var_op_info)

    opinfo.opcode = opcode
    opinfo.is_extended = form == ExtForm

    if has_store_var[opcode]:
        opinfo.store_var = env.mem[operand_ptr]
        opinfo.last_pc_store_var = operand_ptr # to make quetzal saves easier
        operand_ptr += 1

    if has_branch_var[opcode]: # std:4.7
        branch_info = env.mem[operand_ptr]
        opinfo.last_pc_branch_var = operand_ptr # to make quetzal saves easier
        operand_ptr += 1
        opinfo.branch_on = (branch_info & 128) == 128
        if branch_info & 64:
            opinfo.branch_offset = branch_info & 0x3f
        else:
            branch_offset = branch_info & 0x3f
            branch_offset <<= 8
            branch_offset |= env.mem[operand_ptr]
            operand_ptr += 1
            # sign extend 14b # to 16b
            if branch_offset & 0x2000:
                branch_offset |= 0xc000
            opinfo.branch_offset = to_signed_word(branch_offset)

    # handle print_ and print_ret's string operand
    if form != ExtForm and opcode in (178, 179):
        while True:
            word = env.u16(operand_ptr)
            operand_ptr += 2
            operands.append(word)
            if word & 0x8000:
                break

    # After all that, operand_ptr should point to the next opcode
    next_pc = operand_ptr

    if DBG:
        def hex_out(bytes):
            s = ''
            for b in bytes:
                s += hex(b) + ' '
            return s
        op_hex = hex_out(env.mem[pc:next_pc])

        warn('decode: pc', hex(pc))
        warn('      opcode', opcode)
        warn('      form', form)
        warn('      count', count)
        if opinfo.store_var:
            warn('      store_var', ops.get_var_name(opinfo.store_var))
        warn('      sizes', sizes)
        warn('      operands', opinfo.operands)
        warn('      next_pc', hex(next_pc))
        #warn('      bytes', op_hex)

    return dispatch[opcode], opinfo, next_pc
예제 #43
0
def handle_parse(env, text_buffer, parse_buffer, dict_base=0, skip_unknown_words=0):

    used_tbuf_len = get_used_tbuf_len(env, text_buffer)
    parse_buf_len = env.mem[parse_buffer]
    if parse_buf_len < 1:
        err('read error: malformed parse buffer')

    word_separators = []
    if dict_base == 0:
        dict_base = env.hdr.dict_base
    num_word_seps = env.mem[dict_base]
    for i in range(num_word_seps):
        word_separators.append(env.mem[dict_base+1+i])

    word = []
    words = []
    word_locs = []
    word_len = 0
    word_lens = []
    scan_ptr = get_text_scan_ptr(env, text_buffer)
    for i in range(used_tbuf_len):

        c = env.mem[scan_ptr]

        if c == ord(' '):
            if word:
                word_lens.append(word_len)
                word_len = 0
                words.append(word)
                word = []
            scan_ptr += 1

        elif c in word_separators:
            if word:
                word_lens.append(word_len)
                word_len = 0
                words.append(word)
                word = []
            word_locs.append(scan_ptr-text_buffer)
            word_lens.append(1)
            words.append([c])
            scan_ptr += 1

        else:
            if not word:
                word_locs.append(scan_ptr-text_buffer)
            word.append(c)
            word_len += 1
            scan_ptr += 1

    if word:
        word_lens.append(word_len)
        words.append(word)

    words = clip_word_list(env, words)

    # limit to parse_buf_len (which is num words)
    words = words[:parse_buf_len]
    word_locs = word_locs[:parse_buf_len]
    word_lens = word_lens[:parse_buf_len]

    # NOTE: This could be a binary search for read() opcodes,
    # but dictionaries for tokenize() can be unsorted. So maybe
    # just always do a linear search if speed is never an issue
    # these days?

    dict_base = env.hdr.dict_base
    num_word_seps = env.mem[dict_base]

    entry_length = env.mem[dict_base+1+num_word_seps]
    num_entries = env.u16(dict_base+1+num_word_seps+1)
    # this can be negative to signify dictionary is unsorted
    num_entries = abs(num_entries)
    entries_start = dict_base+1+num_word_seps+1+2

    env.write8(parse_buffer+1, len(words))
    parse_ptr = parse_buffer+2
    for word,wloc,wlen in zip(words, word_locs, word_lens):

        wordstr = ''.join(map(chr, word))
        packed_word = make_dict_string(env, wordstr)

        dict_addr = 0
        for i in range(num_entries):
            entry_addr = entries_start+i*entry_length
            if match_dict_entry(env, entry_addr, packed_word):
                dict_addr = entry_addr
                break
        if dict_addr != 0 or skip_unknown_words == 0:
            env.write16(parse_ptr, dict_addr)
            env.write8(parse_ptr+2, wlen)
            env.write8(parse_ptr+3, wloc)
        parse_ptr += 4
예제 #44
0
def decode(env, pc):

    opcode = env.mem[pc]
    form = get_opcode_form(env, opcode)
    count = get_operand_count(opcode, form)

    if form == ExtForm:
        opcode = env.mem[pc + 1]

    if form == ShortForm:
        szbyte = (opcode >> 4) & 3
        szbyte = (szbyte << 6) | 0x3f
        operand_ptr = pc + 1
        sizes = get_operand_sizes(szbyte)
    elif form == VarForm:
        szbyte = env.mem[pc + 1]
        operand_ptr = pc + 2
        sizes = get_operand_sizes(szbyte)
        # handle call_vn2/vs2's extra szbyte
        if opcode in (236, 250):
            szbyte2 = env.mem[pc + 2]
            sizes += get_operand_sizes(szbyte2)
            operand_ptr = pc + 3
    elif form == ExtForm:
        szbyte = env.mem[pc + 2]
        operand_ptr = pc + 3
        sizes = get_operand_sizes(szbyte)
    elif form == LongForm:
        operand_ptr = pc + 1
        sizes = []
        for offset in (6, 5):
            if (opcode >> offset) & 1:
                sizes.append(VarSize)
            else:
                sizes.append(ByteSize)
    else:
        err('unknown opform specified: ' + str(form))

    operands = []
    var_op_info = []
    for i in range(len(sizes)):
        size = sizes[i]
        if size == WordSize:
            operands.append(env.u16(operand_ptr))
            operand_ptr += 2
        elif size == ByteSize:
            operands.append(env.mem[operand_ptr])
            operand_ptr += 1
        elif size == VarSize:
            operands.append(
                None)  #this is fixedup after every load from icache
            var_num = env.mem[operand_ptr]
            var_op_info.append((i, var_num))
            operand_ptr += 1
        else:
            err('unknown operand size specified: ' + str(size))

    if form == ExtForm:
        dispatch = ops.ext_dispatch
        has_store_var = ops.ext_has_store_var
        has_branch_var = ops.ext_has_branch_var
    else:
        dispatch = ops.dispatch
        has_store_var = ops.has_store_var
        has_branch_var = ops.has_branch_var

    opinfo = OpInfo(operands, var_op_info)

    opinfo.opcode = opcode
    opinfo.is_extended = form == ExtForm

    if has_store_var[opcode]:
        opinfo.store_var = env.mem[operand_ptr]
        opinfo.last_pc_store_var = operand_ptr  # to make quetzal saves easier
        operand_ptr += 1

    if has_branch_var[opcode]:  # std:4.7
        branch_info = env.mem[operand_ptr]
        opinfo.last_pc_branch_var = operand_ptr  # to make quetzal saves easier
        operand_ptr += 1
        opinfo.branch_on = (branch_info & 128) == 128
        if branch_info & 64:
            opinfo.branch_offset = branch_info & 0x3f
        else:
            branch_offset = branch_info & 0x3f
            branch_offset <<= 8
            branch_offset |= env.mem[operand_ptr]
            operand_ptr += 1
            # sign extend 14b # to 16b
            if branch_offset & 0x2000:
                branch_offset |= 0xc000
            opinfo.branch_offset = to_signed_word(branch_offset)

    # handle print_ and print_ret's string operand
    if form != ExtForm and opcode in (178, 179):
        while True:
            word = env.u16(operand_ptr)
            operand_ptr += 2
            operands.append(word)
            if word & 0x8000:
                break

    # After all that, operand_ptr should point to the next opcode
    next_pc = operand_ptr

    if DBG:

        def hex_out(bytes):
            s = ''
            for b in bytes:
                s += hex(b) + ' '
            return s

        op_hex = hex_out(env.mem[pc:next_pc])

        warn('decode: pc', hex(pc))
        warn('      opcode', opcode)
        warn('      form', form)
        warn('      count', count)
        if opinfo.store_var:
            warn('      store_var', ops.get_var_name(opinfo.store_var))
        warn('      sizes', sizes)
        warn('      operands', opinfo.operands)
        warn('      next_pc', hex(next_pc))
        #warn('      bytes', op_hex)

    return dispatch[opcode], opinfo, next_pc
예제 #45
0
def handle_parse(env, text_buffer, parse_buffer, dict_base=0, skip_unknown_words=0):

    used_tbuf_len = get_used_tbuf_len(env, text_buffer)
    parse_buf_len = env.mem[parse_buffer]
    if parse_buf_len < 1:
        err('read error: malformed parse buffer')

    word_separators = []
    if dict_base == 0:
        dict_base = env.hdr.dict_base
    num_word_seps = env.mem[dict_base]
    for i in range(num_word_seps):
        word_separators.append(env.mem[dict_base+1+i])

    word = []
    words = []
    word_locs = []
    word_len = 0
    word_lens = []
    scan_ptr = get_text_scan_ptr(env, text_buffer)
    for i in range(used_tbuf_len):

        c = env.mem[scan_ptr]

        if c == ord(' '):
            if word:
                word_lens.append(word_len)
                word_len = 0
                words.append(word)
                word = []
            scan_ptr += 1

        elif c in word_separators:
            if word:
                word_lens.append(word_len)
                word_len = 0
                words.append(word)
                word = []
            word_locs.append(scan_ptr-text_buffer)
            word_lens.append(1)
            words.append([c])
            scan_ptr += 1

        else:
            if not word:
                word_locs.append(scan_ptr-text_buffer)
            word.append(c)
            word_len += 1
            scan_ptr += 1

    if word:
        word_lens.append(word_len)
        words.append(word)

    words = clip_word_list(env, words)

    # limit to parse_buf_len (which is num words)
    words = words[:parse_buf_len]
    word_locs = word_locs[:parse_buf_len]
    word_lens = word_lens[:parse_buf_len]

    # NOTE: This could be a binary search for read() opcodes,
    # but dictionaries for tokenize() can be unsorted. So maybe
    # just always do a linear search if speed is never an issue
    # these days?

    dict_base = env.hdr.dict_base
    num_word_seps = env.mem[dict_base]

    entry_length = env.mem[dict_base+1+num_word_seps]
    num_entries = env.u16(dict_base+1+num_word_seps+1)
    # this can be negative to signify dictionary is unsorted
    num_entries = abs(num_entries)
    entries_start = dict_base+1+num_word_seps+1+2

    env.write8(parse_buffer+1, len(words))
    parse_ptr = parse_buffer+2
    for word,wloc,wlen in zip(words, word_locs, word_lens):

        wordstr = ''.join(map(chr, word))
        packed_word = make_dict_string(env, wordstr)

        dict_addr = 0
        for i in range(num_entries):
            entry_addr = entries_start+i*entry_length
            if match_dict_entry(env, entry_addr, packed_word):
                dict_addr = entry_addr
                break
        if dict_addr != 0 or skip_unknown_words == 0:
            env.write16(parse_ptr, dict_addr)
            env.write8(parse_ptr+2, wlen)
            env.write8(parse_ptr+3, wloc)
        parse_ptr += 4