Leaving ZKR starting fort through the south gate (by abnormal means) and traveling a few steps south leads to a crash #240

Closed
opened 2020-01-19 20:16:46 +00:00 by x-qq · 13 comments
x-qq commented 2020-01-19 20:16:46 +00:00 (Migrated from github.com)

The issue was identifies as trying to check if the tile has a road in a next sector to the south.

Save attached - move 1 tile south.

zkr_road_check_crash.exg.zip

Backtrace:

Thread 1 "Blades of Exile" received signal SIGSEGV, Segmentation fault.
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000EA9  RBX: 0x000000000000000F  RCX: 0x0000000000000000  RDX: 0x000000000000000E  o d I t s z a P c 
  RSI: 0x00000000000000C1  RDI: 0x000055555598AE20  RBP: 0x00007FFFFFFFA580  RSP: 0x00007FFFFFFFA540  RIP: 0x000055555570D6A0
  R8 : 0x000000000000000F  R9 : 0x0000555556BF01E0  R10: 0x0000000000000006  R11: 0x000055555652FD50  R12: 0x000055555557CE10
  R13: 0x0000000000000000  R14: 0x0000000000000000  R15: 0x0000000000000000
  CS: 0033  DS: 0000  ES: 0000  FS: 0000  GS: 0000  SS: 002B				
-----------------------------------------------------------------------------------------------------------------------[code]
=> 0x55555570d6a0 <cCurOut::is_road(int, int)+192>:	movzx  eax,BYTE PTR [rax]
   0x55555570d6a3 <cCurOut::is_road(int, int)+195>:	add    rsp,0x38
   0x55555570d6a7 <cCurOut::is_road(int, int)+199>:	pop    rbx
   0x55555570d6a8 <cCurOut::is_road(int, int)+200>:	pop    rbp
   0x55555570d6a9 <cCurOut::is_road(int, int)+201>:	ret    
   0x55555570d6aa <cUniverse::cUniverse(long)>:	push   rbp
   0x55555570d6ab <cUniverse::cUniverse(long)+1>:	mov    rbp,rsp
   0x55555570d6ae <cUniverse::cUniverse(long)+4>:	push   rbx
-----------------------------------------------------------------------------------------------------------------------------
0x000055555570d6a0 in cCurOut::is_road (this=0x55555599fa98 <univ+87256>, x=0xe, y=0x0) at src/universe/universe.cpp:904
904		return univ.scenario.outdoors[sector_x][sector_y]->roads[x][y];
gdb$ bt full
#0  0x000055555570d6a0 in cCurOut::is_road (this=0x55555599fa98 <univ+87256>, x=0xe, y=0x0) at src/universe/universe.cpp:904
        sector_x = 0x0
        sector_y = 0xf
#1  0x0000555555602669 in draw_terrain (mode=0x0) at build/obj/game/boe.graphics.cpp:901
        r = 0x8
        q = 0x0
        where_draw = {x = 0xe, y = 0x30}
        sector_p_in = {x = 0x0, y = 0xe}
        view_loc = {x = 0x0, y = 0x0}
        can_draw = 0x1
        spec_terrain = 0x0
        off_terrain = 0x0
        draw_frills = 0x1
        frills_on = 0x1
#2  0x00005555555848f3 in handle_action (event=...) at build/obj/game/boe.actions.cpp:1420
        s1 = 0x0
        s2 = 0x0
        s3 = 0x0
        item_hit = 0x0
        are_done = 0x0
        need_redraw = 0x1
        did_something = 0x1
        need_reprint = 0x0
        pc_delayed = 0x0
        cur_loc = {x = 0x12, y = 0x2b}
        loc_in_sec = {x = 0x0, y = 0x0}
        cur_direction = {x = 0x0, y = 0x1}
        button_hit = 0xc
        right_button = 0x0
        previous_mode = 3208677224
        world_screen = {top = 0x14, left = 0x20, bottom = 0x159, right = 0x11d}
        str = <incomplete type>
        the_point = {x = 0x95, y = 0xd7}
        point_in_area = {x = 0x0, y = 0x0}
#3  0x00005555555860ff in handle_keystroke (event=...) at build/obj/game/boe.actions.cpp:1680
        i = 0x2
        are_done = 0x0
        pass_point = {x = 0x12b, y = 0x1ae}
        sout = <incomplete type>
        keypad = {sf::Keyboard::Numpad0, sf::Keyboard::Numpad1, sf::Keyboard::Numpad2, sf::Keyboard::Numpad3, sf::Keyboard::Numpad4, sf::Keyboard::Numpad5, sf::Keyboard::Numpad6, sf::Keyboard::Numpad7, sf::Keyboard::Numpad8, sf::Keyboard::Numpad9}
        terrain_click = {{x = 0x96, y = 0xb9}, {x = 0x78, y = 0xd7}, {x = 0x96, y = 0xd7}, {x = 0xb4, y = 0xd7}, {x = 0x78, y = 0xb9}, {x = 0x96, y = 0xb9}, {x = 0xb4, y = 0xb9}, {x = 0x78, y = 0x9b}, {x = 0x96, y = 0x9b}, {x = 0xb4, y = 0x87}}
        talk_chars = {sf::Keyboard::L, sf::Keyboard::N, sf::Keyboard::J, sf::Keyboard::B, sf::Keyboard::S, sf::Keyboard::R, sf::Keyboard::D, sf::Keyboard::G, sf::Keyboard::A}
        shop_chars = {sf::Keyboard::A, sf::Keyboard::B, sf::Keyboard::C, sf::Keyboard::D, sf::Keyboard::E, sf::Keyboard::F, sf::Keyboard::G, sf::Keyboard::H}
        chr2 = sf::Keyboard::Numpad2
        pass_event = {type = sf::Event::MouseButtonPressed, {size = {width = 0x0, height = 0x12b}, key = {code = sf::Keyboard::A, alt = 0x2b, control = 0x1, shift = 0x0, system = 0x0}, text = {unicode = 0x0}, mouseMove = {x = 0x0, y = 0x12b}, mouseButton = {button = sf::Mouse::Left, x = 0x12b, y = 0x1ae}, mouseWheel = {delta = 0x0, x = 0x12b, y = 0x1ae}, mouseWheelScroll = {wheel = sf::Mouse::VerticalWheel, delta = 4.18988241e-43, x = 0x1ae, y = 0x0}, joystickMove = {joystickId = 0x0, axis = 299, position = 6.0255834e-43}, joystickButton = {joystickId = 0x0, button = 0x12b}, joystickConnect = {joystickId = 0x0}, touch = {finger = 0x0, x = 0x12b, y = 0x1ae}, sensor = {type = sf::Sensor::Accelerometer, x = 4.18988241e-43, y = 6.0255834e-43, z = 0}}}
        chr = 0xa2
#4  0x00005555556336e1 in Handle_One_Event () at build/obj/game/boe.main.cpp:257
        twentyTicks = 0x14d
        fortyTicks = 0x29a
#5  0x0000555555632bc0 in main (argc=0x1, argv=0x7fffffffde78) at build/obj/game/boe.main.cpp:137
        remaining_time_budget_in_us = 0x38bc
        framerate_clock = {m_startTime = {static Zero = {static Zero = <same as static member of an already seen type>, m_microseconds = 0x0}, m_microseconds = 0x2884dd32b}}
        desired_microseconds_per_frame = 0x411a
gdb$
The issue was identifies as trying to check if the tile has a road in a next sector to the south. Save attached - move 1 tile south. [zkr_road_check_crash.exg.zip](https://github.com/calref/cboe/files/4083510/zkr_road_check_crash.exg.zip) Backtrace: ``` Thread 1 "Blades of Exile" received signal SIGSEGV, Segmentation fault. -----------------------------------------------------------------------------------------------------------------------[regs] RAX: 0x0000000000000EA9 RBX: 0x000000000000000F RCX: 0x0000000000000000 RDX: 0x000000000000000E o d I t s z a P c RSI: 0x00000000000000C1 RDI: 0x000055555598AE20 RBP: 0x00007FFFFFFFA580 RSP: 0x00007FFFFFFFA540 RIP: 0x000055555570D6A0 R8 : 0x000000000000000F R9 : 0x0000555556BF01E0 R10: 0x0000000000000006 R11: 0x000055555652FD50 R12: 0x000055555557CE10 R13: 0x0000000000000000 R14: 0x0000000000000000 R15: 0x0000000000000000 CS: 0033 DS: 0000 ES: 0000 FS: 0000 GS: 0000 SS: 002B -----------------------------------------------------------------------------------------------------------------------[code] => 0x55555570d6a0 <cCurOut::is_road(int, int)+192>: movzx eax,BYTE PTR [rax] 0x55555570d6a3 <cCurOut::is_road(int, int)+195>: add rsp,0x38 0x55555570d6a7 <cCurOut::is_road(int, int)+199>: pop rbx 0x55555570d6a8 <cCurOut::is_road(int, int)+200>: pop rbp 0x55555570d6a9 <cCurOut::is_road(int, int)+201>: ret 0x55555570d6aa <cUniverse::cUniverse(long)>: push rbp 0x55555570d6ab <cUniverse::cUniverse(long)+1>: mov rbp,rsp 0x55555570d6ae <cUniverse::cUniverse(long)+4>: push rbx ----------------------------------------------------------------------------------------------------------------------------- 0x000055555570d6a0 in cCurOut::is_road (this=0x55555599fa98 <univ+87256>, x=0xe, y=0x0) at src/universe/universe.cpp:904 904 return univ.scenario.outdoors[sector_x][sector_y]->roads[x][y]; gdb$ bt full #0 0x000055555570d6a0 in cCurOut::is_road (this=0x55555599fa98 <univ+87256>, x=0xe, y=0x0) at src/universe/universe.cpp:904 sector_x = 0x0 sector_y = 0xf #1 0x0000555555602669 in draw_terrain (mode=0x0) at build/obj/game/boe.graphics.cpp:901 r = 0x8 q = 0x0 where_draw = {x = 0xe, y = 0x30} sector_p_in = {x = 0x0, y = 0xe} view_loc = {x = 0x0, y = 0x0} can_draw = 0x1 spec_terrain = 0x0 off_terrain = 0x0 draw_frills = 0x1 frills_on = 0x1 #2 0x00005555555848f3 in handle_action (event=...) at build/obj/game/boe.actions.cpp:1420 s1 = 0x0 s2 = 0x0 s3 = 0x0 item_hit = 0x0 are_done = 0x0 need_redraw = 0x1 did_something = 0x1 need_reprint = 0x0 pc_delayed = 0x0 cur_loc = {x = 0x12, y = 0x2b} loc_in_sec = {x = 0x0, y = 0x0} cur_direction = {x = 0x0, y = 0x1} button_hit = 0xc right_button = 0x0 previous_mode = 3208677224 world_screen = {top = 0x14, left = 0x20, bottom = 0x159, right = 0x11d} str = <incomplete type> the_point = {x = 0x95, y = 0xd7} point_in_area = {x = 0x0, y = 0x0} #3 0x00005555555860ff in handle_keystroke (event=...) at build/obj/game/boe.actions.cpp:1680 i = 0x2 are_done = 0x0 pass_point = {x = 0x12b, y = 0x1ae} sout = <incomplete type> keypad = {sf::Keyboard::Numpad0, sf::Keyboard::Numpad1, sf::Keyboard::Numpad2, sf::Keyboard::Numpad3, sf::Keyboard::Numpad4, sf::Keyboard::Numpad5, sf::Keyboard::Numpad6, sf::Keyboard::Numpad7, sf::Keyboard::Numpad8, sf::Keyboard::Numpad9} terrain_click = {{x = 0x96, y = 0xb9}, {x = 0x78, y = 0xd7}, {x = 0x96, y = 0xd7}, {x = 0xb4, y = 0xd7}, {x = 0x78, y = 0xb9}, {x = 0x96, y = 0xb9}, {x = 0xb4, y = 0xb9}, {x = 0x78, y = 0x9b}, {x = 0x96, y = 0x9b}, {x = 0xb4, y = 0x87}} talk_chars = {sf::Keyboard::L, sf::Keyboard::N, sf::Keyboard::J, sf::Keyboard::B, sf::Keyboard::S, sf::Keyboard::R, sf::Keyboard::D, sf::Keyboard::G, sf::Keyboard::A} shop_chars = {sf::Keyboard::A, sf::Keyboard::B, sf::Keyboard::C, sf::Keyboard::D, sf::Keyboard::E, sf::Keyboard::F, sf::Keyboard::G, sf::Keyboard::H} chr2 = sf::Keyboard::Numpad2 pass_event = {type = sf::Event::MouseButtonPressed, {size = {width = 0x0, height = 0x12b}, key = {code = sf::Keyboard::A, alt = 0x2b, control = 0x1, shift = 0x0, system = 0x0}, text = {unicode = 0x0}, mouseMove = {x = 0x0, y = 0x12b}, mouseButton = {button = sf::Mouse::Left, x = 0x12b, y = 0x1ae}, mouseWheel = {delta = 0x0, x = 0x12b, y = 0x1ae}, mouseWheelScroll = {wheel = sf::Mouse::VerticalWheel, delta = 4.18988241e-43, x = 0x1ae, y = 0x0}, joystickMove = {joystickId = 0x0, axis = 299, position = 6.0255834e-43}, joystickButton = {joystickId = 0x0, button = 0x12b}, joystickConnect = {joystickId = 0x0}, touch = {finger = 0x0, x = 0x12b, y = 0x1ae}, sensor = {type = sf::Sensor::Accelerometer, x = 4.18988241e-43, y = 6.0255834e-43, z = 0}}} chr = 0xa2 #4 0x00005555556336e1 in Handle_One_Event () at build/obj/game/boe.main.cpp:257 twentyTicks = 0x14d fortyTicks = 0x29a #5 0x0000555555632bc0 in main (argc=0x1, argv=0x7fffffffde78) at build/obj/game/boe.main.cpp:137 remaining_time_budget_in_us = 0x38bc framerate_clock = {m_startTime = {static Zero = {static Zero = <same as static member of an already seen type>, m_microseconds = 0x0}, m_microseconds = 0x2884dd32b}} desired_microseconds_per_frame = 0x411a gdb$ ```
CelticMinstrel commented 2020-01-19 20:26:32 +00:00 (Migrated from github.com)

More precisely, it's trying to access the next sector to the south, which doesn't exist. We either need to enable "dummy" values if the view scrolls off the edge of the map, or have some special case so that it doesn't scroll at all when you're at the edge.

More precisely, it's trying to access the next sector to the south, which doesn't exist. We either need to enable "dummy" values if the view scrolls off the edge of the map, or have some special case so that it doesn't scroll at all when you're at the edge.
retropipes commented 2020-01-19 22:48:17 +00:00 (Migrated from github.com)

You could also use a system like what Realmz does here: setting invalid map sectors to ID -1, and checking for that ID before attempting to load an adjacent section. If there’s no sector there, don’t scroll the map past what would be the transition point.

You could also use a system like what Realmz does here: setting invalid map sectors to ID -1, and checking for that ID before attempting to load an adjacent section. If there’s no sector there, don’t scroll the map past what would be the transition point.
CelticMinstrel commented 2020-01-20 04:09:46 +00:00 (Migrated from github.com)

I think that's basically the sort of idea I meant here:

have some special case so that it doesn't scroll at all when you're at the edge.

The other method is probably easier, but then you'd see blackness or something beyond the edge. On the other hand, that might be okay - scenarios are supposed to be structured so you can't get that close, anyway. As long as it doesn't crash…

I think that's basically the sort of idea I meant here: > have some special case so that it doesn't scroll at all when you're at the edge. The other method is probably easier, but then you'd see blackness or something beyond the edge. On the other hand, that might be okay - scenarios are supposed to be structured so you can't get that close, anyway. As long as it doesn't crash…
NQNStudios commented 2024-06-18 19:14:52 +00:00 (Migrated from github.com)

Loading this save file and moving south in the current build, I don't get a crash.

Loading this save file and moving south in the current build, I don't get a crash.
CelticMinstrel commented 2024-06-24 12:23:50 +00:00 (Migrated from github.com)

Responding to what you said on IRC…

[01:45am] NQNStudios: celticminstrel did outdoor sections used to be 96x96 tiles?
[01:46am] NQNStudios: I reproduced this bug by making a scenario with no walls blocking the south boundary https://github.com/calref/cboe/issues/240
[01:46am] NQNStudios: it's boundary checking for 96, but the outdoor section is actually 48x48
[02:02am] NQNStudios: ohhhhh would it be 96 because multiple sections could be in the vector it's checking?

The original game loads 4 outdoor sections into a 96x96 combined grid, shifting them as you move around the outdoors. I don't think I remember altering that aspect.

Responding to what you said on IRC… > [01:45am] [NQNStudios](member:identifier:nqnstudios): [celticminstrel](member:celticminstrel) did outdoor sections used to be 96x96 tiles? > [01:46am] [NQNStudios](member:identifier:nqnstudios): I reproduced this bug by making a scenario with no walls blocking the south boundary https://github.com/calref/cboe/issues/240 > [01:46am] [NQNStudios](member:identifier:nqnstudios): it's boundary checking for 96, but the outdoor section is actually 48x48 > [02:02am] [NQNStudios](member:identifier:nqnstudios): ohhhhh would it be 96 because multiple sections could be in the vector it's checking? The original game loads 4 outdoor sections into a 96x96 combined grid, shifting them as you move around the outdoors. I don't _think_ I remember altering that aspect.
NQNStudios commented 2024-08-03 17:37:46 +00:00 (Migrated from github.com)

I'm working on a replay test case in a custom scenario that takes the party to all 4 world boundaries of the outdoors, and also in all 3 town sizes.

In an outdoors with only one tile, none of the 4 boundaries cause a crash when you get too close (on Mac). Weirdly, however, the cave tiles repeat themselves at the southern and eastern borders, but you get blackness at the north and west.

In a 3x3 outdoors, the behavior is the same (on Mac).

All 3 town sizes work at the boundaries as well.

This is really weird, because on Windows or Linux (don't remember which) I was definitely getting crashes at the boundaries while investigating this issue a few weeks ago. Maybe it's implementation-dependent on out-of-bounds access behavior?

I'm working on a replay test case in a custom scenario that takes the party to all 4 world boundaries of the outdoors, and also in all 3 town sizes. In an outdoors with only one tile, none of the 4 boundaries cause a crash when you get too close (on Mac). Weirdly, however, the cave tiles repeat themselves at the southern and eastern borders, but you get blackness at the north and west. In a 3x3 outdoors, the behavior is the same (on Mac). All 3 town sizes work at the boundaries as well. This is really weird, because on Windows or Linux (don't remember which) I was definitely getting crashes at the boundaries while investigating this issue a few weeks ago. Maybe it's implementation-dependent on out-of-bounds access behavior?
NQNStudios commented 2024-08-03 18:45:37 +00:00 (Migrated from github.com)

That was correct -- the replay does crash on Windows.

That was correct -- the replay does crash on Windows.
CelticMinstrel commented 2024-08-03 18:59:25 +00:00 (Migrated from github.com)

There's supposed to be a special message when you try to travel out-of-bounds – instead of moving the party, the game writes something to the message buffer. I don't recall what the exact message was, but I definitely remember seeing it in one of my own custom scenarios running on vanilla BoE. The message is probably still in the source code somewhere.

There's supposed to be a special message when you try to travel out-of-bounds – instead of moving the party, the game writes something to the message buffer. I don't recall what the exact message was, but I definitely remember seeing it in one of my own custom scenarios running on vanilla BoE. The message is probably still in the source code somewhere.
NQNStudios commented 2024-08-03 18:59:58 +00:00 (Migrated from github.com)

That message is what happens on Mac 👍

That message is what happens on Mac :+1:
CelticMinstrel commented 2024-08-03 19:07:37 +00:00 (Migrated from github.com)

Okay, so there's some issue that prevents the message from triggering on Windows for some reason?

Okay, so there's some issue that prevents the message from triggering on Windows for some reason?
NQNStudios commented 2024-08-03 19:11:51 +00:00 (Migrated from github.com)

I think on Windows, before the party gets to try to move out of bounds, draw_terrain() is trying to check tiles that are out of bounds. On Mac, this out-of-bounds access is for some reason returning safe values that the program then runs with, but on windows, it's throwing an error (as it should) (this is a case of undefined C++ stdlib behavior)

I think on Windows, before the party gets to try to move out of bounds, draw_terrain() is trying to check tiles that are out of bounds. On Mac, this out-of-bounds access is for some reason returning safe values that the program then runs with, but on windows, it's throwing an error (as it should) (this is a case of undefined C++ stdlib behavior)
NQNStudios commented 2024-08-03 19:13:32 +00:00 (Migrated from github.com)

The fix will be to detect the out-of-bounds access before it happens, and supply the safe value on purpose

The fix will be to detect the out-of-bounds access *before* it happens, and supply the safe value on purpose
CelticMinstrel commented 2024-08-03 19:21:03 +00:00 (Migrated from github.com)

Weirdly, however, the cave tiles repeat themselves at the southern and eastern borders, but you get blackness at the north and west.

I wonder if this is related to the same issue.

> Weirdly, however, the cave tiles repeat themselves at the southern and eastern borders, but you get blackness at the north and west. I wonder if this is related to the same issue.
Sign in to join this conversation.
No description provided.