{"id":1934,"date":"2020-08-27T04:57:40","date_gmt":"2020-08-27T04:57:40","guid":{"rendered":"https:\/\/cml-a.com\/content\/?p=1934"},"modified":"2020-08-27T04:57:40","modified_gmt":"2020-08-27T04:57:40","slug":"lagoon-hitboxes","status":"publish","type":"post","link":"https:\/\/cml-a.com\/content\/2020\/08\/27\/lagoon-hitboxes\/","title":{"rendered":"Lagoon Hitboxes"},"content":{"rendered":"\n<p>This post explains Lagoon_hitbox.ips, a proof-of-concept patch created to enlarge the hitboxes in the game Lagoon for SNES. The patch was really quick+and+dirty. Nonetheless it's posted here:<\/p>\n\n\n\n<p><a href=\"http:\/\/secretplace.cml-a.com\/edits.php\">http:\/\/secretplace.cml-a.com\/edits.php<\/a><\/p>\n\n\n\n<p>What follows is a description of the patch and how it works.<\/p>\n\n\n\n<p>First, here are some useful memory locations<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$01:0502-0503 - NASIR's X position in the map\n$01:0504-0505 - NASIR's Y position in the map\n$01:050A- The direction NASIR is facing.\n\t- 00 means right\n\t- 01 means down\n\t- 02 means left\n\t- 03 means up\n\t\n$01:B710-B717 - The offsets of NASIR's hit box from his position, if he is facing right\n$01:B718-B71F - The offsets of NASIR's hit box from his position, if he is facing down\n$01:B720-B727 - The offsets of NASIR's hit box from his position, if he is facing left\n$01:B728-B730 - The offsets of NASIR's hit box from his position, if he is facing up<\/code><\/pre>\n\n\n\n<p>In Lagoon, the following code is invoked whenever an action button is pressed, even if you're not near anything.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$01\/9BBD AD 0A 05    LDA $050A               ; A = the direction NASIR is facing.\n\t\t\t\t\t     ; \n$01\/9BC0 0A          ASL A                   ; A *= 8\n$01\/9BC1 0A          ASL A                   ;\n$01\/9BC2 0A          ASL A                   ;\n$01\/9BC3 18          CLC                     ;\n$01\/9BC4 69 20       ADC #$20                ; A += 0x20\n\t\t\t\t\t     ; \n\t\t\t\t\t     ; Now A effectively stores an array index with \n\t\t\t\t\t     ; which to load hitbox offsets.\n\t\t\t\t\t     ; If facing right: A = 0x20\n\t\t\t\t\t     ; If facing down: A = 0x28\n\t\t\t\t\t     ; If facing left: A = 0x30\n\t\t\t\t\t     ; If facing up: A = 0x38\n\t\t\t\t\t     ; \n$01\/9BC6 20 C3 B6    JSR $B6C3\t             ; Call CalculatePlayerHitboxDimensions()\n$01\/9BC9 60          RTS                                         <\/code><\/pre>\n\n\n\n<p>                  <\/p>\n\n\n\n<p>The function CalculatePlayerHitboxDimensions() looks like the following<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CalculatePlayerHitboxDimensions:\n\t\t\t\t\t     ; Preconditions: A is set to one of \n\t\t\t\t\t     ;     {0x20, 0x28, 0x30, 0x38} depending on\n\t\t\t\t\t     ;     the direction NASIR is facing, as described \n\t\t\t\t\t     ;     above.\n\t\t\t\t\t     ; Postconditions: $40, $42, $44, and $46 contain \n\t\t\t\t\t     ;     the dimensions of NASIR's hit box; left, \n\t\t\t\t\t     ;     right, top and bottom respectively.\n$01\/B6C3 C2 20       REP #$20                \n$01\/B6C5 A8          TAY                     \n$01\/B6C6 AD 02 05    LDA $0502               ; Load NASIR's X position\n$01\/B6C9 18          CLC                    \n$01\/B6CA 79 F0 B6    ADC $B6F0,y             ; Add left edge hitbox offset to NASIR's X position\n$01\/B6CD 85 40       STA $40                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6CF AD 02 05    LDA $0502 \t\t     ; Load NASIR's X position\n$01\/B6D2 18          CLC                     \n$01\/B6D3 79 F2 B6    ADC $B6F2,y             ; Add right edge hitbox offset to NASIR's X position\n$01\/B6D6 85 42       STA $42                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6D8 AD 04 05    LDA $0504               ; Load NASIR's Y position\n$01\/B6DB 18          CLC   \n$01\/B6DC 79 F4 B6    ADC $B6F4,y             ; Add top edge hitbox offset to NASIR's Y position\n$01\/B6DF 85 44       STA $44                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6E1 AD 04 05    LDA $0504               ; Load NASIR's Y position\n$01\/B6E4 18          CLC                     \n$01\/B6E5 79 F6 B6    ADC $B6F6,y             ; Add bottom edge hitbox offset to NASIR's Y position\n$01\/B6E8 85 46       STA $46                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6EA 29 FF 00    AND #$00FF              ; Clean up and return\n$01\/B6ED E2 20       SEP #$20                \n$01\/B6EF 60          RTS          <\/code><\/pre>\n\n\n\n<p>If you were to skim the code quickly you'd see it loads hitbox dimensions from memory. From that, you might get the impression they are something dynamic. But, they come from a table hard-coded in ROM data. (you can see this based on the particular address they're loaded from).<\/p>\n\n\n\n<p>Just so you know, the hitbox table data contains this (Semantics are left, right, top, bottom)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Facing  \tRaw data\t\t\tPlain hex offsets\t\tSigned dec offsets\t\n------\t\t--------\t\t\t-----------------\t\t------------------\nRight\t\t00 00 19 00 F8 FF 08 00\t\t0h, 19h, FFF8h, 8h\t\t0, 25, -8, 8\t\t\nDown\t\tF0 FF 10 00 00 00 0F 00\t\tFFF0h, 10h, 0h, 0Fh\t\t-16, 16, 0, 15\t\nLeft\t\tE7 FF 00 00 F8 FF 08 00\t\tFFE7h, 0h, FFF8h, 8h\t\t-25, 0, -8, 8\nUp\t\tF0 FF 10 00 F1 FF 00 00\t\tFFF0h, 10h, FFF1h, 0h\t\t-16, 16, -15, 0\t<\/code><\/pre>\n\n\n\n<p>Yes, the game uses slightly differently-sized hitboxes depending on the direction you're facing.<\/p>\n\n\n\n<p>Now, the patch. What this patch does is instead of offsetting NASIR's position by values from this table, it hacks it to offset the position simply by a hardcoded number.  The hardcoded numbers yield bigger hitboxes than the offsets from the table.<\/p>\n\n\n\n<p>It always applies the hitbox of offsets        {-0x30, 0x32, -0x38, 0x30 }     =   {-48, 50, -56, 48 }. The hitbox size is 98x104 which is about 5 times bigger than the default.<\/p>\n\n\n\n<p>The patch modifies just four operations in CalculatePlayerHitboxDimensions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CalculatePlayerHitboxDimensions:\n\t\t\t\t\t     ; Preconditions: A is set to one of {0x20, 0x28, 0x30, 0x38} depending on\n\t\t\t\t\t     ;     the direction NASIR is facing, as described above.\n\t\t\t\t\t     ; Postconditions: $40, $42, $44, and $46 contain the dimensions of \n\t\t\t\t\t     ;     NASIR's hit box; left, right, top and bottom respectively.\n$01\/B6C3 C2 20       REP #$20                \n$01\/B6C5 A8          TAY                     \n$01\/B6C6 AD 02 05    LDA $0502               ; Load NASIR's X position\n$01\/B6C9 18          CLC                    \n$01\/B6CA E9 30 00    SBC #$0030              ; Apply left edge hitbox offset -30h\n$01\/B6CD 85 40       STA $40                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6CF AD 02 05    LDA $0502 \t\t     ; Load NASIR's X position\n$01\/B6D2 18          CLC                     \n$01\/B6D3 69 32 00    ADC #$0032              ; Apply left edge hitbox offset 32h\n$01\/B6D6 85 42       STA $42                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6D8 AD 04 05    LDA $0504               ; Load NASIR's Y position\n$01\/B6DB 18          CLC   \n$01\/B6DC E9 38 00    SBC #$0038              ; Apply left edge hitbox offset -38h\n$01\/B6DF 85 44       STA $44                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6E1 AD 04 05    LDA $0504               ; Load NASIR's Y position\n$01\/B6E4 18          CLC                     \n$01\/B6E5 69 30 00    ADC #$0030              ; Apply left edge hitbox offset 30h\n$01\/B6E8 85 46       STA $46                 ; Store it as an output\n\t\t\t\t\t     ; \n$01\/B6EA 29 FF 00    AND #$00FF              ; Clean up and return\n$01\/B6ED E2 20       SEP #$20                \n$01\/B6EF 60          RTS     <\/code><\/pre>\n\n\n\n<p>And there you have it, the code for the proof-of-concept posted at the link above.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>Here is a small improvement that can be made to the above hack. First, it'd be cleaner to modify the hitbox region offsets in the ROM directly. So let's do that instead.<\/p>\n\n\n\n<p>To re-iterate, the default values are (with semantics left, right, top, bottom)-<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Facing  \tRaw data\t\t\tPlain hex offsets\t\tSigned dec offsets\t\n------\t\t--------\t\t\t-----------------\t\t------------------\nRight\t\t00 00 19 00 F8 FF 08 00\t\t0h, 19h, FFF8h, 8h\t\t0, 25, -8, 8\t\nDown\t\tF0 FF 10 00 00 00 0F 00\t\tFFF0h, 10h, 0h, 0Fh\t\t-16, 16, 0, 15\t\nLeft\t\tE7 FF 00 00 F8 FF 08 00\t\tFFE7h, 0h, FFF8h, 8h\t\t-25, 0, -8, 8\t\nUp\t\tF0 FF 10 00 F1 FF 00 00\t\tFFF0h, 10h, FFF1h, 0h\t\t-16, 16, -15, 0\t<\/code><\/pre>\n\n\n\n<p>The ROM file offsets for each direction are<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Facing\t\tHeaderless ROM file offset\n------\t\t--------------------------\nRight\t\tB710\nDown\t\tB718\nLeft\t\tB720\nUp\t\tB728<\/code><\/pre>\n\n\n\n<p>While we can patch the table manually, it makes for easier testing of changes if you use a patching program.<\/p>\n\n\n\n<p>Here's some C++ code for one:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enum Direction \n{ \n    FacingRight = 0, \n    FacingDown = 1,\n    FacingLeft = 2, \n    FacingUp = 3 \n};\n\nstruct HitboxDir\n{\n    int Left;\n    int Right;\n    int Top;\n    int Bottom;\n};\n\nvoid PushValue(int b, std::vector&lt;unsigned char>* out)\n{\n    if (b >= 0)\n    {\n        assert(b &lt; 256);\n        out->push_back(b); \/\/ little endian\n        out->push_back(0);\n    }\n    else\n    {\n        int u = 0x10000 + b;\n        int low = u &amp; 0xFF;\n        out->push_back(low);\n        u >>= 8;\n        assert(u &lt; 256);\n        int high = u &amp; 0xFF;\n        out->push_back(high);\n    }\n}\n\nint main()\n{\n    FILE* pB = nullptr;\n\n    fopen_s(&amp;pB, \"Lagoon.hitbox.v2.smc\", \"rb\");\n\n    \/\/ Check size\n    fseek(pB, 0, SEEK_END);\n    long sizeB = ftell(pB);\n    fseek(pB, 0, SEEK_SET);\n\n    std::vector&lt;unsigned char> dataB;\n    dataB.resize(sizeB);\n\n    fread(dataB.data(), 1, sizeB, pB);\n\n    fclose(pB);\n\n    HitboxDir allDirs[] =\n    {\n        {0, 25, -8, 8},\n        {-16, 16, 0, 15},\n        {-25, 0, -8, 8},\n        {-16, 16, -15, 0}\n    };\n\n    \/\/ Enlarge hitboxes\n    int ff = 3;\n    int hf = 2;\n\n    allDirs[FacingRight].Right *= ff;\n    allDirs[FacingRight].Top *= hf;\n    allDirs[FacingRight].Bottom *= hf;\n\n    allDirs[FacingDown].Bottom *= ff;\n    allDirs[FacingDown].Left *= hf;\n    allDirs[FacingDown].Right *= hf;\n\n    allDirs[FacingLeft].Left *= ff;\n    allDirs[FacingLeft].Top *= hf;\n    allDirs[FacingLeft].Bottom *= hf;\n\n    allDirs[FacingUp].Top *= ff;\n    allDirs[FacingUp].Left *= hf;\n    allDirs[FacingUp].Right *= hf;\n\n    \/\/ Transfer hitbox info into byte data\n    std::vector&lt;unsigned char> hitboxBytes;\n\n    for (int i = 0; i &lt; 4; ++i)\n    {\n        PushValue(allDirs[i].Left, &amp;hitboxBytes);\n        PushValue(allDirs[i].Right, &amp;hitboxBytes);\n        PushValue(allDirs[i].Top, &amp;hitboxBytes);\n        PushValue(allDirs[i].Bottom, &amp;hitboxBytes);\n    }\n\n    \/\/ Patch the new tables in\n    int destOffset = 0xB710;\n    for (size_t i = 0; i &lt; hitboxBytes.size(); ++i)\n    {\n        dataB[destOffset + i] = hitboxBytes[i];\n    }\n\n    fopen_s(&amp;pB, \"Lagoon.hitbox.v3.smc\", \"wb\");\n    fwrite(dataB.data(), 1, dataB.size(), pB);\n    fclose(pB);\n\n}<\/code><\/pre>\n\n\n\n<p>Running this patching program yields the table<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Facing  \tRaw data\t\t\tPlain hex offsets\t\tSigned dec offsets\t\n------\t\t--------\t\t\t-----------------\t\t------------------\nRight\t\t00 00 4B 00 F0 FF 10 00\t\t0h, 4Bh, FFF0h, 10h\t\t0, 75, -16, 16\t\t\t\nDown\t\tE0 FF 20 00 00 00 2D 00\t\tFFE0h, 20h, 0, 2Dh\t\t-32, 32, 0, 45\t\nLeft\t\tB5 FF 00 00 F0 FF 10 00\t\tFFB5h, 0h, FFF0h, 10h\t\t-75, 0, -16, 16\t\t\t\nUp\t\tE0 FF 20 00 D3 FF 00 00\t\tFFE0h, 20h, FFD3, 0h\t\t-32, 32, -45, 0\t\t<\/code><\/pre>\n\n\n\n<p>Find a convenient, buildable version of patcher here: <a href=\"https:\/\/github.com\/clandrew\/lagoonhitbox\/\">https:\/\/github.com\/clandrew\/lagoonhitbox\/<\/a><\/p>\n\n\n\n<p>You can use the patcher to change the hitboxes as you want. If this concept seems useful then it'd be a good idea to fuss with the values until they yield something desirable. <\/p>\n\n\n\n<p>Note:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>All ROM file offsets are on headerless ROMs.<\/li><li>The hitboxes calculated from the routine described here is used for both talking to NPCs, and combat. While there might be a motive to affect <em>only<\/em> combat, there've also been complaints that the hitboxes when talking to NPCs are too fussy, so YMMV.<\/li><li>If you make the hitboxes obscenely large it can make the game hard to play. For example, if NPCs A and B are standing close to each other, attempting to talk to A might acidentally cause conversation with B.<\/li><\/ul>\n\n\n\n<p>This post is also available in text form here: <\/p>\n\n\n\n<p><a href=\"https:\/\/raw.githubusercontent.com\/clandrew\/lagoonhitbox\/master\/lagoon_patch_info.txt\">https:\/\/raw.githubusercontent.com\/clandrew\/lagoonhitbox\/master\/lagoon_patch_info.txt<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post explains Lagoon_hitbox.ips, a proof-of-concept patch created to enlarge the hitboxes in the game Lagoon for SNES. The patch was really quick+and+dirty. Nonetheless it&#8217;s posted here: http:\/\/secretplace.cml-a.com\/edits.php What follows is a description of the patch and how it works. First, here are some useful memory locations In Lagoon, the following code is invoked whenever [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[91,150,152,176,178],"class_list":["post-1934","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-lagoon","tag-retro","tag-reverse-engineering","tag-snes","tag-software-development-project"],"_links":{"self":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts\/1934","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/comments?post=1934"}],"version-history":[{"count":0,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/posts\/1934\/revisions"}],"wp:attachment":[{"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/media?parent=1934"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/categories?post=1934"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cml-a.com\/content\/wp-json\/wp\/v2\/tags?post=1934"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}