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.
This commit is contained in:
kichikuou
2025-11-09 11:47:13 +09:00
parent f1536a801a
commit 0f6d4e2343
10 changed files with 351 additions and 1 deletions
+8
View File
@@ -60,6 +60,14 @@ and examine or modify game variables. There are two ways to use the debugger:
launch the debugger with a console interface. Type `help` to see a list of
available commands.
### Streamer Mode
xsystem35 introduces a "Streamer Mode" to make playing games with NSFW content
safer for streaming or public viewing. When enabled with the `-censor <file>`
option, images specified in the provided file will be automatically mosaiced.
The `misc/censor/` directory contains example censor list files for some games.
## Installation
Prebuilt packages for Windows and Android can be downloaded from the
+5
View File
@@ -49,6 +49,11 @@ finds System 3.x game files (*.ALD) from the current directory.
Suppress text hook on specified scenario pages. _list_ is a comma-separated
list of page numbers. Used to suppress system messages, etc.
*-censor* _file_::
Enable Streamer Mode, which mosaics specific images to make them safe for
streaming. The _file_ is a text file containing a list of image numbers
to censor, one per line. Lines starting with '#' are treated as comments.
*-renderer* _name_::
Use the SDL rendering driver named _name_.
+21
View File
@@ -0,0 +1,21 @@
# Censor Lists for Streamer Mode
This directory contains sample censor lists for various games, intended for use
with xsystem35's Streamer Mode.
## Usage
To use a censor list, specify its path with the `-censor` command-line option
or in your `.xsys35rc` file. For example:
```bash
xsystem35 -censor misc/censor/kichikuou.txt
```
The list files contain image numbers (integers), one per line. Lines starting
with `#` are treated as comments.
## Contributing
We welcome contributions of censor lists for other games! If you have created
a censor list that you'd like to share, please open a Pull Request.
+164
View File
@@ -0,0 +1,164 @@
# Censor list for 鬼畜王ランス (Kichikuou Rance)
27
70
93
101
102
103
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
123
124
125
126
127
128
129
130
131
132
133
134
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
160
161
162
163
164
165
166
167
168
169
171
172
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
193
195
196
197
198
199
200
201
203
204
205
206
207
208
209
210
214
215
216
217
218
219
221
222
224
225
226
227
228
229
230
231
232
233
234
235
239
299
300
361
362
363
375
376
377
393
422
461
466
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
517
844
845
846
847
848
851
852
853
854
855
+16
View File
@@ -130,3 +130,19 @@ void *cache_foreach(cacher *id, int key) {
}
return NULL;
}
void cache_clear(cacher *id) {
cacheinfo *ic = id->top->next;
cacheinfo *next;
while (ic != NULL) {
next = ic->next;
if (ic->data) {
totalsize -= ic->size;
id->free_(ic->data);
}
free(ic);
ic = next;
}
id->top->next = NULL;
}
+1
View File
@@ -46,5 +46,6 @@ typedef struct _cacher cacher;
extern cacher *cache_new(void *delcallback);
extern void cache_insert(cacher *id, int key, void *data, int size, int *in_use);
extern void *cache_foreach(cacher *id, int key);
extern void cache_clear(cacher *id);
#endif /* !__CASHE__ */
+116 -1
View File
@@ -21,9 +21,14 @@
*/
/* $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"
@@ -41,6 +46,9 @@
#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) */
@@ -50,6 +58,8 @@ 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)
@@ -69,6 +79,46 @@ 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
@@ -230,8 +280,12 @@ static cgdata *loader(int no) {
default:
break;
}
/* insert to cache */
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);
}
@@ -536,6 +590,10 @@ SDL_Surface *cg_load_as_sdlsurface(int no) {
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);
@@ -562,3 +620,60 @@ SDL_Surface *cg_load_as_sdlsurface(int no) {
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;
}
}
+1
View File
@@ -84,6 +84,7 @@ extern void cg_get_info(int no, SDL_Rect *info);
extern void cg_clear_display_loc();
extern void cgdata_free(cgdata *cg);
extern struct SDL_Surface *cg_load_as_sdlsurface(int no);
extern void load_censor_list(const char *path);
extern int cg_vspPB;
extern int cg_fflg;
+12
View File
@@ -56,6 +56,7 @@
#include "randMT.h"
#include "ags.h"
#include "font.h"
#include "cg.h"
#include "gfx.h"
#include "event.h"
#include "menu.h"
@@ -114,6 +115,7 @@ static void sys35_usage(bool verbose) {
puts(" -playlist file : load CD playlist from 'file'");
puts(" -texthook mode : text hook mode. 'none' (default), 'print' or 'copy'");
puts(" -texthook_suppress list : suppress text hook on specified pages");
puts(" -censor file : mosaic images listed in 'file'");
puts(" -M? : select output midi methos");
#ifdef ENABLE_MIDI_SDLMIXER
@@ -254,6 +256,10 @@ static void sys35_ParseOption(int *argc, char **argv) {
if (argv[i + 1] != NULL) {
texthook_set_suppression_list(argv[i + 1]);
}
} else if (0 == strcmp(argv[i], "-censor")) {
if (argv[i + 1] != NULL) {
load_censor_list(argv[i + 1]);
}
} else if (0 == strncmp(argv[i], "-M", 2)) {
int subdev = 0;
if (argv[i][3] != '\0') {
@@ -399,6 +405,12 @@ static void check_profile() {
if (param) {
texthook_set_suppression_list(param);
}
/* Censor list for streamer mode */
param = get_profile("censor");
if (param) {
load_censor_list(param);
}
}
#ifdef HAVE_SIGACTION
+7
View File
@@ -105,6 +105,13 @@
#texthook_suppress: 7,11,15,16,17,18,23,24,32,33,197
# Censor list for Streamer Mode.
# This mosaics images listed in the specified file, making them safe for
# streaming or public viewing. The file should contain one image number
# per line, with '#' treated as comments.
#censor: censor_list.txt
# Enables the ZB command (change the font weight). It is disabled by default
# because it makes the text too bold with our default fonts.
# This option is experimental and may be removed in the future. See