mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +00:00
Merge pull request #558 from TomHarte/CodemastersDetection
Implements the Codemasters paging scheme
This commit is contained in:
commit
d5f1e76707
@ -11,6 +11,9 @@
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
if(media.cartridges.empty())
|
||||
return {};
|
||||
|
||||
TargetList targets;
|
||||
std::unique_ptr<Target> target(new Target);
|
||||
|
||||
@ -23,10 +26,53 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
|
||||
target->model = Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
// If this is a Master System title, look for a ROM header.
|
||||
if(target->model == Target::Model::MasterSystem) {
|
||||
const auto &data = media.cartridges.front()->get_segments()[0].data;
|
||||
|
||||
// First try to locate a header.
|
||||
size_t header_offset = 0;
|
||||
size_t potential_offsets[] = {0x1ff0, 0x3ff0, 0x7ff0};
|
||||
for(auto potential_offset: potential_offsets) {
|
||||
if(!memcmp(&data[potential_offset], "TMR SEGA", 8)) {
|
||||
header_offset = potential_offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If a header was found, use it to crib region.
|
||||
if(header_offset) {
|
||||
// Treat export titles as European by default; decline to
|
||||
// do so only if (US) or (NTSC) is in the file name.
|
||||
const uint8_t region = data[header_offset + 0x0f] >> 4;
|
||||
switch(region) {
|
||||
default: break;
|
||||
case 4: {
|
||||
std::string lowercase_name = file_name;
|
||||
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
|
||||
if(lowercase_name.find("(jp)") == std::string::npos) {
|
||||
target->region =
|
||||
(lowercase_name.find("(us)") == std::string::npos &&
|
||||
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
// Also check for a Codemasters header.
|
||||
// If one is found, set the paging scheme appropriately.
|
||||
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
|
||||
if(
|
||||
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
|
||||
(inverse_checksum&0xff) == data[0x7fe8] &&
|
||||
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
|
||||
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
|
||||
) {
|
||||
target->paging_scheme = Target::PagingScheme::Codemasters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target->media.cartridges = media.cartridges;
|
||||
|
||||
if(!target->media.empty())
|
||||
targets.push_back(std::move(target));
|
||||
|
||||
targets.push_back(std::move(target));
|
||||
return targets;
|
||||
}
|
||||
|
@ -19,7 +19,20 @@ struct Target: public ::Analyser::Static::Target {
|
||||
SG1000
|
||||
};
|
||||
|
||||
enum class Region {
|
||||
Japan,
|
||||
USA,
|
||||
Europe
|
||||
};
|
||||
|
||||
enum class PagingScheme {
|
||||
Sega,
|
||||
Codemasters
|
||||
};
|
||||
|
||||
Model model = Model::MasterSystem;
|
||||
Region region = Region::Japan;
|
||||
PagingScheme paging_scheme = PagingScheme::Sega;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,11 @@ const uint8_t StatusSpriteOverflow = 0x40;
|
||||
const int StatusSpriteCollisionShift = 5;
|
||||
const uint8_t StatusSpriteCollision = 0x20;
|
||||
|
||||
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
|
||||
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
|
||||
const unsigned int CRTCyclesPerLine = 1365;
|
||||
const unsigned int CRTCyclesDivider = 4;
|
||||
|
||||
struct ReverseTable {
|
||||
std::uint8_t map[256];
|
||||
|
||||
@ -44,9 +49,7 @@ struct ReverseTable {
|
||||
|
||||
Base::Base(Personality p) :
|
||||
personality_(p),
|
||||
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
|
||||
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
|
||||
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {
|
||||
crt_(new Outputs::CRT::CRT(CRTCyclesPerLine, CRTCyclesDivider, Outputs::CRT::DisplayType::NTSC60, 4)) {
|
||||
|
||||
switch(p) {
|
||||
case TI::TMS::TMS9918A:
|
||||
@ -98,6 +101,21 @@ TMS9918::TMS9918(Personality p):
|
||||
crt_->set_immediate_default_phase(0.85f);
|
||||
}
|
||||
|
||||
void TMS9918::set_tv_standard(TVStandard standard) {
|
||||
switch(standard) {
|
||||
case TVStandard::PAL:
|
||||
mode_timing_.total_lines = 313;
|
||||
mode_timing_.first_vsync_line = 253;
|
||||
crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::PAL50);
|
||||
break;
|
||||
default:
|
||||
mode_timing_.total_lines = 262;
|
||||
mode_timing_.first_vsync_line = 227;
|
||||
crt_->set_new_display_type(CRTCyclesPerLine, Outputs::CRT::DisplayType::NTSC60);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Outputs::CRT::CRT *TMS9918::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
@ -66,8 +66,10 @@ class TMS9918: public Base {
|
||||
/*! Gets the current scan line; provided by the Master System only. */
|
||||
uint8_t get_current_line();
|
||||
|
||||
/*! Gets the current latched horizontal counter; provided by the Master System only. */
|
||||
uint8_t get_latched_horizontal_counter();
|
||||
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
|
||||
/*!
|
||||
|
@ -80,14 +80,18 @@ class ConcreteMachine:
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Sega::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
model_(target.model),
|
||||
region_(target.region),
|
||||
paging_scheme_(target.paging_scheme),
|
||||
z80_(*this),
|
||||
sn76489_(
|
||||
(target.model == Analyser::Static::Sega::Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
|
||||
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
|
||||
audio_queue_,
|
||||
sn76489_divider),
|
||||
speaker_(sn76489_) {
|
||||
speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider));
|
||||
set_clock_rate(3579545);
|
||||
// Pick the clock rate based on the region.
|
||||
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));
|
||||
set_clock_rate(clock_rate);
|
||||
|
||||
// Instantiate the joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
@ -104,10 +108,17 @@ class ConcreteMachine:
|
||||
cartridge_.resize(48*1024);
|
||||
memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
|
||||
}
|
||||
|
||||
if(paging_scheme_ == Target::PagingScheme::Codemasters) {
|
||||
// The Codemasters cartridges start with pages 0, 1 and 0 again initially visible.
|
||||
paging_registers_[0] = 0;
|
||||
paging_registers_[1] = 1;
|
||||
paging_registers_[2] = 0;
|
||||
}
|
||||
page_cartridge();
|
||||
|
||||
// Establish the BIOS (if relevant) and RAM.
|
||||
if(target.model == Analyser::Static::Sega::Target::Model::MasterSystem) {
|
||||
// Load the BIOS if relevant.
|
||||
if(has_bios()) {
|
||||
const auto roms = rom_fetcher("MasterSystem", {"bios.sms"});
|
||||
if(!roms[0]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
@ -115,8 +126,10 @@ class ConcreteMachine:
|
||||
|
||||
roms[0]->resize(8*1024);
|
||||
memcpy(&bios_, roms[0]->data(), roms[0]->size());
|
||||
map(read_pointers_, bios_, 8*1024, 0);
|
||||
}
|
||||
|
||||
// Map RAM.
|
||||
if(model_ == Target::Model::MasterSystem) {
|
||||
map(read_pointers_, ram_, 8*1024, 0xc000, 0x10000);
|
||||
map(write_pointers_, ram_, 8*1024, 0xc000, 0x10000);
|
||||
} else {
|
||||
@ -124,6 +137,7 @@ class ConcreteMachine:
|
||||
map(write_pointers_, ram_, 1024, 0xc000, 0x10000);
|
||||
}
|
||||
|
||||
// Apple a relatively low low-pass filter. More guidance needed here.
|
||||
speaker_.set_high_frequency_cutoff(8000);
|
||||
}
|
||||
|
||||
@ -132,7 +146,10 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
vdp_.reset(new TI::TMS::TMS9918(model_ == Analyser::Static::Sega::Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP));
|
||||
vdp_.reset(new TI::TMS::TMS9918(model_ == Target::Model::SG1000 ? TI::TMS::TMS9918A : TI::TMS::SMSVDP));
|
||||
vdp_->set_tv_standard(
|
||||
(region_ == Target::Region::Europe) ?
|
||||
TI::TMS::TMS9918::TVStandard::PAL : TI::TMS::TMS9918::TVStandard::NTSC);
|
||||
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
|
||||
}
|
||||
|
||||
@ -165,10 +182,20 @@ class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
|
||||
if(paging_registers_[address - 0xfffd] != *cycle.value) {
|
||||
paging_registers_[address - 0xfffd] = *cycle.value;
|
||||
page_cartridge();
|
||||
if(paging_scheme_ == Target::PagingScheme::Sega) {
|
||||
if(address >= 0xfffd && cartridge_.size() > 48*1024) {
|
||||
if(paging_registers_[address - 0xfffd] != *cycle.value) {
|
||||
paging_registers_[address - 0xfffd] = *cycle.value;
|
||||
page_cartridge();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// i.e. this is the Codemasters paging scheme.
|
||||
if(!(address&0x3fff) && address < 0xc000) {
|
||||
if(paging_registers_[address >> 14] != *cycle.value) {
|
||||
paging_registers_[address >> 14] = *cycle.value;
|
||||
page_cartridge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +249,7 @@ class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
switch(address & 0xc1) {
|
||||
case 0x00:
|
||||
if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem) {
|
||||
if(model_ == Target::Model::MasterSystem) {
|
||||
// TODO: Obey the RAM enable.
|
||||
memory_control_ = *cycle.value;
|
||||
page_cartridge();
|
||||
@ -253,7 +280,7 @@ class ConcreteMachine:
|
||||
time_until_interrupt_ = vdp_->get_time_until_interrupt();
|
||||
break;
|
||||
case 0xc0:
|
||||
LOG("TODO: [output] I/O port A/N; " << *cycle.value);
|
||||
LOG("TODO: [output] I/O port A/N; " << int(*cycle.value));
|
||||
break;
|
||||
case 0xc1:
|
||||
LOG("TODO: [output] I/O port B/misc");
|
||||
@ -312,7 +339,10 @@ class ConcreteMachine:
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
}
|
||||
|
||||
Analyser::Static::Sega::Target::Model model_;
|
||||
using Target = Analyser::Static::Sega::Target;
|
||||
Target::Model model_;
|
||||
Target::Region region_;
|
||||
Target::PagingScheme paging_scheme_;
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS::TMS9918> vdp_;
|
||||
|
||||
@ -345,8 +375,9 @@ class ConcreteMachine:
|
||||
uint8_t paging_registers_[3] = {0, 1, 2};
|
||||
uint8_t memory_control_ = 0;
|
||||
void page_cartridge() {
|
||||
// Either install the cartridge or don't.
|
||||
if(!(memory_control_ & 0x40)) {
|
||||
// Either install the cartridge or don't; Japanese machines can't see
|
||||
// anything but the cartridge.
|
||||
if(!(memory_control_ & 0x40) || region_ == Target::Region::Japan) {
|
||||
for(size_t c = 0; c < 3; ++c) {
|
||||
const size_t start_addr = (paging_registers_[c] * 0x4000) % cartridge_.size();
|
||||
map(
|
||||
@ -356,17 +387,22 @@ class ConcreteMachine:
|
||||
c * 0x4000);
|
||||
}
|
||||
|
||||
// The first 1kb doesn't page though.
|
||||
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
|
||||
// The first 1kb doesn't page though, if this is the Sega paging scheme.
|
||||
if(paging_scheme_ == Target::PagingScheme::Sega) {
|
||||
map(read_pointers_, cartridge_.data(), 0x400, 0x0000);
|
||||
}
|
||||
} else {
|
||||
map(read_pointers_, nullptr, 0xc000, 0x0000);
|
||||
}
|
||||
|
||||
// Throw the BIOS on top if this machine has one and it isn't disabled.
|
||||
if(model_ == Analyser::Static::Sega::Target::Model::MasterSystem && !(memory_control_ & 0x08)) {
|
||||
if(has_bios() && !(memory_control_ & 0x08)) {
|
||||
map(read_pointers_, bios_, 8*1024, 0);
|
||||
}
|
||||
}
|
||||
bool has_bios() {
|
||||
return model_ == Target::Model::MasterSystem && region_ != Target::Region::Japan;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user