def __init__(self, fhandle, filetype, number, name, mode, seg, offset, length): """ Initialise program file object and write header. """ devices.RawFile.__init__(self, fhandle, filetype, mode) self.number = number # don't lock binary files self.lock = b'' self.access = b'RW' self.seg, self.offset, self.length = 0, 0, 0 if self.mode == b'O': self.write(devices.type_to_magic[filetype]) if self.filetype == b'M': self.write( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(seg)) + vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(offset)) + vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(length))) self.seg, self.offset, self.length = seg, offset, length else: # drop magic byte self.read_raw(1) if self.filetype == b'M': self.seg = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(self.read(2))) self.offset = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(self.read(2))) # size gets ignored: even the \x1a at the end is read self.length = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(self.read(2)))
def record_to_sprite_size_ega(self, byte_array): """ Read 4-byte record of sprite size in EGA modes. """ dx = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(byte_array[0:2])) dy = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(byte_array[2:4])) return dx, dy
def record_to_sprite_size(self, byte_array): """ Read 4-byte record of sprite size. """ dx = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(byte_array[0:2])) / self.bitsperpixel dy = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(byte_array[2:4])) return dx, dy
def parse_jumpnum(ins, allow_empty=False, err=error.STX): """ Parses a line number pointer as in GOTO, GOSUB, LIST, RENUM, EDIT, etc. """ if skip_white_read_if(ins, (tk.T_UINT,)): return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(ins.read(2))) else: if allow_empty: return -1 # Syntax error raise error.RunError(err)
def show_program(): """ Write a marked-up hex dump of the program to the log. """ code = state.basic_state.bytecode.getvalue() offset_val, p = 0, 0 for key in sorted(state.basic_state.line_numbers.keys())[1:]: offset, linum = code[p+1:p+3], code[p+3:p+5] last_offset = offset_val offset_val = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(offset)) - program.program_memory_start linum_val = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(linum)) logging.debug( (code[p:p+1].encode('hex') + ' ' + offset.encode('hex') + ' (+%03d) ' + code[p+3:p+5].encode('hex') + ' [%05d] ' + code[p+5:state.basic_state.line_numbers[key]].encode('hex')), offset_val - last_offset, linum_val ) p = state.basic_state.line_numbers[key] logging.debug(code[p:p+1].encode('hex') + ' ' + code[p+1:p+3].encode('hex') + ' (ENDS) ' + code[p+3:p+5].encode('hex') + ' ' + code[p+5:].encode('hex'))
def parse_jumpnum(ins, allow_empty=False, err=error.STX): """ Parses a line number pointer as in GOTO, GOSUB, LIST, RENUM, EDIT, etc. """ if skip_white_read_if(ins, (tk.T_UINT, )): return vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(ins.read(2))) else: if allow_empty: return -1 # Syntax error raise error.RunError(err)
def number_inc_gt(typechar, loopvar, stop, step, sgn): """ Increase number and check if it exceeds a limit. """ if sgn == 0: return False if typechar in ('#', '!'): fp_left = fp.from_bytes(loopvar).iadd(step) loopvar[:] = fp_left.to_bytes() return fp_left.gt(stop) if sgn > 0 else stop.gt(fp_left) else: int_left = vartypes.integer_to_int_signed(vartypes.bytes_to_integer(loopvar)) + step loopvar[:] = vartypes.integer_to_bytes(vartypes.int_to_integer_signed(int_left)) return int_left > stop if sgn > 0 else stop > int_left
def parse_line_number(ins): """ Parse line number and leave pointer at first char of line. """ # if end of program or truncated, leave pointer at start of line number C0 DE or 00 00 off = ins.read(2) if off == '\0\0' or len(off) < 2: ins.seek(-len(off), 1) return -1 off = ins.read(2) if len(off) < 2: ins.seek(-len(off) - 2, 1) return -1 else: return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(off))
def parse_line_number(ins): """ Parse line number and leave pointer at first char of line. """ # if end of program or truncated, leave pointer at start of line number C0 DE or 00 00 off = ins.read(2) if off == '\0\0' or len(off) < 2: ins.seek(-len(off), 1) return -1 off = ins.read(2) if len(off) < 2: ins.seek(-len(off)-2, 1) return -1 else: return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(off))
def number_inc_gt(typechar, loopvar, stop, step, sgn): """ Increase number and check if it exceeds a limit. """ if sgn == 0: return False if typechar in ('#', '!'): fp_left = fp.from_bytes(loopvar).iadd(step) loopvar[:] = fp_left.to_bytes() return fp_left.gt(stop) if sgn > 0 else stop.gt(fp_left) else: int_left = vartypes.integer_to_int_signed( vartypes.bytes_to_integer(loopvar)) + step loopvar[:] = vartypes.integer_to_bytes( vartypes.int_to_integer_signed(int_left)) return int_left > stop if sgn > 0 else stop > int_left
def __init__(self, fhandle, filetype, number, name, mode, seg, offset, length): """ Initialise program file object and write header. """ devices.RawFile.__init__(self, fhandle, filetype, mode) self.number = number # don't lock binary files self.lock = b'' self.access = b'RW' self.seg, self.offset, self.length = 0, 0, 0 if self.mode == b'O': self.write(devices.type_to_magic[filetype]) if self.filetype == b'M': self.write(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(seg)) + vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(offset)) + vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(length))) self.seg, self.offset, self.length = seg, offset, length else: # drop magic byte self.read_raw(1) if self.filetype == b'M': self.seg = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(self.read(2))) self.offset = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(self.read(2))) # size gets ignored: even the \x1a at the end is read self.length = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(self.read(2)))
def update_line_dict(pos, afterpos, length, deleteable, beyond): """ Update line number dictionary after deleting lines. """ # subtract length of line we replaced length -= afterpos - pos addr = program_memory_start + afterpos state.basic_state.bytecode.seek(afterpos + length + 1) # pass \x00 while True: next_addr = state.basic_state.bytecode.read(2) if len(next_addr) < 2 or next_addr == '\0\0': break next_addr = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(next_addr)) state.basic_state.bytecode.seek(-2, 1) state.basic_state.bytecode.write(str(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(next_addr + length)))) state.basic_state.bytecode.read(next_addr - addr - 2) addr = next_addr # update line number dict for key in deleteable: del state.basic_state.line_numbers[key] for key in beyond: state.basic_state.line_numbers[key] += length
def get_value_for_varptrstr(varptrstr): """ Get a value given a VARPTR$ representation. """ if len(varptrstr) < 3: raise error.RunError(error.IFC) varptrstr = bytearray(varptrstr) varptr = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(varptrstr[1:3])) for name, data in state.basic_state.var_memory.iteritems(): if data[1] == varptr: return var.get_scalar(name) # no scalar found, try arrays found_addr = -1 found_name = None for name, data in state.basic_state.array_memory.iteritems(): addr = state.basic_state.var_current + data[1] if addr > found_addr and addr <= varptr: found_addr = addr found_name = name if found_name is None: raise error.RunError(error.IFC) _, lst, _ = state.basic_state.arrays[name] offset = varptr - found_addr return (name[-1], lst[offset:offset+var.var_size_bytes(name)])
def get_value_for_varptrstr(varptrstr): """ Get a value given a VARPTR$ representation. """ if len(varptrstr) < 3: raise error.RunError(error.IFC) varptrstr = bytearray(varptrstr) varptr = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(varptrstr[1:3])) for name, data in state.basic_state.var_memory.iteritems(): if data[1] == varptr: return var.get_scalar(name) # no scalar found, try arrays found_addr = -1 found_name = None for name, data in state.basic_state.array_memory.iteritems(): addr = state.basic_state.var_current + data[1] if addr > found_addr and addr <= varptr: found_addr = addr found_name = name if found_name is None: raise error.RunError(error.IFC) _, lst, _ = state.basic_state.arrays[name] offset = varptr - found_addr return (name[-1], lst[offset:offset + var.var_size_bytes(name)])
def randomize(val): """ Reseed the random number generator. """ # RANDOMIZE converts to int in a non-standard way - looking at the first two bytes in the internal representation # on a program line, if a number outside the signed int range (or -32768) is entered, # the number is stored as a MBF double or float. Randomize then: # - ignores the first 4 bytes (if it's a double) # - reads the next two # - xors them with the final two (most signifant including sign bit, and exponent) # and interprets them as a signed int # e.g. 1# = /x00/x00/x00/x00 /x00/x00/x00/x81 gets read as /x00/x00 ^ /x00/x81 = /x00/x81 -> 0x10000-0x8100 = -32512 (sign bit set) # 0.25# = /x00/x00/x00/x00 /x00/x00/x00/x7f gets read as /x00/x00 ^ /x00/x7f = /x00/x7F -> 0x7F00 = 32512 (sign bit not set) # /xDE/xAD/xBE/xEF /xFF/x80/x00/x80 gets read as /xFF/x80 ^ /x00/x80 = /xFF/x00 -> 0x00FF = 255 s = val[1] final_two = s[-2:] mask = bytearray(2) if len(s) >= 4: mask = s[-4:-2] final_two = bytearray(chr(final_two[0]^mask[0]) + chr(final_two[1]^mask[1])) n = vartypes.integer_to_int_signed(vartypes.bytes_to_integer(final_two)) state.basic_state.rnd_seed &= 0xff get_random_int(1) # RND(1) state.basic_state.rnd_seed += n * rnd_step state.basic_state.rnd_seed %= rnd_period
def randomize(val): """ Reseed the random number generator. """ # RANDOMIZE converts to int in a non-standard way - looking at the first two bytes in the internal representation # on a program line, if a number outside the signed int range (or -32768) is entered, # the number is stored as a MBF double or float. Randomize then: # - ignores the first 4 bytes (if it's a double) # - reads the next two # - xors them with the final two (most signifant including sign bit, and exponent) # and interprets them as a signed int # e.g. 1# = /x00/x00/x00/x00 /x00/x00/x00/x81 gets read as /x00/x00 ^ /x00/x81 = /x00/x81 -> 0x10000-0x8100 = -32512 (sign bit set) # 0.25# = /x00/x00/x00/x00 /x00/x00/x00/x7f gets read as /x00/x00 ^ /x00/x7f = /x00/x7F -> 0x7F00 = 32512 (sign bit not set) # /xDE/xAD/xBE/xEF /xFF/x80/x00/x80 gets read as /xFF/x80 ^ /x00/x80 = /xFF/x00 -> 0x00FF = 255 s = val[1] final_two = s[-2:] mask = bytearray(2) if len(s) >= 4: mask = s[-4:-2] final_two = bytearray( chr(final_two[0] ^ mask[0]) + chr(final_two[1] ^ mask[1])) n = vartypes.integer_to_int_signed(vartypes.bytes_to_integer(final_two)) state.basic_state.rnd_seed &= 0xff get_random_int(1) # RND(1) state.basic_state.rnd_seed += n * rnd_step state.basic_state.rnd_seed %= rnd_period
def update_line_dict(pos, afterpos, length, deleteable, beyond): """ Update line number dictionary after deleting lines. """ # subtract length of line we replaced length -= afterpos - pos addr = program_memory_start + afterpos state.basic_state.bytecode.seek(afterpos + length + 1) # pass \x00 while True: next_addr = state.basic_state.bytecode.read(2) if len(next_addr) < 2 or next_addr == '\0\0': break next_addr = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(next_addr)) state.basic_state.bytecode.seek(-2, 1) state.basic_state.bytecode.write( str( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(next_addr + length)))) state.basic_state.bytecode.read(next_addr - addr - 2) addr = next_addr # update line number dict for key in deleteable: del state.basic_state.line_numbers[key] for key in beyond: state.basic_state.line_numbers[key] += length
def hex_token_to_str(s): """ Convert hex token to Python str. """ return '&H' + integer_to_str_hex(vartypes.bytes_to_integer(s))
def int_token_to_str(s): """ Convert signed int token to Python string. """ return str(vartypes.integer_to_int_signed(vartypes.bytes_to_integer(s)))
def value_cvi(ins): """ CVI: return the int value of a byte representation. """ cstr = var.copy_str(vartypes.pass_string(parse_bracket(ins))) if len(cstr) < 2: raise error.RunError(error.IFC) return vartypes.bytes_to_integer(cstr[:2])
def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted([ k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek(state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write(str(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(old_to_new[old_line])))) # write the indirect line numbers ins = state.basic_state.bytecode ins.seek(0) while util.skip_to_read(ins, (tk.T_UINT,)) == tk.T_UINT: # get the old g number jumpnum = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(ins.read(2))) # handle exception for ERROR GOTO if jumpnum == 0: pos = ins.tell() # skip line number token ins.seek(-3, 1) if util.backskip_white(ins) == tk.GOTO and util.backskip_white(ins) == tk.ERROR: ins.seek(pos) continue ins.seek(pos) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum not in state.basic_state.line_numbers: linum = get_line_number(ins.tell()-1) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) newjump = jumpnum ins.seek(-2, 1) ins.write(str(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(newjump)))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted( [k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek( state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write( str( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(old_to_new[old_line])))) # write the indirect line numbers ins = state.basic_state.bytecode ins.seek(0) while util.skip_to_read(ins, (tk.T_UINT, )) == tk.T_UINT: # get the old g number jumpnum = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(ins.read(2))) # handle exception for ERROR GOTO if jumpnum == 0: pos = ins.tell() # skip line number token ins.seek(-3, 1) if util.backskip_white(ins) == tk.GOTO and util.backskip_white( ins) == tk.ERROR: ins.seek(pos) continue ins.seek(pos) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum not in state.basic_state.line_numbers: linum = get_line_number(ins.tell() - 1) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) newjump = jumpnum ins.seek(-2, 1) ins.write( str( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(newjump)))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[ old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def record_to_sprite_size(self, byte_array): """ Read 4-byte record of sprite size. """ dx = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(byte_array[0:2])) / self.bitsperpixel dy = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(byte_array[2:4])) return dx, dy
def record_to_sprite_size_ega(self, byte_array): """ Read 4-byte record of sprite size in EGA modes. """ dx = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(byte_array[0:2])) dy = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(byte_array[2:4])) return dx, dy
def oct_token_to_str(s): """ Convert oct token to Python str. """ return '&O' + integer_to_str_oct(vartypes.bytes_to_integer(s))
def address(self, key): """ Return the address of a given key. """ return vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(key[-2:]))