mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-17 10:06:21 +00:00
Add full cursor automation.
This commit is contained in:
parent
c82517c9fd
commit
18ffb9294f
@ -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 <cstdio>
|
||||
#include <set>
|
||||
|
||||
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<uint32_t> 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;
|
||||
}
|
||||
|
||||
|
||||
}*/
|
@ -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 <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
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<Register, 14> registers;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SWIIndex_hpp */
|
@ -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<int>();
|
||||
if(cursor_action_waited_ >= action.value.wait.duration) {
|
||||
move_to_next();
|
||||
}
|
||||
break;
|
||||
case CursorAction::Type::Button:
|
||||
get_mouse().set_button_pressed(action.value.button.button, action.value.button.down);
|
||||
move_to_next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Execution proper.
|
||||
//
|
||||
// TODO: divide up the following if necessary to put scripted mouse actions
|
||||
// at predictably-regular steps.
|
||||
if(use_original_speed) run_for<true>(cycles);
|
||||
else run_for<false>(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<CursorAction> cursor_actions_;
|
||||
int cursor_action_waited_ = 0;
|
||||
|
||||
enum class AutoloadPhase {
|
||||
WaitingForStartup,
|
||||
OpeningDisk,
|
||||
Ended,
|
||||
};
|
||||
AutoloadPhase autoload_phase_ = AutoloadPhase::Ended;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ struct Video {
|
||||
// The following is provided for input automation;
|
||||
// it does not correlate with real hardware functionality.
|
||||
//
|
||||
std::optional<std::pair<uint32_t, uint32_t>> cursor_location() {
|
||||
std::optional<std::pair<int, int>> 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:
|
||||
|
@ -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 = "<group>"; };
|
||||
4BB505852B9634F30031C43C /* Archimedes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Archimedes.hpp; sourceTree = "<group>"; };
|
||||
4BB505882B9C0E6F0031C43C /* Messy ARM */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Messy ARM"; sourceTree = "<group>"; };
|
||||
4BB508652BE816E8000ACC9F /* SWIIndex.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SWIIndex.cpp; sourceTree = "<group>"; };
|
||||
4BB508662BE816E8000ACC9F /* SWIIndex.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SWIIndex.hpp; sourceTree = "<group>"; };
|
||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = "<group>"; };
|
||||
4BB5B996281B1E3F00522DA9 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = "<group>"; };
|
||||
4BB5B997281B1F7B00522DA9 /* Status.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Status.hpp; sourceTree = "<group>"; };
|
||||
@ -3689,8 +3685,6 @@
|
||||
4B8944ED201967B4007DE474 /* StaticAnalyser.hpp */,
|
||||
4B8944EF201967B4007DE474 /* Tape.hpp */,
|
||||
4BE32313205327D7006EF799 /* Target.hpp */,
|
||||
4BB508652BE816E8000ACC9F /* SWIIndex.cpp */,
|
||||
4BB508662BE816E8000ACC9F /* SWIIndex.hpp */,
|
||||
);
|
||||
path = Acorn;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
Loading…
Reference in New Issue
Block a user