}/* z_get_child */ /* * z_get_next_prop, store the number of the first or next property. * * Process.zargs[0] = object * Process.zargs[1] = address of current property (0 gets the first property) * */ internal static void ZGetNextProp() { zbyte value; if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_GET_NEXT_PROP_0); Process.Store(0); return; } /* Property id is in bottom five (six) bits */ zbyte mask = (zbyte)((Main.h_version <= ZMachine.V3) ? 0x1f : 0x3f); /* Load address of first property */ zword prop_addr = FirstProperty(Process.zargs[0]); if (Process.zargs[1] != 0) { /* Scan down the property list */ do { FastMem.LowByte(prop_addr, out value); prop_addr = NextProperty(prop_addr); } while ((value & mask) > Process.zargs[1]); /* Exit if the property does not exist */ if ((value & mask) != Process.zargs[1]) { Err.RuntimeError(ErrorCodes.ERR_NO_PROP); } } /* Return the property id */ FastMem.LowByte(prop_addr, out value); Process.Store((zword)(value & mask)); }/* z_get_next_prop */
}/* call */ /* * ret * * Return from the current subroutine and restore the previous stack * frame. The result may be stored (0), thrown away (1) or pushed on * the stack (2). In the latter case a direct call has been finished * and we must exit the interpreter loop. * */ internal static void ret(zword value) { long pc; int ct; if (main.sp > main.fp) { Err.runtime_error(ErrorCodes.ERR_STK_UNDF); } main.sp = main.fp; DebugState.Output("Removing Frame: {0}", main.frame_count); ct = main.stack[main.sp++] >> (main.option_save_quetzal == true ? 12 : 8); main.frame_count--; main.fp = 1 + main.stack[main.sp++]; // fp = stack + 1 + *sp++; pc = main.stack[main.sp++]; pc = (main.stack[main.sp++] << 9) | (int)pc; // TODO Really don't trust casting PC to int FastMem.SET_PC(pc); /* Handle resulting value */ if (ct == 0) { store(value); } if (ct == 2) { main.stack[--main.sp] = value; } /* Stop main loop for direct calls */ if (ct == 2) { finished++; } }/* ret */
}/* z_get_prop_len */ /* * z_get_sibling, store the sibling of an object. * * Process.zargs[0] = object * */ internal static void ZGetSibling() { zword obj_addr; if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_GET_SIBLING_0); Process.Store(0); Process.Branch(false); return; } obj_addr = ObjectAddress(Process.zargs[0]); if (Main.h_version <= ZMachine.V3) { /* Get sibling id from object */ obj_addr += O1_SIBLING; FastMem.LowByte(obj_addr, out byte sibling); /* Store sibling and branch */ Process.Store(sibling); Process.Branch(sibling > 0); // TODO I'm not sure about this logic Process.branch (sibling); // I think it means if the sibling isn't zero, jump.. } else { /* Get sibling id from object */ obj_addr += O4_SIBLING; FastMem.LowWord(obj_addr, out ushort sibling); /* Store sibling and branch */ Process.Store(sibling); Process.Branch(sibling > 0); } }/* z_get_sibling */
}/* z_set_attr */ /* * z_test_attr, branch if an object attribute is set. * * Process.zargs[0] = object * Process.zargs[1] = number of attribute to test * */ internal static void ZTestAttr() { zword obj_addr; if (Process.zargs[1] > ((Main.h_version <= ZMachine.V3) ? 31 : 47)) { Err.RuntimeError(ErrorCodes.ERR_ILL_ATTR); } /* If we are monitoring attribute testing display a short note */ if (Main.option_attribute_testing == true) { Stream.StreamMssgOn(); Text.PrintString("@test_attr "); Text.PrintObject(Process.zargs[0]); Text.PrintString(" "); Text.PrintNum(Process.zargs[1]); Stream.StreamMssgOff(); } if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_TEST_ATTR_0); Process.Branch(false); return; } /* Get attribute address */ obj_addr = (zword)(ObjectAddress(Process.zargs[0]) + Process.zargs[1] / 8); /* Load attribute byte */ FastMem.LowByte(obj_addr, out byte value); /* Test attribute */ Process.Branch((value & (0x80 >> (Process.zargs[1] & 7))) > 0); }/* z_test_attr */
/* * object_address * * Calculate the address of an object. * */ internal static zword object_address(zword obj) { /* Check object number */ if (obj > ((main.h_version <= ZMachine.V3) ? 255 : MAX_OBJECT)) { Text.print_string("@Attempt to address illegal object "); Text.print_num(obj); Text.print_string(". This is normally fatal."); Buffer.new_line(); Err.runtime_error(ErrorCodes.ERR_ILL_OBJ); } /* Return object address */ if (main.h_version <= ZMachine.V3) { return((zword)(main.h_objects + ((obj - 1) * O1_SIZE + 62))); } else { return((zword)(main.h_objects + ((obj - 1) * O4_SIZE + 126))); } }/* object_address */
/* * Save a game using Quetzal format. Return 1 if OK, 0 if failed. */ internal static zword SaveQuetzal(FileStream svf, System.IO.Stream stf) { zlong stkslen = 0; zword i, j, n; int nvars, nargs, nstk, p; zbyte var; long cmempos, stkspos; int c; /* Write `IFZS' header. */ if (!WriteChunk(svf, ID_FORM, 0)) { return(0); } if (!WriteLong(svf, ID_IFZS)) { return(0); } /* Write `IFhd' chunk. */ FastMem.GetPc(out long pc); if (!WriteChunk(svf, ID_IFhd, 13)) { return(0); } if (!WriteWord(svf, Main.h_release)) { return(0); } for (i = ZMachine.H_SERIAL; i < ZMachine.H_SERIAL + 6; ++i) { if (!WriteByte(svf, FastMem.ZMData[FastMem.Zmp + i])) { return(0); } } if (!WriteWord(svf, Main.h_checksum)) { return(0); } if (!WriteLong(svf, pc << 8)) /* Includes pad. */ return { (0); } /* Write `CMem' chunk. */ if ((cmempos = svf.Position) < 0) { return(0); } if (!WriteChunk(svf, ID_CMem, 0)) { return(0); } // (void) fseek (stf, 0, SEEK_SET); stf.Position = 0; uint cmemlen; /* j holds current run length. */ for (i = 0, j = 0, cmemlen = 0; i < Main.h_dynamic_size; ++i) { if ((c = stf.ReadByte()) == -1) { return(0); } c ^= FastMem.ZMData[i]; if (c == 0) { ++j; /* It's a run of equal bytes. */ } else { /* Write out any run there may be. */ if (j > 0) { for (; j > 0x100; j -= 0x100) { if (!WriteRun(svf, 0xFF)) { return(0); } cmemlen += 2; } if (!WriteRun(svf, (byte)(j - 1))) { return(0); } cmemlen += 2; j = 0; } /* Any runs are now written. Write this (nonzero) byte. */ if (!WriteByte(svf, (zbyte)c)) { return(0); } ++cmemlen; } } /* * Reached end of dynamic memory. We ignore any unwritten run there may be * at this point. */ if ((cmemlen & 1) > 0) /* Chunk length must be even. */ { if (!WriteByte(svf, 0)) { return(0); } } /* Write `Stks' chunk. You are not expected to understand this. ;) */ if ((stkspos = svf.Position) < 0) { return(0); } if (!WriteChunk(svf, ID_Stks, 0)) { return(0); } using var buffer = SpanOwner <zword> .Allocate(General.STACK_SIZE / 4 + 1); var frames = buffer.Span; /* * We construct a list of frame indices, most recent first, in `frames'. * These indices are the offsets into the `stack' array of the word before * the first word pushed in each frame. */ frames[0] = (zword)Main.sp; /* The frame we'd get by doing a call now. */ for (i = (zword)(Main.fp + 4), n = 0; i < General.STACK_SIZE + 4; i = (zword)(Main.Stack[i - 3] + 5)) { frames[++n] = i; } /* * All versions other than V6 can use evaluation stack outside a function * context. We write a faked stack frame (most fields zero) to cater for * this. */ if (Main.h_version != ZMachine.V6) { for (i = 0; i < 6; ++i) { if (!WriteByte(svf, 0)) { return(0); } } nstk = General.STACK_SIZE - frames[n]; if (!WriteWord(svf, nstk)) { return(0); } for (j = General.STACK_SIZE - 1; j >= frames[n]; --j) { if (!WriteWord(svf, Main.Stack[j])) { return(0); } } stkslen = (zword)(8 + 2 * nstk); } /* Write out the rest of the stack frames. */ for (i = n; i > 0; --i) { p = frames[i] - 4; // p = stack + frames[i] - 4; /* Points to call frame. */ nvars = (Main.Stack[p] & 0x0F00) >> 8; nargs = Main.Stack[p] & 0x00FF; nstk = (zword)(frames[i] - frames[i - 1] - nvars - 4); pc = ((zlong)Main.Stack[p + 3] << 9) | Main.Stack[p + 2]; switch (Main.Stack[p] & 0xF000) /* Check type of call. */ { case 0x0000: /* Function. */ var = FastMem.ZMData[FastMem.Zmp + pc]; pc = ((pc + 1) << 8) | (zlong)nvars; break; case 0x1000: /* Procedure. */ var = 0; pc = (pc << 8) | 0x10 | (zlong)nvars; /* Set procedure flag. */ break; /* case 0x2000: */ default: Err.RuntimeError(ErrorCodes.ERR_SAVE_IN_INTER); return(0); } if (nargs != 0) { nargs = (zword)((1 << nargs) - 1); /* Make args into bitmap. */ } /* Write the main part of the frame... */ if (!WriteLong(svf, pc) || !WriteByte(svf, var) || !WriteByte(svf, (byte)nargs) || !WriteWord(svf, nstk)) { return(0); } /* Write the variables and eval stack. */ for (j = 0, --p; j < nvars + nstk; ++j, --p) { if (!WriteWord(svf, Main.Stack[p])) { return(0); } } /* Calculate length written thus far. */ stkslen += (zword)(8 + 2 * (nvars + nstk)); } /* Fill in variable chunk lengths. */ uint ifzslen = 3 * 8 + 4 + 14 + cmemlen + stkslen; if ((cmemlen & 1) > 0) { ++ifzslen; } svf.Position = 4; if (!WriteLong(svf, ifzslen)) { return(0); } svf.Position = cmempos + 4; if (!WriteLong(svf, cmemlen)) { return(0); } svf.Position = stkspos + 4; if (!WriteLong(svf, stkslen)) { return(0); } /* After all that, still nothing went wrong! */ return(1); }
}/* __extended__ */ /* * __illegal__ * * Exit game because an unknown opcode has been hit. * */ static void __illegal__() { Err.runtime_error(ErrorCodes.ERR_ILL_OPCODE); }/* __illegal__ */
}/* interpret */ /* * call * * Call a subroutine. Save PC and FP then load new PC and initialise * new stack frame. Note that the caller may legally provide less or * more arguments than the function actually has. The call type "ct" * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). * */ internal static void call(zword routine, int argc, int args_offset, int ct) { long pc; zword value; zbyte count; int i; if (main.sp < 4)//if (sp - stack < 4) { Err.runtime_error(ErrorCodes.ERR_STK_OVF); } FastMem.GET_PC(out pc); main.stack[--main.sp] = (zword)(pc >> 9); main.stack[--main.sp] = (zword)(pc & 0x1ff); main.stack[--main.sp] = (zword)(main.fp - 1); // *--sp = (zword) (fp - stack - 1); main.stack[--main.sp] = (zword)(argc | (ct << (main.option_save_quetzal == true ? 12 : 8))); main.fp = main.sp; main.frame_count++; DebugState.Output("Added Frame: {0} -> {1}:{2}:{3}:{4}", main.frame_count, main.stack[main.sp + 0], main.stack[main.sp + 1], main.stack[main.sp + 2], main.stack[main.sp + 3]); /* Calculate byte address of routine */ if (main.h_version <= ZMachine.V3) { pc = (long)routine << 1; } else if (main.h_version <= ZMachine.V5) { pc = (long)routine << 2; } else if (main.h_version <= ZMachine.V7) { pc = ((long)routine << 2) + ((long)main.h_functions_offset << 3); } else /* (h_version <= V8) */ { pc = (long)routine << 3; } if (pc >= main.story_size) { Err.runtime_error(ErrorCodes.ERR_ILL_CALL_ADDR); } FastMem.SET_PC(pc); /* Initialise local variables */ FastMem.CODE_BYTE(out count); if (count > 15) { Err.runtime_error(ErrorCodes.ERR_CALL_NON_RTN); } if (main.sp < count) { Err.runtime_error(ErrorCodes.ERR_STK_OVF); } if (main.option_save_quetzal == true) { main.stack[main.fp] |= (zword)(count << 8); /* Save local var count for Quetzal. */ } value = 0; for (i = 0; i < count; i++) { if (main.h_version <= ZMachine.V4) /* V1 to V4 games provide default */ { FastMem.CODE_WORD(out value); /* values for all local variables */ } main.stack[--main.sp] = (zword)((argc-- > 0) ? zargs[args_offset + i] : value); //*--sp = (zword) ((argc-- > 0) ? args[i] : value); } /* Start main loop for direct calls */ if (ct == 2) { interpret(); } }/* call */
}/* z_get_sibling */ /* * z_insert_obj, make an object the first child of another object. * * Process.zargs[0] = object to be moved * Process.zargs[1] = destination object * */ internal static void z_insert_obj() { zword obj1 = Process.zargs[0]; zword obj2 = Process.zargs[1]; zword obj1_addr; zword obj2_addr; /* If we are monitoring object movements display a short note */ if (main.option_object_movement == true) { Stream.stream_mssg_on(); Text.print_string("@move_obj "); Text.print_object(obj1); Text.print_string(" "); Text.print_object(obj2); Stream.stream_mssg_off(); } if (obj1 == 0) { Err.runtime_error(ErrorCodes.ERR_MOVE_OBJECT_0); return; } if (obj2 == 0) { Err.runtime_error(ErrorCodes.ERR_MOVE_OBJECT_TO_0); return; } /* Get addresses of both objects */ obj1_addr = object_address(obj1); obj2_addr = object_address(obj2); /* Remove object 1 from current parent */ unlink_object(obj1); /* Make object 1 first child of object 2 */ if (main.h_version <= ZMachine.V3) { zbyte child; obj1_addr += O1_PARENT; FastMem.SET_BYTE(obj1_addr, (zbyte)obj2); obj2_addr += O1_CHILD; FastMem.LOW_BYTE(obj2_addr, out child); FastMem.SET_BYTE(obj2_addr, (zbyte)obj1); obj1_addr += (zword)(O1_SIBLING - O1_PARENT); FastMem.SET_BYTE(obj1_addr, child); } else { zword child; obj1_addr += O4_PARENT; FastMem.SET_WORD(obj1_addr, obj2); obj2_addr += O4_CHILD; FastMem.LOW_WORD(obj2_addr, out child); FastMem.SET_WORD(obj2_addr, obj1); obj1_addr += (zword)(O4_SIBLING - O4_PARENT); FastMem.SET_WORD(obj1_addr, child); } }/* z_insert_obj */
}/* z_get_parent */ /* * z_get_prop, store the value of an object property. * * Process.zargs[0] = object * Process.zargs[1] = number of property to be examined * */ internal static void z_get_prop() { zword prop_addr; zword wprop_val; zbyte bprop_val; zbyte value; zbyte mask; if (Process.zargs[0] == 0) { Err.runtime_error(ErrorCodes.ERR_GET_PROP_0); Process.store(0); return; } /* Property id is in bottom five (six) bits */ mask = (zbyte)((main.h_version <= ZMachine.V3) ? 0x1f : 0x3f); /* Load address of first property */ prop_addr = first_property(Process.zargs[0]); /* Scan down the property list */ for (; ;) { FastMem.LOW_BYTE(prop_addr, out value); if ((value & mask) <= Process.zargs[1]) { break; } prop_addr = next_property(prop_addr); } if ((value & mask) == Process.zargs[1]) /* property found */ /* Load property (byte or word sized) */ { prop_addr++; if ((main.h_version <= ZMachine.V3 && !((value & 0xe0) > 0)) || (main.h_version >= ZMachine.V4 && !((value & 0xc0) > 0))) { FastMem.LOW_BYTE(prop_addr, out bprop_val); wprop_val = bprop_val; } else { FastMem.LOW_WORD(prop_addr, out wprop_val); } } else /* property not found */ /* Load default value */ { prop_addr = (zword)(main.h_objects + 2 * (Process.zargs[1] - 1)); FastMem.LOW_WORD(prop_addr, out wprop_val); } /* Store the property value */ Process.store(wprop_val); }/* z_get_prop */
}/* next_property */ /* * unlink_object * * Unlink an object from its parent and siblings. * */ static void unlink_object(zword object_var) { zword obj_addr; zword parent_addr; zword sibling_addr; if (object_var == 0) { Err.runtime_error(ErrorCodes.ERR_REMOVE_OBJECT_0); return; } obj_addr = object_address(object_var); if (main.h_version <= ZMachine.V3) { zbyte parent; zbyte younger_sibling; zbyte older_sibling; zbyte zero = 0; /* Get parent of object, and return if no parent */ obj_addr += O1_PARENT; FastMem.LOW_BYTE(obj_addr, out parent); if (parent == 0) { return; } /* Get (older) sibling of object and set both parent and sibling * pointers to 0 */ FastMem.SET_BYTE(obj_addr, zero); obj_addr += (zword)(O1_SIBLING - O1_PARENT); FastMem.LOW_BYTE(obj_addr, out older_sibling); FastMem.SET_BYTE(obj_addr, zero); /* Get first child of parent (the youngest sibling of the object) */ parent_addr = (zword)(object_address(parent) + O1_CHILD); FastMem.LOW_BYTE(parent_addr, out younger_sibling); /* Remove object from the list of siblings */ if (younger_sibling == object_var) { FastMem.SET_BYTE(parent_addr, older_sibling); } else { do { sibling_addr = (zword)(object_address(younger_sibling) + O1_SIBLING); FastMem.LOW_BYTE(sibling_addr, out younger_sibling); } while (younger_sibling != object_var); FastMem.SET_BYTE(sibling_addr, older_sibling); } } else { zword parent; zword younger_sibling; zword older_sibling; zword zero = 0; /* Get parent of object, and return if no parent */ obj_addr += O4_PARENT; FastMem.LOW_WORD(obj_addr, out parent); if (parent == 0) { return; } /* Get (older) sibling of object and set both parent and sibling * pointers to 0 */ FastMem.SET_WORD(obj_addr, zero); obj_addr += (zword)(O4_SIBLING - O4_PARENT); FastMem.LOW_WORD(obj_addr, out older_sibling); FastMem.SET_WORD(obj_addr, zero); /* Get first child of parent (the youngest sibling of the object) */ parent_addr = (zword)(object_address(parent) + O4_CHILD); FastMem.LOW_WORD(parent_addr, out younger_sibling); /* Remove object from the list of siblings */ if (younger_sibling == object_var) { FastMem.SET_WORD(parent_addr, older_sibling); } else { do { sibling_addr = (zword)(object_address(younger_sibling) + O4_SIBLING); FastMem.LOW_WORD(sibling_addr, out younger_sibling); } while (younger_sibling != object_var); FastMem.SET_WORD(sibling_addr, older_sibling); } } }/* unlink_object */
static void decode_text(string_type st, zword addr) { // zword* ptr; long byte_addr; zword c2; zword code; zbyte c, prev_c = 0; int shift_state = 0; int shift_lock = 0; int status = 0; // ptr = NULL; /* makes compilers shut up */ byte_addr = 0; if (resolution == 0) { find_resolution(); } /* Calculate the byte address if necessary */ if (st == string_type.ABBREVIATION) { byte_addr = (long)addr << 1; } else if (st == string_type.HIGH_STRING) { if (main.h_version <= ZMachine.V3) { byte_addr = (long)addr << 1; } else if (main.h_version <= ZMachine.V5) { byte_addr = (long)addr << 2; } else if (main.h_version <= ZMachine.V7) { byte_addr = ((long)addr << 2) + ((long)main.h_strings_offset << 3); } else /* (h_version <= V8) */ { byte_addr = (long)addr << 3; } if (byte_addr >= main.story_size) { Err.runtime_error(ErrorCodes.ERR_ILL_PRINT_ADDR); } } /* Loop until a 16bit word has the highest bit set */ if (st == string_type.VOCABULARY) { ptrDt = 0; } do { int i; /* Fetch the next 16bit word */ if (st == string_type.LOW_STRING || st == string_type.VOCABULARY) { FastMem.LOW_WORD(addr, out code); addr += 2; } else if (st == string_type.HIGH_STRING || st == string_type.ABBREVIATION) { FastMem.HIGH_WORD(byte_addr, out code); byte_addr += 2; } else { FastMem.CODE_WORD(out code); } /* Read its three Z-characters */ for (i = 10; i >= 0; i -= 5) { zword abbr_addr; zword ptr_addr; zword zc; c = (zbyte)((code >> i) & 0x1f); switch (status) { case 0: /* normal operation */ if (shift_state == 2 && c == 6) { status = 2; } else if (main.h_version == ZMachine.V1 && c == 1) { Buffer.new_line(); } else if (main.h_version >= ZMachine.V2 && shift_state == 2 && c == 7) { Buffer.new_line(); } else if (c >= 6) { outchar(st, alphabet(shift_state, c - 6)); } else if (c == 0) { outchar(st, ' '); } else if (main.h_version >= ZMachine.V2 && c == 1) { status = 1; } else if (main.h_version >= ZMachine.V3 && c <= 3) { status = 1; } else { shift_state = (shift_lock + (c & 1) + 1) % 3; if (main.h_version <= ZMachine.V2 && c >= 4) { shift_lock = shift_state; } break; } shift_state = shift_lock; break; case 1: /* abbreviation */ ptr_addr = (zword)(main.h_abbreviations + 64 * (prev_c - 1) + 2 * c); FastMem.LOW_WORD(ptr_addr, out abbr_addr); decode_text(string_type.ABBREVIATION, abbr_addr); status = 0; break; case 2: /* ZSCII character - first part */ status = 3; break; case 3: /* ZSCII character - second part */ zc = (zword)((prev_c << 5) | c); c2 = translate_from_zscii((zbyte)zc); // TODO This doesn't seem right outchar(st, c2); status = 0; break; } prev_c = c; } } while (!((code & 0x8000) > 0)); if (st == string_type.VOCABULARY) { ptrDt = 0; } }/* decode_text */
}/* next_property */ /* * unlink_object * * Unlink an object from its parent and siblings. * */ private static void UnlinkObject(zword object_var) { zword parent_addr; zword sibling_addr; if (object_var == 0) { Err.RuntimeError(ErrorCodes.ERR_REMOVE_OBJECT_0); return; } zword obj_addr = ObjectAddress(object_var); if (Main.h_version <= ZMachine.V3) { zbyte zero = 0; /* Get parent of object, and return if no parent */ obj_addr += O1_PARENT; FastMem.LowByte(obj_addr, out byte parent); if (parent == 0) { return; } /* Get (older) sibling of object and set both parent and sibling * pointers to 0 */ FastMem.SetByte(obj_addr, zero); obj_addr += (zword)(O1_SIBLING - O1_PARENT); FastMem.LowByte(obj_addr, out byte older_sibling); FastMem.SetByte(obj_addr, zero); /* Get first child of parent (the youngest sibling of the object) */ parent_addr = (zword)(ObjectAddress(parent) + O1_CHILD); FastMem.LowByte(parent_addr, out byte younger_sibling); /* Remove object from the list of siblings */ if (younger_sibling == object_var) { FastMem.SetByte(parent_addr, older_sibling); } else { do { sibling_addr = (zword)(ObjectAddress(younger_sibling) + O1_SIBLING); FastMem.LowByte(sibling_addr, out younger_sibling); } while (younger_sibling != object_var); FastMem.SetByte(sibling_addr, older_sibling); } } else { zword zero = 0; /* Get parent of object, and return if no parent */ obj_addr += O4_PARENT; FastMem.LowWord(obj_addr, out zword parent); if (parent == 0) { return; } /* Get (older) sibling of object and set both parent and sibling * pointers to 0 */ FastMem.SetWord(obj_addr, zero); obj_addr += (zword)(O4_SIBLING - O4_PARENT); FastMem.LowWord(obj_addr, out ushort older_sibling); FastMem.SetWord(obj_addr, zero); /* Get first child of parent (the youngest sibling of the object) */ parent_addr = (zword)(ObjectAddress(parent) + O4_CHILD); FastMem.LowWord(parent_addr, out ushort younger_sibling); /* Remove object from the list of siblings */ if (younger_sibling == object_var) { FastMem.SetWord(parent_addr, older_sibling); } else { do { sibling_addr = (zword)(ObjectAddress(younger_sibling) + O4_SIBLING); FastMem.LowWord(sibling_addr, out younger_sibling); } while (younger_sibling != object_var); FastMem.SetWord(sibling_addr, older_sibling); } } }/* unlink_object */