11 enum class Kind { Background, Pattern,
Button, Title, Slot, SelectedSlot, Tab, SelectedTab, Toggle, SelectedToggle, PlayerCard, Confirmation, AuctionBackground, AuctionBottom, AuctionPlayer, AuctionBid, TokenPreview, TokenThumbnail, TokenFrame, TradeBackground, TradePanel, TradeOffer, TradeRail, TradeTitle, TradeButton, StatsPanel, CalculatorPanel, CalculatorDescription, StatsBackground, StatsBar, StatsBarHeading, StatsTab, StatsSelectedTab, CalculatorKey, CalculatorSelectedKey, BankSummary, DeedsFrame,
CalculatorFunction, CalculatorSelectedFunction, PortfolioFrame, PortfolioAmount, CitySelector, TradeCashPanel, TradeCashButton, TradeCashPressed };
15 0x0347,0x0331,0x0327,0x0325,0x0329,0x032D,0x032B,0x032F};
17 "Odds",
"Net\nworth",
"Future\nto you",
"Future\nto other",
"Maximum\nexpense",
18 "Current\nincome",
"Maximum\nincome",
"Potential\nincome"};
20 constexpr std::array<std::array<unsigned, 3>, 6>
PlayerColours{{
21 {255,0,0}, {0,0,255}, {60,150,60}, {255,255,0}, {255,0,255}, {255,128,0}}};
23 0x004A,0x0059,0x003B,0x003E,0x0056,0x0053,0x004D,0x0041,0x0047,0x0050,0x0044};
26 {0x019E,0x0E6C,114,43,
"Players"}, {0x017F,0x0900,106,43,
"Deeds"},
27 {0x0176,0x02F6,95,43,
"Bank"}, {0x01A4,0x11BC,76,43,
"Turn"},
28 {0x0198,0x0E3E,100,42,
"Net worth"}, {0x0195,0x092E,105,42,
"Future value"},
29 {0x017C,0x0308,107,42,
"Cash"}
34 {{{0x00FE,0x00FD,0x00FF}},0x0C94,76,43,
"houses hotels"},
35 {{{0x0104,0x0103,0x0105}},0x0CAE,100,42,
"properties"},
36 {{{0x0101,0x0100,0x0102}},0x0CA1,105,42,
"liabilities"},
37 {{{0x00FB,0x00FA,0x00FC}},0x0C87,107,42,
"turn history"},
38 {{{0x01A1,0x01A3,0x01A2}},0x0E9B,76,43,
"price"},
39 {{{0x019B,0x019D,0x019C}},0x0E5E,100,42,
"owner"},
40 {{{0x0179,0x017B,0x017A}},0x07FC,105,42,
"current rent"},
41 {{{0x0182,0x0184,0x0183}},0x0917,107,42,
"game earnings"}
46 {0x01A0,0x124E,126,43},{0x01A0,0x124F,126,43},{0x01A0,0x1250,126,43},
47 {0x01A0,0x1251,126,43},{0x01A0,0x1252,126,43},{0x01A0,0x1253,126,43},
48 {0x01A0,0x1254,125,43},{0x01A0,0x1255,122,43},{0x01A0,0x1256,126,43},
49 {0x01A0,0x1257,130,43},{0x01A0,0x1258,133,78},{0x01A0,0x1259,133,78},
50 {0x01A6,0x1266,76,43},{0x01A6,0x1267,76,43},{0x01A6,0x1268,76,43},
51 {0x01A6,0x1269,76,43},{0x01A6,0x126A,76,43},{0x01A6,0x126B,76,43},
52 {0x01A6,0x126C,76,43},{0x01A6,0x126D,75,43},{0x01A6,0x126E,76,43},
53 {0x01A6,0x126F,78,43},{0x01A6,0x1270,83,43},{0x01A6,0x1271,83,43},
54 {0x01A5,0x11BD,76,43},{0x01A5,0x11BE,76,43},{0x01A5,0x11BF,76,43},
55 {0x01A5,0x11C0,76,43},{0x01A5,0x11C1,76,43},{0x01A5,0x11C2,76,43},
56 {0x01A5,0x11C3,75,43},{0x01A5,0x11C4,76,43},{0x01A5,0x11C5,78,43},
57 {0x01A5,0x11C6,83,43},{0x01A5,0x11C7,83,43},{0x01A5,0x11C8,72,41},
58 {0x01A5,0x11C9,74,43},{0x01A5,0x11CA,72,41},{0x01A5,0x11CB,72,41},
59 {0x01A5,0x11CC,72,41},{0x019F,0x0E77,119,52}
62 {0x0181,0x1212,122,43},{0x0178,0x11EE,102,43},{0x019A,0x1236,100,42},
63 {0x0197,0x122A,105,42},{0x017E,0x11FA,107,42}
68 {0x00FF,0x0EA7,72,43},{0x00FF,0x0CA0,83,43},
69 {0x0105,0x0E4A,72,42},{0x0105,0x0CBA,104,42},
70 {0x0102,0x0808,73,42},{0x0102,0x0CAD,110,50},
71 {0x00FC,0x080D,74,41},{0x00FC,0x0C93,116,50},
72 {0x01A2,0x0EA7,72,43},{0x01A2,0x0EA6,83,43},
73 {0x019C,0x0E59,71,41},{0x019C,0x0E69,104,42},
74 {0x017A,0x0808,73,42},{0x017A,0x0807,110,50},
75 {0x0183,0x0923,74,41},{0x0183,0x0922,116,50},
76 {0x00FD,0x0C9E,83,43},{0x0103,0x0CB8,104,42},
77 {0x0100,0x0CAB,110,50},{0x00FA,0x0C91,116,50},
78 {0x01A3,0x1265,83,43},{0x01A3,0x125A,76,43},
79 {0x019D,0x124D,104,42},{0x019D,0x1242,100,42},
80 {0x017B,0x1211,110,50},{0x017B,0x1206,105,42},
81 {0x0184,0x1229,116,50},{0x0184,0x121E,108,42},
82 {0x0177,0x0303,87,34},{0x0177,0x0301,101,51},
83 {0x0180,0x08FB,88,33},{0x0180,0x090B,111,52},{0x0181,0x121D,129,79}
87 if (
root >= 0x0003002D &&
root <= 0x00030037)
88 return Descriptor{Kind::TokenPreview, {}, -1,
int(
root - 0x0003002D)};
89 if (
root >= 0x00030038 &&
root <= 0x0003003A)
return Descriptor{Kind::TokenFrame, {}};
93 return Descriptor{Kind::TokenThumbnail, {}, -1,
int(
token)};
94 if (
root >= 0x0002000B &&
root <= 0x0002000E)
return Descriptor{Kind::BankSummary,{}};
95 if (
root == 0x000200CD)
return Descriptor{Kind::DeedsFrame,{}};
96 if (
root == 0x000200C6)
return Descriptor{Kind::PortfolioFrame,{}};
97 if (
root == 0x000200D0)
return Descriptor{Kind::PortfolioAmount,{}};
98 if (
root >= 0x0002006D &&
root <= 0x00020074)
100 if (
root >= 0x00020075 &&
root <= 0x0002007C)
102 constexpr std::array<std::string_view,10>
digits{
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0"};
103 if (
root >= 0x0002007D &&
root <= 0x00020086)
104 return Descriptor{Kind::CalculatorKey,
digits[
root-0x0002007D]};
105 if (
root >= 0x00020087 &&
root <= 0x00020090)
106 return Descriptor{Kind::CalculatorSelectedKey,
digits[
root-0x00020087]};
108 if (
root >= 0x00020349 &&
root <= 0x0002034E)
109 return Descriptor{Kind::StatsPanel, {},
int(
root - 0x00020349)};
110 if (
root >= 0x0002034F &&
root <= 0x00020354)
111 return Descriptor{Kind::StatsPanel, {},
int(
root - 0x0002034F)};
112 if (
root == 0x0002006A)
return Descriptor{Kind::CalculatorPanel, {}};
113 if (
root == 0x00020091)
return Descriptor{Kind::CalculatorDescription, {}};
114 if (
root == 0x00050089)
return Descriptor{Kind::StatsBackground, {}};
116 if (
root == 0x00030000)
return Descriptor{Kind::AuctionBackground,{}};
117 if (
root == 0x0003036F)
return Descriptor{Kind::AuctionBottom, {}};
118 if (
root >= 0x00030370 &&
root <= 0x0003037B)
119 return Descriptor{Kind::AuctionPlayer, {},
int((
root - 0x00030370) % 6)};
120 if (
root == 0x00030384)
return Descriptor{Kind::AuctionBid, {}};
122 if (
root >= 0x00030025 &&
root <= 0x00030027)
return Descriptor{Kind::PlayerCard, {}};
124 if (
root == 0x00050003)
return Descriptor{Kind::Background,
"MONOPOLY"};
125 if (
root == 0x00030002)
return Descriptor{Kind::Pattern, {}};
127 if (
root == 0x0002006B)
return Descriptor{Kind::Slot, {}};
128 if (
root == 0x0002006C)
return Descriptor{Kind::SelectedSlot, {}};
132 if (tag == 0x118A)
return Descriptor{Kind::TradeCashPanel,{}};
133 if (tag >= 0x02D9 && tag <= 0x02DE)
135 constexpr std::array<std::string_view,3>
labels{
"Clear",
"Okay",
"Cancel"};
136 return Descriptor{(tag-0x02D9)%2 ? Kind::TradeCashPressed : Kind::TradeCashButton,
139 if (tag == 0x01FE)
return Descriptor{Kind::CalculatorKey,
"CLEAR"};
140 if (tag == 0x029B || tag == 0x0286 || tag == 0x022C || tag == 0x0006)
return Descriptor{Kind::StatsBar,{}};
141 if (tag == 0x029C)
return Descriptor{Kind::StatsBarHeading,
"Display status by:"};
142 if (tag == 0x0287)
return Descriptor{Kind::StatsBarHeading,
"Sort Players by:"};
143 if (tag == 0x022D)
return Descriptor{Kind::StatsBarHeading,
"Sort deeds by:"};
144 if (tag == 0x0007)
return Descriptor{Kind::StatsBarHeading,
"Show Summary of:"};
152 if (tag == 0x02CD)
return Descriptor{Kind::TradeBackground,{}};
153 if (tag == 0x02BB)
return Descriptor{Kind::TradePanel,{}};
154 if (tag == 0x02CF || tag == 0x02D0)
return Descriptor{Kind::TradeOffer,{}};
155 if (tag >= 0x02AD && tag <= 0x02BA)
return Descriptor{Kind::TradeRail,{},
int((tag-0x02AD)%7)};
156 if (tag == 0x02DF)
return Descriptor{Kind::TradeTitle,
"Trade this for this"};
157 if (tag == 0x01AF || tag == 0x01B1)
return Descriptor{Kind::TradeButton,
"Cancel"};
158 if (tag == 0x01B8 || tag == 0x01BA)
return Descriptor{Kind::TradeButton,
"Propose"};
160 if (tag == 0x02F4)
return Descriptor{Kind::Confirmation,
"Are you sure?"};
161 if (tag == 0x16BA)
return Descriptor{Kind::Button,
"Yes"};
162 if (tag == 0x0E4F)
return Descriptor{Kind::Button,
"No"};
164 constexpr std::array<Navigation, 4>
navigation{{
165 {0x018E,
"Files"}, {0x0192,
"Options"}, {0x0186,
"Credits"}, {0x018A,
"Help"}}};
168 return Descriptor{tag == item.
first + 2 ? Kind::SelectedTab : Kind::Tab, item.
label};
172 constexpr std::array<Button, 24>
buttons{{
173 {0x0279,
"Local game"}, {0x0283,
"Network game"},
174 {0x0256,
"Load saved game"}, {0x027E,
"Play"},
176 {0x0240,
"New game"}, {0x023D,
"Load game"},
177 {0x0243,
"Save game"}, {0x0237,
"Exit game"}, {0x0234,
"Cancel"},
178 {0x025F,
"New player"}, {0x0259,
"More names"}, {0x025C,
"Next"},
179 {0x0220,
"Add human player"}, {0x021D,
"Add computer player"},
180 {0x028A,
"Remove player"}, {0x029D,
"Start game"},
181 {0x0246,
"First time buyer"}, {0x0231,
"Entrepreneur"}, {0x02A8,
"Tycoon"},
182 {0x0223,
"Classic board"}, {0x0252,
"Load board"},
183 {0x02A1,
"Standard rules"}, {0x0229,
"Custom rules"}, {0x026D,
"OK"}
188 if (tag >= 0x0214 && tag <= 0x0216)
return Descriptor{Kind::CitySelector,
"Select city"};
190 if (tag >= 0x022E && tag <= 0x0230)
return Descriptor{Kind::Slot, {}};
191 if (tag == 0x026B || tag == 0x026C)
192 return Descriptor{tag == 0x026C ? Kind::SelectedToggle : Kind::Toggle,
"Off"};
193 if (tag == 0x0272 || tag == 0x0273)
194 return Descriptor{tag == 0x0273 ? Kind::SelectedToggle : Kind::Toggle,
"On"};
195 struct Title { data::DataTag tag; std::string_view
label; };
196 constexpr std::array<Title, 8>
titles{{{0x0288,
"Select player"}, {0x027D,
"Enter name"},
197 {0x0289,
"Choose token"}, {0x027C,
"Computer difficulty"}, {0x0293,
"Game rules"},
198 {0x0277,
"Options"}, {0x0278,
"Sound"}, {0x0262,
"Display"}}};
199 for (
const auto& title :
titles)
200 if (tag == title.tag)
return Descriptor{Kind::Title, title.
label};
201 if (tag == 0x0249)
return Descriptor{Kind::Title,
"Help menu"};
202 if (tag >= 0x024A && tag <= 0x024C)
return Descriptor{Kind::Button,
"Full help\n(game will pause)"};
203 if (tag >= 0x024F && tag <= 0x0251)
return Descriptor{Kind::Button,
"Quick help"};
204 if (tag == 0x023A)
return Descriptor{Kind::Title,
"File"};
205 if (tag == 0x0255)
return Descriptor{Kind::Title,
"Load game"};
206 if (tag == 0x02A4)
return Descriptor{Kind::Title,
"Save game"};
211 if (!
text.width || !
text.height ||
text.width > 4096 ||
text.height > 512 ||
212 text.pixels.size() != std::size_t(
text.width) *
text.height * 4 ||
218 const unsigned width = std::max(1U,
unsigned(
text.width * scale));
219 const unsigned height = std::max(1U,
unsigned(
text.height * scale));
223 for (
unsigned x = 0;
x <
width; ++
x)
228 constexpr std::array<unsigned, 3>
ink{245,235,211};
229 for (
unsigned c = 0;
c < 3; ++
c)
245 text.pixels.size()!=std::size_t(
text.width)*
text.height*4)
return false;
259 constexpr std::array<unsigned,3>
ink{245,235,211};
267 bool caption(data::LegacyBitmapRGBA8&
target,
const data::LegacyBitmapRGBA8&
text,
271 if (!
text.width || !
text.height ||
text.width > 4096 ||
text.height > 512 ||
272 text.pixels.size() != std::size_t(
text.width) *
text.height * 4 ||
275 const float scale = std::min({
heading ? 2.0F : 1.0F,
278 const auto w = std::max(1U,
unsigned(
text.width * scale));
279 const auto h = std::max(1U,
unsigned(
text.height * scale));
282 for (
unsigned y = 0;
y <
h; ++
y)
283 for (
unsigned x = 0;
x <
w; ++
x)
288 for (
unsigned c = 0;
c < 3; ++
c)
300 if (background_ ==
image)
return;
301 background_ = std::move(
image);
302 cache_.clear(); cachedBytes_ = 0;
308 cache_.clear(); cachedBytes_ = 0;
313 tradeCashPresentation_ = std::move(
qualified);
314 cache_.clear(); cachedBytes_ = 0;
336 const auto leaf = original->dataId;
338 leaf == 0x00051062 ||
leaf == 0x00051064 ||
leaf == 0x00051066 ||
leaf == 0x00051067 :
339 leaf == 0x0005106D ||
leaf == 0x0005106F ||
leaf == 0x00051071 ||
leaf == 0x00051072;
342 original->image.width != 59 || original->image.height != 33)
return original;
347 if (!
principal || !tradeCashPresentation_ || !tradeCashPresentation_() ||
349 !original->source || original->image.width != 800 || original->image.height != 450)
return original;
352 metadata->bitsPerPixel != 24 ||
metadata->compression != 0)
return original;
358 if (!
principal || !tradeCashPresentation_ || !tradeCashPresentation_() ||
366 constexpr std::array<unsigned,3>
first{0x1160,0x1175,0x118B};
367 constexpr std::array<unsigned,3>
widths{58,59,58},
origins{374,312,435};
378 original->image.width !=
width || original->image.height !=
height ||
386 (
root == 0x00030025 ||
root == 0x00030027) &&
387 original->dataId >= 0x00030426 && original->dataId <= 0x0003042A &&
389 original->image.width == 96 && original->image.height == 110)
397 (original->image.width != 97 || original->image.height != 110))))
return original;
398 const auto&
source = original->image;
404 const bool leaf = original->dataId >= 0x00050F03 && original->dataId <= 0x00050F05 &&
405 (
root != 0x00050215 || original->dataId == 0x00050F03);
407 !original->source ||
source.width != 210 ||
source.height != 133)
return original;
424 const bool title =
helpTag == 0x0249;
434 const bool trade =
descriptor.kind == Kind::TradeBackground ||
descriptor.kind == Kind::TradePanel ||
472 if (original->dataId == 0x00050914) {
width = 800;
height = 225; }
473 else if (original->dataId == 0x00050913) {
width = 400;
height = 225; }
474 else if (original->dataId == 0x00050916) {
width = 399;
height = 3; }
475 else return original;
476 expected = original->dataId;
482 {
constexpr std::array<data::DataId,4>
leaves{0x0002009E,0x0002009F,0x000200A0,0x000200A2};
486 else if (
descriptor.kind == Kind::PortfolioFrame)
488 else if (
descriptor.kind == Kind::PortfolioAmount)
492 else if (
descriptor.kind == Kind::CalculatorPanel)
493 { expected = 0x00020324;
width = 199;
height = 208; }
494 else { expected = 0x0002035A;
width = 188;
height = 209; }
511 for (
const auto owner :
control.roots)
516 for (
const auto&
frame : frames)
527 else if (tag == 0x0286 || tag == 0x022C || tag == 0x0006) {
leaf = 0x0CBB;
width = 313;
height = 42; }
547 else if (
root == 0x000501FE) { expected = 0x00050F14;
width = 50; }
549 else if (
descriptor.kind == Kind::CalculatorSelectedKey) expected = 0x0002033D + (
root - 0x00020087 + 1) % 10;
550 else expected = 0x00020333 + (
root - 0x0002007D + 1) % 10;
556 if (
descriptor.kind == Kind::TokenThumbnail && (
source.width != 100 ||
source.height != 41))
return original;
557 if (
descriptor.kind == Kind::TokenFrame && (
source.width != 254 ||
source.height != 194))
return original;
566 const bool auction =
descriptor.kind == Kind::AuctionBackground ||
descriptor.kind == Kind::AuctionBottom ||
572 constexpr std::array<std::array<unsigned,2>,12>
playerSizes{{
573 {200,92},{201,90},{200,91},{200,91},{200,92},{200,91},
574 {133,90},{134,90},{134,90},{134,90},{134,90},{134,90}}};
582 source.pixels.size() != std::size_t(
source.width) *
source.height * 4)
return original;
584 if (
const auto found = cache_.find(
key);
found != cache_.end())
585 {
found->second.lastUsed = ++cacheClock_;
return found->second.replacement; }
586 std::shared_ptr<const data::LegacyBitmapRGBA8>
tokenImage;
591 tokenImage->pixels.size() != std::size_t(768)*640*4)
return original;
593 auto replacement = std::make_shared<data::BitmapRuntimeAsset>();
594 replacement->dataId = original->dataId;
596 replacement->preferLinearFiltering =
true;
597 auto&
image = replacement->image;
603 if(
source.pixels[
at]!=255)
return original;
604 *replacement=*original;
606 constexpr std::array<unsigned,3>
teal{80,133,136};
607 for(
unsigned y=0;
y<280;++
y)
610 const unsigned weight=
y<240 ? 255U : (280-
y)*255U/40U;
611 for(
unsigned x=0;
x<800;++
x)
613 const auto at=(std::size_t(
y)*800+
x)*4;
616 for(
unsigned c=0;
c<3;++
c)
620 source.pixels[
at+
c]*(255U-weight)+127U)/255U);
627 else if (
descriptor.kind == Kind::TokenPreview)
630 replacement->presentationRect = std::array<float,4>{527.0F,275.5F,747.0F,458.5F};
640 const auto*
photo =
descriptor.kind == Kind::Background ? background_.get() :
nullptr;
644 const bool selected =
descriptor.kind == Kind::SelectedSlot ||
descriptor.kind == Kind::SelectedTab ||
646 descriptor.kind == Kind::CalculatorSelectedKey ||
647 descriptor.kind == Kind::CalculatorSelectedFunction ||
650 const bool background =
descriptor.kind == Kind::Background ||
descriptor.kind == Kind::Pattern ||
661 const std::array<unsigned, 3> fill =
selectedToggle ? std::array<unsigned, 3>{188, 157, 94} :
662 background ? std::array<unsigned, 3>{13, 35, 38} :
663 selected ? std::array<unsigned, 3>{42, 85, 82} : std::array<unsigned, 3>{22, 60, 61};
664 constexpr std::array<unsigned, 3>
brass{188, 157, 94};
670 constexpr std::array<unsigned,3>
citySkyInk{117,139,123};
674 [&](
unsigned x,
unsigned y, std::size_t
offset)
678 { rowGradient = 6 * (h - y) / h; sourceRow = std::size_t(y/3) * source.width; }
684 for (
unsigned c = 0;
c < 3; ++
c)
704 for (
unsigned c=0;
c<3;++
c)
725 for (
unsigned c = 0;
c < 3; ++
c)
729 for (
unsigned c = 0;
c < 3; ++
c)
732 for (
unsigned c = 0;
c < 3; ++
c)
image.pixels[
offset+
c] = std::uint8_t(
738 if (
descriptor.kind == Kind::TradeCashPanel &&
748 const auto src = (std::size_t(
py) *
photo->width +
px) * 4;
751 for (
unsigned c = 0;
c < 3; ++
c)
761 for(
unsigned y=0;
y<
ih;++
y)
for(
unsigned x=0;
x<
iw;++
x)
773 const unsigned count=
split==std::string_view::npos?1:2;
774 std::array<data::LegacyBitmapRGBA8,2>
lines;
776 if(!
first)
return original;
781 if(!second)
return original;
789 if (!
text)
return original;
797 std::array<unsigned,3>{245,235,211});
807 while (!cache_.empty() && (cache_.size() >= 64 ||
808 cachedBytes_ +
image.pixels.size() > 96U * 1024U * 1024U))
810 const auto oldest = std::ranges::min_element(cache_, {},
811 [](
const auto&
entry) {
return entry.second.lastUsed; });
812 cachedBytes_ -=
oldest->second.replacement->image.pixels.size();
815 cachedBytes_ +=
image.pixels.size();
816 cache_.emplace(
key, Entry{original, replacement, ++cacheClock_});
std::atomic< float > left
LegacyBitmapMetadata metadata
std::shared_ptr< const data::HmdTextureImage > source
constexpr DataTag dataTag(DataId id) noexcept
constexpr std::uint16_t legacyGroupValue(LegacyGroupId group) noexcept
std::expected< LegacyUapMetadata, BitmapError > inspectLegacyUap(std::span< const std::byte > bytes)
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
std::expected< LegacyBitmapMetadata, BitmapError > inspectLegacyBitmap(std::span< const std::byte > bytes)
constexpr std::uint16_t dataGroup(DataId id) noexcept