2016-09-15 23:34:45 +00:00
|
|
|
//
|
|
|
|
// StaticAnalyser.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 15/09/2016.
|
|
|
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#include "StaticAnalyser.hpp"
|
|
|
|
|
2017-02-20 22:44:36 +00:00
|
|
|
#include "../Disassembler/Disassembler6502.hpp"
|
|
|
|
|
2016-09-15 23:34:45 +00:00
|
|
|
using namespace StaticAnalyser::Atari;
|
|
|
|
|
2017-02-27 02:24:54 +00:00
|
|
|
static void DeterminePagingFor2kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
|
|
|
|
{
|
|
|
|
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
|
|
|
|
uint16_t entry_address, break_address;
|
|
|
|
|
2017-02-27 02:58:09 +00:00
|
|
|
entry_address = ((uint16_t)(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
|
|
|
|
break_address = ((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-02-27 12:56:59 +00:00
|
|
|
std::function<size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
|
|
|
|
address &= 0x1fff;
|
|
|
|
return (size_t)(address - 0x1800);
|
|
|
|
};
|
|
|
|
std::function<size_t(uint16_t address)> full_range_mapper = [](uint16_t address) {
|
|
|
|
if(!(address & 0x1000)) return (size_t)-1;
|
|
|
|
return (size_t)(address & 0x7ff);
|
|
|
|
};
|
|
|
|
|
|
|
|
StaticAnalyser::MOS6502::Disassembly high_location_disassembly =
|
|
|
|
StaticAnalyser::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
|
2017-02-27 13:39:53 +00:00
|
|
|
// StaticAnalyser::MOS6502::Disassembly full_range_disassembly =
|
|
|
|
// StaticAnalyser::MOS6502::Disassemble(segment.data, full_range_mapper, {entry_address, break_address});
|
2017-02-27 02:58:09 +00:00
|
|
|
|
|
|
|
// if there are no subroutines in the top 2kb of memory then this isn't a CommaVid
|
2017-02-27 13:06:57 +00:00
|
|
|
bool has_appropriate_subroutine_calls = false;
|
|
|
|
bool has_inappropriate_subroutine_calls = false;
|
2017-02-27 12:56:59 +00:00
|
|
|
for(uint16_t address : high_location_disassembly.internal_calls)
|
2017-02-27 02:58:09 +00:00
|
|
|
{
|
|
|
|
const uint16_t masked_address = address & 0x1fff;
|
2017-02-27 13:06:57 +00:00
|
|
|
has_appropriate_subroutine_calls |= (masked_address >= 0x1800);
|
|
|
|
has_inappropriate_subroutine_calls |= (masked_address < 0x1800);
|
2017-02-27 02:58:09 +00:00
|
|
|
}
|
2017-02-27 13:06:57 +00:00
|
|
|
|
|
|
|
// assumption here: a CommaVid will never branch into RAM. Possibly unsafe: if it won't then what's the RAM for?
|
|
|
|
if(!has_appropriate_subroutine_calls || has_inappropriate_subroutine_calls) return;
|
2017-02-27 02:58:09 +00:00
|
|
|
|
2017-02-27 12:56:59 +00:00
|
|
|
std::set<uint16_t> all_writes = high_location_disassembly.external_stores;
|
|
|
|
all_writes.insert(high_location_disassembly.external_modifies.begin(), high_location_disassembly.external_modifies.end());
|
2017-02-27 02:24:54 +00:00
|
|
|
|
|
|
|
// a CommaVid will use its RAM
|
|
|
|
if(all_writes.empty()) return;
|
|
|
|
|
2017-02-27 13:39:53 +00:00
|
|
|
bool has_appropriate_accesses = false;
|
2017-02-27 02:24:54 +00:00
|
|
|
for(uint16_t address : all_writes)
|
|
|
|
{
|
|
|
|
const uint16_t masked_address = address & 0x1fff;
|
|
|
|
if(masked_address >= 0x1400 && masked_address < 0x1800)
|
|
|
|
{
|
2017-02-27 13:39:53 +00:00
|
|
|
has_appropriate_accesses = true;
|
2017-02-27 02:24:54 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-27 13:39:53 +00:00
|
|
|
// in desperation, accept any kind of store that looks likely to be intended for large amounts of memory
|
|
|
|
bool has_wide_area_store = false;
|
|
|
|
if(!has_appropriate_accesses)
|
|
|
|
{
|
|
|
|
for(std::map<uint16_t, StaticAnalyser::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address)
|
|
|
|
{
|
|
|
|
if(entry.second.operation == StaticAnalyser::MOS6502::Instruction::STA)
|
|
|
|
{
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::Indirect;
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndexedIndirectX;
|
|
|
|
has_wide_area_store |= entry.second.addressing_mode == StaticAnalyser::MOS6502::Instruction::IndirectIndexedY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
if(has_appropriate_accesses || has_wide_area_store)
|
2017-02-27 13:06:57 +00:00
|
|
|
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CommaVid;
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
|
|
|
|
2017-03-11 16:41:18 +00:00
|
|
|
static void DeterminePagingFor8kCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const std::vector<StaticAnalyser::MOS6502::Disassembly> &disassemblies)
|
|
|
|
{
|
2017-03-12 19:36:01 +00:00
|
|
|
// make an assumption that this is the Atari paging model
|
|
|
|
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari8k;
|
|
|
|
|
2017-03-11 16:41:18 +00:00
|
|
|
std::set<uint16_t> internal_accesses;
|
2017-03-11 18:12:23 +00:00
|
|
|
std::set<uint16_t> external_stores;
|
2017-03-11 16:41:18 +00:00
|
|
|
for(const StaticAnalyser::MOS6502::Disassembly &disassembly : disassemblies)
|
|
|
|
{
|
|
|
|
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 18:12:23 +00:00
|
|
|
external_stores.insert(disassembly.external_stores.begin(), disassembly.external_stores.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-11 16:41:18 +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
|
|
|
}
|
|
|
|
for(uint16_t address: external_stores)
|
|
|
|
{
|
|
|
|
uint16_t masked_address = address & 0x1fff;
|
|
|
|
tigervision_access_count += masked_address == 0x3f;
|
2017-03-11 16:41:18 +00:00
|
|
|
}
|
|
|
|
|
2017-03-12 19:36:01 +00:00
|
|
|
if(parker_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::ParkerBros;
|
|
|
|
else if(tigervision_access_count > atari_access_count) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
|
2017-03-11 16:41:18 +00:00
|
|
|
}
|
|
|
|
|
2017-02-27 02:24:54 +00:00
|
|
|
static void DeterminePagingForCartridge(StaticAnalyser::Target &target, const Storage::Cartridge::Cartridge::Segment &segment)
|
|
|
|
{
|
|
|
|
if(segment.data.size() == 2048)
|
|
|
|
{
|
|
|
|
DeterminePagingFor2kCartridge(target, segment);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t entry_address, break_address;
|
|
|
|
|
2017-03-11 16:41:18 +00:00
|
|
|
entry_address = (uint16_t)(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
|
|
|
|
break_address = (uint16_t)(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-02-27 12:56:59 +00:00
|
|
|
std::function<size_t(uint16_t address)> address_mapper = [](uint16_t address) {
|
|
|
|
if(!(address & 0x1000)) return (size_t)-1;
|
|
|
|
return (size_t)(address & 0xfff);
|
|
|
|
};
|
2017-03-05 16:58:52 +00:00
|
|
|
|
|
|
|
std::vector<StaticAnalyser::MOS6502::Disassembly> disassemblies;
|
|
|
|
std::set<uint16_t> internal_stores;
|
2017-03-11 18:12:23 +00:00
|
|
|
std::set<uint16_t> external_stores;
|
2017-03-05 16:58:52 +00:00
|
|
|
for(std::vector<uint8_t>::difference_type base = 0; base < segment.data.size(); base += 4096)
|
|
|
|
{
|
2017-03-11 16:41:18 +00:00
|
|
|
std::vector<uint8_t> sub_data(segment.data.begin() + base, segment.data.begin() + base + 4096);
|
2017-03-05 16:58:52 +00:00
|
|
|
disassemblies.push_back(StaticAnalyser::MOS6502::Disassemble(sub_data, address_mapper, {entry_address, break_address}));
|
|
|
|
internal_stores.insert(disassemblies.back().internal_stores.begin(), disassemblies.back().internal_stores.end());
|
2017-03-11 18:12:23 +00:00
|
|
|
external_stores.insert(disassemblies.back().external_stores.begin(), disassemblies.back().external_stores.end());
|
2017-03-05 16:58:52 +00:00
|
|
|
}
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-03-12 01:51:25 +00:00
|
|
|
switch(segment.data.size())
|
2017-03-11 16:41:18 +00:00
|
|
|
{
|
2017-03-12 01:51:25 +00:00
|
|
|
case 8192:
|
|
|
|
DeterminePagingFor8kCartridge(target, segment, disassemblies);
|
|
|
|
break;
|
2017-03-12 18:03:17 +00:00
|
|
|
case 12288:
|
|
|
|
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::CBSRamPlus;
|
|
|
|
break;
|
2017-03-12 01:51:25 +00:00
|
|
|
case 16384:
|
|
|
|
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari16k;
|
|
|
|
break;
|
|
|
|
case 32768:
|
|
|
|
target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Atari32k;
|
|
|
|
break;
|
|
|
|
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-11 18:04:23 +00:00
|
|
|
// regions.
|
2017-03-12 19:36:01 +00:00
|
|
|
if(target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::CBSRamPlus && target.atari.paging_model != StaticAnalyser::Atari2600PagingModel::MNetwork)
|
2017-02-27 02:24:54 +00:00
|
|
|
{
|
2017-03-12 18:03:17 +00:00
|
|
|
bool has_superchip = true;
|
|
|
|
for(size_t address = 0; address < 256; address++)
|
2017-02-27 02:24:54 +00:00
|
|
|
{
|
2017-03-12 18:03:17 +00:00
|
|
|
if(segment.data[address] != segment.data[0])
|
|
|
|
{
|
|
|
|
has_superchip = false;
|
|
|
|
break;
|
|
|
|
}
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
2017-03-12 18:03:17 +00:00
|
|
|
target.atari.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
|
2017-03-12 01:51:25 +00:00
|
|
|
if(target.atari.paging_model == StaticAnalyser::Atari2600PagingModel::None && segment.data.size() > 4096)
|
2017-03-11 18:12:23 +00:00
|
|
|
{
|
|
|
|
bool looks_like_tigervision = external_stores.find(0x3f) != external_stores.end();
|
|
|
|
if(looks_like_tigervision) target.atari.paging_model = StaticAnalyser::Atari2600PagingModel::Tigervision;
|
|
|
|
}
|
2017-02-27 02:24:54 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 23:34:45 +00:00
|
|
|
void StaticAnalyser::Atari::AddTargets(
|
|
|
|
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
|
|
|
|
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
|
|
|
|
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
|
|
|
|
std::list<StaticAnalyser::Target> &destination)
|
|
|
|
{
|
|
|
|
// TODO: any sort of sanity checking at all; at the minute just trust the file type
|
|
|
|
// approximation already performed.
|
|
|
|
Target target;
|
|
|
|
target.machine = Target::Atari2600;
|
|
|
|
target.probability = 1.0;
|
|
|
|
target.disks = disks;
|
|
|
|
target.tapes = tapes;
|
|
|
|
target.cartridges = cartridges;
|
2017-02-20 22:44:36 +00:00
|
|
|
target.atari.paging_model = Atari2600PagingModel::None;
|
2017-02-26 22:11:57 +00:00
|
|
|
target.atari.uses_superchip = false;
|
2017-02-20 22:44:36 +00:00
|
|
|
|
|
|
|
// try to figure out the paging scheme
|
2017-02-26 22:11:57 +00:00
|
|
|
if(!cartridges.empty())
|
2017-02-20 22:44:36 +00:00
|
|
|
{
|
|
|
|
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridges.front()->get_segments();
|
2017-02-27 02:24:54 +00:00
|
|
|
|
2017-02-20 22:44:36 +00:00
|
|
|
if(segments.size() == 1)
|
|
|
|
{
|
|
|
|
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
|
2017-02-27 02:24:54 +00:00
|
|
|
DeterminePagingForCartridge(target, segment);
|
2017-02-20 22:44:36 +00:00
|
|
|
}
|
2017-02-26 22:11:57 +00:00
|
|
|
}
|
2017-02-20 22:44:36 +00:00
|
|
|
|
2016-09-15 23:34:45 +00:00
|
|
|
destination.push_back(target);
|
|
|
|
}
|