ChakkariCopy.cc 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #include "ChakkariCopy.hh"
  2. #include "CliComm.hh"
  3. #include "serialize.hh"
  4. // The cartridge has 2 buttons with 2 LEDs, marked PAUSE and COPY. It also has
  5. // a toggle switch with the options COPY and RAM. Setting this toggle to RAM
  6. // makes the cartridge a 16kB RAM expansion.
  7. //
  8. // The following information is provided by Rudolf Lechleitner, who devised it
  9. // together with Enrico Barbisan.
  10. //
  11. // ----------------------------------------------------------------------------
  12. //
  13. // The memory layout:
  14. //
  15. // +--------------------------------------------+
  16. // | 0000-3FFF: 16 KB RAM in COPY mode |
  17. // | Page 0: available in COPY mode only |
  18. // | can be toggled to read-only |
  19. // +--------------------------------------------+
  20. // | 4000-5FFF: 8KB ROM code |
  21. // | Page 1: + |
  22. // | 6000-67FF: 2KB internal RAM |
  23. // +--------------------------------------------+
  24. //
  25. // The shown 16KB RAM in page 0 are only available in COPY mode, but can be
  26. // toggled to read-only. The location of the RAM in RAM mode is unknown.
  27. //
  28. // The memory at 6800-7FFF is unused by the cartridge.
  29. //
  30. // Internal handling: during cartridge initialisation Chakkari Copy copies the
  31. // contents of BIOS page 0 into its own RAM and patches some entries to
  32. // capture the input of PAUSE and COPY buttons of the cartridge. Afterwards the
  33. // patched memory is set to read-only.
  34. // PAUSE and COPY itself are processed by the routines in the ROM. In case the
  35. // page 0 is (for any reason) switched to the original MSX-BIOS the cartridge
  36. // buttons are no longer working. In this case CALL SCHANGE switches the BIOS
  37. // to the patched version again.
  38. //
  39. // In case a program switches screen modes in a way that prevents Chakkara Copy
  40. // from detecting the correct parameters Chakkara Copy can be started by
  41. // keeping the COPY button pressed. In this case the screen-mode autodetect
  42. // will be skipped and the parameters can be entered manually.
  43. // This appears to be an undocumented feature, confirmed by typos and a very
  44. // rudimentary screen handling.
  45. //
  46. // In case the printer is not ready or not available the printing can be
  47. // aborted by pressing Ctrl+Stop.
  48. //
  49. // The Chakkari Copy cartridge has one register, which controls its complete
  50. // behavior. This register is available at MSX port &H7F.
  51. //
  52. // READ only:
  53. //
  54. // +-----------------------------------------+
  55. // | 1 | 1 | 1 | 1 | 1 | PSE | CPY |
  56. // +-----+-----+-----+-----+-----+-----+-----+
  57. // CPY: 0 = Copy button pressed, 1 = Copy button released
  58. // PSE: 0 = Pause button pressed, 1 = Pause button released
  59. //
  60. // WRITE only:
  61. //
  62. // +-----+-----+-----+-----+-----+-----+-----+
  63. // | 1 | 1 | 1 | 1 | RAM | PSE | CPY |
  64. // +-----+-----+-----+-----+-----+-----+-----+
  65. // CPY: 0 = Copy LED on, 1 = Copy LED off
  66. // PSE: 0 = Pause LED on, 1 = Pause LED off
  67. // RAM: 0 = Page 0 in RAM mode, 1 = Page 0 in ROM mode (applies to COPY mode only)
  68. //
  69. // ----------------------------------------------------------------------------
  70. // The RAM mode has now been implemented to switch the RAM to page 1. This
  71. // doesn't seem to be the most logical, but it's closest to the output of
  72. // MSXMEM2. It could still be useful for 32kB machines.
  73. //
  74. // The work RAM has been mirrored in the 0x6000-0x8000 area to match the output
  75. // of MSXMEM2.BAS which was run on a machine with the real cartridge inserted.
  76. // It showed 'R' on page 1 for both COPY and RAM mode. The only thing that
  77. // doesn't match yet is that it also showed 'R' in page 0 for COPY mode for
  78. // subslots 0 and 1. The latter is unexplained so far.
  79. namespace openmsx {
  80. ChakkariCopy::ChakkariCopy(const DeviceConfig& config)
  81. : MSXDevice(config)
  82. , biosRam(config, getName() + " BIOS RAM", "Chakkari Copy BIOS RAM", 0x4000)
  83. , workRam(config, getName() + " work RAM", "Chakkari Copy work RAM", 0x0800)
  84. , rom(getName() + " ROM", "rom", config)
  85. , pauseButtonPressedSetting(getCommandController(),
  86. getName() + " PAUSE button pressed",
  87. "controls the PAUSE button state", false, Setting::DONT_SAVE)
  88. , copyButtonPressedSetting(getCommandController(),
  89. getName() + " COPY button pressed",
  90. "controls the COPY button state", false, Setting::DONT_SAVE)
  91. , modeSetting(getCommandController(), getName() + " mode",
  92. "Sets mode of the cartridge: in COPY mode you can hardcopy MSX1 screens, "
  93. "in RAM mode you just have a 16kB RAM expansion", ChakkariCopy::COPY,
  94. EnumSetting<ChakkariCopy::Mode>::Map{
  95. {"COPY", ChakkariCopy::COPY}, {"RAM", ChakkariCopy::RAM}})
  96. , reg(0xFF) // avoid UMR in initial writeIO()
  97. {
  98. reset(getCurrentTime());
  99. modeSetting.attach(*this);
  100. }
  101. ChakkariCopy::~ChakkariCopy()
  102. {
  103. modeSetting.detach(*this);
  104. }
  105. void ChakkariCopy::reset(EmuTime::param time)
  106. {
  107. writeIO(0, 0xFF, time);
  108. }
  109. void ChakkariCopy::writeIO(word /*port*/, byte value, EmuTime::param /*time*/)
  110. {
  111. byte diff = reg ^ value;
  112. reg = value;
  113. if (diff & 0x01) {
  114. getCliComm().printInfo(getName(), " COPY LED ",
  115. (((value & 1) == 0x01) ? "OFF" : "ON"));
  116. }
  117. if (diff & 0x02) {
  118. getCliComm().printInfo(getName(), " PAUSE LED ",
  119. (((value & 2) == 0x02) ? "OFF" : "ON"));
  120. }
  121. if (diff & 0x04) {
  122. if (modeSetting.getEnum() == COPY) {
  123. // page 0 toggles writable/read-only
  124. invalidateDeviceRWCache(0x0000, 0x4000);
  125. }
  126. }
  127. }
  128. byte ChakkariCopy::readIO(word port, EmuTime::param time)
  129. {
  130. return peekIO(port, time);
  131. }
  132. byte ChakkariCopy::peekIO(word /*port*/, EmuTime::param /*time*/) const
  133. {
  134. byte retVal = 0xFF;
  135. if (copyButtonPressedSetting .getBoolean()) retVal &= ~0x01;
  136. if (pauseButtonPressedSetting.getBoolean()) retVal &= ~0x02;
  137. return retVal;
  138. }
  139. byte ChakkariCopy::readMem(word address, EmuTime::param time)
  140. {
  141. return peekMem(address, time);
  142. }
  143. byte ChakkariCopy::peekMem(word address, EmuTime::param /*time*/) const
  144. {
  145. return *getReadCacheLine(address);
  146. }
  147. const byte* ChakkariCopy::getReadCacheLine(word address) const
  148. {
  149. if (modeSetting.getEnum() == COPY) {
  150. // page 0
  151. if (address < 0x4000) {
  152. return &biosRam[address];
  153. }
  154. // page 1
  155. if ((0x4000 <= address) && (address < 0x6000)) {
  156. return &rom[address & 0x1FFF];
  157. }
  158. // the work RAM is mirrored in 0x6000-0x8000, see above
  159. if ((0x6000 <= address) && (address < 0x8000)) {
  160. return &workRam[address & 0x07FF];
  161. }
  162. } else {
  163. // page 1 RAM mode
  164. if ((0x4000 <= address) && (address < 0x8000)) {
  165. return &biosRam[address & 0x3FFF];
  166. }
  167. }
  168. return unmappedRead;
  169. }
  170. void ChakkariCopy::writeMem(word address, byte value, EmuTime::param /*time*/)
  171. {
  172. *getWriteCacheLine(address) = value;
  173. }
  174. byte* ChakkariCopy::getWriteCacheLine(word address) const
  175. {
  176. if (modeSetting.getEnum() == COPY) {
  177. // page 0
  178. if ((address < 0x4000) && ((reg & 0x04) == 0)) {
  179. return const_cast<byte*>(&biosRam[address & 0x3FFF]);
  180. }
  181. // page 1
  182. // the work RAM is mirrored in 0x6000-0x8000, see above
  183. if ((0x6000 <= address) && (address < 0x8000)) {
  184. return const_cast<byte*>(&workRam[address & 0x07FF]);
  185. }
  186. } else {
  187. // page 1 RAM mode
  188. if ((0x4000 <= address) && (address < 0x8000)) {
  189. return const_cast<byte*>(&biosRam[address & 0x3FFF]);
  190. }
  191. }
  192. return unmappedWrite;
  193. }
  194. void ChakkariCopy::update(const Setting& /*setting*/)
  195. {
  196. // switch COPY <-> RAM mode, memory layout changes
  197. invalidateDeviceRWCache();
  198. }
  199. template<typename Archive>
  200. void ChakkariCopy::serialize(Archive& ar, unsigned /*version*/)
  201. {
  202. ar.template serializeBase<MSXDevice>(*this);
  203. ar.serialize("biosRam", biosRam,
  204. "workRam", workRam,
  205. "reg", reg);
  206. if (ar.isLoader()) {
  207. writeIO(0, reg, getCurrentTime());
  208. }
  209. }
  210. INSTANTIATE_SERIALIZE_METHODS(ChakkariCopy);
  211. REGISTER_MSXDEVICE(ChakkariCopy, "ChakkariCopy");
  212. } // namespace openmsx