1
0
mirror of https://github.com/crskycode/GARbro.git synced 2024-12-25 04:14:13 +08:00

422 lines
18 KiB
C#
Raw Normal View History

//! \file ImageG.cs
//! \date 2023 Aug 28
//! \brief HyperWorks image format.
//
// Copyright (C) 2023 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
using GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
// [951207][Love Gun] ACE OF SPADES
// I24 predecessor
namespace GameRes.Formats.HyperWorks
{
[Export(typeof(ImageFormat))]
public class GFormat : ImageFormat
{
public override string Tag => "G";
public override string Description => "HyperWorks indexed image format";
public override uint Signature => 0x1A477D00;
public GFormat ()
{
Signatures = new[] { 0x1A477D00u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (12);
if (header.ToUInt16 (2) != 0x1A47)
return null;
// not sure if 0x7D00 is a required signature, so rely on filename
if (header.ToUInt16 (0) != 0x7D00 && !file.Name.HasExtension (".G"))
return null;
return new ImageMetaData {
Width = header.ToUInt16 (8),
Height = header.ToUInt16 (10),
OffsetX = header.ToInt16 (4),
OffsetY = header.ToInt16 (6),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GFormat.Write not implemented");
}
}
internal sealed class GReader
{
IBinaryStream m_input;
ImageMetaData m_info;
int m_stride;
byte[] m_palette_data;
byte[] m_output;
ushort[] m_buffer;
int[] m_line_ptr;
public GReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
m_stride = (m_info.iWidth * m_info.BPP / 8 + 1) & -2;
int s = Math.Max (0x142, m_stride / 2 + 2); // line buffer size
m_buffer = new ushort[s * 3 + 1];
m_line_ptr = new int[3] { 1, 1 + s, 1 + s*2 };
}
public ImageData Unpack ()
{
m_input.Position = 0x0C;
m_palette_data = m_input.ReadBytes (0x30);
m_input.Position = 0x40;
int width = ((m_info.iWidth + 7) & -8);
int rows = ((m_info.iHeight + 1) & -2);
m_output = new byte[m_stride * rows];
InitColorTable();
int blockW = width >> 1;
int blockH = rows >> 1;
SetupBitReader();
for (int y = 0; y < blockH; ++y)
{
var dst = m_line_ptr[2];
m_line_ptr[2] = m_line_ptr[1];
m_line_ptr[1] = m_line_ptr[0];
m_line_ptr[0] = dst;
int x = 0;
while (x < blockW)
{
if (GetNextBit() != 0)
{
m_buffer[dst++] = GetColorFromTable (x);
++x;
}
else
{
int count = ExtractBits (BitTable1);
if (count >= 0x40)
count += ExtractBits (BitTable1);
int idx = ExtractBits (BitTable2) * 2;
int src = m_line_ptr[OffTable[idx + 1]];
src += OffTable[idx] + x;
x += count;
while (count --> 0)
{
m_buffer[dst++] = m_buffer[src++];
}
}
}
UnpackRow (y, blockW, m_line_ptr[0]);
}
var palette = UnpackPalette();
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride);
}
void UnpackRow (int y, int width, int buf_pos)
{
int row1 = m_stride * y * 2;
int row2 = row1 + m_stride;
for (int i = 0; i < width; ++i)
{
ushort v = m_buffer[buf_pos++];
ushort v0 = (ushort)((v & 0xF00 | ((v & 0xF000) >> 12)) + 0xA0A);
LittleEndian.Pack (v0, m_output, row2);
row2 += 2;
ushort v1 = (ushort)((((v & 0xF) << 8) | ((v & 0xF0) >> 4)) + 0xA0A);
LittleEndian.Pack (v1, m_output, row1);
row1 += 2;
}
}
byte[] g_palIndexes = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 0xC, 0xD, 0xA, 0xB, 0xE, 0xF };
BitmapPalette UnpackPalette ()
{
var colors = new Color[42];
for (int i = 0; i < 16; ++i)
{
int R = m_palette_data[3 * g_palIndexes[i] ]; R |= R << 4;
int G = m_palette_data[3 * g_palIndexes[i] + 1]; G |= G << 4;
int B = m_palette_data[3 * g_palIndexes[i] + 2]; B |= B << 4;
colors[i+10] = Color.FromRgb ((byte)R, (byte)G, (byte)B);
int b = R & 1;
int c = (sbyte)R >> 1;
if (c < 0)
c += b;
colors[i+26].R = (byte)c;
b = G & 1;
c = (sbyte)G >> 1;
if (c < 0)
c += b;
colors[i+26].G = (byte)c;
b = B & 1;
c = (sbyte)B >> 1;
if (c < 0)
c += b;
colors[i+26].B = (byte)c;
}
return new BitmapPalette (colors);
}
byte[] g_colorTable = new byte[256];
void InitColorTable ()
{
int dst = 0;
for (int i = 0; i < 16; ++i)
for (int j = 0; j < 16; ++j)
g_colorTable[dst++] = (byte)((j + i + 1) & 0xF);
}
ushort GetColorFromTable (int x)
{
ushort b0 = m_buffer[m_line_ptr[1] + x];
int n0 = b0 & 0xF;
int n1 = (b0 >> 4) & 0xF;
int n2 = (b0 >> 8) & 0xF;
int n3 = (b0 >> 12) & 0xF;
ushort b1 = m_buffer[m_line_ptr[1] + x - 1];
int m0 = b1 & 0xF;
int m1 = (b1 >> 4) & 0xF;
int m2 = (b1 >> 8) & 0xF;
int m3 = (b1 >> 12) & 0xF;
ushort b2 = m_buffer[m_line_ptr[0] + x - 1];
int p0 = b2 & 0xF;
int p1 = (b2 >> 4) & 0xF;
int p2 = (b2 >> 8) & 0xF;
int p3 = (b2 >> 12) & 0xF;
int r1 = n1;
if (n1 != n3 && (n1 != m1 || n1 != p1))
{
if (p0 == p1)
r1 = p0;
else
r1 = m2;
}
if (GetNextBit() != 0)
r1 = AdjustColorTable (r1);
int r0 = n0;
if (n0 != n2 && (n0 != m0 || n0 != p0))
{
if (r1 == p1)
r0 = p1;
else
r0 = n3;
}
if (GetNextBit() != 0)
r0 = AdjustColorTable (r0);
int r3 = n3;
if (r1 != n3 && (n3 != m3 || n3 != p3))
{
if (p2 == p3)
r3 = p2;
else
r3 = p0;
}
if (GetNextBit() != 0)
r3 = AdjustColorTable (r3);
int r2 = n2;
if (n2 != r0 && (n2 != m2 || n2 != p2))
{
if (p3 == r3)
r2 = p3;
else
r2 = r1;
}
if (GetNextBit() != 0)
r2 = AdjustColorTable (r2);
return (ushort)((r3 << 12) | (r2 << 8) | (r1 << 4) | r0);
}
byte AdjustColorTable (int idx)
{
int shift_count = ExtractBits (BitTable3);
int i = 16 * idx + shift_count;
byte c = g_colorTable[i];
if (shift_count != 0)
{
while (shift_count --> 0)
{
g_colorTable[i] = g_colorTable[i-1];
--i;
}
g_colorTable[i] = c;
}
return c;
}
int ExtractBits (byte[] table)
{
int idx = ((bits >> 8) & 0xFF) << 1;
int n = table[idx];
if (n != 0)
{
if (n >= bitCount)
{
n -= bitCount;
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
bits <<= n;
bitCount -= n;
return table[idx+1];
}
else
{
bits <<= bitCount;
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bits <<= 8 - bitCount;
int t = table[idx+1];
do
{
int i = GetNextBit();
t = OffTable[2 * t + 54 + i];
}
while (OffTable[2 * t + 54] != 0);
return OffTable[2 * t + 55];
}
}
int bits;
int bitCount;
private void SetupBitReader ()
{
bits = m_input.ReadUInt8() << 8;
bits |= m_input.ReadUInt8();
bitCount = 8;
}
private int GetNextBit ()
{
bits <<= 1;
if (0 == --bitCount)
{
int b = m_input.ReadByte();
if (b != -1) // XXX ignore EOF
bits |= b;
bitCount = 8;
}
return (bits >> 16) & 1;
}
static readonly byte[] BitTable1 = new byte[] {
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 8,
0xD, 0, 0, 0, 1, 8, 0xE, 6, 7, 6, 7, 6, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
7, 0xA, 7, 0xA, 0, 2, 0, 7, 7, 0xB, 7, 0xB, 8, 0xF, 0, 3, 6, 8, 6, 8, 6, 8, 6, 8, 8, 0x10, 0, 4,
0, 5, 8, 0x11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 0xC, 7, 0xC, 0, 8, 0, 6, 6, 9, 6,
9, 6, 9, 6, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
static readonly byte[] BitTable2 = new byte[] {
5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7, 0x17, 7, 0x17, 7, 0x16, 7, 0x16, 6, 0x0E, 6,
0x0E, 6, 0x0E, 6, 0x0E, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4,
3, 4, 3, 4, 3, 4, 3, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0C, 6, 0x0C, 6, 0x0C, 6, 0x0C, 5, 6,
5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 7, 0x14, 7,
0x14, 7, 0x18, 7, 0x18, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x11, 6, 0x11, 6, 0x11, 6, 0x11, 6,
0x0F, 6, 0x0F, 6, 0x0F, 6, 0x0F, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5,
0x0A, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1,
6, 0x13, 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x12, 6, 0x12, 6, 0x12, 6, 0x12, 5, 0x0B, 5, 0x0B, 5, 0x0B,
5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 7, 0x19, 7, 0x19, 7, 0x1A, 7, 0x1A, 6, 0x15, 6, 0x15,
6, 0x15, 6, 0x15, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0,
};
static readonly byte[] BitTable3 = new byte[] {
4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5,
4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8, 0x0A, 8, 0x0B,
4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 6, 7, 6, 7, 6, 7, 7, 9, 7, 9, 0, 0x5F, 8, 0x0C,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
};
static readonly short[] OffTable = new short[] {
0, 1, 1, 1, -1, 0, -1, 1, -4, 0, -2, 0, 2, 1, -2, 1, 4, 1, 0, 2, 1, 2, -1, 2, -8, 0, -4, 1, 8, 1,
-2, 2, 2, 2, -4, 2, 4, 2, 8, 2, -8, 1, -0x10, 0, -8, 2, 0x10, 1, 0x10, 2, -0x10, 1, -0x10, 2,
0x2C, 9, 0x2D, 0x0A, 0x2E, 0x0B, 0x2F, 0x0C, 0x30, 0x0D, 0x31, 0x32, 0x0E, 0x33, 0x0F, 0x10, 0x11,
0x12, 0x13, 0x14, 0x34, 0x15, 0x37, 0x35, 0x16, 0x38, 0x17, 0x39, 0x3D, 0x18, 0x19, 0x36, 0x1A,
0x1B, 0x3A, 0x3C, 0x1C, 0x3B, 0x1D, 0x3E, 0x3F, 0x1E, 0x40, 0x1F, 0x45, 0x46, 0x43, 0x20, 0x21,
0x48, 0x22, 0x41, 0x23, 0x42, 0x24, 0x25, 0x44, 0x47, 0x4F, 0x4A, 0x49, 0x53, 0x4B, 0x4D, 0x57,
0x52, 0x59, 0x58, 0x4E, 0x50, 0x56, 0x26, 0x54, 0x4C, 0x51, 0x55, 0x27, 0x28, 0x5A, 0x2B, 0x29,
0x5B, 0x2A, 0x5C, 0x5D, 0x5E, 0, 0, 0, 0x12, 0, 0x13, 0, 0x14, 0, 0x15, 0, 0x16, 0, 0x17, 0, 0x18,
0, 0x19, 0, 0x1A, 0, 0x1B, 0, 0x1C, 0, 0x1D, 0, 0x1E, 0, 0x1F, 0, 0x20, 0, 0x21, 0, 0x22, 0, 0x23,
0, 0x24, 0, 0x25, 0, 0x26, 0, 0x27, 0, 0x28, 0, 0x29, 0, 0x2A, 0, 0x2B, 0, 0x2C, 0, 0x2D, 0, 0x2E,
0, 0x2F, 0, 0x30, 0, 0x31, 0, 0x32, 0, 0x33, 0, 0x34, 0, 0x35, 0, 0x36, 0, 0x37, 0, 0x38, 0, 0x39,
0, 0x3A, 0, 0x3B, 0, 0x3C, 0, 0x3D, 0, 0x3E, 0, 0x3F, 0, 0x40, 0, 0x80, 0, 0x0C0, 0, 0x100, 0,
0x140, 0x60, 0x61, 0, 0x0D, 0, 0x0E,
};
}
}