procon_bypass_man 0.1.22 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.github/workflows/gitleacks.yml +12 -0
- data/.github/workflows/release.yml +1 -0
- data/.github/workflows/ruby.yml +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +5 -5
- data/docs/getting_started.md +9 -1
- data/docs/setup_raspi.md +0 -1
- data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +1 -1
- data/lib/procon_bypass_man/{commands → bypass}/bypass_command.rb +36 -24
- data/lib/procon_bypass_man/bypass/usb_hid_logger.rb +3 -0
- data/lib/procon_bypass_man/bypass.rb +40 -23
- data/lib/procon_bypass_man/commands/print_boot_message_command.rb +2 -3
- data/lib/procon_bypass_man/commands/send_error_command.rb +2 -2
- data/lib/procon_bypass_man/commands.rb +0 -3
- data/lib/procon_bypass_man/configuration.rb +22 -3
- data/lib/procon_bypass_man/device_connection/command.rb +28 -0
- data/lib/procon_bypass_man/device_connection/executor.rb +190 -0
- data/lib/procon_bypass_man/device_connection/output_report_generator.rb +42 -0
- data/lib/procon_bypass_man/device_connection/output_report_markerable.rb +28 -0
- data/lib/procon_bypass_man/device_connection/output_report_sub_command_table.rb +133 -0
- data/lib/procon_bypass_man/device_connection/output_report_watcher.rb +41 -0
- data/lib/procon_bypass_man/device_connection/pre_bypass.rb +67 -0
- data/lib/procon_bypass_man/device_connection/procon_setting_overrider.rb +78 -0
- data/lib/procon_bypass_man/device_connection/spoofing_output_report_watcher.rb +39 -0
- data/lib/procon_bypass_man/device_connection.rb +16 -0
- data/lib/procon_bypass_man/device_model.rb +17 -0
- data/lib/procon_bypass_man/io_monitor.rb +20 -1
- data/lib/procon_bypass_man/procon/macro_builder.rb +5 -3
- data/lib/procon_bypass_man/procon/suppress_rumble.rb +13 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/analog_stick_position.rb +0 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/base.rb +0 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_immutable_binary.rb +0 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_mutable_binary.rb +0 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/inbound_procon_binary.rb +5 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/processing_procon_binary.rb +0 -0
- data/lib/procon_bypass_man/procon/value_objects/binary.rb +11 -0
- data/lib/procon_bypass_man/{domains → procon/value_objects}/bypass_mode.rb +0 -0
- data/lib/procon_bypass_man/procon/value_objects/rumble_binary.rb +18 -0
- data/lib/procon_bypass_man/procon.rb +8 -0
- data/lib/procon_bypass_man/procon_display/http_request.rb +31 -0
- data/lib/procon_bypass_man/procon_display/http_response.rb +23 -0
- data/lib/procon_bypass_man/procon_display/server.rb +33 -0
- data/lib/procon_bypass_man/procon_display/server_app.rb +17 -0
- data/lib/procon_bypass_man/procon_display/status.rb +20 -0
- data/lib/procon_bypass_man/procon_display.rb +10 -0
- data/lib/procon_bypass_man/{commands → remote_pbm_action/commands}/run_remote_pbm_action_dispatch_command.rb +0 -0
- data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +7 -1
- data/lib/procon_bypass_man/remote_pbm_action.rb +1 -0
- data/lib/procon_bypass_man/runner.rb +10 -13
- data/lib/procon_bypass_man/support/cycle_sleep.rb +22 -0
- data/lib/procon_bypass_man/support/device_mouse_finder.rb +60 -0
- data/lib/procon_bypass_man/{device_procon_finder.rb → support/device_procon_finder.rb} +0 -0
- data/lib/procon_bypass_man/support/never_exit_accidentally.rb +3 -3
- data/lib/procon_bypass_man/support/safe_timeout.rb +7 -1
- data/lib/procon_bypass_man/{usb_device_controller.rb → support/usb_device_controller.rb} +9 -4
- data/lib/procon_bypass_man/support/yaml_loader.rb +12 -0
- data/lib/procon_bypass_man/version.rb +1 -1
- data/lib/procon_bypass_man/websocket/client.rb +1 -4
- data/lib/procon_bypass_man.rb +66 -51
- data/project_template/app.rb +23 -7
- data/sig/main.rbs +4 -16
- metadata +37 -16
- data/lib/procon_bypass_man/commands/connect_device_command.rb +0 -18
- data/lib/procon_bypass_man/device_connector.rb +0 -293
- data/lib/procon_bypass_man/domains.rb +0 -13
@@ -0,0 +1,190 @@
|
|
1
|
+
require "timeout"
|
2
|
+
|
3
|
+
class ProconBypassMan::DeviceConnection::Executer
|
4
|
+
class Value
|
5
|
+
attr_accessor :read_from, :values, :call_block_if_receive, :block
|
6
|
+
|
7
|
+
def initialize(values: , read_from: , call_block_if_receive: false, &block)
|
8
|
+
@values = values
|
9
|
+
@read_from = read_from
|
10
|
+
@call_block_if_receive = call_block_if_receive
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new_with_default_args
|
16
|
+
new(throw_error_if_timeout: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.execute!
|
20
|
+
s = new_with_default_args
|
21
|
+
s.add(expected_to_receive: [
|
22
|
+
["0000"],
|
23
|
+
["0000"],
|
24
|
+
["8005"],
|
25
|
+
["0000"],
|
26
|
+
], read_from: :switch)
|
27
|
+
# 1. Sends current connection status, and if the Joy-Con are connected,
|
28
|
+
s.add(expected_to_receive: [["8001"]], read_from: :switch)
|
29
|
+
s.add(expected_to_receive: [/^8101/], read_from: :procon) # <<< 81010003176d96e7a5480000000, macaddressとコントローラー番号を返す
|
30
|
+
# 2. Sends handshaking packets over UART to the Joy-Con or Pro Controller Broadcom chip. This command can only be called once per session.
|
31
|
+
s.add(expected_to_receive: [["8002"]], read_from: :switch)
|
32
|
+
s.add(expected_to_receive: [/^8102/], read_from: :procon)
|
33
|
+
# 3
|
34
|
+
s.add(expected_to_receive: [/^0100/], read_from: :switch)
|
35
|
+
s.add(expected_to_receive: [/^21/], read_from: :procon, call_block_if_receive: /^8101/) do |this|
|
36
|
+
begin
|
37
|
+
ProconBypassMan.logger.info "(start special route)"
|
38
|
+
this.blocking_read_with_timeout_from_procon # <<< 810100032dbd42e9b698000
|
39
|
+
this.write_to_procon("8002")
|
40
|
+
this.blocking_read_with_timeout_from_procon # <<< 8102
|
41
|
+
this.write_to_procon("01000000000000000000033000000000000000000000000000000000000000000000000000000000000000000000000000")
|
42
|
+
this.blocking_read_with_timeout_from_procon # <<< 21
|
43
|
+
rescue ProconBypassMan::SafeTimeout::Timeout, Timeout::Error
|
44
|
+
raise ProconBypassMan::DeviceConnection::TimeoutErrorInConditionalRoute
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# 4. Forces the Joy-Con or Pro Controller to only talk over USB HID without any timeouts. This is required for the Pro Controller to not time out and revert to Bluetooth.
|
49
|
+
s.add(expected_to_receive: [["8004"]], read_from: :switch)
|
50
|
+
s.drain_all
|
51
|
+
return [s.switch, s.procon]
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(throw_error_if_timeout: false, throw_error_if_mismatch: false)
|
55
|
+
@queue = []
|
56
|
+
@initialized_devices = false
|
57
|
+
@throw_error_if_timeout = throw_error_if_timeout
|
58
|
+
@throw_error_if_mismatch = throw_error_if_mismatch
|
59
|
+
end
|
60
|
+
|
61
|
+
def add(expected_to_receive: , read_from: , call_block_if_receive: nil, &block)
|
62
|
+
values = expected_to_receive
|
63
|
+
@queue << Value.new(values: values, read_from: read_from, call_block_if_receive: call_block_if_receive, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def drain_all
|
67
|
+
debug_log_buffer = []
|
68
|
+
unless @initialized_devices
|
69
|
+
init_devices
|
70
|
+
end
|
71
|
+
|
72
|
+
while(item = @queue.shift)
|
73
|
+
item.values.each do |value|
|
74
|
+
raw_data = nil
|
75
|
+
timer = ProconBypassMan::SafeTimeout.new
|
76
|
+
|
77
|
+
begin
|
78
|
+
timer.throw_if_timeout!
|
79
|
+
raw_data = from_device(item).read_nonblock(64)
|
80
|
+
debug_log_buffer << "read_from(#{item.read_from}): #{raw_data.unpack("H*")}"
|
81
|
+
rescue IO::EAGAINWaitReadable
|
82
|
+
# debug_log_buffer << "read_from(#{item.read_from}): IO::EAGAINWaitReadable"
|
83
|
+
retry
|
84
|
+
end
|
85
|
+
|
86
|
+
if item.call_block_if_receive
|
87
|
+
ProconBypassMan.logger.info "call block if receive: #{item.call_block_if_receive}, actual: #{raw_data.unpack("H*")} from: #{item.read_from}"
|
88
|
+
if item.call_block_if_receive =~ raw_data.unpack("H*").first
|
89
|
+
raw_data = item.block.call(self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
result =
|
94
|
+
case value
|
95
|
+
when String, Array
|
96
|
+
value == raw_data.unpack("H*")
|
97
|
+
when Regexp
|
98
|
+
value =~ raw_data.unpack("H*").first
|
99
|
+
else
|
100
|
+
raise "#{value}は知りません"
|
101
|
+
end
|
102
|
+
|
103
|
+
if result
|
104
|
+
ProconBypassMan.logger.info "OK(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
|
105
|
+
debug_log_buffer << "OK(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
|
106
|
+
else
|
107
|
+
ProconBypassMan.logger.info "NG(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
|
108
|
+
debug_log_buffer << "NG(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
|
109
|
+
raise ProconBypassMan::DeviceConnection::BytesMismatchError.new(debug_log_buffer) if @throw_error_if_mismatch
|
110
|
+
end
|
111
|
+
to_device(item).write_nonblock(raw_data)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
rescue ProconBypassMan::SafeTimeout::Timeout, Timeout::Error => e
|
115
|
+
ProconBypassMan.logger.error "timeoutになりました(#{e.message})"
|
116
|
+
compressed_buffer_text = ProconBypassMan::CompressArray.new(debug_log_buffer).compress.join("\n")
|
117
|
+
ProconBypassMan::SendErrorCommand.execute(error: compressed_buffer_text, stdout: false)
|
118
|
+
raise ProconBypassMan::SafeTimeout::Timeout if @throw_error_if_timeout
|
119
|
+
end
|
120
|
+
|
121
|
+
def from_device(item)
|
122
|
+
case item.read_from
|
123
|
+
when :switch
|
124
|
+
switch
|
125
|
+
when :procon
|
126
|
+
procon
|
127
|
+
else
|
128
|
+
raise
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# fromの対になる
|
133
|
+
def to_device(item)
|
134
|
+
case item.read_from
|
135
|
+
when :switch
|
136
|
+
procon
|
137
|
+
when :procon
|
138
|
+
switch
|
139
|
+
else
|
140
|
+
raise
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def switch
|
145
|
+
@gadget
|
146
|
+
end
|
147
|
+
|
148
|
+
def procon
|
149
|
+
@procon
|
150
|
+
end
|
151
|
+
|
152
|
+
def init_devices
|
153
|
+
if @initialized_devices
|
154
|
+
return
|
155
|
+
end
|
156
|
+
ProconBypassMan::UsbDeviceController.init
|
157
|
+
ProconBypassMan::UsbDeviceController.reset
|
158
|
+
|
159
|
+
if path = ProconBypassMan::DeviceProconFinder.find
|
160
|
+
@procon = File.open(path, "w+b")
|
161
|
+
ProconBypassMan.logger.info "proconのデバイスファイルは#{path}を使います"
|
162
|
+
else
|
163
|
+
raise(ProconBypassMan::DeviceConnection::NotFoundProconError)
|
164
|
+
end
|
165
|
+
|
166
|
+
begin
|
167
|
+
@gadget = File.open('/dev/hidg0', "w+b")
|
168
|
+
rescue Errno::ENXIO => e
|
169
|
+
# /dev/hidg0 をopenできないときがある
|
170
|
+
ProconBypassMan::SendErrorCommand.execute(error: "Errno::ENXIOが起きたのでresetします.\n #{e.full_message}", stdout: false)
|
171
|
+
ProconBypassMan::UsbDeviceController.reset
|
172
|
+
retry
|
173
|
+
end
|
174
|
+
|
175
|
+
@initialized_devices = true
|
176
|
+
end
|
177
|
+
|
178
|
+
def blocking_read_with_timeout_from_procon
|
179
|
+
Timeout.timeout(4) do
|
180
|
+
raw_data = procon.read(64)
|
181
|
+
ProconBypassMan.logger.info "<<< #{raw_data.unpack("H*").first}"
|
182
|
+
return raw_data
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def write_to_procon(data)
|
187
|
+
ProconBypassMan.logger.info ">>> #{data}"
|
188
|
+
procon.write_nonblock([data].pack("H*"))
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::OutputReportGenerator
|
2
|
+
def initialize
|
3
|
+
@counter = 0
|
4
|
+
end
|
5
|
+
|
6
|
+
# @return [String]
|
7
|
+
def generate_by_step(step)
|
8
|
+
sub_command_with_arg = ProconBypassMan::DeviceConnection::ProconSettingOverrider::ALL_SETTINGS[step]&.join or raise("Unsupport step")
|
9
|
+
raw_data = generate(sub_command_with_arg)
|
10
|
+
count_up
|
11
|
+
raw_data
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String]
|
15
|
+
def generate_by_sub_command_with_arg(sub_command_with_arg)
|
16
|
+
raw_data = generate(sub_command_with_arg)
|
17
|
+
count_up
|
18
|
+
raw_data
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def generate(sub_command_with_arg)
|
25
|
+
[
|
26
|
+
["01", counter, "00" * 8, sub_command_with_arg].join
|
27
|
+
].pack("H*")
|
28
|
+
end
|
29
|
+
|
30
|
+
def count_up
|
31
|
+
@counter = @counter + 1
|
32
|
+
if @counter >= 256
|
33
|
+
@counter = 0
|
34
|
+
else
|
35
|
+
@counter
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def counter
|
40
|
+
@counter.to_s(16).rjust(2, "0")
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ProconBypassMan::DeviceConnection::OutputReportMarkerable
|
2
|
+
OUTPUT_REPORT_FORMAT = /^01/
|
3
|
+
INPUT_REPORT_FORMAT = /^21/
|
4
|
+
|
5
|
+
# @param [String] raw_data
|
6
|
+
# @return [void]
|
7
|
+
def mark_as_send(raw_data)
|
8
|
+
data = raw_data.unpack("H*").first
|
9
|
+
case data
|
10
|
+
when OUTPUT_REPORT_FORMAT
|
11
|
+
sub_command = data[20..21]
|
12
|
+
sub_command_arg = data[22..23]
|
13
|
+
@hid_sub_command_request_table.mask_as_send(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [String] raw_data
|
18
|
+
# @return [void]
|
19
|
+
def mark_as_receive(raw_data)
|
20
|
+
data = raw_data.unpack("H*").first
|
21
|
+
case data
|
22
|
+
when INPUT_REPORT_FORMAT
|
23
|
+
sub_command = data[28..29]
|
24
|
+
sub_command_arg = data[30..31]
|
25
|
+
@hid_sub_command_request_table.mark_as_receive(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::OutputReportSubCommandTable
|
2
|
+
class HIDSubCommandResponse
|
3
|
+
attr_accessor :sub_command, :sub_command_arg
|
4
|
+
|
5
|
+
def initialize(sub_command: , sub_command_arg: )
|
6
|
+
@sub_command = sub_command
|
7
|
+
@sub_command_arg = sub_command_arg
|
8
|
+
end
|
9
|
+
|
10
|
+
def sub_command_with_arg
|
11
|
+
case @sub_command
|
12
|
+
when *SPECIAL_SUB_COMMANDS
|
13
|
+
@sub_command
|
14
|
+
else
|
15
|
+
if @sub_command_arg
|
16
|
+
"#{@sub_command}-#{@sub_command_arg}"
|
17
|
+
else
|
18
|
+
@sub_command
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
IGNORE_SUB_COMMANDS = {
|
25
|
+
"48-01" => true,
|
26
|
+
"04-00" => true,
|
27
|
+
"10-28" => true, # 返ってこないことがあった
|
28
|
+
}
|
29
|
+
# レスポンスに引数が含まれない
|
30
|
+
SPECIAL_SUB_COMMANDS = [
|
31
|
+
"01",
|
32
|
+
"02",
|
33
|
+
"03",
|
34
|
+
"30",
|
35
|
+
"38", # home led
|
36
|
+
"40",
|
37
|
+
"48",
|
38
|
+
]
|
39
|
+
|
40
|
+
EXPECTED_SUB_COMMANDS = [
|
41
|
+
"01-04",
|
42
|
+
"02-",
|
43
|
+
"04-00",
|
44
|
+
"08-00",
|
45
|
+
"10-00",
|
46
|
+
"10-50",
|
47
|
+
"10-80",
|
48
|
+
"10-98",
|
49
|
+
"10-10",
|
50
|
+
"30-",
|
51
|
+
"40-",
|
52
|
+
"48-", # Enable vibration
|
53
|
+
]
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@table = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [String] sub_command
|
60
|
+
# @param [String] sub_command_arg
|
61
|
+
# @return [void]
|
62
|
+
def mask_as_send(sub_command: , sub_command_arg: )
|
63
|
+
case sub_command
|
64
|
+
when *SPECIAL_SUB_COMMANDS
|
65
|
+
@table[sub_command] = false
|
66
|
+
else
|
67
|
+
response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
68
|
+
@table[response.sub_command_with_arg] = false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [String] sub_command
|
73
|
+
# @param [String] sub_command_arg
|
74
|
+
# @return [void]
|
75
|
+
def mark_as_receive(sub_command: , sub_command_arg: )
|
76
|
+
response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
77
|
+
if @table.key?(response.sub_command_with_arg)
|
78
|
+
@table[response.sub_command_with_arg] = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param [String] sub_command
|
83
|
+
# @param [String] sub_command_arg
|
84
|
+
# @return [Boolean]
|
85
|
+
def has_key?(sub_command: , sub_command_arg: )
|
86
|
+
if IGNORE_SUB_COMMANDS["#{sub_command}-#{sub_command_arg}"]
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
|
90
|
+
case sub_command
|
91
|
+
when *SPECIAL_SUB_COMMANDS
|
92
|
+
@table.key?(sub_command)
|
93
|
+
else
|
94
|
+
response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
95
|
+
@table.key?(response.sub_command_with_arg)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param [String] sub_command
|
100
|
+
# @param [String] sub_command_arg
|
101
|
+
# @return [Boolean]
|
102
|
+
def has_value?(sub_command: , sub_command_arg: )
|
103
|
+
if IGNORE_SUB_COMMANDS["#{sub_command}-#{sub_command_arg}"]
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
108
|
+
!!@table[response.sub_command_with_arg]
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Boolean]
|
112
|
+
def has_unreceived_command?
|
113
|
+
!@table.values.all?(&:itself)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [String, NilClass]
|
117
|
+
def unreceived_sub_command_with_arg
|
118
|
+
sub_command = @table.detect { |_key, value| !value }&.first
|
119
|
+
if(arg = ProconBypassMan::DeviceConnection::ProconSettingOverrider::SPECIAL_SUB_COMMAND_ARGS[sub_command])
|
120
|
+
"#{sub_command}#{arg}"
|
121
|
+
else
|
122
|
+
sub_command
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [Boolean]
|
127
|
+
def completed?
|
128
|
+
EXPECTED_SUB_COMMANDS.all? do |key|
|
129
|
+
sub_command, sub_command_arg = key.split("-")
|
130
|
+
has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::OutputReportWatcher
|
2
|
+
include ProconBypassMan::DeviceConnection::OutputReportMarkerable
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@hid_sub_command_request_table = ProconBypassMan::DeviceConnection::OutputReportSubCommandTable.new
|
6
|
+
@timer = ProconBypassMan::SafeTimeout.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param [String] sub_command
|
10
|
+
# @param [String] sub_command_arg
|
11
|
+
# @return [Boolean]
|
12
|
+
def sent?(sub_command: , sub_command_arg: )
|
13
|
+
@hid_sub_command_request_table.has_key?(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [String] sub_command
|
17
|
+
# @param [String] sub_command_arg
|
18
|
+
# @return [Boolean]
|
19
|
+
def received?(sub_command: , sub_command_arg: )
|
20
|
+
@hid_sub_command_request_table.has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Boolean]
|
24
|
+
def completed?
|
25
|
+
@hid_sub_command_request_table.completed?
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean]
|
29
|
+
# @raise [Timeout::Error]
|
30
|
+
def timeout_or_completed?
|
31
|
+
if @timer.timeout?
|
32
|
+
ProconBypassMan::SendErrorCommand.execute(error: "[pre_bypass] pre_bypassフェーズがタイムアウトしました")
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
|
36
|
+
if completed?
|
37
|
+
ProconBypassMan.logger.info "[pre_bypass] pre_bypassフェーズが想定通り終了しました"
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::PreBypass
|
2
|
+
attr_accessor :gadget, :procon, :output_report_watcher
|
3
|
+
|
4
|
+
def initialize(gadget: , procon: )
|
5
|
+
self.gadget = ProconBypassMan::DeviceModel.new(gadget)
|
6
|
+
self.procon = ProconBypassMan::DeviceModel.new(procon)
|
7
|
+
self.output_report_watcher = ProconBypassMan::DeviceConnection::OutputReportWatcher.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [void]
|
11
|
+
def execute!
|
12
|
+
loop do
|
13
|
+
run_once
|
14
|
+
|
15
|
+
if output_report_watcher.timeout_or_completed?
|
16
|
+
break
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [void]
|
22
|
+
def run_once
|
23
|
+
begin
|
24
|
+
raw_data = non_blocking_read_switch
|
25
|
+
output_report_watcher.mark_as_send(raw_data)
|
26
|
+
ProconBypassMan.logger.info "[pre_bypass] >>> #{raw_data.unpack("H*").first}"
|
27
|
+
send_procon(raw_data)
|
28
|
+
rescue IO::EAGAINWaitReadable
|
29
|
+
# no-op
|
30
|
+
end
|
31
|
+
|
32
|
+
3.times do
|
33
|
+
begin
|
34
|
+
raw_data = non_blocking_read_procon
|
35
|
+
output_report_watcher.mark_as_receive(raw_data)
|
36
|
+
ProconBypassMan.logger.info "[pre_bypass] <<< #{raw_data.unpack("H*").first}"
|
37
|
+
send_switch(raw_data)
|
38
|
+
rescue IO::EAGAINWaitReadable
|
39
|
+
# no-op
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @raise [IO::EAGAINWaitReadable]
|
47
|
+
# @return [String]
|
48
|
+
def non_blocking_read_switch
|
49
|
+
gadget.non_blocking_read
|
50
|
+
end
|
51
|
+
|
52
|
+
# @raise [IO::EAGAINWaitReadable]
|
53
|
+
# @return [String]
|
54
|
+
def non_blocking_read_procon
|
55
|
+
procon.non_blocking_read
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [void]
|
59
|
+
def send_procon(raw_data)
|
60
|
+
procon.send(raw_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [void]
|
64
|
+
def send_switch(raw_data)
|
65
|
+
gadget.send(raw_data)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::ProconSettingOverrider
|
2
|
+
attr_accessor :procon, :output_report_watcher, :output_report_generator
|
3
|
+
|
4
|
+
SUB_COMMAND_HOME_LED_ON = "38"
|
5
|
+
SUB_COMMAND_ARG_HOME_LED_ON = "1FF0FF"
|
6
|
+
SPECIAL_SUB_COMMAND_ARGS = {
|
7
|
+
SUB_COMMAND_HOME_LED_ON => SUB_COMMAND_ARG_HOME_LED_ON,
|
8
|
+
}
|
9
|
+
SETTING_HOME_LED_ON = { home_led_on: [SUB_COMMAND_HOME_LED_ON, SUB_COMMAND_ARG_HOME_LED_ON] }
|
10
|
+
ALL_SETTINGS = SETTING_HOME_LED_ON
|
11
|
+
|
12
|
+
def initialize(procon: )
|
13
|
+
use_steps = {}
|
14
|
+
if ProconBypassMan.config.enable_home_led_on_connect
|
15
|
+
use_steps.merge!(SETTING_HOME_LED_ON)
|
16
|
+
end
|
17
|
+
|
18
|
+
@setting_steps = use_steps.keys
|
19
|
+
self.procon = ProconBypassMan::DeviceModel.new(procon)
|
20
|
+
self.output_report_watcher = ProconBypassMan::DeviceConnection::SpoofingOutputReportWatcher.new(expected_sub_commands: use_steps.values)
|
21
|
+
self.output_report_generator = ProconBypassMan::DeviceConnection::OutputReportGenerator.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute!
|
25
|
+
loop do
|
26
|
+
run_once
|
27
|
+
|
28
|
+
if output_report_watcher.timeout_or_completed?
|
29
|
+
break
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def run_once
|
35
|
+
begin
|
36
|
+
raw_data = non_blocking_read_procon
|
37
|
+
rescue IO::EAGAINWaitReadable
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
ProconBypassMan.logger.info "[procon_setting_overrider] <<< #{raw_data.unpack("H*").first}"
|
42
|
+
output_report_watcher.mark_as_receive(raw_data)
|
43
|
+
if output_report_watcher.has_unreceived_command?
|
44
|
+
re_override_setting_by_cmd(output_report_watcher.unreceived_sub_command_with_arg)
|
45
|
+
else
|
46
|
+
if(setting_step = @setting_steps.shift)
|
47
|
+
override_setting_by_step(setting_step)
|
48
|
+
else
|
49
|
+
return
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @return [void]
|
57
|
+
def override_setting_by_step(setting_step)
|
58
|
+
raw_data = output_report_generator.generate_by_step(setting_step)
|
59
|
+
ProconBypassMan.logger.info "[procon_setting_overrider] >>> #{raw_data.unpack("H*").first}"
|
60
|
+
output_report_watcher.mark_as_send(raw_data)
|
61
|
+
send_procon(raw_data)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [void]
|
65
|
+
def re_override_setting_by_cmd(sub_command_with_arg)
|
66
|
+
raw_data = output_report_generator.generate_by_sub_command_with_arg(sub_command_with_arg)
|
67
|
+
ProconBypassMan.logger.info "[procon_setting_overrider] >>> #{raw_data.unpack("H*").first}"
|
68
|
+
send_procon(raw_data)
|
69
|
+
end
|
70
|
+
|
71
|
+
def non_blocking_read_procon
|
72
|
+
procon.non_blocking_read
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_procon(raw_data)
|
76
|
+
procon.send(raw_data)
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class ProconBypassMan::DeviceConnection::SpoofingOutputReportWatcher
|
2
|
+
include ProconBypassMan::DeviceConnection::OutputReportMarkerable
|
3
|
+
|
4
|
+
def initialize(expected_sub_commands: )
|
5
|
+
@timer = ProconBypassMan::SafeTimeout.new
|
6
|
+
@hid_sub_command_request_table = ProconBypassMan::DeviceConnection::OutputReportSubCommandTable.new
|
7
|
+
@expected_sub_commands = expected_sub_commands
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Boolean]
|
11
|
+
def has_unreceived_command?
|
12
|
+
@hid_sub_command_request_table.has_unreceived_command?
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String, NilClass]
|
16
|
+
def unreceived_sub_command_with_arg
|
17
|
+
@hid_sub_command_request_table.unreceived_sub_command_with_arg
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Boolean]
|
21
|
+
def completed?
|
22
|
+
@expected_sub_commands.all? do |sub_command, sub_command_arg|
|
23
|
+
@hid_sub_command_request_table.has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Boolean]
|
28
|
+
def timeout_or_completed?
|
29
|
+
if @timer.timeout?
|
30
|
+
ProconBypassMan.logger.info "[procon setting override] プロコンの設定上書き処理がタイムアウトしました"
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
if completed?
|
35
|
+
ProconBypassMan.logger.info "[procon setting override] プロコンの設定上書き処理が想定通り終了しました"
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ProconBypassMan::DeviceConnection
|
2
|
+
class BytesMismatchError < StandardError; end
|
3
|
+
class NotFoundProconError < StandardError; end
|
4
|
+
class TimeoutErrorInConditionalRoute < StandardError; end
|
5
|
+
class TimeoutError < StandardError; end
|
6
|
+
end
|
7
|
+
|
8
|
+
require_relative "device_connection/executor"
|
9
|
+
require_relative "device_connection/pre_bypass"
|
10
|
+
require_relative "device_connection/command"
|
11
|
+
require_relative "device_connection/output_report_markerable"
|
12
|
+
require_relative "device_connection/procon_setting_overrider"
|
13
|
+
require_relative "device_connection/output_report_generator"
|
14
|
+
require_relative "device_connection/output_report_sub_command_table"
|
15
|
+
require_relative "device_connection/spoofing_output_report_watcher"
|
16
|
+
require_relative "device_connection/output_report_watcher"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ProconBypassMan::DeviceModel
|
2
|
+
# @param [File] device
|
3
|
+
def initialize(device)
|
4
|
+
@device = device
|
5
|
+
end
|
6
|
+
|
7
|
+
# @param [String] raw_data
|
8
|
+
def send(raw_data)
|
9
|
+
@device.write_nonblock(raw_data)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @raise [IO::EAGAINWaitReadable]
|
13
|
+
# @return [String]
|
14
|
+
def non_blocking_read
|
15
|
+
@device.read_nonblock(64)
|
16
|
+
end
|
17
|
+
end
|
@@ -1,4 +1,15 @@
|
|
1
1
|
module ProconBypassMan
|
2
|
+
class NullCounter
|
3
|
+
def initialize(label: )
|
4
|
+
end
|
5
|
+
|
6
|
+
def record(_)
|
7
|
+
end
|
8
|
+
|
9
|
+
def shutdown
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
2
13
|
class Counter
|
3
14
|
attr_accessor :label, :table, :previous_table, :active
|
4
15
|
|
@@ -40,7 +51,11 @@ module ProconBypassMan
|
|
40
51
|
end
|
41
52
|
|
42
53
|
module IOMonitor
|
54
|
+
@@thread = nil
|
55
|
+
|
43
56
|
def self.new(label: )
|
57
|
+
return NullCounter.new(label: label) if not started?
|
58
|
+
|
44
59
|
counter = Counter.new(label: label)
|
45
60
|
@@list << counter
|
46
61
|
counter
|
@@ -51,9 +66,13 @@ module ProconBypassMan
|
|
51
66
|
@@list
|
52
67
|
end
|
53
68
|
|
69
|
+
def self.started?
|
70
|
+
!!@@thread
|
71
|
+
end
|
72
|
+
|
54
73
|
# ここで集計する
|
55
74
|
def self.start!
|
56
|
-
Thread.start do
|
75
|
+
@@thread = Thread.start do
|
57
76
|
max_output_length = 0
|
58
77
|
loop do
|
59
78
|
list = @@list.select(&:active).dup
|