/* * memory_open * * Begin output redirection to the memory of the Z-machine. * */ internal static void MemoryOpen(zword table, zword xsize, bool buffering) { if (++depth < MAX_NESTING) { if (!buffering) { xsize = 0xffff; } if (buffering && (short)xsize <= 0) { xsize = Screen.GetMaxWidth((zword)(-(short)xsize)); } FastMem.StoreW(table, 0); redirect[depth].Table = table; redirect[depth].Width = 0; redirect[depth].Total = 0; redirect[depth].XSize = xsize; Main.ostream_memory = true; } else { Err.RuntimeError(ErrorCodes.ERR_STR3_NESTING); } }/* memory_open */
}/* 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 ZInsertObj() { 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.StreamMssgOn(); Text.PrintString("@move_obj "); Text.PrintObject(obj1); Text.PrintString(" "); Text.PrintObject(obj2); Stream.StreamMssgOff(); } if (obj1 == 0) { Err.RuntimeError(ErrorCodes.ERR_MOVE_OBJECT_0); return; } if (obj2 == 0) { Err.RuntimeError(ErrorCodes.ERR_MOVE_OBJECT_TO_0); return; } /* Get addresses of both objects */ obj1_addr = ObjectAddress(obj1); obj2_addr = ObjectAddress(obj2); /* Remove object 1 from current parent */ UnlinkObject(obj1); /* Make object 1 first child of object 2 */ if (Main.h_version <= ZMachine.V3) { obj1_addr += O1_PARENT; FastMem.SetByte(obj1_addr, (zbyte)obj2); obj2_addr += O1_CHILD; FastMem.LowByte(obj2_addr, out byte child); FastMem.SetByte(obj2_addr, (zbyte)obj1); obj1_addr += (zword)(O1_SIBLING - O1_PARENT); FastMem.SetByte(obj1_addr, child); } else { obj1_addr += O4_PARENT; FastMem.SetWord(obj1_addr, obj2); obj2_addr += O4_CHILD; FastMem.LowWord(obj2_addr, out ushort child); FastMem.SetWord(obj2_addr, obj1); obj1_addr += (zword)(O4_SIBLING - O4_PARENT); FastMem.SetWord(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 ZGetProp() { zword prop_addr; zword wprop_val; zbyte value; zbyte mask; if (Process.zargs[0] == 0) { Err.RuntimeError(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 = FirstProperty(Process.zargs[0]); /* Scan down the property list */ for (; ;) { FastMem.LowByte(prop_addr, out value); if ((value & mask) <= Process.zargs[1]) { break; } prop_addr = NextProperty(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.LowByte(prop_addr, out zbyte bprop_val); wprop_val = bprop_val; } else { FastMem.LowWord(prop_addr, out wprop_val); } } else { /* property not found */ /* Load default value */ prop_addr = (zword)(Main.h_objects + 2 * (Process.zargs[1] - 1)); FastMem.LowWord(prop_addr, out wprop_val); } /* Store the property value */ Process.Store(wprop_val); }/* z_get_prop */
}/* z_art_shift */ /* * z_div, signed 16bit division. * * zargs[0] = first value * zargs[1] = second value * */ internal static void ZDiv() { if (Process.zargs[1] == 0) { Err.RuntimeError(ErrorCodes.ERR_DIV_ZERO); } Process.Store((zword)((short)Process.zargs[0] / (short)Process.zargs[1])); }/* z_div */
internal static void PrintChar(zword c) { if (Main.message || Main.ostream_memory || Main.enable_buffering) { if (!PrintCharFlag) { /* Characters 0 and ZC_RETURN are special cases */ if (c == CharCodes.ZC_RETURN) { NewLine(); return; } if (c == 0) { return; } /* Flush the buffer before a whitespace or after a hyphen */ if ((c is ' ' or CharCodes.ZC_INDENT or CharCodes.ZC_GAP) || (prev_c == '-' && c != '-')) { FlushBuffer(); } /* Set the flag if this is part one of a style or font change */ if (c is CharCodes.ZC_NEW_FONT or CharCodes.ZC_NEW_STYLE) { PrintCharFlag = true; } /* Remember the current character code */ prev_c = c; } else { PrintCharFlag = false; } /* Insert the character into the buffer */ buffer_var[bufpos++] = c; if (bufpos == General.TEXT_BUFFER_SIZE) { Err.RuntimeError(ErrorCodes.ERR_TEXT_BUF_OVF); } } else { Stream.StreamChar(c); } }/* print_char */
}/* z_get_prop */ /* * z_get_prop_addr, store the address of an object property. * * Process.zargs[0] = object * Process.zargs[1] = number of property to be examined * */ internal static void ZGetPropAddr() { zword prop_addr; zbyte value; zbyte mask; if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_GET_PROP_ADDR_0); Process.Store(0); return; } if (Main.StoryId == Story.BEYOND_ZORK && Process.zargs[0] > MAX_OBJECT) { 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 = FirstProperty(Process.zargs[0]); /* Scan down the property list */ for (; ;) { FastMem.LowByte(prop_addr, out value); if ((value & mask) <= Process.zargs[1]) { break; } prop_addr = NextProperty(prop_addr); } /* Calculate the property address or return zero */ if ((value & mask) == Process.zargs[1]) { if (Main.h_version >= ZMachine.V4 && (value & 0x80) > 0) { prop_addr++; } Process.Store((zword)(prop_addr + 1)); } else { Process.Store(0); } }/* z_get_prop_addr */
}/* z_insert_obj */ /* * z_put_prop, set the value of an object property. * * Process.zargs[0] = object * Process.zargs[1] = number of property to set * Process.zargs[2] = value to set property to * */ internal static void ZPutProp() { zword prop_addr; zbyte value; zbyte mask; if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_PUT_PROP_0); return; } /* Property id is in bottom five or six bits */ mask = (zbyte)((Main.h_version <= ZMachine.V3) ? 0x1f : 0x3f); /* Load address of first property */ prop_addr = FirstProperty(Process.zargs[0]); /* Scan down the property list */ for (; ;) { FastMem.LowByte(prop_addr, out value); if ((value & mask) <= Process.zargs[1]) { break; } prop_addr = NextProperty(prop_addr); } /* Exit if the property does not exist */ if ((value & mask) != Process.zargs[1]) { Err.RuntimeError(ErrorCodes.ERR_NO_PROP); } /* Store the new property value (byte or word sized) */ prop_addr++; if ((Main.h_version <= ZMachine.V3 && !((value & 0xe0) > 0)) || (Main.h_version >= ZMachine.V4 && !((value & 0xc0) > 0))) { zbyte v = (zbyte)Process.zargs[2]; FastMem.SetByte(prop_addr, v); } else { zword v = Process.zargs[2]; FastMem.SetWord(prop_addr, v); } }/* z_put_prop */
}/* z_remove_obj */ /* * z_set_attr, set an object attribute. * * Process.zargs[0] = object * Process.zargs[1] = number of attribute to set * */ internal static void ZSetAttr() { zword obj_addr; if (Main.StoryId == Story.SHERLOCK) { if (Process.zargs[1] == 48) { return; } } if (Process.zargs[1] > ((Main.h_version <= ZMachine.V3) ? 31 : 47)) { Err.RuntimeError(ErrorCodes.ERR_ILL_ATTR); } /* If we are monitoring attribute assignment display a short note */ if (Main.option_attribute_assignment == true) { Stream.StreamMssgOn(); Text.PrintString("@set_attr "); Text.PrintObject(Process.zargs[0]); Text.PrintString(" "); Text.PrintNum(Process.zargs[1]); Stream.StreamMssgOff(); } if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_SET_ATTR_0); return; } /* Get attribute address */ obj_addr = (zword)(ObjectAddress(Process.zargs[0]) + Process.zargs[1] / 8); /* Load attribute byte */ FastMem.LowByte(obj_addr, out zbyte value); /* Set attribute bit */ value |= (zbyte)(0x80 >> (Process.zargs[1] & 7)); /* Store attribute byte */ FastMem.SetByte(obj_addr, value); }/* z_set_attr */
}/* z_clear_attr */ /* * z_jin, branch if the first object is inside the second. * * Process.zargs[0] = first object * Process.zargs[1] = second object * */ internal static void ZJin() { zword obj_addr; /* If we are monitoring object locating display a short note */ if (Main.option_object_locating) { Stream.StreamMssgOn(); Text.PrintString("@jin "); Text.PrintObject(Process.zargs[0]); Text.PrintString(" "); Text.PrintObject(Process.zargs[1]); Stream.StreamMssgOff(); } if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_JIN_0); Process.Branch(0 == Process.zargs[1]); return; } obj_addr = ObjectAddress(Process.zargs[0]); if (Main.h_version <= ZMachine.V3) { /* Get parent id from object */ obj_addr += O1_PARENT; FastMem.LowByte(obj_addr, out zbyte parent); /* Branch if the parent is obj2 */ Process.Branch(parent == Process.zargs[1]); } else { /* Get parent id from object */ obj_addr += O4_PARENT; FastMem.LowWord(obj_addr, out zword parent); /* Branch if the parent is obj2 */ Process.Branch(parent == Process.zargs[1]); } }/* z_jin */
}/* z_get_next_prop */ /* * z_get_parent, store the parent of an object. * * Process.zargs[0] = object * */ internal static void ZGetParent() { zword obj_addr; /* If we are monitoring object locating display a short note */ if (Main.option_object_locating) { Stream.StreamMssgOn(); Text.PrintString("@get_parent "); Text.PrintObject(Process.zargs[0]); Stream.StreamMssgOff(); } if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_GET_PARENT_0); Process.Store(0); return; } obj_addr = ObjectAddress(Process.zargs[0]); if (Main.h_version <= ZMachine.V3) { /* Get parent id from object */ obj_addr += O1_PARENT; FastMem.LowByte(obj_addr, out zbyte parent); /* Store parent */ Process.Store(parent); } else { /* Get parent id from object */ obj_addr += O4_PARENT; FastMem.LowWord(obj_addr, out zword parent); /* Store parent */ Process.Store(parent); } }/* z_get_parent */
}/* z_jin */ /* * z_get_child, store the child of an object. * * Process.zargs[0] = object * */ internal static void ZGetChild() { zword obj_addr; /* If we are monitoring object locating display a short note */ if (Main.option_object_locating) { Stream.StreamMssgOn(); Text.PrintString("@get_child "); Text.PrintObject(Process.zargs[0]); Stream.StreamMssgOff(); } if (Process.zargs[0] == 0) { Err.RuntimeError(ErrorCodes.ERR_GET_CHILD_0); Process.Store(0); Process.Branch(false); return; } obj_addr = ObjectAddress(Process.zargs[0]); if (Main.h_version <= ZMachine.V3) { /* Get child id from object */ obj_addr += O1_CHILD; FastMem.LowByte(obj_addr, out zbyte child); /* Store child id and branch */ Process.Store(child); Process.Branch(child > 0); } else { /* Get child id from object */ obj_addr += O4_CHILD; FastMem.LowWord(obj_addr, out zword child); /* Store child id and branch */ Process.Store(child); Process.Branch(child > 0); } }/* z_get_child */
}/* alphabet */ /* * find_resolution * * Find the number of bytes used for dictionary resolution. * */ internal static void FindResolution() { zword dct = Main.h_dictionary; FastMem.LowByte(dct, out zbyte sep_count); dct += (zword)(1 + sep_count); /* skip word separators */ FastMem.LowByte(dct, out zbyte entry_len); Resolution = (Main.h_version <= ZMachine.V3) ? 2 : 3; if (2 * Resolution > entry_len) { Err.RuntimeError(ErrorCodes.ERR_DICT_LEN); } Decoded = new zword[3 * Resolution + 1]; Encoded = new zword[Resolution]; }/* find_resolution */
}/* 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 */
}/* 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 ObjectAddress(zword obj) { /* Check object number */ if (obj > ((Main.h_version <= ZMachine.V3) ? 255 : MAX_OBJECT)) { Text.PrintString("@Attempt to address illegal object "); Text.PrintNum(obj); Text.PrintString(". This is normally fatal."); Buffer.NewLine(); Err.RuntimeError(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); }
}/* 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 */