Skip to content

Commit 66e01d3

Browse files
committed
Automatically detect international vs. Japan-locked Switch 2.
1 parent 81fe49b commit 66e01d3

12 files changed

+255
-79
lines changed

SerialPrograms/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,8 @@ file(GLOB MAIN_SOURCES
967967
Source/NintendoSwitch/Inference/NintendoSwitch_StartGameUserSelectDetector.h
968968
Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.cpp
969969
Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h
970+
Source/NintendoSwitch/Inference/NintendoSwitch2_BinarySliderDetector.cpp
971+
Source/NintendoSwitch/Inference/NintendoSwitch2_BinarySliderDetector.h
970972
Source/NintendoSwitch/NintendoSwitch_ConsoleHandle.cpp
971973
Source/NintendoSwitch/NintendoSwitch_ConsoleHandle.h
972974
Source/NintendoSwitch/NintendoSwitch_ConsoleState.cpp

SerialPrograms/Source/CommonTools/ImageMatch/WaterfillTemplateMatcher.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ WaterfillTemplateMatcher::WaterfillTemplateMatcher(
3232
std::string full_path = RESOURCE_PATH() + path;
3333
ImageRGB32 reference(full_path);
3434

35-
PackedBinaryMatrix matrix = compress_rgb32_to_binary_range(reference, (uint32_t)min_color, (uint32_t)max_color);
35+
PackedBinaryMatrix matrix = compress_rgb32_to_binary_range(
36+
reference,
37+
(uint32_t)min_color, (uint32_t)max_color
38+
);
3639
if (PreloadSettings::debug().IMAGE_TEMPLATE_MATCHING){
3740
ImageRGB32 binaryImage = reference.copy();
3841
filter_by_mask(matrix, binaryImage, Color(COLOR_BLACK), true);

SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Session.tpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ bool WaterfillSession_t<Tile, TileRoutines>::find_object(
339339
popcount, sum_x, sum_y
340340
);
341341
if (!(tile_min_x < x && x < tile_max_x && tile_min_y < y && y < tile_max_y)){
342-
// Get the min max of the 1-bits locaitons in the tile
342+
// Get the min max of the 1-bits locations in the tile
343343
size_t cmin_x, cmax_x, cmin_y, cmax_y;
344344
TileRoutines::boundaries(recorded_tile, cmin_x, cmax_x, cmin_y, cmax_y);
345345
stats.accumulate_boundary(

SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129
#include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_RollDateForward1.h"
130130
#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip_US.h"
131131
#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip_24h.h"
132+
#include "CommonTools/Images/BinaryImage_FilterRgb32.h"
133+
#include "NintendoSwitch/Inference/NintendoSwitch2_BinarySliderDetector.h"
132134

133135
#include <QPixmap>
134136
#include <QVideoFrame>
@@ -147,51 +149,6 @@ using namespace PokemonAutomation::Kernels::Waterfill;
147149
namespace PokemonAutomation{
148150
namespace NintendoSwitch{
149151

150-
uint16_t scroll_to(
151-
ProControllerContext& context,
152-
uint8_t start_digit, uint8_t end_digit, bool actually_scroll
153-
);
154-
155-
156-
namespace PokemonSwSh{
157-
std::vector<ImagePixelBox> find_selection_arrows(const ImageViewRGB32& image, size_t min_area);
158-
}
159-
160-
161-
162-
163-
StringSelectDatabase make_database(){
164-
StringSelectDatabase ret;
165-
for (size_t c = 0; c < 1000; c++){
166-
ret.add_entry(StringSelectEntry("slug" + std::to_string(c), "Display " + std::to_string(c)));
167-
}
168-
return ret;
169-
}
170-
const StringSelectDatabase& test_database(){
171-
static StringSelectDatabase database = make_database();
172-
return database;
173-
}
174-
175-
176-
177-
178-
179-
180-
class MacAddressCell : public StringCell{
181-
public:
182-
183-
private:
184-
185-
};
186-
187-
188-
189-
190-
191-
192-
193-
194-
195152

196153

197154

@@ -232,7 +189,6 @@ TestProgram::TestProgram()
232189
)
233190
, IMAGE_PATH(false, "Path to image for testing", LockMode::UNLOCK_WHILE_RUNNING, "default.png", "default.png")
234191
, STATIC_TEXT("Test text...")
235-
, SELECT("String Select", test_database(), LockMode::LOCK_WHILE_RUNNING, 0)
236192
// , PLAYER_LIST("Test Table", LockMode::UNLOCK_WHILE_RUNNING, "Notes")
237193
, DATE0(
238194
"Date",
@@ -273,7 +229,6 @@ TestProgram::TestProgram()
273229
// PA_ADD_OPTION(CONSOLE_MODEL);
274230
PA_ADD_OPTION(IMAGE_PATH);
275231
PA_ADD_OPTION(STATIC_TEXT);
276-
PA_ADD_OPTION(SELECT);
277232
// PA_ADD_OPTION(PLAYER_LIST);
278233
// PA_ADD_OPTION(battle_AI);
279234
PA_ADD_OPTION(DATE0);
@@ -306,6 +261,9 @@ void TestProgram::on_press(){
306261

307262

308263

264+
265+
266+
309267
void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& scope){
310268
using namespace Kernels;
311269
using namespace Kernels::Waterfill;
@@ -325,7 +283,45 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope&
325283
ProControllerContext context(scope, console.pro_controller());
326284
VideoOverlaySet overlays(overlay);
327285

328-
FastCodeEntry::numberpad_enter_code(console, context, "708538991006", true);
286+
287+
288+
// ImageRGB32 image0("menu-light.png");
289+
// ImageRGB32 image1("menu-dark.png");
290+
// ImageRGB32 image2("menu-jpn.png");
291+
292+
auto screenshot = feed.snapshot();
293+
294+
295+
BinarySliderDetector detector(COLOR_RED, {0.831784, 0.140496, 0.088290, 0.671074});
296+
auto sliders = detector.detect(screenshot);
297+
298+
for (auto& item : sliders){
299+
cout << item.first << " : " << item.second.min_y << endl;
300+
}
301+
302+
303+
#if 0
304+
ImageFloatBox box(0.842007, 0.626446, 0.050186, 0.049587);
305+
ImageViewRGB32 cropped = extract_box_reference(screenshot, box);
306+
307+
cropped.save("temp.png");
308+
309+
PackedBinaryMatrix matrix = compress_rgb32_to_binary_range(
310+
cropped, 0xffc0c0c0, 0xffffffff
311+
);
312+
313+
cout << matrix.dump() << endl;
314+
315+
std::vector<WaterfillObject> objects = find_objects_inplace(matrix, 200);
316+
cout << "objects.size() = " << objects.size() << endl;
317+
for (auto& item : objects){
318+
extract_box_reference(cropped, item).save("test.png");
319+
}
320+
#endif
321+
322+
323+
324+
// FastCodeEntry::numberpad_enter_code(console, context, "708538991006", true);
329325

330326

331327

@@ -418,7 +414,7 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope&
418414
#endif
419415

420416

421-
#if 1
417+
#if 0
422418
HomeMenuDetector detector0(console);
423419
StartGameUserSelectDetector detector1(console);
424420
UpdatePopupDetector detector2(console);

SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ class TestProgram : public MultiSwitchProgramInstance, public ButtonListener{
7373

7474
StaticTextOption STATIC_TEXT;
7575

76-
StringSelectOption SELECT;
77-
7876
// PokemonSV::PlayerListTable PLAYER_LIST;
7977
DateTimeOption DATE0;
8078
DateTimeOption DATE1;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* Binary Slider Detector
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#include "Kernels/Waterfill/Kernels_Waterfill_Session.h"
8+
#include "CommonFramework/Globals.h"
9+
#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h"
10+
#include "CommonTools/Images/BinaryImage_FilterRgb32.h"
11+
#include "CommonTools/ImageMatch/ExactImageMatcher.h"
12+
#include "NintendoSwitch2_BinarySliderDetector.h"
13+
14+
namespace PokemonAutomation{
15+
namespace NintendoSwitch{
16+
17+
18+
19+
BinarySliderDetector::BinarySliderDetector(Color color, const ImageFloatBox& box)
20+
: m_color(color)
21+
, m_box(box)
22+
{}
23+
void BinarySliderDetector::make_overlays(VideoOverlaySet& items) const{
24+
items.add(m_color, m_box);
25+
}
26+
27+
std::vector<std::pair<bool, ImagePixelBox>> BinarySliderDetector::detect(const ImageViewRGB32& image) const{
28+
using namespace Kernels::Waterfill;
29+
30+
static ImageMatch::ExactImageMatcher LIGHT_OFF_CURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Light-Off-Cursor.png");
31+
static ImageMatch::ExactImageMatcher LIGHT_OFF_NOCURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Light-Off-NoCursor.png");
32+
static ImageMatch::ExactImageMatcher LIGHT_ON_CURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Light-On-Cursor.png");
33+
static ImageMatch::ExactImageMatcher LIGHT_ON_NOCURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Light-On-NoCursor.png");
34+
static ImageMatch::ExactImageMatcher DARK_OFF_CURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Dark-Off-Cursor.png");
35+
static ImageMatch::ExactImageMatcher DARK_OFF_NOCURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Dark-Off-NoCursor.png");
36+
static ImageMatch::ExactImageMatcher DARK_ON_CURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Dark-On-Cursor.png");
37+
static ImageMatch::ExactImageMatcher DARK_ON_NOCURSOR (RESOURCE_PATH() + "NintendoSwitch2/BinarySlider-Dark-On-NoCursor.png");
38+
39+
ImageViewRGB32 region = extract_box_reference(image, m_box);
40+
41+
auto matrix = compress_rgb32_to_binary_range(region, 0xffc0c0c0, 0xffffffff);
42+
auto session = make_WaterfillSession(matrix);
43+
auto iter = session->make_iterator(200);
44+
WaterfillObject object;
45+
46+
std::vector<std::pair<bool, ImagePixelBox>> ret;
47+
48+
while (iter->find_next(object, false)){
49+
double aspect_ratio = object.aspect_ratio();
50+
if (aspect_ratio < 0.9 || aspect_ratio > 1.1){
51+
continue;
52+
}
53+
ImageViewRGB32 cropped = extract_box_reference(region, object);
54+
55+
double best_off_rmsd = 9999;
56+
best_off_rmsd = std::min(best_off_rmsd, LIGHT_OFF_CURSOR.rmsd(cropped));
57+
best_off_rmsd = std::min(best_off_rmsd, LIGHT_OFF_NOCURSOR.rmsd(cropped));
58+
best_off_rmsd = std::min(best_off_rmsd, DARK_OFF_CURSOR.rmsd(cropped));
59+
best_off_rmsd = std::min(best_off_rmsd, DARK_OFF_NOCURSOR.rmsd(cropped));
60+
if (best_off_rmsd < 40){
61+
ret.emplace_back(false, object);
62+
continue;
63+
}
64+
65+
double best_on_rmsd = 9999;
66+
best_on_rmsd = std::min(best_on_rmsd, LIGHT_ON_CURSOR.rmsd(cropped));
67+
best_on_rmsd = std::min(best_on_rmsd, LIGHT_ON_NOCURSOR.rmsd(cropped));
68+
best_on_rmsd = std::min(best_on_rmsd, DARK_ON_CURSOR.rmsd(cropped));
69+
best_on_rmsd = std::min(best_on_rmsd, DARK_ON_NOCURSOR.rmsd(cropped));
70+
if (best_on_rmsd < 40){
71+
ret.emplace_back(true, object);
72+
continue;
73+
}
74+
75+
// cout << "best_off_rmsd = " << best_off_rmsd << endl;
76+
// cout << "best_on_rmsd = " << best_on_rmsd << endl;
77+
}
78+
79+
return ret;
80+
}
81+
82+
83+
84+
}
85+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Binary Slider Detector
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#ifndef PokemonAutomation_NintendoSwitch2_BinarySliderDetector_H
8+
#define PokemonAutomation_NintendoSwitch2_BinarySliderDetector_H
9+
10+
#include "Common/Cpp/Color.h"
11+
#include "CommonFramework/ImageTools/ImageBoxes.h"
12+
#include "CommonTools/VisualDetector.h"
13+
14+
namespace PokemonAutomation{
15+
namespace NintendoSwitch{
16+
17+
18+
19+
20+
class BinarySliderDetector{
21+
public:
22+
BinarySliderDetector(Color color, const ImageFloatBox& box);
23+
void make_overlays(VideoOverlaySet& items) const;
24+
25+
std::vector<std::pair<bool, ImagePixelBox>> detect(const ImageViewRGB32& image) const;
26+
27+
private:
28+
Color m_color;
29+
ImageFloatBox m_box;
30+
};
31+
32+
33+
34+
35+
}
36+
}
37+
#endif

SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_ConsoleTypeDetector.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ConsoleTypeDetector_Home::ConsoleTypeDetector_Home(ConsoleHandle& console, Color
2020
: m_console(console)
2121
, m_color(color)
2222
, m_bottom_line(0.10, 0.88, 0.80, 0.03)
23+
, m_last(ConsoleType::Unknown)
2324
{}
2425
void ConsoleTypeDetector_Home::make_overlays(VideoOverlaySet& items) const{
2526
ConsoleType known_state = m_console.state().console_type();
@@ -30,7 +31,9 @@ void ConsoleTypeDetector_Home::make_overlays(VideoOverlaySet& items) const{
3031
}
3132
ConsoleType ConsoleTypeDetector_Home::detect_only(const ImageViewRGB32& screen){
3233
if (m_console.state().console_type_confirmed()){
33-
return m_console.state().console_type();
34+
ConsoleType state = m_console.state().console_type();
35+
m_last = state;
36+
return state;
3437
}
3538

3639
ImageStats stats = image_stats(extract_box_reference(screen, m_bottom_line));
@@ -45,7 +48,9 @@ ConsoleType ConsoleTypeDetector_Home::detect_only(const ImageViewRGB32& screen){
4548
return state;
4649
}
4750
void ConsoleTypeDetector_Home::commit_to_cache(){
48-
m_console.state().set_console_type(m_console, m_last);
51+
if (m_last != ConsoleType::Unknown){
52+
m_console.state().set_console_type(m_console, m_last);
53+
}
4954
}
5055

5156

SerialPrograms/Source/NintendoSwitch/NintendoSwitch_ConsoleState.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,24 @@ namespace NintendoSwitch{
1414

1515

1616

17-
const char* ConsoleType_Strings[] = {
18-
"Unknown",
19-
"Switch 1 + OLED",
20-
"Switch 2 (unknown model)",
21-
"Switch 2 (FW19, international)",
22-
"Switch 2 (FW19, Japan-locked)",
23-
"Switch 2 (FW20, international)",
24-
"Switch 2 (FW20, Japan-locked)",
25-
};
26-
const char* ConsoleType_strings(ConsoleType type){
27-
return ConsoleType_Strings[(size_t)type];
17+
const std::string& ConsoleType_strings(ConsoleType type){
18+
static std::string STRINGS[] = {
19+
"Unknown",
20+
"Switch 1 + OLED",
21+
"Switch 2 (unknown model)",
22+
"Switch 2 (FW19, international)",
23+
"Switch 2 (FW19, Japan-locked)",
24+
"Switch 2 (FW20, international)",
25+
"Switch 2 (FW20, Japan-locked)",
26+
};
27+
size_t index = (size_t)type;
28+
if (index < sizeof(STRINGS) / sizeof(std::string)){
29+
return STRINGS[(size_t)type];
30+
}
31+
throw InternalProgramError(
32+
nullptr, PA_CURRENT_FUNCTION,
33+
"Invalid ConsoleType enum: " + std::to_string(index)
34+
);
2835
}
2936

3037
[[noreturn]] void throw_conflict(
@@ -130,7 +137,7 @@ void ConsoleState::set_console_type(Logger& logger, ConsoleType type){
130137
logger.log(std::string("Setting console type to: ") + ConsoleType_strings(type));
131138

132139
m_data->m_console_type.store(type, std::memory_order_relaxed);
133-
m_data->m_console_type_confirmed.store(confirmed, std::memory_order_relaxed);
140+
m_data->m_console_type_confirmed.store(true, std::memory_order_relaxed);
134141
}
135142

136143

SerialPrograms/Source/NintendoSwitch/NintendoSwitch_ConsoleState.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ enum class ConsoleType{
2424
Switch2_FW20_International,
2525
Switch2_FW20_JapanLocked,
2626
};
27-
const char* ConsoleType_strings(ConsoleType type);
27+
const std::string& ConsoleType_strings(ConsoleType type);
2828

2929
inline bool is_switch1(ConsoleType type){
3030
return type == ConsoleType::Switch1;

0 commit comments

Comments
 (0)