famicom-disk.cpp 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. auto FamicomDisk::export(string location) -> vector<uint8_t> {
  2. vector<uint8_t> data;
  3. for(auto& filename : directory::files(location, "disk?*.side?*")) {
  4. append(data, {location, filename});
  5. }
  6. return data;
  7. }
  8. auto FamicomDisk::heuristics(vector<uint8_t>& data, string location) -> string {
  9. string s;
  10. s += "game\n";
  11. s +={" name: ", Media::name(location), "\n"};
  12. s +={" label: ", Media::name(location), "\n"};
  13. return s;
  14. }
  15. auto FamicomDisk::import(string location) -> string {
  16. auto input = Media::read(location);
  17. if(input.size() < 65500) return "disk image is too small";
  18. location = {pathname, Location::prefix(location), "/"};
  19. if(!directory::create(location)) return "output directory not writable";
  20. if(input.size() % 65500 == 16) {
  21. //iNES and fwNES headers are unnecessary
  22. memory::move(&input[0], &input[16], input.size() - 16);
  23. input.resize(input.size() - 16);
  24. }
  25. array_view<uint8_t> view = input;
  26. uint disk = 0, side = 0;
  27. while(auto output = transform(view)) {
  28. string name{"disk", 1 + disk, ".", "side", !side ? "A" : "B"};
  29. file::write({location, name}, output);
  30. side ^= 1;
  31. if(!side) disk++;
  32. view += 65500;
  33. }
  34. return {};
  35. }
  36. auto FamicomDisk::transform(array_view<uint8_t> input) -> vector<uint8_t> {
  37. if(input.size() < 65500) return {};
  38. array_view<uint8_t> data{input.data(), 65500};
  39. if(data[0x00] != 0x01) return {};
  40. if(data[0x38] != 0x02) return {};
  41. if(data[0x3a] != 0x03) return {};
  42. if(data[0x4a] != 0x04) return {};
  43. vector<uint8_t> output;
  44. uint16_t crc16 = 0;
  45. auto hash = [&](uint8_t byte) {
  46. for(uint bit : range(8)) {
  47. bool carry = crc16 & 1;
  48. crc16 = crc16 >> 1 | bool(byte & 1 << bit) << 15;
  49. if(carry) crc16 ^= 0x8408;
  50. }
  51. };
  52. auto write = [&](uint8_t byte) {
  53. hash(byte);
  54. output.append(byte);
  55. };
  56. auto flush = [&] {
  57. hash(0x00);
  58. hash(0x00);
  59. output.append(crc16 >> 0);
  60. output.append(crc16 >> 8);
  61. crc16 = 0;
  62. };
  63. //block 1
  64. for(uint n : range(0xe00)) write(0x00); //pregap
  65. write(0x80);
  66. for(uint n : range(0x38)) write(*data++);
  67. flush();
  68. //block 2
  69. for(uint n : range(0x80)) write(0x00); //gap
  70. write(0x80);
  71. for(uint n : range(0x02)) write(*data++);
  72. flush();
  73. while(true) {
  74. if(data[0x00] != 0x03 || data.size() < 0x11) break;
  75. uint16_t size = data[0x0d] << 0 | data[0x0e] << 8;
  76. if(data[0x10] != 0x04 || data.size() < 0x11 + size) break;
  77. //block 3
  78. for(uint n : range(0x80)) write(0x00); //gap
  79. write(0x80);
  80. for(uint n : range(0x10)) write(*data++);
  81. flush();
  82. //block 4
  83. for(uint n : range(0x80)) write(0x00); //gap
  84. write(0x80);
  85. for(uint n : range(1 + size)) write(*data++);
  86. flush();
  87. }
  88. //note: actual maximum capacity of a Famicom Disk is currently unknown
  89. while(output.size() < 0x12000) output.append(0x00); //expand if too small
  90. output.resize(0x12000); //shrink if too large
  91. return output;
  92. }