From a3dbb1b169f0bec0bc08fc21bd31ed337a605188 Mon Sep 17 00:00:00 2001 From: Roman Sergienko Date: Sat, 29 Nov 2025 13:18:54 +0300 Subject: [PATCH] Added files --- README.md | 99 ++++++++++++++++++++++++++- ccd2cue.c | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 ccd2cue.c diff --git a/README.md b/README.md index 93b8d52..14400ac 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,97 @@ -# ccd2cue -Bruh, just a ccd -> cue converter +# CCD2CUE - Конвертер CCD в CUE файлы + +## Описание + +`ccd2cue` - Утилита для преобразования файлов образов CD/DVD в формате CCD (CloneCD) в CUE-файлы. + +## Особенности + +- Конвертирует CCD-файлы в CUE-формат +- Автоматически определяет файлы образов (.img, .bin, .iso) +- Поддерживает различные типы треков (данные и аудио) +- Корректно обрабатывает временные метки и индексы треков +- Не требует внешних зависимостей (кроме стандартной библиотеки C) + +## Установка и компиляция + +### Требования +- Компилятор GCC +- Стандартная библиотека C +- POSIX-совместимая операционная система + +### Компиляция + +```bash +gcc -o ccd2cue ccd2cue.c +``` + +## Использование + +### Базовый синтаксис + +```bash +ccd2cue -c <файл.ccd> +``` + +### Параметры командной строки + +| Параметр | Описание | +|----------|----------| +| `-c <файл>` | Обязательный параметр. Указывает путь к входному CCD-файлу | + +### Примеры использования + +```bash +# Конвертировать файл example.ccd +./ccd2cue -c example.ccd + +# Конвертировать файл в другом каталоге +./ccd2cue -c /path/to/disc.ccd +``` + +## Входные и выходные файлы + +### Входные файлы +- **CCD-файл**: Файл с расширением `.ccd`, содержащий метаданные о структуре диска +- **Файл образа**: Автоматически определяется по расширениям: + - `.img` + - `.bin` + - `.iso` + +### Выходные файлы +- **CUE-файл**: Создается с тем же именем, что и CCD-файл, но с расширением `.cue` + +### Пример структуры файлов + +``` +директория_проекта/ +├── disc.ccd # Входной файл +├── disc.img # Файл образа (автоопределение) +└── disc.cue # Выходной файл (создается программой) +``` + +## Форматы поддерживаемых треков + +Программа поддерживает два основных типа треков: + +| Тип трека | Описание | CUE-формат | +|-----------|----------|------------| +| Данные | Треки с компьютерными данными | `MODE1/2352` | +| Аудио | Аудио треки | `AUDIO` | + +## Обработка временных меток + +Программа автоматически корректирует временные метки: +- Вычитает 2 секунды из времени каждого трека +- Корректно обрабатывает переходы через минуту +- Сохраняет точность до кадров (frames) + +## Коды возврата + +| Код | Значение | +|-----|----------| +| `0` | Успешное выполнение | +| `1` | Ошибка: не указан CCD-файл | +| `2` | Ошибка: не удалось прочитать CCD-файл | +| `3` | Ошибка: не найден файл образа | + diff --git a/ccd2cue.c b/ccd2cue.c new file mode 100644 index 0000000..52b67af --- /dev/null +++ b/ccd2cue.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LINE_LEN 256 +#define MAX_FILENAME_LEN 256 +#define MAX_SECTIONS 100 + +typedef struct { + char name[MAX_LINE_LEN]; + char control[MAX_LINE_LEN]; + char session[MAX_LINE_LEN]; + char pmin[MAX_LINE_LEN]; + char psec[MAX_LINE_LEN]; + char pframe[MAX_LINE_LEN]; + char plba[MAX_LINE_LEN]; +} Section; + +typedef struct { + Section sections[MAX_SECTIONS]; + int count; +} Config; + +void config_init(Config *config) { + config->count = 0; +} + +int is_section_line(const char *line) { + return line[0] == '[' && strchr(line, ']') != NULL; +} + +void parse_section_name(const char *line, char *section_name) { + const char *start = strchr(line, '[') + 1; + const char *end = strchr(line, ']'); + strncpy(section_name, start, end - start); + section_name[end - start] = '\0'; +} + +void trim_whitespace(char *str) { + char *end; + while (isspace((unsigned char)*str)) str++; + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + *(end + 1) = '\0'; +} + +int parse_config_file(const char *filename, Config *config) { + FILE *file = fopen(filename, "r"); + if (!file) return -1; + + char line[MAX_LINE_LEN]; + int current_section = -1; + + while (fgets(line, sizeof(line), file)) { + trim_whitespace(line); + + if (strlen(line) == 0) continue; + + if (is_section_line(line)) { + current_section = config->count++; + if (current_section >= MAX_SECTIONS) break; + parse_section_name(line, config->sections[current_section].name); + continue; + } + + if (current_section >= 0) { + char *sep = strchr(line, '='); + if (!sep) continue; + *sep = '\0'; + char *key = line; + char *value = sep + 1; + trim_whitespace(key); + trim_whitespace(value); + + Section *sec = &config->sections[current_section]; + if (strcmp(key, "Control") == 0) strcpy(sec->control, value); + else if (strcmp(key, "Session") == 0) strcpy(sec->session, value); + else if (strcmp(key, "PMin") == 0) strcpy(sec->pmin, value); + else if (strcmp(key, "PSec") == 0) strcpy(sec->psec, value); + else if (strcmp(key, "PFrame") == 0) strcpy(sec->pframe, value); + else if (strcmp(key, "PLBA") == 0) strcpy(sec->plba, value); + } + } + + fclose(file); + return 0; +} + +int find_image_file(char *found_name) { + const char *extensions[] = {".img", ".bin", ".iso", NULL}; + DIR *dir = opendir("."); + if (!dir) return -1; + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_type != DT_REG) continue; + + const char *name = entry->d_name; + const char *dot = strrchr(name, '.'); + if (!dot) continue; + + for (int i = 0; extensions[i]; i++) { + if (strcasecmp(dot, extensions[i]) == 0) { + strcpy(found_name, name); + closedir(dir); + return 0; + } + } + } + + closedir(dir); + return -1; +} + +void write_cue_file(const char *ccd_file, const Config *config, const char *img_file) { + char cue_file[MAX_FILENAME_LEN]; + strcpy(cue_file, ccd_file); + char *dot = strrchr(cue_file, '.'); + if (dot) *dot = '\0'; + strcat(cue_file, ".cue"); + + FILE *cue_fp = fopen(cue_file, "w"); + if (!cue_fp) { + perror("Failed to create CUE file"); + return; + } + + fprintf(cue_fp, "FILE \"%s\" BINARY\n", img_file); + + int track_counter = 0; + int begin_found = 0; + + for (int i = 0; i < config->count; i++) { + const Section *sec = &config->sections[i]; + if (strstr(sec->name, "Entry") != sec->name) continue; + + int plba = atoi(sec->plba); + if (plba == 0) begin_found = 1; + + if (!begin_found) continue; + + track_counter++; + int minute = atoi(sec->pmin); + int second = atoi(sec->psec); + int frame = atoi(sec->pframe); + + if (second == 0) { + if (minute >= 1) { + minute--; + second = 60; + } + } + second -= 2; + + const char *track_type = strcmp(sec->control, "0x04") == 0 ? "MODE1/2352" : "AUDIO"; + int track_index = atoi(sec->session); + + fprintf(cue_fp, " TRACK %02d %s\n", track_counter, track_type); + fprintf(cue_fp, " INDEX %02d %02d:%02d:%02d\n", + track_index, minute, second, frame); + } + + fclose(cue_fp); +} + +int main(int argc, char *argv[]) { + char *ccd_file = NULL; + int opt; + + while ((opt = getopt(argc, argv, "c:")) != -1) { + if (opt == 'c') ccd_file = optarg; + } + + if (!ccd_file) { + fprintf(stderr, "Usage: %s -c \n", argv[0]); + return 1; + } + + Config config; + config_init(&config); + + if (parse_config_file(ccd_file, &config) != 0) { + fprintf(stderr, "Failed to parse CCD file\n"); + return 1; + } + + char img_file[MAX_FILENAME_LEN] = ""; + if (find_image_file(img_file) != 0) { + fprintf(stderr, "No image file found\n"); + return 1; + } + + write_cue_file(ccd_file, &config, img_file); + return 0; +}