Skip to content

Commit a7305e1

Browse files
committed
2 parents bf5e2a2 + cf755c0 commit a7305e1

24 files changed

Lines changed: 1171 additions & 274 deletions

.azure-pipelines/azure-pipelines.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ variables:
2222
- group: discord-symbols
2323
- group: telemetry
2424
- group: alarm_clock
25+
- group: artifacts
2526

2627
parameters:
2728
- name: buildType
@@ -228,7 +229,7 @@ stages:
228229
if ($buildType -eq "PrivateBeta") {
229230
Write-Host "=== Encrypting build artifact with password ==="
230231
$zipPath = "$artifactSubdir/$name.zip"
231-
& "C:/Program Files/7-Zip/7z.exe" a -tzip -mx=9 -p"$env:ARTIFACT_PASSWORD" -mem=AES256 $zipPath "$tempFolder/*"
232+
& "C:/Program Files/7-Zip/7z.exe" a -tzip -mx=9 "-p$env:ARTIFACT_PASSWORD" -mem=AES256 $zipPath "$tempFolder/*"
232233
} else {
233234
$zipPath = "$artifactSubdir/$name.zip"
234235
& "C:/Program Files/7-Zip/7z.exe" a -tzip -mx=9 $zipPath "$tempFolder/*"
@@ -406,7 +407,7 @@ stages:
406407
BUILD_TYPE="${{ parameters.buildType }}"
407408
if [ "$BUILD_TYPE" = "PrivateBeta" ]; then
408409
echo "=== Encrypting build artifact with password ==="
409-
7z a -tzip -mx=9 -p"$ARTIFACT_PASSWORD" -mem=AES256 "$(Pipeline.Workspace)/Arduino-Source-Internal/Repository/Public/build/$(cmake_preset)/cache-build/SerialPrograms-Ubuntu-$(compiler)-$(architecture).zip" "$FOLDER_NAME"
410+
7z a -tzip -mx=9 "-p$ARTIFACT_PASSWORD" -mem=AES256 "$(Pipeline.Workspace)/Arduino-Source-Internal/Repository/Public/build/$(cmake_preset)/cache-build/SerialPrograms-Ubuntu-$(compiler)-$(architecture).zip" "$FOLDER_NAME"
410411
else
411412
tar -I "gzip -9" -cvf $(Pipeline.Workspace)/Arduino-Source-Internal/Repository/Public/build/$(cmake_preset)/cache-build/SerialPrograms-Ubuntu-$(compiler)-$(architecture).tar.gz "$FOLDER_NAME"
412413
fi
@@ -625,6 +626,7 @@ stages:
625626
displayName: 'Publish GitHub Release'
626627
dependsOn:
627628
- Update_GitHub_Telemetry_JSON
629+
- Manual_Validation
628630
platforms:
629631
- Windows
630632
- Linux
@@ -647,13 +649,15 @@ stages:
647649
displayName: 'Update GitHub Latest Version JSON'
648650
dependsOn:
649651
- Publish_GitHub_Release
652+
- Manual_Validation
650653
buildType: ${{ parameters.buildType }}
651654
versionMajor: ${{ parameters.versionMajor }}
652655
versionMinor: ${{ parameters.versionMinor }}
653656
versionPatch: ${{ parameters.versionPatch }}
654657
versionRepo: PokemonAutomation/ComputerControl
655658
condition: |
656659
and(
660+
in(dependencies.Manual_Validation.result, 'Succeeded'),
657661
in(dependencies.Publish_GitHub_Release.result, 'Succeeded', 'Skipped'),
658662
eq('${{ parameters.targetOS }}', 'All'),
659663
ne('${{ parameters.buildType }}', 'Commit')

.azure-pipelines/templates/macos-notarize.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,15 @@ stages:
178178
VERSION="${{ parameters.versionMajor }}.${{ parameters.versionMinor }}.${{ parameters.versionPatch }}"
179179
FOLDER_NAME="SerialPrograms-$VERSION-MacOS-${{ parameters.architecture }}"
180180
BUILD_TYPE="${{ parameters.buildType }}"
181+
EXT=$([ "$BUILD_TYPE" = "PrivateBeta" ] && echo "zip" || echo "tar.gz")
181182
182183
cd "$(Pipeline.Workspace)/Arduino-Source-Internal"
183184
if [ "$BUILD_TYPE" = "PrivateBeta" ]; then
184185
echo "=== Creating password-protected archive for PrivateBeta ==="
185-
7z a -tzip -mx=9 -p"$ARTIFACT_PASSWORD" -mem=AES256 "SerialPrograms-MacOS-$(compiler)-$(architecture).zip" "$FOLDER_NAME"
186+
7z a -tzip -mx=9 "-p$ARTIFACT_PASSWORD" -mem=AES256 "SerialPrograms-MacOS-$(compiler)-$(architecture).$EXT" "$FOLDER_NAME"
186187
else
187188
echo "=== Creating tarball with enclosing folder ==="
188-
tar -I "gzip -9" -czf "SerialPrograms-MacOS-$(compiler)-$(architecture).tar.gz" "$FOLDER_NAME"
189+
tar -I "gzip -9" -czf "SerialPrograms-MacOS-$(compiler)-$(architecture).$EXT" "$FOLDER_NAME"
189190
fi
190191
191192
echo "=== Creating cache directory and moving tarball ==="

SerialPrograms/Source/CommonFramework/Recording/StreamHistoryOption.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ StreamHistoryOption::StreamHistoryOption()
1313
: GroupOption(
1414
"Stream History",
1515
LockMode::LOCK_WHILE_RUNNING,
16-
GroupOption::EnableMode::DEFAULT_DISABLED,
16+
IS_BETA_VERSION ? GroupOption::EnableMode::DEFAULT_ENABLED : GroupOption::EnableMode::DEFAULT_DISABLED,
1717
true
1818
)
1919
, DESCRIPTION(
@@ -93,7 +93,7 @@ StreamHistoryOption::StreamHistoryOption()
9393
{VideoFPS::FPS_01, "fps-01", "1 FPS"},
9494
},
9595
LockMode::UNLOCK_WHILE_RUNNING,
96-
VideoFPS::FPS_15
96+
VideoFPS::FPS_10
9797
)
9898
, JPEG_QUALITY(
9999
"<b>JPEG Quality:</b><br>"
@@ -141,3 +141,5 @@ void StreamHistoryOption::on_config_value_changed(void* object){
141141

142142

143143
}
144+
145+

SerialPrograms/Source/CommonFramework/Tools/FileUnzip.cpp

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ namespace PokemonAutomation{
3737

3838
// Callback triggered for every chunk of decompressed data
3939
// pOpaque is an opaque pointer that actually represents ProgressData
40-
size_t write_callback(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n) {
40+
size_t write_callback(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n){
4141
ProgressData* data = static_cast<ProgressData*>(pOpaque);
4242

4343
// 1. Check if we actually need to seek
4444
// tellp() returns the current 'put' position. get the current position of the write pointer in an output stream.
45-
if (static_cast<mz_uint64>(data->out_file->tellp()) != file_ofs) {
45+
if (static_cast<mz_uint64>(data->out_file->tellp()) != file_ofs){
4646
data->out_file->seekp(file_ofs);
4747
}
4848

@@ -55,7 +55,7 @@ namespace PokemonAutomation{
5555
int current_percent = static_cast<int>(percent);
5656

5757
// Only print if the integer value has changed
58-
if (current_percent > data->last_percentage) {
58+
if (current_percent > data->last_percentage){
5959
data->last_percentage = current_percent;
6060
std::cout << "\rProgress: " << current_percent << "% ("
6161
<< data->processed_bytes << "/" << data->total_bytes << " bytes)" << endl;
@@ -65,7 +65,7 @@ namespace PokemonAutomation{
6565
}
6666

6767
// ensure that entry_name is inside target_dir, to prevent path traversal attacks.
68-
bool is_safe(const std::string& target_dir, const std::string& entry_name) {
68+
bool is_safe(const std::string& target_dir, const std::string& entry_name){
6969
try {
7070
// 1. Get absolute, normalized paths
7171
// handles symlinks. and resolves .. and . components. throws error if path doesn't exist
@@ -86,20 +86,20 @@ namespace PokemonAutomation{
8686
// - If rel is empty, they are likely different roots
8787
// - If rel starts with "..", it escaped the base
8888
// - If rel is ".", it IS the base directory (usually safe)
89-
if (rel.empty() || *rel.begin() == "..") {
89+
if (rel.empty() || *rel.begin() == ".."){
9090
return false;
9191
}
9292

9393
return true;
94-
} catch (...) {
94+
} catch (...){
9595
cout << "target_dir path doesn't exist." << endl;
9696
return false;
9797
}
9898
}
9999

100-
void unzip_file(const char* zip_path, const char* target_dir) {
100+
void unzip_file(const char* zip_path, const char* target_dir){
101101
Filesystem::Path p{zip_path};
102-
if (!fs::exists(p)) {
102+
if (!fs::exists(p)){
103103
throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "unzip_all: Attempted to unzip a file that doesn't exist.");
104104
}
105105

@@ -119,7 +119,7 @@ namespace PokemonAutomation{
119119

120120
// calculate total uncompressed size
121121
uint64_t total_uncompressed_size = 0;
122-
for (int i = 0; i < num_files; i++) {
122+
for (int i = 0; i < num_files; i++){
123123
mz_zip_archive_file_stat file_stat; // holds info on the specific file
124124

125125
// fills file_stat with the data for the current index
@@ -130,15 +130,15 @@ namespace PokemonAutomation{
130130
}
131131

132132
uint64_t total_processed_bytes = 0;
133-
for (int i = 0; i < num_files; i++) {
133+
for (int i = 0; i < num_files; i++){
134134
mz_zip_archive_file_stat file_stat; // holds info on the specific file
135135

136136
// fills file_stat with the data for the current index
137137
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
138138

139139
// Checks if the current entry is a folder. Miniz treats folders as entries;
140140
// this code skips them to avoid trying to "write" a folder as if it were a file.
141-
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
141+
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)){
142142
continue;
143143
}
144144

@@ -149,7 +149,7 @@ namespace PokemonAutomation{
149149
// Create the entire directory, including intermediate directories for this file
150150
std::error_code ec{};
151151
fs::create_directories(parent_dir, ec);
152-
if (ec) {
152+
if (ec){
153153
std::cerr << "Error creating " << parent_dir << ": " << ec.message() << std::endl;
154154
ec.clear();
155155
}
@@ -172,7 +172,7 @@ namespace PokemonAutomation{
172172
mz_zip_reader_end(&zip_archive);
173173
}
174174

175-
// void unzip_file(const std::string& zip_path, const std::string& output_dir) {
175+
// void unzip_file(const std::string& zip_path, const std::string& output_dir){
176176
// cout << "try to unzip the file." << endl;
177177
// miniz_cpp::zip_file archive(zip_path);
178178

@@ -186,7 +186,7 @@ namespace PokemonAutomation{
186186
// // Create the entire directory tree for this file
187187
// std::filesystem::create_directories(p, ec);
188188

189-
// if (ec) {
189+
// if (ec){
190190
// std::cerr << "Error creating " << p << ": " << ec.message() << std::endl;
191191
// ec.clear();
192192
// }
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/* Battle Dialog Detectors
2+
*
3+
* From: https://github.com/PokemonAutomation/
4+
*
5+
*/
6+
7+
#include "CommonTools/Images/SolidColorTest.h"
8+
#include "CommonTools/Images/ImageFilter.h"
9+
#include "PokemonFRLG/PokemonFRLG_Settings.h"
10+
#include "PokemonFRLG_BattleDialogs.h"
11+
12+
namespace PokemonAutomation{
13+
namespace NintendoSwitch{
14+
namespace PokemonFRLG{
15+
16+
17+
18+
BattleDialogDetector::BattleDialogDetector(Color color)
19+
: m_dialog_top_box(0.0372308, 0.750154, 0.925538, 0.00934615)
20+
, m_dialog_right_box(0.956615, 0.7595, 0.00615385, 0.185885)
21+
, m_dialog_top_jpn_box(0.043890, 0.749227, 0.910248, 0.008023) //jpn positions might work for other languages
22+
, m_dialog_right_jpn_box(0.950583, 0.751901, 0.003556, 0.199230) //dialog might get in the way though
23+
{}
24+
void BattleDialogDetector::make_overlays(VideoOverlaySet& items) const{
25+
const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
26+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box));
27+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box));
28+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_jpn_box));
29+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_jpn_box));
30+
}
31+
bool BattleDialogDetector::detect(const ImageViewRGB32& screen){
32+
ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX);
33+
34+
ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box);
35+
ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box);
36+
ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_jpn_box);
37+
ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_jpn_box);
38+
39+
if ((is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
40+
&& is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20))
41+
||
42+
(is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
43+
&& is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20))
44+
){
45+
return true;
46+
}
47+
return false;
48+
}
49+
50+
51+
BattleMenuDetector::BattleMenuDetector(Color color)
52+
: m_menu_top_box(0.528308, 0.742885, 0.439385, 0.00830769) //top of the white dialog box
53+
, m_menu_right_box(0.961538, 0.752231, 0.00615385, 0.200423)
54+
, m_dialog_top_box(0.036, 0.749115, 0.459077, 0.0135)
55+
, m_dialog_right_box(0.490154, 0.762615, 0.00615385, 0.178615) //right side, closest to the menu
56+
, m_menu_top_jpn_box(0.593239, 0.743878, 0.373344, 0.009360) //very different positions!
57+
, m_menu_right_jpn_box(0.961538, 0.752231, 0.00615385, 0.200423)
58+
, m_dialog_top_jpn_box(0.043001, 0.750564, 0.520015, 0.002674)
59+
, m_dialog_right_jpn_box(0.559460, 0.750564, 0.003556, 0.196556)
60+
{}
61+
void BattleMenuDetector::make_overlays(VideoOverlaySet& items) const{
62+
const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
63+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_top_box));
64+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_right_box));
65+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box));
66+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box));
67+
68+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_top_jpn_box));
69+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_right_jpn_box));
70+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_jpn_box));
71+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_jpn_box));
72+
}
73+
bool BattleMenuDetector::detect(const ImageViewRGB32& screen){
74+
ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX);
75+
76+
//Menu is white
77+
ImageViewRGB32 menu_top_image = extract_box_reference(game_screen, m_menu_top_box);
78+
ImageViewRGB32 menu_right_image = extract_box_reference(game_screen, m_menu_right_box);
79+
ImageViewRGB32 menu_top_jpn_image = extract_box_reference(game_screen, m_menu_top_jpn_box);
80+
ImageViewRGB32 menu_right_jpn_image = extract_box_reference(game_screen, m_menu_right_jpn_box);
81+
82+
//Background dialog is teal
83+
ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box);
84+
ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box);
85+
ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_jpn_box);
86+
ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_jpn_box);
87+
88+
if ((is_white(menu_top_image) //All languages except japanese
89+
&& is_white(menu_right_image)
90+
&& is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20) //40, 81, 106 teal
91+
&& is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20))
92+
||
93+
(is_white(menu_top_jpn_image) //japanese
94+
&& is_white(menu_right_jpn_image)
95+
&& is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
96+
&& is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20))
97+
){
98+
return true;
99+
}
100+
return false;
101+
}
102+
103+
104+
AdvanceBattleDialogDetector::AdvanceBattleDialogDetector(Color color)
105+
: m_dialog_box(0.036, 0.748077, 0.926769, 0.204577)
106+
, m_dialog_top_box(0.0372308, 0.750154, 0.925538, 0.00934615)
107+
, m_dialog_right_box(0.956615, 0.7595, 0.00615385, 0.185885)
108+
, m_dialog_jpn_box(0.043890, 0.749227, 0.911137, 0.203242) //this position is very different!
109+
, m_dialog_top_jpn_box(0.043890, 0.749227, 0.910248, 0.008023)
110+
, m_dialog_right_jpn_box(0.950583, 0.751901, 0.003556, 0.199230)
111+
{}
112+
void AdvanceBattleDialogDetector::make_overlays(VideoOverlaySet& items) const{
113+
const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX;
114+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_box));
115+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box));
116+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box));
117+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_jpn_box));
118+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_jpn_box));
119+
items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_jpn_box));
120+
}
121+
bool AdvanceBattleDialogDetector::detect(const ImageViewRGB32& screen){
122+
ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX);
123+
124+
const bool replace_color_within_range = false;
125+
126+
//Filter out background
127+
ImageRGB32 filtered_region = filter_rgb32_range(
128+
extract_box_reference(game_screen, m_dialog_box),
129+
combine_rgb(164, 0, 0), combine_rgb(255, 114, 87), Color(0), replace_color_within_range
130+
);
131+
ImageStats stats = image_stats(filtered_region);
132+
133+
/*
134+
filtered_region.save("./filtered_only.png");
135+
cout << stats.average.r << endl;
136+
cout << stats.average.g << endl;
137+
cout << stats.average.b << endl;
138+
*/
139+
140+
//japanese
141+
ImageRGB32 filtered_region_jpn = filter_rgb32_range(
142+
extract_box_reference(game_screen, m_dialog_jpn_box),
143+
combine_rgb(164, 0, 0), combine_rgb(255, 114, 87), Color(0), replace_color_within_range
144+
);
145+
ImageStats stats2 = image_stats(filtered_region_jpn);
146+
147+
ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box);
148+
ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box);
149+
ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_jpn_box);
150+
ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_jpn_box);
151+
152+
if ((is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
153+
&& is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
154+
&& (stats.average.r > stats.average.b + 180)
155+
&& (stats.average.r > stats.average.g + 180))
156+
||
157+
(is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
158+
&& is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)
159+
&& (stats2.average.r > stats2.average.b + 180)
160+
&& (stats2.average.r > stats2.average.g + 180))
161+
){
162+
return true;
163+
}
164+
return false;
165+
}
166+
167+
168+
169+
170+
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)