Added files
This commit is contained in:
99
README.md
99
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` | Ошибка: не найден файл образа |
|
||||
|
||||
|
||||
199
ccd2cue.c
Normal file
199
ccd2cue.c
Normal file
@@ -0,0 +1,199 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#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 <ccd_file>\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;
|
||||
}
|
||||
Reference in New Issue
Block a user