mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 16:18:19 -10:00
Mission flags are saved to the mission file as the full 64-bit flagset value, but three issues prevented anything larger than 32 bits from being saved as intended: * `stuff_flagset` parsed the value with `stuff_long`, where `atol` clamps anything above `LONG_MAX` * `flagset::from_u64` cast the incoming value to `unsigned long`, truncating bits 32-63 * `flagset::to_u64` used `bitset::to_ulong` where it should use `bitset::to_ullong` Fix these issues and add `static_assert` in the flagset functions to defend against future issues. Also use the proper UINT64_T_ARG where it should be used. This was first triggered by `Limited_support_rearm_pool`, the first mission flag to occupy bit 31. Only mission flags are affected; the other flagset serialization paths were audited and are safe. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
229 lines
5.2 KiB
C++
229 lines
5.2 KiB
C++
#ifndef _FLAGSET_
|
|
#define _FLAGSET_
|
|
|
|
#include <bitset>
|
|
#include <cstdint>
|
|
|
|
#include "osapi/dialogs.h"
|
|
|
|
constexpr size_t UBYTE_SIZE = 8;
|
|
|
|
template<class T, size_t SIZE = static_cast < size_t >(T::NUM_VALUES)>
|
|
class flagset {
|
|
protected:
|
|
std::bitset<SIZE> values;
|
|
public:
|
|
flagset() {}
|
|
|
|
flagset(const std::initializer_list<T>& init_list) {
|
|
for (auto& val : init_list) {
|
|
set(val);
|
|
}
|
|
}
|
|
// Returns true if any of the given flags are set.
|
|
template <typename... Ts>
|
|
bool any_of(T first, Ts... rest) const
|
|
{
|
|
static_assert((std::is_same<Ts, T>::value && ...),
|
|
"All arguments to any_of() must be flags of the same enum type");
|
|
return (*this)[first] || ((*this)[rest] || ...);
|
|
}
|
|
|
|
// Returns true if none of the given flags are set.
|
|
template <typename... Ts>
|
|
bool none_of(T first, Ts... rest) const
|
|
{
|
|
return !any_of(first, rest...);
|
|
}
|
|
bool operator[](const T idx) const { return values[(static_cast < size_t >(idx))]; };
|
|
|
|
|
|
flagset<T> operator&(const flagset<T>& other) const {
|
|
flagset<T> result;
|
|
result.values = this->values & other.values;
|
|
return result;
|
|
}
|
|
|
|
flagset<T> operator+(const T flag) const {
|
|
flagset<T> result = *this;
|
|
result.set(flag);
|
|
return result;
|
|
}
|
|
|
|
flagset<T>& operator+=(const T flag) {
|
|
this->set(flag);
|
|
return *this;
|
|
}
|
|
|
|
flagset<T> operator-(const T flag) const {
|
|
flagset<T> result = *this;
|
|
result.remove(flag);
|
|
return result;
|
|
}
|
|
|
|
flagset<T>& operator-=(const T flag) {
|
|
this->remove(flag);
|
|
return *this;
|
|
}
|
|
|
|
flagset<T> operator|(const flagset<T>& other) const {
|
|
flagset<T> result;
|
|
result.values = this->values | other.values;
|
|
return result;
|
|
}
|
|
|
|
flagset<T>& operator|=(const flagset<T>& other) {
|
|
this->values |= other.values;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void operator|=(const T flag) {
|
|
set(flag);
|
|
}
|
|
|
|
bool operator==(const flagset<T>& other) const { return this->values == other.values; }
|
|
bool operator!=(const flagset<T>& other) const { return this->values != other.values; }
|
|
|
|
void reset() { values.reset(); }
|
|
|
|
flagset<T>& set(T idx, bool value = true) {
|
|
Assertion(static_cast<size_t>(idx) < values.size(),
|
|
"Invalid value passed to flagset::set(), get a stacktrace, a coder, an old priest and a young priest.");
|
|
|
|
values.set(static_cast < size_t >(idx), value);
|
|
return *this;
|
|
}
|
|
|
|
template<typename TIter>
|
|
flagset<T>& set_multiple(TIter begin, TIter end) {
|
|
auto current = begin;
|
|
while (current != end) {
|
|
set(*current, true);
|
|
|
|
current = std::next(current);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
template<typename TIter>
|
|
flagset<T>& set_multiple_from_source(const flagset<T>& source, TIter begin, TIter end) {
|
|
auto current = begin;
|
|
while (current != end) {
|
|
if (source[*current]) {
|
|
set(*current, true);
|
|
}
|
|
|
|
current = std::next(current);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
flagset<T>& remove(T idx) {
|
|
return set(idx, false);
|
|
}
|
|
|
|
template<typename TIter>
|
|
flagset<T>& remove_multiple(TIter begin, TIter end) {
|
|
auto current = begin;
|
|
while (current != end) {
|
|
set(*current, false);
|
|
|
|
current = std::next(current);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
size_t to_ubyte_vector(SCP_vector<ubyte>& vector_out)
|
|
{
|
|
int counter = 0; // a counter to determine how far to bitshift
|
|
ubyte ubyte_out= 0; // the holder for our bitshifted flags
|
|
|
|
for (size_t i = 0; i < SIZE; i++) {
|
|
|
|
// this needs to be at the beginning for push_back after the loop
|
|
// to work properly.
|
|
if (counter == UBYTE_SIZE) {
|
|
vector_out.push_back(ubyte_out);
|
|
counter = 0;
|
|
ubyte_out = 0;
|
|
}
|
|
|
|
// check whether that flag was set
|
|
// there is probably a more efficient way to do this,
|
|
// but not unless we can copy the underlying data safely.
|
|
if (values[i]) {
|
|
ubyte_out |= 1 << counter;
|
|
}
|
|
|
|
// iterate
|
|
++counter;
|
|
}
|
|
|
|
vector_out.push_back(ubyte_out);
|
|
|
|
return vector_out.size();
|
|
}
|
|
|
|
void set_from_vector(const SCP_vector<ubyte>& vector_in)
|
|
{
|
|
size_t flag = 0;
|
|
|
|
// just iterate through the entries, and set flags as appropriate.
|
|
for (auto& entry : vector_in) {
|
|
|
|
for (uint bitshift = 0; bitshift < UBYTE_SIZE; bitshift++){
|
|
if (entry & (1 << bitshift)) {
|
|
values.set(flag, true);
|
|
}
|
|
|
|
++flag;
|
|
|
|
// this function is used in multi where the size of flagsets can be different
|
|
// on server and client. Small differences in flagset sizes should not affect
|
|
// gameplay, but we have to make sure not to exceed the size of the flagset.
|
|
if (flag >= SIZE){
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
flagset<T>& toggle(T idx) {
|
|
values[static_cast < size_t >(idx)] = !values[static_cast < size_t >(idx)];
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool any_set() const { return values.any(); }
|
|
bool none_set() const { return values.none(); }
|
|
|
|
void from_u64(std::uint64_t num)
|
|
{
|
|
static_assert(SIZE <= 64, "from_u64 can only be used with flagsets of up to 64 flags");
|
|
values = std::bitset<SIZE>(num);
|
|
}
|
|
|
|
std::uint64_t to_u64() const
|
|
{
|
|
static_assert(SIZE <= 64, "to_u64 can only be used with flagsets of up to 64 flags");
|
|
return static_cast<std::uint64_t>(values.to_ullong());
|
|
}
|
|
|
|
size_t hash() const { return std::hash<std::bitset<SIZE>>()(values); }
|
|
};
|
|
|
|
namespace std {
|
|
template <typename T, size_t N>
|
|
struct hash<::flagset<T, N>> {
|
|
size_t operator()(const ::flagset<T, N>& val) const { return val.hash(); }
|
|
};
|
|
} // namespace std
|
|
|
|
#define FLAG_LIST(Type) enum class Type : size_t
|
|
|
|
#endif
|