mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
Fix hang on full loops with xma1 formats
- happens with FFmpeg n5.1.2
This commit is contained in:
parent
ebb9b51e04
commit
41bad49872
@ -622,6 +622,9 @@ ffmpeg_codec_data* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* ou
|
||||
ffmpeg_codec_data* init_ffmpeg_aac(STREAMFILE* sf, off_t offset, size_t size, int skip_samples);
|
||||
ffmpeg_codec_data* init_ffmpeg_xwma(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int format, int channels, int sample_rate, int avg_bitrate, int block_size);
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_xma1_raw(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int channels, int sample_rate, int stream_mode);
|
||||
|
||||
|
||||
/* ffmpeg_decoder_custom_opus.c (helper-things) */
|
||||
typedef struct {
|
||||
int channels;
|
||||
@ -680,7 +683,6 @@ ffmpeg_codec_data* init_ffmpeg_mp4_custom_lyn(STREAMFILE* sf, mp4_custom_t* mp4)
|
||||
/* coding_utils */
|
||||
int ffmpeg_fmt_chunk_swap_endian(uint8_t* chunk, size_t chunk_size, uint16_t codec);
|
||||
int ffmpeg_make_riff_atrac3plus(uint8_t* buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_align, int encoder_delay);
|
||||
int ffmpeg_make_riff_xma1(uint8_t* buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int stream_mode);
|
||||
int ffmpeg_make_riff_xma2(uint8_t* buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_count, int block_size);
|
||||
int ffmpeg_make_riff_xma_from_fmt_chunk(uint8_t* buf, size_t buf_size, off_t fmt_offset, size_t fmt_size, size_t data_size, STREAMFILE* sf, int big_endian);
|
||||
int ffmpeg_make_riff_xma2_from_xma2_chunk(uint8_t* buf, size_t buf_size, off_t xma2_offset, size_t xma2_size, size_t data_size, STREAMFILE* sf);
|
||||
|
@ -80,90 +80,6 @@ int ffmpeg_make_riff_atrac3plus(uint8_t* buf, size_t buf_size, size_t sample_cou
|
||||
return riff_size;
|
||||
}
|
||||
|
||||
int ffmpeg_make_riff_xma1(uint8_t* buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int stream_mode) {
|
||||
uint16_t codec_XMA1 = 0x0165;
|
||||
size_t riff_size;
|
||||
int streams, i;
|
||||
|
||||
/* stream disposition:
|
||||
* 0: default (ex. 5ch = 2ch + 2ch + 1ch = 3 streams)
|
||||
* 1: lineal (ex. 5ch = 1ch + 1ch + 1ch + 1ch + 1ch = 5 streams), unusual but exists
|
||||
* others: not seen (ex. maybe 5ch = 2ch + 1ch + 1ch + 1ch = 4 streams) */
|
||||
switch(stream_mode) {
|
||||
case 0 : streams = (channels + 1) / 2; break;
|
||||
case 1 : streams = channels; break;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
riff_size = 4+4+ 4 + 0x14 + 0x14*streams + 4+4;
|
||||
|
||||
if (buf_size < riff_size)
|
||||
return -1;
|
||||
|
||||
memcpy(buf+0x00, "RIFF", 4);
|
||||
put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */
|
||||
memcpy(buf+0x08, "WAVE", 4);
|
||||
|
||||
memcpy(buf+0x0c, "fmt ", 4);
|
||||
put_32bitLE(buf+0x10, 0xc + 0x14*streams);/*fmt size*/
|
||||
put_16bitLE(buf+0x14, codec_XMA1);
|
||||
put_16bitLE(buf+0x16, 16); /* bits per sample */
|
||||
put_16bitLE(buf+0x18, 0x10D6); /* encoder options */
|
||||
put_16bitLE(buf+0x1a, 0); /* largest stream skip (wrong, unneeded) */
|
||||
put_16bitLE(buf+0x1c, streams); /* number of streams */
|
||||
put_8bit (buf+0x1e, 0); /* loop count */
|
||||
put_8bit (buf+0x1f, 2); /* version */
|
||||
|
||||
for (i = 0; i < streams; i++) {
|
||||
int stream_channels;
|
||||
uint32_t speakers;
|
||||
off_t off = 0x20 + 0x14*i;/* stream riff offset */
|
||||
|
||||
if (stream_mode == 1) {
|
||||
/* lineal */
|
||||
stream_channels = 1;
|
||||
switch(i) { /* per stream, values observed */
|
||||
case 0: speakers = 0x0001; break;/* L */
|
||||
case 1: speakers = 0x0002; break;/* R */
|
||||
case 2: speakers = 0x0004; break;/* C */
|
||||
case 3: speakers = 0x0008; break;/* LFE */
|
||||
case 4: speakers = 0x0040; break;/* LB */
|
||||
case 5: speakers = 0x0080; break;/* RB */
|
||||
case 6: speakers = 0x0000; break;/* ? */
|
||||
case 7: speakers = 0x0000; break;/* ? */
|
||||
default: speakers = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* with odd channels the last stream is mono */
|
||||
stream_channels = channels / streams + (channels%2 != 0 && i+1 != streams ? 1 : 0);
|
||||
switch(i) { /* per stream, values from xmaencode */
|
||||
case 0: speakers = stream_channels == 1 ? 0x0001 : 0x0201; break;/* L R */
|
||||
case 1: speakers = stream_channels == 1 ? 0x0004 : 0x0804; break;/* C LFE */
|
||||
case 2: speakers = stream_channels == 1 ? 0x0040 : 0x8040; break;/* LB RB */
|
||||
case 3: speakers = stream_channels == 1 ? 0x0000 : 0x0000; break;/* somehow empty (maybe should use 0x2010 LS RS) */
|
||||
default: speakers = 0;
|
||||
}
|
||||
}
|
||||
|
||||
put_32bitLE(buf+off+0x00, sample_rate*stream_channels / sizeof(sample)); /* average bytes per second (wrong, unneeded) */
|
||||
put_32bitLE(buf+off+0x04, sample_rate);
|
||||
put_32bitLE(buf+off+0x08, 0); /* loop start */
|
||||
put_32bitLE(buf+off+0x0c, 0); /* loop end */
|
||||
put_8bit (buf+off+0x10, 0); /* loop subframe */
|
||||
put_8bit (buf+off+0x11, stream_channels);
|
||||
put_16bitLE(buf+off+0x12, speakers);
|
||||
}
|
||||
|
||||
/* xmaencode decoding rejects XMA1 without "seek" chunk, though it doesn't seem to use it
|
||||
* (needs to be have entries but can be bogus, also generates seek for even small sounds) */
|
||||
|
||||
memcpy(buf+riff_size-4-4, "data", 4);
|
||||
put_32bitLE(buf+riff_size-4, data_size); /* data size */
|
||||
|
||||
return riff_size;
|
||||
}
|
||||
|
||||
int ffmpeg_make_riff_xma2(uint8_t* buf, size_t buf_size, size_t sample_count, size_t data_size, int channels, int sample_rate, int block_count, int block_size) {
|
||||
uint16_t codec_XMA2 = 0x0166;
|
||||
size_t riff_size = 4+4+ 4 + 0x3c + 4+4;
|
||||
@ -235,7 +151,7 @@ int ffmpeg_make_riff_xma_from_fmt_chunk(uint8_t* buf, size_t buf_size, off_t fmt
|
||||
goto fail;
|
||||
|
||||
if (big_endian) {
|
||||
int codec = read_16bitBE(fmt_offset,sf);
|
||||
int codec = read_16bitBE(fmt_offset,sf);//TODO read from buf
|
||||
ffmpeg_fmt_chunk_swap_endian(chunk, fmt_size, codec);
|
||||
}
|
||||
|
||||
|
@ -220,4 +220,106 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static int ffmpeg_make_riff_xma1(uint8_t* buf, size_t buf_size, size_t data_size, int channels, int sample_rate, int stream_mode) {
|
||||
uint16_t codec_XMA1 = 0x0165;
|
||||
size_t riff_size;
|
||||
int streams, i;
|
||||
|
||||
/* stream disposition:
|
||||
* 0: default (ex. 5ch = 2ch + 2ch + 1ch = 3 streams)
|
||||
* 1: lineal (ex. 5ch = 1ch + 1ch + 1ch + 1ch + 1ch = 5 streams), unusual but exists
|
||||
* others: not seen (ex. maybe 5ch = 2ch + 1ch + 1ch + 1ch = 4 streams) */
|
||||
switch(stream_mode) {
|
||||
case 0 : streams = (channels + 1) / 2; break;
|
||||
case 1 : streams = channels; break;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
riff_size = 4+4+ 4 + 0x14 + 0x14*streams + 4+4;
|
||||
|
||||
if (buf_size < riff_size)
|
||||
return -1;
|
||||
|
||||
memcpy(buf+0x00, "RIFF", 4);
|
||||
put_32bitLE(buf+0x04, (int32_t)(riff_size-4-4 + data_size)); /* riff size */
|
||||
memcpy(buf+0x08, "WAVE", 4);
|
||||
|
||||
memcpy(buf+0x0c, "fmt ", 4);
|
||||
put_32bitLE(buf+0x10, 0xc + 0x14*streams);/*fmt size*/
|
||||
put_16bitLE(buf+0x14, codec_XMA1);
|
||||
put_16bitLE(buf+0x16, 16); /* bits per sample */
|
||||
put_16bitLE(buf+0x18, 0x10D6); /* encoder options */
|
||||
put_16bitLE(buf+0x1a, 0); /* largest stream skip (wrong, unneeded) */
|
||||
put_16bitLE(buf+0x1c, streams); /* number of streams */
|
||||
put_8bit (buf+0x1e, 0); /* loop count */
|
||||
put_8bit (buf+0x1f, 2); /* version */
|
||||
|
||||
for (i = 0; i < streams; i++) {
|
||||
int stream_channels;
|
||||
uint32_t speakers;
|
||||
off_t off = 0x20 + 0x14*i;/* stream riff offset */
|
||||
|
||||
if (stream_mode == 1) {
|
||||
/* lineal */
|
||||
stream_channels = 1;
|
||||
switch(i) { /* per stream, values observed */
|
||||
case 0: speakers = 0x0001; break;/* L */
|
||||
case 1: speakers = 0x0002; break;/* R */
|
||||
case 2: speakers = 0x0004; break;/* C */
|
||||
case 3: speakers = 0x0008; break;/* LFE */
|
||||
case 4: speakers = 0x0040; break;/* LB */
|
||||
case 5: speakers = 0x0080; break;/* RB */
|
||||
case 6: speakers = 0x0000; break;/* ? */
|
||||
case 7: speakers = 0x0000; break;/* ? */
|
||||
default: speakers = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* with odd channels the last stream is mono */
|
||||
stream_channels = channels / streams + (channels%2 != 0 && i+1 != streams ? 1 : 0);
|
||||
switch(i) { /* per stream, values from xmaencode */
|
||||
case 0: speakers = stream_channels == 1 ? 0x0001 : 0x0201; break;/* L R */
|
||||
case 1: speakers = stream_channels == 1 ? 0x0004 : 0x0804; break;/* C LFE */
|
||||
case 2: speakers = stream_channels == 1 ? 0x0040 : 0x8040; break;/* LB RB */
|
||||
case 3: speakers = stream_channels == 1 ? 0x0000 : 0x0000; break;/* somehow empty (maybe should use 0x2010 LS RS) */
|
||||
default: speakers = 0;
|
||||
}
|
||||
}
|
||||
|
||||
put_32bitLE(buf+off+0x00, sample_rate*stream_channels / sizeof(sample)); /* average bytes per second (wrong, unneeded) */
|
||||
put_32bitLE(buf+off+0x04, sample_rate);
|
||||
put_32bitLE(buf+off+0x08, 0); /* loop start */
|
||||
put_32bitLE(buf+off+0x0c, 0); /* loop end */
|
||||
put_8bit (buf+off+0x10, 0); /* loop subframe */
|
||||
put_8bit (buf+off+0x11, stream_channels);
|
||||
put_16bitLE(buf+off+0x12, speakers);
|
||||
}
|
||||
|
||||
/* xmaencode decoding rejects XMA1 without "seek" chunk, though it doesn't seem to use it
|
||||
* (needs to be have entries but can be bogus, also generates seek for even small sounds) */
|
||||
|
||||
memcpy(buf+riff_size-4-4, "data", 4);
|
||||
put_32bitLE(buf+riff_size-4, data_size); /* data size */
|
||||
|
||||
return riff_size;
|
||||
}
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_xma1_raw(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, int channels, int sample_rate, int stream_mode) {
|
||||
ffmpeg_codec_data* data = NULL;
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), data_size, channels, sample_rate, stream_mode);
|
||||
data = init_ffmpeg_header_offset(sf, buf, bytes, data_offset, data_size);
|
||||
|
||||
/* n5.1.2 XMA1 hangs on seeks near end (infinite loop), presumably due to missing flush in wmapro.c's ff_xma1_decoder + frame skip samples */
|
||||
ffmpeg_set_force_seek(data);
|
||||
|
||||
return data;
|
||||
fail:
|
||||
free_ffmpeg(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -179,11 +179,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *sf) {
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x08: {
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf,0x100, 0, stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset,stream_size);
|
||||
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, start_offset, stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
@ -1863,11 +1863,13 @@ static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_
|
||||
* (SNS with XMA2 do exist). Decoder should work when playing XMA1 as XMA2, but
|
||||
* the other way around can cause issues, so it's safer to just use XMA2. */
|
||||
is_xma1 = 0; //eaac->version == EAAC_VERSION_V0; /* approximate */
|
||||
if (is_xma1)
|
||||
bytes = ffmpeg_make_riff_xma1(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, 0);
|
||||
else
|
||||
if (is_xma1) {
|
||||
data->layers[i]->codec_data = init_ffmpeg_xma1_raw(temp_sf, 0x00, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, 0);
|
||||
}
|
||||
else {
|
||||
bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size);
|
||||
data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size);
|
||||
data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size);
|
||||
}
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
|
||||
data->layers[i]->coding_type = coding_FFmpeg;
|
||||
|
@ -391,16 +391,17 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
|
||||
uint8_t buf[0x100];
|
||||
size_t bytes, block_size, block_count;
|
||||
|
||||
if (fsb.version != FMOD_FSB_VERSION_4_0) { /* 3.x, though no actual output changes [ex. Guitar Hero III (X360)] */
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, 0);
|
||||
if (fsb.version != FMOD_FSB_VERSION_4_0) {
|
||||
/* 3.x, though no actual output changes [ex. Guitar Hero III (X360), The Bourne Conspiracy (X360)] */
|
||||
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, fsb.stream_offset, fsb.stream_size, fsb.channels, fsb.sample_rate, 0);
|
||||
}
|
||||
else {
|
||||
block_size = 0x8000; /* FSB default */
|
||||
block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size);
|
||||
}
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
@ -333,8 +333,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
else if (genh.codec == XMA1) {
|
||||
int xma_stream_mode = genh.codec_mode == 1 ? 1 : 0;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf, 100, vgmstream->num_samples, genh.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(streamFile, buf,bytes, genh.start_offset,genh.data_size);
|
||||
ffmpeg_data = init_ffmpeg_xma1_raw(streamFile, genh.start_offset, genh.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
}
|
||||
else if (genh.codec == XMA2) {
|
||||
|
@ -62,16 +62,12 @@ VGMSTREAM* init_vgmstream_ivaud(STREAMFILE* sf) {
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x0000: { /* XMA2 (X360) */
|
||||
uint8_t buf[0x100];
|
||||
size_t bytes;
|
||||
|
||||
if (ivaud.is_music) {
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
/* regular XMA for sfx */
|
||||
bytes = ffmpeg_make_riff_xma1(buf, 0x100, ivaud.num_samples, ivaud.stream_size, ivaud.channel_count, ivaud.sample_rate, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, ivaud.stream_offset, ivaud.stream_size);
|
||||
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, ivaud.stream_offset, ivaud.stream_size, ivaud.channel_count, ivaud.sample_rate, 0);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
@ -252,8 +252,6 @@ static layered_layout_data* build_layered_mul(STREAMFILE* sf, off_t offset, int
|
||||
switch(codec) {
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA1: {
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
size_t stream_size;
|
||||
int layer_channels = 1;
|
||||
|
||||
@ -269,12 +267,11 @@ static layered_layout_data* build_layered_mul(STREAMFILE* sf, off_t offset, int
|
||||
data->layers[i]->sample_rate = vgmstream->sample_rate;
|
||||
data->layers[i]->num_samples = vgmstream->num_samples;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, 0);
|
||||
data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size);
|
||||
data->layers[i]->codec_data = init_ffmpeg_xma1_raw(temp_sf, 0x00, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, 0);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
|
||||
data->layers[i]->coding_type = coding_FFmpeg;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
|
||||
data->layers[i]->stream_size = stream_size;
|
||||
|
||||
xma_fix_raw_samples(data->layers[i], temp_sf, 0x00,stream_size, 0, 0,0); /* ? */
|
||||
|
@ -574,8 +574,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
|
||||
else if (txth.codec == XMA1) {
|
||||
int xma_stream_mode = txth.codec_mode == 1 ? 1 : 0;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), vgmstream->num_samples, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(txth.sf_body, buf,bytes, txth.start_offset,txth.data_size);
|
||||
ffmpeg_data = init_ffmpeg_xma1_raw(txth.sf_body, txth.start_offset, txth.data_size, vgmstream->channels, vgmstream->sample_rate, xma_stream_mode);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
}
|
||||
else if (txth.codec == XMA2) {
|
||||
|
@ -484,11 +484,7 @@ VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA1: { /* Kameo (X360), Table Tennis (X360) */
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf, bytes, xwb.stream_offset,xwb.stream_size);
|
||||
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, xwb.stream_offset, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
Loading…
Reference in New Issue
Block a user