<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: StealthC</title>
    <description>The latest articles on DEV Community by StealthC (@stealthc).</description>
    <link>https://dev.to/stealthc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F632906%2F43662a80-9ac0-408f-b80a-463af352604f.jpeg</url>
      <title>DEV Community: StealthC</title>
      <link>https://dev.to/stealthc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stealthc"/>
    <language>en</language>
    <item>
      <title>[EN] Gatonauta: GameDev.tv GameJam 2025 Experience</title>
      <dc:creator>StealthC</dc:creator>
      <pubDate>Wed, 28 May 2025 17:48:12 +0000</pubDate>
      <link>https://dev.to/stealthc/en-gatonauta-gamedevtv-gamejam-2025-experience-2ppe</link>
      <guid>https://dev.to/stealthc/en-gatonauta-gamedevtv-gamejam-2025-experience-2ppe</guid>
      <description>&lt;p&gt;Translation: Este artigo também está &lt;a href="https://dev.to/stealthc/pt-br-gatonauta-experiencia-do-gamedevtv-gamejam-2025-3f45"&gt;disponível em Português&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided to join the GameDev.tv game jam (&lt;a href="https://itch.io/jam/gamedevtv-jam-2025" rel="noopener noreferrer"&gt;GameDev.tv Game Jam 2025 - Free Course For All Submissions! - itch.io&lt;/a&gt;), which is a great choice for anyone getting started with game development. The community is very welcoming, mostly made up of students and beginners, and there's a lot of active and constructive feedback exchange. Also, everyone who submits a game gets a free course, which is an amazing perk if you’re just starting out!&lt;/p&gt;

&lt;p&gt;This is a 10-day jam, and the chosen theme was &lt;strong&gt;"Tiny World"&lt;/strong&gt;. Last year, I joined with a game called &lt;a href="https://stealthc.itch.io/gataria" rel="noopener noreferrer"&gt;Gataria by StealthC&lt;/a&gt;, made for the theme &lt;strong&gt;"Last Stand"&lt;/strong&gt;, and it got a lot of positive feedback. I already knew I wanted to make another game with a cat protagonist, and as soon as the theme was announced, I started thinking about how to make it fit.&lt;/p&gt;

&lt;p&gt;This time, I decided to go big. Way bigger than &lt;em&gt;Gataria&lt;/em&gt;. I knew I was risking not finishing in time, especially since most of the 10 days were weekdays and I had to balance it with my regular job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing Tools and Initial Scope...
&lt;/h2&gt;

&lt;p&gt;My idea was to make a game focused on &lt;strong&gt;survival, exploration, and crafting&lt;/strong&gt;, heavily inspired by games like &lt;em&gt;Factorio&lt;/em&gt; and &lt;em&gt;Forager&lt;/em&gt;. I also wanted to implement a gravity mechanic on small planets, something like &lt;em&gt;Super Mario Galaxy&lt;/em&gt;, but in 2D.&lt;/p&gt;

&lt;p&gt;I chose to use Godot Engine (went all in with the beta version 4.5 dev3), which I know pretty well and lets me develop quickly. Its Web builds are lightweight and perfect for game jams, and the community is super active, which helps a lot when solving problems.&lt;/p&gt;

&lt;p&gt;One thing I hadn’t done before, but decided to try this time, was setting up a CI pipeline using GitHub. That way, every time I pushed to the main branch, the game would be automatically built and uploaded to Itch.io. It helped me track progress, get testers earlier, and catch bugs faster.&lt;/p&gt;

&lt;p&gt;Here’s the workflow I used (if you want to try it yourself, remember to adapt the keys and the Godot version to your own project):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export and Upload to Itch.io&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export Godot project&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;export&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;firebelley/godot-export@v6.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;godot_executable_download_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/godotengine/godot-builds/releases/download/4.5-dev4/Godot_v4.5-dev4_linux.x86_64.zip&lt;/span&gt;
          &lt;span class="na"&gt;godot_export_templates_download_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/godotengine/godot-builds/releases/download/4.5-dev4/Godot_v4.5-dev4_export_templates.tpz&lt;/span&gt;
          &lt;span class="na"&gt;relative_project_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
          &lt;span class="na"&gt;archive_output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to Itch.io&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ayowel/butler-to-itch@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;butler_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ITCHIO_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;itch_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_ITCHIO_USERNAME&lt;/span&gt;
          &lt;span class="na"&gt;itch_game&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR_GAME_NAME&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref_name }}&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.export.outputs.archive_directory }}/Web.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Pretty Ambitious Concept...
&lt;/h2&gt;

&lt;p&gt;From the start, it was clear the scope would be a challenge. I wanted the game to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;survival system&lt;/strong&gt;, with hunger and resource management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combat&lt;/strong&gt; and action elements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dungeons&lt;/strong&gt;, for exploration and extra challenges&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;grappling hook&lt;/strong&gt;, to attach to asteroids or pull objects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localization&lt;/strong&gt;, so it could be played in English and Portuguese&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that scope, a playthrough would take a while. So a save system was a must. I started building that first, which took me almost an entire day, plus time to plan the overall structure of the game.&lt;/p&gt;

&lt;p&gt;Here’s my initial sketch and the AI-generated version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitjbmqzzrh225m9od017.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitjbmqzzrh225m9od017.png" alt="Image description" width="800" height="1144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt9f4h0ad4d22hz731wr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt9f4h0ad4d22hz731wr.png" alt="Image description" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Reinventing Gravity From Scratch...
&lt;/h2&gt;

&lt;p&gt;On day two, I tackled one of the biggest technical challenges: movement with gravity on planetoids. It sounded easy in theory: just make gravity point toward the planet’s center. In practice... not that simple.&lt;/p&gt;

&lt;p&gt;The solution I found was to calculate all movement (walking, jumping, gravity) as if it were a regular 2D Cartesian plane. Only at the end did I rotate the final movement vector to align it with the planet’s surface. It ended up feeling pretty smooth and intuitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_on_floor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_velocity&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_velocity&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Smooth air control&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gravity_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global_position&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;direction_to_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gravity_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;global_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target_rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction_to_center&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;local_rotated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_rotated&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;_gravity_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;_jetpack_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;move_and_slide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The game has two basic physics modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On planets: with gravity and lateral movement&lt;/li&gt;
&lt;li&gt;In space: no gravity, fixed camera, and free rotation + propulsion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, problems came up. In Web builds, physics started acting weird: probably due to floating point precision loss. After a lot of trial and error, I found that using &lt;strong&gt;square collision&lt;/strong&gt; for the player and &lt;strong&gt;circle collision&lt;/strong&gt; for planets fixed a bunch of it. Using capsules or circles for the player caused a strange bug: moving faster to one side, which got worse over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Racing Against Time and Using AI
&lt;/h2&gt;

&lt;p&gt;For the next five days, I spent my daytime working, and only had time to work on the game late at night, usually already exhausted and not thinking straight. So I focused on building &lt;strong&gt;assets&lt;/strong&gt;: sprites, planets, items, sounds, music, etc.&lt;/p&gt;

&lt;p&gt;And that’s when I hit a hot topic: using &lt;strong&gt;AI to create assets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Yeah, it’s a controversial subject in game dev, and for good reason. AI tools have become really powerful, especially for solo devs or small teams, which is basically the case in most game jams. When time is short and you don’t have all the skills needed (art, sound, music...), AI can be a huge help.&lt;/p&gt;

&lt;p&gt;I’m not a professional artist. Creating all assets from scratch: sprites, sound effects, music, UI... just wouldn’t be realistic in a few days. So yes, I used AI tools to speed things up.&lt;/p&gt;

&lt;p&gt;But I believe it’s important to think about how and why we use AI. It shouldn’t replace human creativity (at least not right now), but extend it. I used it to bring my ideas to life: rough sketches, textures, sounds. Almost everything needed editing, rework, adjustments to fit the game’s style.&lt;/p&gt;

&lt;p&gt;In that sense, AI is just another tool, like a sound effect generator, for example, &lt;a href="https://sfxr.me/" rel="noopener noreferrer"&gt;jsfxr - 8 bit sound maker&lt;/a&gt;, which I also used. It helps turn ideas into something playable, especially when you’re stuck on skills you don’t have.&lt;/p&gt;

&lt;p&gt;Of course, I also understand the criticism. There are valid concerns around how models are trained (often using artists’ work without consent) and how it affects the creative industry.&lt;/p&gt;

&lt;p&gt;To me, the biggest problems aren’t about the AI itself, but how the entire system around it is structured: tech monopolies, business models, etc. It’s a deeper issue.&lt;/p&gt;

&lt;p&gt;In the context of a game jam, where we’re prototyping, learning, experimenting... I see AI use as totally valid, as long as you’re being conscious, honest, and transparent. The rules of this jam even mention that.&lt;/p&gt;

&lt;p&gt;Still, as devs, I think we should keep asking questions and thinking about how to build a space where these tools can exist ethically and fairly, helping everyone: artists, developers, and players.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthttvhremyvc89wkdyjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthttvhremyvc89wkdyjr.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Development of Gatonauta: Racing the Clock
&lt;/h2&gt;

&lt;p&gt;Back to development: during this period, I created the first planet assets, the character, some items, and resources, but nothing was functional yet. It wasn’t until around day seven that I finally managed to finish crucial systems like inventory and resource collection. By then, I had already scrapped several parts of my initial scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Survival&lt;/strong&gt;: In the context of my idea, survival was just another layer. If the game core wasn’t even ready, that layer would only get in the way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combat&lt;/strong&gt;: Sadly, combat systems weren’t going to happen with so little free time. Maybe next time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grappling Hook&lt;/strong&gt;: This was scrapped along with space mining, but it would’ve been super cool to have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dialogues&lt;/strong&gt;: I had a whole story planned involving the Gatonaut and his sidekick, CatGPT (the little drone that flies around breaking stuff).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dungeons&lt;/strong&gt;: Another idea dropped due to time constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I focused on getting the basics working: movement, resource gathering, crafting, and building. Plus, the planet exploration part. I needed the game to at least have a complete gameplay loop. So I decided to go for a single simple goal: fix the spaceship. To do that, the player needs to craft three different parts. I planned the recipes for those parts and designed the steps to get them. Nothing overly complex, just enough to be fun for most players without requiring a grind.&lt;/p&gt;

&lt;p&gt;I tried my best to make things modular using Godot’s signals, so the game would be easy to expand. In practice, if I wanted to add more resources, nodes, or structures, I just needed to create new instances using the existing logic, data, and textures.&lt;/p&gt;

&lt;p&gt;All recipes, crafting, and upgrades are defined with simple dictionaries. Sometimes I use special objects for recipes, but they still rely on dictionaries under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="c1"&gt;## Structure costs dictionary&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_COST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"STORAGE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"WORKBENCH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"BAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;## Structure prefab dictionary&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_PREFAB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PackedScene&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scenes/structures/furnace.tscn"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scenes/structures/3d_printer.tscn"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_RECIPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/charcoal.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/bar.tres"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"DAMAGED_SHIP"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/fix-ship.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-1.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-2.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-3.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UPGRADES_RECIPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FIX_JETPACK"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"PLANETOID_INDICATOR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"BETTER_JETPACK"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"FASTER_HARVEST"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"MORE_RESOURCES"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"ORE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wish I had polished the UI more, but I realized that every time I added a new feature, I spent way too long finishing it. This made it clear how inexperienced I am with Godot’s UI system. So I decided to keep it simple but functional. No fancy icons or effects... just text. The goal was to make sure players understood what to do and how to do it, even if it wasn’t the prettiest.&lt;/p&gt;

&lt;p&gt;I spent day nine testing and fixing bugs, prepared a credits screen, and wrapped up the game. On day ten, I had work, so I could only fix a few last-minute bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Joining this jam was, without a doubt, an amazing experience. I learned so much, tried out ideas I had shelved for a long time, like planet gravity, continuous integration, and even using AI more seriously in my workflow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Gatonauta&lt;/em&gt; ended up being far from what I originally dreamed, but that’s not necessarily a bad thing. Part of the process is understanding your limits, adjusting the scope, and turning a giant idea into something that’s functional, fun, and gets the concept across.&lt;/p&gt;

&lt;p&gt;I definitely leave this jam even more motivated to keep developing games, improving my processes, understanding my strengths better, and, more importantly, my weaknesses.&lt;/p&gt;

&lt;p&gt;If you made it this far, thank you so much for reading. I hope this post inspired you, helped you, or maybe even gave you that little push to join your first (or next) game jam.&lt;/p&gt;

&lt;p&gt;And if you’d like to try the game, it’s available here:&lt;br&gt;
&lt;a href="https://stealthc.itch.io/gatonauta" rel="noopener noreferrer"&gt;Gatonauta by StealthC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5itqxfzqimonawuskff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5itqxfzqimonawuskff.png" alt="Image description" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>gdscript</category>
    </item>
    <item>
      <title>[PT-BR] Gatonauta: Experiência do GameDev.tv GameJam 2025</title>
      <dc:creator>StealthC</dc:creator>
      <pubDate>Wed, 28 May 2025 17:38:20 +0000</pubDate>
      <link>https://dev.to/stealthc/pt-br-gatonauta-experiencia-do-gamedevtv-gamejam-2025-3f45</link>
      <guid>https://dev.to/stealthc/pt-br-gatonauta-experiencia-do-gamedevtv-gamejam-2025-3f45</guid>
      <description>&lt;p&gt;Tradução: This post is also &lt;a href="https://dev.to/stealthc/en-gatonauta-gamedevtv-gamejam-2025-experience-2ppe"&gt;available in English&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Decidi participar da game jam da GameDev.tv (&lt;a href="https://itch.io/jam/gamedevtv-jam-2025" rel="noopener noreferrer"&gt;GameDev.tv Game Jam 2025 - Free Course For All Submissions! - itch.io&lt;/a&gt;), que é uma ótima opção para quem está começando no desenvolvimento de jogos. A comunidade é bem acolhedora, formada principalmente por alunos e iniciantes, e a troca de feedbacks é muito ativa e construtiva. Além disso, todo participante que enviar um jogo, automaticamente ganha um curso deles, que é algo muito bom pra quem está começando!&lt;/p&gt;

&lt;p&gt;Essa é uma jam de 10 dias, e o tema escolhido foi &lt;strong&gt;“Tiny World”&lt;/strong&gt;. No ano passado, participei com um jogo chamado &lt;a href="https://stealthc.itch.io/gataria" rel="noopener noreferrer"&gt;Gataria by StealthC&lt;/a&gt;, feito para o tema &lt;strong&gt;“Last Stand”&lt;/strong&gt;, e recebi muitos feedbacks positivos. Eu já sabia que queria fazer mais um jogo com um gato como protagonista, e, assim que o tema saiu, comecei a pensar em como encaixar essa ideia.&lt;/p&gt;

&lt;p&gt;Dessa vez, decidi me arriscar com um escopo bem maior que &lt;em&gt;Gataria&lt;/em&gt;, mesmo sabendo que corria o risco de não conseguir finalizar a tempo. Afinal, boa parte dos 10 dias eram dias úteis, e eu também precisava conciliar com meu trabalho.&lt;/p&gt;




&lt;h2&gt;
  
  
  Escolha de ferramentas e escopo inicial...
&lt;/h2&gt;

&lt;p&gt;Minha proposta era criar um jogo de &lt;strong&gt;sobrevivência, exploração e crafting&lt;/strong&gt;, com bastante inspiração em jogos como &lt;em&gt;Factorio&lt;/em&gt; e &lt;em&gt;Forager&lt;/em&gt;. Além disso, queria implementar uma mecânica de gravidade em pequenos planetas, no estilo &lt;em&gt;Super Mario Galaxy&lt;/em&gt;, mas adaptado para um jogo 2D.&lt;/p&gt;

&lt;p&gt;Decidi usar o Godot Engine (arrisquei e fiz todo na versão beta 4.5 dev3), que é uma ferramenta que já conheço bem e que me permite desenvolver jogos de forma rápida e eficiente. Suas builds para Web são ótimas e leves, perfeitas para game jams, e a comunidade é bem ativa, o que facilita encontrar soluções para problemas comuns.&lt;/p&gt;

&lt;p&gt;Uma das coisas que eu não tinha feito antes, mas dessa vez decidi implementar, foi preparar uma CI (integração contínua) para o projeto, usando o GitHub. Assim, a cada commit que eu fizesse na branch main, o jogo seria compilado e disponibilizado automaticamente no Itch.io. Isso me ajudou a manter o controle do progresso, fazer outras pessoas testarem e identificar bugs mais rapidamente.&lt;/p&gt;

&lt;p&gt;O workflow ficou assim (Se você quiser usar este workflow, lembre-se de adaptar as chaves e ajustar a versão do Godot conforme o seu projeto):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export and Upload to Itch.io&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Export Godot project&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;export&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;firebelley/godot-export@v6.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;godot_executable_download_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/godotengine/godot-builds/releases/download/4.5-dev4/Godot_v4.5-dev4_linux.x86_64.zip&lt;/span&gt;
          &lt;span class="na"&gt;godot_export_templates_download_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/godotengine/godot-builds/releases/download/4.5-dev4/Godot_v4.5-dev4_export_templates.tpz&lt;/span&gt;
          &lt;span class="na"&gt;relative_project_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
          &lt;span class="na"&gt;archive_output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to Itch.io&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ayowel/butler-to-itch@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;butler_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ITCHIO_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;itch_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEU_USUARIO_ITCHIO&lt;/span&gt;
          &lt;span class="na"&gt;itch_game&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NOME_DO_JOGO_ITCHIO&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.ref_name }}&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.export.outputs.archive_directory }}/Web.zip&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Um conceito bem ambicioso...
&lt;/h2&gt;

&lt;p&gt;O meu conceito inicial já deixava claro que não seria uma tarefa fácil. Eu queria que o jogo tivesse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Um sistema de &lt;strong&gt;sobrevivência&lt;/strong&gt;, com fome e gerenciamento de recursos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combate&lt;/strong&gt; e elementos de ação.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dungeons&lt;/strong&gt;, para exploração e desafios extras.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Um &lt;strong&gt;gancho&lt;/strong&gt;, para se prender em asteroides ou puxar objetos até o personagem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internacionalização&lt;/strong&gt;, para que o jogo pudesse ser jogado em português e inglês. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com esse escopo, uma partida poderia demorar bastante. Então, seria necessário ter um sistema de salvamento. Comecei criando esse sistema, que me tomou praticamente o primeiro dia inteiro, além de dedicar tempo ao planejamento da estrutura geral do jogo.&lt;/p&gt;

&lt;p&gt;Aqui vai meu sketch inicial do projeto e a versão gerada por IA:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitjbmqzzrh225m9od017.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitjbmqzzrh225m9od017.png" alt="Image description" width="800" height="1144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt9f4h0ad4d22hz731wr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbt9f4h0ad4d22hz731wr.png" alt="Image description" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Recriando toda a teoria da gravidade...
&lt;/h2&gt;

&lt;p&gt;No segundo dia, parti para um dos maiores desafios técnicos: fazer um sistema de movimentação com gravidade em planetoides. A ideia parecia simples na teoria: basta fazer a gravidade sempre apontar para o centro do planeta. Na prática... foi bem mais trabalhoso do que parecia.&lt;/p&gt;

&lt;p&gt;A solução que encontrei foi calcular todos os movimentos (andar, pular, gravidade) como se estivessem num plano cartesiano normal. Só no final dos cálculos é que eu rotacionava o movimento, alinhando para que ele ficasse na direção correta em relação ao planeta. Na minha opinião, ficou bem suave e intuitivo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_on_floor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input_x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_velocity&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_velocity&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Suaviza o movimento lateral no ar&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gravity_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_planet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;global_position&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;direction_to_center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gravity_center&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;global_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;target_rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction_to_center&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="c1"&gt;# -PI/2 para alinhar com a direção do planeta&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;local_rotated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_local_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Aplica a rotação do jogador à velocidade local&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_rotated&lt;/span&gt; &lt;span class="c1"&gt;# Atualiza a velocidade do jogador com a velocidade local rotacionada&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;_gravity_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Aplica a velocidade da gravidade&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;_jetpack_velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;global_rotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Aplica a velocidade do jetpack rotacionada pela rotação do jogador&lt;/span&gt;
&lt;span class="n"&gt;move_and_slide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No jogo há basicamente dois modos de física:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dentro de um planeta, onde é calculado a gravidade, movimentos laterais e etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No espaço, onde a câmera fica fixa, a gravidade é desligada, mas agora você pode girar o personagem e impulsioná-lo na direção que ele está. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claro que surgiram problemas no caminho. Nas builds Web, por exemplo, a física começava a apresentar erros, provavelmente por conta da perda de precisão dos floats. Depois de muito quebrar a cabeça, descobri que usar colisão &lt;strong&gt;quadrada&lt;/strong&gt; no personagem e &lt;strong&gt;circular&lt;/strong&gt; no planeta resolvia vários desses problemas. Se o personagem usasse uma colisão do tipo cápsula ou redonda, acontecia um bug estranho: ele se movia mais rápido para um lado do que para o outro, e esse erro ia acumulando aos poucos, ficando cada vez pior.&lt;/p&gt;




&lt;h2&gt;
  
  
  A corrida contra o tempo e uso de IA
&lt;/h2&gt;

&lt;p&gt;Nos cinco dias seguintes, minha rotina foi basicamente trabalhar o dia todo e só conseguir mexer no jogo bem tarde da noite, quando já estava exausto e com a cabeça pouco produtiva. Por isso, optei por focar na criação dos &lt;strong&gt;assets&lt;/strong&gt;: sprites, planetas, itens, sons, músicas e afins.&lt;/p&gt;

&lt;p&gt;E é aqui que entro em um tema meio polêmico: o uso de &lt;strong&gt;inteligência artificial na criação de assets para jogos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sem dúvida, esse é um assunto que gera bastante discussão na comunidade de desenvolvimento, e com razão. A IA se tornou uma ferramenta extremamente poderosa, principalmente para quem trabalha sozinho ou em equipes pequenas, como é o caso de muita gente em game jams. Quando o tempo é apertado e nem sempre temos habilidades em todas as áreas, como arte, música ou efeitos sonoros, ela acaba sendo uma aliada bem interessante.&lt;/p&gt;

&lt;p&gt;No meu caso, eu não sou artista profissional. Produzir todos os assets do zero: sprites, efeitos sonoros, músicas, interfaces... dentro de poucos dias simplesmente, não seria viável. Então sim, recorri a ferramentas de IA para acelerar parte desse processo.&lt;/p&gt;

&lt;p&gt;Mas acho essencial refletir sobre como e por que usar IA. Ela não deve ser encarada como um substituto da criatividade humana, pelo menos não nesse momento, mas sim como uma extensão dela. Usei IA para transformar ideias que eu já tinha em algo mais concreto, como rascunhos visuais, texturas e até sons. Mas, na prática, quase tudo exigiu bastante trabalho manual: ajustar, editar, retrabalhar e encaixar no estilo e na proposta do jogo.&lt;/p&gt;

&lt;p&gt;A IA, nesse contexto, acaba sendo apenas mais uma ferramenta, assim como é, por exemplo, um gerador de efeitos sonoros como o &lt;a href="https://sfxr.me/" rel="noopener noreferrer"&gt;jsfxr - 8 bit sound maker&lt;/a&gt;, que também usei no projeto. Ela resolve um problema prático, permitindo que uma ideia se transforme em algo jogável, sem que você trave nas partes onde não domina 100%.&lt;/p&gt;

&lt;p&gt;Claro que eu também entendo, e concordo em vários pontos, com quem traz críticas. Existem preocupações bem válidas sobre como os modelos são treinados, muitas vezes usando trabalhos de artistas sem consentimento, e sobre como isso pode afetar o mercado criativo e desvalorizar o trabalho de profissionais.&lt;/p&gt;

&lt;p&gt;Na minha opinião, boa parte dos problemas que vemos hoje não são causados exatamente pela IA em si, mas pela forma como todo o sistema foi estruturado. A concentração de tecnologia nas mãos de poucos, todo o modelo de negócios usado, tudo isso faz parte do problema. E esse debate, honestamente, vai muito além da tecnologia em si.&lt;/p&gt;

&lt;p&gt;No contexto de uma game jam, que é um ambiente de aprendizado, prototipagem e experimentação, vejo o uso da IA como algo totalmente válido, desde que usado de forma consciente, honesta e transparente. Inclusive, isso é abordado nas próprias regras dessa jam específica.&lt;/p&gt;

&lt;p&gt;Mas acho que, como desenvolvedores, é nosso papel continuar questionando e discutindo como construir um cenário onde essas ferramentas possam existir de forma mais justa, ética e equilibrada, beneficiando a todos, desde quem cria, quem desenvolve e quem joga.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthttvhremyvc89wkdyjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthttvhremyvc89wkdyjr.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  O desenvolvimento do Gatonauta: Problemas de Prazo
&lt;/h2&gt;

&lt;p&gt;Voltando ao desenvolvimento, nesse tempo eu criei os primeiros assets de planetas, do personagem, de alguns itens e recursos, mas nada ainda estava funcional. Foi só lá pelo sétimo dia que finalmente consegui concluir partes mais importantes, como o sistema de inventário e a coleta de recursos. Nesse tempo, eu já havia riscado várias partes do meu escopo inicial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Survival&lt;/strong&gt;: No caso do meu conceito, survival seria só mais uma camada do jogo. Se a base nem está completa, essa camada mais atrapalharia do que ajudaria.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combate&lt;/strong&gt;: infelizmente, fazer sistemas de combate com tão pouco tempo livre não iria rolar. Fica para a próxima.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gancho&lt;/strong&gt;: isso foi riscado com toda a parte de mineração no espaço, mas seria muito legal de ter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diálogos&lt;/strong&gt;: cheguei a pensar em toda uma história envolvendo o Gatonauta e seu assistente, o GatoGPT (que é esse drone que fica destruindo as coisas).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dungeons&lt;/strong&gt;: outro plano riscado por falta de tempo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Me concentrei em fazer o básico funcionar: movimentação, coleta de recursos, crafting e construção. E, claro, a parte de exploração com os planetas. Só que precisaria, pelo menos, ter um gameplay completo. Então decidi focar em um único objetivo simples: consertar a nave. Para isso, o jogador precisaria obter três peças diferentes. Planejei a receita dessas peças e os passos para obtê-las. Não quis deixar muito complexo, mas algo que fosse viável e divertido para a maioria dos jogadores, sem muito grind.&lt;/p&gt;

&lt;p&gt;Tentei ao máximo abstrair e usar os signals do Godot para poder reutilizar as classes e fazer o jogo ser facilmente expansível, na prática, se eu quisesse criar mais recursos, nodes, ou estruturas, era só criar instâncias dos objetos existentes, com os novos dados e texturas.&lt;/p&gt;

&lt;p&gt;Todas as receitas, crafting e upgrades usam dicionários simples para definição, algumas vezes, uso um tipo especial de objeto para as receitas, mas continua sendo baseado em dicionários:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="c1"&gt;## Dicionário de custos das estruturas&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_COST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"STORAGE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"WORKBENCH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"BAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;## Dicionário de prefabs das estruturas&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_PREFAB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PackedScene&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scenes/structures/furnace.tscn"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scenes/structures/3d_printer.tscn"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STRUCTURE_RECIPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FURNACE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/charcoal.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/bar.tres"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"DAMAGED_SHIP"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/fix-ship.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"3D_PRINTER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-1.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-2.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"res://scripts/data/recipes/ship-piece-3.tres"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UPGRADES_RECIPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"FIX_JETPACK"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"PLANETOID_INDICATOR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"BETTER_JETPACK"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"CHARCOAL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"FASTER_HARVEST"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"WOOD"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"COBBLESTONE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"MORE_RESOURCES"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"ORE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"GEAR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eu gostaria de ter polido mais a UI, mas percebi que, a cada nova feature, eu demorava muito para finalizar. Isso deixou clara minha falta de experiência com a UI do Godot. Então decidi deixar a UI mais simples, mas funcional. Nada de ícones ou efeitos, só texto. A ideia era que o jogador conseguisse entender o que fazer e como fazer, mesmo que não fosse tão bonito.&lt;/p&gt;

&lt;p&gt;Gastei o nono dia testando e resolvendo bugs, preparei uma tela de créditos e finalizei o jogo. No décimo dia, eu tinha trabalho, então só consegui ajustar pequenos bugs que escaparam.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Participar dessa jam foi, sem dúvidas, uma experiência incrível. Além de aprender muita coisa nova, pude testar ideias que estavam na gaveta há tempos, como o sistema de gravidade em planetas, integração contínua e até experimentar o uso de IA de forma mais consistente no desenvolvimento.&lt;/p&gt;

&lt;p&gt;O &lt;em&gt;Gatonauta&lt;/em&gt; ficou longe de tudo que sonhei no início, mas isso não é necessariamente algo ruim. Faz parte do processo entender os próprios limites, ajustar o escopo e transformar uma ideia gigante em algo que, pelo menos, funcione, seja divertido e transmita uma proposta.&lt;/p&gt;

&lt;p&gt;Com certeza saio dessa jam com ainda mais vontade de continuar desenvolvendo, melhorar meus processos, entender melhor meus pontos fortes e, principalmente, meus pontos fracos.&lt;/p&gt;

&lt;p&gt;Se você leu até aqui, muito obrigado. Espero que esse relato tenha te inspirado, ajudado ou, quem sabe, te dado aquele empurrãozinho para participar da sua primeira (ou próxima) game jam.&lt;/p&gt;

&lt;p&gt;E se quiser jogar o jogo, ele está disponível em:&lt;br&gt;
&lt;a href="https://stealthc.itch.io/gatonauta" rel="noopener noreferrer"&gt;Gatonauta by StealthC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5itqxfzqimonawuskff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5itqxfzqimonawuskff.png" alt="Image description" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>gdscript</category>
    </item>
    <item>
      <title>[EN] Introduction to Frida: Real-Time Memory Manipulation with TypeScript (and DOOM)</title>
      <dc:creator>StealthC</dc:creator>
      <pubDate>Wed, 11 Dec 2024 00:59:52 +0000</pubDate>
      <link>https://dev.to/stealthc/en-introduction-to-frida-real-time-memory-manipulation-with-typescript-and-doom-357d</link>
      <guid>https://dev.to/stealthc/en-introduction-to-frida-real-time-memory-manipulation-with-typescript-and-doom-357d</guid>
      <description>&lt;p&gt;Translation: Este artigo também está &lt;a href="https://dev.to/stealthc/pt-br-introducao-ao-frida-como-manipular-memoria-em-tempo-real-com-typescript-e-doom-5167"&gt;disponível em Português&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The other day, I was reading updates on the website of a tool I follow called &lt;a href="https://frida.re/docs/home/" rel="noopener noreferrer"&gt;Frida&lt;/a&gt;, which is, as they describe it, a "Greasemonkey for native programs." In other words, it’s a toolkit with its own API to analyze, interact, and manipulate running programs, supporting multiple platforms.&lt;/p&gt;

&lt;p&gt;Anyway, I came across this &lt;a href="https://frida.re/news/2024/09/06/frida-16-5-0-released/" rel="noopener noreferrer"&gt;September 2024 update&lt;/a&gt; that not only announced some new features but also showcased an interesting use case. It involved the game "Doom + Doom II," which was released recently. In the example, they demonstrated a tool that scans memory to locate where the ammo count is stored and, with remarkable ease, creates a sort of Cheat Engine to maintain infinite ammo. I loved how practical this example was and decided to recreate it while detailing every step here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Frida
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, Frida is available for multiple platforms. Here, I’m using Windows and already have Python installed, so I’ll simplify the installation process by using pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;frida-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Usage Example
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;frida-tools&lt;/em&gt; package comes with several utilities to interact with the Frida ecosystem (you can learn more by visiting the &lt;a href="https://frida.re/docs/home/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A practical example is using &lt;code&gt;frida&lt;/code&gt; to attach directly to a running program. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;frida &lt;span class="nt"&gt;-n&lt;/span&gt; CalculatorApp.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens Frida and attaches it to the process called &lt;code&gt;CalculatorApp.exe&lt;/code&gt;, which is the Windows calculator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gxoomcf6jamv7lr22v0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gxoomcf6jamv7lr22v0.png" alt="Image demonstrating Frida execution" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this new prompt, you can use several built-in functions and call JavaScript directly to access the application’s memory, registers, interrupts, etc.&lt;/p&gt;

&lt;p&gt;It’s great for quick tests, but the real power comes from creating your own scripts, known as "agents."&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing the API Language
&lt;/h2&gt;

&lt;p&gt;You can write these agent scripts in various languages. Frida offers support for Python, JavaScript, C, Go, Swift, and others.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For this example, I’ll go with JavaScript—or even better, TypeScript, which provides autocomplete and quick access to documentation, as shown below:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp6ccpyglnz6h3qrak5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp6ccpyglnz6h3qrak5v.png" alt="Image showing TypeScript documentation support in VS Code" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;
Documentation and autocomplete—some of the best things about modern IDEs (until AI came along)






&lt;h2&gt;
  
  
  Setting Up the Project Structure
&lt;/h2&gt;

&lt;p&gt;Frida’s documentation for JavaScript suggests cloning the following repository to start developing your "agent": (&lt;a href="https://github.com/oleavr/frida-agent-example" rel="noopener noreferrer"&gt;https://github.com/oleavr/frida-agent-example&lt;/a&gt;). So, I’ll clone it, install the dependencies, and open it in VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/oleavr/frida-agent-example.git doom_example
&lt;span class="nb"&gt;cd&lt;/span&gt; .&lt;span class="se"&gt;\d&lt;/span&gt;oom_example&lt;span class="se"&gt;\&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository simplifies programming in TypeScript and compiling JavaScript to be used by Frida. It comes with some scripts in &lt;code&gt;package.json&lt;/code&gt;, like &lt;code&gt;pnpm watch&lt;/code&gt;, which watches and compiles the &lt;code&gt;./agent/index.ts&lt;/code&gt; file automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;However, in the latest versions of Frida, it can directly read our TypeScript, so we don’t need to compile to &lt;code&gt;.js&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  One More Thing Before We Continue
&lt;/h2&gt;

&lt;p&gt;It’s likely, as was the case during my tests, that the repository has some outdated dependencies. Don’t forget to run &lt;code&gt;npm update --save&lt;/code&gt; to update the dependencies, especially the typings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic Agent
&lt;/h2&gt;

&lt;p&gt;Let’s start with the same example from the original post, adding some comments, typings, and adapting it for TypeScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important note: If you use plain JavaScript for the agent, you can directly call any function or variable declared in the script. However, when using TypeScript, functions and variables are not added to the global object. You must export them to the &lt;code&gt;globalThis&lt;/code&gt; object. I’m making this export at the end of the script.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NativePointer&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Scans the process memory for a pattern&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MatchPattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerateMallocRanges&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scanSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Further filters results for those containing a specific value&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readU32&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Filtered down to:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Converts a numeric value into a pattern for a 32-bit unsigned integer&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;patternFromU32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MatchPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchPattern&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reduce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;patternFromU32&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;patternFromU32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Attaching Frida to the Game
&lt;/h2&gt;

&lt;p&gt;Now, let’s run Frida and attach it to the game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;frida &lt;span class="nt"&gt;-n&lt;/span&gt; doom_gog.exe &lt;span class="nt"&gt;-l&lt;/span&gt; agent/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the game is running so &lt;code&gt;frida&lt;/code&gt; can locate the process using the &lt;code&gt;-n&lt;/code&gt; option. If you’re unsure about the process name, use &lt;code&gt;frida-ps&lt;/code&gt; to list all running processes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v0szhg34e6cj113t5xf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v0szhg34e6cj113t5xf.png" alt="Image demonstrating Frida execution" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding a Value in Memory (a needle in a haystack)
&lt;/h2&gt;

&lt;p&gt;In the original post, the author searches for the memory location where the ammo count is stored. But let’s switch things up and look for where our health (or "HP," "life," or whatever you call it) is stored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56fgsex01p02qlnb8iq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56fgsex01p02qlnb8iq9.png" alt="Gameplay image of Doom" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;
Let’s hope this 100% is an integer...



&lt;p&gt;In the open Frida console, we’ll execute our &lt;code&gt;scan&lt;/code&gt; function with a pattern for the number 100 as an argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; scan(patternFromU32(100))
Found 8073 matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoa, 8073 matches for the number 100! That seems like a lot, but not too bad. We need to narrow it down. First, let’s take some damage in the game:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqm7kub3qfjejrkiublc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqm7kub3qfjejrkiublc.gif" alt="The player shoots at a barrel, causing it to explode" width="634" height="375"&gt;&lt;/a&gt;&lt;/p&gt;
Take that, barrel filled with mysterious green liquid!



&lt;p&gt;Now that our health has dropped to 67%, let’s further filter our results using the &lt;code&gt;reduce&lt;/code&gt; function we created in the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; reduce(67)
Filtered down to:
["0x179b96a0d0c","0x179f5499984"]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re left with two possibilities. Which one is it? Hang on, I’ll figure it out…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydnsmh3wpuej33b2t73s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydnsmh3wpuej33b2t73s.gif" alt="The player picks up a blue potion" width="500" height="295"&gt;&lt;/a&gt;&lt;/p&gt;
Drinking this blue potion I found on the ground...



&lt;p&gt;Now that our health is at 68%, let’s filter the results again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; reduce(68)
Filtered down to:
["0x179b96a0d0c","0x179f5499984"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm, we still have two addresses left. It’s possible that health is stored in two different memory locations or represents two separate update states. Maybe I shouldn’t have tested this by both losing and then regaining health, but oh well—let’s move on with the experiment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating Dynamic Watchpoints
&lt;/h2&gt;

&lt;p&gt;Let’s proceed with the article by creating a helper function to set watchpoints. This function will take the memory address, the size of the stored data, and the break condition type as arguments. We’ll use &lt;code&gt;w&lt;/code&gt; to specify that it should only trigger on &lt;code&gt;write&lt;/code&gt; conditions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don’t forget to register the function with &lt;code&gt;globalThis&lt;/code&gt; at the end.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;installWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NativePointerValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HardwareWatchpointConditions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerateThreads&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n=== Handler got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exception at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentThreadId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;breakpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;single-step&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsetHardwareWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;Disabled hardware watchpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;Passing to application&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHardwareWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;installWatchpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;installWatchpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Identifying the Code Location (via Watchpoints)
&lt;/h2&gt;

&lt;p&gt;Now the goal is to find where in the game’s code the damage calculation is executed. Let’s start by registering a watchpoint for the first address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; installWatchpoint(ptr('0x179b96a0d0c'), 4, 'w')
Ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After inserting the watchpoint, I tried taking more damage, but nothing happened. However, when I picked up a potion, the code was triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt;
=== Handler got single-step exception at 0x7ff6332f7b08
        Disabled hardware watchpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates that the first address we located is likely used in the context of health recovery. Since we’re more interested in the damage context, I tested the other address. This time it triggered when I took damage in the game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; installWatchpoint(ptr('0x179f5499984'), 4, 'w')
Ready
[Local::doom_gog.exe ]-&amp;gt;
=== Handler got single-step exception at 0x7ff6332fafcc
        Disabled hardware watchpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Finding the Exact Code Execution Location
&lt;/h2&gt;

&lt;p&gt;With the address where the value change occurs, &lt;code&gt;0x179f5499984&lt;/code&gt;, let’s find its relative offset to the program’s base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; healthCode = ptr('0x7ff6332fafcc')
"0x7ff6332fafcc"
[Local::doom_gog.exe ]-&amp;gt; healthModule = Process.getModuleByAddress(healthCode)
{
    "base": "0x7ff633020000",
    "name": "doom_gog.exe",
    "path": "F:\\GOG Games\\DOOM + DOOM II\\doom_gog.exe",
    "size": 15450112
}
[Local::doom_gog.exe ]-&amp;gt; offset = healthCode.sub(healthModule.base)
"0x2dafcc"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we know the address is in the &lt;code&gt;doom_gog.exe&lt;/code&gt; module and its position relative to the program’s base is &lt;code&gt;0x2dafcc&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checking the Address in a Disassembler
&lt;/h2&gt;

&lt;p&gt;We can use any debugger with disassembler support to analyze this segment and determine its functionality. You can use tools like radare2, IDA, or GHidra. In this example, I used x64dbg and navigated to the correct address by pressing Ctrl+G and entering the expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doom_gog.exe+0x2dafcc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... which produced the following disassembly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l8t9xjmsosewq2428no.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l8t9xjmsosewq2428no.png" alt="Address displayed in x64dbg" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we see the instruction executed immediately after the health value decreases. We can hypothesize that &lt;code&gt;[rsi+24]&lt;/code&gt; holds our health value and &lt;code&gt;r15d&lt;/code&gt; represents the damage received. Let’s focus on the damage received for now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating Interceptors to Dynamically Receive Data
&lt;/h2&gt;

&lt;p&gt;Following the example from the original article, let’s create an interceptor for this instruction to capture the amount of damage received.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since this function is executed directly in the script, we don’t need to add it to &lt;code&gt;globalThis&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;doom_gog.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2dafcc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;X64CpuContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;damageReceived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Damage Received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;damageReceived&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After saving the script, if we continue to take damage in the game, the Frida console will print messages showing the amount of damage:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lueekdnk2eflmrn3777.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lueekdnk2eflmrn3777.png" alt="Damage displayed in the console" width="417" height="131"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Now, the Ultimate Manipulation!
&lt;/h2&gt;

&lt;p&gt;If we want, as in the original example, to completely ignore the damage, we just need to move the interceptor to the previous instruction (address &lt;code&gt;0x2dafc8&lt;/code&gt;) and dynamically set the register value to zero!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;doom_gog.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2dafc8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;X64CpuContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;damageReceived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Damage Received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;damageReceived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUInt32&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, but we will just ignore it`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y2edw88ggyxks0wvfh1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y2edw88ggyxks0wvfh1.gif" alt="Animation of the player taking damage but the health value doesn't decrease" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;
Who needs IDDQD?






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This exercise was fantastic for exploring what a tool like Frida can offer. This dynamic way of handling data is also incredibly interesting for learners. The example with Doom was perfect due to its simplicity. Keep in mind that creating a game cheat like this is merely "scratching the surface" of possibilities. Tools like Frida can—and should—be extensively used for tasks like malware analysis, software behavior studies, security assessments, and more.&lt;/p&gt;

</description>
      <category>frida</category>
      <category>reverse</category>
      <category>typescript</category>
      <category>hacking</category>
    </item>
    <item>
      <title>[PT-BR] Introdução ao Frida: Como Manipular Memória em Tempo Real com TypeScript (e DOOM)</title>
      <dc:creator>StealthC</dc:creator>
      <pubDate>Wed, 11 Dec 2024 00:47:11 +0000</pubDate>
      <link>https://dev.to/stealthc/pt-br-introducao-ao-frida-como-manipular-memoria-em-tempo-real-com-typescript-e-doom-5167</link>
      <guid>https://dev.to/stealthc/pt-br-introducao-ao-frida-como-manipular-memoria-em-tempo-real-com-typescript-e-doom-5167</guid>
      <description>&lt;p&gt;Tradução: This post is also &lt;a href="https://dev.to/stealthc/en-introduction-to-frida-real-time-memory-manipulation-with-typescript-and-doom-357d"&gt;available in English&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Outro dia eu estava lendo as notícias do site de uma ferramenta que eu acompanho, chamado &lt;a href="https://frida.re/docs/home/" rel="noopener noreferrer"&gt;Frida&lt;/a&gt; que é, como eles mesmo dizem no site, um Greasemonkey para programas nativos, ou seja, é um kit de ferramentas com uma API própria que ajuda a analisar, interagir e manipular programas em execução com suporte para várias plataformas diferentes.&lt;/p&gt;

&lt;p&gt;Mas enfim, me deparei com essa &lt;a href="https://frida.re/news/2024/09/06/frida-16-5-0-released/" rel="noopener noreferrer"&gt;notícia de setembro de 2024&lt;/a&gt; que, além de informar novidades no projeto, demonstra uma utilização bastante interessante. Usa como base, o jogo "Doom + Doom II", que foi lançado a pouco tempo. No exemplo, ele cria uma ferramenta que busca na memória, onde número de munição é armazenado e com muita facilidade, cria uma espécie de Cheat Engine para manter as balas infinitas. Eu adorei a praticidade do exemplo e resolvi refazê-lo, mas detalhando aqui cada etapa.&lt;/p&gt;




&lt;h2&gt;
  
  
  Instalando o Frida
&lt;/h2&gt;

&lt;p&gt;Como eu disse no início, o Frida está disponível para várias plataformas. Aqui, estou usando o Windows e já tenho o python instalado, então vou simplificar o processo de instalação usando o pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;frida-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Exemplo de utilização
&lt;/h2&gt;

&lt;p&gt;O pacote &lt;em&gt;frida-tools&lt;/em&gt; vem com vários utilitários para trabalhar com o ecossistema do Frida (Você pode saber mais sobre eles visitando a &lt;a href="https://frida.re/docs/home/" rel="noopener noreferrer"&gt;documentação oficial&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Porém, um exemplo bem prático é usar o &lt;code&gt;frida&lt;/code&gt; para anexar-se diretamente em um programa em execução, por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;frida &lt;span class="nt"&gt;-n&lt;/span&gt; CalculatorApp.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vai abrir o frida e se anexar no processo chamado &lt;code&gt;CalculatorApp.exe&lt;/code&gt;, que é a calculadora do windows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gxoomcf6jamv7lr22v0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gxoomcf6jamv7lr22v0.png" alt="Imagem demonstrando a execução do frida" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A partir desse novo prompt, você pode usar diversas funções embutidas e chamar JavaScript diretamente para acessar a memória do aplicativo, registros, interrupções, etc.&lt;/p&gt;

&lt;p&gt;É ótimo para fazer testes rápidos, mas o melhor mesmo é fazer seus próprios scripts, os chamados "agentes".&lt;/p&gt;




&lt;h2&gt;
  
  
  Escolhendo a linguagem da API
&lt;/h2&gt;

&lt;p&gt;Você pode escrever esses scripts de agentes em várias linguagens diferentes, o Frida oferece já suporte para linguagens como Python, JavaScript, C, Go e Swift, dentre outras.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Neste meu exemplo, vou de JavaScript, ou melhor ainda, TypeScript, que oferece algum suporte para autocompletar e verificar rapidamente a documentação, conforme mostrado abaixo:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp6ccpyglnz6h3qrak5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp6ccpyglnz6h3qrak5v.png" alt="Imagem mostrando o recurso de documentação do TypeScript no VS Code)" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;
Documentação e autocompletar, as melhores coisas de IDE modernos (até a chegada das IAs)






&lt;h2&gt;
  
  
  Preparando a estrutura do projeto
&lt;/h2&gt;

&lt;p&gt;A documentação do Frida sobre JavaScript sugere clonar o seguinte repositório para começar a desenvolver seu "agente": (&lt;a href="https://github.com/oleavr/frida-agent-example" rel="noopener noreferrer"&gt;https://github.com/oleavr/frida-agent-example&lt;/a&gt;), então vou clonar ele, instalar as dependências e abrir no VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/oleavr/frida-agent-example.git doom_example
&lt;span class="nb"&gt;cd&lt;/span&gt; .&lt;span class="se"&gt;\d&lt;/span&gt;oom_example&lt;span class="se"&gt;\&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ideia do repositório é simplificar a tarefa de programar em TypeScripte compilar o JavaScript para ser usado pelo Frida, ele vem com alguns scripts no &lt;code&gt;package.json&lt;/code&gt;, como &lt;code&gt;npm watch&lt;/code&gt; que observa e compila automaticamente o arquivo &lt;code&gt;./agent/index.ts&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Porém, nas últimas versões do Frida, ele consegue ler automaticamente nosso TypeScript, então não precisamos compilar o .js.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Mais uma coisa antes de seguirmos
&lt;/h2&gt;

&lt;p&gt;É bem provável, como foi o caso quando eu estava testando, que o repositório esteja com algumas dependências já ultrapassadas, não esqueça de rodar &lt;code&gt;npm update --save&lt;/code&gt; para atualizar as dependências, principalmente as tipagens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agente básico
&lt;/h2&gt;

&lt;p&gt;Vamos começar usando o mesmo exemplo da postagem original, adicionando alguns comentários, inserindo tipagens e adaptando para o TypeScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uma observação importante: Se você usar JavaScript puro para fazer o agente, poderá chamar diretamente qualquer função ou variável declarada no script. Porém, ao usar TypeScript, as funções e variáveis não são inseridas no objeto global e portanto, você não conseguirá acessá-las diretamente a menos as exporte para o objeto &lt;code&gt;globalThis&lt;/code&gt;. Estou fazendo essa exportação logo no fim do script.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NativePointer&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Escaneia a memória do processo em busca de um padrão&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;MatchPattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;locations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerateMallocRanges&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scanSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;matches&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Filtra ainda mais os resultados para aqueles que contém um valor específico&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readU32&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Filtered down to:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Converte um valor numérico em um padrão para número inteiro unsigened de 32 bits&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;patternFromU32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MatchPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchPattern&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reduce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;patternFromU32&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;patternFromU32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Anexando o Frida ao jogo
&lt;/h2&gt;

&lt;p&gt;Agora rodamos o Frida e anexamos ao jogo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;frida &lt;span class="nt"&gt;-n&lt;/span&gt; doom_gog.exe &lt;span class="nt"&gt;-l&lt;/span&gt; agent/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você deve estar com o jogo em execução para que o &lt;code&gt;frida&lt;/code&gt; possa encontrar o processo usando a opção &lt;code&gt;-n&lt;/code&gt;, se você tiver dúvidas sobre o nome do processo, use o &lt;code&gt;frida-ps&lt;/code&gt; para listar todos os processos em execução.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v0szhg34e6cj113t5xf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v0szhg34e6cj113t5xf.png" alt="Imagem demonstrando a execução do frida" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Encontrando um valor na memória (uma palha em um agulheiro)
&lt;/h2&gt;

&lt;p&gt;Na postagem original, o autor busca na memória o local onde o valor da munição é armazenado, mas vamos tentar diferente e procurar onde está nossa vida (ou "saúde", "life", "HP", ou como você gostar de chamar)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56fgsex01p02qlnb8iq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56fgsex01p02qlnb8iq9.png" alt="Gameplay image of Doom" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;
Vamos torcer para que esse 100% seja um número inteiro…



&lt;p&gt;No console aberto do frida, vamos executar nossa função &lt;code&gt;scan&lt;/code&gt; com um padrão do número 100 criado como argumento.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; scan(patternFromU32(100))
Found 8073 matches`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eita, 8073 resultados para o número 100, isso parece muito, mas nem tanto, precisamos diminuir as possibilidades, para isso, vamos primeiro sofrer algum dano no jogo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqm7kub3qfjejrkiublc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqm7kub3qfjejrkiublc.gif" alt="O jogador atira em um barril, que explode" width="634" height="375"&gt;&lt;/a&gt;&lt;/p&gt;
Tome isto, barril com líquido verde estranho!



&lt;p&gt;Agora que reduzimos nossa vida para 67%, vamos filtrar ainda mais nossa busca usando a função criada no script &lt;code&gt;reduce&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; reduce(67)
Filtered down to:
["0x179b96a0d0c","0x179f5499984"]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ainda sobraram duas possibilidades, qual será? Espera, que já resolvo isso…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydnsmh3wpuej33b2t73s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydnsmh3wpuej33b2t73s.gif" alt="O jogador pega uma poção azul" width="500" height="295"&gt;&lt;/a&gt;&lt;/p&gt;
Bebendo desse frasco azul que encontrei no chão…



&lt;p&gt;Agora que estamos com 68% de vida, vamos filtrar novamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; reduce(68)
Filtered down to:
["0x179b96a0d0c","0x179f5499984"]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ué, continuamos com os dois endereços... É provável que a vida esteja mesmo sendo armazenada em dois locais diferentes na memória ou ainda podem ser estados diferentes de atualização. Talvez eu não devesse ter testado sofrendo e depois recuperando vida, mas enfim, vamos seguir com a experiência...&lt;/p&gt;




&lt;h2&gt;
  
  
  Criando watchpoints dinamicamente
&lt;/h2&gt;

&lt;p&gt;Vamos continuar com o artigo e criar uma função helper para designar watchpoints. Essa função vai receber como argumentos, o endereço da memória, o tamanho do dado armazenado e o tipo de condição para o break, que posteriormente vamos usar &lt;code&gt;w&lt;/code&gt; para dizer para ele parar apenas em condições de &lt;code&gt;write&lt;/code&gt;, ou seja, de escrita.&lt;br&gt;
&lt;em&gt;Não se esqueça de registrar a função no &lt;code&gt;globalThis&lt;/code&gt;, no final.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;installWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NativePointerValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HardwareWatchpointConditions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerateThreads&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExceptionHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n=== Handler got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exception at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentThreadId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;breakpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;single-step&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsetHardwareWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;Disabled hardware watchpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;Passing to application&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHardwareWatchpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;installWatchpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;installWatchpoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Obtendo a localização do código (via watchpoints)
&lt;/h2&gt;

&lt;p&gt;O objetivo agora é obter onde no código do jogo, o dano é executado. Então vamos começar registrando o watchpoint do primeiro endereço:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; installWatchpoint(ptr('0x179b96a0d0c'), 4, 'w')
Ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após inserir o watchpoint, tentei sofrer mais dano, porém nada aconteceu. Mas aí, ao pegar uma poção, o código é executado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt;
=== Handler got single-step exception at 0x7ff6332f7b08
        Disabled hardware watchpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, esse primeiro endereço, endereço que localizamos provavelmente é usado no contexto de recuperação de vida.&lt;br&gt;
Estamos mais interessados no contexto de dano sofrido, então, testei com o outro endereço e dessa vez sim ele ativou quando eu tomei dano no jogo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; installWatchpoint(ptr('0x179f5499984'), 4, 'w')
Ready
[Local::doom_gog.exe ]-&amp;gt;
=== Handler got single-step exception at 0x7ff6332fafcc
        Disabled hardware watchpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Obtendo a localização exata da execução do código
&lt;/h2&gt;

&lt;p&gt;De posse do endereço onde a mudança do valor ocorre &lt;code&gt;0x7ff6332fafcc&lt;/code&gt;, vamos obter o endereço relativo à base do programa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Local::doom_gog.exe ]-&amp;gt; healthCode = ptr('0x7ff6332fafcc')
"0x7ff6332fafcc"
[Local::doom_gog.exe ]-&amp;gt; healthModule = Process.getModuleByAddress(healthCode)
{
    "base": "0x7ff633020000",
    "name": "doom_gog.exe",
    "path": "F:\\GOG Games\\DOOM + DOOM II\\doom_gog.exe",
    "size": 15450112
}
[Local::doom_gog.exe ]-&amp;gt; offset = healthCode.sub(healthModule.base)
"0x2dafcc"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora sabemos que o endereço está no módulo &lt;code&gt;doom_gog.exe&lt;/code&gt; e que sua posição relativa à base do programa é &lt;code&gt;0x2dafcc&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conferindo o endereço em um disassembler
&lt;/h2&gt;

&lt;p&gt;Podemos usar algum debugger com suporte a disasm para ver esse trecho e definir sua funcionalidade, você pode usar qualquer tipo de ferramenta: radare2, ida, GHidra, nesse exemplo eu abri com o x64dbg, e fui para o endereço correto pressionando Ctrl+G e inserindo a expressão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doom_gog.exe+0x2dafcc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... que resultou no disasm abaixo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l8t9xjmsosewq2428no.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l8t9xjmsosewq2428no.png" alt="O endereço mostrado no x64dbg" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aqui, nós temos a instrução que é executada exatamente depois do valor da vida ter diminuído, imaginamos então que &lt;code&gt;[rsi+24]&lt;/code&gt; é o nosso valor de vida e &lt;code&gt;r15d&lt;/code&gt; é o dano recebido. Vamos nos concentrar no dano recebido por enquanto...&lt;/p&gt;




&lt;h2&gt;
  
  
  Criando interceptadores para receber dados dinamicamente
&lt;/h2&gt;

&lt;p&gt;Seguindo o exemplo do artigo original, vamos criar um Interceptador para essa instrução, que vai nos dizer a quantidade de dano recebido.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Como essa função é executada diretamente no script, não precisamos adicionar ao &lt;code&gt;globalThis&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;doom_gog.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2dafcc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;X64CpuContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;damageReceived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Damage Received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;damageReceived&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logo depois de salvar o script, se continuarmos a receber dano no jogo, o console do frida irá imprimir mensagens informando a quantidade de dano:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lueekdnk2eflmrn3777.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lueekdnk2eflmrn3777.png" alt="Dano sendo mostrado no console" width="417" height="131"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  E agora, a manipulação suprema!
&lt;/h2&gt;

&lt;p&gt;Se quisermos, igual ao exemplo original, que o dano seja totalmente ignorado, basta trazer o interceptador para a instrução anterior (endereço &lt;code&gt;0x2dafc8&lt;/code&gt;) e substituir dinamicamente o valor de registro para zero!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Interceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;doom_gog.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x2dafc8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;X64CpuContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;damageReceived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Damage Received: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;damageReceived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUInt32&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;, but we will just ignore it`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r15&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y2edw88ggyxks0wvfh1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8y2edw88ggyxks0wvfh1.gif" alt="Animação do player sofrendo dano, porém, o valor da saúde não diminui" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;
Pra que o IDDQD?






&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Esse exercício foi ótimo para testar o que uma ferramenta como o Frida pode oferecer, essa forma dinâmica de lidar com os dados é muito interessante também para quem está aprendendo, e o exemplo com o Doom foi muito bem-vindo pela simplicidade. Note que ao fazer um cheat de um jogo, estamos apenas "arranhando" a superfície das possibilidades, ferramentas como o Frida podem e devem ser amplamente utilizadas para auxilio e simplificação de análise de malware, estudo de comportamento de software, segurança e outros processos. &lt;/p&gt;

</description>
      <category>frida</category>
      <category>reverse</category>
      <category>typescript</category>
      <category>hacking</category>
    </item>
  </channel>
</rss>
