2016-09-15 23:34:45 +00:00
|
|
|
//
|
|
|
|
// StaticAnalyser.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 15/09/2016.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
2016-09-15 23:34:45 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "StaticAnalyser.hpp"
|
|
|
|
|
2018-03-09 21:07:29 +00:00
|
|
|
#include "Target.hpp"
|
|
|
|
|
2017-12-31 23:49:35 +00:00
|
|
|
#include "../Disassembler/6502.hpp"
|
2017-02-20 22:44:36 +00:00
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
using namespace Analyser::Static::Atari2600;
|
|
|
|
using Target = Analyser::Static::Atari2600::Target;
|
2016-09-15 23:34:45 +00:00
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
2017-02-27 02:24:54 +00:00
|
|
|
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
|
|
|
uint16_t entry_address, break_address;
|
|
|
|
|
2017-10-22 01:50:53 +00:00
|
|
|
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
|
|
|
|
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
|
2017-02-27 02:24:54 +00:00
|
|
|
|
|
|
|
// a CommaVid start address needs to be outside of its RAM
|
|
|
|
if(entry_address < 0x1800 || break_address < 0x1800) return;
|
|
|
|
|
2017-11-11 20:28:40 +00:00
|
|
|
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
|
2017-02-27 12:56:59 +00:00
|
|
|
address &= 0x1fff;
|
2017-11-11 20:28:40 +00:00
|
|
|
return static_cast<std::size_t>(address - 0x1800);
|
2017-02-27 12:56:59 +00:00
|
|
|
};
|
2018-01-25 02:48:44 +00:00
|
|
|
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
|
|
|
|
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
2017-02-27 02:58:09 +00:00
|
|
|
|
2017-03-13 02:14:39 +00:00
|
|
|
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
|
|
|
|
// large amounts of memory
|
2017-02-27 13:39:53 +00:00
|
|
|
bool has_wide_area_store = false;
|
2018-01-25 02:48:44 +00:00
|
|
|
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
|
|
|
|
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
|
2017-03-13 02:14:39 +00:00
|
|
|
|
|
|
|
if(has_wide_area_store) break;
|
2017-02-27 13:39:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
|
|
|
|
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
|
|
|
|
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
|
|
|
|
// attempts to modify itself but it probably doesn't
|
2019-10-04 02:10:10 +00:00
|
|
|
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
2017-03-13 01:14:12 +00:00
|
|
|
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
|
2017-03-13 02:11:58 +00:00
|
|
|
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
|
|
|
|
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
|
2017-03-13 01:14:12 +00:00
|
|
|
if(
|
|
|
|
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
|
2017-03-13 02:11:58 +00:00
|
|
|
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
|
|
|
|
segment.data[0] == 0x78
|
2017-03-26 18:34:47 +00:00
|
|
|
) {
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::ActivisionStack;
|
2017-03-13 01:14:12 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-12 19:36:01 +00:00
|
|
|
// make an assumption that this is the Atari paging model
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::Atari8k;
|
2017-03-12 19:36:01 +00:00
|
|
|
|
2017-03-11 16:41:18 +00:00
|
|
|
std::set<uint16_t> internal_accesses;
|
2017-03-12 21:50:24 +00:00
|
|
|
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
|
|
|
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
|
|
|
|
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
|
2017-03-11 16:41:18 +00:00
|
|
|
|
2017-03-12 19:36:01 +00:00
|
|
|
int atari_access_count = 0;
|
|
|
|
int parker_access_count = 0;
|
|
|
|
int tigervision_access_count = 0;
|
2017-03-26 18:34:47 +00:00
|
|
|
for(uint16_t address : internal_accesses) {
|
2017-03-12 19:36:01 +00:00
|
|
|
uint16_t masked_address = address & 0x1fff;
|
|
|
|
atari_access_count += masked_address >= 0x1ff8 && masked_address < 0x1ffa;
|
|
|
|
parker_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ff8;
|
2017-03-12 19:41:48 +00:00
|
|
|
}
|
2017-03-26 18:34:47 +00:00
|
|
|
for(uint16_t address: disassembly.external_stores) {
|
2017-03-12 19:41:48 +00:00
|
|
|
uint16_t masked_address = address & 0x1fff;
|
|
|
|
tigervision_access_count += masked_address == 0x3f;
|
2017-03-11 16:41:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros;
|
|
|
|
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
|
2017-03-11 16:41:18 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
2017-03-12 22:54:49 +00:00
|
|
|
// make an assumption that this is the Atari paging model
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::Atari16k;
|
2017-03-12 22:54:49 +00:00
|
|
|
|
|
|
|
std::set<uint16_t> internal_accesses;
|
|
|
|
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
|
|
|
|
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
|
|
|
|
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
|
|
|
|
|
|
|
|
int atari_access_count = 0;
|
|
|
|
int mnetwork_access_count = 0;
|
2017-03-26 18:34:47 +00:00
|
|
|
for(uint16_t address : internal_accesses) {
|
2017-03-12 22:54:49 +00:00
|
|
|
uint16_t masked_address = address & 0x1fff;
|
|
|
|
atari_access_count += masked_address >= 0x1ff6 && masked_address < 0x1ffa;
|
|
|
|
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
|
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
|
2017-03-12 22:54:49 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
|
2017-03-14 00:43:12 +00:00
|
|
|
// make an assumption that this is a Tigervision if there is a write to 3F
|
2018-03-09 21:07:29 +00:00
|
|
|
target.paging_model =
|
2017-03-14 00:43:12 +00:00
|
|
|
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
|
2019-10-04 02:10:10 +00:00
|
|
|
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
|
2017-03-14 00:43:12 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
|
2017-03-26 18:34:47 +00:00
|
|
|
if(segment.data.size() == 2048) {
|
2017-02-27 02:24:54 +00:00
|
|
|
DeterminePagingFor2kCartridge(target, segment);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t entry_address, break_address;
|
|
|
|
|
2017-10-22 01:50:53 +00:00
|
|
|
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
|
|
|
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-11-11 20:28:40 +00:00
|
|
|
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
|
|
|
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
|
|
|
|
return static_cast<std::size_t>(address & 0xfff);
|
2017-02-27 12:56:59 +00:00
|
|
|
};
|
2017-03-05 16:58:52 +00:00
|
|
|
|
2017-03-12 21:50:24 +00:00
|
|
|
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
|
2018-01-25 02:48:44 +00:00
|
|
|
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
switch(segment.data.size()) {
|
2017-03-12 01:51:25 +00:00
|
|
|
case 8192:
|
2017-03-12 21:50:24 +00:00
|
|
|
DeterminePagingFor8kCartridge(target, segment, disassembly);
|
2017-03-12 01:51:25 +00:00
|
|
|
break;
|
2017-03-13 12:15:36 +00:00
|
|
|
case 10495:
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::Pitfall2;
|
2017-03-13 12:15:36 +00:00
|
|
|
break;
|
2017-03-12 18:03:17 +00:00
|
|
|
case 12288:
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::CBSRamPlus;
|
2017-03-12 18:03:17 +00:00
|
|
|
break;
|
2017-03-12 01:51:25 +00:00
|
|
|
case 16384:
|
2017-03-12 22:54:49 +00:00
|
|
|
DeterminePagingFor16kCartridge(target, segment, disassembly);
|
2017-03-12 01:51:25 +00:00
|
|
|
break;
|
|
|
|
case 32768:
|
2019-10-04 02:10:10 +00:00
|
|
|
target.paging_model = Target::PagingModel::Atari32k;
|
2017-03-12 01:51:25 +00:00
|
|
|
break;
|
2017-03-14 00:43:12 +00:00
|
|
|
case 65536:
|
|
|
|
DeterminePagingFor64kCartridge(target, segment, disassembly);
|
|
|
|
break;
|
2017-03-12 01:51:25 +00:00
|
|
|
default:
|
|
|
|
break;
|
2017-03-11 16:41:18 +00:00
|
|
|
}
|
|
|
|
|
2017-03-12 18:03:17 +00:00
|
|
|
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
|
2017-03-13 00:39:45 +00:00
|
|
|
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
|
|
|
|
// next 128 bytes. So check for that.
|
2019-10-04 02:10:10 +00:00
|
|
|
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
|
|
|
|
target.paging_model != Target::PagingModel::MNetwork) {
|
2017-03-12 18:03:17 +00:00
|
|
|
bool has_superchip = true;
|
2017-11-11 20:28:40 +00:00
|
|
|
for(std::size_t address = 0; address < 128; address++) {
|
2017-03-26 18:34:47 +00:00
|
|
|
if(segment.data[address] != segment.data[address+128]) {
|
2017-03-12 18:03:17 +00:00
|
|
|
has_superchip = false;
|
|
|
|
break;
|
|
|
|
}
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
2018-03-09 21:07:29 +00:00
|
|
|
target.uses_superchip = has_superchip;
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
2017-03-11 18:12:23 +00:00
|
|
|
|
|
|
|
// check for a Tigervision or Tigervision-esque scheme
|
2019-10-04 02:10:10 +00:00
|
|
|
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
|
2017-03-12 21:50:24 +00:00
|
|
|
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
|
2019-10-04 02:10:10 +00:00
|
|
|
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
|
2017-03-11 18:12:23 +00:00
|
|
|
}
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-04 02:10:10 +00:00
|
|
|
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
2018-03-07 21:21:17 +00:00
|
|
|
// TODO: sanity checking; is this image really for an Atari 2600?
|
2019-12-22 04:52:04 +00:00
|
|
|
auto target = std::make_unique<Target>();
|
2018-01-25 03:35:54 +00:00
|
|
|
target->machine = Machine::Atari2600;
|
2018-03-07 19:24:52 +00:00
|
|
|
target->confidence = 0.5;
|
2018-01-25 03:35:54 +00:00
|
|
|
target->media.cartridges = media.cartridges;
|
2019-10-04 02:10:10 +00:00
|
|
|
target->paging_model = Target::PagingModel::None;
|
2018-03-09 21:07:29 +00:00
|
|
|
target->uses_superchip = false;
|
2017-02-20 22:44:36 +00:00
|
|
|
|
|
|
|
// try to figure out the paging scheme
|
2017-08-17 14:48:29 +00:00
|
|
|
if(!media.cartridges.empty()) {
|
2017-12-02 21:01:30 +00:00
|
|
|
const auto &segments = media.cartridges.front()->get_segments();
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
if(segments.size() == 1) {
|
2017-02-20 22:44:36 +00:00
|
|
|
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
2018-01-25 03:35:54 +00:00
|
|
|
DeterminePagingForCartridge(*target, segment);
|
2017-02-20 22:44:36 +00:00
|
|
|
}
|
2017-02-26 22:11:57 +00:00
|
|
|
}
|
2018-04-14 16:12:12 +00:00
|
|
|
TargetList destinations;
|
|
|
|
destinations.push_back(std::move(target));
|
|
|
|
return destinations;
|
2016-09-15 23:34:45 +00:00
|
|
|
}
|