1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "../Context.h"
11 #include "../OpenRCT2.h"
12 #include "../PlatformEnvironment.h"
13 #include "../config/Config.h"
14 #include "../core/FileStream.h"
15 #include "../core/Path.hpp"
16 #include "../platform/platform.h"
17 #include "../sprites.h"
18 #include "../ui/UiContext.h"
19 #include "../util/Util.h"
20 #include "Drawing.h"
21 #include "ScrollingText.h"
22 
23 #include <algorithm>
24 #include <memory>
25 #include <stdexcept>
26 #include <vector>
27 
28 using namespace OpenRCT2;
29 using namespace OpenRCT2::Ui;
30 
31 // clang-format off
32 constexpr struct
33 {
34     int start;
35     int32_t x_offset;
36     int32_t y_offset;
37 }
38 sprite_peep_pickup_starts[15] =
39 {
40     {SPR_PEEP_PICKUP_GUEST_START, 0, 15},
41     {SPR_PEEP_PICKUP_HANDYMAN_START, 1, 18},
42     {SPR_PEEP_PICKUP_MECHANIC_START, 3, 22},
43     {SPR_PEEP_PICKUP_GUARD_START, 3, 15},
44     {SPR_PEEP_PICKUP_PANDA_START, -1, 19},
45     {SPR_PEEP_PICKUP_TIGER_START, -1, 17},
46     {SPR_PEEP_PICKUP_ELEPHANT_START, -1, 17},
47     {SPR_PEEP_PICKUP_GORILLA_START, 0, 17},
48     {SPR_PEEP_PICKUP_SNOWMAN_START, -1, 16},
49     {SPR_PEEP_PICKUP_KNIGHT_START, -2, 17},
50     {SPR_PEEP_PICKUP_BANDIT_START, 0, 16},
51     {SPR_PEEP_PICKUP_PIRATE_START, 0, 16},
52     {SPR_PEEP_PICKUP_SHERIFF_START, 0, 16},
53     {SPR_PEEP_PICKUP_ASTRONAUT_START, 0, 16},
54     {SPR_PEEP_PICKUP_ROMAN_START, -1, 17},
55 };
56 
rctc_to_rct2_index(uint32_t image)57 static inline uint32_t rctc_to_rct2_index(uint32_t image)
58 {
59     if (                  image <  1542) return image;
60     if (image >=  1574 && image <  4983) return image - 32;
61     if (image >=  4986 && image < 17189) return image - 35;
62     if (image >= 17191 && image < 18121) return image - 37;
63     if (image >= 18123 && image < 23800) return image - 39;
64     if (image >= 23804 && image < 24670) return image - 43;
65     if (image >= 24674 && image < 28244) return image - 47;
66     if (image >= 28246                 ) return image - 49;
67     throw std::runtime_error("Invalid RCTC g1.dat file");
68 }
69 // clang-format on
70 
read_and_convert_gxdat(IStream * stream,size_t count,bool is_rctc,rct_g1_element * elements)71 static void read_and_convert_gxdat(IStream* stream, size_t count, bool is_rctc, rct_g1_element* elements)
72 {
73     auto g1Elements32 = std::make_unique<rct_g1_element_32bit[]>(count);
74     stream->Read(g1Elements32.get(), count * sizeof(rct_g1_element_32bit));
75     if (is_rctc)
76     {
77         // Process RCTC's g1.dat file
78         uint32_t rctc = 0;
79         for (size_t i = 0; i < SPR_G1_END; ++i)
80         {
81             // RCTC's g1.dat has a number of additional elements
82             // added between the RCT2 elements. This switch
83             // statement skips over the elements we don't want.
84             switch (i)
85             {
86                 case 1542:
87                     rctc += 32;
88                     break;
89                 case 23761:
90                 case 24627:
91                     rctc += 4;
92                     break;
93                 case 4951:
94                     rctc += 3;
95                     break;
96                 case 17154:
97                 case 18084:
98                 case 28197:
99                     rctc += 2;
100                     break;
101             }
102 
103             const rct_g1_element_32bit& src = g1Elements32[rctc];
104 
105             // Double cast to silence compiler warning about casting to
106             // pointer from integer of mismatched length.
107             elements[i].offset = reinterpret_cast<uint8_t*>(static_cast<uintptr_t>(src.offset));
108             elements[i].width = src.width;
109             elements[i].height = src.height;
110             elements[i].x_offset = src.x_offset;
111             elements[i].y_offset = src.y_offset;
112             elements[i].flags = src.flags;
113 
114             if (src.flags & G1_FLAG_HAS_ZOOM_SPRITE)
115             {
116                 elements[i].zoomed_offset = static_cast<int32_t>(i - rctc_to_rct2_index(rctc - src.zoomed_offset));
117             }
118             else
119             {
120                 elements[i].zoomed_offset = src.zoomed_offset;
121             }
122 
123             ++rctc;
124         }
125 
126         // The pincer graphic for picking up peeps is different in
127         // RCTC, and the sprites have different offsets to accommodate
128         // the change. This reverts the offsets to their RCT2 values.
129         for (const auto& animation : sprite_peep_pickup_starts)
130         {
131             for (int i = 0; i < SPR_PEEP_PICKUP_COUNT; ++i)
132             {
133                 elements[animation.start + i].x_offset -= animation.x_offset;
134                 elements[animation.start + i].y_offset -= animation.y_offset;
135             }
136         }
137     }
138     else
139     {
140         for (size_t i = 0; i < count; i++)
141         {
142             const rct_g1_element_32bit& src = g1Elements32[i];
143 
144             // Double cast to silence compiler warning about casting to
145             // pointer from integer of mismatched length.
146             elements[i].offset = reinterpret_cast<uint8_t*>(static_cast<uintptr_t>(src.offset));
147             elements[i].width = src.width;
148             elements[i].height = src.height;
149             elements[i].x_offset = src.x_offset;
150             elements[i].y_offset = src.y_offset;
151             elements[i].flags = src.flags;
152             elements[i].zoomed_offset = src.zoomed_offset;
153         }
154     }
155 }
156 
mask_scalar(int32_t width,int32_t height,const uint8_t * RESTRICT maskSrc,const uint8_t * RESTRICT colourSrc,uint8_t * RESTRICT dst,int32_t maskWrap,int32_t colourWrap,int32_t dstWrap)157 void mask_scalar(
158     int32_t width, int32_t height, const uint8_t* RESTRICT maskSrc, const uint8_t* RESTRICT colourSrc, uint8_t* RESTRICT dst,
159     int32_t maskWrap, int32_t colourWrap, int32_t dstWrap)
160 {
161     for (int32_t yy = 0; yy < height; yy++)
162     {
163         for (int32_t xx = 0; xx < width; xx++)
164         {
165             uint8_t colour = (*colourSrc) & (*maskSrc);
166             if (colour != 0)
167             {
168                 *dst = colour;
169             }
170 
171             maskSrc++;
172             colourSrc++;
173             dst++;
174         }
175         maskSrc += maskWrap;
176         colourSrc += colourWrap;
177         dst += dstWrap;
178     }
179 }
180 
181 static rct_gx _g1 = {};
182 static rct_gx _g2 = {};
183 static rct_gx _csg = {};
184 static rct_g1_element _scrollingText[MaxScrollingTextEntries]{};
185 static bool _csgLoaded = false;
186 
187 static rct_g1_element _g1Temp = {};
188 static std::vector<rct_g1_element> _imageListElements;
189 bool gTinyFontAntiAliased = false;
190 
191 /**
192  *
193  *  rct2: 0x00678998
194  */
gfx_load_g1(const IPlatformEnvironment & env)195 bool gfx_load_g1(const IPlatformEnvironment& env)
196 {
197     log_verbose("gfx_load_g1(...)");
198     try
199     {
200         auto path = Path::Combine(env.GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA), "g1.dat");
201         auto fs = FileStream(path, FILE_MODE_OPEN);
202         _g1.header = fs.ReadValue<rct_g1_header>();
203 
204         log_verbose("g1.dat, number of entries: %u", _g1.header.num_entries);
205 
206         if (_g1.header.num_entries < SPR_G1_END)
207         {
208             throw std::runtime_error("Not enough elements in g1.dat");
209         }
210 
211         // Read element headers
212         bool is_rctc = _g1.header.num_entries == SPR_RCTC_G1_END;
213         _g1.elements.resize(_g1.header.num_entries);
214         read_and_convert_gxdat(&fs, _g1.header.num_entries, is_rctc, _g1.elements.data());
215         gTinyFontAntiAliased = is_rctc;
216 
217         // Read element data
218         _g1.data = fs.ReadArray<uint8_t>(_g1.header.total_size);
219 
220         // Fix entry data offsets
221         for (uint32_t i = 0; i < _g1.header.num_entries; i++)
222         {
223             _g1.elements[i].offset += reinterpret_cast<uintptr_t>(_g1.data.get());
224         }
225         return true;
226     }
227     catch (const std::exception&)
228     {
229         _g1.elements.clear();
230         _g1.elements.shrink_to_fit();
231 
232         log_fatal("Unable to load g1 graphics");
233         if (!gOpenRCT2Headless)
234         {
235             auto uiContext = GetContext()->GetUiContext();
236             uiContext->ShowMessageBox("Unable to load g1.dat. Your RollerCoaster Tycoon 2 path may be incorrectly set.");
237         }
238         return false;
239     }
240 }
241 
gfx_unload_g1()242 void gfx_unload_g1()
243 {
244     _g1.data.reset();
245     _g1.elements.clear();
246     _g1.elements.shrink_to_fit();
247 }
248 
gfx_unload_g2()249 void gfx_unload_g2()
250 {
251     _g2.data.reset();
252     _g2.elements.clear();
253     _g2.elements.shrink_to_fit();
254 }
255 
gfx_unload_csg()256 void gfx_unload_csg()
257 {
258     _csg.data.reset();
259     _csg.elements.clear();
260     _csg.elements.shrink_to_fit();
261 }
262 
gfx_load_g2()263 bool gfx_load_g2()
264 {
265     log_verbose("gfx_load_g2()");
266 
267     char path[MAX_PATH];
268 
269     platform_get_openrct2_data_path(path, sizeof(path));
270     safe_strcat_path(path, "g2.dat", MAX_PATH);
271     try
272     {
273         auto fs = FileStream(path, FILE_MODE_OPEN);
274         _g2.header = fs.ReadValue<rct_g1_header>();
275 
276         // Read element headers
277         _g2.elements.resize(_g2.header.num_entries);
278         read_and_convert_gxdat(&fs, _g2.header.num_entries, false, _g2.elements.data());
279 
280         // Read element data
281         _g2.data = fs.ReadArray<uint8_t>(_g2.header.total_size);
282 
283         // Fix entry data offsets
284         for (uint32_t i = 0; i < _g2.header.num_entries; i++)
285         {
286             _g2.elements[i].offset += reinterpret_cast<uintptr_t>(_g2.data.get());
287         }
288         return true;
289     }
290     catch (const std::exception&)
291     {
292         _g2.elements.clear();
293         _g2.elements.shrink_to_fit();
294 
295         log_fatal("Unable to load g2 graphics");
296         if (!gOpenRCT2Headless)
297         {
298             auto uiContext = GetContext()->GetUiContext();
299             uiContext->ShowMessageBox("Unable to load g2.dat");
300         }
301     }
302     return false;
303 }
304 
gfx_load_csg()305 bool gfx_load_csg()
306 {
307     log_verbose("gfx_load_csg()");
308 
309     if (str_is_null_or_empty(gConfigGeneral.rct1_path))
310     {
311         log_verbose("  unable to load CSG, RCT1 path not set");
312         return false;
313     }
314 
315     auto pathHeaderPath = FindCsg1idatAtLocation(gConfigGeneral.rct1_path);
316     auto pathDataPath = FindCsg1datAtLocation(gConfigGeneral.rct1_path);
317     try
318     {
319         auto fileHeader = FileStream(pathHeaderPath, FILE_MODE_OPEN);
320         auto fileData = FileStream(pathDataPath, FILE_MODE_OPEN);
321         size_t fileHeaderSize = fileHeader.GetLength();
322         size_t fileDataSize = fileData.GetLength();
323 
324         _csg.header.num_entries = static_cast<uint32_t>(fileHeaderSize / sizeof(rct_g1_element_32bit));
325         _csg.header.total_size = static_cast<uint32_t>(fileDataSize);
326 
327         if (!CsgIsUsable(_csg))
328         {
329             log_warning("Cannot load CSG1.DAT, it has too few entries. Only CSG1.DAT from Loopy Landscapes will work.");
330             return false;
331         }
332 
333         // Read element headers
334         _csg.elements.resize(_csg.header.num_entries);
335         read_and_convert_gxdat(&fileHeader, _csg.header.num_entries, false, _csg.elements.data());
336 
337         // Read element data
338         _csg.data = fileData.ReadArray<uint8_t>(_csg.header.total_size);
339 
340         // Fix entry data offsets
341         for (uint32_t i = 0; i < _csg.header.num_entries; i++)
342         {
343             _csg.elements[i].offset += reinterpret_cast<uintptr_t>(_csg.data.get());
344             // RCT1 used zoomed offsets that counted from the beginning of the file, rather than from the current sprite.
345             if (_csg.elements[i].flags & G1_FLAG_HAS_ZOOM_SPRITE)
346             {
347                 _csg.elements[i].zoomed_offset = i - _csg.elements[i].zoomed_offset;
348             }
349         }
350         _csgLoaded = true;
351         return true;
352     }
353     catch (const std::exception&)
354     {
355         _csg.elements.clear();
356         _csg.elements.shrink_to_fit();
357 
358         log_error("Unable to load csg graphics");
359         return false;
360     }
361 }
362 
gfx_draw_sprite_get_palette(ImageId imageId)363 static std::optional<PaletteMap> FASTCALL gfx_draw_sprite_get_palette(ImageId imageId)
364 {
365     if (!imageId.HasSecondary())
366     {
367         uint8_t paletteId = imageId.GetRemap();
368         if (!imageId.IsBlended())
369         {
370             paletteId &= 0x7F;
371         }
372         return GetPaletteMapForColour(paletteId);
373     }
374 
375     auto paletteMap = PaletteMap(gPeepPalette);
376     if (imageId.HasTertiary())
377     {
378         paletteMap = PaletteMap(gOtherPalette);
379         auto tertiaryPaletteMap = GetPaletteMapForColour(imageId.GetTertiary());
380         if (tertiaryPaletteMap.has_value())
381         {
382             paletteMap.Copy(
383                 PALETTE_OFFSET_REMAP_TERTIARY, tertiaryPaletteMap.value(), PALETTE_OFFSET_REMAP_PRIMARY, PALETTE_LENGTH_REMAP);
384         }
385     }
386 
387     auto primaryPaletteMap = GetPaletteMapForColour(imageId.GetPrimary());
388     if (primaryPaletteMap.has_value())
389     {
390         paletteMap.Copy(
391             PALETTE_OFFSET_REMAP_PRIMARY, primaryPaletteMap.value(), PALETTE_OFFSET_REMAP_PRIMARY, PALETTE_LENGTH_REMAP);
392     }
393 
394     auto secondaryPaletteMap = GetPaletteMapForColour(imageId.GetSecondary());
395     if (secondaryPaletteMap.has_value())
396     {
397         paletteMap.Copy(
398             PALETTE_OFFSET_REMAP_SECONDARY, secondaryPaletteMap.value(), PALETTE_OFFSET_REMAP_PRIMARY, PALETTE_LENGTH_REMAP);
399     }
400 
401     return paletteMap;
402 }
403 
gfx_draw_sprite_software(rct_drawpixelinfo * dpi,ImageId imageId,const ScreenCoordsXY & spriteCoords)404 void FASTCALL gfx_draw_sprite_software(rct_drawpixelinfo* dpi, ImageId imageId, const ScreenCoordsXY& spriteCoords)
405 {
406     if (imageId.HasValue())
407     {
408         auto palette = gfx_draw_sprite_get_palette(imageId);
409         if (!palette)
410         {
411             palette = PaletteMap::GetDefault();
412         }
413         gfx_draw_sprite_palette_set_software(dpi, imageId, spriteCoords, *palette);
414     }
415 }
416 
417 /*
418  * rct: 0x0067A46E
419  * image_id (ebx) and also (0x00EDF81C)
420  * palette_pointer (0x9ABDA4)
421  * unknown_pointer (0x9E3CDC)
422  * dpi (edi)
423  * x (cx)
424  * y (dx)
425  */
gfx_draw_sprite_palette_set_software(rct_drawpixelinfo * dpi,ImageId imageId,const ScreenCoordsXY & coords,const PaletteMap & paletteMap)426 void FASTCALL gfx_draw_sprite_palette_set_software(
427     rct_drawpixelinfo* dpi, ImageId imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap)
428 {
429     int32_t x = coords.x;
430     int32_t y = coords.y;
431 
432     const auto* g1 = gfx_get_g1_element(imageId);
433     if (g1 == nullptr)
434     {
435         return;
436     }
437 
438     if (dpi->zoom_level > 0 && (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE))
439     {
440         rct_drawpixelinfo zoomed_dpi = *dpi;
441         zoomed_dpi.bits = dpi->bits;
442         zoomed_dpi.x = dpi->x >> 1;
443         zoomed_dpi.y = dpi->y >> 1;
444         zoomed_dpi.height = dpi->height >> 1;
445         zoomed_dpi.width = dpi->width >> 1;
446         zoomed_dpi.pitch = dpi->pitch;
447         zoomed_dpi.zoom_level = dpi->zoom_level - 1;
448 
449         const auto spriteCoords = ScreenCoordsXY{ x >> 1, y >> 1 };
450         gfx_draw_sprite_palette_set_software(
451             &zoomed_dpi, imageId.WithIndex(imageId.GetIndex() - g1->zoomed_offset), spriteCoords, paletteMap);
452         return;
453     }
454 
455     if (dpi->zoom_level > 0 && (g1->flags & G1_FLAG_NO_ZOOM_DRAW))
456     {
457         return;
458     }
459 
460     // Its used super often so we will define it to a separate variable.
461     auto zoom_level = dpi->zoom_level;
462     int32_t zoom_mask = zoom_level > 0 ? 0xFFFFFFFF * zoom_level : 0xFFFFFFFF;
463 
464     if (zoom_level > 0 && g1->flags & G1_FLAG_RLE_COMPRESSION)
465     {
466         x -= ~zoom_mask;
467         y -= ~zoom_mask;
468     }
469 
470     // This will be the height of the drawn image
471     int32_t height = g1->height;
472 
473     // This is the start y coordinate on the destination
474     int16_t dest_start_y = y + g1->y_offset;
475 
476     // For whatever reason the RLE version does not use
477     // the zoom mask on the y coordinate but does on x.
478     if (g1->flags & G1_FLAG_RLE_COMPRESSION)
479     {
480         dest_start_y -= dpi->y;
481     }
482     else
483     {
484         dest_start_y = (dest_start_y & zoom_mask) - dpi->y;
485     }
486     // This is the start y coordinate on the source
487     int32_t source_start_y = 0;
488 
489     if (dest_start_y < 0)
490     {
491         // If the destination y is negative reduce the height of the
492         // image as we will cut off the bottom
493         height += dest_start_y;
494         // If the image is no longer visible nothing to draw
495         if (height <= 0)
496         {
497             return;
498         }
499         // The source image will start a further up the image
500         source_start_y -= dest_start_y;
501         // The destination start is now reset to 0
502         dest_start_y = 0;
503     }
504     else
505     {
506         if ((g1->flags & G1_FLAG_RLE_COMPRESSION) && zoom_level > 0)
507         {
508             source_start_y -= dest_start_y & ~zoom_mask;
509             height += dest_start_y & ~zoom_mask;
510         }
511     }
512 
513     int32_t dest_end_y = dest_start_y + height;
514 
515     if (dest_end_y > dpi->height)
516     {
517         // If the destination y is outside of the drawing
518         // image reduce the height of the image
519         height -= dest_end_y - dpi->height;
520     }
521     // If the image no longer has anything to draw
522     if (height <= 0)
523         return;
524 
525     dest_start_y = dest_start_y / zoom_level;
526 
527     // This will be the width of the drawn image
528     int32_t width = g1->width;
529 
530     // This is the source start x coordinate
531     int32_t source_start_x = 0;
532     // This is the destination start x coordinate
533     int16_t dest_start_x = ((x + g1->x_offset + ~zoom_mask) & zoom_mask) - dpi->x;
534 
535     if (dest_start_x < 0)
536     {
537         // If the destination is negative reduce the width
538         // image will cut off the side
539         width += dest_start_x;
540         // If there is no image to draw
541         if (width <= 0)
542         {
543             return;
544         }
545         // The source start will also need to cut off the side
546         source_start_x -= dest_start_x;
547         // Reset the destination to 0
548         dest_start_x = 0;
549     }
550     else
551     {
552         if ((g1->flags & G1_FLAG_RLE_COMPRESSION) && zoom_level > 0)
553         {
554             source_start_x -= dest_start_x & ~zoom_mask;
555         }
556     }
557 
558     int32_t dest_end_x = dest_start_x + width;
559 
560     if (dest_end_x > dpi->width)
561     {
562         // If the destination x is outside of the drawing area
563         // reduce the image width.
564         width -= dest_end_x - dpi->width;
565         // If there is no image to draw.
566         if (width <= 0)
567             return;
568     }
569 
570     dest_start_x = dest_start_x / zoom_level;
571 
572     uint8_t* dest_pointer = dpi->bits;
573     // Move the pointer to the start point of the destination
574     dest_pointer += ((dpi->width / zoom_level) + dpi->pitch) * dest_start_y + dest_start_x;
575 
576     DrawSpriteArgs args(imageId, paletteMap, *g1, source_start_x, source_start_y, width, height, dest_pointer);
577     gfx_sprite_to_buffer(*dpi, args);
578 }
579 
gfx_sprite_to_buffer(rct_drawpixelinfo & dpi,const DrawSpriteArgs & args)580 void FASTCALL gfx_sprite_to_buffer(rct_drawpixelinfo& dpi, const DrawSpriteArgs& args)
581 {
582     if (args.SourceImage.flags & G1_FLAG_RLE_COMPRESSION)
583     {
584         gfx_rle_sprite_to_buffer(dpi, args);
585     }
586     else if (!(args.SourceImage.flags & G1_FLAG_1))
587     {
588         gfx_bmp_sprite_to_buffer(dpi, args);
589     }
590 }
591 
592 /**
593  * Draws the given colour image masked out by the given mask image. This can currently only cope with bitmap formatted mask and
594  * colour images. Presumably the original game never used RLE images for masking. Colour 0 represents transparent.
595  *
596  *  rct2: 0x00681DE2
597  */
gfx_draw_sprite_raw_masked_software(rct_drawpixelinfo * dpi,const ScreenCoordsXY & scrCoords,int32_t maskImage,int32_t colourImage)598 void FASTCALL gfx_draw_sprite_raw_masked_software(
599     rct_drawpixelinfo* dpi, const ScreenCoordsXY& scrCoords, int32_t maskImage, int32_t colourImage)
600 {
601     int32_t left, top, right, bottom, width, height;
602     auto imgMask = gfx_get_g1_element(maskImage & 0x7FFFF);
603     auto imgColour = gfx_get_g1_element(colourImage & 0x7FFFF);
604     if (imgMask == nullptr || imgColour == nullptr)
605     {
606         return;
607     }
608 
609     // Only BMP format is supported for masking
610     if (!(imgMask->flags & G1_FLAG_BMP) || !(imgColour->flags & G1_FLAG_BMP))
611     {
612         gfx_draw_sprite_software(dpi, ImageId::FromUInt32(colourImage), scrCoords);
613         return;
614     }
615 
616     if (dpi->zoom_level != 0)
617     {
618         // TODO: Implement other zoom levels (probably not used though)
619         assert(false);
620         return;
621     }
622 
623     width = std::min(imgMask->width, imgColour->width);
624     height = std::min(imgMask->height, imgColour->height);
625 
626     auto offsetCoords = scrCoords + ScreenCoordsXY{ imgMask->x_offset, imgMask->y_offset };
627 
628     left = std::max<int32_t>(dpi->x, offsetCoords.x);
629     top = std::max<int32_t>(dpi->y, offsetCoords.y);
630     right = std::min(dpi->x + dpi->width, offsetCoords.x + width);
631     bottom = std::min(dpi->y + dpi->height, offsetCoords.y + height);
632 
633     width = right - left;
634     height = bottom - top;
635     if (width < 0 || height < 0)
636         return;
637 
638     int32_t skipX = left - offsetCoords.x;
639     int32_t skipY = top - offsetCoords.y;
640 
641     uint8_t const* maskSrc = imgMask->offset + (skipY * imgMask->width) + skipX;
642     uint8_t const* colourSrc = imgColour->offset + (skipY * imgColour->width) + skipX;
643     uint8_t* dst = dpi->bits + (left - dpi->x) + ((top - dpi->y) * (dpi->width + dpi->pitch));
644 
645     int32_t maskWrap = imgMask->width - width;
646     int32_t colourWrap = imgColour->width - width;
647     int32_t dstWrap = ((dpi->width + dpi->pitch) - width);
648 
649     mask_fn(width, height, maskSrc, colourSrc, dst, maskWrap, colourWrap, dstWrap);
650 }
651 
gfx_get_g1_element(ImageId imageId)652 const rct_g1_element* gfx_get_g1_element(ImageId imageId)
653 {
654     return gfx_get_g1_element(imageId.GetIndex());
655 }
656 
gfx_get_g1_element(int32_t image_id)657 const rct_g1_element* gfx_get_g1_element(int32_t image_id)
658 {
659     openrct2_assert(!gOpenRCT2NoGraphics, "gfx_get_g1_element called on headless instance");
660 
661     auto offset = static_cast<size_t>(image_id);
662     if (offset == 0x7FFFF)
663     {
664         return nullptr;
665     }
666 
667     if (offset == SPR_TEMP)
668     {
669         return &_g1Temp;
670     }
671 
672     if (offset < SPR_RCTC_G1_END)
673     {
674         if (offset < _g1.elements.size())
675         {
676             return &_g1.elements[offset];
677         }
678     }
679     else if (offset < SPR_G2_END)
680     {
681         size_t idx = offset - SPR_G2_BEGIN;
682         if (idx < _g2.header.num_entries)
683         {
684             return &_g2.elements[idx];
685         }
686 
687         log_warning("Invalid entry in g2.dat requested, idx = %u. You may have to update your g2.dat.", idx);
688     }
689     else if (offset < SPR_CSG_END)
690     {
691         if (is_csg_loaded())
692         {
693             size_t idx = offset - SPR_CSG_BEGIN;
694             if (idx < _csg.header.num_entries)
695             {
696                 return &_csg.elements[idx];
697             }
698 
699             log_warning("Invalid entry in csg.dat requested, idx = %u.", idx);
700         }
701     }
702     else if (offset < SPR_SCROLLING_TEXT_END)
703     {
704         size_t idx = offset - SPR_SCROLLING_TEXT_START;
705         if (idx < std::size(_scrollingText))
706         {
707             return &_scrollingText[idx];
708         }
709     }
710     else if (offset < SPR_IMAGE_LIST_END)
711     {
712         size_t idx = offset - SPR_IMAGE_LIST_BEGIN;
713         if (idx < _imageListElements.size())
714         {
715             return &_imageListElements[idx];
716         }
717     }
718     return nullptr;
719 }
720 
gfx_set_g1_element(int32_t imageId,const rct_g1_element * g1)721 void gfx_set_g1_element(int32_t imageId, const rct_g1_element* g1)
722 {
723     bool isTemp = imageId == SPR_TEMP;
724     bool isValid = (imageId >= SPR_IMAGE_LIST_BEGIN && imageId < SPR_IMAGE_LIST_END)
725         || (imageId >= SPR_SCROLLING_TEXT_START && imageId < SPR_SCROLLING_TEXT_END);
726 
727 #ifdef DEBUG
728     openrct2_assert(!gOpenRCT2NoGraphics, "gfx_set_g1_element called on headless instance");
729     openrct2_assert(isValid || isTemp, "gfx_set_g1_element called with unexpected image id");
730     openrct2_assert(g1 != nullptr, "g1 was nullptr");
731 #endif
732 
733     if (g1 != nullptr)
734     {
735         if (isTemp)
736         {
737             _g1Temp = *g1;
738         }
739         else if (isValid)
740         {
741             if (imageId < SPR_RCTC_G1_END)
742             {
743                 if (imageId < static_cast<int32_t>(_g1.elements.size()))
744                 {
745                     _g1.elements[imageId] = *g1;
746                 }
747             }
748             else if (imageId < SPR_SCROLLING_TEXT_END)
749             {
750                 size_t idx = static_cast<size_t>(imageId) - SPR_SCROLLING_TEXT_START;
751                 if (idx < std::size(_scrollingText))
752                 {
753                     _scrollingText[idx] = *g1;
754                 }
755             }
756             else
757             {
758                 size_t idx = static_cast<size_t>(imageId) - SPR_IMAGE_LIST_BEGIN;
759                 // Grow the element buffer if necessary
760                 while (idx >= _imageListElements.size())
761                 {
762                     _imageListElements.resize(std::max<size_t>(256, _imageListElements.size() * 2));
763                 }
764                 _imageListElements[idx] = *g1;
765             }
766         }
767     }
768 }
769 
is_csg_loaded()770 bool is_csg_loaded()
771 {
772     return _csgLoaded;
773 }
774 
gfx_get_sprite_size(uint32_t image_id)775 rct_size16 FASTCALL gfx_get_sprite_size(uint32_t image_id)
776 {
777     const rct_g1_element* g1 = gfx_get_g1_element(image_id & 0X7FFFF);
778     rct_size16 size = {};
779     if (g1 != nullptr)
780     {
781         size.width = g1->width;
782         size.height = g1->height;
783     }
784     return size;
785 }
786 
g1_calculate_data_size(const rct_g1_element * g1)787 size_t g1_calculate_data_size(const rct_g1_element* g1)
788 {
789     if (g1->flags & G1_FLAG_PALETTE)
790     {
791         return g1->width * 3;
792     }
793 
794     if (g1->flags & G1_FLAG_RLE_COMPRESSION)
795     {
796         if (g1->offset == nullptr)
797         {
798             return 0;
799         }
800 
801         auto idx = (g1->height - 1) * 2;
802         uint16_t offset = g1->offset[idx] | (g1->offset[idx + 1] << 8);
803         uint8_t* ptr = g1->offset + offset;
804         bool endOfLine = false;
805         do
806         {
807             uint8_t chunk0 = *ptr++;
808             ptr++; // offset
809             uint8_t chunkSize = chunk0 & 0x7F;
810             ptr += chunkSize;
811             endOfLine = (chunk0 & 0x80) != 0;
812         } while (!endOfLine);
813         return ptr - g1->offset;
814     }
815 
816     return g1->width * g1->height;
817 }
818