diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index 968ab49eb0..996d1d847d 100644 --- a/src/tracerestrict.cpp +++ b/src/tracerestrict.cpp @@ -395,6 +395,14 @@ CommandCost TraceRestrictProgram::Validate(const std::vector } HandleCondition(condstack, condflags, true); } + } else { + // check multi-word instructions + if (IsTraceRestrictDoubleItem(item)) { + i++; + if (i >= size) { + return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE); // instruction ran off end + } + } } } if(!condstack.empty()) { @@ -403,6 +411,35 @@ CommandCost TraceRestrictProgram::Validate(const std::vector return CommandCost(); } +/** + * Convert an instruction index into an item array index + */ +size_t TraceRestrictProgram::InstructionOffsetToArrayOffset(const std::vector &items, size_t offset) +{ + size_t output_offset = 0; + size_t size = items.size(); + for (size_t i = 0; i < offset && output_offset < size; i++, output_offset++) { + if (IsTraceRestrictDoubleItem(items[output_offset])) { + output_offset++; + } + } + return output_offset; +} + +/** + * Convert an item array index into an instruction index + */ +size_t TraceRestrictProgram::ArrayOffsetToInstructionOffset(const std::vector &items, size_t offset) +{ + size_t output_offset = 0; + for (size_t i = 0; i < offset; i++, output_offset++) { + if (IsTraceRestrictDoubleItem(items[i])) { + i++; + } + } + return output_offset; +} + /** * Set the value and aux field of @p item, as per the value type in @p value_type */ @@ -595,6 +632,18 @@ static CommandCost TraceRestrictCheckTileIsUsable(TileIndex tile, Track track) return CommandCost(); } +/** + * Returns an appropriate default value for the second item of a dual-item instruction + * @p item is the first item of the instruction + */ +static uint32 GetDualInstructionInitialValue(TraceRestrictItem item) +{ + switch (GetTraceRestrictType(item)) { + default: + NOT_REACHED(); + } +} + /** * The main command for editing a signal tracerestrict program. * @param tile The tile which contains the signal. @@ -641,28 +690,46 @@ CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, u switch (type) { case TRDCT_INSERT_ITEM: - items.insert(items.begin() + offset, item); + items.insert(TraceRestrictProgram::InstructionAt(items, offset), item); if (IsTraceRestrictConditional(item) && GetTraceRestrictCondFlags(item) == 0 && GetTraceRestrictType(item) != TRIT_COND_ENDIF) { // this is an opening if block, insert a corresponding end if TraceRestrictItem endif_item = 0; SetTraceRestrictType(endif_item, TRIT_COND_ENDIF); - items.insert(items.begin() + offset + 1, endif_item); + items.insert(TraceRestrictProgram::InstructionAt(items, offset) + 1, endif_item); + } else if (IsTraceRestrictDoubleItem(item)) { + items.insert(TraceRestrictProgram::InstructionAt(items, offset) + 1, GetDualInstructionInitialValue(item)); } break; case TRDCT_MODIFY_ITEM: { - TraceRestrictItem old_item = items[offset]; - if (IsTraceRestrictConditional(old_item) != IsTraceRestrictConditional(item)) { + std::vector::iterator old_item = TraceRestrictProgram::InstructionAt(items, offset); + if (IsTraceRestrictConditional(*old_item) != IsTraceRestrictConditional(item)) { return_cmd_error(STR_TRACE_RESTRICT_ERROR_CAN_T_CHANGE_CONDITIONALITY); } - items[offset] = item; + bool old_is_dual = IsTraceRestrictDoubleItem(*old_item); + bool new_is_dual = IsTraceRestrictDoubleItem(item); + *old_item = item; + if (old_is_dual && !new_is_dual) { + items.erase(old_item + 1); + } else if (!old_is_dual && new_is_dual) { + items.insert(old_item + 1, GetDualInstructionInitialValue(item)); + } + break; + } + + case TRDCT_MODIFY_DUAL_ITEM: { + std::vector::iterator old_item = TraceRestrictProgram::InstructionAt(items, offset); + if (!IsTraceRestrictDoubleItem(*old_item)) { + return CMD_ERROR; + } + *(old_item + 1) = p2; break; } case TRDCT_REMOVE_ITEM: { - TraceRestrictItem old_item = items[offset]; + TraceRestrictItem old_item = *TraceRestrictProgram::InstructionAt(items, offset); if (IsTraceRestrictConditional(old_item)) { bool remove_whole_block = false; if (GetTraceRestrictCondFlags(old_item) == 0) { @@ -676,7 +743,7 @@ CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, u } uint32 recursion_depth = 1; - std::vector::iterator remove_start = items.begin() + offset; + std::vector::iterator remove_start = TraceRestrictProgram::InstructionAt(items, offset); std::vector::iterator remove_end = remove_start + 1; // iterate until matching end block found @@ -709,12 +776,22 @@ CommandCost CmdProgramSignalTraceRestrict(TileIndex tile, DoCommandFlag flags, u break; } } + } else if (IsTraceRestrictDoubleItem(current_item)) { + // this is a double-item, jump over the next item as well + ++remove_end; } } if (recursion_depth != 0) return CMD_ERROR; // ran off the end items.erase(remove_start, remove_end); } else { - items.erase(items.begin() + offset); + std::vector::iterator remove_start = TraceRestrictProgram::InstructionAt(items, offset); + std::vector::iterator remove_end = remove_start + 1; + + if (IsTraceRestrictDoubleItem(old_item)) { + // this is a double-item, remove the next item as well + ++remove_end; + } + items.erase(remove_start, remove_end); } break; } diff --git a/src/tracerestrict.h b/src/tracerestrict.h index 4e0f0f52bb..df7d2f73c2 100644 --- a/src/tracerestrict.h +++ b/src/tracerestrict.h @@ -216,6 +216,46 @@ struct TraceRestrictProgram : TraceRestrictProgramPool::PoolItem<&_tracerestrict static CommandCost Validate(const std::vector &items); + static size_t InstructionOffsetToArrayOffset(const std::vector &items, size_t offset); + + static size_t ArrayOffsetToInstructionOffset(const std::vector &items, size_t offset); + + /** Call InstructionOffsetToArrayOffset on current program instruction list */ + size_t InstructionOffsetToArrayOffset(size_t offset) const + { + return TraceRestrictProgram::InstructionOffsetToArrayOffset(this->items, offset); + } + + /** Call ArrayOffsetToInstructionOffset on current program instruction list */ + size_t ArrayOffsetToInstructionOffset(size_t offset) const + { + return TraceRestrictProgram::ArrayOffsetToInstructionOffset(this->items, offset); + } + + /** Get number of instructions in @p items */ + static size_t GetInstructionCount(const std::vector &items) + { + return ArrayOffsetToInstructionOffset(items, items.size()); + } + + /** Call GetInstructionCount on current program instruction list */ + size_t GetInstructionCount() const + { + return TraceRestrictProgram::GetInstructionCount(this->items); + } + + /** Get an iterator to the instruction at a given @p instruction_offset in @p items */ + static std::vector::iterator InstructionAt(std::vector &items, size_t instruction_offset) + { + return items.begin() + TraceRestrictProgram::InstructionOffsetToArrayOffset(items, instruction_offset); + } + + /** Get a const_iterator to the instruction at a given @p instruction_offset in @p items */ + static std::vector::const_iterator InstructionAt(const std::vector &items, size_t instruction_offset) + { + return items.begin() + TraceRestrictProgram::InstructionOffsetToArrayOffset(items, instruction_offset); + } + /** * Call validation function on current program instruction list */ @@ -294,6 +334,12 @@ static inline bool IsTraceRestrictConditional(TraceRestrictItem item) return IsTraceRestrictTypeConditional(GetTraceRestrictType(item)); } +/** Is TraceRestrictItem a double-item type? */ +static inline bool IsTraceRestrictDoubleItem(TraceRestrictItem item) +{ + return false; +} + /** * Categorisation of what is allowed in the TraceRestrictItem condition op field * see TraceRestrictTypePropertySet @@ -432,14 +478,15 @@ static inline const TraceRestrictProgram *GetExistingTraceRestrictProgram(TileIn * Enumeration for command action type field, indicates what command to do */ enum TraceRestrictDoCommandType { - TRDCT_INSERT_ITEM = 0, ///< insert new instruction before offset field as given value - TRDCT_MODIFY_ITEM = 1, ///< modify instruction at offset field to given value - TRDCT_REMOVE_ITEM = 2, ///< remove instruction at offset field + TRDCT_INSERT_ITEM, ///< insert new instruction before offset field as given value + TRDCT_MODIFY_ITEM, ///< modify instruction at offset field to given value + TRDCT_MODIFY_DUAL_ITEM, ///< modify second item of dual-part instruction at offset field to given value + TRDCT_REMOVE_ITEM, ///< remove instruction at offset field - TRDCT_PROG_COPY = 3, ///< copy program operation. Do not re-order this with respect to other values - TRDCT_PROG_SHARE = 4, ///< share program operation - TRDCT_PROG_UNSHARE = 5, ///< unshare program (copy as a new program) - TRDCT_PROG_RESET = 6, ///< reset program state of signal + TRDCT_PROG_COPY, ///< copy program operation. Do not re-order this with respect to other values + TRDCT_PROG_SHARE, ///< share program operation + TRDCT_PROG_UNSHARE, ///< unshare program (copy as a new program) + TRDCT_PROG_RESET, ///< reset program state of signal }; void TraceRestrictDoCommandP(TileIndex tile, Track track, TraceRestrictDoCommandType type, uint32 offset, uint32 value, StringID error_msg); diff --git a/src/tracerestrict_gui.cpp b/src/tracerestrict_gui.cpp index 630c3abbd6..0aa867d3ed 100644 --- a/src/tracerestrict_gui.cpp +++ b/src/tracerestrict_gui.cpp @@ -1109,7 +1109,7 @@ private: int GetItemCount(const TraceRestrictProgram *prog) const { if (prog) { - return 2 + prog->items.size(); + return 2 + prog->GetInstructionCount(); } else { return 2; } @@ -1141,17 +1141,17 @@ private: } if (prog) { - const std::vector &items = prog->items; + size_t instruction_count = prog->GetInstructionCount(); - if (static_cast(index) == items.size() + 1) { + if (static_cast(index) == instruction_count + 1) { return MakeSpecialItem(TRNTSV_END); } - if (static_cast(index) > items.size() + 1) { + if (static_cast(index) > instruction_count + 1) { return 0; } - return items[index - 1]; + return prog->items[prog->InstructionOffsetToArrayOffset(index - 1)]; } else { // No program defined, this is equivalent to an empty program if (index == 1) { @@ -1460,12 +1460,13 @@ private: std::vector items = prog->items; // copy - if (offset >= (items.size() + (replace ? 0 : 1))) return false; // off the end of the program + if (offset >= (TraceRestrictProgram::GetInstructionCount(items) + (replace ? 0 : 1))) return false; // off the end of the program + uint array_offset = TraceRestrictProgram::InstructionOffsetToArrayOffset(items, offset); if (replace) { - items[offset] = item; + items[array_offset] = item; } else { - items.insert(items.begin() + offset, item); + items.insert(items.begin() + array_offset, item); } return TraceRestrictProgram::Validate(items).Succeeded();