2017-06-01 21:55:10 +02:00
|
|
|
#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
|
2016-10-15 21:44:51 +02:00
|
|
|
/*****************************************************************************
|
|
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
|
|
*
|
|
|
|
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
|
|
|
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
|
|
|
|
*
|
|
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* A full copy of the GNU General Public License can be found in licence.txt
|
|
|
|
*****************************************************************************/
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
#include "PaintIntercept.hpp"
|
2016-12-19 18:45:08 +01:00
|
|
|
#include "FunctionCall.hpp"
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
extern "C" {
|
2016-12-28 14:16:15 +01:00
|
|
|
#include <openrct2/common.h>
|
|
|
|
#include <openrct2/rct2/hook.h>
|
|
|
|
#include <openrct2/interface/viewport.h>
|
|
|
|
#include <openrct2/paint/supports.h>
|
|
|
|
#include <openrct2/sprites.h>
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
static bool _woodenSupports = false;
|
|
|
|
static uint8 _callCount = 0;
|
|
|
|
static function_call _calls[256] = {0};
|
2016-12-19 18:45:08 +01:00
|
|
|
static paint_struct _paintStructs = {0};
|
2016-10-17 12:42:59 +02:00
|
|
|
|
|
|
|
namespace PaintIntercept {
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptWoodenASupports(registers *regs);
|
|
|
|
static uint8 InterceptWoodenBSupports(registers *regs);
|
|
|
|
static uint8 InterceptMetalASupports(registers *regs);
|
|
|
|
static uint8 InterceptMetalBSupports(registers *regs);
|
|
|
|
static uint8 InterceptPaint6C(registers *regs);
|
|
|
|
static uint8 InterceptPaint7C(registers *regs);
|
|
|
|
static uint8 InterceptPaint8C(registers *regs);
|
|
|
|
static uint8 InterceptPaint9C(registers *regs);
|
|
|
|
static uint8 InterceptPaintFull(uint8 function, registers *regs);
|
|
|
|
|
2016-10-17 21:28:27 +02:00
|
|
|
bool PaintMetalSupports(uint8 function, int supportType, uint8 segment, int special, int height, uint32 imageColourFlags);
|
|
|
|
bool PaintWoodenSupports(uint8 function, int supportType, int special, int height, uint32 imageColourFlags, bool *underground);
|
2016-10-17 12:42:59 +02:00
|
|
|
static void CheckSegmentSupportHeight();
|
|
|
|
|
|
|
|
void InitHooks() {
|
2016-11-13 18:32:32 +01:00
|
|
|
addhook(0x006629BC, InterceptWoodenASupports);
|
|
|
|
addhook(0x00662D5C, InterceptWoodenBSupports);
|
|
|
|
|
|
|
|
addhook(0x00663105, InterceptMetalASupports);
|
|
|
|
addhook(0x00663584, InterceptMetalBSupports);
|
|
|
|
|
|
|
|
addhook(0x006861AC, InterceptPaint6C);
|
|
|
|
addhook(0x00686337, InterceptPaint6C);
|
|
|
|
addhook(0x006864D0, InterceptPaint6C);
|
|
|
|
addhook(0x0068666B, InterceptPaint6C);
|
|
|
|
|
|
|
|
addhook(0x00686806, InterceptPaint7C);
|
|
|
|
addhook(0x006869B2, InterceptPaint7C);
|
|
|
|
addhook(0x00686B6F, InterceptPaint7C);
|
|
|
|
addhook(0x00686D31, InterceptPaint7C);
|
|
|
|
|
|
|
|
addhook(0x00686EF0, InterceptPaint8C);
|
|
|
|
addhook(0x00687056, InterceptPaint8C);
|
|
|
|
addhook(0x006871C8, InterceptPaint8C);
|
|
|
|
addhook(0x0068733C, InterceptPaint8C);
|
|
|
|
|
|
|
|
addhook(0x006874B0, InterceptPaint9C);
|
|
|
|
addhook(0x00687618, InterceptPaint9C);
|
|
|
|
addhook(0x0068778C, InterceptPaint9C);
|
|
|
|
addhook(0x00687902, InterceptPaint9C);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
bool PaintWoodenSupports(uint8 function, int supportType, int special, int height, uint32 imageColourFlags, bool *underground) {
|
2016-12-19 18:45:08 +01:00
|
|
|
function_call * call = &_calls[_callCount];
|
|
|
|
call->function = function;
|
|
|
|
call->supports.type = supportType;
|
|
|
|
call->supports.special = special;
|
|
|
|
call->supports.height = height;
|
|
|
|
call->supports.colour_flags = imageColourFlags;
|
|
|
|
|
|
|
|
call->supports.prepend_to = SPR_NONE;
|
|
|
|
if (gWoodenSupportsPrependTo != nullptr)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < _callCount; i++)
|
|
|
|
{
|
|
|
|
if (&_calls[i].paint.output_struct == gWoodenSupportsPrependTo)
|
|
|
|
{
|
|
|
|
call->supports.prepend_to = _calls[i].paint.image_id;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
_callCount++;
|
|
|
|
|
|
|
|
return _woodenSupports;
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
bool PaintMetalSupports(uint8 function, int supportType, uint8 segment, int special, int height, uint32 imageColourFlags) {
|
2016-10-15 21:44:51 +02:00
|
|
|
CheckSegmentSupportHeight();
|
|
|
|
|
2016-12-19 18:45:08 +01:00
|
|
|
function_call * call = &_calls[_callCount];
|
|
|
|
call->function = function;
|
|
|
|
call->supports.type = supportType;
|
|
|
|
call->supports.segment = segment;
|
|
|
|
call->supports.special = special;
|
|
|
|
call->supports.height = height;
|
|
|
|
call->supports.colour_flags = imageColourFlags;
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
_callCount++;
|
2016-10-16 15:00:59 +02:00
|
|
|
|
2016-10-15 21:44:51 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static paint_struct *Paint6C(
|
|
|
|
uint32 imageID,
|
|
|
|
sint8 xOffset, sint8 yOffset,
|
|
|
|
sint16 boundBoxLengthX, sint16 boundBoxLengthY, sint8 boundBoxLengthZ,
|
|
|
|
sint16 zOffset,
|
|
|
|
uint32 rotation
|
|
|
|
) {
|
2016-12-19 18:45:08 +01:00
|
|
|
function_call * call = &_calls[_callCount];
|
|
|
|
call->function = PAINT_98196C;
|
|
|
|
call->paint.image_id = imageID;
|
|
|
|
call->paint.offset = {xOffset, yOffset};
|
|
|
|
call->paint.bound_box_length = {boundBoxLengthX, boundBoxLengthY, boundBoxLengthZ};
|
|
|
|
call->paint.z_offset = zOffset;
|
|
|
|
call->paint.rotation = rotation;
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
_callCount++;
|
|
|
|
|
2016-12-19 18:45:08 +01:00
|
|
|
return &call->paint.output_struct;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static paint_struct *PaintFull(
|
|
|
|
uint8 function,
|
|
|
|
uint32 imageID,
|
|
|
|
sint8 xOffset, sint8 yOffset,
|
|
|
|
sint16 boundBoxLengthX, sint16 boundBoxLengthY, sint8 boundBoxLengthZ,
|
|
|
|
sint16 zOffset,
|
|
|
|
sint16 boundBoxOffsetX, sint16 boundBoxOffsetY, sint16 boundBoxOffsetZ,
|
|
|
|
uint32 rotation
|
|
|
|
) {
|
2016-12-19 18:45:08 +01:00
|
|
|
function_call * call = &_calls[_callCount];
|
|
|
|
call->function = function;
|
|
|
|
call->paint.image_id = imageID;
|
|
|
|
call->paint.offset = {xOffset, yOffset};
|
|
|
|
call->paint.bound_box_length = {boundBoxLengthX, boundBoxLengthY, boundBoxLengthZ};
|
|
|
|
call->paint.bound_box_offset = {boundBoxOffsetX, boundBoxOffsetY, boundBoxOffsetZ};
|
|
|
|
call->paint.z_offset = zOffset;
|
|
|
|
call->paint.rotation = rotation;
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
_callCount++;
|
|
|
|
|
2016-12-19 18:45:08 +01:00
|
|
|
return &call->paint.output_struct;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
void ClearCalls() {
|
2016-10-15 21:44:51 +02:00
|
|
|
_callCount = 0;
|
|
|
|
memset(_calls, 0, sizeof(_calls));
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
int GetCalls(function_call *buffer) {
|
2016-10-15 21:44:51 +02:00
|
|
|
memcpy(buffer, _calls, _callCount * sizeof(function_call));
|
|
|
|
return _callCount;
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:42:59 +02:00
|
|
|
void SetSimulateWoodenSupports(bool enabled) {
|
2016-10-15 21:44:51 +02:00
|
|
|
_woodenSupports = enabled;
|
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptMetalASupports(registers *regs)
|
|
|
|
{
|
|
|
|
bool output = PaintMetalSupports(SUPPORTS_METAL_A, regs->edi, regs->ebx, (sint16) regs->ax, regs->dx, regs->ebp);
|
2016-10-15 21:44:51 +02:00
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
return output ? X86_FLAG_CARRY : 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptMetalBSupports(registers *regs)
|
|
|
|
{
|
|
|
|
bool output = PaintMetalSupports(SUPPORTS_METAL_B, regs->edi, regs->ebx, (sint16) regs->ax, regs->dx, regs->ebp);
|
2016-10-15 21:44:51 +02:00
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
return output ? X86_FLAG_CARRY : 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void CheckSegmentSupportHeight() {
|
2016-10-16 21:15:40 +02:00
|
|
|
bool hasChanged = false;
|
|
|
|
for (int i = 0; i < 9; i++) {
|
|
|
|
if (gSupportSegments[i].height != 0) hasChanged = true;
|
|
|
|
if (gSupportSegments[i].slope != 0xFF) hasChanged = true;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-10-16 21:15:40 +02:00
|
|
|
if (!hasChanged) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-15 21:44:51 +02:00
|
|
|
|
2016-10-16 15:00:59 +02:00
|
|
|
function_call call = {0};
|
|
|
|
call.function = SET_SEGMENT_HEIGHT;
|
2016-10-15 21:44:51 +02:00
|
|
|
|
|
|
|
_calls[_callCount] = call;
|
|
|
|
_callCount++;
|
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptWoodenASupports(registers *regs)
|
|
|
|
{
|
|
|
|
bool cf = false;
|
|
|
|
regs->al = PaintWoodenSupports(SUPPORTS_WOOD_A, regs->edi, regs->ax, regs->dx, regs->ebp, &cf);
|
2016-10-15 21:44:51 +02:00
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
if (cf)
|
|
|
|
{
|
|
|
|
return X86_FLAG_CARRY;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptWoodenBSupports(registers *regs)
|
|
|
|
{
|
|
|
|
bool cf = false;
|
|
|
|
regs->al = PaintWoodenSupports(SUPPORTS_WOOD_B, regs->edi, regs->ax, regs->dx, regs->ebp, &cf);
|
|
|
|
|
|
|
|
if (cf)
|
|
|
|
{
|
|
|
|
return X86_FLAG_CARRY;
|
|
|
|
}
|
2016-10-15 21:44:51 +02:00
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
return 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptPaint6C(registers *regs)
|
|
|
|
{
|
|
|
|
if ((regs->ebp & 0x03) != get_current_rotation())
|
|
|
|
{
|
2016-10-15 21:44:51 +02:00
|
|
|
// Log error
|
|
|
|
log_error("Ebp is different from current rotation");
|
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
paint_struct *out = Paint6C(
|
|
|
|
regs->ebx,
|
|
|
|
(sint8) regs->al, (sint8) regs->cl,
|
|
|
|
(sint16) regs->di, (sint16) regs->si, (sint8) regs->ah,
|
|
|
|
regs->dx,
|
|
|
|
regs->ebp & 0x03
|
2016-10-15 21:44:51 +02:00
|
|
|
);
|
2016-11-13 18:32:32 +01:00
|
|
|
|
|
|
|
if (out == nullptr)
|
|
|
|
{
|
|
|
|
return X86_FLAG_CARRY;
|
|
|
|
}
|
|
|
|
|
|
|
|
regs->ebp = (int) out;
|
2016-12-19 18:45:08 +01:00
|
|
|
regs->al = 1;
|
2016-11-13 18:32:32 +01:00
|
|
|
return 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptPaint7C(registers *regs)
|
|
|
|
{
|
|
|
|
return InterceptPaintFull(PAINT_98197C, regs);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptPaint8C(registers *regs)
|
|
|
|
{
|
|
|
|
return InterceptPaintFull(PAINT_98198C, regs);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptPaint9C(registers *regs)
|
|
|
|
{
|
|
|
|
return InterceptPaintFull(PAINT_98199C, regs);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
static uint8 InterceptPaintFull(uint8 function, registers *regs) {
|
|
|
|
if ((regs->ebp & 0x03) != get_current_rotation()) {
|
2016-10-15 21:44:51 +02:00
|
|
|
// Log error
|
|
|
|
log_error("Ebp is different from current rotation");
|
|
|
|
}
|
|
|
|
|
|
|
|
rct_xyz16 boundOffset = {
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_PAINT_BOUNDBOX_OFFSET_X, sint16),
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_PAINT_BOUNDBOX_OFFSET_Y, sint16),
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_PAINT_BOUNDBOX_OFFSET_Z, sint16)
|
|
|
|
};
|
|
|
|
|
2016-11-13 18:32:32 +01:00
|
|
|
paint_struct *out = PaintFull(
|
2016-10-15 21:44:51 +02:00
|
|
|
function,
|
2016-11-13 18:32:32 +01:00
|
|
|
regs->ebx,
|
|
|
|
(sint8) regs->al, (sint8) regs->cl,
|
|
|
|
(sint16) regs->di, (sint16) regs->si, (sint8) regs->ah,
|
|
|
|
regs->dx,
|
2016-10-15 21:44:51 +02:00
|
|
|
boundOffset.x, boundOffset.y, boundOffset.z,
|
2016-11-13 18:32:32 +01:00
|
|
|
regs->ebp & 0x03
|
2016-10-15 21:44:51 +02:00
|
|
|
);
|
2016-11-13 18:32:32 +01:00
|
|
|
|
|
|
|
if (out == nullptr)
|
|
|
|
{
|
|
|
|
return X86_FLAG_CARRY;
|
|
|
|
}
|
|
|
|
|
|
|
|
regs->ebp = (int) out;
|
|
|
|
return 0;
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
bool wooden_a_supports_paint_setup(int supportType, int special, int height, uint32 imageColourFlags, bool *underground) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintWoodenSupports(SUPPORTS_WOOD_A, supportType, special, height, imageColourFlags, underground);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool wooden_b_supports_paint_setup(int supportType, int special, int height, uint32 imageColourFlags, bool *underground) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintWoodenSupports(SUPPORTS_WOOD_B, supportType, special, height, imageColourFlags, underground);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool metal_a_supports_paint_setup(uint8 supportType, uint8 segment, int special, int height, uint32 imageColourFlags) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintMetalSupports(SUPPORTS_METAL_A, supportType, segment, special, height, imageColourFlags);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool metal_b_supports_paint_setup(uint8 supportType, uint8 segment, int special, int height, uint32 imageColourFlags) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintMetalSupports(SUPPORTS_METAL_B, supportType, segment, special, height, imageColourFlags);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
paint_struct *sub_98196C(uint32 image_id, sint8 x_offset, sint8 y_offset, sint16 bound_box_length_x, sint16 bound_box_length_y, sint8 bound_box_length_z, sint16 z_offset, uint32 rotation) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::Paint6C(image_id, x_offset, y_offset, bound_box_length_x, bound_box_length_y, bound_box_length_z, z_offset, rotation);
|
2016-10-15 21:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
paint_struct *sub_98197C(
|
|
|
|
uint32 image_id,
|
|
|
|
sint8 x_offset, sint8 y_offset,
|
|
|
|
sint16 bound_box_length_x, sint16 bound_box_length_y, sint8 bound_box_length_z,
|
|
|
|
sint16 z_offset,
|
|
|
|
sint16 bound_box_offset_x, sint16 bound_box_offset_y, sint16 bound_box_offset_z,
|
|
|
|
uint32 rotation
|
|
|
|
) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintFull(
|
2016-10-15 21:44:51 +02:00
|
|
|
PAINT_98197C,
|
|
|
|
image_id,
|
|
|
|
x_offset, y_offset,
|
|
|
|
bound_box_length_x, bound_box_length_y, bound_box_length_z,
|
|
|
|
z_offset,
|
|
|
|
bound_box_offset_x, bound_box_offset_y, bound_box_offset_z,
|
|
|
|
rotation
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
paint_struct *sub_98198C(
|
|
|
|
uint32 image_id,
|
|
|
|
sint8 x_offset, sint8 y_offset,
|
|
|
|
sint16 bound_box_length_x, sint16 bound_box_length_y, sint8 bound_box_length_z,
|
|
|
|
sint16 z_offset,
|
|
|
|
sint16 bound_box_offset_x, sint16 bound_box_offset_y, sint16 bound_box_offset_z,
|
|
|
|
uint32 rotation
|
|
|
|
) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintFull(
|
2016-10-15 21:44:51 +02:00
|
|
|
PAINT_98198C,
|
|
|
|
image_id,
|
|
|
|
x_offset, y_offset,
|
|
|
|
bound_box_length_x, bound_box_length_y, bound_box_length_z,
|
|
|
|
z_offset,
|
|
|
|
bound_box_offset_x, bound_box_offset_y, bound_box_offset_z,
|
|
|
|
rotation
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
paint_struct *sub_98199C(
|
|
|
|
uint32 image_id,
|
|
|
|
sint8 x_offset, sint8 y_offset,
|
|
|
|
sint16 bound_box_length_x, sint16 bound_box_length_y, sint8 bound_box_length_z,
|
|
|
|
sint16 z_offset,
|
|
|
|
sint16 bound_box_offset_x, sint16 bound_box_offset_y, sint16 bound_box_offset_z,
|
|
|
|
uint32 rotation
|
|
|
|
) {
|
2016-10-17 12:42:59 +02:00
|
|
|
return PaintIntercept::PaintFull(
|
2016-10-15 21:44:51 +02:00
|
|
|
PAINT_98199C,
|
|
|
|
image_id,
|
|
|
|
x_offset, y_offset,
|
|
|
|
bound_box_length_x, bound_box_length_y, bound_box_length_z,
|
|
|
|
z_offset,
|
|
|
|
bound_box_offset_x, bound_box_offset_y, bound_box_offset_z,
|
|
|
|
rotation
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool paint_attach_to_previous_ps(uint32 image_id, uint16 x, uint16 y) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|