mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-01 09:37:21 +01:00
Add .sps EAMP3 [Need for Speed (PS4)]
This commit is contained in:
parent
79d523a4db
commit
34d4500e54
175
src/coding/mpeg_custom_utils_eamp3.c
Normal file
175
src/coding/mpeg_custom_utils_eamp3.c
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#include "mpeg_decoder.h"
|
||||||
|
|
||||||
|
#ifdef VGM_USE_MPEG
|
||||||
|
|
||||||
|
/* parsed info from a single EAMP3 frame */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t extended_flag;
|
||||||
|
uint32_t stereo_flag; /* assumed */
|
||||||
|
uint32_t unknown_flag; /* unused? */
|
||||||
|
uint32_t frame_size; /* full size including headers and pcm block */
|
||||||
|
uint32_t pcm_number; /* samples in the PCM block (typically 1 MPEG frame, 1152) */
|
||||||
|
|
||||||
|
uint32_t pre_size; /* size of the header part */
|
||||||
|
uint32_t mpeg_size; /* size of the MPEG part */
|
||||||
|
uint32_t pcm_size; /* size of the PCM block */
|
||||||
|
} eamp3_frame_info;
|
||||||
|
|
||||||
|
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
|
||||||
|
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
|
||||||
|
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||||
|
|
||||||
|
/* init config and validate */
|
||||||
|
int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||||
|
mpeg_frame_info info;
|
||||||
|
uint16_t frame_header;
|
||||||
|
size_t header_size;
|
||||||
|
|
||||||
|
|
||||||
|
/* test unknown stuff */
|
||||||
|
frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
|
||||||
|
if (frame_header & 0x2000) {
|
||||||
|
VGM_LOG("EAMP3: found unknown bit 13\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
|
||||||
|
VGM_LOG("EAMP3: found big PCM block\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get frame info at offset */
|
||||||
|
header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
|
||||||
|
if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
|
||||||
|
goto fail;
|
||||||
|
switch(info.layer) {
|
||||||
|
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||||
|
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||||
|
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||||
|
default: goto fail;
|
||||||
|
}
|
||||||
|
data->channels_per_frame = info.channels;
|
||||||
|
data->samples_per_frame = info.frame_samples;
|
||||||
|
data->bitrate_per_frame = info.bit_rate;
|
||||||
|
data->sample_rate_per_frame = info.sample_rate;
|
||||||
|
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reads custom frame header + MPEG data + (optional) PCM block */
|
||||||
|
int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||||
|
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||||
|
eamp3_frame_info eaf;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
|
||||||
|
if (!eamp3_skip_data(stream, data, num_stream, 1))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||||
|
if (!ok) goto fail;
|
||||||
|
|
||||||
|
ok = eamp3_write_pcm_block(stream, data, num_stream, &eaf);
|
||||||
|
if (!ok) goto fail;
|
||||||
|
|
||||||
|
ms->bytes_in_buffer = read_streamfile(ms->buffer,stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||||
|
stream->offset += eaf.frame_size;
|
||||||
|
|
||||||
|
if (!eamp3_skip_data(stream, data, num_stream, 0))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
|
||||||
|
uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
|
||||||
|
|
||||||
|
eaf->extended_flag = (current_header & 0x8000);
|
||||||
|
eaf->stereo_flag = (current_header & 0x4000);
|
||||||
|
eaf->unknown_flag = (current_header & 0x2000);
|
||||||
|
eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
|
||||||
|
eaf->pcm_number = 0;
|
||||||
|
if (eaf->extended_flag > 0) {
|
||||||
|
eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
|
||||||
|
eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
|
||||||
|
eaf->pre_size = 0x06;
|
||||||
|
eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
|
||||||
|
if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
|
||||||
|
VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
eaf->pcm_size = 0;
|
||||||
|
eaf->pre_size = 0x02;
|
||||||
|
eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
|
||||||
|
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf) {
|
||||||
|
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||||
|
size_t bytes_filled;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
|
||||||
|
bytes_filled = sizeof(sample) * ms->samples_filled * data->channels_per_frame;
|
||||||
|
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||||
|
VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (eaf->pcm_number) {
|
||||||
|
|
||||||
|
/* read + write PCM block samples (always LE) */
|
||||||
|
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
|
||||||
|
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
|
||||||
|
int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
|
||||||
|
put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
|
||||||
|
}
|
||||||
|
ms->samples_filled += eaf->pcm_number;
|
||||||
|
|
||||||
|
/* modify decoded samples */
|
||||||
|
{
|
||||||
|
size_t decode_to_discard = eaf->pcm_number; //todo guessed
|
||||||
|
ms->decode_to_discard += decode_to_discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
|
||||||
|
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||||
|
int ok, i;
|
||||||
|
eamp3_frame_info eaf;
|
||||||
|
int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < skips; i++) {
|
||||||
|
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||||
|
if (!ok) goto fail;
|
||||||
|
|
||||||
|
//;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
|
||||||
|
stream->offset += eaf.frame_size;
|
||||||
|
}
|
||||||
|
//;VGM_LOG("s%i: skipped %i frames, now at %lx\n", num_stream,skips,stream->offset);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -140,6 +140,7 @@ mpeg_codec_data *init_mpeg_custom(STREAMFILE *streamFile, off_t start_offset, co
|
|||||||
case MPEG_EAL32P:
|
case MPEG_EAL32P:
|
||||||
case MPEG_EAL32S: ok = mpeg_custom_setup_init_ealayer3(streamFile, start_offset, data, coding_type); break;
|
case MPEG_EAL32S: ok = mpeg_custom_setup_init_ealayer3(streamFile, start_offset, data, coding_type); break;
|
||||||
case MPEG_AWC: ok = mpeg_custom_setup_init_awc(streamFile, start_offset, data, coding_type); break;
|
case MPEG_AWC: ok = mpeg_custom_setup_init_awc(streamFile, start_offset, data, coding_type); break;
|
||||||
|
case MPEG_EAMP3: ok = mpeg_custom_setup_init_eamp3(streamFile, start_offset, data, coding_type); break;
|
||||||
default: ok = mpeg_custom_setup_init_default(streamFile, start_offset, data, coding_type); break;
|
default: ok = mpeg_custom_setup_init_default(streamFile, start_offset, data, coding_type); break;
|
||||||
}
|
}
|
||||||
if (!ok)
|
if (!ok)
|
||||||
@ -399,6 +400,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data
|
|||||||
case MPEG_EAL32S: ok = mpeg_custom_parse_frame_ealayer3(stream, data, num_stream); break;
|
case MPEG_EAL32S: ok = mpeg_custom_parse_frame_ealayer3(stream, data, num_stream); break;
|
||||||
case MPEG_AHX: ok = mpeg_custom_parse_frame_ahx(stream, data, num_stream); break;
|
case MPEG_AHX: ok = mpeg_custom_parse_frame_ahx(stream, data, num_stream); break;
|
||||||
case MPEG_AWC: ok = mpeg_custom_parse_frame_awc(stream, data, num_stream); break;
|
case MPEG_AWC: ok = mpeg_custom_parse_frame_awc(stream, data, num_stream); break;
|
||||||
|
case MPEG_EAMP3: ok = mpeg_custom_parse_frame_eamp3(stream, data, num_stream); break;
|
||||||
default: ok = mpeg_custom_parse_frame_default(stream, data, num_stream); break;
|
default: ok = mpeg_custom_parse_frame_default(stream, data, num_stream); break;
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
@ -21,11 +21,13 @@ int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_info *
|
|||||||
int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
||||||
int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
||||||
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
int mpeg_custom_setup_init_awc(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
||||||
|
int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type);
|
||||||
|
|
||||||
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||||
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
int mpeg_custom_parse_frame_ahx(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||||
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||||
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
int mpeg_custom_parse_frame_awc(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||||
|
int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream);
|
||||||
|
|
||||||
#endif/* VGM_USE_MPEG */
|
#endif/* VGM_USE_MPEG */
|
||||||
|
|
||||||
|
@ -1837,6 +1837,10 @@
|
|||||||
<File
|
<File
|
||||||
RelativePath=".\coding\mpeg_custom_utils_ealayer3.c"
|
RelativePath=".\coding\mpeg_custom_utils_ealayer3.c"
|
||||||
>
|
>
|
||||||
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\coding\mpeg_custom_utils_eamp3.c"
|
||||||
|
>
|
||||||
</File>
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\coding\mpeg_decoder.c"
|
RelativePath=".\coding\mpeg_decoder.c"
|
||||||
|
@ -515,6 +515,7 @@
|
|||||||
<ClCompile Include="coding\mpeg_custom_utils_ahx.c" />
|
<ClCompile Include="coding\mpeg_custom_utils_ahx.c" />
|
||||||
<ClCompile Include="coding\mpeg_custom_utils_awc.c" />
|
<ClCompile Include="coding\mpeg_custom_utils_awc.c" />
|
||||||
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c" />
|
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c" />
|
||||||
|
<ClCompile Include="coding\mpeg_custom_utils_eamp3.c" />
|
||||||
<ClCompile Include="coding\mpeg_decoder.c" />
|
<ClCompile Include="coding\mpeg_decoder.c" />
|
||||||
<ClCompile Include="coding\msadpcm_decoder.c" />
|
<ClCompile Include="coding\msadpcm_decoder.c" />
|
||||||
<ClCompile Include="coding\nds_procyon_decoder.c" />
|
<ClCompile Include="coding\nds_procyon_decoder.c" />
|
||||||
|
@ -1090,6 +1090,9 @@
|
|||||||
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c">
|
<ClCompile Include="coding\mpeg_custom_utils_ealayer3.c">
|
||||||
<Filter>coding\Source Files</Filter>
|
<Filter>coding\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="coding\mpeg_custom_utils_eamp3.c">
|
||||||
|
<Filter>coding\Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="coding\mpeg_decoder.c">
|
<ClCompile Include="coding\mpeg_decoder.c">
|
||||||
<Filter>coding\Source Files</Filter>
|
<Filter>coding\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#define EAAC_CODEC_DSP 0x08
|
#define EAAC_CODEC_DSP 0x08
|
||||||
#define EAAC_CODEC_EASPEEX 0x09
|
#define EAAC_CODEC_EASPEEX 0x09
|
||||||
#define EAAC_CODEC_EATRAX 0x0a
|
#define EAAC_CODEC_EATRAX 0x0a
|
||||||
|
#define EAAC_CODEC_EAMP3 0x0b
|
||||||
#define EAAC_CODEC_EAOPUS 0x0c
|
#define EAAC_CODEC_EAOPUS 0x0c
|
||||||
|
|
||||||
#define EAAC_FLAG_NONE 0x00
|
#define EAAC_FLAG_NONE 0x00
|
||||||
@ -680,6 +681,25 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef VGM_USE_MPEG
|
||||||
|
case EAAC_CODEC_EAMP3: { /* "EM30"?: EAMP3 [Need for Speed 2015 (PS4)] */
|
||||||
|
mpeg_custom_config cfg = {0};
|
||||||
|
|
||||||
|
start_offset = 0x00; /* must point to the custom streamfile's beginning */
|
||||||
|
|
||||||
|
temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed,0,0, eaac.stream_offset);
|
||||||
|
if (!temp_streamFile) goto fail;
|
||||||
|
|
||||||
|
vgmstream->codec_data = init_mpeg_custom(temp_streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_EAMP3, &cfg);
|
||||||
|
if (!vgmstream->codec_data) goto fail;
|
||||||
|
vgmstream->layout_type = layout_none;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef VGM_USE_FFMPEG
|
#ifdef VGM_USE_FFMPEG
|
||||||
case EAAC_CODEC_EAOPUS: { /* EAOpus (unknown FourCC) [FIFA 17 (PC), FIFA 19 (Switch)]*/
|
case EAAC_CODEC_EAOPUS: { /* EAOpus (unknown FourCC) [FIFA 17 (PC), FIFA 19 (Switch)]*/
|
||||||
int skip = 0;
|
int skip = 0;
|
||||||
@ -701,7 +721,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case EAAC_CODEC_EASPEEX: /* EASpeex (libspeex variant, base versions vary: 1.0.5, 1.2beta3) */ //todo
|
case EAAC_CODEC_EASPEEX: /* "Esp0"?: EASpeex (libspeex variant, base versions vary: 1.0.5, 1.2beta3) */ //todo
|
||||||
default:
|
default:
|
||||||
VGM_LOG("EA EAAC: unknown codec 0x%02x\n", eaac.codec);
|
VGM_LOG("EA EAAC: unknown codec 0x%02x\n", eaac.codec);
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -85,6 +85,7 @@ static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset,
|
|||||||
case 0x05: /* EALayer3 v1 */
|
case 0x05: /* EALayer3 v1 */
|
||||||
case 0x06: /* EALayer3 v2 "PCM" */
|
case 0x06: /* EALayer3 v2 "PCM" */
|
||||||
case 0x07: /* EALayer3 v2 "Spike" */
|
case 0x07: /* EALayer3 v2 "Spike" */
|
||||||
|
case 0x0b: /* EAMP3 */
|
||||||
case 0x0c: /* EAOpus */
|
case 0x0c: /* EAOpus */
|
||||||
data->skip_size = 0x08;
|
data->skip_size = 0x08;
|
||||||
data->data_size = data->block_size - data->skip_size;
|
data->data_size = data->block_size - data->skip_size;
|
||||||
@ -205,6 +206,7 @@ static size_t eaac_io_size(STREAMFILE *streamfile, eaac_io_data* data) {
|
|||||||
case 0x05: /* EALayer3 v1 */
|
case 0x05: /* EALayer3 v1 */
|
||||||
case 0x06: /* EALayer3 v2 "PCM" */
|
case 0x06: /* EALayer3 v2 "PCM" */
|
||||||
case 0x07: /* EALayer3 v2 "Spike" */
|
case 0x07: /* EALayer3 v2 "Spike" */
|
||||||
|
case 0x0b: /* EAMP3 */
|
||||||
case 0x0c: /* EAOpus */
|
case 0x0c: /* EAOpus */
|
||||||
data_size = block_size - 0x08;
|
data_size = block_size - 0x08;
|
||||||
break;
|
break;
|
||||||
|
@ -952,7 +952,8 @@ typedef enum {
|
|||||||
MPEG_EAL32P, /* EALayer3 v2 "PCM", custom frames with v2 header + bigger PCM blocks? */
|
MPEG_EAL32P, /* EALayer3 v2 "PCM", custom frames with v2 header + bigger PCM blocks? */
|
||||||
MPEG_EAL32S, /* EALayer3 v2 "Spike", custom frames with v2 header + smaller PCM blocks? */
|
MPEG_EAL32S, /* EALayer3 v2 "Spike", custom frames with v2 header + smaller PCM blocks? */
|
||||||
MPEG_LYN, /* N streams of fixed interleave */
|
MPEG_LYN, /* N streams of fixed interleave */
|
||||||
MPEG_AWC /* N streams in block layout (music) or absolute offsets (sfx) */
|
MPEG_AWC, /* N streams in block layout (music) or absolute offsets (sfx) */
|
||||||
|
MPEG_EAMP3 /* custom frame header + MPEG frame + PCM blocks */
|
||||||
} mpeg_custom_t;
|
} mpeg_custom_t;
|
||||||
|
|
||||||
/* config for the above modes */
|
/* config for the above modes */
|
||||||
|
Loading…
Reference in New Issue
Block a user