Files
T
kichikuou 0f6d4e2343 Add Streamer Mode with -censor option
This introduces "Streamer Mode" to mosaic specified NSFW images, making
the game suitable for streaming or public viewing. This feature is
enabled via the '-censor <file>' command-line option, which takes a file
containing a list of image numbers to be filtered.
2025-11-09 11:47:13 +09:00

680 lines
15 KiB
C

/*
* cg.c load and display cg
*
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
* 1998- <masaki-c@is.aist-nara.ac.jp>
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* $Id: cg.c,v 1.12 2001/09/16 15:59:11 chikama Exp $ */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include "portab.h"
#include "system.h"
#include "nact.h"
#include "ags.h"
#include "cg.h"
#include "vsp.h"
#include "pms.h"
#include "bmp.h"
#include "qnt.h"
#include "jpeg.h"
#ifdef HAVE_WEBP
#include "webp.h"
#endif
#include "ald_manager.h"
#include "filecheck.h"
#include "cache.h"
static int *censor_list; // sorted list of cg numbers
static int censor_list_size;
/* VSPのパレット展開バンク */
int cg_vspPB;
/* cg,palette 展開フラグ (funciotn flag) */
int cg_fflg;
/* CGをロードした回数を書き込む変数 */
int *cg_loadCountVar;
/* Brightness (PD command) */
int cg_brightness;
#define MOSAIC_SIZE 16
#define GCMD_EXTRACTCG(c) ((c) & 0x01)
#define GCMD_SET_PALETTE(c) ((c) & 0x02)
#define GCMD_LOAD_PALETTE(c) ((c) & 0x04)
/* CG表示位置に関する情報 */
static CG_WHERETODISP loc_policy, loc_policy0;
static SDL_Point loc_where, loc_where0;
/* extracted cg data cache control object */
static cacher *cacheid;
/* static methods */
static CG_TYPE check_cgformat(uint8_t *data);
static void set_vspbank(uint8_t *pic, int bank, int width, int height);
static SDL_Point set_display_loc(cgdata *cg);
static void clear_display_loc();
static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend);
static cgdata *loader(int no);
static int compare_integers(const void *a, const void *b) {
int int_a = *(const int *)a;
int int_b = *(const int *)b;
return (int_a > int_b) - (int_a < int_b);
}
static bool is_censored_cg(int no) {
no += 1; // CG numbers are 1-based.
if (!censor_list)
return false;
return bsearch(&no, censor_list, censor_list_size, sizeof(int), compare_integers) != NULL;
}
static void cg_mosaic(cgdata *cg) {
Uint32 format;
switch (cg->depth) {
case 8:
format = SDL_PIXELFORMAT_INDEX8;
break;
case 16:
format = SDL_PIXELFORMAT_RGB565;
break;
case 24:
format = SDL_PIXELFORMAT_RGB24;
break;
default:
SYSERROR("Unsupported cg depth %d", cg->depth);
}
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom(
cg->pic, cg->width, cg->height, cg->depth, cg->width * (cg->depth / 8), format);
SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat(
0, (cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format);
if (sf->format->palette)
SDL_SetSurfacePalette(tmp, sf->format->palette);
// NOTE: SDL_BlitScaled() does not support 8-bit surfaces.
SDL_SoftStretch(sf, NULL, tmp, NULL);
SDL_SoftStretch(tmp, NULL, sf, NULL);
SDL_FreeSurface(tmp);
SDL_FreeSurface(sf);
}
/*
* Identify cg format
* data: pointer to compressed data
* return: cg type
*/
static CG_TYPE check_cgformat(uint8_t *data) {
if (qnt_checkfmt(data)) {
return ALCG_QNT;
} else if (pms256_checkfmt(data)) {
return ALCG_PMS8;
} else if (pms64k_checkfmt(data) && nact->ags.world_depth >= 15) {
return ALCG_PMS16;
} else if (bmp16m_checkfmt(data) && nact->ags.world_depth >= 15) {
return ALCG_BMP24;
} else if (bmp256_checkfmt(data)) {
return ALCG_BMP8;
} else if (vsp_checkfmt(data)) {
return ALCG_VSP;
} else if (jpeg_checkfmt(data) && nact->ags.world_depth >= 15) {
return ALCG_JPEG;
#ifdef HAVE_WEBP
} else if (webp_checkfmt(data)) {
return ALCG_WEBP;
#endif
}
WARNING("Unknown Cg Type");
return ALCG_UNKNOWN;
}
/*
* Modify pixel accoding to palette bank (vsp only)
* pic : pixel to be modifyied.
* bank : palette bank (use only MSB 4bit)
* width : image width
* height: image height
*/
static void set_vspbank(uint8_t *pic, int bank, int width, int height) {
int pixels = width * height;
while (pixels--) {
*pic = (*pic & 0x0f) | (uint8_t)bank; pic++;
}
}
/*
* Free data
* cg: freeing data object
*/
void cgdata_free(cgdata *cg) {
if (cg->pic) free(cg->pic);
if (cg->pal) free(cg->pal);
if (cg->alpha) free(cg->alpha);
free(cg);
}
/*
* Determine the location of display image
* cg: cg information
* return: x and y for display location
*/
static SDL_Point set_display_loc(cgdata *cg) {
SDL_Point p;
switch(loc_policy) {
case OFFSET_ABSOLUTE_GC:
case OFFSET_ABSOLUTE_JC:
p.x = loc_where.x;
p.y = loc_where.y;
break;
case OFFSET_RELATIVE_GC:
case OFFSET_RELATIVE_JC:
p.x = cg->x + loc_where.x;
p.y = cg->y + loc_where.y;
break;
case OFFSET_NOMOVE:
p.x = cg->x;
p.y = cg->y;
break;
}
return p;
}
/*
* Reset location movement according to loc_policy
*/
static void clear_display_loc() {
switch(loc_policy) {
case OFFSET_ABSOLUTE_GC:
case OFFSET_RELATIVE_GC:
if (loc_policy0 == OFFSET_ABSOLUTE_JC ||
loc_policy0 == OFFSET_RELATIVE_JC ) {
loc_policy = loc_policy0;
loc_where = loc_where0;
loc_policy0 = OFFSET_NOMOVE;
} else {
loc_policy = OFFSET_NOMOVE;
}
break;
default:
break;
}
}
/*
* Call ags to display cg
* cg: cg to be drawn
* x : display location x
* y : display location y
*/
static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend) {
ags_drawCg(cg, x, y, cg_brightness, sprite_color, alpha_blend);
ags_updateArea(x, y, cg->width, cg->height);
}
/*
* Load cg data from file or cache
* no: file no ( >= 0)
* return: cg object(extracted)
*/
static cgdata *loader(int no) {
dridata *dfile;
cgdata *cg = NULL;
/* search in cache */
if (NULL != (cg = (cgdata *)cache_foreach(cacheid, no))) return cg;
/* read from file */
if (NULL == (dfile = ald_getdata(DRIFILE_CG, no))) return NULL;
/* update load cg counter */
if (cg_loadCountVar != NULL) {
(*(cg_loadCountVar + no + 1))++;
}
/* extract cg */
switch (check_cgformat(dfile->data)) {
case ALCG_VSP:
cg = vsp_extract(dfile->data);
break;
case ALCG_PMS8:
cg = pms256_extract(dfile->data);
break;
case ALCG_PMS16:
cg = pms64k_extract(dfile->data);
break;
case ALCG_BMP8:
cg = bmp256_extract(dfile->data);
break;
case ALCG_BMP24:
cg = bmp16m_extract(dfile->data);
break;
case ALCG_QNT:
cg = qnt_extract(dfile->data);
break;
case ALCG_JPEG:
cg = jpeg_extract(dfile->data, dfile->size);
break;
default:
break;
}
if (cg) {
if (is_censored_cg(no)) {
cg_mosaic(cg);
}
/* insert to cache */
int size = cg->width * cg->height * (cg->depth / 8);
cache_insert(cacheid, no, cg, size, NULL);
}
/* ok to free */
ald_freedata(dfile);
return cg;
}
/*
* Initilize cache
*/
void cg_init(void) {
cacheid = cache_new(cgdata_free);
cg_reset();
}
void cg_reset(void) {
cg_vspPB = -1;
cg_fflg = 7;
cg_loadCountVar = NULL;
cg_brightness = 255;
loc_policy = loc_policy0 = OFFSET_NOMOVE;
memset(&loc_where, 0, sizeof(loc_where));
memset(&loc_where0, 0, sizeof(loc_where0));
}
/*
* Set cg display location
* x : display location x
* y : display location y
* policy: location offset policy
*/
void cg_set_display_location(int x, int y, CG_WHERETODISP policy) {
if (policy == OFFSET_ABSOLUTE_GC || policy == OFFSET_RELATIVE_GC) {
if (loc_policy == OFFSET_ABSOLUTE_JC ||
loc_policy == OFFSET_RELATIVE_JC) {
loc_policy0 = loc_policy;
loc_where0 = loc_where;
}
}
loc_policy = policy;
loc_where.x = x;
loc_where.y = y;
}
/*
* Load and display cg
* no : file no ( >= 0)
* flg: sprite color(!-1)
*/
void cg_load(int no, int flg) {
cgdata *cg;
SDL_Point p;
int i, bank = cg_vspPB;
/* load and extract cg */
if (NULL == (cg = loader(no))) {
return;
}
/* need to set vsp bank or not */
if (GCMD_EXTRACTCG(cg_fflg) && cg->type == ALCG_VSP) {
bank = cg_vspPB == -1 ? cg->vsp_bank : cg_vspPB;
set_vspbank(cg->pic, bank << 4, cg->width, cg->height);
/* copy palettes 0 -> bank */
if (bank != 0) {
memcpy(cg->pal + bank * 16, cg->pal, 16 * sizeof(cg->pal[0]));
}
if (flg != -1) {
flg |= (bank << 4);
}
}
/* copy palette to system */
if (GCMD_LOAD_PALETTE(cg_fflg)) {
switch(cg->type) {
case ALCG_VSP:
ags_setPalettes(cg->pal, bank << 4, 16);
break;
case ALCG_PMS8:
// Avoid changing the Windows' reserved palette area (0-9, 246-255)
if (cg->pms_bank & 1)
ags_setPalettes(cg->pal + 10, 10, 6);
if (cg->pms_bank & (1 << 15))
ags_setPalettes(cg->pal + 240, 240, 6);
for (i = 1; i < 15; i++) {
if (cg->pms_bank & (1 << i)) {
ags_setPalettes(cg->pal + i * 16, i * 16, 16);
}
}
break;
case ALCG_BMP8:
ags_setPalettes(cg->pal + 10, 10, 236);
break;
default:
break;
}
}
/* refrect palette change */
if (GCMD_SET_PALETTE(cg_fflg) && cg->depth == 8)
ags_setPaletteToSystem(0, 256);
/* draw cg */
if (GCMD_EXTRACTCG(cg_fflg)) {
/* set display offset */
p = set_display_loc(cg);
/* draw alpha pixel */
if (cg->alpha) {
ags_alpha_setPixel(p.x, p.y, cg->width, cg->height, cg->alpha);
}
/* draw cg pixel */
display_cg(cg, p.x, p.y, flg, flg != -1);
}
/* clear display offset */
clear_display_loc();
}
/*
* Load and display cg with alpha
* cgno : file no for cg ( >= 0 or -1(alpha only))
* shadowno: file no for alpha ( >= 0 )
*/
void cg_load_with_alpha(int cgno, int shadowno) {
cgdata *cg = NULL, *scg;
SDL_Point p;
/* load pixel */
if (cgno >= 0) {
if (NULL == (cg = loader(cgno))) return;
if (cg->type != ALCG_PMS16) {
WARNING("commandGX cg_no != 16bitPMS");
return;
}
}
/* load alpha pixel */
if (NULL == (scg = loader(shadowno))) return;
if (scg->type != ALCG_PMS8) {
WARNING("commandGX shadow_no != 8bitPMS");
return;
}
/* set alpha pixel offset */
p = set_display_loc(scg);
/* draw alpha pixel */
ags_alpha_setPixel(p.x, p.y, scg->width, scg->height, scg->pic);
/* draw pixel */
if (cg) {
p = set_display_loc(cg);
display_cg(cg, p.x, p.y, -1, true);
}
/* clear display offset */
clear_display_loc();
}
static uint8_t* load_cg_from_file(char *fname_utf8, int *status, long *filesize) {
int size;
FILE *fp;
static uint8_t *tmp;
*status = 0;
if (NULL == (fp = fc_open(fname_utf8, 'r'))) {
*status = SAVE_LOADERR; return NULL;
}
fseek(fp, 0L, SEEK_END);
*filesize = ftell(fp);
if (*filesize == 0) {
*status = SAVE_LOADERR; return NULL;
}
tmp = (char *)malloc(*filesize);
if (tmp == NULL) {
WARNING("Out of memory");
*status = SAVE_LOADERR; return NULL;
}
fseek(fp, 0L, SEEK_SET);
size = fread(tmp, 1, *filesize,fp);
if (size != *filesize) {
*status = SAVE_LOADSHORTAGE;
} else {
*status = SAVE_LOADOK;
}
fclose(fp);
return tmp;
}
/*
* Load and display cg from file 'name' (not cached right now)
* name: file name to be read
* x : display location x
* y : display location y
* return: file read status
*/
int cg_load_with_filename(char *fname_utf8, int x, int y) {
int status, type;
long filesize;
uint8_t *data;
cgdata *cg = NULL;
SDL_Point p;
data = load_cg_from_file(fname_utf8, &status, &filesize);
if (data == NULL) return status;
cg_set_display_location(x, y, OFFSET_ABSOLUTE_GC);
type = check_cgformat(data);
switch(type) {
case ALCG_BMP8:
cg = bmp256_extract(data);
break;
case ALCG_BMP24:
cg = bmp16m_extract(data);
break;
case ALCG_JPEG:
cg = jpeg_extract(data, filesize);
break;
default:
return status;
}
/* load palette if not extracted */
if (cg->depth == 8) {
if (GCMD_LOAD_PALETTE(cg_fflg))
ags_setPalettes(cg->pal + 10, 10, 236);
if (GCMD_SET_PALETTE(cg_fflg))
ags_setPaletteToSystem(0, 256);
}
/* draw cg */
if (GCMD_EXTRACTCG(cg_fflg)) {
/* set display offset */
p = set_display_loc(cg);
/* draw cg pixel */
display_cg(cg, p.x, p.y, -1, false);
/* clear display offset */
clear_display_loc();
}
cgdata_free(cg);
return status;
}
/*
* Get cg information
* no : file no for cg ( >= 0 )
* info: information to be retored
*/
void cg_get_info(int no, SDL_Rect *info) {
cgdata *cg = loader(no);
SDL_Point p;
if (cg == NULL) {
info->x = info->y = info->w = info->h = 0;
} else {
p = set_display_loc(cg);
info->x = p.x;
info->y = p.y;
info->w = cg->width;
info->h = cg->height;
}
}
void cg_clear_display_loc() {
clear_display_loc();
}
SDL_Surface *cg_load_as_sdlsurface(int no) {
dridata *dfile = ald_getdata(DRIFILE_CG, no);
if (!dfile) return NULL;
CG_TYPE type = check_cgformat(dfile->data);
#ifdef HAVE_WEBP
if (type == ALCG_WEBP) {
SDL_Surface *sf = webp_extract(dfile->data, dfile->size);
ald_freedata(dfile);
return sf;
}
#endif
cgdata *cg = NULL;
switch (type) {
case ALCG_PMS16:
cg = pms64k_extract(dfile->data);
break;
case ALCG_QNT:
cg = qnt_extract(dfile->data);
break;
default:
WARNING("Invalid CG type");
break;
}
ald_freedata(dfile);
if (!cg) return NULL;
if (is_censored_cg(no)) {
cg_mosaic(cg);
}
SDL_Surface *pic = type == ALCG_PMS16
? SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565)
: SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24);
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, cg->width, cg->height, 32,
cg->alpha ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_XRGB8888);
SDL_BlitSurface(pic, NULL, sf, NULL);
if (cg->alpha) {
// Copy alpha values from cg->alpha to sf->pixels.
uint8_t *p_ds = ALPHA_AT(sf, 0, 0);
uint8_t *adata = cg->alpha;
for (int y = 0; y < cg->height; y++) {
uint8_t *p_dst = p_ds;
for (int x = 0; x < cg->width; x++) {
*p_dst = *adata++;
p_dst += 4;
}
p_ds += sf->pitch;
}
}
SDL_FreeSurface(pic);
cgdata_free(cg);
return sf;
}
EMSCRIPTEN_KEEPALIVE
void load_censor_list(const char *path) {
if (cacheid)
cache_clear(cacheid);
if (censor_list) {
free(censor_list);
censor_list = NULL;
censor_list_size = 0;
}
if (!path || !*path)
return;
FILE *fp = fopen(path, "r");
if (!fp) {
SYSERROR("failed to open censor list file %s", path);
return;
}
char line[256];
int count = 0, cap = 256;
censor_list = malloc(cap * sizeof(int));
int line_no = 0;
while (fgets(line, sizeof(line), fp)) {
line_no++;
char *p = line;
char *comment = strchr(p, '#');
if (comment)
*comment = '\0';
while (*p && isspace((unsigned char)*p))
p++;
if (*p == '\0')
continue;
if (count >= cap) {
cap *= 2;
censor_list = realloc(censor_list, cap * sizeof(int));
}
char *endptr;
censor_list[count++] = (int)strtol(p, &endptr, 10);
while (*endptr && isspace((unsigned char)*endptr))
endptr++;
if (*endptr != '\0')
WARNING("invalid line %d in %s", line_no, path);
}
censor_list_size = count;
fclose(fp);
if (censor_list_size > 0) {
qsort(censor_list, censor_list_size, sizeof(int), compare_integers);
} else {
free(censor_list);
censor_list = NULL;
}
}