mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-01 07:33:17 +00:00
Merge pull request #1374 from TomHarte/SWIAnalyser
Add Archimedes autostart behaviour.
This commit is contained in:
commit
537b91fa3f
@ -145,8 +145,9 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
}
|
||||
name[c] = '\0';
|
||||
|
||||
// Skip if the name is empty.
|
||||
if(name[0] == '\0') continue;
|
||||
// An empty name implies the directory has ended; files are always listed in case-insensitive
|
||||
// sorted order, with that list being terminated by a '\0'.
|
||||
if(name[0] == '\0') break;
|
||||
|
||||
// Populate a file then.
|
||||
File new_file;
|
||||
@ -200,5 +201,20 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
// Include the directory title.
|
||||
const char *title, *name;
|
||||
if(catalogue->has_large_sectors) {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x7dd]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x7f0]);
|
||||
} else {
|
||||
title = reinterpret_cast<const char *>(&root_directory[0x4d9]);
|
||||
name = reinterpret_cast<const char *>(&root_directory[0x4cc]);
|
||||
}
|
||||
|
||||
catalogue->name = std::string(title, strnlen(title, 19));
|
||||
if(catalogue->name.empty() || catalogue->name == "$") {
|
||||
catalogue->name = std::string(name, strnlen(name, 10));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
@ -12,7 +12,10 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Numeric/StringSimilarity.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
@ -59,9 +62,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
return acorn_cartridges;
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target8bit = std::make_unique<Target>();
|
||||
auto targetArchimedes = std::make_unique<Analyser::Static::Target>(Machine::Archimedes);
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
|
||||
auto target8bit = std::make_unique<ElectronTarget>();
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
@ -102,14 +105,14 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
// TODO: below requires an [8-bit compatible] 'Hugo' ADFS catalogue, disallowing
|
||||
// [Archimedes-exclusive] 'Nick' catalogues.
|
||||
//
|
||||
// Would be better to form the appropriate target in the latter case.
|
||||
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
|
||||
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
|
||||
|
||||
// Get any sort of catalogue that can be found.
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
|
||||
// 8-bit options: DFS and Hugo-style ADFS.
|
||||
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
@ -144,7 +147,42 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
}
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
targetArchimedes->media.disks = media.disks;
|
||||
|
||||
// Also look for the best possible startup program name, if it can be discerned.
|
||||
std::multimap<double, std::string, std::greater<double>> options;
|
||||
for(const auto &file: adfs_catalogue->files) {
|
||||
// Skip non-Pling files.
|
||||
if(file.name[0] != '!') continue;
|
||||
|
||||
// Take whatever else comes with a preference for things that don't
|
||||
// have 'boot' or 'read' in them (the latter of which will tend to be
|
||||
// read_me or read_this or similar).
|
||||
constexpr char read[] = "read";
|
||||
constexpr char boot[] = "boot";
|
||||
const auto has = [&](const char *begin, const char *end) {
|
||||
return std::search(
|
||||
file.name.begin(), file.name.end(),
|
||||
begin, end - 1, // i.e. don't compare the trailing NULL.
|
||||
[](char lhs, char rhs) {
|
||||
return std::tolower(lhs) == rhs;
|
||||
}
|
||||
) != file.name.end();
|
||||
};
|
||||
const auto has_read = has(std::begin(read), std::end(read));
|
||||
const auto has_boot = has(std::begin(boot), std::end(boot));
|
||||
|
||||
const auto probability =
|
||||
Numeric::similarity(file.name, adfs_catalogue->name) +
|
||||
Numeric::similarity(file.name, file_name) -
|
||||
((has_read || has_boot) ? 0.2 : 0.0);
|
||||
options.emplace(probability, file.name);
|
||||
}
|
||||
|
||||
if(!options.empty()) {
|
||||
targetArchimedes->main_program = options.begin()->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ElectronTarget> {
|
||||
bool has_acorn_adfs = false;
|
||||
bool has_pres_adfs = false;
|
||||
bool has_dfs = false;
|
||||
@ -23,7 +23,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
ElectronTarget() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
@ -34,4 +34,10 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
|
||||
std::string main_program;
|
||||
|
||||
ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
#include "../../../Components/I2C/I2C.hpp"
|
||||
|
||||
#include "../../../Analyser/Static/Acorn/Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <set>
|
||||
@ -36,287 +38,6 @@
|
||||
|
||||
namespace Archimedes {
|
||||
|
||||
#ifndef NDEBUG
|
||||
namespace {
|
||||
Log::Logger<Log::Source::Archimedes> logger;
|
||||
}
|
||||
|
||||
template <InstructionSet::ARM::Model model, typename Executor>
|
||||
struct HackyDebugger {
|
||||
void notify(uint32_t address, uint32_t instruction, Executor &executor) {
|
||||
pc_history[pc_history_ptr] = address;
|
||||
pc_history_ptr = (pc_history_ptr + 1) % pc_history.size();
|
||||
|
||||
// if(
|
||||
// executor_.pc() > 0x038021d0 &&
|
||||
// last_r1 != executor_.registers()[1]
|
||||
// ||
|
||||
// (
|
||||
// last_link != executor_.registers()[14] ||
|
||||
// last_r0 != executor_.registers()[0] ||
|
||||
// last_r10 != executor_.registers()[10] ||
|
||||
// last_r1 != executor_.registers()[1]
|
||||
// )
|
||||
// ) {
|
||||
// logger.info().append("%08x modified R14 to %08x; R0 to %08x; R10 to %08x; R1 to %08x",
|
||||
// last_pc,
|
||||
// executor_.registers()[14],
|
||||
// executor_.registers()[0],
|
||||
// executor_.registers()[10],
|
||||
// executor_.registers()[1]
|
||||
// );
|
||||
// logger.info().append("%08x modified R1 to %08x",
|
||||
// last_pc,
|
||||
// executor_.registers()[1]
|
||||
// );
|
||||
// last_link = executor_.registers()[14];
|
||||
// last_r0 = executor_.registers()[0];
|
||||
// last_r10 = executor_.registers()[10];
|
||||
// last_r1 = executor_.registers()[1];
|
||||
// }
|
||||
|
||||
// if(instruction == 0xe8fd7fff) {
|
||||
// printf("At %08x [%d]; after last PC %08x and %zu ago was %08x\n",
|
||||
// address,
|
||||
// instr_count,
|
||||
// pc_history[(pc_history_ptr - 2 + pc_history.size()) % pc_history.size()],
|
||||
// pc_history.size(),
|
||||
// pc_history[pc_history_ptr]);
|
||||
// }
|
||||
// last_r9 = executor_.registers()[9];
|
||||
|
||||
// log |= address == 0x038031c4;
|
||||
// log |= instr_count == 53552731 - 30;
|
||||
// log &= executor_.pc() != 0x000000a0;
|
||||
|
||||
// log = (executor_.pc() == 0x038162afc) || (executor_.pc() == 0x03824b00);
|
||||
// log |= instruction & ;
|
||||
|
||||
// The following has the effect of logging all taken SWIs and their return codes.
|
||||
/* if(
|
||||
(instruction & 0x0f00'0000) == 0x0f00'0000 &&
|
||||
executor.registers().test(InstructionSet::ARM::Condition(instruction >> 28))
|
||||
) {
|
||||
if(instruction & 0x2'0000) {
|
||||
swis.emplace_back();
|
||||
swis.back().count = swi_count++;
|
||||
swis.back().opcode = instruction;
|
||||
swis.back().address = executor.pc();
|
||||
swis.back().return_address = executor.registers().pc(4);
|
||||
for(int c = 0; c < 10; c++) swis.back().regs[c] = executor.registers()[uint32_t(c)];
|
||||
|
||||
// Possibly capture more detail.
|
||||
//
|
||||
// Cf. http://productsdb.riscos.com/support/developers/prm_index/numswilist.html
|
||||
uint32_t pointer = 0;
|
||||
switch(instruction & 0xfd'ffff) {
|
||||
case 0x41501:
|
||||
swis.back().swi_name = "MessageTrans_OpenFile";
|
||||
|
||||
// R0: pointer to file descriptor; R1: pointer to filename; R2: pointer to hold file data.
|
||||
// (R0 and R1 are in the RMA if R2 = 0)
|
||||
pointer = executor.registers()[1];
|
||||
break;
|
||||
case 0x41502:
|
||||
swis.back().swi_name = "MessageTrans_Lookup";
|
||||
break;
|
||||
case 0x41506:
|
||||
swis.back().swi_name = "MessageTrans_ErrorLookup";
|
||||
break;
|
||||
|
||||
case 0x4028a:
|
||||
swis.back().swi_name = "Podule_EnumerateChunksWithInfo";
|
||||
break;
|
||||
|
||||
case 0x4000a:
|
||||
swis.back().swi_name = "Econet_ReadLocalStationAndNet";
|
||||
break;
|
||||
case 0x4000e:
|
||||
swis.back().swi_name = "Econet_SetProtection";
|
||||
break;
|
||||
case 0x40015:
|
||||
swis.back().swi_name = "Econet_ClaimPort";
|
||||
break;
|
||||
|
||||
case 0x40541:
|
||||
swis.back().swi_name = "FileCore_Create";
|
||||
break;
|
||||
|
||||
case 0x80156:
|
||||
case 0x8015b:
|
||||
swis.back().swi_name = "PDriver_MiscOpForDriver";
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
swis.back().swi_name = "OS_CLI";
|
||||
pointer = executor.registers()[0];
|
||||
break;
|
||||
case 0x0d:
|
||||
swis.back().swi_name = "OS_Find";
|
||||
if(executor.registers()[0] >= 0x40) {
|
||||
pointer = executor.registers()[1];
|
||||
}
|
||||
break;
|
||||
case 0x1d:
|
||||
swis.back().swi_name = "OS_Heap";
|
||||
break;
|
||||
case 0x1e:
|
||||
swis.back().swi_name = "OS_Module";
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
swis.back().swi_name = "OS_Release";
|
||||
break;
|
||||
case 0x21:
|
||||
swis.back().swi_name = "OS_ReadUnsigned";
|
||||
break;
|
||||
case 0x23:
|
||||
swis.back().swi_name = "OS_ReadVarVal";
|
||||
|
||||
// R0: pointer to variable name.
|
||||
pointer = executor.registers()[0];
|
||||
break;
|
||||
case 0x24:
|
||||
swis.back().swi_name = "OS_SetVarVal";
|
||||
|
||||
// R0: pointer to variable name.
|
||||
pointer = executor.registers()[0];
|
||||
break;
|
||||
case 0x26:
|
||||
swis.back().swi_name = "OS_GSRead";
|
||||
break;
|
||||
case 0x27:
|
||||
swis.back().swi_name = "OS_GSTrans";
|
||||
pointer = executor.registers()[0];
|
||||
break;
|
||||
case 0x29:
|
||||
swis.back().swi_name = "OS_FSControl";
|
||||
break;
|
||||
case 0x2a:
|
||||
swis.back().swi_name = "OS_ChangeDynamicArea";
|
||||
break;
|
||||
|
||||
case 0x4c:
|
||||
swis.back().swi_name = "OS_ReleaseDeviceVector";
|
||||
break;
|
||||
|
||||
case 0x43057:
|
||||
swis.back().swi_name = "Territory_LowerCaseTable";
|
||||
break;
|
||||
case 0x43058:
|
||||
swis.back().swi_name = "Territory_UpperCaseTable";
|
||||
break;
|
||||
|
||||
case 0x42fc0:
|
||||
swis.back().swi_name = "Portable_Speed";
|
||||
break;
|
||||
case 0x42fc1:
|
||||
swis.back().swi_name = "Portable_Control";
|
||||
break;
|
||||
}
|
||||
|
||||
if(pointer) {
|
||||
while(true) {
|
||||
uint8_t next;
|
||||
executor.bus.template read<uint8_t>(pointer, next, InstructionSet::ARM::Mode::Supervisor, false);
|
||||
++pointer;
|
||||
|
||||
if(next < 32) break;
|
||||
swis.back().value_name.push_back(static_cast<char>(next));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(executor.registers().pc_status(0) & InstructionSet::ARM::ConditionCode::Overflow) {
|
||||
logger.error().append("SWI called with V set");
|
||||
}
|
||||
}
|
||||
if(!swis.empty() && executor.pc() == swis.back().return_address) {
|
||||
// Overflow set => SWI failure.
|
||||
auto &back = swis.back();
|
||||
if(executor.registers().pc_status(0) & InstructionSet::ARM::ConditionCode::Overflow) {
|
||||
auto info = logger.info();
|
||||
|
||||
info.append("[%d] Failed swi ", back.count);
|
||||
if(back.swi_name.empty()) {
|
||||
info.append("&%x", back.opcode & 0xfd'ffff);
|
||||
} else {
|
||||
info.append("%s", back.swi_name.c_str());
|
||||
}
|
||||
|
||||
if(!back.value_name.empty()) {
|
||||
info.append(" %s", back.value_name.c_str());
|
||||
}
|
||||
|
||||
info.append(" @ %08x ", back.address);
|
||||
for(uint32_t c = 0; c < 10; c++) {
|
||||
info.append("r%d:%08x ", c, back.regs[c]);
|
||||
}
|
||||
}
|
||||
|
||||
swis.pop_back();
|
||||
}*/
|
||||
|
||||
if(log) {
|
||||
InstructionSet::ARM::Disassembler<model> disassembler;
|
||||
InstructionSet::ARM::dispatch<model>(instruction, disassembler);
|
||||
|
||||
auto info = logger.info();
|
||||
info.append("[%d] %08x: %08x\t\t%s\t prior:[",
|
||||
instr_count,
|
||||
executor.pc(),
|
||||
instruction,
|
||||
disassembler.last().to_string(executor.pc()).c_str());
|
||||
for(uint32_t c = 0; c < 15; c++) {
|
||||
info.append("r%d:%08x ", c, executor.registers()[c]);
|
||||
}
|
||||
info.append("]");
|
||||
}
|
||||
// opcodes.insert(instruction);
|
||||
// if(accumulate) {
|
||||
// int c = 0;
|
||||
// for(auto instr : opcodes) {
|
||||
// printf("0x%08x, ", instr);
|
||||
// ++c;
|
||||
// if(!(c&15)) printf("\n");
|
||||
// }
|
||||
// accumulate = false;
|
||||
// }
|
||||
|
||||
++instr_count;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint32_t, 75> pc_history;
|
||||
std::size_t pc_history_ptr = 0;
|
||||
uint32_t instr_count = 0;
|
||||
uint32_t swi_count = 0;
|
||||
|
||||
struct SWICall {
|
||||
uint32_t count;
|
||||
uint32_t opcode;
|
||||
uint32_t address;
|
||||
uint32_t regs[10];
|
||||
uint32_t return_address;
|
||||
std::string value_name;
|
||||
std::string swi_name;
|
||||
};
|
||||
std::vector<SWICall> swis;
|
||||
uint32_t last_pc = 0;
|
||||
// uint32_t last_r9 = 0;
|
||||
bool log = false;
|
||||
bool accumulate = true;
|
||||
|
||||
std::set<uint32_t> opcodes;
|
||||
};
|
||||
#else
|
||||
template <InstructionSet::ARM::Model model, typename Executor>
|
||||
struct HackyDebugger {
|
||||
void notify(uint32_t, uint32_t, Executor &) {}
|
||||
};
|
||||
#endif
|
||||
|
||||
class ConcreteMachine:
|
||||
public Machine,
|
||||
public MachineTypes::AudioProducer,
|
||||
@ -328,7 +49,9 @@ class ConcreteMachine:
|
||||
public Activity::Source
|
||||
{
|
||||
private:
|
||||
// TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'.
|
||||
Log::Logger<Log::Source::Archimedes> logger;
|
||||
|
||||
// This fictitious clock rate just means '24 MIPS, please'; it's divided elsewhere.
|
||||
static constexpr int ClockRate = 24'000'000;
|
||||
|
||||
// Runs for 24 cycles, distributing calls to the various ticking subsystems
|
||||
@ -386,7 +109,7 @@ class ConcreteMachine:
|
||||
|
||||
public:
|
||||
ConcreteMachine(
|
||||
const Analyser::Static::Target &target,
|
||||
const Analyser::Static::Acorn::ArchimedesTarget &target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) : executor_(*this, *this, *this) {
|
||||
set_clock_rate(ClockRate);
|
||||
@ -401,6 +124,13 @@ class ConcreteMachine:
|
||||
executor_.bus.set_rom(roms.find(risc_os)->second);
|
||||
insert_media(target.media);
|
||||
|
||||
if(!target.media.disks.empty()) {
|
||||
autoload_phase_ = AutoloadPhase::WaitingForStartup;
|
||||
target_program_ = target.main_program;
|
||||
|
||||
printf("Will seek %s?\n", target_program_.c_str());
|
||||
}
|
||||
|
||||
fill_pipeline(0);
|
||||
}
|
||||
|
||||
@ -428,13 +158,197 @@ class ConcreteMachine:
|
||||
fill_pipeline(executor_.pc());
|
||||
}
|
||||
|
||||
bool should_swi(uint32_t) {
|
||||
bool should_swi(uint32_t comment) {
|
||||
using Exception = InstructionSet::ARM::Registers::Exception;
|
||||
using SWISubversion = Pipeline::SWISubversion;
|
||||
|
||||
switch(pipeline_.swi_subversion()) {
|
||||
case Pipeline::SWISubversion::None:
|
||||
return true;
|
||||
case Pipeline::SWISubversion::None: {
|
||||
// TODO: 400C1 to intercept create window 400C1 and positioning; then
|
||||
// plot icon 400e2 to listen for icons in window. That'll give a click area.
|
||||
// Probably also 400c2 which seems to be used to add icons to the icon bar.
|
||||
//
|
||||
// 400D4 for menus?
|
||||
|
||||
const auto get_string = [&](uint32_t address, bool indirect) -> std::string {
|
||||
std::string desc;
|
||||
if(indirect) {
|
||||
executor_.bus.read(address, address, false);
|
||||
}
|
||||
while(true) {
|
||||
uint8_t next = 0;
|
||||
executor_.bus.read(address, next, false);
|
||||
if(next < 0x20) break;
|
||||
desc.push_back(static_cast<char>(next) & 0x7f);
|
||||
++address;
|
||||
}
|
||||
return desc;
|
||||
};
|
||||
|
||||
const uint32_t swi_code = comment & static_cast<uint32_t>(~(1 << 17));
|
||||
switch(swi_code) {
|
||||
// To consider: catching VDU 22, though that means parsing the output stream
|
||||
// via OS_WriteC, SWI &00, sufficiently to be able to spot VDUs.
|
||||
|
||||
case 0x400e3: // Wimp_SetMode
|
||||
case 0x65: // OS_ScreenMode
|
||||
case 0x3f: // OS_CheckModeValid
|
||||
if(autoload_phase_ == AutoloadPhase::OpeningProgram) {
|
||||
autoload_phase_ = AutoloadPhase::Ended;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x400d4: {
|
||||
if(autoload_phase_ == AutoloadPhase::TestingMenu) {
|
||||
autoload_phase_ = AutoloadPhase::Ended;
|
||||
|
||||
uint32_t address = executor_.registers()[1] + 28;
|
||||
bool should_left_click = true;
|
||||
|
||||
while(true) {
|
||||
uint32_t icon_flags;
|
||||
uint32_t item_flags;
|
||||
executor_.bus.read(address, item_flags, false);
|
||||
executor_.bus.read(address + 8, icon_flags, false);
|
||||
auto desc = get_string(address + 12, icon_flags & (1 << 8));
|
||||
|
||||
should_left_click &=
|
||||
(desc == "Info") ||
|
||||
(desc == "Quit");
|
||||
|
||||
address += 24;
|
||||
if(item_flags & (1 << 7)) break;
|
||||
}
|
||||
|
||||
if(should_left_click) {
|
||||
// Exit the application menu, then click once further to launch.
|
||||
CursorActionBuilder(cursor_actions_)
|
||||
.move_to(IconBarProgramX - 128, IconBarY - 32)
|
||||
.click(0)
|
||||
.move_to(IconBarProgramX, IconBarY)
|
||||
.click(0);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
// Wimp_OpenWindow.
|
||||
case 0x400c5: {
|
||||
const uint32_t address = executor_.registers()[1];
|
||||
uint32_t x1, y1, x2, y2;
|
||||
executor_.bus.read(address + 4, x1, false);
|
||||
executor_.bus.read(address + 8, y1, false);
|
||||
executor_.bus.read(address + 12, x2, false);
|
||||
executor_.bus.read(address + 16, y2, false);
|
||||
|
||||
switch(autoload_phase_) {
|
||||
|
||||
default: break;
|
||||
|
||||
case AutoloadPhase::WaitingForDiskContents: {
|
||||
autoload_phase_ = AutoloadPhase::WaitingForTargetIcon;
|
||||
|
||||
// Crib top left of window content.
|
||||
target_window_[0] = static_cast<int32_t>(x1);
|
||||
target_window_[1] = static_cast<int32_t>(y2);
|
||||
} break;
|
||||
|
||||
case AutoloadPhase::WaitingForStartup:
|
||||
printf("%d %d %d %d\n", x1, y1, x2, y2);
|
||||
if(static_cast<int32_t>(y1) == -268435472) { // VERY TEMPORARY. TODO: find better trigger.
|
||||
// Creation of any icon is used to spot that RISC OS has started up.
|
||||
//
|
||||
// Wait a further second, mouse down to (32, 240), left click.
|
||||
// That'll trigger disk access. Then move up to the top left,
|
||||
// in anticipation of the appearance of a window.
|
||||
CursorActionBuilder(cursor_actions_)
|
||||
// .wait(5 * 24'000'000)
|
||||
.move_to(IconBarDriveX, IconBarY)
|
||||
.click(0)
|
||||
.set_phase(
|
||||
target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents
|
||||
)
|
||||
.move_to(IconBarDriveX, 36); // Just a guess of 'close' to where the program to launch
|
||||
// will probably be, to have the cursor already nearby.
|
||||
|
||||
autoload_phase_ = AutoloadPhase::OpeningDisk;
|
||||
}
|
||||
break;
|
||||
// printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Wimp_CreateIcon, which also adds to the icon bar.
|
||||
case 0x400c2:
|
||||
switch(autoload_phase_) {
|
||||
// case AutoloadPhase::WaitingForStartup:
|
||||
// // Creation of any icon is used to spot that RISC OS has started up.
|
||||
// //
|
||||
// // Wait a further second, mouse down to (32, 240), left click.
|
||||
// // That'll trigger disk access. Then move up to the top left,
|
||||
// // in anticipation of the appearance of a window.
|
||||
// CursorActionBuilder(cursor_actions_)
|
||||
// .wait(24'000'000)
|
||||
// .move_to(IconBarDriveX, IconBarY)
|
||||
// .click(0)
|
||||
// .set_phase(
|
||||
// target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents
|
||||
// )
|
||||
// .move_to(IconBarDriveX, 36); // Just a guess of 'close' to where the program to launch
|
||||
// // will probably be, to have the cursor already nearby.
|
||||
//
|
||||
// autoload_phase_ = AutoloadPhase::OpeningDisk;
|
||||
// break;
|
||||
|
||||
case AutoloadPhase::OpeningProgram: {
|
||||
const uint32_t address = executor_.registers()[1];
|
||||
uint32_t handle;
|
||||
executor_.bus.read(address, handle, false);
|
||||
|
||||
// Test whether the program has added an icon on the right.
|
||||
if(static_cast<int32_t>(handle) == -1) {
|
||||
CursorActionBuilder(cursor_actions_)
|
||||
.move_to(IconBarProgramX, IconBarY)
|
||||
.click(1);
|
||||
autoload_phase_ = AutoloadPhase::TestingMenu;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Wimp_PlotIcon.
|
||||
case 0x400e2: {
|
||||
if(autoload_phase_ == AutoloadPhase::WaitingForTargetIcon) {
|
||||
const uint32_t address = executor_.registers()[1];
|
||||
uint32_t flags;
|
||||
executor_.bus.read(address + 16, flags, false);
|
||||
|
||||
std::string desc;
|
||||
if(flags & 1) {
|
||||
desc = get_string(address + 20, flags & (1 << 8));
|
||||
}
|
||||
|
||||
if(desc == target_program_) {
|
||||
uint32_t x1, y1, x2, y2;
|
||||
executor_.bus.read(address + 0, x1, false);
|
||||
executor_.bus.read(address + 4, y1, false);
|
||||
executor_.bus.read(address + 8, x2, false);
|
||||
executor_.bus.read(address + 12, y2, false);
|
||||
|
||||
autoload_phase_ = AutoloadPhase::OpeningProgram;
|
||||
|
||||
// Some default icon sizing assumptions are baked in here.
|
||||
const auto x_target = target_window_[0] + (static_cast<int32_t>(x1) + static_cast<int32_t>(x2)) / 2;
|
||||
const auto y_target = target_window_[1] + static_cast<int32_t>(y1) + 24;
|
||||
CursorActionBuilder(cursor_actions_)
|
||||
.move_to(x_target >> 1, 256 - (y_target >> 2))
|
||||
.double_click(0);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
} return true;
|
||||
|
||||
case SWISubversion::DataAbort:
|
||||
// executor_.set_pc(executor_.pc() - 4);
|
||||
@ -483,8 +397,89 @@ class ConcreteMachine:
|
||||
const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10;
|
||||
#endif
|
||||
|
||||
if(use_original_speed) run_for<true>(cycles);
|
||||
else run_for<false>(cycles);
|
||||
const auto run = [&](Cycles cycles) {
|
||||
if(use_original_speed) run_for<true>(cycles);
|
||||
else run_for<false>(cycles);
|
||||
};
|
||||
|
||||
//
|
||||
// Short-circuit: no cursor actions means **just run**.
|
||||
//
|
||||
if(cursor_actions_.empty()) {
|
||||
run(cycles);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Mouse scripting; tick at a minimum of frame length.
|
||||
//
|
||||
static constexpr int TickFrequency = 24'000'000 / 50;
|
||||
cursor_action_subcycle_ += cycles;
|
||||
auto segments = cursor_action_subcycle_.divide(Cycles(TickFrequency)).as<int>();
|
||||
while(segments--) {
|
||||
Cycles next = Cycles(TickFrequency);
|
||||
if(next > cycles) next = cycles;
|
||||
cycles -= next;
|
||||
|
||||
if(!cursor_actions_.empty()) {
|
||||
const auto move_to_next = [&]() {
|
||||
cursor_action_waited_ = 0;
|
||||
cursor_actions_.erase(cursor_actions_.begin());
|
||||
};
|
||||
|
||||
const auto &action = cursor_actions_.front();
|
||||
switch(action.type) {
|
||||
case CursorAction::Type::MoveTo: {
|
||||
// A measure of where within the tip lies within
|
||||
// the default RISC OS cursor.
|
||||
constexpr int ActionPointOffset = 20;
|
||||
constexpr int MaxStep = 24;
|
||||
|
||||
const auto position = executor_.bus.video().cursor_location();
|
||||
if(!position) break;
|
||||
const auto [x, y] = *position;
|
||||
|
||||
auto x_diff = action.value.move_to.x - (x + ActionPointOffset);
|
||||
auto y_diff = action.value.move_to.y - y;
|
||||
|
||||
if(abs(x_diff) < 2 && abs(y_diff) < 2) {
|
||||
move_to_next();
|
||||
break;
|
||||
}
|
||||
|
||||
if(abs(y_diff) > MaxStep || abs(x_diff) > MaxStep) {
|
||||
if(abs(y_diff) > abs(x_diff)) {
|
||||
x_diff = (x_diff * MaxStep + (abs(y_diff) >> 1)) / abs(y_diff);
|
||||
y_diff = std::clamp(y_diff, -MaxStep, MaxStep);
|
||||
} else {
|
||||
y_diff = (y_diff * MaxStep + (abs(x_diff) >> 1)) / abs(x_diff);
|
||||
x_diff = std::clamp(x_diff, -MaxStep, MaxStep);
|
||||
}
|
||||
}
|
||||
get_mouse().move(x_diff, y_diff);
|
||||
} break;
|
||||
case CursorAction::Type::Wait:
|
||||
cursor_action_waited_ += next.as<int>();
|
||||
if(cursor_action_waited_ >= action.value.wait.duration) {
|
||||
move_to_next();
|
||||
}
|
||||
break;
|
||||
case CursorAction::Type::Button:
|
||||
get_mouse().set_button_pressed(action.value.button.button, action.value.button.down);
|
||||
move_to_next();
|
||||
break;
|
||||
case CursorAction::Type::SetPhase:
|
||||
autoload_phase_ = action.value.set_phase.phase;
|
||||
move_to_next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Execution proper.
|
||||
//
|
||||
run(next);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool original_speed>
|
||||
@ -498,13 +493,11 @@ class ConcreteMachine:
|
||||
case 4: macro_tick<4, original_speed>(); break;
|
||||
case 6: macro_tick<6, original_speed>(); break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void tick_cpu() {
|
||||
const uint32_t instruction = advance_pipeline(executor_.pc() + 8);
|
||||
debugger_.notify(executor_.pc(), instruction, executor_);
|
||||
InstructionSet::ARM::execute(instruction, executor_);
|
||||
}
|
||||
|
||||
@ -541,9 +534,7 @@ class ConcreteMachine:
|
||||
Archimedes::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) override {
|
||||
const int row = Archimedes::KeyboardMapper::row(key);
|
||||
const int column = Archimedes::KeyboardMapper::column(key);
|
||||
executor_.bus.keyboard().set_key_state(row, column, is_pressed);
|
||||
executor_.bus.keyboard().set_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
// MARK: - MouseMachine.
|
||||
@ -623,14 +614,139 @@ class ConcreteMachine:
|
||||
SWISubversion latched_subversion_;
|
||||
} pipeline_;
|
||||
|
||||
// MARK: - Yucky, temporary junk.
|
||||
HackyDebugger<arm_model, Executor> debugger_;
|
||||
// MARK: - Autoload, including cursor scripting.
|
||||
|
||||
enum class AutoloadPhase {
|
||||
WaitingForStartup,
|
||||
OpeningDisk,
|
||||
WaitingForDiskContents,
|
||||
WaitingForTargetIcon,
|
||||
OpeningProgram,
|
||||
TestingMenu,
|
||||
Ended,
|
||||
};
|
||||
AutoloadPhase autoload_phase_ = AutoloadPhase::Ended;
|
||||
std::string target_program_;
|
||||
|
||||
struct CursorAction {
|
||||
enum class Type {
|
||||
MoveTo,
|
||||
Button,
|
||||
Wait,
|
||||
SetPhase,
|
||||
} type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
int x, y;
|
||||
} move_to;
|
||||
struct {
|
||||
int duration;
|
||||
} wait;
|
||||
struct {
|
||||
int button;
|
||||
bool down;
|
||||
} button;
|
||||
struct {
|
||||
AutoloadPhase phase;
|
||||
} set_phase;
|
||||
} value;
|
||||
|
||||
static CursorAction move_to(int x, int y) {
|
||||
CursorAction action;
|
||||
action.type = Type::MoveTo;
|
||||
action.value.move_to.x = x;
|
||||
action.value.move_to.y = y;
|
||||
return action;
|
||||
}
|
||||
static CursorAction wait(int duration) {
|
||||
CursorAction action;
|
||||
action.type = Type::Wait;
|
||||
action.value.wait.duration = duration;
|
||||
return action;
|
||||
}
|
||||
static CursorAction button(int button, bool down) {
|
||||
CursorAction action;
|
||||
action.type = Type::Button;
|
||||
action.value.button.button = button;
|
||||
action.value.button.down = down;
|
||||
return action;
|
||||
}
|
||||
static CursorAction set_phase(AutoloadPhase phase) {
|
||||
CursorAction action;
|
||||
action.type = Type::SetPhase;
|
||||
action.value.set_phase.phase = phase;
|
||||
return action;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<CursorAction> cursor_actions_;
|
||||
|
||||
struct CursorActionBuilder {
|
||||
CursorActionBuilder(std::vector<CursorAction> &actions) : actions_(actions) {}
|
||||
|
||||
CursorActionBuilder &wait(int duration) {
|
||||
actions_.push_back(CursorAction::wait(duration));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CursorActionBuilder &move_to(int x, int y) {
|
||||
// Special case: if this sets a move_to when one is in progress,
|
||||
// just update the target.
|
||||
if(!actions_.empty() && actions_.back().type == CursorAction::Type::MoveTo) {
|
||||
actions_.back().value.move_to.x = x;
|
||||
actions_.back().value.move_to.y = y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
actions_.push_back(CursorAction::move_to(x, y));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CursorActionBuilder &click(int button) {
|
||||
actions_.push_back(CursorAction::button(button, true));
|
||||
actions_.push_back(CursorAction::wait(6'000'000));
|
||||
actions_.push_back(CursorAction::button(button, false));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CursorActionBuilder &double_click(int button) {
|
||||
actions_.push_back(CursorAction::button(button, true));
|
||||
actions_.push_back(CursorAction::wait(6'000'000));
|
||||
actions_.push_back(CursorAction::button(button, false));
|
||||
actions_.push_back(CursorAction::wait(6'000'000));
|
||||
actions_.push_back(CursorAction::button(button, true));
|
||||
actions_.push_back(CursorAction::wait(6'000'000));
|
||||
actions_.push_back(CursorAction::button(button, false));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CursorActionBuilder &set_phase(AutoloadPhase phase) {
|
||||
actions_.push_back(CursorAction::set_phase(phase));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<CursorAction> &actions_;
|
||||
};
|
||||
static constexpr int IconBarY = 240;
|
||||
static constexpr int IconBarProgramX = 532;
|
||||
static constexpr int IconBarDriveX = 32;
|
||||
|
||||
std::vector<CursorAction> &begin();
|
||||
|
||||
Cycles cursor_action_subcycle_;
|
||||
int cursor_action_waited_;
|
||||
int32_t target_window_[2];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace Archimedes;
|
||||
|
||||
std::unique_ptr<Machine> Machine::Archimedes(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
return std::make_unique<ConcreteMachine>(*target, rom_fetcher);
|
||||
std::unique_ptr<Machine> Machine::Archimedes(
|
||||
const Analyser::Static::Target *target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) {
|
||||
const auto archimedes_target = dynamic_cast<const Analyser::Static::Acorn::ArchimedesTarget *>(target);
|
||||
return std::make_unique<ConcreteMachine>(*archimedes_target, rom_fetcher);
|
||||
}
|
||||
|
@ -13,26 +13,91 @@
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
#include "../../../Inputs/Mouse.hpp"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Archimedes {
|
||||
|
||||
namespace {
|
||||
constexpr uint16_t map(int row, int column) {
|
||||
return static_cast<uint16_t>((row << 4) | column);
|
||||
}
|
||||
|
||||
constexpr uint8_t row(uint16_t key) {
|
||||
return static_cast<uint8_t>(key >> 4);
|
||||
}
|
||||
|
||||
constexpr uint8_t column(uint16_t key) {
|
||||
return static_cast<uint8_t>(key & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
struct Key {
|
||||
/// Named key codes that the machine wlll accept directly.
|
||||
enum Value: uint16_t {
|
||||
Escape = map(0, 0), F1 = map(0, 1), F2 = map(0, 2), F3 = map(0, 3),
|
||||
F4 = map(0, 4), F5 = map(0, 5), F6 = map(0, 6), F7 = map(0, 7),
|
||||
F8 = map(0, 8), F9 = map(0, 9), F10 = map(0, 10), F11 = map(0, 11),
|
||||
F12 = map(0, 12), Print = map(0, 13), Scroll = map(0, 14), Break = map(0, 15),
|
||||
|
||||
Tilde = map(1, 0), k1 = map(1, 1), k2 = map(1, 2), k3 = map(1, 3),
|
||||
k4 = map(1, 4), k5 = map(1, 5), k6 = map(1, 6), k7 = map(1, 7),
|
||||
k8 = map(1, 8), k9 = map(1, 9), k0 = map(1, 10), Hyphen = map(1, 11),
|
||||
Equals = map(1, 12), GBPound = map(1, 13), Backspace = map(1, 14), Insert = map(1, 15),
|
||||
|
||||
Home = map(2, 0), PageUp = map(2, 1), NumLock = map(2, 2), KeypadSlash = map(2, 3),
|
||||
KeypadAsterisk = map(2, 4), KeypadHash = map(2, 5), Tab = map(2, 6), Q = map(2, 7),
|
||||
W = map(2, 8), E = map(2, 9), R = map(2, 10), T = map(2, 11),
|
||||
Y = map(2, 12), U = map(2, 13), I = map(2, 14), O = map(2, 15),
|
||||
|
||||
P = map(3, 0), OpenSquareBracket = map(3, 1), CloseSquareBracket = map(3, 2), Backslash = map(3, 3),
|
||||
Delete = map(3, 4), Copy = map(3, 5), PageDown = map(3, 6), Keypad7 = map(3, 7),
|
||||
Keypad8 = map(3, 8), Keypad9 = map(3, 9), KeypadMinus = map(3, 10), LeftControl = map(3, 11),
|
||||
A = map(3, 12), S = map(3, 13), D = map(3, 14), F = map(3, 15),
|
||||
|
||||
G = map(4, 0), H = map(4, 1), J = map(4, 2), K = map(4, 3),
|
||||
L = map(4, 4), Semicolon = map(4, 5), Quote = map(4, 6), Return = map(4, 7),
|
||||
Keypad4 = map(4, 8), Keypad5 = map(4, 9), Keypad6 = map(4, 10), KeypadPlus = map(4, 11),
|
||||
LeftShift = map(4, 12), /* unused */ Z = map(4, 14), X = map(4, 15),
|
||||
|
||||
C = map(5, 0), V = map(5, 1), B = map(5, 2), N = map(5, 3),
|
||||
M = map(5, 4), Comma = map(5, 5), FullStop = map(5, 6), ForwardSlash = map(5, 7),
|
||||
RightShift = map(5, 8), Up = map(5, 9), Keypad1 = map(5, 10), Keypad2 = map(5, 11),
|
||||
Keypad3 = map(5, 12), CapsLock = map(5, 13), LeftAlt = map(5, 14), Space = map(5, 15),
|
||||
|
||||
RightAlt = map(6, 0), RightControl = map(6, 1), Left = map(6, 2), Down = map(6, 3),
|
||||
Right = map(6, 4), Keypad0 = map(6, 5), KeypadDecimalPoint = map(6, 6), KeypadEnter = map(6, 7),
|
||||
|
||||
Max = KeypadEnter,
|
||||
};
|
||||
};
|
||||
|
||||
// Resource for the keyboard protocol: https://github.com/tmk/tmk_keyboard/wiki/ACORN-ARCHIMEDES-Keyboard
|
||||
struct Keyboard {
|
||||
Keyboard(HalfDuplexSerial &serial) : serial_(serial), mouse_(*this) {}
|
||||
|
||||
void set_key_state(int row, int column, bool is_pressed) {
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
states_[key] = is_pressed;
|
||||
|
||||
if(!scan_keyboard_) {
|
||||
logger_.info().append("Ignored key event as key scanning disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't waste bandwidth on repeating facts.
|
||||
if(states_[row][column] == is_pressed) return;
|
||||
states_[row][column] = is_pressed;
|
||||
if(posted_states_[key] == is_pressed) return;
|
||||
|
||||
// Post new key event.
|
||||
logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released");
|
||||
const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000;
|
||||
enqueue(static_cast<uint8_t>(prefix | row), static_cast<uint8_t>(prefix | column));
|
||||
enqueue_key_event(key, is_pressed);
|
||||
consider_dequeue();
|
||||
}
|
||||
|
||||
void set_mouse_button(uint8_t button, bool is_pressed) {
|
||||
if(!scan_mouse_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Post new key event.
|
||||
enqueue_key_event(7, button, is_pressed);
|
||||
consider_dequeue();
|
||||
}
|
||||
|
||||
@ -80,11 +145,17 @@ struct Keyboard {
|
||||
enqueue(0, 0);
|
||||
break;
|
||||
|
||||
case NACK: case SMAK: case MACK: case SACK:
|
||||
case NACK: case SMAK: case MACK: case SACK: {
|
||||
const bool was_scanning_keyboard = input & 1;
|
||||
scan_keyboard_ = input & 1;
|
||||
if(!scan_keyboard_) {
|
||||
posted_states_.reset();
|
||||
} else if(!was_scanning_keyboard) {
|
||||
needs_state_check_ = true;
|
||||
}
|
||||
scan_mouse_ = input & 2;
|
||||
logger_.info().append("ACK; keyboard:%d mouse:%d", scan_keyboard_, scan_mouse_);
|
||||
break;
|
||||
} break;
|
||||
|
||||
default:
|
||||
if((input & 0b1111'0000) == 0b0100'0000) {
|
||||
@ -141,7 +212,20 @@ struct Keyboard {
|
||||
|
||||
void consider_dequeue() {
|
||||
if(state_ == State::Idle) {
|
||||
// If the key event queue is empty, grab as much mouse motion
|
||||
// If the key event queue is empty but keyboard scanning is enabled, check for
|
||||
// any disparity between posted keys states and actuals.
|
||||
if(needs_state_check_) {
|
||||
needs_state_check_ = false;
|
||||
if(states_ != posted_states_) {
|
||||
for(size_t key = 0; key < Key::Max; key++) {
|
||||
if(states_[key] != posted_states_[key]) {
|
||||
enqueue_key_event(static_cast<uint16_t>(key), states_[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the key event queue is _still_ empty, grab as much mouse motion
|
||||
// as available.
|
||||
if(event_queue_.empty()) {
|
||||
const int x = std::clamp(mouse_x_, -0x3f, 0x3f);
|
||||
@ -168,7 +252,9 @@ private:
|
||||
HalfDuplexSerial &serial_;
|
||||
Log::Logger<Log::Source::Keyboard> logger_;
|
||||
|
||||
bool states_[16][16]{};
|
||||
std::bitset<Key::Max> states_;
|
||||
std::bitset<Key::Max> posted_states_;
|
||||
bool needs_state_check_ = false;
|
||||
|
||||
bool scan_keyboard_ = false;
|
||||
bool scan_mouse_ = false;
|
||||
@ -196,6 +282,15 @@ private:
|
||||
event_queue_.erase(event_queue_.begin());
|
||||
return true;
|
||||
}
|
||||
void enqueue_key_event(uint16_t key, bool is_pressed) {
|
||||
posted_states_[key] = is_pressed;
|
||||
enqueue_key_event(row(key), column(key), is_pressed);
|
||||
}
|
||||
void enqueue_key_event(uint8_t row, uint8_t column, bool is_pressed) {
|
||||
logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released");
|
||||
const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000;
|
||||
enqueue(static_cast<uint8_t>(prefix | row), static_cast<uint8_t>(prefix | column));
|
||||
}
|
||||
|
||||
static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset.
|
||||
static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1.
|
||||
@ -211,7 +306,6 @@ private:
|
||||
static constexpr uint8_t SMAK = 0b0011'0011; // Last data byte acknowledge, enabling scanning and mouse.
|
||||
static constexpr uint8_t PRST = 0b0010'0001; // Does nothing.
|
||||
|
||||
|
||||
struct Mouse: public Inputs::Mouse {
|
||||
Mouse(Keyboard &keyboard): keyboard_(keyboard) {}
|
||||
|
||||
@ -225,7 +319,7 @@ private:
|
||||
}
|
||||
|
||||
virtual void set_button_pressed(int index, bool is_pressed) override {
|
||||
keyboard_.set_key_state(7, index, is_pressed);
|
||||
keyboard_.set_mouse_button(static_cast<uint8_t>(index), is_pressed);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -9,136 +9,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
namespace Archimedes {
|
||||
|
||||
/// Converter from this emulator's custom definition of a generic keyboard to the machine-specific key set defined above.
|
||||
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
public:
|
||||
static constexpr uint16_t map(int row, int column) {
|
||||
return static_cast<uint16_t>((row << 4) | column);
|
||||
}
|
||||
|
||||
static constexpr int row(uint16_t key) {
|
||||
return key >> 4;
|
||||
}
|
||||
|
||||
static constexpr int column(uint16_t key) {
|
||||
return key & 0xf;
|
||||
}
|
||||
|
||||
// Adapted from the A500 Series Technical Reference Manual.
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const override {
|
||||
using k = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
case k::Escape: return map(0, 0);
|
||||
case k::F1: return map(0, 1);
|
||||
case k::F2: return map(0, 2);
|
||||
case k::F3: return map(0, 3);
|
||||
case k::F4: return map(0, 4);
|
||||
case k::F5: return map(0, 5);
|
||||
case k::F6: return map(0, 6);
|
||||
case k::F7: return map(0, 7);
|
||||
case k::F8: return map(0, 8);
|
||||
case k::F9: return map(0, 9);
|
||||
case k::F10: return map(0, 10);
|
||||
case k::F11: return map(0, 11);
|
||||
case k::F12: return map(0, 12);
|
||||
case k::PrintScreen: return map(0, 13);
|
||||
case k::ScrollLock: return map(0, 14);
|
||||
case k::Pause: return map(0, 15);
|
||||
case k::Escape: return Key::Escape;
|
||||
case k::F1: return Key::F1;
|
||||
case k::F2: return Key::F2;
|
||||
case k::F3: return Key::F3;
|
||||
case k::F4: return Key::F4;
|
||||
case k::F5: return Key::F5;
|
||||
case k::F6: return Key::F6;
|
||||
case k::F7: return Key::F7;
|
||||
case k::F8: return Key::F8;
|
||||
case k::F9: return Key::F9;
|
||||
case k::F10: return Key::F10;
|
||||
case k::F11: return Key::F11;
|
||||
case k::F12: return Key::F12;
|
||||
case k::PrintScreen: return Key::Print;
|
||||
case k::ScrollLock: return Key::Scroll;
|
||||
case k::Pause: return Key::Break;
|
||||
|
||||
case k::BackTick: return map(1, 0);
|
||||
case k::k1: return map(1, 1);
|
||||
case k::k2: return map(1, 2);
|
||||
case k::k3: return map(1, 3);
|
||||
case k::k4: return map(1, 4);
|
||||
case k::k5: return map(1, 5);
|
||||
case k::k6: return map(1, 6);
|
||||
case k::k7: return map(1, 7);
|
||||
case k::k8: return map(1, 8);
|
||||
case k::k9: return map(1, 9);
|
||||
case k::k0: return map(1, 10);
|
||||
case k::Hyphen: return map(1, 11);
|
||||
case k::Equals: return map(1, 12);
|
||||
case k::BackTick: return Key::Tilde;
|
||||
case k::k1: return Key::k1;
|
||||
case k::k2: return Key::k2;
|
||||
case k::k3: return Key::k3;
|
||||
case k::k4: return Key::k4;
|
||||
case k::k5: return Key::k5;
|
||||
case k::k6: return Key::k6;
|
||||
case k::k7: return Key::k7;
|
||||
case k::k8: return Key::k8;
|
||||
case k::k9: return Key::k9;
|
||||
case k::k0: return Key::k0;
|
||||
case k::Hyphen: return Key::Hyphen;
|
||||
case k::Equals: return Key::Equals;
|
||||
// TODO: pound key.
|
||||
case k::Backspace: return map(1, 14);
|
||||
case k::Insert: return map(1, 15);
|
||||
case k::Backspace: return Key::Backspace;
|
||||
case k::Insert: return Key::Insert;
|
||||
|
||||
case k::Home: return map(2, 0);
|
||||
case k::PageUp: return map(2, 1);
|
||||
case k::NumLock: return map(2, 2);
|
||||
case k::KeypadSlash: return map(2, 3);
|
||||
case k::KeypadAsterisk: return map(2, 4);
|
||||
case k::Home: return Key::Home;
|
||||
case k::PageUp: return Key::PageUp;
|
||||
case k::NumLock: return Key::NumLock;
|
||||
case k::KeypadSlash: return Key::KeypadSlash;
|
||||
case k::KeypadAsterisk: return Key::KeypadAsterisk;
|
||||
// TODO: keypad hash key
|
||||
case k::Tab: return map(2, 6);
|
||||
case k::Q: return map(2, 7);
|
||||
case k::W: return map(2, 8);
|
||||
case k::E: return map(2, 9);
|
||||
case k::R: return map(2, 10);
|
||||
case k::T: return map(2, 11);
|
||||
case k::Y: return map(2, 12);
|
||||
case k::U: return map(2, 13);
|
||||
case k::I: return map(2, 14);
|
||||
case k::O: return map(2, 15);
|
||||
case k::Tab: return Key::Tab;
|
||||
case k::Q: return Key::Q;
|
||||
case k::W: return Key::W;
|
||||
case k::E: return Key::E;
|
||||
case k::R: return Key::R;
|
||||
case k::T: return Key::T;
|
||||
case k::Y: return Key::Y;
|
||||
case k::U: return Key::U;
|
||||
case k::I: return Key::I;
|
||||
case k::O: return Key::O;
|
||||
|
||||
case k::P: return map(3, 0);
|
||||
case k::OpenSquareBracket: return map(3, 1);
|
||||
case k::CloseSquareBracket: return map(3, 2);
|
||||
case k::Backslash: return map(3, 3);
|
||||
case k::Delete: return map(3, 4);
|
||||
case k::End: return map(3, 5);
|
||||
case k::PageDown: return map(3, 6);
|
||||
case k::Keypad7: return map(3, 7);
|
||||
case k::Keypad8: return map(3, 8);
|
||||
case k::Keypad9: return map(3, 9);
|
||||
case k::KeypadMinus: return map(3, 10);
|
||||
case k::LeftControl: return map(3, 11);
|
||||
case k::A: return map(3, 12);
|
||||
case k::S: return map(3, 13);
|
||||
case k::D: return map(3, 14);
|
||||
case k::F: return map(3, 15);
|
||||
case k::P: return Key::P;
|
||||
case k::OpenSquareBracket: return Key::OpenSquareBracket;
|
||||
case k::CloseSquareBracket: return Key::CloseSquareBracket;
|
||||
case k::Backslash: return Key::Backslash;
|
||||
case k::Delete: return Key::Delete;
|
||||
case k::End: return Key::Copy;
|
||||
case k::PageDown: return Key::PageDown;
|
||||
case k::Keypad7: return Key::Keypad7;
|
||||
case k::Keypad8: return Key::Keypad8;
|
||||
case k::Keypad9: return Key::Keypad9;
|
||||
case k::KeypadMinus: return Key::KeypadMinus;
|
||||
case k::LeftControl: return Key::LeftControl;
|
||||
case k::A: return Key::A;
|
||||
case k::S: return Key::S;
|
||||
case k::D: return Key::D;
|
||||
case k::F: return Key::F;
|
||||
|
||||
case k::G: return map(4, 0);
|
||||
case k::H: return map(4, 1);
|
||||
case k::J: return map(4, 2);
|
||||
case k::K: return map(4, 3);
|
||||
case k::L: return map(4, 4);
|
||||
case k::Semicolon: return map(4, 5);
|
||||
case k::Quote: return map(4, 6);
|
||||
case k::Enter: return map(4, 7);
|
||||
case k::Keypad4: return map(4, 8);
|
||||
case k::Keypad5: return map(4, 9);
|
||||
case k::Keypad6: return map(4, 10);
|
||||
case k::KeypadPlus: return map(4, 11);
|
||||
case k::LeftShift: return map(4, 12);
|
||||
case k::Z: return map(4, 14);
|
||||
case k::X: return map(4, 15);
|
||||
case k::G: return Key::G;
|
||||
case k::H: return Key::H;
|
||||
case k::J: return Key::J;
|
||||
case k::K: return Key::K;
|
||||
case k::L: return Key::L;
|
||||
case k::Semicolon: return Key::Semicolon;
|
||||
case k::Quote: return Key::Quote;
|
||||
case k::Enter: return Key::Return;
|
||||
case k::Keypad4: return Key::Keypad4;
|
||||
case k::Keypad5: return Key::Keypad5;
|
||||
case k::Keypad6: return Key::Keypad6;
|
||||
case k::KeypadPlus: return Key::KeypadPlus;
|
||||
case k::LeftShift: return Key::LeftShift;
|
||||
case k::Z: return Key::Z;
|
||||
case k::X: return Key::X;
|
||||
|
||||
case k::C: return map(5, 0);
|
||||
case k::V: return map(5, 1);
|
||||
case k::B: return map(5, 2);
|
||||
case k::N: return map(5, 3);
|
||||
case k::M: return map(5, 4);
|
||||
case k::Comma: return map(5, 5);
|
||||
case k::FullStop: return map(5, 6);
|
||||
case k::ForwardSlash: return map(5, 7);
|
||||
case k::RightShift: return map(5, 8);
|
||||
case k::Up: return map(5, 9);
|
||||
case k::Keypad1: return map(5, 10);
|
||||
case k::Keypad2: return map(5, 11);
|
||||
case k::Keypad3: return map(5, 12);
|
||||
case k::CapsLock: return map(5, 13);
|
||||
case k::LeftOption: return map(5, 14);
|
||||
case k::Space: return map(5, 15);
|
||||
case k::C: return Key::C;
|
||||
case k::V: return Key::V;
|
||||
case k::B: return Key::B;
|
||||
case k::N: return Key::N;
|
||||
case k::M: return Key::M;
|
||||
case k::Comma: return Key::Comma;
|
||||
case k::FullStop: return Key::FullStop;
|
||||
case k::ForwardSlash: return Key::ForwardSlash;
|
||||
case k::RightShift: return Key::RightShift;
|
||||
case k::Up: return Key::Up;
|
||||
case k::Keypad1: return Key::Keypad1;
|
||||
case k::Keypad2: return Key::Keypad2;
|
||||
case k::Keypad3: return Key::Keypad3;
|
||||
case k::CapsLock: return Key::CapsLock;
|
||||
case k::LeftOption: return Key::LeftAlt;
|
||||
case k::Space: return Key::Space;
|
||||
|
||||
case k::RightOption: return map(6, 0);
|
||||
case k::RightControl: return map(6, 1);
|
||||
case k::Left: return map(6, 2);
|
||||
case k::Down: return map(6, 3);
|
||||
case k::Right: return map(6, 4);
|
||||
case k::Keypad0: return map(6, 5);
|
||||
case k::KeypadDecimalPoint: return map(6, 6);
|
||||
case k::KeypadEnter: return map(6, 7);
|
||||
case k::RightOption: return Key::RightAlt;
|
||||
case k::RightControl: return Key::RightControl;
|
||||
case k::Left: return Key::Left;
|
||||
case k::Down: return Key::Down;
|
||||
case k::Right: return Key::Right;
|
||||
case k::Keypad0: return Key::Keypad0;
|
||||
case k::KeypadDecimalPoint: return Key::KeypadDecimalPoint;
|
||||
case k::KeypadEnter: return Key::KeypadEnter;
|
||||
|
||||
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
|
@ -106,8 +106,9 @@ struct MemoryController {
|
||||
case 0b111:
|
||||
os_mode_ = address & (1 << 12);
|
||||
sound_dma_enable_ = address & (1 << 11);
|
||||
ioc_.sound().set_dma_enabled(sound_dma_enable_);
|
||||
video_dma_enable_ = address & (1 << 10);
|
||||
ioc_.sound().set_dma_enabled(sound_dma_enable_);
|
||||
ioc_.video().set_dma_enabled(video_dma_enable_);
|
||||
switch((address >> 8) & 3) {
|
||||
default:
|
||||
dynamic_ram_refresh_ = DynamicRAMRefresh::None;
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
namespace Archimedes {
|
||||
|
||||
@ -243,6 +244,30 @@ struct Video {
|
||||
return overages_;
|
||||
}
|
||||
|
||||
void set_dma_enabled(bool dma_enabled) {
|
||||
dma_enabled_ = dma_enabled;
|
||||
}
|
||||
|
||||
//
|
||||
// The following is provided for input automation;
|
||||
// it does not correlate with real hardware functionality.
|
||||
//
|
||||
std::optional<std::pair<int, int>> cursor_location() {
|
||||
if(
|
||||
!dma_enabled_ ||
|
||||
vertical_timing_.cursor_end <= vertical_timing_.cursor_start ||
|
||||
horizontal_timing_.cursor_start >= (horizontal_timing_.period * 2)
|
||||
|
||||
) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto horizontal_start = horizontal_timing_.display_start + horizontal_state_.output_latency(colour_depth_);
|
||||
return std::make_pair(
|
||||
int(horizontal_timing_.cursor_start) + 6 - int(horizontal_start * 2),
|
||||
int(vertical_timing_.cursor_start) - int(vertical_timing_.display_start));
|
||||
}
|
||||
|
||||
private:
|
||||
Log::Logger<Log::Source::ARMIOC> logger;
|
||||
InterruptObserverT &interrupt_observer_;
|
||||
@ -253,6 +278,7 @@ private:
|
||||
// being deferred to the component itself.
|
||||
const uint8_t *ram_ = nullptr;
|
||||
Outputs::CRT::CRT crt_;
|
||||
bool dma_enabled_ = false;
|
||||
|
||||
// Horizontal and vertical timing.
|
||||
struct Timing {
|
||||
@ -330,11 +356,12 @@ private:
|
||||
}
|
||||
|
||||
bool is_outputting(Depth depth) const {
|
||||
return position >= display_start + output_latencies[static_cast<uint32_t>(depth)] && position < display_end + output_latencies[static_cast<uint32_t>(depth)];
|
||||
const auto latency = output_latency(depth);
|
||||
return position >= display_start + latency && position < display_end + latency;
|
||||
}
|
||||
|
||||
uint32_t output_cycle(Depth depth) const {
|
||||
return position - display_start - output_latencies[static_cast<uint32_t>(depth)];
|
||||
return position - display_start - output_latency(depth);
|
||||
}
|
||||
|
||||
static constexpr uint32_t output_latencies[] = {
|
||||
@ -343,6 +370,9 @@ private:
|
||||
7 >> 1, // 4 bpp.
|
||||
5 >> 1 // 8 bpp.
|
||||
};
|
||||
uint32_t output_latency(Depth depth) const {
|
||||
return output_latencies[static_cast<uint32_t>(depth)];
|
||||
}
|
||||
|
||||
static constexpr uint8_t SyncEnded = 0x1;
|
||||
static constexpr uint8_t BorderStarted = 0x2;
|
||||
|
@ -51,7 +51,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
public SCSI::Bus::Observer,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
ConcreteMachine(const Analyser::Static::Acorn::ElectronTarget &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
scsi_bus_(4'000'000),
|
||||
hard_drive_(scsi_bus_, 0),
|
||||
@ -787,7 +787,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
using namespace Electron;
|
||||
|
||||
std::unique_ptr<Machine> Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Acorn::Target;
|
||||
using Target = Analyser::Static::Acorn::ElectronTarget;
|
||||
const Target *const acorn_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
if(acorn_target->media.mass_storage_devices.empty()) {
|
||||
|
@ -217,7 +217,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
|
||||
std::map<std::string, std::unique_ptr<Reflection::Struct>> options;
|
||||
|
||||
#define Emplace(machine, class) \
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique<class::Options>(Configurable::OptionsType::UserFriendly)));
|
||||
options.emplace(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique<class::Options>(Configurable::OptionsType::UserFriendly))
|
||||
|
||||
Emplace(AmstradCPC, AmstradCPC::Machine);
|
||||
Emplace(AppleII, Apple::II::Machine);
|
||||
@ -243,16 +243,16 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
|
||||
std::map<std::string, std::unique_ptr<Analyser::Static::Target>> options;
|
||||
|
||||
#define AddMapped(Name, TargetNamespace) \
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Name), new Analyser::Static::TargetNamespace::Target));
|
||||
options.emplace(LongNameForTargetMachine(Analyser::Machine::Name), std::make_unique<Analyser::Static::TargetNamespace::Target>());
|
||||
#define Add(Name) AddMapped(Name, Name)
|
||||
|
||||
Add(Amiga);
|
||||
Add(AmstradCPC);
|
||||
Add(AppleII);
|
||||
Add(AppleIIgs);
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Target(Analyser::Machine::Archimedes)));
|
||||
options.emplace(LongNameForTargetMachine(Analyser::Machine::Archimedes), std::make_unique<Analyser::Static::Acorn::ArchimedesTarget>());
|
||||
Add(AtariST);
|
||||
AddMapped(Electron, Acorn);
|
||||
options.emplace(LongNameForTargetMachine(Analyser::Machine::Electron), std::make_unique<Analyser::Static::Acorn::ElectronTarget>());
|
||||
Add(Enterprise);
|
||||
Add(Macintosh);
|
||||
Add(MSX);
|
||||
@ -264,7 +264,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
|
||||
|
||||
if(!meaningful_without_media_only) {
|
||||
Add(Atari2600);
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), new Analyser::Static::Target(Analyser::Machine::ColecoVision)));
|
||||
options.emplace(LongNameForTargetMachine(Analyser::Machine::ColecoVision), std::make_unique<Analyser::Static::Target>(Analyser::Machine::ColecoVision));
|
||||
AddMapped(MasterSystem, Sega);
|
||||
}
|
||||
|
||||
|
65
Numeric/StringSimilarity.hpp
Normal file
65
Numeric/StringSimilarity.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// StringSimilarity.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/05/2024.
|
||||
// Copyright © 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef StringSimilarity_hpp
|
||||
#define StringSimilarity_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
/// Seeks to implement algorithm as per http://www.catalysoft.com/articles/StrikeAMatch.html
|
||||
///
|
||||
/// @returns A number in the range 0.0 to 1.0 indicating the similarity between two strings;
|
||||
/// 1.0 is most similar, 0.0 is least.
|
||||
inline double similarity(std::string_view first, std::string_view second) {
|
||||
if(first.size() < 2 || second.size() < 2) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const auto pairs = [](std::string_view source) -> std::set<uint16_t> {
|
||||
std::set<uint16_t> result;
|
||||
for(std::size_t c = 0; c < source.size() - 1; c++) {
|
||||
if(isalpha(source[c]) && isalpha(source[c+1])) {
|
||||
result.insert(static_cast<uint16_t>(
|
||||
(toupper(source[c]) << 8) |
|
||||
toupper(source[c+1])
|
||||
));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto first_pairs = pairs(first);
|
||||
const auto second_pairs = pairs(second);
|
||||
|
||||
const auto denominator = static_cast<double>(first_pairs.size() + second_pairs.size());
|
||||
|
||||
std::size_t numerator = 0;
|
||||
auto first_it = first_pairs.begin();
|
||||
auto second_it = second_pairs.begin();
|
||||
while(first_it != first_pairs.end() && second_it != second_pairs.end()) {
|
||||
if(*first_it == *second_it) {
|
||||
++numerator;
|
||||
++first_it;
|
||||
++second_it;
|
||||
} else if(*first_it < *second_it) {
|
||||
++first_it;
|
||||
} else {
|
||||
++second_it;
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<double>(numerator * 2) / denominator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* StringSimilarity_h */
|
@ -1050,6 +1050,7 @@
|
||||
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
|
||||
4BD91D732401960C007BDC91 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
|
||||
4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */; };
|
||||
4BD971392BFC3D9D00C907AA /* ArchimedesStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */; };
|
||||
4BDA00DA22E60EE300AC3CD0 /* ROMRequester.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BDA00D922E60EE300AC3CD0 /* ROMRequester.xib */; };
|
||||
4BDA00DD22E622C200AC3CD0 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
|
||||
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; };
|
||||
@ -2266,6 +2267,8 @@
|
||||
4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
|
||||
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8255.hpp; sourceTree = "<group>"; };
|
||||
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatrikRakTests.swift; sourceTree = "<group>"; };
|
||||
4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArchimedesStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StringSimilarity.hpp; sourceTree = "<group>"; };
|
||||
4BDA00D922E60EE300AC3CD0 /* ROMRequester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ROMRequester.xib; sourceTree = "<group>"; };
|
||||
4BDA00DE22E644AF00AC3CD0 /* CSROMReceiverView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSROMReceiverView.h; sourceTree = "<group>"; };
|
||||
4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; };
|
||||
@ -3526,6 +3529,7 @@
|
||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
||||
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||
4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */,
|
||||
);
|
||||
name = Numeric;
|
||||
path = ../../Numeric;
|
||||
@ -4546,6 +4550,7 @@
|
||||
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */,
|
||||
4BD388872239E198002D14B5 /* 68000Tests.mm */,
|
||||
4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */,
|
||||
4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */,
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
|
||||
@ -6383,6 +6388,7 @@
|
||||
4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */,
|
||||
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */,
|
||||
4B778F5923A5F2D00000D260 /* Z80.cpp in Sources */,
|
||||
4BD971392BFC3D9D00C907AA /* ArchimedesStaticAnalyserTests.mm in Sources */,
|
||||
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */,
|
||||
4B778F0E23A5EC4F0000D260 /* Tape.cpp in Sources */,
|
||||
4B778F2D23A5EF190000D260 /* MFMDiskController.cpp in Sources */,
|
||||
|
@ -62,7 +62,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -769,11 +769,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>24.04.20</string>
|
||||
<string>24.05.05</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>24.04.20</string>
|
||||
<string>24.05.05</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.entertainment</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
@ -136,11 +136,10 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithArchimedesModel:(CSMachineArchimedesModel)model {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
auto target = std::make_unique<Analyser::Static::Target>(Analyser::Machine::Archimedes);
|
||||
auto target = std::make_unique<Analyser::Static::Acorn::ArchimedesTarget>();
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
@ -164,8 +163,7 @@
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Acorn::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
auto target = std::make_unique<Analyser::Static::Acorn::ElectronTarget>();
|
||||
target->has_dfs = dfs;
|
||||
target->has_pres_adfs = adfs;
|
||||
target->has_ap6_rom = ap6;
|
||||
|
@ -34,7 +34,7 @@ constexpr char TestSuiteHome[] = "/Users/tharte/Projects/ProcessorTests/8088/v1"
|
||||
using Flags = InstructionSet::x86::Flags;
|
||||
struct Registers {
|
||||
public:
|
||||
static constexpr bool is_32bit = false;
|
||||
// static constexpr bool is_32bit = false;
|
||||
|
||||
uint8_t &al() { return ax_.halves.low; }
|
||||
uint8_t &ah() { return ax_.halves.high; }
|
||||
|
@ -0,0 +1,85 @@
|
||||
//
|
||||
// ArchimedesStaticAnalyserTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/05/2024.
|
||||
// Copyright 2024 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../../Analyser/Static/Acorn/Target.hpp"
|
||||
|
||||
static NSString *archimedesDiskPath = @"/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Soft/Archimedes";
|
||||
|
||||
static NSDictionary<NSString *, NSString *> *mainProgramsBySHA1 = @{
|
||||
@"371b30787a782cb1fe6cb6ad2217a832a06e1e96": @"!TimeZone",
|
||||
@"3459adef724e2cd6f3681050a9ce47394231b4f9": @"!Talisman",
|
||||
@"3022e18d47ed0fc14b09c18caff3fc0ac1f4edff": @"!StarTrade",
|
||||
@"252bfde8d602fe171e0657fa3f9dfeba1803e6eb": @"!Blowpipe",
|
||||
@"e3c32b8cbd3cd31cbca93e5a45b94e7f8058b8f7": @"Zelanites.!Zelanites",
|
||||
@"2e1cb15cde588e22f50518b6ffa47a8df89b14c0": @"!Fire_Ice",
|
||||
@"069592c0b90a0b9112daf014b7e19b4a51f9653b": @"!UIM",
|
||||
@"14c3785b3bc3f7e2d4a81e92ff06e11656e6b76c": @"!UIM",
|
||||
@"93b67127286d861e4df31cac27e78e623a1e852f": @"!FineRacer",
|
||||
@"53f95c169bbe9cfa7252d90d6181ced31086f1a5": @"!adventure",
|
||||
@"4168bb21f6df0976ce227a20f9fa4eb240289f3b": @"!BigBang",
|
||||
@"8fcad522ea22b75b393ceb334cfef3f324b248ee": @"!E-TYPE",
|
||||
@"8ca4289ac423d4878129cb17d6177123b321108f": @"!StrtWrite",
|
||||
@"4f92efecfc1e3a510a816f570ccb7082f0154e37": @"!HeroQuest",
|
||||
@"9bd6d2514c04ce02fcf8ef214815229b28be56d8": @"!adventure",
|
||||
@"d3493850e8ed91ae0a55a53866139781ad65e63d": @"!Nebulus",
|
||||
@"ba655bd8936859a33bab5fde447e33486c3b0d3e": @"!Attack",
|
||||
@"a6502faf15ddb4acaed2ca859cedc1225e7fa762": @"!Wolf",
|
||||
// @"04f588f87facd507e043b06f512e9bdb6fe996c0": // TODO: should decline to pick.
|
||||
|
||||
// Various things that are not the first disk.
|
||||
@"2cff99237837e2291b845eb63977362ad9b4f040": @"",
|
||||
@"3615bcb8a953fbba3d56a956243341a022208101": @"",
|
||||
@"03672244691b292d6b4816aa592b312ea6297b22": @"",
|
||||
@"b7139d9bd927b8e4d933fd8aa3080a7249117495": @"",
|
||||
@"66a82651f86d9cf0aa5b54c55bcaa8fefd3901da": @"",
|
||||
@"c3d3cd9e28f5e7499fd70057f820c75219538c69": @"",
|
||||
@"81bfd4ab92c538f5b15ad64bba625aac2ffb243d": @"",
|
||||
@"39318695b6e64c9d7270f2b6d8213a7d4b0b0c43": @"",
|
||||
};
|
||||
#undef Record
|
||||
|
||||
@interface ArchimedesStaticAnalyserTests : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ArchimedesStaticAnalyserTests
|
||||
|
||||
- (void)testADFs {
|
||||
for(NSString *testFile in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:archimedesDiskPath error:nil]) {
|
||||
NSString *fullPath = [archimedesDiskPath stringByAppendingPathComponent:testFile];
|
||||
|
||||
// Compute file SHA1.
|
||||
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
|
||||
uint8_t sha1Bytes[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1([fileData bytes], (CC_LONG)[fileData length], sha1Bytes);
|
||||
NSMutableString *const sha1 = [[NSMutableString alloc] init];
|
||||
for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]];
|
||||
|
||||
// Get analysed target and correct answer per list above.
|
||||
auto targets = Analyser::Static::GetTargets([fullPath UTF8String]);
|
||||
NSString *const mainProgram = mainProgramsBySHA1[sha1];
|
||||
if(!mainProgram) {
|
||||
NSLog(@"Not checking %@ with SHA1 %@", testFile, sha1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(![mainProgram length]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Test equality.
|
||||
auto *const target = dynamic_cast<Analyser::Static::Acorn::ArchimedesTarget *>(targets.front().get());
|
||||
XCTAssert(target != nullptr);
|
||||
XCTAssert(target->main_program == std::string([mainProgram UTF8String]), @"%@; should be %@, is %s", testFile, mainProgram, target->main_program.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
Loading…
x
Reference in New Issue
Block a user