1/* 2 * Argonaut Games CVG (de)muxer 3 * 4 * Copyright (C) 2021 Zane van Iperen (zane@zanevaniperen.com) 5 * 6 * This file is part of FFmpeg. 7 * 8 * FFmpeg is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Lesser General Public 10 * License as published by the Free Software Foundation; either 11 * version 2.1 of the License, or (at your option) any later version. 12 * 13 * FFmpeg is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public 19 * License along with FFmpeg; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 */ 22 23#include "config_components.h" 24 25#include "libavutil/avstring.h" 26#include "libavutil/channel_layout.h" 27#include "avformat.h" 28#include "internal.h" 29#include "libavutil/opt.h" 30#include "libavutil/intreadwrite.h" 31 32/* 33 * .CVG files are essentially PSX ADPCM wrapped with a size and checksum. 34 * Found in the PSX versions of the game. 35 */ 36 37#define ARGO_CVG_HEADER_SIZE 12 38#define ARGO_CVG_BLOCK_ALIGN 0x10 39#define ARGO_CVG_NB_BLOCKS 32 40#define ARGO_CVG_SAMPLES_PER_BLOCK 28 41 42typedef struct ArgoCVGHeader { 43 uint32_t size; /*< File size -8 (this + trailing checksum) */ 44 uint32_t unk1; /*< Unknown. Always seems to be 0 or 1. */ 45 uint32_t unk2; /*< Unknown. Always seems to be 0 or 1. */ 46} ArgoCVGHeader; 47 48typedef struct ArgoCVGOverride { 49 const char *name; 50 ArgoCVGHeader header; 51 uint32_t checksum; 52 int sample_rate; 53} ArgoCVGOverride; 54 55typedef struct ArgoCVGDemuxContext { 56 ArgoCVGHeader header; 57 uint32_t checksum; 58 uint32_t num_blocks; 59 uint32_t blocks_read; 60} ArgoCVGDemuxContext; 61 62typedef struct ArgoCVGMuxContext { 63 const AVClass *class; 64 int skip_rate_check; 65 uint32_t checksum; 66 size_t size; 67} ArgoCVGMuxContext; 68 69#if CONFIG_ARGO_CVG_DEMUXER 70/* "Special" files that are played at a different rate. */ 71static ArgoCVGOverride overrides[] = { 72 { "CRYS.CVG", { 23592, 0, 1 }, 2495499, 88200 }, /* Beta */ 73 { "REDCRY88.CVG", { 38280, 0, 1 }, 4134848, 88200 }, /* Beta */ 74 { "DANLOOP1.CVG", { 54744, 1, 0 }, 5684641, 37800 }, /* Beta */ 75 { "PICKUP88.CVG", { 12904, 0, 1 }, 1348091, 48000 }, /* Beta */ 76 { "SELECT1.CVG", { 5080, 0, 1 }, 549987, 44100 }, /* Beta */ 77}; 78 79static int argo_cvg_probe(const AVProbeData *p) 80{ 81 ArgoCVGHeader cvg; 82 83 /* 84 * It's almost impossible to detect these files based 85 * on the header alone. File extension is (unfortunately) 86 * the best way forward. 87 */ 88 if (!av_match_ext(p->filename, "cvg")) 89 return 0; 90 91 if (p->buf_size < ARGO_CVG_HEADER_SIZE) 92 return 0; 93 94 cvg.size = AV_RL32(p->buf + 0); 95 cvg.unk1 = AV_RL32(p->buf + 4); 96 cvg.unk2 = AV_RL32(p->buf + 8); 97 98 if (cvg.size < 8) 99 return 0; 100 101 if (cvg.unk1 != 0 && cvg.unk1 != 1) 102 return 0; 103 104 if (cvg.unk2 != 0 && cvg.unk2 != 1) 105 return 0; 106 107 return AVPROBE_SCORE_MAX / 4 + 1; 108} 109 110static int argo_cvg_read_checksum(AVIOContext *pb, const ArgoCVGHeader *cvg, uint32_t *checksum) 111{ 112 int ret; 113 uint8_t buf[4]; 114 115 if (!(pb->seekable & AVIO_SEEKABLE_NORMAL)) { 116 *checksum = 0; 117 return 0; 118 } 119 120 if ((ret = avio_seek(pb, cvg->size + 4, SEEK_SET)) < 0) 121 return ret; 122 123 /* NB: Not using avio_rl32() because no error checking. */ 124 if ((ret = avio_read(pb, buf, sizeof(buf))) < 0) 125 return ret; 126 else if (ret != sizeof(buf)) 127 return AVERROR(EIO); 128 129 if ((ret = avio_seek(pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0) 130 return ret; 131 132 *checksum = AV_RL32(buf); 133 return 0; 134} 135 136static int argo_cvg_read_header(AVFormatContext *s) 137{ 138 int ret; 139 AVStream *st; 140 AVCodecParameters *par; 141 uint8_t buf[ARGO_CVG_HEADER_SIZE]; 142 const char *filename = av_basename(s->url); 143 ArgoCVGDemuxContext *ctx = s->priv_data; 144 145 if (!(st = avformat_new_stream(s, NULL))) 146 return AVERROR(ENOMEM); 147 148 if ((ret = avio_read(s->pb, buf, ARGO_CVG_HEADER_SIZE)) < 0) 149 return ret; 150 else if (ret != ARGO_CVG_HEADER_SIZE) 151 return AVERROR(EIO); 152 153 ctx->header.size = AV_RL32(buf + 0); 154 ctx->header.unk1 = AV_RL32(buf + 4); 155 ctx->header.unk2 = AV_RL32(buf + 8); 156 157 if (ctx->header.size < 8) 158 return AVERROR_INVALIDDATA; 159 160 av_log(s, AV_LOG_TRACE, "size = %u\n", ctx->header.size); 161 av_log(s, AV_LOG_TRACE, "unk = %u, %u\n", ctx->header.unk1, ctx->header.unk2); 162 163 if ((ret = argo_cvg_read_checksum(s->pb, &ctx->header, &ctx->checksum)) < 0) 164 return ret; 165 166 av_log(s, AV_LOG_TRACE, "checksum = %u\n", ctx->checksum); 167 168 par = st->codecpar; 169 par->codec_type = AVMEDIA_TYPE_AUDIO; 170 par->codec_id = AV_CODEC_ID_ADPCM_PSX; 171 par->sample_rate = 22050; 172 173 for (size_t i = 0; i < FF_ARRAY_ELEMS(overrides); i++) { 174 const ArgoCVGOverride *ovr = overrides + i; 175 if (ovr->header.size != ctx->header.size || 176 ovr->header.unk1 != ctx->header.unk1 || 177 ovr->header.unk2 != ctx->header.unk2 || 178 ovr->checksum != ctx->checksum || 179 av_strcasecmp(filename, ovr->name) != 0) 180 continue; 181 182 av_log(s, AV_LOG_TRACE, "found override, name = %s\n", ovr->name); 183 par->sample_rate = ovr->sample_rate; 184 break; 185 } 186 187 par->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO; 188 189 par->bits_per_coded_sample = 4; 190 par->block_align = ARGO_CVG_BLOCK_ALIGN; 191 par->bit_rate = par->sample_rate * par->bits_per_coded_sample; 192 193 ctx->num_blocks = (ctx->header.size - 8) / ARGO_CVG_BLOCK_ALIGN; 194 195 av_log(s, AV_LOG_TRACE, "num blocks = %u\n", ctx->num_blocks); 196 197 avpriv_set_pts_info(st, 64, 1, par->sample_rate); 198 199 st->start_time = 0; 200 st->duration = ctx->num_blocks * ARGO_CVG_SAMPLES_PER_BLOCK; 201 st->nb_frames = ctx->num_blocks; 202 return 0; 203} 204 205static int argo_cvg_read_packet(AVFormatContext *s, AVPacket *pkt) 206{ 207 int ret; 208 AVStream *st = s->streams[0]; 209 ArgoCVGDemuxContext *ctx = s->priv_data; 210 211 if (ctx->blocks_read >= ctx->num_blocks) 212 return AVERROR_EOF; 213 214 ret = av_get_packet(s->pb, pkt, st->codecpar->block_align * 215 FFMIN(ARGO_CVG_NB_BLOCKS, ctx->num_blocks - ctx->blocks_read)); 216 217 if (ret < 0) 218 return ret; 219 220 if (ret % st->codecpar->block_align != 0) 221 return AVERROR_INVALIDDATA; 222 223 pkt->stream_index = 0; 224 pkt->duration = ARGO_CVG_SAMPLES_PER_BLOCK * (ret / st->codecpar->block_align); 225 pkt->pts = ctx->blocks_read * ARGO_CVG_SAMPLES_PER_BLOCK; 226 pkt->flags &= ~AV_PKT_FLAG_CORRUPT; 227 228 ctx->blocks_read += ret / st->codecpar->block_align; 229 230 return 0; 231} 232 233static int argo_cvg_seek(AVFormatContext *s, int stream_index, 234 int64_t pts, int flags) 235{ 236 int64_t ret; 237 ArgoCVGDemuxContext *ctx = s->priv_data; 238 239 if (pts != 0 || stream_index != 0) 240 return AVERROR(EINVAL); 241 242 if ((ret = avio_seek(s->pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0) 243 return ret; 244 245 ctx->blocks_read = 0; 246 return 0; 247} 248 249const AVInputFormat ff_argo_cvg_demuxer = { 250 .name = "argo_cvg", 251 .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"), 252 .priv_data_size = sizeof(ArgoCVGDemuxContext), 253 .read_probe = argo_cvg_probe, 254 .read_header = argo_cvg_read_header, 255 .read_packet = argo_cvg_read_packet, 256 .read_seek = argo_cvg_seek, 257}; 258#endif 259 260#if CONFIG_ARGO_CVG_MUXER 261static int argo_cvg_write_init(AVFormatContext *s) 262{ 263 ArgoCVGMuxContext *ctx = s->priv_data; 264 const AVCodecParameters *par; 265 266 if (s->nb_streams != 1) { 267 av_log(s, AV_LOG_ERROR, "CVG files have exactly one stream\n"); 268 return AVERROR(EINVAL); 269 } 270 271 par = s->streams[0]->codecpar; 272 273 if (par->codec_id != AV_CODEC_ID_ADPCM_PSX) { 274 av_log(s, AV_LOG_ERROR, "%s codec not supported\n", 275 avcodec_get_name(par->codec_id)); 276 return AVERROR(EINVAL); 277 } 278 279 if (par->ch_layout.nb_channels != 1) { 280 av_log(s, AV_LOG_ERROR, "CVG files only support 1 channel\n"); 281 return AVERROR(EINVAL); 282 } 283 284 if (par->block_align != ARGO_CVG_BLOCK_ALIGN) 285 return AVERROR(EINVAL); 286 287 if (!ctx->skip_rate_check && par->sample_rate != 22050) { 288 av_log(s, AV_LOG_ERROR, "Sample rate must be 22050\n"); 289 return AVERROR(EINVAL); 290 } 291 292 if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) { 293 av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n"); 294 return AVERROR(EINVAL); 295 } 296 297 return 0; 298} 299 300static int argo_cvg_write_header(AVFormatContext *s) 301{ 302 ArgoCVGMuxContext *ctx = s->priv_data; 303 304 avio_wl32(s->pb, 0); /* Size, fixed later. */ 305 avio_wl32(s->pb, 0); 306 avio_wl32(s->pb, 1); 307 308 ctx->checksum = 1; 309 ctx->size = 8; 310 return 0; 311} 312 313static int argo_cvg_write_packet(AVFormatContext *s, AVPacket *pkt) 314{ 315 ArgoCVGMuxContext *ctx = s->priv_data; 316 AVCodecParameters *par = s->streams[0]->codecpar; 317 318 if (pkt->size % par->block_align != 0) 319 return AVERROR_INVALIDDATA; 320 321 avio_write(s->pb, pkt->data, pkt->size); 322 323 ctx->size += pkt->size; 324 325 if (ctx->size > UINT32_MAX) 326 return AVERROR_INVALIDDATA; 327 328 for (int i = 0; i < pkt->size; i++) 329 ctx->checksum += pkt->data[i]; 330 331 return 0; 332} 333 334static int argo_cvg_write_trailer(AVFormatContext *s) 335{ 336 ArgoCVGMuxContext *ctx = s->priv_data; 337 int64_t ret; 338 339 ctx->checksum += (ctx->size & 255) 340 + ((ctx->size>> 8) & 255) 341 + ((ctx->size>>16) & 255) 342 + (ctx->size>>24); 343 344 av_log(s, AV_LOG_TRACE, "size = %zu\n", ctx->size); 345 av_log(s, AV_LOG_TRACE, "checksum = %u\n", ctx->checksum); 346 347 avio_wl32(s->pb, ctx->checksum); 348 349 if ((ret = avio_seek(s->pb, 0, SEEK_SET)) < 0) 350 return ret; 351 352 avio_wl32(s->pb, (uint32_t)ctx->size); 353 return 0; 354} 355 356static const AVOption argo_cvg_options[] = { 357 { 358 .name = "skip_rate_check", 359 .help = "skip sample rate check", 360 .offset = offsetof(ArgoCVGMuxContext, skip_rate_check), 361 .type = AV_OPT_TYPE_BOOL, 362 .default_val = {.i64 = 0}, 363 .min = 0, 364 .max = 1, 365 .flags = AV_OPT_FLAG_ENCODING_PARAM 366 }, 367 { NULL } 368}; 369 370static const AVClass argo_cvg_muxer_class = { 371 .class_name = "argo_cvg_muxer", 372 .item_name = av_default_item_name, 373 .option = argo_cvg_options, 374 .version = LIBAVUTIL_VERSION_INT 375}; 376 377const AVOutputFormat ff_argo_cvg_muxer = { 378 .name = "argo_cvg", 379 .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"), 380 .extensions = "cvg", 381 .audio_codec = AV_CODEC_ID_ADPCM_PSX, 382 .video_codec = AV_CODEC_ID_NONE, 383 .init = argo_cvg_write_init, 384 .write_header = argo_cvg_write_header, 385 .write_packet = argo_cvg_write_packet, 386 .write_trailer = argo_cvg_write_trailer, 387 .priv_class = &argo_cvg_muxer_class, 388 .priv_data_size = sizeof(ArgoCVGMuxContext), 389}; 390#endif 391