|
| 1 | +#include "libxa_deinterleaver.hxx" |
| 2 | + |
| 3 | +#define VER "VERSION" |
| 4 | +#define XA_DATA_SIZE 2336 |
| 5 | +#define CD_SECTOR_SIZE 2352 |
| 6 | +#define SUBHEAD_OFFSET 16 |
| 7 | + |
| 8 | +int check_file(FILE *fp, const std::filesystem::path &path, uintmax_t &fileSize, uint8_t *buffer) |
| 9 | +{ |
| 10 | + if (!fp) |
| 11 | + { |
| 12 | + fprintf(stderr, "Error: Cannot open file \"%s\".\n", path.filename().string().c_str()); |
| 13 | + exit(1); |
| 14 | + } |
| 15 | + |
| 16 | + fileSize = std::filesystem::file_size(path); |
| 17 | + if (fread(buffer + SUBHEAD_OFFSET, 1, XA_DATA_SIZE, fp) == XA_DATA_SIZE) |
| 18 | + { |
| 19 | + if (fileSize % CD_SECTOR_SIZE == 0 && memcmp(buffer + SUBHEAD_OFFSET, buffer, 12) == 0) |
| 20 | + return CD_SECTOR_SIZE; |
| 21 | + if (fileSize % XA_DATA_SIZE == 0) |
| 22 | + return XA_DATA_SIZE; |
| 23 | + } |
| 24 | + |
| 25 | + fprintf(stderr, "Error: File \"%s\" is not aligned to 2336/2352 bytes.\n", path.filename().string().c_str()); |
| 26 | + exit(1); |
| 27 | +} |
| 28 | + |
| 29 | +int main(int argc, char *argv[]) |
| 30 | +{ |
| 31 | + if (argc < 4) |
| 32 | + { |
| 33 | + printf("xa-replacer " VER " by N4gtan\n\n" |
| 34 | + " Usage: xa-replacer <target> <source> <sector>\n\n" |
| 35 | + "Target: File to be modified (in-place)\n" |
| 36 | + "Source: Deinterleaved XA file to inject\n" |
| 37 | + "Sector: Starting LBA of the stream to replace\n"); |
| 38 | + return 0; |
| 39 | + } |
| 40 | + |
| 41 | + const std::filesystem::path tgtPath = argv[1]; |
| 42 | + const std::filesystem::path srcPath = argv[2]; |
| 43 | + const int sector = atoi(argv[3]); |
| 44 | + |
| 45 | + const std::vector<deinterleaver::FileInfo> entries = std::move(deinterleaver(tgtPath).entries); |
| 46 | + const auto it = std::find_if(entries.begin(), entries.end(), [sector](const auto &entry) { return entry.begSec == sector; }); |
| 47 | + if (it == entries.end()) |
| 48 | + { |
| 49 | + fprintf(stderr, "Error: There is no stream that starts at sector %d.\n", sector); |
| 50 | + return 1; |
| 51 | + } |
| 52 | + const auto &entry = *it; |
| 53 | + |
| 54 | + uint8_t buffer[CD_SECTOR_SIZE] {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; |
| 55 | + std::unique_ptr<FILE, decltype(&fclose)> tgtFile(fopen(tgtPath.string().c_str(), "r+b"), &fclose); |
| 56 | + std::unique_ptr<FILE, decltype(&fclose)> srcFile(fopen(srcPath.string().c_str(), "rb"), &fclose); |
| 57 | + |
| 58 | + // Retrieve sector sizes and offsets |
| 59 | + uintmax_t tgtSize, srcSize; |
| 60 | + const int tgtSectorSize = check_file(tgtFile.get(), tgtPath, tgtSize, buffer); |
| 61 | + const int srcSectorSize = check_file(srcFile.get(), srcPath, srcSize, buffer); |
| 62 | + const int dataOffset = tgtSectorSize - XA_DATA_SIZE; |
| 63 | + const int buffOffset = CD_SECTOR_SIZE - srcSectorSize; |
| 64 | + |
| 65 | + // Validate target available space |
| 66 | + const int srcSectorCount = srcSize / srcSectorSize; |
| 67 | + if (srcSectorCount > entry.sectorCount) |
| 68 | + { |
| 69 | + fprintf(stderr, "Error: Source exceeds target stream size by %d sector(s).\n", srcSectorCount - entry.sectorCount); |
| 70 | + return 1; |
| 71 | + } |
| 72 | + |
| 73 | + // Check eof bit and reset file pointers |
| 74 | + fseeko(tgtFile.get(), static_cast<int64_t>(entry.endSec) * tgtSectorSize + dataOffset + 2, SEEK_SET); |
| 75 | + uint8_t eofBit = fgetc(tgtFile.get()) & 0x80; |
| 76 | + fseeko(srcFile.get(), 0, SEEK_SET); |
| 77 | + fseeko(tgtFile.get(), static_cast<int64_t>(sector) * tgtSectorSize + dataOffset, SEEK_SET); |
| 78 | + |
| 79 | + // Write data |
| 80 | + auto processSector = [&](auto tag) -> void |
| 81 | + { |
| 82 | + [[maybe_unused]] size_t __ = fread(buffer + buffOffset, 1, srcSectorSize, srcFile.get()); |
| 83 | + buffer[SUBHEAD_OFFSET + 4] = buffer[SUBHEAD_OFFSET] = entry.filenum; |
| 84 | + buffer[SUBHEAD_OFFSET + 5] = buffer[SUBHEAD_OFFSET + 1] = entry.channel; |
| 85 | + if constexpr (tag) |
| 86 | + buffer[SUBHEAD_OFFSET + 6] = buffer[SUBHEAD_OFFSET + 2] = (buffer[SUBHEAD_OFFSET + 2] & 0x7F) | eofBit; |
| 87 | + fwrite(buffer + SUBHEAD_OFFSET, 1, XA_DATA_SIZE, tgtFile.get()); |
| 88 | + }; |
| 89 | + |
| 90 | + const int skipSize = entry.sectorStride * tgtSectorSize + dataOffset; |
| 91 | + for (int i = 1; i < srcSectorCount; ++i) |
| 92 | + { |
| 93 | + processSector(std::false_type{}); |
| 94 | + fseeko(tgtFile.get(), skipSize, SEEK_CUR); |
| 95 | + } |
| 96 | + processSector(std::true_type{}); |
| 97 | + |
| 98 | + // Null filler |
| 99 | + int sectorsToFill = (entry.sectorCount - entry.nullTermination) - srcSectorCount; |
| 100 | + if (sectorsToFill > 0) |
| 101 | + { |
| 102 | + printf("Warning: Source is smaller than target stream size by %d sector(s).\n" |
| 103 | + " Write null sector(s) as filler? <Y/n> ", sectorsToFill); |
| 104 | + |
| 105 | + const uint8_t key = getchar(); |
| 106 | + if (std::tolower(key) != 'n') |
| 107 | + { |
| 108 | + memcpy(&buffer[SUBHEAD_OFFSET], &entry.nullSubheader, sizeof(entry.nullSubheader)); |
| 109 | + memcpy(&buffer[SUBHEAD_OFFSET + 4], &entry.nullSubheader, sizeof(entry.nullSubheader)); |
| 110 | + memset(&buffer[SUBHEAD_OFFSET + 8], entry.nullSubheader[2] == 0xFF ? 0xFF : 0, sizeof(buffer) - (SUBHEAD_OFFSET + 8)); |
| 111 | + while (sectorsToFill-- > 0) |
| 112 | + { |
| 113 | + fseeko(tgtFile.get(), skipSize, SEEK_CUR); |
| 114 | + fwrite(buffer + SUBHEAD_OFFSET, 1, XA_DATA_SIZE, tgtFile.get()); |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + printf("Process complete.\n"); |
| 120 | + return 0; |
| 121 | +} |
0 commit comments