From bdf1dff97663027eba1fbfadf1b92f9693e65283 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 4 May 2024 21:16:43 -0400 Subject: [PATCH 01/25] Update version number. --- OSBindings/Mac/Clock Signal/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index ae00cecbe..ee22edf8a 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -769,11 +769,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 24.04.20 + 24.05.05 CFBundleSignature ???? CFBundleVersion - 24.04.20 + 24.05.05 LSApplicationCategoryType public.app-category.entertainment LSMinimumSystemVersion From 02ee3a78044cb0ef047b8639f133e4c79dbade92 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 6 May 2024 20:36:00 -0400 Subject: [PATCH 02/25] Turf out old debugging cruft. --- Analyser/Static/Acorn/SWIIndex.cpp | 230 ++++++++++++++ Analyser/Static/Acorn/SWIIndex.hpp | 24 ++ Machines/Acorn/Archimedes/Archimedes.cpp | 289 +----------------- .../Clock Signal.xcodeproj/project.pbxproj | 8 + 4 files changed, 265 insertions(+), 286 deletions(-) create mode 100644 Analyser/Static/Acorn/SWIIndex.cpp create mode 100644 Analyser/Static/Acorn/SWIIndex.hpp diff --git a/Analyser/Static/Acorn/SWIIndex.cpp b/Analyser/Static/Acorn/SWIIndex.cpp new file mode 100644 index 000000000..a61178b76 --- /dev/null +++ b/Analyser/Static/Acorn/SWIIndex.cpp @@ -0,0 +1,230 @@ +// +// SWIIndex.cpp +// Clock Signal +// +// Created by Thomas Harte on 05/05/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#include "SWIIndex.hpp" + +using namespace Analyser::Static::Acorn; + +const SWIDescription &Analyser::Static::Acorn::describe_swi(uint32_t comment) { + static SWIDescription none; + + (void)comment; + return none; +} + + +// 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(pointer, next, InstructionSet::ARM::Mode::Supervisor, false); + ++pointer; + + if(next < 32) break; + swis.back().value_name.push_back(static_cast(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(); + }*/ diff --git a/Analyser/Static/Acorn/SWIIndex.hpp b/Analyser/Static/Acorn/SWIIndex.hpp new file mode 100644 index 000000000..524d6a16e --- /dev/null +++ b/Analyser/Static/Acorn/SWIIndex.hpp @@ -0,0 +1,24 @@ +// +// SWIIndex.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/05/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#ifndef SWIIndex_hpp +#define SWIIndex_hpp + +#include + +namespace Analyser::Static::Acorn { + +struct SWIDescription { + +}; + +const SWIDescription &describe_swi(uint32_t comment); + +} + +#endif /* SWIIndex_hpp */ diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 5c1300779..58a538d65 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -36,287 +36,6 @@ namespace Archimedes { -#ifndef NDEBUG -namespace { -Log::Logger logger; -} - -template -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(pointer, next, InstructionSet::ARM::Mode::Supervisor, false); - ++pointer; - - if(next < 32) break; - swis.back().value_name.push_back(static_cast(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 disassembler; - InstructionSet::ARM::dispatch(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 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 swis; - uint32_t last_pc = 0; -// uint32_t last_r9 = 0; - bool log = false; - bool accumulate = true; - - std::set opcodes; -}; -#else -template -struct HackyDebugger { - void notify(uint32_t, uint32_t, Executor &) {} -}; -#endif - class ConcreteMachine: public Machine, public MachineTypes::AudioProducer, @@ -328,7 +47,9 @@ class ConcreteMachine: public Activity::Source { private: - // TODO: pick a sensible clock rate; this is just code for '24 MIPS, please'. + Log::Logger 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 @@ -504,7 +225,6 @@ class ConcreteMachine: void tick_cpu() { const uint32_t instruction = advance_pipeline(executor_.pc() + 8); - debugger_.notify(executor_.pc(), instruction, executor_); InstructionSet::ARM::execute(instruction, executor_); } @@ -622,9 +342,6 @@ class ConcreteMachine: SWISubversion latched_subversion_; } pipeline_; - - // MARK: - Yucky, temporary junk. - HackyDebugger debugger_; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4309b3eb1..c535f85e5 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -971,6 +971,8 @@ 4BB505862B9634F30031C43C /* Archimedes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB505842B9634F30031C43C /* Archimedes.cpp */; }; 4BB505872B9634F30031C43C /* Archimedes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB505842B9634F30031C43C /* Archimedes.cpp */; }; 4BB505892B9C0E6F0031C43C /* Messy ARM in Resources */ = {isa = PBXBuildFile; fileRef = 4BB505882B9C0E6F0031C43C /* Messy ARM */; }; + 4BB508672BE816E8000ACC9F /* SWIIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */; }; + 4BB508682BE816E8000ACC9F /* SWIIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */; }; 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; @@ -2122,6 +2124,8 @@ 4BB505842B9634F30031C43C /* Archimedes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Archimedes.cpp; sourceTree = ""; }; 4BB505852B9634F30031C43C /* Archimedes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Archimedes.hpp; sourceTree = ""; }; 4BB505882B9C0E6F0031C43C /* Messy ARM */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Messy ARM"; sourceTree = ""; }; + 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SWIIndex.cpp; sourceTree = ""; }; + 4BB508662BE816E8000ACC9F /* SWIIndex.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SWIIndex.hpp; sourceTree = ""; }; 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = ""; }; 4BB5B996281B1E3F00522DA9 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; 4BB5B997281B1F7B00522DA9 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; @@ -3685,6 +3689,8 @@ 4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944EF201967B4007DE474 /* Tape.hpp */, 4BE32313205327D7006EF799 /* Target.hpp */, + 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */, + 4BB508662BE816E8000ACC9F /* SWIIndex.hpp */, ); path = Acorn; sourceTree = ""; @@ -5927,6 +5933,7 @@ 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, + 4BB508682BE816E8000ACC9F /* SWIIndex.cpp in Sources */, 4B2E86C925D892EF0024F1E9 /* DAT.cpp in Sources */, 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, @@ -6293,6 +6300,7 @@ 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */, 4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */, 4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4BB508672BE816E8000ACC9F /* SWIIndex.cpp in Sources */, 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */, 4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */, 4BB505782B962DDF0031C43C /* SoundGenerator.cpp in Sources */, From 6d42c9aaf93f77f1b8d14becaa8e3759882a3506 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 6 May 2024 22:40:00 -0400 Subject: [PATCH 03/25] Start making leeway on interesting SWIs. --- Analyser/Static/Acorn/SWIIndex.cpp | 200 +++++++++++------------ Analyser/Static/Acorn/SWIIndex.hpp | 35 +++- Machines/Acorn/Archimedes/Archimedes.cpp | 67 +++++++- 3 files changed, 194 insertions(+), 108 deletions(-) diff --git a/Analyser/Static/Acorn/SWIIndex.cpp b/Analyser/Static/Acorn/SWIIndex.cpp index a61178b76..7fc085882 100644 --- a/Analyser/Static/Acorn/SWIIndex.cpp +++ b/Analyser/Static/Acorn/SWIIndex.cpp @@ -8,60 +8,109 @@ #include "SWIIndex.hpp" +#include +#include + using namespace Analyser::Static::Acorn; -const SWIDescription &Analyser::Static::Acorn::describe_swi(uint32_t comment) { - static SWIDescription none; +SWIDescription::SWIDescription(uint32_t comment) { + chunk_offset = comment & 0b111111; + chunk_number = (comment >> 6) & 0b11111111111; + error_flag = comment & (1 << 17); + swi_group = SWIGroup((comment >> 18) & 0b11); + os_flag = (comment >> 20) & 0b1111; - (void)comment; - return none; + static std::set encountered; + + const uint32_t number = comment & uint32_t(~(1 << 17)); + switch(number) { + case 0x00: + name = "OS_WriteC"; + registers[0].type = Register::Type::Character; + break; + case 0x01: + name = "OS_WriteS"; + registers[0].type = Register::Type::FollowingString; + break; + case 0x02: + name = "OS_Write0"; + registers[0].type = Register::Type::PointerToString; + break; + case 0x03: + name = "OS_NewLine"; + break; + case 0x04: + name = "OS_ReadC"; + break; + case 0x05: + name = "OS_CLI"; + registers[0].type = Register::Type::PointerToString; + break; + case 0x06: + name = "OS_Byte"; + registers[0].type = Register::Type::ReasonCode; + registers[1].type = Register::Type::ReasonCodeDependent; + registers[2].type = Register::Type::ReasonCodeDependent; + break; + case 0x07: + name = "OS_Word"; + registers[0].type = Register::Type::ReasonCode; + registers[1].type = Register::Type::Pointer; + break; + case 0x08: + name = "OS_File"; + registers[0].type = Register::Type::ReasonCode; + break; + case 0x09: + name = "OS_Args"; + registers[0].type = Register::Type::ReasonCode; + registers[1].type = Register::Type::Pointer; + registers[2].type = Register::Type::ReasonCodeDependent; + break; + case 0x0c: + name = "OS_GBPB"; + registers[0].type = Register::Type::ReasonCode; + break; + case 0x0d: + name = "OS_Find"; + registers[0].type = Register::Type::ReasonCode; + break; + case 0x0f: + name = "OS_Control"; + registers[0].type = Register::Type::Pointer; + registers[1].type = Register::Type::Pointer; + registers[2].type = Register::Type::Pointer; + registers[3].type = Register::Type::Pointer; + break; + case 0x1d: + name = "OS_Heap"; + registers[0].type = Register::Type::ReasonCode; + registers[1].type = Register::Type::Pointer; + registers[2].type = Register::Type::Pointer; + registers[3].type = Register::Type::ReasonCodeDependent; + break; + case 0x3a: + name = "OS_ValidateAddress"; + registers[0].type = Register::Type::Pointer; + registers[1].type = Register::Type::Pointer; + break; + + case 0x400e2: + name = "Wimp_PlotIcon"; + registers[1].type = Register::Type::Pointer; + break; + + + default: + if(encountered.find(number) == encountered.end()) { + encountered.insert(number); + printf("SWI: %08x\n", number); + } + break; + } } -// 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( @@ -118,19 +167,6 @@ const SWIDescription &Analyser::Static::Acorn::describe_swi(uint32_t comment) { 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; @@ -186,45 +222,5 @@ const SWIDescription &Analyser::Static::Acorn::describe_swi(uint32_t comment) { break; } - if(pointer) { - while(true) { - uint8_t next; - executor.bus.template read(pointer, next, InstructionSet::ARM::Mode::Supervisor, false); - ++pointer; - - if(next < 32) break; - swis.back().value_name.push_back(static_cast(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(); + }*/ diff --git a/Analyser/Static/Acorn/SWIIndex.hpp b/Analyser/Static/Acorn/SWIIndex.hpp index 524d6a16e..06b865539 100644 --- a/Analyser/Static/Acorn/SWIIndex.hpp +++ b/Analyser/Static/Acorn/SWIIndex.hpp @@ -9,15 +9,44 @@ #ifndef SWIIndex_hpp #define SWIIndex_hpp +#include #include +#include namespace Analyser::Static::Acorn { -struct SWIDescription { - +enum class SWIGroup: uint8_t { + OperatingSystem = 0b00, + OperatingSystemModules = 0b01, + ThirdPartyApplications = 0b10, + UserApplications = 0b11, }; -const SWIDescription &describe_swi(uint32_t comment); +struct SWIDescription { + SWIDescription(uint32_t comment); + + uint8_t chunk_offset; + SWIGroup swi_group; + uint16_t chunk_number; + uint8_t os_flag; + bool error_flag; + + std::string name; + struct Register { + enum class Type { + Unused, + ReasonCode, + Pointer, + PointerToString, + ReasonCodeDependent, + Character, + + /// A string that appears immediately after the SWI in memory. + FollowingString, + } type = Type::Unused; + }; + std::array registers; +}; } diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 58a538d65..b979d3cde 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -34,6 +34,8 @@ #include #include +#include "../../../Analyser/Static/Acorn/SWIIndex.hpp" + namespace Archimedes { class ConcreteMachine: @@ -149,13 +151,72 @@ 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: { + [[maybe_unused]] Analyser::Static::Acorn::SWIDescription description(comment); + + // 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; + executor_.bus.read(address, next, false); + if(next < 0x20) break; + desc.push_back(static_cast(next)); + ++address; + } + return desc; + }; + + switch(comment & static_cast(~(1 << 17))) { + case 0x400d4: { + uint32_t address = executor_.registers()[1] + 28; + + printf("Menu:\n"); + 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)); + printf("%s\n", desc.c_str()); + address += 24; + if(item_flags & (1 << 7)) break; + } + } break; + +// case 0x400c2: + case 0x400e2: { + // Wimp_PlotIcon; try to determine what's on-screen next. + const uint32_t address = executor_.registers()[1]; + uint32_t x1, y1, x2, y2, flags; + 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); + executor_.bus.read(address + 16, flags, false); + + std::string desc; + if(flags & 1) { + desc = get_string(address + 20, flags & (1 << 8)); + } + + printf("Wimp_PlotIcon: %d, %d -> %d, %d; flags %08x; icon data: %s\n", x1, y1, x2, y2, flags, desc.c_str()); + } break; + } + } return true; case SWISubversion::DataAbort: // executor_.set_pc(executor_.pc() - 4); From c82517c9fd9dd73197de4dde9bf1d8aa27f85f50 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 11 May 2024 13:07:12 -0400 Subject: [PATCH 04/25] Add mouse position getter. --- .../Acorn/Archimedes/MemoryController.hpp | 3 +- Machines/Acorn/Archimedes/Video.hpp | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Machines/Acorn/Archimedes/MemoryController.hpp b/Machines/Acorn/Archimedes/MemoryController.hpp index 2e43da591..3a1614a0d 100644 --- a/Machines/Acorn/Archimedes/MemoryController.hpp +++ b/Machines/Acorn/Archimedes/MemoryController.hpp @@ -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; diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index 607f82c0d..282a38836 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -15,6 +15,7 @@ #include #include #include +#include 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> 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( + horizontal_timing_.cursor_start + 6 - (horizontal_start * 2), + vertical_timing_.cursor_start - vertical_timing_.display_start); + } + private: Log::Logger 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(depth)] && position < display_end + output_latencies[static_cast(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(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(depth)]; + } static constexpr uint8_t SyncEnded = 0x1; static constexpr uint8_t BorderStarted = 0x2; From 18ffb9294f0a63954ae13e868b41e8c3fbd57532 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 12 May 2024 22:16:29 -0400 Subject: [PATCH 05/25] Add full cursor automation. --- Analyser/Static/Acorn/SWIIndex.cpp | 226 ------------------ Analyser/Static/Acorn/SWIIndex.hpp | 53 ---- Machines/Acorn/Archimedes/Archimedes.cpp | 140 ++++++++++- Machines/Acorn/Archimedes/Video.hpp | 6 +- .../Clock Signal.xcodeproj/project.pbxproj | 8 - 5 files changed, 141 insertions(+), 292 deletions(-) delete mode 100644 Analyser/Static/Acorn/SWIIndex.cpp delete mode 100644 Analyser/Static/Acorn/SWIIndex.hpp diff --git a/Analyser/Static/Acorn/SWIIndex.cpp b/Analyser/Static/Acorn/SWIIndex.cpp deleted file mode 100644 index 7fc085882..000000000 --- a/Analyser/Static/Acorn/SWIIndex.cpp +++ /dev/null @@ -1,226 +0,0 @@ -// -// SWIIndex.cpp -// Clock Signal -// -// Created by Thomas Harte on 05/05/2024. -// Copyright © 2024 Thomas Harte. All rights reserved. -// - -#include "SWIIndex.hpp" - -#include -#include - -using namespace Analyser::Static::Acorn; - -SWIDescription::SWIDescription(uint32_t comment) { - chunk_offset = comment & 0b111111; - chunk_number = (comment >> 6) & 0b11111111111; - error_flag = comment & (1 << 17); - swi_group = SWIGroup((comment >> 18) & 0b11); - os_flag = (comment >> 20) & 0b1111; - - static std::set encountered; - - const uint32_t number = comment & uint32_t(~(1 << 17)); - switch(number) { - case 0x00: - name = "OS_WriteC"; - registers[0].type = Register::Type::Character; - break; - case 0x01: - name = "OS_WriteS"; - registers[0].type = Register::Type::FollowingString; - break; - case 0x02: - name = "OS_Write0"; - registers[0].type = Register::Type::PointerToString; - break; - case 0x03: - name = "OS_NewLine"; - break; - case 0x04: - name = "OS_ReadC"; - break; - case 0x05: - name = "OS_CLI"; - registers[0].type = Register::Type::PointerToString; - break; - case 0x06: - name = "OS_Byte"; - registers[0].type = Register::Type::ReasonCode; - registers[1].type = Register::Type::ReasonCodeDependent; - registers[2].type = Register::Type::ReasonCodeDependent; - break; - case 0x07: - name = "OS_Word"; - registers[0].type = Register::Type::ReasonCode; - registers[1].type = Register::Type::Pointer; - break; - case 0x08: - name = "OS_File"; - registers[0].type = Register::Type::ReasonCode; - break; - case 0x09: - name = "OS_Args"; - registers[0].type = Register::Type::ReasonCode; - registers[1].type = Register::Type::Pointer; - registers[2].type = Register::Type::ReasonCodeDependent; - break; - case 0x0c: - name = "OS_GBPB"; - registers[0].type = Register::Type::ReasonCode; - break; - case 0x0d: - name = "OS_Find"; - registers[0].type = Register::Type::ReasonCode; - break; - case 0x0f: - name = "OS_Control"; - registers[0].type = Register::Type::Pointer; - registers[1].type = Register::Type::Pointer; - registers[2].type = Register::Type::Pointer; - registers[3].type = Register::Type::Pointer; - break; - case 0x1d: - name = "OS_Heap"; - registers[0].type = Register::Type::ReasonCode; - registers[1].type = Register::Type::Pointer; - registers[2].type = Register::Type::Pointer; - registers[3].type = Register::Type::ReasonCodeDependent; - break; - case 0x3a: - name = "OS_ValidateAddress"; - registers[0].type = Register::Type::Pointer; - registers[1].type = Register::Type::Pointer; - break; - - case 0x400e2: - name = "Wimp_PlotIcon"; - registers[1].type = Register::Type::Pointer; - break; - - - default: - if(encountered.find(number) == encountered.end()) { - encountered.insert(number); - printf("SWI: %08x\n", number); - } - break; - } -} - - - - // 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 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; - } - - - }*/ diff --git a/Analyser/Static/Acorn/SWIIndex.hpp b/Analyser/Static/Acorn/SWIIndex.hpp deleted file mode 100644 index 06b865539..000000000 --- a/Analyser/Static/Acorn/SWIIndex.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// SWIIndex.hpp -// Clock Signal -// -// Created by Thomas Harte on 05/05/2024. -// Copyright © 2024 Thomas Harte. All rights reserved. -// - -#ifndef SWIIndex_hpp -#define SWIIndex_hpp - -#include -#include -#include - -namespace Analyser::Static::Acorn { - -enum class SWIGroup: uint8_t { - OperatingSystem = 0b00, - OperatingSystemModules = 0b01, - ThirdPartyApplications = 0b10, - UserApplications = 0b11, -}; - -struct SWIDescription { - SWIDescription(uint32_t comment); - - uint8_t chunk_offset; - SWIGroup swi_group; - uint16_t chunk_number; - uint8_t os_flag; - bool error_flag; - - std::string name; - struct Register { - enum class Type { - Unused, - ReasonCode, - Pointer, - PointerToString, - ReasonCodeDependent, - Character, - - /// A string that appears immediately after the SWI in memory. - FollowingString, - } type = Type::Unused; - }; - std::array registers; -}; - -} - -#endif /* SWIIndex_hpp */ diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index b979d3cde..5b6d982d4 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -124,6 +124,10 @@ class ConcreteMachine: executor_.bus.set_rom(roms.find(risc_os)->second); insert_media(target.media); + if(!target.media.disks.empty()) { + autoload_phase_ = AutoloadPhase::WaitingForStartup; + } + fill_pipeline(0); } @@ -197,7 +201,31 @@ class ConcreteMachine: } } break; -// case 0x400c2: + 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); + + printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2); + } break; + + case 0x400c2: + if(autoload_phase_ == AutoloadPhase::WaitingForStartup) { + // Wait a further second, mouse down to (32, 240), left click. + // That'll trigger disk access. + cursor_actions_.push_back(CursorAction::wait(24'000'000)); + cursor_actions_.push_back(CursorAction::move_to(32, 240)); + cursor_actions_.push_back(CursorAction::button(0, true)); + cursor_actions_.push_back(CursorAction::wait(12'000'000)); + cursor_actions_.push_back(CursorAction::button(0, false)); + autoload_phase_ = AutoloadPhase::OpeningDisk; + } + + printf("!!"); + [[fallthrough]]; case 0x400e2: { // Wimp_PlotIcon; try to determine what's on-screen next. const uint32_t address = executor_.registers()[1]; @@ -265,6 +293,64 @@ class ConcreteMachine: const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10; #endif + // + // Mouse scripting. + // + 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 = 8; + + 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_ += cycles.as(); + 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; + } + } + + // + // Execution proper. + // + // TODO: divide up the following if necessary to put scripted mouse actions + // at predictably-regular steps. if(use_original_speed) run_for(cycles); else run_for(cycles); } @@ -280,7 +366,6 @@ class ConcreteMachine: case 4: macro_tick<4, original_speed>(); break; case 6: macro_tick<6, original_speed>(); break; } - } } @@ -403,6 +488,57 @@ class ConcreteMachine: SWISubversion latched_subversion_; } pipeline_; + + struct CursorAction { + enum class Type { + MoveTo, + Button, + Wait, + } type; + + union { + struct { + int x, y; + } move_to; + struct { + int duration; + } wait; + struct { + int button; + bool down; + } button; + } 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; + } + }; + std::vector cursor_actions_; + int cursor_action_waited_ = 0; + + enum class AutoloadPhase { + WaitingForStartup, + OpeningDisk, + Ended, + }; + AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; }; } diff --git a/Machines/Acorn/Archimedes/Video.hpp b/Machines/Acorn/Archimedes/Video.hpp index 282a38836..78e5bef0d 100644 --- a/Machines/Acorn/Archimedes/Video.hpp +++ b/Machines/Acorn/Archimedes/Video.hpp @@ -252,7 +252,7 @@ struct Video { // The following is provided for input automation; // it does not correlate with real hardware functionality. // - std::optional> cursor_location() { + std::optional> cursor_location() { if( !dma_enabled_ || vertical_timing_.cursor_end <= vertical_timing_.cursor_start || @@ -264,8 +264,8 @@ struct Video { const auto horizontal_start = horizontal_timing_.display_start + horizontal_state_.output_latency(colour_depth_); return std::make_pair( - horizontal_timing_.cursor_start + 6 - (horizontal_start * 2), - vertical_timing_.cursor_start - vertical_timing_.display_start); + int(horizontal_timing_.cursor_start) + 6 - int(horizontal_start * 2), + int(vertical_timing_.cursor_start) - int(vertical_timing_.display_start)); } private: diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index c535f85e5..4309b3eb1 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -971,8 +971,6 @@ 4BB505862B9634F30031C43C /* Archimedes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB505842B9634F30031C43C /* Archimedes.cpp */; }; 4BB505872B9634F30031C43C /* Archimedes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB505842B9634F30031C43C /* Archimedes.cpp */; }; 4BB505892B9C0E6F0031C43C /* Messy ARM in Resources */ = {isa = PBXBuildFile; fileRef = 4BB505882B9C0E6F0031C43C /* Messy ARM */; }; - 4BB508672BE816E8000ACC9F /* SWIIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */; }; - 4BB508682BE816E8000ACC9F /* SWIIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */; }; 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; @@ -2124,8 +2122,6 @@ 4BB505842B9634F30031C43C /* Archimedes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Archimedes.cpp; sourceTree = ""; }; 4BB505852B9634F30031C43C /* Archimedes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Archimedes.hpp; sourceTree = ""; }; 4BB505882B9C0E6F0031C43C /* Messy ARM */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Messy ARM"; sourceTree = ""; }; - 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SWIIndex.cpp; sourceTree = ""; }; - 4BB508662BE816E8000ACC9F /* SWIIndex.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SWIIndex.hpp; sourceTree = ""; }; 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = ""; }; 4BB5B996281B1E3F00522DA9 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; 4BB5B997281B1F7B00522DA9 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = ""; }; @@ -3689,8 +3685,6 @@ 4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */, 4B8944EF201967B4007DE474 /* Tape.hpp */, 4BE32313205327D7006EF799 /* Target.hpp */, - 4BB508652BE816E8000ACC9F /* SWIIndex.cpp */, - 4BB508662BE816E8000ACC9F /* SWIIndex.hpp */, ); path = Acorn; sourceTree = ""; @@ -5933,7 +5927,6 @@ 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, 4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */, 4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */, - 4BB508682BE816E8000ACC9F /* SWIIndex.cpp in Sources */, 4B2E86C925D892EF0024F1E9 /* DAT.cpp in Sources */, 4B6AAEAE230E40250078E864 /* Target.cpp in Sources */, 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */, @@ -6300,7 +6293,6 @@ 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */, 4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */, 4B894526201967B4007DE474 /* StaticAnalyser.cpp in Sources */, - 4BB508672BE816E8000ACC9F /* SWIIndex.cpp in Sources */, 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */, 4B051CB62680158600CA44E8 /* EXDos.cpp in Sources */, 4BB505782B962DDF0031C43C /* SoundGenerator.cpp in Sources */, From 0b11fc259b30662fba7eb92b6a551027cfcf5a31 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 13 May 2024 21:42:38 -0400 Subject: [PATCH 06/25] Add Archimedes-specific target class. --- Analyser/Static/Acorn/StaticAnalyser.cpp | 4 ++-- Analyser/Static/Acorn/Target.hpp | 11 +++++++++-- Machines/Acorn/Archimedes/Archimedes.cpp | 11 +++++------ Machines/Acorn/Electron/Electron.cpp | 4 ++-- Machines/Utility/MachineForTarget.cpp | 3 ++- .../Machine/StaticAnalyser/CSStaticAnalyser.mm | 6 ++---- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 1aaa149c0..a82a8e1cf 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -60,8 +60,8 @@ static std::vector> } Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { - auto target8bit = std::make_unique(); - auto targetArchimedes = std::make_unique(Machine::Archimedes); + auto target8bit = std::make_unique(); + auto targetArchimedes = std::make_unique(); // Copy appropriate cartridges to the 8-bit target. target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges); diff --git a/Analyser/Static/Acorn/Target.hpp b/Analyser/Static/Acorn/Target.hpp index 47aea1ca9..2f24c69d6 100644 --- a/Analyser/Static/Acorn/Target.hpp +++ b/Analyser/Static/Acorn/Target.hpp @@ -14,7 +14,7 @@ namespace Analyser::Static::Acorn { -struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { +struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::StructImpl { 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,11 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< } }; +struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl { + bool should_shift_restart = false; + std::string main_program; + + ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {} +}; + } diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 5b6d982d4..4e8e29490 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -29,13 +29,13 @@ #include "../../../Outputs/Log.hpp" #include "../../../Components/I2C/I2C.hpp" +#include "../../../Analyser/Static/Acorn/Target.hpp" + #include #include #include #include -#include "../../../Analyser/Static/Acorn/SWIIndex.hpp" - namespace Archimedes { class ConcreteMachine: @@ -109,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); @@ -161,8 +161,6 @@ class ConcreteMachine: switch(pipeline_.swi_subversion()) { case Pipeline::SWISubversion::None: { - [[maybe_unused]] Analyser::Static::Acorn::SWIDescription description(comment); - // 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. @@ -546,5 +544,6 @@ class ConcreteMachine: using namespace Archimedes; std::unique_ptr Machine::Archimedes(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { - return std::make_unique(*target, rom_fetcher); + const auto archimedes_target = dynamic_cast(target); + return std::make_unique(*archimedes_target, rom_fetcher); } diff --git a/Machines/Acorn/Electron/Electron.cpp b/Machines/Acorn/Electron/Electron.cpp index c7494b4c9..511fbe7a6 100644 --- a/Machines/Acorn/Electron/Electron.cpp +++ b/Machines/Acorn/Electron/Electron.cpp @@ -51,7 +51,7 @@ template 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 class ConcreteMachine: using namespace Electron; std::unique_ptr 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(target); if(acorn_target->media.mass_storage_devices.empty()) { diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 98851fd15..390d0f84a 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -252,7 +252,8 @@ std::map> Machine::Target Add(AppleIIgs); options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Target(Analyser::Machine::Archimedes))); Add(AtariST); - AddMapped(Electron, Acorn); + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), new Analyser::Static::Acorn::ElectronTarget)); + options.emplace(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Acorn::ArchimedesTarget); Add(Enterprise); Add(Macintosh); Add(MSX); diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 2d56a0b68..8148c8080 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -136,11 +136,10 @@ return self; } - - (instancetype)initWithArchimedesModel:(CSMachineArchimedesModel)model { self = [super init]; if(self) { - auto target = std::make_unique(Analyser::Machine::Archimedes); + auto target = std::make_unique(); _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(); + auto target = std::make_unique(); target->has_dfs = dfs; target->has_pres_adfs = adfs; target->has_ap6_rom = ap6; From ca67afea4c154ec7ae959abb08489959ab957423 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 13 May 2024 21:46:03 -0400 Subject: [PATCH 07/25] Correct some unique-ptr oddities. --- Machines/Acorn/Archimedes/Archimedes.cpp | 5 ++++- Machines/Utility/MachineForTarget.cpp | 11 +++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 4e8e29490..25156ebc0 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -543,7 +543,10 @@ class ConcreteMachine: using namespace Archimedes; -std::unique_ptr Machine::Archimedes(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { +std::unique_ptr Machine::Archimedes( + const Analyser::Static::Target *target, + const ROMMachine::ROMFetcher &rom_fetcher +) { const auto archimedes_target = dynamic_cast(target); return std::make_unique(*archimedes_target, rom_fetcher); } diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index 390d0f84a..a4648874e 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -217,7 +217,7 @@ std::map> Machine::AllOptionsBy std::map> options; #define Emplace(machine, class) \ - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique(Configurable::OptionsType::UserFriendly))); + options.emplace(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique(Configurable::OptionsType::UserFriendly)) Emplace(AmstradCPC, AmstradCPC::Machine); Emplace(AppleII, Apple::II::Machine); @@ -243,17 +243,16 @@ std::map> Machine::Target std::map> 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()); #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()); Add(AtariST); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), new Analyser::Static::Acorn::ElectronTarget)); - options.emplace(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Acorn::ArchimedesTarget); + options.emplace(LongNameForTargetMachine(Analyser::Machine::Electron), std::make_unique()); Add(Enterprise); Add(Macintosh); Add(MSX); @@ -265,7 +264,7 @@ std::map> 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::Machine::ColecoVision)); AddMapped(MasterSystem, Sega); } From 308b3ca4481709a0c1fce4de66be2b27550b9a63 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 13 May 2024 22:25:02 -0400 Subject: [PATCH 08/25] Gamely attempt to pick an Arc program to start. --- Analyser/Static/Acorn/Disk.cpp | 5 ++-- Analyser/Static/Acorn/StaticAnalyser.cpp | 37 +++++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Analyser/Static/Acorn/Disk.cpp b/Analyser/Static/Acorn/Disk.cpp index b83eedee8..e0da241e6 100644 --- a/Analyser/Static/Acorn/Disk.cpp +++ b/Analyser/Static/Acorn/Disk.cpp @@ -145,8 +145,9 @@ std::unique_ptr 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; diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index a82a8e1cf..84a8a36a8 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -102,14 +102,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 disk = media.disks.front(); std::unique_ptr 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 +144,36 @@ 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; + + // Always try a shift-restart; it's worth a go. + targetArchimedes->should_shift_restart = true; + + // Also look for the best possible startup program name, if it can be discerned. + for(const auto &file: adfs_catalogue->files) { + // Skip files that would have been caught by shift-restart if suitable. + if(file.name == "!System" || file.name == "!Boot") continue; + + // Skip non-Pling files. + if(file.name[0] != '!') continue; + + // Take whatever else comes with a preference for things that don't + // have 'read' in them (which will tend to be read_me or read_this or similar). + constexpr char read[] = "read"; + const auto has_read = + std::search( + file.name.begin(), file.name.end(), + std::begin(read), std::end(read) - 1, // i.e. don't compare the trailing NULL. + [](char lhs, char rhs) { + return std::tolower(lhs) == rhs; + } + ) != file.name.end(); + + if(targetArchimedes->main_program.empty() || !has_read) { + targetArchimedes->main_program = file.name; + } + } } } From 1d8b33d7ae4b3b44cad6820f795cb79338c1f73d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 13 May 2024 22:26:36 -0400 Subject: [PATCH 09/25] Attempt to improve trajectory. --- Machines/Acorn/Archimedes/Archimedes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 25156ebc0..18e06df62 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -322,10 +322,10 @@ class ConcreteMachine: 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); + 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); + y_diff = (y_diff * MaxStep + (abs(x_diff) >> 1)) / abs(x_diff); x_diff = std::clamp(x_diff, -MaxStep, MaxStep); } } From ef03ddf2aeaf9a9b9a1e1a930960eb0dd05578db Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 14 May 2024 22:23:35 -0400 Subject: [PATCH 10/25] Extend to launching the target program. --- Machines/Acorn/Archimedes/Archimedes.cpp | 123 +++++++++++++++++------ 1 file changed, 92 insertions(+), 31 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 18e06df62..ad0cd51fc 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -126,6 +126,7 @@ class ConcreteMachine: if(!target.media.disks.empty()) { autoload_phase_ = AutoloadPhase::WaitingForStartup; + target_program_ = target.main_program; } fill_pipeline(0); @@ -199,47 +200,86 @@ class ConcreteMachine: } } 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); + if(autoload_phase_ == AutoloadPhase::WaitingForDiskContents) { + autoload_phase_ = AutoloadPhase::WaitingForTargetIcon; + + 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); + + // Crib top left of window content. + target_window_[0] = static_cast(x1); + target_window_[1] = static_cast(y2); + printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2); + } - printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2); } break; + // Wimp_CreateIcon, which also adds to the icon bar. case 0x400c2: + // Creation of any icon is used to spot that RISC OS has started up. if(autoload_phase_ == AutoloadPhase::WaitingForStartup) { + autoload_phase_ = AutoloadPhase::OpeningDisk; + // Wait a further second, mouse down to (32, 240), left click. - // That'll trigger disk access. + // That'll trigger disk access. Then move up to the top left, + // in anticipation of the appearance of a window. cursor_actions_.push_back(CursorAction::wait(24'000'000)); cursor_actions_.push_back(CursorAction::move_to(32, 240)); cursor_actions_.push_back(CursorAction::button(0, true)); cursor_actions_.push_back(CursorAction::wait(12'000'000)); cursor_actions_.push_back(CursorAction::button(0, false)); - autoload_phase_ = AutoloadPhase::OpeningDisk; + cursor_actions_.push_back(CursorAction::move_to(64, 32)); + cursor_actions_.push_back(CursorAction::set_phase( + target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents) + ); } - printf("!!"); - [[fallthrough]]; + // TODO: spot potential addition of extra program icon. + break; + + // Wimp_PlotIcon. case 0x400e2: { - // Wimp_PlotIcon; try to determine what's on-screen next. - const uint32_t address = executor_.registers()[1]; - uint32_t x1, y1, x2, y2, flags; - 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); - executor_.bus.read(address + 16, flags, false); + 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)); + 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(x1) + 200; + const auto y_target = target_window_[1] + static_cast(y1) + 24; + cursor_actions_.push_back(CursorAction::move_to( + x_target >> 2, + 256 - (y_target >> 2) + )); + cursor_actions_.push_back(CursorAction::button(0, true)); + cursor_actions_.push_back(CursorAction::wait(6'000'000)); + cursor_actions_.push_back(CursorAction::button(0, false)); + cursor_actions_.push_back(CursorAction::wait(6'000'000)); + cursor_actions_.push_back(CursorAction::button(0, true)); + cursor_actions_.push_back(CursorAction::wait(6'000'000)); + cursor_actions_.push_back(CursorAction::button(0, false)); + } } - - printf("Wimp_PlotIcon: %d, %d -> %d, %d; flags %08x; icon data: %s\n", x1, y1, x2, y2, flags, desc.c_str()); } break; } } return true; @@ -341,6 +381,10 @@ class ConcreteMachine: 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; } } @@ -487,11 +531,25 @@ class ConcreteMachine: SWISubversion latched_subversion_; } pipeline_; + // MARK: - Autoload, including cursor scripting. + + enum class AutoloadPhase { + WaitingForStartup, + OpeningDisk, + WaitingForDiskContents, + WaitingForTargetIcon, + OpeningProgram, + Ended, + }; + AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; + std::string target_program_; + struct CursorAction { enum class Type { MoveTo, Button, Wait, + SetPhase, } type; union { @@ -505,6 +563,9 @@ class ConcreteMachine: int button; bool down; } button; + struct { + AutoloadPhase phase; + } set_phase; } value; static CursorAction move_to(int x, int y) { @@ -527,16 +588,16 @@ class ConcreteMachine: 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 cursor_actions_; int cursor_action_waited_ = 0; - - enum class AutoloadPhase { - WaitingForStartup, - OpeningDisk, - Ended, - }; - AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; + int32_t target_window_[2]; }; } From 8d0d7abd5aec7c856cce419b7718755084ff6b58 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 16 May 2024 22:18:51 -0400 Subject: [PATCH 11/25] Keep track of state separately from scanning. --- Machines/Acorn/Archimedes/Keyboard.hpp | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Machines/Acorn/Archimedes/Keyboard.hpp b/Machines/Acorn/Archimedes/Keyboard.hpp index bfbe0710b..f3783014b 100644 --- a/Machines/Acorn/Archimedes/Keyboard.hpp +++ b/Machines/Acorn/Archimedes/Keyboard.hpp @@ -20,19 +20,18 @@ struct Keyboard { Keyboard(HalfDuplexSerial &serial) : serial_(serial), mouse_(*this) {} void set_key_state(int row, int column, bool is_pressed) { + states_[row][column] = 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_[row][column] == 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(prefix | row), static_cast(prefix | column)); + enqueue_key_event(row, column, is_pressed); consider_dequeue(); } @@ -141,7 +140,17 @@ 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. + for(int row = 0; row < 16 && event_queue_.empty(); row++) { + for(int column = 0; column < 16 && event_queue_.empty(); column++) { + if(posted_states_[row][column] != states_[row][column]) { + enqueue_key_event(row, column, states_[row][column]); + } + } + } + + // 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); @@ -169,6 +178,7 @@ private: Log::Logger logger_; bool states_[16][16]{}; + bool posted_states_[16][16]{}; bool scan_keyboard_ = false; bool scan_mouse_ = false; @@ -196,6 +206,12 @@ private: event_queue_.erase(event_queue_.begin()); return true; } + void enqueue_key_event(int row, int column, bool is_pressed) { + logger_.info().append("Posting row %d, column %d is now %s", row, column, is_pressed ? "pressed" : "released"); + posted_states_[row][column] = is_pressed; + const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000; + enqueue(static_cast(prefix | row), static_cast(prefix | column)); + } static constexpr uint8_t HRST = 0b1111'1111; // Keyboard reset. static constexpr uint8_t RAK1 = 0b1111'1110; // Reset response #1. From 26375dc023882c0dab14668df0bb9e3bc7908808 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 16 May 2024 22:19:26 -0400 Subject: [PATCH 12/25] Introduce named constants. --- Machines/Acorn/Archimedes/KeyboardMapper.hpp | 247 +++++++++++-------- 1 file changed, 142 insertions(+), 105 deletions(-) diff --git a/Machines/Acorn/Archimedes/KeyboardMapper.hpp b/Machines/Acorn/Archimedes/KeyboardMapper.hpp index 416ece362..eeeab129d 100644 --- a/Machines/Acorn/Archimedes/KeyboardMapper.hpp +++ b/Machines/Acorn/Archimedes/KeyboardMapper.hpp @@ -12,12 +12,49 @@ namespace Archimedes { +static constexpr uint16_t map(int row, int column) { + return static_cast((row << 4) | column); +} + +/// Named key codes that the machine wlll accept directly. +enum Key: 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, 14), + + 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), +}; + +/// 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((row << 4) | column); - } - static constexpr int row(uint16_t key) { return key >> 4; } @@ -30,115 +67,115 @@ class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper 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; } From 3be5d60b1e696cec40d7ba0a256e163fc5322c01 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 18 May 2024 22:16:58 -0400 Subject: [PATCH 13/25] Eliminate comparison costs. --- Analyser/Static/Acorn/StaticAnalyser.cpp | 3 - Analyser/Static/Acorn/Target.hpp | 1 - Machines/Acorn/Archimedes/Archimedes.cpp | 15 +-- Machines/Acorn/Archimedes/Keyboard.hpp | 110 ++++++++++++++++--- Machines/Acorn/Archimedes/KeyboardMapper.hpp | 49 +-------- 5 files changed, 103 insertions(+), 75 deletions(-) diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 84a8a36a8..846503221 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -147,9 +147,6 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me // Archimedes options, implicitly: ADFS, non-Hugo. targetArchimedes->media.disks = media.disks; - // Always try a shift-restart; it's worth a go. - targetArchimedes->should_shift_restart = true; - // Also look for the best possible startup program name, if it can be discerned. for(const auto &file: adfs_catalogue->files) { // Skip files that would have been caught by shift-restart if suitable. diff --git a/Analyser/Static/Acorn/Target.hpp b/Analyser/Static/Acorn/Target.hpp index 2f24c69d6..bcb63b217 100644 --- a/Analyser/Static/Acorn/Target.hpp +++ b/Analyser/Static/Acorn/Target.hpp @@ -35,7 +35,6 @@ struct ElectronTarget: public ::Analyser::Static::Target, public Reflection::Str }; struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl { - bool should_shift_restart = false; std::string main_program; ArchimedesTarget() : Analyser::Static::Target(Machine::Archimedes) {} diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index ad0cd51fc..f6b94c7b5 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -127,6 +127,8 @@ class ConcreteMachine: 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); @@ -234,10 +236,10 @@ class ConcreteMachine: cursor_actions_.push_back(CursorAction::button(0, true)); cursor_actions_.push_back(CursorAction::wait(12'000'000)); cursor_actions_.push_back(CursorAction::button(0, false)); - cursor_actions_.push_back(CursorAction::move_to(64, 32)); cursor_actions_.push_back(CursorAction::set_phase( target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents) ); + cursor_actions_.push_back(CursorAction::move_to(64, 36)); } // TODO: spot potential addition of extra program icon. @@ -255,6 +257,7 @@ class ConcreteMachine: desc = get_string(address + 20, flags & (1 << 8)); } + printf("%s == %s?\n", desc.c_str(), target_program_.c_str()); if(desc == target_program_) { uint32_t x1, y1, x2, y2; executor_.bus.read(address + 0, x1, false); @@ -265,10 +268,10 @@ class ConcreteMachine: autoload_phase_ = AutoloadPhase::OpeningProgram; // Some default icon sizing assumptions are baked in here. - const auto x_target = target_window_[0] + static_cast(x1) + 200; + const auto x_target = target_window_[0] + (static_cast(x1) + static_cast(x2)) / 2; const auto y_target = target_window_[1] + static_cast(y1) + 24; cursor_actions_.push_back(CursorAction::move_to( - x_target >> 2, + x_target >> 1, 256 - (y_target >> 2) )); cursor_actions_.push_back(CursorAction::button(0, true)); @@ -346,7 +349,7 @@ class ConcreteMachine: // A measure of where within the tip lies within // the default RISC OS cursor. constexpr int ActionPointOffset = 20; - constexpr int MaxStep = 8; + constexpr int MaxStep = 24; const auto position = executor_.bus.video().cursor_location(); if(!position) break; @@ -449,9 +452,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. diff --git a/Machines/Acorn/Archimedes/Keyboard.hpp b/Machines/Acorn/Archimedes/Keyboard.hpp index f3783014b..617c87a65 100644 --- a/Machines/Acorn/Archimedes/Keyboard.hpp +++ b/Machines/Acorn/Archimedes/Keyboard.hpp @@ -13,14 +13,70 @@ #include "../../../Outputs/Log.hpp" #include "../../../Inputs/Mouse.hpp" +#include + namespace Archimedes { +namespace { +constexpr uint16_t map(int row, int column) { + return static_cast((row << 4) | column); +} + +constexpr uint8_t row(uint16_t key) { + return static_cast(key >> 4); +} + +constexpr uint8_t column(uint16_t key) { + return static_cast(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) { - states_[row][column] = 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"); @@ -28,10 +84,20 @@ struct Keyboard { } // Don't waste bandwidth on repeating facts. - if(posted_states_[row][column] == is_pressed) return; + if(posted_states_[key] == is_pressed) return; // Post new key event. - enqueue_key_event(row, column, is_pressed); + 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(); } @@ -79,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) { @@ -142,10 +214,13 @@ struct Keyboard { if(state_ == State::Idle) { // If the key event queue is empty but keyboard scanning is enabled, check for // any disparity between posted keys states and actuals. - for(int row = 0; row < 16 && event_queue_.empty(); row++) { - for(int column = 0; column < 16 && event_queue_.empty(); column++) { - if(posted_states_[row][column] != states_[row][column]) { - enqueue_key_event(row, column, states_[row][column]); + 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(key), states_[key]); + } } } } @@ -177,8 +252,9 @@ private: HalfDuplexSerial &serial_; Log::Logger logger_; - bool states_[16][16]{}; - bool posted_states_[16][16]{}; + std::bitset states_; + std::bitset posted_states_; + bool needs_state_check_ = false; bool scan_keyboard_ = false; bool scan_mouse_ = false; @@ -206,9 +282,12 @@ private: event_queue_.erase(event_queue_.begin()); return true; } - void enqueue_key_event(int row, int column, bool is_pressed) { + 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"); - posted_states_[row][column] = is_pressed; const uint8_t prefix = is_pressed ? 0b1100'0000 : 0b1101'0000; enqueue(static_cast(prefix | row), static_cast(prefix | column)); } @@ -227,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) {} @@ -241,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(index), is_pressed); } private: diff --git a/Machines/Acorn/Archimedes/KeyboardMapper.hpp b/Machines/Acorn/Archimedes/KeyboardMapper.hpp index eeeab129d..2bec57838 100644 --- a/Machines/Acorn/Archimedes/KeyboardMapper.hpp +++ b/Machines/Acorn/Archimedes/KeyboardMapper.hpp @@ -9,60 +9,13 @@ #pragma once #include "../../KeyboardMachine.hpp" +#include "Keyboard.hpp" namespace Archimedes { -static constexpr uint16_t map(int row, int column) { - return static_cast((row << 4) | column); -} - -/// Named key codes that the machine wlll accept directly. -enum Key: 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, 14), - - 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), -}; - /// 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 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; From 6d6dfa4f44a47e196b70f2ed09a4f7dcd281aa72 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 May 2024 22:48:20 -0400 Subject: [PATCH 14/25] Introduce Archimedes analyser tests. --- .../Clock Signal.xcodeproj/project.pbxproj | 4 ++ .../ArchimedesStaticAnalyserTests.mm | 64 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 4309b3eb1..1f8ca8671 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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,7 @@ 4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = ""; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8255.hpp; sourceTree = ""; }; 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatrikRakTests.swift; sourceTree = ""; }; + 4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArchimedesStaticAnalyserTests.mm; sourceTree = ""; }; 4BDA00D922E60EE300AC3CD0 /* ROMRequester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ROMRequester.xib; sourceTree = ""; }; 4BDA00DE22E644AF00AC3CD0 /* CSROMReceiverView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSROMReceiverView.h; sourceTree = ""; }; 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = ""; }; @@ -4546,6 +4548,7 @@ 4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */, 4BD388872239E198002D14B5 /* 68000Tests.mm */, 4BF7019F26FFD32300996424 /* AmigaBlitterTests.mm */, + 4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */, 4B2005422B804D6400420C5C /* ARMDecoderTests.mm */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */, @@ -6383,6 +6386,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 */, diff --git a/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm new file mode 100644 index 000000000..78cf5b9f1 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm @@ -0,0 +1,64 @@ +// +// ArchimedesStaticAnalyserTests.m +// Clock Signal +// +// Created by Thomas Harte on 20/05/2024. +// Copyright 2024 Thomas Harte. All rights reserved. +// + +#import + +#import +#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 *mainProgramsBySHA1 = @{ + @"371b30787a782cb1fe6cb6ad2217a832a06e1e96": @"!TimeZone", + @"3459adef724e2cd6f3681050a9ce47394231b4f9": @"!Talisman", + @"3022e18d47ed0fc14b09c18caff3fc0ac1f4edff": @"!StarTrade", + @"252bfde8d602fe171e0657fa3f9dfeba1803e6eb": @"!Blowpipe", + @"e3c32b8cbd3cd31cbca93e5a45b94e7f8058b8f7": @"Zelanites.!Zelanites", + @"2e1cb15cde588e22f50518b6ffa47a8df89b14c0": @"!Fire_Ice", + @"069592c0b90a0b9112daf014b7e19b4a51f9653b": @"!UIM", + @"14c3785b3bc3f7e2d4a81e92ff06e11656e6b76c": @"!UIM", + + // Various things that are not the first disk. + @"2cff99237837e2291b845eb63977362ad9b4f040": @"", + @"3615bcb8a953fbba3d56a956243341a022208101": @"", +}; +#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; + } + + // Test equality. + auto *const target = dynamic_cast(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 From 6d769c9e89802d588e54af76b2b7c9a2b916c149 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 May 2024 21:49:30 -0400 Subject: [PATCH 15/25] Use string similarity as a program differentiator. --- Analyser/Static/Acorn/Disk.cpp | 9 +++ Analyser/Static/Acorn/StaticAnalyser.cpp | 16 +++-- Numeric/StringSimilarity.hpp | 65 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 2 + 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 Numeric/StringSimilarity.hpp diff --git a/Analyser/Static/Acorn/Disk.cpp b/Analyser/Static/Acorn/Disk.cpp index e0da241e6..e2c392d53 100644 --- a/Analyser/Static/Acorn/Disk.cpp +++ b/Analyser/Static/Acorn/Disk.cpp @@ -201,5 +201,14 @@ std::unique_ptr Analyser::Static::Acorn::GetADFSCatalogue(const std:: catalogue->files.push_back(std::move(new_file)); } + // Include the directory title. + const uint8_t *title; + if(catalogue->has_large_sectors) { + title = &root_directory[0x7dd]; + } else { + title = &root_directory[0x4d9]; + } + catalogue->name = std::string(reinterpret_cast(title), 19); + return catalogue; } diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 846503221..612cdd083 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -12,7 +12,10 @@ #include "Tape.hpp" #include "Target.hpp" +#include "../../../Numeric/StringSimilarity.hpp" + #include +#include using namespace Analyser::Static::Acorn; @@ -148,10 +151,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me targetArchimedes->media.disks = media.disks; // Also look for the best possible startup program name, if it can be discerned. + std::map> options; for(const auto &file: adfs_catalogue->files) { - // Skip files that would have been caught by shift-restart if suitable. - if(file.name == "!System" || file.name == "!Boot") continue; - // Skip non-Pling files. if(file.name[0] != '!') continue; @@ -167,9 +168,12 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me } ) != file.name.end(); - if(targetArchimedes->main_program.empty() || !has_read) { - targetArchimedes->main_program = file.name; - } + const auto probability = Numeric::similarity(file.name, adfs_catalogue->name) * (has_read ? 0.5 : 1.0); + options.emplace(probability, file.name); + } + + if(!options.empty()) { + targetArchimedes->main_program = options.begin()->second; } } } diff --git a/Numeric/StringSimilarity.hpp b/Numeric/StringSimilarity.hpp new file mode 100644 index 000000000..d830bd988 --- /dev/null +++ b/Numeric/StringSimilarity.hpp @@ -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 +#include +#include + +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. +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 { + std::set result; + for(std::size_t c = 0; c < source.size() - 1; c++) { + if(isalpha(source[c]) && isalpha(source[c+1])) { + result.insert(static_cast( + (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(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(numerator * 2) / denominator; +} + +} + +#endif /* StringSimilarity_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1f8ca8671..341d4dbbf 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -2268,6 +2268,7 @@ 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8255.hpp; sourceTree = ""; }; 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatrikRakTests.swift; sourceTree = ""; }; 4BD971382BFC3D9C00C907AA /* ArchimedesStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArchimedesStaticAnalyserTests.mm; sourceTree = ""; }; + 4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StringSimilarity.hpp; sourceTree = ""; }; 4BDA00D922E60EE300AC3CD0 /* ROMRequester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ROMRequester.xib; sourceTree = ""; }; 4BDA00DE22E644AF00AC3CD0 /* CSROMReceiverView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSROMReceiverView.h; sourceTree = ""; }; 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = ""; }; @@ -3528,6 +3529,7 @@ 4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */, 4BFEA2F12682A90200EBF94C /* Sizes.hpp */, 4281572E2AA0334300E16AA1 /* Carry.hpp */, + 4BD9713A2BFD7E7100C907AA /* StringSimilarity.hpp */, ); name = Numeric; path = ../../Numeric; From b6b70bb7ffd5a3587c1edafd6348c9382cfea83b Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 May 2024 22:06:09 -0400 Subject: [PATCH 16/25] Add title fallbacks, ensure 'read' costs even 0.0-weight options. --- Analyser/Static/Acorn/Disk.cpp | 14 ++++++++++---- Analyser/Static/Acorn/StaticAnalyser.cpp | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Analyser/Static/Acorn/Disk.cpp b/Analyser/Static/Acorn/Disk.cpp index e2c392d53..2b38aaca4 100644 --- a/Analyser/Static/Acorn/Disk.cpp +++ b/Analyser/Static/Acorn/Disk.cpp @@ -202,13 +202,19 @@ std::unique_ptr Analyser::Static::Acorn::GetADFSCatalogue(const std:: } // Include the directory title. - const uint8_t *title; + const char *title, *name; if(catalogue->has_large_sectors) { - title = &root_directory[0x7dd]; + title = reinterpret_cast(&root_directory[0x7dd]); + name = reinterpret_cast(&root_directory[0x7f0]); } else { - title = &root_directory[0x4d9]; + title = reinterpret_cast(&root_directory[0x4d9]); + name = reinterpret_cast(&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)); } - catalogue->name = std::string(reinterpret_cast(title), 19); return catalogue; } diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 612cdd083..7d2010ec4 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -62,7 +62,7 @@ static std::vector> return acorn_cartridges; } -Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) { +Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) { auto target8bit = std::make_unique(); auto targetArchimedes = std::make_unique(); @@ -152,6 +152,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me // Also look for the best possible startup program name, if it can be discerned. std::map> options; + const std::string &disk_title = + (adfs_catalogue->name.empty() || adfs_catalogue->name == "$") ? file_name : adfs_catalogue->name; for(const auto &file: adfs_catalogue->files) { // Skip non-Pling files. if(file.name[0] != '!') continue; @@ -168,7 +170,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me } ) != file.name.end(); - const auto probability = Numeric::similarity(file.name, adfs_catalogue->name) * (has_read ? 0.5 : 1.0); + const auto probability = Numeric::similarity(file.name, disk_title) - (has_read ? 0.2 : 0.0); options.emplace(probability, file.name); } From a32da9a6e17b409817feb51c6a30f90af8df421f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 May 2024 22:12:55 -0400 Subject: [PATCH 17/25] Restore preference against !Boot. --- Analyser/Static/Acorn/StaticAnalyser.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 7d2010ec4..6edb79728 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -151,7 +151,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me targetArchimedes->media.disks = media.disks; // Also look for the best possible startup program name, if it can be discerned. - std::map> options; + std::multimap> options; const std::string &disk_title = (adfs_catalogue->name.empty() || adfs_catalogue->name == "$") ? file_name : adfs_catalogue->name; for(const auto &file: adfs_catalogue->files) { @@ -159,18 +159,23 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me if(file.name[0] != '!') continue; // Take whatever else comes with a preference for things that don't - // have 'read' in them (which will tend to be read_me or read_this or similar). + // 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"; - const auto has_read = - std::search( + constexpr char boot[] = "boot"; + const auto has = [&](const char *begin, const char *end) { + return std::search( file.name.begin(), file.name.end(), - std::begin(read), std::end(read) - 1, // i.e. don't compare the trailing NULL. + 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, disk_title) - (has_read ? 0.2 : 0.0); + const auto probability = Numeric::similarity(file.name, disk_title) - ((has_read || has_boot) ? 0.2 : 0.0); options.emplace(probability, file.name); } From 67add0da93be2f3ab4eec2cf28d7648d307520c9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 21 May 2024 22:23:53 -0400 Subject: [PATCH 18/25] Use both sources. --- Analyser/Static/Acorn/StaticAnalyser.cpp | 7 ++++--- .../ArchimedesStaticAnalyserTests.mm | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 6edb79728..6c4a830d6 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -152,8 +152,6 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me // Also look for the best possible startup program name, if it can be discerned. std::multimap> options; - const std::string &disk_title = - (adfs_catalogue->name.empty() || adfs_catalogue->name == "$") ? file_name : adfs_catalogue->name; for(const auto &file: adfs_catalogue->files) { // Skip non-Pling files. if(file.name[0] != '!') continue; @@ -175,7 +173,10 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me 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, disk_title) - ((has_read || has_boot) ? 0.2 : 0.0); + 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); } diff --git a/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm index 78cf5b9f1..d36009bf0 100644 --- a/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm +++ b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm @@ -23,10 +23,21 @@ static NSDictionary *mainProgramsBySHA1 = @{ @"2e1cb15cde588e22f50518b6ffa47a8df89b14c0": @"!Fire_Ice", @"069592c0b90a0b9112daf014b7e19b4a51f9653b": @"!UIM", @"14c3785b3bc3f7e2d4a81e92ff06e11656e6b76c": @"!UIM", + @"93b67127286d861e4df31cac27e78e623a1e852f": @"!FineRacer", + @"53f95c169bbe9cfa7252d90d6181ced31086f1a5": @"!adventure", + @"4168bb21f6df0976ce227a20f9fa4eb240289f3b": @"!BigBang", + @"8fcad522ea22b75b393ceb334cfef3f324b248ee": @"!E-TYPE", + @"8ca4289ac423d4878129cb17d6177123b321108f": @"!StrtWrite", + @"4f92efecfc1e3a510a816f570ccb7082f0154e37": @"!HeroQuest", // Various things that are not the first disk. @"2cff99237837e2291b845eb63977362ad9b4f040": @"", @"3615bcb8a953fbba3d56a956243341a022208101": @"", + @"03672244691b292d6b4816aa592b312ea6297b22": @"", + @"b7139d9bd927b8e4d933fd8aa3080a7249117495": @"", + @"66a82651f86d9cf0aa5b54c55bcaa8fefd3901da": @"", + @"c3d3cd9e28f5e7499fd70057f820c75219538c69": @"", + }; #undef Record @@ -54,6 +65,10 @@ static NSDictionary *mainProgramsBySHA1 = @{ continue; } + if(![mainProgram length]) { + continue; + } + // Test equality. auto *const target = dynamic_cast(targets.front().get()); XCTAssert(target != nullptr); From 5280f5aba20a42becd2508e5377edbc9a38e53c5 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 23 May 2024 22:03:40 -0400 Subject: [PATCH 19/25] Attempt to spot screen takeovers. --- Machines/Acorn/Archimedes/Archimedes.cpp | 15 ++++++++++++++- OSBindings/Mac/Clock SignalTests/8088Tests.mm | 2 +- .../ArchimedesStaticAnalyserTests.mm | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index f6b94c7b5..0aafe8f90 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -185,7 +185,19 @@ class ConcreteMachine: return desc; }; - switch(comment & static_cast(~(1 << 17))) { + const uint32_t swi_code = comment & static_cast(~(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: { uint32_t address = executor_.registers()[1] + 28; @@ -270,6 +282,7 @@ class ConcreteMachine: // Some default icon sizing assumptions are baked in here. const auto x_target = target_window_[0] + (static_cast(x1) + static_cast(x2)) / 2; const auto y_target = target_window_[1] + static_cast(y1) + 24; + cursor_actions_.clear(); cursor_actions_.push_back(CursorAction::move_to( x_target >> 1, 256 - (y_target >> 2) diff --git a/OSBindings/Mac/Clock SignalTests/8088Tests.mm b/OSBindings/Mac/Clock SignalTests/8088Tests.mm index ed1c4e35d..2aa017fc0 100644 --- a/OSBindings/Mac/Clock SignalTests/8088Tests.mm +++ b/OSBindings/Mac/Clock SignalTests/8088Tests.mm @@ -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; } diff --git a/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm index d36009bf0..6e87e8047 100644 --- a/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm +++ b/OSBindings/Mac/Clock SignalTests/ArchimedesStaticAnalyserTests.mm @@ -29,6 +29,11 @@ static NSDictionary *mainProgramsBySHA1 = @{ @"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": @"", @@ -37,7 +42,8 @@ static NSDictionary *mainProgramsBySHA1 = @{ @"b7139d9bd927b8e4d933fd8aa3080a7249117495": @"", @"66a82651f86d9cf0aa5b54c55bcaa8fefd3901da": @"", @"c3d3cd9e28f5e7499fd70057f820c75219538c69": @"", - + @"81bfd4ab92c538f5b15ad64bba625aac2ffb243d": @"", + @"39318695b6e64c9d7270f2b6d8213a7d4b0b0c43": @"", }; #undef Record From 778ac6e6d11a1950e6a3650906a2ba2093834b1d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 23 May 2024 22:16:40 -0400 Subject: [PATCH 20/25] Complete autoload loop. --- Machines/Acorn/Archimedes/Archimedes.cpp | 95 ++++++++++++++++-------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 0aafe8f90..3e4435a80 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -199,18 +199,35 @@ class ConcreteMachine: break; case 0x400d4: { - uint32_t address = executor_.registers()[1] + 28; + if(autoload_phase_ == AutoloadPhase::TestingMenu) { + autoload_phase_ = AutoloadPhase::Ended; - printf("Menu:\n"); - 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)); - printf("%s\n", desc.c_str()); - address += 24; - if(item_flags & (1 << 7)) break; + 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) { + cursor_actions_.push_back(CursorAction::button(1, true)); + cursor_actions_.push_back(CursorAction::wait(12'000'000)); + cursor_actions_.push_back(CursorAction::button(1, false)); + cursor_actions_.push_back(CursorAction::button(0, true)); + cursor_actions_.push_back(CursorAction::wait(12'000'000)); + cursor_actions_.push_back(CursorAction::button(0, false)); + } } } break; @@ -236,25 +253,45 @@ class ConcreteMachine: // Wimp_CreateIcon, which also adds to the icon bar. case 0x400c2: - // Creation of any icon is used to spot that RISC OS has started up. - if(autoload_phase_ == AutoloadPhase::WaitingForStartup) { - autoload_phase_ = AutoloadPhase::OpeningDisk; + 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. + cursor_actions_.push_back(CursorAction::wait(24'000'000)); + cursor_actions_.push_back(CursorAction::move_to(32, 240)); + cursor_actions_.push_back(CursorAction::button(0, true)); + cursor_actions_.push_back(CursorAction::wait(12'000'000)); + cursor_actions_.push_back(CursorAction::button(0, false)); + cursor_actions_.push_back(CursorAction::set_phase( + target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents) + ); + cursor_actions_.push_back(CursorAction::move_to(64, 36)); - // 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. - cursor_actions_.push_back(CursorAction::wait(24'000'000)); - cursor_actions_.push_back(CursorAction::move_to(32, 240)); - cursor_actions_.push_back(CursorAction::button(0, true)); - cursor_actions_.push_back(CursorAction::wait(12'000'000)); - cursor_actions_.push_back(CursorAction::button(0, false)); - cursor_actions_.push_back(CursorAction::set_phase( - target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents) - ); - cursor_actions_.push_back(CursorAction::move_to(64, 36)); + 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(handle) == -1) { + cursor_actions_.clear(); + cursor_actions_.push_back(CursorAction::move_to(536, 240)); + cursor_actions_.push_back(CursorAction::button(1, true)); + cursor_actions_.push_back(CursorAction::wait(12'000'000)); + cursor_actions_.push_back(CursorAction::button(1, false)); + + autoload_phase_ = AutoloadPhase::TestingMenu; + } + } break; + + default: break; } - - // TODO: spot potential addition of extra program icon. break; // Wimp_PlotIcon. @@ -269,7 +306,6 @@ class ConcreteMachine: desc = get_string(address + 20, flags & (1 << 8)); } - printf("%s == %s?\n", desc.c_str(), target_program_.c_str()); if(desc == target_program_) { uint32_t x1, y1, x2, y2; executor_.bus.read(address + 0, x1, false); @@ -553,6 +589,7 @@ class ConcreteMachine: WaitingForDiskContents, WaitingForTargetIcon, OpeningProgram, + TestingMenu, Ended, }; AutoloadPhase autoload_phase_ = AutoloadPhase::Ended; From 78ec9e5a6063d9c02840b09cf92075138cc4772c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 23 May 2024 22:31:43 -0400 Subject: [PATCH 21/25] Limit character range. --- Machines/Acorn/Archimedes/Archimedes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 3e4435a80..a37dd6890 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -179,7 +179,7 @@ class ConcreteMachine: uint8_t next; executor_.bus.read(address, next, false); if(next < 0x20) break; - desc.push_back(static_cast(next)); + desc.push_back(static_cast(next) & 0x7f); ++address; } return desc; From 6407ab0673cd2363dfad6e7abde97afc0a1bdd33 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 May 2024 14:32:55 -0400 Subject: [PATCH 22/25] Clean up, improve application-menu response. --- Machines/Acorn/Archimedes/Archimedes.cpp | 310 ++++++++++++++--------- 1 file changed, 196 insertions(+), 114 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index a37dd6890..3f614e47d 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -221,57 +221,83 @@ class ConcreteMachine: } if(should_left_click) { - cursor_actions_.push_back(CursorAction::button(1, true)); - cursor_actions_.push_back(CursorAction::wait(12'000'000)); - cursor_actions_.push_back(CursorAction::button(1, false)); - cursor_actions_.push_back(CursorAction::button(0, true)); - cursor_actions_.push_back(CursorAction::wait(12'000'000)); - cursor_actions_.push_back(CursorAction::button(0, false)); + // 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: { - if(autoload_phase_ == AutoloadPhase::WaitingForDiskContents) { - autoload_phase_ = AutoloadPhase::WaitingForTargetIcon; + 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); - 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_) { - // Crib top left of window content. - target_window_[0] = static_cast(x1); - target_window_[1] = static_cast(y2); - printf("Wimp_OpenWindow: %d, %d -> %d, %d\n", x1, y1, x2, y2); + default: break; + + case AutoloadPhase::WaitingForDiskContents: { + autoload_phase_ = AutoloadPhase::WaitingForTargetIcon; + + // Crib top left of window content. + target_window_[0] = static_cast(x1); + target_window_[1] = static_cast(y2); + } break; + + case AutoloadPhase::WaitingForStartup: + printf("%d %d %d %d\n", x1, y1, x2, y2); + if(static_cast(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. - cursor_actions_.push_back(CursorAction::wait(24'000'000)); - cursor_actions_.push_back(CursorAction::move_to(32, 240)); - cursor_actions_.push_back(CursorAction::button(0, true)); - cursor_actions_.push_back(CursorAction::wait(12'000'000)); - cursor_actions_.push_back(CursorAction::button(0, false)); - cursor_actions_.push_back(CursorAction::set_phase( - target_program_.empty() ? AutoloadPhase::Ended : AutoloadPhase::WaitingForDiskContents) - ); - cursor_actions_.push_back(CursorAction::move_to(64, 36)); - - autoload_phase_ = AutoloadPhase::OpeningDisk; - break; +// 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]; @@ -280,12 +306,9 @@ class ConcreteMachine: // Test whether the program has added an icon on the right. if(static_cast(handle) == -1) { - cursor_actions_.clear(); - cursor_actions_.push_back(CursorAction::move_to(536, 240)); - cursor_actions_.push_back(CursorAction::button(1, true)); - cursor_actions_.push_back(CursorAction::wait(12'000'000)); - cursor_actions_.push_back(CursorAction::button(1, false)); - + CursorActionBuilder(cursor_actions_) + .move_to(IconBarProgramX, IconBarY) + .click(1); autoload_phase_ = AutoloadPhase::TestingMenu; } } break; @@ -318,18 +341,9 @@ class ConcreteMachine: // Some default icon sizing assumptions are baked in here. const auto x_target = target_window_[0] + (static_cast(x1) + static_cast(x2)) / 2; const auto y_target = target_window_[1] + static_cast(y1) + 24; - cursor_actions_.clear(); - cursor_actions_.push_back(CursorAction::move_to( - x_target >> 1, - 256 - (y_target >> 2) - )); - cursor_actions_.push_back(CursorAction::button(0, true)); - cursor_actions_.push_back(CursorAction::wait(6'000'000)); - cursor_actions_.push_back(CursorAction::button(0, false)); - cursor_actions_.push_back(CursorAction::wait(6'000'000)); - cursor_actions_.push_back(CursorAction::button(0, true)); - cursor_actions_.push_back(CursorAction::wait(6'000'000)); - cursor_actions_.push_back(CursorAction::button(0, false)); + CursorActionBuilder(cursor_actions_) + .move_to(x_target >> 1, 256 - (y_target >> 2)) + .double_click(0); } } } break; @@ -383,70 +397,89 @@ class ConcreteMachine: const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10; #endif + const auto run = [&](Cycles cycles) { + if(use_original_speed) run_for(cycles); + else run_for(cycles); + }; + // - // Mouse scripting. + // Short-circuit: no cursor actions means **just run**. // - 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_ += cycles.as(); - 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; - } + if(cursor_actions_.empty()) { + run(cycles); + return; } // - // Execution proper. + // Mouse scripting; tick at a minimum of frame length. // - // TODO: divide up the following if necessary to put scripted mouse actions - // at predictably-regular steps. - if(use_original_speed) run_for(cycles); - else run_for(cycles); + static constexpr int TickFrequency = 24'000'000 / 50; + cursor_action_subcycle_ += cycles; + auto segments = cursor_action_subcycle_.divide(Cycles(TickFrequency)).as(); + 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(); + 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 @@ -646,8 +679,57 @@ class ConcreteMachine: return action; } }; + std::vector cursor_actions_; - int cursor_action_waited_ = 0; + + struct CursorActionBuilder { + CursorActionBuilder(std::vector &actions) : actions_(actions) { + actions_.clear(); + } + + CursorActionBuilder &wait(int duration) { + actions_.push_back(CursorAction::wait(duration)); + return *this; + } + + CursorActionBuilder &move_to(int x, int y) { + 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(12'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 &actions_; + }; + static constexpr int IconBarY = 240; + static constexpr int IconBarProgramX = 532; + static constexpr int IconBarDriveX = 32; + + std::vector &begin(); + + Cycles cursor_action_subcycle_; + int cursor_action_waited_; int32_t target_window_[2]; }; From 7b90c36463d8405d9b824849abfb90a38acf8297 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 May 2024 15:23:45 -0400 Subject: [PATCH 23/25] Shorten clicks, ensure no lost actions. --- Machines/Acorn/Archimedes/Archimedes.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index 3f614e47d..c1132b069 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -683,9 +683,7 @@ class ConcreteMachine: std::vector cursor_actions_; struct CursorActionBuilder { - CursorActionBuilder(std::vector &actions) : actions_(actions) { - actions_.clear(); - } + CursorActionBuilder(std::vector &actions) : actions_(actions) {} CursorActionBuilder &wait(int duration) { actions_.push_back(CursorAction::wait(duration)); @@ -693,13 +691,21 @@ class ConcreteMachine: } 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(12'000'000)); + actions_.push_back(CursorAction::wait(6'000'000)); actions_.push_back(CursorAction::button(button, false)); return *this; } From 5235262855aa0694c0da2c42bf5bf513f9cdc9bb Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 24 May 2024 15:29:11 -0400 Subject: [PATCH 24/25] Add default value. --- Machines/Acorn/Archimedes/Archimedes.cpp | 2 +- .../xcshareddata/xcschemes/Clock Signal.xcscheme | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index c1132b069..2782bcec0 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -176,7 +176,7 @@ class ConcreteMachine: executor_.bus.read(address, address, false); } while(true) { - uint8_t next; + uint8_t next = 0; executor_.bus.read(address, next, false); if(next < 0x20) break; desc.push_back(static_cast(next) & 0x7f); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 474fa352e..19b54b90e 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -62,7 +62,7 @@ Date: Fri, 24 May 2024 15:39:38 -0400 Subject: [PATCH 25/25] Avoid linkage warning. --- Numeric/StringSimilarity.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Numeric/StringSimilarity.hpp b/Numeric/StringSimilarity.hpp index d830bd988..dadf33a7a 100644 --- a/Numeric/StringSimilarity.hpp +++ b/Numeric/StringSimilarity.hpp @@ -19,7 +19,7 @@ namespace Numeric { /// /// @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. -double similarity(std::string_view first, std::string_view second) { +inline double similarity(std::string_view first, std::string_view second) { if(first.size() < 2 || second.size() < 2) { return 0.0; }