22 bool within(
const std::filesystem::path&
root,
const std::filesystem::path&
child)
32 return {
reinterpret_cast<const char*
>(
value.data()),
value.size()};
38 const int top = 112 +
static_cast<int>(
slot) * 68;
39 return {152,
top, 648,
top + 34};
49 if (!
moduleDirectory.is_absolute())
return std::unexpected(
"custom boards require an absolute executable directory");
50 std::error_code
error;
51 const auto module = std::filesystem::canonical(moduleDirectory, error);
53 return std::unexpected(
"custom-board executable directory is unavailable");
56 next.directory = std::filesystem::weakly_canonical(
module /
"custbrds",
error);
58 return std::unexpected(
"custom-board directory escapes the executable directory");
60 if (
error)
return std::unexpected(
"cannot inspect custom-board directory: " +
error.message());
64 if (
error)
return std::unexpected(
"cannot enumerate custom boards: " +
error.message());
67 if (
error)
return std::unexpected(
"cannot enumerate custom boards: " +
error.message());
71 if (
error)
return std::unexpected(
"cannot inspect custom-board file: " +
error.message());
75 return std::unexpected(
"custom-board file escapes its directory");
76 const auto name =
entry.path().filename();
79 if (
error)
return std::unexpected(
"cannot enumerate custom boards: " +
error.message());
80 std::sort(
next.entries.begin(),
next.entries.end(), [](
const auto&
a,
const auto&
b)
81 { return a.fileName < b.fileName; });
84 next.selectedIndex =
next.entries.empty() ? -1 : 0;
92 state.buttonRects = {};
98 if (!
state.active)
return result;
101 const int x =
static_cast<int>(message.numberA),
y =
static_cast<int>(message.numberB);
122 state.selectedIndex =
static_cast<int>(
state.pageOffset);
134 L"Software\\Hasbro Interactive\\Monopoly\\2.00.101", 0,
137 return std::unexpected(
"custom-board editor registry key is unavailable (Windows error " + std::to_string(
opened) +
")");
143 return std::unexpected(
"custom-board editor Version must be an installed four-byte REG_BINARY value");
144 return static_cast<std::uint32_t
>(
value);
146 return std::unexpected(
"custom-board editor ownership validation requires the Windows registry");
150 const std::filesystem::path&
directory,
const std::filesystem::path& fileName,
153 if (!
directory.is_absolute() || fileName.empty() || fileName.has_parent_path() ||
154 !
boardExtension(fileName) || fileName.stem().empty() || fileName.stem() ==
"." || fileName.stem() ==
"..")
155 return std::unexpected(
"invalid custom-board filename or directory");
156 std::error_code
error;
158 if (
error)
return std::unexpected(
"custom-board directory is unavailable");
159 const auto file = std::filesystem::canonical(
root / fileName,
error);
161 return std::unexpected(
"custom-board file is unavailable or escapes its directory");
162 const auto assets = std::filesystem::canonical(
root / fileName.stem(),
error);
164 return std::unexpected(
"custom-board asset directory is unavailable or escapes its directory");
165 std::ifstream
input(file, std::ios::binary);
166 std::array<unsigned char, 4>
bytes{};
168 return std::unexpected(
"custom-board file lacks its four-byte ownership code");
169 const auto version =
static_cast<std::uint32_t
>(
bytes[0]) |
170 (
static_cast<std::uint32_t
>(
bytes[1]) << 8U) |
171 (
static_cast<std::uint32_t
>(
bytes[2]) << 16U) |
172 (
static_cast<std::uint32_t
>(
bytes[3]) << 24U);
174 return std::unexpected(
"custom board was not created by this installed board editor");
179 if (!
state.active ||
state.selectedIndex < 0 ||
static_cast<std::size_t
>(
state.selectedIndex) >=
state.entries.size())
180 return std::unexpected(
"no custom board is selected");
182 if (!version)
return std::unexpected(version.error());
186 std::expected<std::optional<std::filesystem::path>, std::string>
190 if (
savedAssetRoot.empty())
return std::optional<std::filesystem::path>{};
192 return std::unexpected(
"saved custom-board path contains NUL");
198 if (!paths)
return std::unexpected(paths.error().detail);
199 std::string
firstName =
"2dboards/2dview01.bmp";
206 return std::optional<std::filesystem::path>{};
207 return std::unexpected(
first.error().detail);
209 const auto root =
first->parent_path().parent_path();
211 resources.context().language, -1, monetarySystem,
root};
219 if (!
path)
return std::unexpected(
path.error());
220 std::ifstream
input(*
path, std::ios::binary | std::ios::ate);
221 if (!
input)
return std::unexpected(
"cannot open saved custom-board camera");
222 const auto size =
input.tellg();
223 if (size < 0 ||
static_cast<std::uint64_t
>(size) > 4U * 1024U * 1024U)
224 return std::unexpected(
"saved custom-board camera exceeds file budget");
225 std::vector<std::byte>
bytes(
static_cast<std::size_t
>(size));
226 input.seekg(0, std::ios::beg);
228 static_cast<std::streamsize
>(
bytes.size()));
229 if (!
input)
return std::unexpected(
"cannot read saved custom-board camera");
236 if (!
recipe)
return std::unexpected(std::string(
recipe.error().detail));
238 if (!textures)
return std::unexpected(textures.error());
239 return std::optional<std::filesystem::path>{
root};
241 catch (
const std::filesystem::filesystem_error&
error)
243 return std::unexpected(std::string(
"invalid saved custom-board path: ") +
error.what());
std::vector< std::byte > bytes
std::filesystem::path directory
static std::expected< ResourcePaths, DataError > create(std::span< const std::filesystem::path > roots)
BoardTextureRecipeResult buildUsaTextureRecipe(BoardMeshKind mesh, TextureResolution resolution)
std::span< const std::string_view > twoDimensionalBoardTextureNames() noexcept
std::expected< LegacyBitmapRGBA8, BitmapError > decodeLegacyBitmapRGBA8(std::span< const std::byte > bytes, std::size_t maxPixels)
std::expected< std::filesystem::path, std::string > resolveBoardTexturePath(const ResourcePaths &stockPaths, BoardMeshKind mesh, TextureLocation location, std::string_view fileName, const BoardTextureContext &context)
std::expected< std::vector< std::shared_ptr< const HmdTextureImage > >, std::string > loadBoardTextureImages(const ResourcePaths &paths, const BoardTextureRecipe &recipe, const BoardTextureContext &context)
BoardTextureRecipeResult buildEuropeanTextureRecipe(BoardMeshKind mesh, TextureResolution resolution)
Rect customBoardSlotRect(std::size_t slot) noexcept
CustomBoardInput processCustomBoardInput(CustomBoardState &state, const uimsg::Message &message) noexcept
void closeCustomBoardDialog(CustomBoardState &state) noexcept
constexpr std::size_t CustomBoardLimit
std::expected< std::optional< std::filesystem::path >, std::string > restoreSavedCustomBoard(std::string_view savedAssetRoot, const data::ResourceSnapshot &resources, int monetarySystem)
constexpr std::size_t CustomBoardPageSize
std::expected< void, std::string > openCustomBoardDialog(CustomBoardState &state, const std::filesystem::path &moduleDirectory, display::Screen2D previousView)
bool customBoardHasNext(const CustomBoardState &state) noexcept
bool customBoardHasPrevious(const CustomBoardState &state) noexcept
std::expected< CustomBoardSelection, std::string > validateCustomBoardFile(const std::filesystem::path &directory, const std::filesystem::path &fileName, std::uint32_t installedSecurityVersion)
std::expected< CustomBoardSelection, std::string > validateSelectedCustomBoard(const CustomBoardState &state)
std::expected< std::uint32_t, std::string > readCustomBoardSecurityVersion()
display::Screen2D previousView
bool contains(int x, int y) const noexcept