Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .agents/skills/hayday-zoom-injector/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: hayday-zoom-injector
description: >-
Automated tool and runbook to extract, modify, and inject custom zoom levels, view distances,
and synchronized fingerprint.json hashes into Supercell's Hay Day client on Android emulators.
---

# Hay Day Zoom Injector Skill

Use this skill whenever Hay Day updates to a new version (e.g., 1.72.x -> 1.73.x) or when `premium.exe` requires updated camera zoom overrides (`MinZoom`, `DefaultZoom`, `MaxZoom`).

---

## 1. When to Use This Skill

* **After a Game Update:** Hay Day updates wipe or update `fingerprint.json` and change CSV schemas.
* **Black Screen / Zoom Reversion:** When `game_config.csv` is rejected by Supercell's dynamic asset validator.
* **New Emulator Setup:** Initializing `injecthacks/` and the emulator's `/update/` directory.

---

## 2. Automated Runbook

### Step 1: Run the Universal Zoom Injector Tool
Point the tool to the new game APK or APKM bundle and the active emulator:

```powershell
& "D:\02_Projects\Hayday-Bot-Pro\.venv\Scripts\python.exe" tools/hayday_zoom_injector.py `
--apkm "D:\Path\To\HayDay_new_version.apkm" `
--device "127.0.0.1:21503" `
--min-zoom 30 `
--default-zoom 10 `
--max-zoom 170
```

### Step 2: What the Tool Automatically Performs
1. **Extracts** `assets/data/game_config.csv` from the official game split asset pack.
2. **Patches** `MinZoom: 30`, `DefaultZoom: 10`, and `MaxZoom: 170` (and mobile equivalents).
3. **Calculates** the new SHA-1 cryptographic hash of the modified CSV.
4. **Pushes** the file to `HD/x64/Release/injecthacks/game_config.csv` for `premium.exe`.
5. **Synchronizes** the new SHA-1 hash into `/data/data/com.supercell.hayday/update/fingerprint.json` on the device.
6. **Sets Permissions** (`chmod 777`) on the remote update directory.

---

## 3. Verification Commands

Verify that the patched file and fingerprint are aligned on the emulator:

```powershell
adb -s 127.0.0.1:21503 shell "ls -la /data/data/com.supercell.hayday/update/data/game_config.csv"
adb -s 127.0.0.1:21503 shell "cat /data/data/com.supercell.hayday/update/fingerprint.json | grep -o '\"file\":\"data/game_config.csv\",\"sha\":\"[^\"]*\"'"
```

---

## 4. Manual Fallback Steps

If running without the CLI script:
1. Open the APK with 7-Zip or Python `zipfile`.
2. Extract `assets/data/game_config.csv`.
3. Modify line `MinZoom,50,,` to `MinZoom,30,,`.
4. Calculate SHA-1: `Get-FileHash -Algorithm SHA1 game_config.csv`.
5. Pull `/data/data/com.supercell.hayday/update/fingerprint.json`, replace the `"sha"` value under `"data/game_config.csv"`, and push both files back with `chmod 777`.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ cow/
*.apk
*.idsig

# Temporary analysis screenshots and VM test boots
network_study/android/*.png
network_study/android/magisk_memu_boot/

58 changes: 33 additions & 25 deletions HD/BotEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,14 @@ static bool DismissUnknownOverlay(int instanceId, const char* reason) {
}

static bool IsSiloFullPopupVisible(const cv::Mat& screen, MatchResult* outPopup = nullptr) {
MatchResult popup = FindImage(screen, silo_full_templatePath, 0.75f, false, 1.0f, false);
if (screen.empty()) return false;
MatchResult popup = FindImage(screen, silo_full_templatePath, 0.70f, false, 1.0f, false);
if (!popup.found) {
popup = FindImage(screen, "templates\\silo_full_banner.png", 0.70f, false, 1.0f, false);
}
if (!popup.found) {
popup = FindImage(screen, silo_full_cross_templatePath, std::max(0.65f, g_Thresholds.siloFullCrossThreshold), false, 1.0f, false);
}
if (outPopup) *outPopup = popup;
return popup.found;
}
Expand Down Expand Up @@ -505,11 +512,25 @@ static float GetSaleProductThresholdForCrop(int cropMode) {

static MatchResult FindShopIconPreferCache(const cv::Mat& screen, AccountSlot& account) {
MatchResult shopRes{ false, 0, 0, 0.0 };
auto isInvalidShopPoint = [&](int x, int y) {
if (x < 120 && y < 100) return true; // Top-left HUD (Level star / coins)
if (y > screen.rows - 90) return true; // Bottom HUD (Buttons / tools)
return false;
};

if (account.cachedShopX >= 0 && account.cachedShopY >= 0) {
shopRes = FindTemplateNearCachedPoint(screen, account.cachedShopX, account.cachedShopY, 90, 90, shop_templatePath, g_Thresholds.shopThreshold);
if (shopRes.found && isInvalidShopPoint(shopRes.x, shopRes.y)) {
shopRes = MatchResult{ false, 0, 0, 0.0 };
account.cachedShopX = -1;
account.cachedShopY = -1;
}
}
if (!shopRes.found) {
shopRes = FindImage(screen, shop_templatePath, g_Thresholds.shopThreshold);
if (shopRes.found && isInvalidShopPoint(shopRes.x, shopRes.y)) {
shopRes = MatchResult{ false, 0, 0, 0.0 };
}
}
if (shopRes.found) {
account.cachedShopX = shopRes.x;
Expand Down Expand Up @@ -2716,9 +2737,6 @@ bool AutoDetectTouchDevice(int instanceId) {
void ExecuteDenseGridGesture(int instanceId, int startX, int startY, const std::vector<MatchResult>& fields) {
if (fields.empty()) return;

std::string gestureDetail = "Start=(" + std::to_string(startX) + "," + std::to_string(startY) + "), TargetFields=" + std::to_string(fields.size());
AppendActionDebugLog(instanceId, "SWIPE_GRID", "MinitouchSweep", gestureDetail);

AddLog(instanceId, "[INFO] Executing Sweep...", ImVec4(0.8f, 0.4f, 1.0f, 1.0f));

// 1. FIND FIELD'S BORDER SO THE BOT CAN DRAG IT TO THE CORNER OF THE FIELDS.
Expand All @@ -2730,14 +2748,13 @@ void ExecuteDenseGridGesture(int instanceId, int startX, int startY, const std::
if (f.y > maxY) maxY = f.y;
}


int marginX = 80;
int marginY = 80;

int targetMinX = std::max(0, minX - marginX);
int targetMaxX = std::min(1280, maxX + marginX);
int targetMinY = std::max(0, minY - marginY);
int targetMaxY = std::min(720, maxY + marginY);
int targetMinX = (std::max)(0, minX - marginX);
int targetMaxX = (std::min)(640, maxX + marginX);
int targetMinY = (std::max)(0, minY - marginY);
int targetMaxY = (std::min)(480, maxY + marginY);

WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return;
Expand Down Expand Up @@ -2780,10 +2797,10 @@ void ExecuteDenseGridGesture(int instanceId, int startX, int startY, const std::
auto interpolate = [&](int x0, int y0, int x1, int y1, int tX0, int tY0, int tX1, int tY1) {
float dist0 = std::hypot(tX0 - x0, tY0 - y0);
float dist1 = std::hypot(tX1 - x1, tY1 - y1);
float maxDist = std::max(dist0, dist1);
float maxDist = (std::max)(dist0, dist1);

// THE LESSER THE SPEED, THE BETTER FOR THE BOT TO MISS EMPTY FIELDS.
int steps = std::max(20, (int)(maxDist / 5.0f));
int steps = (std::max)(20, (int)(maxDist / 5.0f));

for (int i = 1; i <= steps; ++i) {
float t = (float)i / steps;
Expand All @@ -2792,33 +2809,24 @@ void ExecuteDenseGridGesture(int instanceId, int startX, int startY, const std::
int cx1 = x1 + (int)((tX1 - x1) * t);
int cy1 = y1 + (int)((tY1 - y1) * t);

// Add humanized micro-jitter to drag waypoints
int jx0 = cx0 + (rand() % 5 - 2);
int jy0 = cy0 + (rand() % 5 - 2);
int jx1 = cx1 + (rand() % 5 - 2);
int jy1 = cy1 + (rand() % 5 - 2);

std::string mCmd = "m 0 " + std::to_string(jx0) + " " + std::to_string(jy0) + " 50\n" +
"m 1 " + std::to_string(jx1) + " " + std::to_string(jy1) + " 50\nc\n";
std::string mCmd = "m 0 " + std::to_string(cx0) + " " + std::to_string(cy0) + " 50\n" +
"m 1 " + std::to_string(cx1) + " " + std::to_string(cy1) + " 50\nc\n";
send(sock, mCmd.c_str(), mCmd.length(), 0);

// Micro-variable step delay (18ms - 23ms)
int stepDelay = 18 + (rand() % 6);
std::this_thread::sleep_for(std::chrono::milliseconds(stepDelay));
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
};
};

interpolate(p1_startX, p1_startY, p2_startX, p2_startY, p1_x1, p1_y1, p2_x1, p2_y1);
interpolate(p1_x1, p1_y1, p2_x1, p2_y1, p1_x2, p1_y2, p2_x2, p2_y2);
interpolate(p1_x2, p1_y2, p2_x2, p2_y2, p1_x3, p1_y3, p2_x3, p2_y3);


std::string cmdUp0 = "u 0\nc\n";
send(sock, cmdUp0.c_str(), cmdUp0.length(), 0);
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // RELEASE FIRST FINGER

std::string cmdUp1 = "u 1\nc\n";
send(sock, cmdUp1.c_str(), cmdUp1.length(), 0); // RELEASE SECOND FINGER
send(sock, cmdUp1.c_str(), cmdUp1.length(), 0); // RELEASE SECOND FINGER

// GIVE SOME TIME TO ANDROID BEFORE CLOSING TCP CONNECTION
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Expand Down
14 changes: 7 additions & 7 deletions HD/Premium bot/bot.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
Expand Down Expand Up @@ -203,7 +203,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libcurl.lib;archive.lib;libcrypto.lib;libssl.lib;bz2.lib;lz4.lib;glfw3.lib;tesseract55.lib;leptonica-1.87.0.lib;libwebp.lib;libwebpmux.lib;libwebpdecoder.lib;libwebpdemux.lib;libsharpyuv.lib;libpng16.lib;zlib.lib;jpeg.lib;tiff.lib;gif.lib;openjp2.lib;lzma.lib;zstd.lib;Gdi32.lib;User32.lib;Ws2_32.lib;Wldap32.lib;Normaliz.lib;Crypt32.lib;Advapi32.lib;Iphlpapi.lib;Secur32.lib;XmlLite.lib;opencv_calib3d4.lib;opencv_core4.lib;opencv_dnn4.lib;opencv_features2d4.lib;opencv_flann4.lib;opencv_highgui4.lib;opencv_imgcodecs4.lib;opencv_imgproc4.lib;opencv_ml4.lib;opencv_objdetect4.lib;opencv_photo4.lib;opencv_stitching4.lib;opencv_video4.lib;opencv_videoio4.lib</AdditionalDependencies>
<AdditionalDependencies>libcurl.lib;archive.lib;libcrypto.lib;libssl.lib;bz2.lib;lz4.lib;glfw3dll.lib;tesseract55.lib;leptonica-1.87.0.lib;libwebp.lib;libwebpmux.lib;libwebpdecoder.lib;libwebpdemux.lib;libsharpyuv.lib;libpng16.lib;z.lib;jpeg.lib;tiff.lib;gif.lib;openjp2.lib;lzma.lib;zstd.lib;Gdi32.lib;User32.lib;Ws2_32.lib;Wldap32.lib;Normaliz.lib;Crypt32.lib;Advapi32.lib;Iphlpapi.lib;Secur32.lib;XmlLite.lib;opencv_calib3d4.lib;opencv_core4.lib;opencv_dnn4.lib;opencv_features2d4.lib;opencv_flann4.lib;opencv_highgui4.lib;opencv_imgcodecs4.lib;opencv_imgproc4.lib;opencv_ml4.lib;opencv_objdetect4.lib;opencv_photo4.lib;opencv_stitching4.lib;opencv_video4.lib;opencv_videoio4.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);$(HaydayVcpkgRoot)\lib;$(ProjectDir)discord-rpc;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<UACExecutionLevel>RequireAdministrator</UACExecutionLevel>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
Expand All @@ -212,9 +212,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<VcpkgApplocalDeps>false</VcpkgApplocalDeps>
</PropertyGroup>
<Target Name="CopyReleaseRuntimeDlls" AfterTargets="Build" Condition="'$(Configuration)|$(Platform)'=='Release|x64' and Exists('E:\\vcpkg\\installed\\x64-windows\\bin')">
<Target Name="CopyReleaseRuntimeDlls" AfterTargets="Build" Condition="'$(Configuration)|$(Platform)'=='Release|x64' and Exists('$(HaydayVcpkgRoot)\\bin')">
<ItemGroup>
<ReleaseRuntimeDlls Include="E:\\vcpkg\\installed\\x64-windows\\bin\\*.dll" />
<ReleaseRuntimeDlls Include="$(HaydayVcpkgRoot)\\bin\\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(ReleaseRuntimeDlls)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
</Target>
Expand Down
15 changes: 9 additions & 6 deletions HD/bot_logic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,19 +283,22 @@ std::string ResolveTemplatePath(const std::string& templatePath) {
return "";
};

if (fs::exists(requested, ec)) return requested.lexically_normal().string();
// 1. First priority: Check relative to executable directory (where premium.exe is located)
char modulePath[MAX_PATH] = {};
DWORD moduleLen = GetModuleFileNameA(nullptr, modulePath, MAX_PATH);
if (moduleLen > 0 && moduleLen < MAX_PATH) {
fs::path exeDir = fs::path(modulePath).parent_path();
if (std::string resolved = findFromBase(exeDir); !resolved.empty()) return resolved;
}

// 2. Second priority: Check relative to current working directory
ec.clear();
fs::path currentDir = fs::current_path(ec);
if (!ec) {
if (std::string resolved = findFromBase(currentDir); !resolved.empty()) return resolved;
}

char modulePath[MAX_PATH] = {};
DWORD moduleLen = GetModuleFileNameA(nullptr, modulePath, MAX_PATH);
if (moduleLen > 0 && moduleLen < MAX_PATH) {
if (std::string resolved = findFromBase(fs::path(modulePath).parent_path()); !resolved.empty()) return resolved;
}
if (fs::exists(requested, ec)) return requested.lexically_normal().string();

return templatePath;
}
Expand Down
9 changes: 8 additions & 1 deletion HD/cpp/src/bot/ScreenRecovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,14 @@ bool IsNotRespondingDialogVisible(const cv::Mat& screen, MatchResult* outDialog)
}

bool IsSiloFullPopupVisible(const cv::Mat& screen, MatchResult* outPopup) {
MatchResult popup = FindImage(screen, silo_full_templatePath, 0.75f, false, 1.0f, false);
if (screen.empty()) return false;
MatchResult popup = FindImage(screen, silo_full_templatePath, 0.70f, false, 1.0f, false);
if (!popup.found) {
popup = FindImage(screen, "templates\\silo_full_banner.png", 0.70f, false, 1.0f, false);
}
if (!popup.found) {
popup = FindImage(screen, silo_full_cross_templatePath, std::max(0.65f, g_Thresholds.siloFullCrossThreshold), false, 1.0f, false);
}
if (outPopup) *outPopup = popup;
return popup.found;
}
Expand Down
2 changes: 1 addition & 1 deletion HD/cpp/src/operations/market/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ float GetSaleProductThresholdForCrop(int cropMode) {
MatchResult FindShopIconPreferCache(const cv::Mat& screen, AccountSlot& account) {
MatchResult shopRes{ false, 0, 0, 0.0 };
auto rejectHudMatch = [&]() {
if (shopRes.found && IsTopLeftHudPoint(screen, shopRes.x, shopRes.y)) {
if (shopRes.found && (IsTopLeftHudPoint(screen, shopRes.x, shopRes.y) || shopRes.y > screen.rows - 90)) {
shopRes = MatchResult{ false, 0, 0, 0.0 };
account.cachedShopX = -1;
account.cachedShopY = -1;
Expand Down
Loading
Loading