mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-22 11:22:18 -04:00
implement support for DBIGFONT lump in FON2 format (#1575)
* add SLADE copyright * adapt V_LinearToTransPatch from Eternity Engine This will be useful for PNG support. * nit-pick: sort copyright holders in README.md --------- Co-authored-by: Fabian Greffrath <fabian@greffrath.com>
This commit is contained in:
parent
80704d11fc
commit
5ca3c41045
@ -149,6 +149,7 @@ Copyright:
|
||||
© 2005-2006 by Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko;
|
||||
© 2005-2018 Simon Howard;
|
||||
© 2006 Ben Ryves;
|
||||
© 2008-2019 Simon Judd;
|
||||
© 2017 Christoph Oelckers;
|
||||
© 2019 Fernando Carmona Varo;
|
||||
© 2020 Alex Mayfield;
|
||||
|
@ -57,6 +57,7 @@ set(WOOF_SOURCES
|
||||
m_fixed.h
|
||||
m_input.c m_input.h
|
||||
m_io.c m_io.h
|
||||
mn_font.c mn_font.h
|
||||
mn_menu.c mn_menu.h
|
||||
mn_setup.c mn_setup.h
|
||||
m_misc.c m_misc.h
|
||||
|
223
src/mn_font.c
Normal file
223
src/mn_font.c
Normal file
@ -0,0 +1,223 @@
|
||||
//
|
||||
// SLADE - It's a Doom Editor
|
||||
// Copyright(C) 2008 - 2019 Simon Judd
|
||||
// Copyright(C) 2024 Roman Fomin
|
||||
//
|
||||
// This program 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 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// DESCRIPTION:
|
||||
// Load and draw ZDoom FON2 fonts
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "doomtype.h"
|
||||
#include "i_video.h"
|
||||
#include "m_swap.h"
|
||||
#include "r_defs.h"
|
||||
#include "v_video.h"
|
||||
#include "w_wad.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t width;
|
||||
patch_t *patch;
|
||||
} fon2_char_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
byte magic[4];
|
||||
uint16_t charheight;
|
||||
byte firstc;
|
||||
byte lastc;
|
||||
byte constantw;
|
||||
byte shading;
|
||||
byte palsize;
|
||||
byte kerning; // flag field, but with only one flag
|
||||
} fon2_header_t;
|
||||
|
||||
static fon2_char_t *chars = NULL;
|
||||
static int numchars;
|
||||
static int height;
|
||||
static int firstc;
|
||||
static int kerning;
|
||||
|
||||
#define FON2_SPACE 12
|
||||
|
||||
boolean MN_LoadFon2(const byte *gfx_data, int size)
|
||||
{
|
||||
if (size < (int)sizeof(fon2_header_t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const fon2_header_t *header = (fon2_header_t *)gfx_data;
|
||||
if (memcmp(header->magic, "FON2", 4))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
height = SHORT(header->charheight);
|
||||
if (height == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const byte *p = gfx_data + sizeof(fon2_header_t);
|
||||
|
||||
if (header->kerning)
|
||||
{
|
||||
kerning = SHORT(*(int16_t *)p);
|
||||
p += 2;
|
||||
}
|
||||
|
||||
firstc = header->firstc;
|
||||
numchars = header->lastc - header->firstc + 1;
|
||||
chars = malloc(numchars * sizeof(*chars));
|
||||
|
||||
for (int i = 0; i < numchars; ++i)
|
||||
{
|
||||
chars[i].width = SHORT(*(uint16_t *)p);
|
||||
// The width information is enumerated for each character only if they
|
||||
// are not constant width. Regardless, move the read pointer away after
|
||||
// the last.
|
||||
if (!(header->constantw) || (i == numchars - 1))
|
||||
{
|
||||
p += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Build translation table for palette.
|
||||
byte *playpal = W_CacheLumpName("PLAYPAL", PU_CACHE);
|
||||
byte *translate = malloc(header->palsize + 1);
|
||||
for (int i = 0; i < header->palsize + 1; ++i)
|
||||
{
|
||||
int r = *p++;
|
||||
int g = *p++;
|
||||
int b = *p++;
|
||||
translate[i] = I_GetPaletteIndex(playpal, r, g, b);
|
||||
}
|
||||
|
||||
// 0 is transparent, last is border color
|
||||
byte color_key = translate[0];
|
||||
|
||||
// The picture data follows, using the same RLE as FON1 and IMGZ.
|
||||
for (int i = 0; i < numchars; ++i)
|
||||
{
|
||||
// A big font is not necessarily continuous; several characters
|
||||
// may be skipped; they are given a width of 0.
|
||||
if (!chars[i].width)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int numpixels = chars[i].width * height;
|
||||
byte *data = malloc(numpixels);
|
||||
byte *d = data;
|
||||
byte code = 0;
|
||||
int length = 0;
|
||||
|
||||
while (numpixels)
|
||||
{
|
||||
code = *p++;
|
||||
if (code < 0x80)
|
||||
{
|
||||
length = code + 1;
|
||||
for (int k = 0; k < length; ++k)
|
||||
{
|
||||
d[k] = translate[p[k]];
|
||||
}
|
||||
d += length;
|
||||
p += length;
|
||||
numpixels -= length;
|
||||
}
|
||||
else if (code > 0x80)
|
||||
{
|
||||
length = 0x101 - code;
|
||||
code = *p++;
|
||||
memset(d, translate[code], length);
|
||||
d += length;
|
||||
numpixels -= length;
|
||||
}
|
||||
}
|
||||
|
||||
chars[i].patch = V_LinearToTransPatch(data, chars[i].width, height,
|
||||
color_key);
|
||||
free(data);
|
||||
}
|
||||
|
||||
free(translate);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean MN_DrawFon2String(int x, int y, byte *cr, const char *str)
|
||||
{
|
||||
if (!numchars)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int c, cx;
|
||||
|
||||
cx = x;
|
||||
|
||||
while (*str)
|
||||
{
|
||||
c = *str++;
|
||||
|
||||
c = toupper(c) - firstc;
|
||||
if (c < 0 || c >= numchars)
|
||||
{
|
||||
cx += FON2_SPACE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chars[c].width)
|
||||
{
|
||||
V_DrawPatchTranslated(cx, y, chars[c].patch, cr);
|
||||
cx += chars[c].width + kerning;
|
||||
}
|
||||
else
|
||||
{
|
||||
cx += FON2_SPACE + kerning;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int MN_GetFon2PixelWidth(const char *str)
|
||||
{
|
||||
if (!numchars)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int c;
|
||||
|
||||
while (*str)
|
||||
{
|
||||
c = *str++;
|
||||
|
||||
c = toupper(c) - firstc;
|
||||
if (c < 0 || c > numchars)
|
||||
{
|
||||
len += FON2_SPACE; // space
|
||||
continue;
|
||||
}
|
||||
|
||||
len += chars[c].width + kerning;
|
||||
}
|
||||
len -= kerning;
|
||||
return len;
|
||||
}
|
30
src/mn_font.h
Normal file
30
src/mn_font.h
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// SLADE - It's a Doom Editor
|
||||
// Copyright(C) 2008 - 2019 Simon Judd
|
||||
// Copyright(C) 2024 Roman Fomin
|
||||
//
|
||||
// This program 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 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// DESCRIPTION:
|
||||
// Load and draw ZDoom FON2 fonts
|
||||
|
||||
#ifndef MN_FONT
|
||||
#define MN_FONT
|
||||
|
||||
#include "doomtype.h"
|
||||
|
||||
boolean MN_LoadFon2(const byte *gfx_data, int size);
|
||||
|
||||
boolean MN_DrawFon2String(int x, int y, byte *cr, const char *str);
|
||||
|
||||
int MN_GetFon2PixelWidth(const char *str);
|
||||
|
||||
#endif
|
@ -44,10 +44,11 @@
|
||||
#include "m_input.h"
|
||||
#include "m_io.h"
|
||||
#include "m_misc.h"
|
||||
#include "mn_snapshot.h"
|
||||
#include "m_swap.h"
|
||||
#include "mn_font.h"
|
||||
#include "mn_menu.h"
|
||||
#include "mn_setup.h"
|
||||
#include "mn_snapshot.h"
|
||||
#include "p_saveg.h"
|
||||
#include "r_defs.h"
|
||||
#include "r_draw.h"
|
||||
@ -1727,8 +1728,8 @@ static menuitem_t SetupMenu[] = {
|
||||
// [FG] alternative text for missing menu graphics lumps
|
||||
{1, "M_GENERL", MN_General, 'g', "GENERAL", SETUP_MENU_RECT(0)},
|
||||
{1, "M_KEYBND", MN_KeyBindings, 'k', "KEY BINDINGS", SETUP_MENU_RECT(1)},
|
||||
{1, "M_COMPAT", MN_Compat, 'd', "DOOM COMPATIBILITY", SETUP_MENU_RECT(2)},
|
||||
{1, "M_STAT", MN_StatusBar, 's', "STATUS BAR / HUD", SETUP_MENU_RECT(3)},
|
||||
{1, "M_COMPAT", MN_Compat, 'c', "COMPATIBILITY", SETUP_MENU_RECT(2)},
|
||||
{1, "M_STAT", MN_StatusBar, 's', "STATUS BAR/HUD", SETUP_MENU_RECT(3)},
|
||||
{1, "M_AUTO", MN_Automap, 'a', "AUTOMAP", SETUP_MENU_RECT(4)},
|
||||
{1, "M_WEAP", MN_Weapons, 'w', "WEAPONS", SETUP_MENU_RECT(5)},
|
||||
{1, "M_ENEM", MN_Enemy, 'e', "ENEMIES", SETUP_MENU_RECT(6)},
|
||||
@ -1871,7 +1872,7 @@ void MN_SetNextMenuAlt(ss_types type)
|
||||
|
||||
static void M_DrawSetup(void)
|
||||
{
|
||||
MN_DrawTitle(124, 15, "M_OPTTTL", "OPTIONS");
|
||||
MN_DrawTitle(108, 15, "M_OPTTTL", "OPTIONS");
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
@ -1936,6 +1937,12 @@ void M_Init(void)
|
||||
messageLastMenuActive = menuactive;
|
||||
quickSaveSlot = -1;
|
||||
|
||||
int lumpnum = W_CheckNumForName("DBIGFONT");
|
||||
if (lumpnum > 0)
|
||||
{
|
||||
MN_LoadFon2(W_CacheLumpNum(lumpnum, PU_CACHE), W_LumpLength(lumpnum));
|
||||
}
|
||||
|
||||
// Here we could catch other version dependencies,
|
||||
// like HELP1/2, and four episodes.
|
||||
|
||||
@ -3126,8 +3133,11 @@ void M_Drawer(void)
|
||||
{
|
||||
if (alttext)
|
||||
{
|
||||
MN_DrawStringCR(x, y + 8 - MN_StringHeight(alttext) / 2, cr,
|
||||
NULL, alttext);
|
||||
if (!MN_DrawFon2String(x, y, cr, alttext))
|
||||
{
|
||||
MN_DrawStringCR(x, y + 8 - MN_StringHeight(alttext) / 2, cr,
|
||||
NULL, alttext);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (name[0])
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "m_input.h"
|
||||
#include "m_misc.h"
|
||||
#include "m_swap.h"
|
||||
#include "mn_font.h"
|
||||
#include "mn_menu.h"
|
||||
#include "p_mobj.h"
|
||||
#include "r_bmaps.h"
|
||||
@ -47,6 +48,7 @@
|
||||
#include "sounds.h"
|
||||
#include "v_video.h"
|
||||
#include "w_wad.h"
|
||||
#include "z_zone.h"
|
||||
|
||||
static int M_GetKeyString(int c, int offset);
|
||||
static void DrawMenuString(int cx, int cy, int color);
|
||||
@ -1533,7 +1535,7 @@ void MN_DrawStatusHUD(void)
|
||||
inhelpscreens = true; // killough 4/6/98: Force status bar redraw
|
||||
|
||||
DrawBackground("FLOOR4_6"); // Draw background
|
||||
MN_DrawTitle(59, 2, "M_STAT", "STATUS BAR / HUD");
|
||||
MN_DrawTitle(59, 2, "M_STAT", "STATUS BAR/HUD");
|
||||
DrawTabs();
|
||||
DrawInstructions();
|
||||
DrawScreenItems(current_menu);
|
||||
@ -1730,8 +1732,6 @@ static const char *default_complevel_strings[] = {
|
||||
|
||||
setup_menu_t comp_settings1[] = {
|
||||
|
||||
{"Compatibility", S_SKIP | S_TITLE, M_X, M_SPC},
|
||||
|
||||
{"Default Compatibility Level", S_CHOICE | S_LEVWARN, M_X, M_SPC,
|
||||
{"default_complevel"}, m_null, input_null, str_default_complevel},
|
||||
|
||||
@ -1792,7 +1792,7 @@ void MN_DrawCompat(void)
|
||||
inhelpscreens = true;
|
||||
|
||||
DrawBackground("FLOOR4_6"); // Draw background
|
||||
MN_DrawTitle(52, 2, "M_COMPAT", "DOOM COMPATIBILITY");
|
||||
MN_DrawTitle(52, 2, "M_COMPAT", "COMPATIBILITY");
|
||||
DrawInstructions();
|
||||
DrawScreenItems(current_menu);
|
||||
|
||||
@ -3712,11 +3712,16 @@ void MN_DrawTitle(int x, int y, const char *patch, const char *alttext)
|
||||
else
|
||||
{
|
||||
// patch doesn't exist, draw some text in place of it
|
||||
M_snprintf(menu_buffer, sizeof(menu_buffer), "%s", alttext);
|
||||
DrawMenuString(
|
||||
SCREENWIDTH / 2 - MN_StringWidth(alttext) / 2,
|
||||
y + 8 - MN_StringHeight(alttext) / 2, // assumes patch height 16
|
||||
CR_TITLE);
|
||||
if (!MN_DrawFon2String(
|
||||
SCREENWIDTH / 2 - MN_GetFon2PixelWidth(alttext) / 2,
|
||||
y, NULL, alttext))
|
||||
{
|
||||
M_snprintf(menu_buffer, sizeof(menu_buffer), "%s", alttext);
|
||||
DrawMenuString(
|
||||
SCREENWIDTH / 2 - MN_StringWidth(alttext) / 2,
|
||||
y + 8 - MN_StringHeight(alttext) / 2, // assumes patch height 16
|
||||
CR_TITLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
209
src/v_video.c
209
src/v_video.c
@ -1065,9 +1065,216 @@ void V_RestoreBuffer(void)
|
||||
dest_screen = I_VideoBuffer;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
byte row_off;
|
||||
byte *pixels;
|
||||
} vpost_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vpost_t *posts;
|
||||
} vcolumn_t;
|
||||
|
||||
#define M_ARRAY_INIT_CAPACITY 16
|
||||
#include "m_array.h"
|
||||
|
||||
#define PUTBYTE(r, v) \
|
||||
*r = (uint8_t)(v); \
|
||||
++r
|
||||
|
||||
#define PUTSHORT(r, v) \
|
||||
*(r + 0) = (byte)(((uint16_t)(v) >> 0) & 0xff); \
|
||||
*(r + 1) = (byte)(((uint16_t)(v) >> 8) & 0xff); \
|
||||
r += 2
|
||||
|
||||
#define PUTLONG(r, v) \
|
||||
*(r + 0) = (byte)(((uint32_t)(v) >> 0) & 0xff); \
|
||||
*(r + 1) = (byte)(((uint32_t)(v) >> 8) & 0xff); \
|
||||
*(r + 2) = (byte)(((uint32_t)(v) >> 16) & 0xff); \
|
||||
*(r + 3) = (byte)(((uint32_t)(v) >> 24) & 0xff); \
|
||||
r += 4
|
||||
|
||||
//
|
||||
// SCREEN SHOTS
|
||||
// Converts a linear graphic to a patch with transparency. Mostly straight
|
||||
// from psxwadgen, which is mostly straight from SLADE.
|
||||
//
|
||||
patch_t *V_LinearToTransPatch(const byte *data, int width, int height,
|
||||
int color_key)
|
||||
{
|
||||
vcolumn_t *columns = NULL;
|
||||
|
||||
// Go through columns
|
||||
uint32_t offset = 0;
|
||||
for (int c = 0; c < width; c++)
|
||||
{
|
||||
vcolumn_t col = {0};
|
||||
vpost_t post = {0};
|
||||
post.row_off = 0;
|
||||
boolean ispost = false;
|
||||
boolean first_254 = true; // first 254 pixels use absolute offsets
|
||||
|
||||
offset = c;
|
||||
byte row_off = 0;
|
||||
for (int r = 0; r < height; r++)
|
||||
{
|
||||
// if we're at offset 254, create a dummy post for tall doom gfx
|
||||
// support
|
||||
if (row_off == 254)
|
||||
{
|
||||
// Finish current post if any
|
||||
if (ispost)
|
||||
{
|
||||
array_push(col.posts, post);
|
||||
post.pixels = NULL;
|
||||
ispost = false;
|
||||
}
|
||||
|
||||
// Begin relative offsets
|
||||
first_254 = false;
|
||||
|
||||
// Create dummy post
|
||||
post.row_off = 254;
|
||||
array_push(col.posts, post);
|
||||
|
||||
// Clear post
|
||||
row_off = 0;
|
||||
ispost = false;
|
||||
}
|
||||
|
||||
// If the current pixel is not transparent, add it to the current
|
||||
// post
|
||||
if (data[offset] != color_key)
|
||||
{
|
||||
// If we're not currently building a post, begin one and set its
|
||||
// offset
|
||||
if (!ispost)
|
||||
{
|
||||
// Set offset
|
||||
post.row_off = row_off;
|
||||
|
||||
// Reset offset if we're in relative offsets mode
|
||||
if (!first_254)
|
||||
{
|
||||
row_off = 0;
|
||||
}
|
||||
|
||||
// Start post
|
||||
ispost = true;
|
||||
}
|
||||
|
||||
// Add the pixel to the post
|
||||
array_push(post.pixels, data[offset]);
|
||||
}
|
||||
else if (ispost)
|
||||
{
|
||||
// If the current pixel is transparent and we are currently
|
||||
// building a post, add the current post to the list and clear
|
||||
// it
|
||||
array_push(col.posts, post);
|
||||
post.pixels = NULL;
|
||||
ispost = false;
|
||||
}
|
||||
|
||||
// Go to next row
|
||||
offset += width;
|
||||
++row_off;
|
||||
}
|
||||
|
||||
// If the column ended with a post, add it
|
||||
if (ispost)
|
||||
{
|
||||
array_push(col.posts, post);
|
||||
}
|
||||
|
||||
// Add the column data
|
||||
array_push(columns, col);
|
||||
|
||||
// Go to next column
|
||||
++offset;
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
|
||||
// Calculate needed memory size to allocate patch buffer
|
||||
size += 4 * sizeof(int16_t); // 4 header shorts
|
||||
size += array_size(columns) * sizeof(int32_t); // offsets table
|
||||
|
||||
for (int c = 0; c < array_size(columns); c++)
|
||||
{
|
||||
for (int p = 0; p < array_size(columns[c].posts); p++)
|
||||
{
|
||||
size_t post_len = 0;
|
||||
|
||||
post_len += 2; // two bytes for post header
|
||||
post_len += 1; // dummy byte
|
||||
post_len += array_size(columns[c].posts[p].pixels); // pixels
|
||||
post_len += 1; // dummy byte
|
||||
|
||||
size += post_len;
|
||||
}
|
||||
|
||||
size += 1; // room for 0xff cap byte
|
||||
}
|
||||
|
||||
byte *output = malloc(size);
|
||||
byte *rover = output;
|
||||
|
||||
// write header fields
|
||||
PUTSHORT(rover, width);
|
||||
PUTSHORT(rover, height);
|
||||
// This is written to afterwards
|
||||
PUTSHORT(rover, 0);
|
||||
PUTSHORT(rover, 0);
|
||||
|
||||
// set starting position of column offsets table, and skip over it
|
||||
byte *col_offsets = rover;
|
||||
rover += array_size(columns) * 4;
|
||||
|
||||
for (int c = 0; c < array_size(columns); c++)
|
||||
{
|
||||
// write column offset to offset table
|
||||
uint32_t offs = (uint32_t)(rover - output);
|
||||
PUTLONG(col_offsets, offs);
|
||||
|
||||
// write column posts
|
||||
for (int p = 0; p < array_size(columns[c].posts); p++)
|
||||
{
|
||||
// Write row offset
|
||||
PUTBYTE(rover, columns[c].posts[p].row_off);
|
||||
|
||||
// Write number of pixels
|
||||
int numpixels = array_size(columns[c].posts[p].pixels);
|
||||
PUTBYTE(rover, numpixels);
|
||||
|
||||
// Write pad byte
|
||||
byte lastval = numpixels ? columns[c].posts[p].pixels[0] : 0;
|
||||
PUTBYTE(rover, lastval);
|
||||
|
||||
// Write pixels
|
||||
for (int a = 0; a < numpixels; a++)
|
||||
{
|
||||
lastval = columns[c].posts[p].pixels[a];
|
||||
PUTBYTE(rover, lastval);
|
||||
}
|
||||
|
||||
// Write pad byte
|
||||
PUTBYTE(rover, lastval);
|
||||
|
||||
array_free(columns[c].posts[p].pixels);
|
||||
}
|
||||
|
||||
// Write 255 cap byte
|
||||
PUTBYTE(rover, 0xff);
|
||||
|
||||
array_free(columns[c].posts);
|
||||
}
|
||||
|
||||
array_free(columns);
|
||||
|
||||
// Done!
|
||||
return (patch_t *)output;
|
||||
}
|
||||
|
||||
//
|
||||
// V_ScreenShot
|
||||
|
@ -182,6 +182,9 @@ void V_DrawBackground(const char *patchname);
|
||||
|
||||
int V_BloodColor(int blood);
|
||||
|
||||
struct patch_s *V_LinearToTransPatch(const byte *data, int width, int height,
|
||||
int color_key);
|
||||
|
||||
void V_ScreenShot(void);
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user