bruvzg / gdsdecomp

Godot reverse engineering tools
MIT License
1.54k stars 146 forks source link

Crash in ImageExporter::_convert_tex when decompiling Psychopomp Gold #202

Open Alex-gnzl opened 5 days ago

Alex-gnzl commented 5 days ago

System information

Arch Linux, 6.11

Issue description

A certain asset (assets?) in Psychopomp Gold causes the engine to abort

GDB trace from own build:

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#1  0x00007ffff79b6463 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:78
#2  0x00007ffff795d120 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3  0x00007ffff79444c3 in __GI_abort () at abort.c:79
#4  0x00007ffff7945354 in __libc_message_impl (fmt=fmt@entry=0x7ffff7ad32f5 "%s\n") at ../sysdeps/posix/libc_fatal.c:132
#5  0x00007ffff79c0765 in malloc_printerr (str=str@entry=0x7ffff7ad6370 "free(): corrupted unsorted chunks") at malloc.c:5772
#6  0x00007ffff79c163c in _int_free_create_chunk (av=av@entry=0x7ffff7b07ac0 <main_arena>, p=p@entry=0x55555e1205c0, size=<optimized out>, size@entry=131120, 
    nextchunk=nextchunk@entry=0x55555e1405f0, nextsize=nextsize@entry=368) at malloc.c:4735
#7  0x00007ffff79c297a in _int_free_merge_chunk (av=av@entry=0x7ffff7b07ac0 <main_arena>, p=0x55555e1205c0, size=131120) at malloc.c:4700
#8  0x00007ffff79c2cfa in _int_free (av=0x7ffff7b07ac0 <main_arena>, p=p@entry=0x55555e1205c0, have_lock=<optimized out>, have_lock@entry=0) at malloc.c:4646
#9  0x00007ffff79c55ce in __GI___libc_free (mem=0x55555e1205d0) at malloc.c:3398
#10 0x0000555558816029 in CowData<unsigned char>::_unref (this=0x55555d88b558) at ./core/templates/cowdata.h:270
#11 CowData<unsigned char>::_ref (this=0x55555d88b558, p_from=...) at ./core/templates/cowdata.h:465
#12 Vector<unsigned char>::operator= (this=0x55555d88b550, p_from=...) at ./core/templates/vector.h:148
#13 Image::initialize_data (this=0x55555d88b350, p_width=384, p_height=128, p_use_mipmaps=true, p_format=Image::FORMAT_RGBA8, p_data=...) at core/io/image.cpp:2310
#14 0x0000555555b0ee65 in image_decompress_bcdec (p_image=0x55555d88b350) at modules/bcdec/image_decompress_bcdec.cpp:181
#15 0x0000555558815dc4 in Image::decompress (this=0x7da6c) at core/io/image.cpp:2711
#16 0x0000555555e624e1 in ImportExporter::_convert_tex (this=this@entry=0x55555e04bfd0, output_dir=..., p_path=..., p_dst=..., lossy=false)
    at modules/gdsdecomp/utility/import_exporter.cpp:1250
#17 0x0000555555e559cf in ImportExporter::export_texture (this=this@entry=0x55555e04bfd0, output_dir=..., iinfo=...) at modules/gdsdecomp/utility/import_exporter.cpp:1122
#18 0x0000555555e50afe in ImportExporter::_export_imports (this=0x55555e04bfd0, p_out_dir=..., files_to_export=..., pr=0x0, error_string=...)
    at modules/gdsdecomp/utility/import_exporter.cpp:260
#19 0x0000555555e71b64 in call_with_validated_variant_args_ret_helper<__UnexistingClass, Error, String const&, Vector<String> const&, 0ul, 1ul> (p_instance=<optimized out>, 
    p_method=<optimized out>, p_args=<optimized out>, r_ret=<optimized out>) at ./core/variant/binder_common.h:375
#20 call_with_validated_object_instance_args_ret<__UnexistingClass, Error, String const&, Vector<String> const&> (base=<optimized out>, p_method=<optimized out>, 
    p_args=<optimized out>, r_ret=<optimized out>) at ./core/variant/binder_common.h:662
#21 MethodBindTR<Error, String const&, Vector<String> const&>::validated_call (this=<optimized out>, p_object=<optimized out>, p_args=<optimized out>, r_ret=0x7fffffffa058)
    at ./core/object/method_bind.h:536
#22 0x0000555555d25724 in GDScriptFunction::call (this=0x55555b982270, p_instance=0x55555b7e2b80, p_args=<optimized out>, p_argcount=<optimized out>, r_err=..., p_state=0x0)
    at modules/gdscript/gdscript_vm.cpp:2246
#23 0x0000555555bee46b in GDScriptInstance::callp (this=0x55555b7e2b80, p_method=..., p_args=0x7fffffffade0, p_argcount=2, r_error=...) at modules/gdscript/gdscript.cpp:2040
#24 0x0000555558caf297 in Object::callp (this=0x55555b7a36f0, p_method=..., p_args=0x7fffffffade0, p_argcount=2, r_error=...) at core/object/object.cpp:791
#25 0x00005555589ac0ea in Variant::callp (this=<optimized out>, p_method=..., p_args=0x7ffff79b63f4 <__pthread_kill_implementation+276>, p_argcount=2, r_ret=..., r_error=...)
    at core/variant/variant_call.cpp:1227
#26 0x0000555555d1fa83 in GDScriptFunction::call (this=0x55555b987550, p_instance=0x55555b7e2b80, p_args=<optimized out>, p_argcount=<optimized out>, r_err=..., p_state=0x0)
    at modules/gdscript/gdscript_vm.cpp:1920
#27 0x0000555555bee46b in GDScriptInstance::callp (this=0x55555b7e2b80, p_method=..., p_args=0x7fffffffbb98, p_argcount=7, r_error=...) at modules/gdscript/gdscript.cpp:2040
#28 0x0000555558caf297 in Object::callp (this=0x55555b7a36f0, p_method=..., p_args=0x7fffffffbb98, p_argcount=7, r_error=...) at core/object/object.cpp:791
#29 0x00005555589ac0ea in Variant::callp (this=<optimized out>, p_method=..., p_args=0x7ffff79b63f4 <__pthread_kill_implementation+276>, p_argcount=7, r_ret=..., r_error=...)
--Type <RET> for more, q to quit, c to continue without paging--c
    at core/variant/variant_call.cpp:1227
#30 0x0000555555d1fa83 in GDScriptFunction::call (this=0x55555b98e370, p_instance=0x55555b7e2b80, p_args=<optimized out>, p_argcount=<optimized out>, r_err=..., p_state=0x0)
    at modules/gdscript/gdscript_vm.cpp:1920
#31 0x0000555555bee46b in GDScriptInstance::callp (this=0x55555b7e2b80, p_method=..., p_args=0x7fffffffc768, p_argcount=1, r_error=...) at modules/gdscript/gdscript.cpp:2040
#32 0x0000555558caf297 in Object::callp (this=0x55555b7a36f0, p_method=..., p_args=0x7fffffffc768, p_argcount=1, r_error=...) at core/object/object.cpp:791
#33 0x00005555589ac0ea in Variant::callp (this=<optimized out>, p_method=..., p_args=0x7ffff79b63f4 <__pthread_kill_implementation+276>, p_argcount=1, r_ret=..., r_error=...)
    at core/variant/variant_call.cpp:1227
#34 0x0000555555d1fac4 in GDScriptFunction::call (this=0x55555b97dbc0, p_instance=0x55555b7e2b80, p_args=<optimized out>, p_argcount=<optimized out>, r_err=..., p_state=0x0)
    at modules/gdscript/gdscript_vm.cpp:1890
#35 0x0000555555bee46b in GDScriptInstance::callp (this=0x55555b7e2b80, p_method=..., p_args=0x0, p_argcount=0, r_error=...) at modules/gdscript/gdscript.cpp:2040
#36 0x0000555556d81527 in Node::_gdvirtual__ready_call (this=0x55555b7a36f0) at scene/main/node.h:384
#37 Node::_notification (this=0x55555b7a36f0, p_notification=<optimized out>) at scene/main/node.cpp:224
#38 0x0000555556f40420 in Node::_notificationv (this=<optimized out>, p_notification=<optimized out>, p_reversed=<optimized out>) at ./scene/main/node.h:50
#39 CanvasItem::_notificationv (this=<optimized out>, p_notification=<optimized out>, p_reversed=<optimized out>) at ./scene/main/canvas_item.h:45
#40 Control::_notificationv (this=0x55555b7a36f0, p_notification=13, p_reversed=false) at scene/gui/control.h:47
#41 0x0000555558cacec4 in Object::notification (this=0x55555b7a36f0, p_notification=13, p_reversed=false) at core/object/object.cpp:875
#42 0x0000555556d8411d in Node::_propagate_ready (this=0x55555b7a36f0) at scene/main/node.cpp:279
#43 0x0000555556d840c9 in Node::_propagate_ready (this=0x55555b69d1a0) at scene/main/node.cpp:270
#44 0x0000555556d8a3f9 in Node::_set_tree (this=0x55555b69d1a0, p_tree=0x55555b69ccd0) at scene/main/node.cpp:3245
#45 0x00005555559c46dc in OS_LinuxBSD::run (this=0x7fffffffd290) at platform/linuxbsd/os_linuxbsd.cpp:950
#46 0x00005555559ba36e in main (argc=<optimized out>, argv=<optimized out>) at platform/linuxbsd/godot_linuxbsd.cpp:85

(First?) asset that causes this: res://Sprites/Items/ParagonKeyAnim.png

Steps to reproduce

Try to recover Psychopomp Gold.exe using either the Windows or Linux version of gdsdecomp

Recovery log

N/A

Minimal reproduction project

https://store.steampowered.com/app/3243190/Psychopomp_GOLD/

I wish I could provide an example texture that causes this, but I'm not sure what settings result in this happening.

nikitalita commented 4 days ago

To be clear, you're building from the current gdsdecomp master with Godot @ the commit listed in the readme, correct?

Alex-gnzl commented 4 days ago

Yes, master with the particular commit -- it also seems to happen with the release and (at the time of writing) latest Actions build.

nikitalita commented 4 days ago

Ah, heck, I thought this was fixed in the godot engine. Lemme guess, that particular texture is 1x1 pixels, right?

Alex-gnzl commented 3 days ago

I initially thought so too after searching for a bit, but The readme commit is after that fix: (modules/bcdec/image_decompress_bcdec.cpp:98)

        // Compressed images' dimensions should be padded to the upper multiple of 4.
        // If they aren't, they need to be realigned (the actual data is correctly padded though).
        if (width % 4 != 0 || height % 4 != 0) {
                int new_width = width + (4 - (width % 4));
                int new_height = height + (4 - (height % 4));

                print_verbose(vformat("Compressed image's dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", width, height, new_width, new_height));

                width = new_width;
                height = new_height;
        }

The stack trace suggests it's 384x128:

#13 Image::initialize_data (this=0x55555d883450, p_width=384, p_height=128, p_use_mipmaps=true, p_format=Image::FORMAT_RGBA8, p_data=...) at core/io/image.cpp:2310
2310            data = p_data;

Which checks out because it's a padded up 381x127 (quickly tossed into the Editor to check): image

Alex-gnzl commented 3 days ago

I had a random thought and cleared mipmaps in image_decompress_bcdec before it gets to them and that made it work!
Something's wrong with the mipmaps -- could it be related to the <4 width/height thing; or did something change in Godot (I believe the game uses 4.2.1? not sure)?

nikitalita commented 3 days ago

This is definitely a bug in Godot. I would recommend filing an issue that cross-references https://github.com/godotengine/godot/issues/97862 and https://github.com/godotengine/godot/pull/97873.

Protip: mention that it causes a heap buffer overflow in the title. That always gets their attention.

octogeckoyhtgyrgfvfthhyfhfdyh commented 15 hours ago

How do I do this? Clear mipmaps, I mean.

nikitalita commented 2 hours ago

I got my hands on Psychopomp Gold and attempted to recreate this to no avail. I think I might have fixed it on the latest PR? Could you try the artifacts here and see if you still have the problem? https://github.com/bruvzg/gdsdecomp/actions/runs/11611147300?pr=208